Compare commits

..

1 Commits

Author SHA1 Message Date
damencho
ff6f0ee949 debug 2025-10-21 14:48:16 -05:00
207 changed files with 1886 additions and 2567 deletions

View File

@@ -14,12 +14,3 @@ trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[*.{java,kt}]
indent_size = 4
[*.xml]
indent_size = 2
[*.{swift,m,mm,h}]
indent_size = 4

View File

@@ -1,12 +0,0 @@
version: 2
updates:
# Monitor GitHub Actions versions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "chore(ci)"

View File

@@ -7,7 +7,7 @@ jobs:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install luarocks
run: sudo apt-get --install-recommends -y install luarocks

View File

@@ -7,8 +7,8 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -42,8 +42,8 @@ jobs:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -59,8 +59,8 @@ jobs:
name: Build mobile bundle (Android)
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -74,8 +74,8 @@ jobs:
name: Build mobile bundle (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -103,10 +103,10 @@ jobs:
android-sdk-build:
name: Build mobile SDK (Android)
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android:v15.0
container: reactnativecommunity/react-native-android:v18.0
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -123,8 +123,8 @@ jobs:
name: Build mobile SDK (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -173,8 +173,8 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@v8
with:
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'

View File

@@ -37,6 +37,7 @@ public class JitsiMeetActivityDelegate {
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* Tells whether or not the permissions request is currently in progress.
@@ -141,6 +142,11 @@ public class JitsiMeetActivityDelegate {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
if (permissionsCallback != null) {
permissionsCallback.invoke();
permissionsCallback = null;
}
}
/**
@@ -163,10 +169,15 @@ public class JitsiMeetActivityDelegate {
public static void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
// Invoke the callback immediately
if (permissionListener != null && permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
permissionsCallback = new Callback() {
@Override
public void invoke(Object... args) {
if (permissionListener != null
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
}
};
}
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {

View File

@@ -99,7 +99,6 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
public static void launch(Context context, HashMap<String, Object> extraData) {
List<String> permissionsList = new ArrayList<>();
Activity activity = (Activity) context;
PermissionListener listener = new PermissionListener() {
@Override
@@ -135,7 +134,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
if (permissionsArray.length > 0) {
JitsiMeetActivityDelegate.requestPermissions(
activity,
(Activity) context,
permissionsArray,
PERMISSIONS_REQUEST_CODE,
listener
@@ -160,20 +159,12 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
} catch (Exception e) {
// Handle ForegroundServiceStartNotAllowedException when app is in background and cannot start foreground service.
// See: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start#wiu-restrictions
JitsiMeetLogger.w(TAG + " Failed to start foreground service", e);
stopSelf();
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}

View File

@@ -1375,7 +1375,7 @@ export default {
}
APP.store.dispatch(updateRemoteParticipantFeatures(user));
logger.log(`USER ${id} connected`);
logger.log(`USER ${id} connected:`, user);
APP.UI.addUser(user);
});

View File

@@ -139,9 +139,6 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables chat feature entirely including notifications, sounds, and private messages.
// disableChat: false,
// Disables demote button from self-view
// disableSelfDemote: false,
@@ -907,8 +904,6 @@ var config = {
// alwaysVisible: false,
// // Indicates whether the toolbar should still autohide when chat is open
// autoHideWhileChatIsOpen: false,
// // Default background color for the main toolbar. Accepts any valid CSS color.
// // backgroundColor: '#ffffff',
// },
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed

View File

@@ -124,17 +124,10 @@ case "$1" in
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
fi
PROSODY_CREATE_JICOFO_USER="true"
fi
if ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
# on some distributions main prosody config doesn't include configs
# from conf.d folder enable it as this where we put our config by default
# also when upgrading to new prosody version from prosody repo we need to add it again
if ! grep -q "Include \"conf\.d\/\*\.cfg.lua\"" $PROSODY_CONFIG_OLD; then
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
fi
fi

View File

@@ -1484,7 +1484,6 @@
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",

View File

@@ -114,9 +114,6 @@
"error": "Errore: il tuo messaggio non è stato inviato. Motivo: {{error}}",
"everyone": "Tutti",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"fileAccessibleTitle": "{{user}} ha caricato un file",
"fileAccessibleTitleMe": "ho caricato un file",
"fileDeleted": "Un file è stato eliminato",
"guestsChatIndicator": "(ospite)",
"lobbyChatMessageTo": "Messaggio a {{recipient}} in sala d'attesa",
"message": "Messaggio",
@@ -283,6 +280,7 @@
"Submit": "Invia",
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
"WaitForHostMsg": "La riunione non è ancora iniziata. Se sei l'organizzatore, per favore autenticati. Altrimenti, attendi l'arrivo dell'organizzatore.",
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
"WaitingForHostButton": "Attendi l'organizzatore",
"WaitingForHostTitle": "In attesa dell'organizzatore…",
@@ -525,7 +523,6 @@
"tokenAuthFailedWithReasons": "Non sei autorizzato a partecipare a questa chiamata. Possibile motivo: {{reason}}",
"tokenAuthUnsupported": "Il token URL non è supportato.",
"transcribing": "Trascrizione in corso",
"unauthenticatedAccessDisabled": "Questa chiamata richiede l'autenticazione. Si prega di accedere per procedere.",
"unlockRoom": "Rimuovi la $t(lockRoomPassword) alla riunione",
"user": "Utente",
"userIdentifier": "Identificatore utente",
@@ -573,12 +570,10 @@
"downloadStarted": "Download del file iniziato",
"dragAndDrop": "Trascina e rilascia i file qui o da qualsiasi altra parte nella schermata",
"fileAlreadyUploaded": "Questo file è già stato caricato nella riunione.",
"fileRemovedByOther": "Il tuo file '{{ fileName }}' è stato rimosso",
"fileTooLargeDescription": "Assicurati che il file non superi {{ maxFileSize }}.",
"fileTooLargeTitle": "Il file selezionato è troppo grande",
"fileUploadProgress": "Caricamento del file in corso",
"fileUploadedSuccessfully": "Il file è stato caricato con successo",
"newFileNotification": "{{ participantName }} ha condiviso '{{ fileName }}'",
"removeFile": "Rimuovi",
"removeFileSuccess": "File rimosso con successo",
"uploadFailedDescription": "Si prega di riprovare.",
@@ -753,8 +748,7 @@
"notificationTitle": "Sala d'attesa",
"passwordJoinButton": "Entra",
"title": "Sala d'attesa",
"toggleLabel": "Attiva sala d'attesa",
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
"toggleLabel": "Attiva sala d'attesa"
},
"localRecording": {
"clientState": {
@@ -970,9 +964,6 @@
"by": "Da {{ name }}",
"closeButton": "Chiudi sondaggio",
"create": {
"accessibilityLabel": {
"send": "Invia sondaggio"
},
"addOption": "Aggiungi opzione",
"answerPlaceholder": "Opzione {{index}}",
"cancel": "Annulla",
@@ -981,7 +972,8 @@
"pollQuestion": "Domanda del sondaggio",
"questionPlaceholder": "Fai una domanda",
"removeOption": "Elimina opzione",
"save": "Salva"
"save": "Salva",
"send": "Invia"
},
"errors": {
"notUniqueOption": "Le opzioni devono essere uniche"
@@ -1625,8 +1617,6 @@
"noMainParticipantsTitle": "La riunione non è ancora iniziata.",
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
"requestToJoin": "Mano alzata",
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
"title": "Sei uno spettatore nella riunione"
},
"waitingMessage": "Ti unirai alla riunione quando inizierà!"

View File

@@ -114,9 +114,6 @@
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
"everyone": "Visi",
"fieldPlaceHolder": "Rakstiet ziņu šeit",
"fileAccessibleTitle": "{{user}} augšuplādēja failu",
"fileAccessibleTitleMe": "es augšuplādēju failu",
"fileDeleted": "Fails tika dzēsts",
"guestsChatIndicator": "(viesis)",
"lobbyChatMessageTo": "Vestibila tērzēšanas ziņa adresātam {{recipient}}",
"message": "Ziņa",
@@ -126,20 +123,12 @@
"messagebox": "Rakstiet ziņu",
"newMessages": "Jaunas ziņas",
"nickname": {
"featureChat": "tērzētava",
"featureClosedCaptions": "slēgtie subtitri",
"featureFileSharing": "failu kopīgošana",
"featurePolls": "aptaujas",
"popover": "Izvēlieties segvārdu",
"title": "Ierakstiet segvārdu, lai izmantotu tērzēšanu",
"titleWith1Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}",
"titleWith2Features": "Ievadiet segvārdu, lai izmantotu {{feature1}} un {{feature2}}",
"titleWith3Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}} un {{feature3}}",
"titleWith4Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}}, {{feature3}} un {{feature4}}",
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu un slēgtos subtitrus",
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanu un aptaujas",
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas un slēgtos subtitrus",
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas, slēgtos subtitrus un failus"
"popover": "Izvēlieties vārdu",
"title": "Ierakstiet vārdu, lai izmantotu tērzēšanā",
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā un slēptos subtitros",
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanā un aptaujās",
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros",
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās, slēptos subtitros un failos"
},
"noMessagesMessage": "Sapulcē pagaidām nav nevienas ziņas. Uzsāciet saraksti!",
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
@@ -148,12 +137,12 @@
"systemDisplayName": "Sistēma",
"tabs": {
"chat": "Tērzēšana",
"closedCaptions": "Slēgtie subtitri",
"closedCaptions": "Slēptie subtitri",
"fileSharing": "Faili",
"polls": "Aptaujas"
},
"title": "Tērzēšana",
"titleWithCC": "Tērzēšana un Slēgtie subtitri",
"titleWithCC": "Tērzēšana un Slēptie subtitri",
"titleWithFeatures": "Tērzēšana un",
"titleWithFileSharing": "Faili",
"titleWithPolls": "Tērzēšana un Aptaujas",
@@ -167,8 +156,8 @@
"installExtensionText": "Uzstādīt spraudni Google kalendāra un Office 365 integrācijai"
},
"closedCaptionsTab": {
"emptyState": "Slēgto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
"startClosedCaptionsButton": "Uzsākt slēgtos subtitrus"
"emptyState": "Slēpto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
"startClosedCaptionsButton": "Uzsākt slēptos subtitrus"
},
"connectingOverlay": {
"joiningRoom": "Notiek pieslēgšanās jūsu sapulcei…"
@@ -291,6 +280,7 @@
"Submit": "Iesniegt",
"Understand": "Saprotu",
"UnderstandAndUnmute": "Es saprotu, lūdzu, ieslēdziet skaņu.",
"WaitForHostMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, autorizējieties, lai kļūtu par moderatoru. Pretējā gadījumā, lūdzu, uzgaidiet.",
"WaitForHostNoAuthMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, uzgaidiet.",
"WaitingForHostButton": "Gaidīt rīkotāju",
"WaitingForHostTitle": "Gaida rīkotāju…",
@@ -427,7 +417,7 @@
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
"noDropboxToken": "Nav derīgas Dropbox pilnvaras",
"noDropboxToken": "Nav derīga Dropbox tokena",
"password": "Parole",
"passwordLabel": "Dalībnieks ir aizslēdzis sapulci. Lūdzu, ievadiet $t(lockRoomPassword), lai pievienotos.",
"passwordNotSupported": "Sapulces slēgšana ar $t(lockRoomPassword) netiek atbalstīta.",
@@ -511,7 +501,7 @@
"stopStreamingWarning": "Tiešām vēlaties beigt tiešraidi?",
"streamKey": "Tiešraides atslēga",
"thankYou": "Paldies, ka izmantojāt {{appName}}!",
"token": "pilnvara",
"token": "tokens",
"tokenAuthFailed": "Atvainojiet, jums nav atļauts pievienoties šim zvanam.",
"tokenAuthFailedReason": {
"audInvalid": "Nederīga `aud` vērtība. Tai vajadzētu būt `jitsi`.",
@@ -527,13 +517,12 @@
"nbfFuture": "`nbf` vērtība ir nākotnē.",
"nbfInvalid": "Nederīga `nbf` vērtība.",
"payloadNotFound": "Trūkst satura.",
"tokenExpired": "Pilnvara ir beigusies."
"tokenExpired": "Token ir beidzies."
},
"tokenAuthFailedTitle": "Autentifikācijas kļūda",
"tokenAuthFailedWithReasons": "Atvainojiet, jums nav atļauts pievienoties šim zvanam. Iespējamie iemesli: {{reason}}",
"tokenAuthUnsupported": "Pilnvaras URL nav atbalstīts.",
"tokenAuthUnsupported": "Token URL netiek atbalstīts.",
"transcribing": "Notiek atšifrējuma izveide",
"unauthenticatedAccessDisabled": "Šim zvanam nepieciešama autentifikācija. Lūdzu, piesakieties, lai turpinātu.",
"unlockRoom": "Noņemt $t(lockRoomPassword)",
"user": "Lietotājs",
"userIdentifier": "Lietotājvārds",
@@ -581,12 +570,10 @@
"downloadStarted": "Sākta faila lejuplāde",
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
"fileAlreadyUploaded": "Fails jau ir augšuplādēts šajā sanāksmē.",
"fileRemovedByOther": "Jūsu fails '{{ fileName }}' tika noņemts",
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
"fileUploadProgress": "Faila augšuplādes gaita",
"fileUploadedSuccessfully": "Fails veiksmīgi augšuplādēts",
"newFileNotification": "{{ participantName }} kopīgoja '{{ fileName }}'",
"removeFile": "Noņemt",
"removeFileSuccess": "Fails veiksmīgi noņemts",
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
@@ -761,8 +748,7 @@
"notificationTitle": "Vestibils",
"passwordJoinButton": "Pievienoties",
"title": "Vestibils",
"toggleLabel": "Iespējot vestibilu",
"waitForModerator": "Konference vēl nav sākusies, jo vēl nav ieradušies moderatori. Ja vēlaties kļūt par moderatoru, lūdzu, piesakieties. Pretējā gadījumā, lūdzu, uzgaidiet."
"toggleLabel": "Iespējot vestibilu"
},
"localRecording": {
"clientState": {
@@ -789,7 +775,7 @@
"participant": "Dalībnieks",
"participantStats": "Dalībnieku statistika",
"selectTabTitle": "🎥 Lūdzu, atveriet šo cilni ierakstīšanai",
"sessionToken": "Sesijas Pilnvara",
"sessionToken": "Sessijas tokens",
"start": "Sākt ierakstu",
"stop": "Beigt ierakstu",
"stopping": "Ierakstīšanas pārtraukšana",
@@ -879,7 +865,6 @@
"oldElectronClientDescription1": "Izskatās, ka jūs izmantojat vecu Jitsi Meet klienta versiju, kurai ir zināmas drošības ievainojamības. Lūdzu, atjauniniet uz ",
"oldElectronClientDescription2": "jaunākā versija",
"oldElectronClientDescription3": "tagad!",
"openChat": "Atvērt tērzētavu",
"participantWantsToJoin": "Vēlas pievienoties sapulcei",
"participantsWantToJoin": "Vēlas pievienoties sapulcei",
"passwordRemovedRemotely": "Kāds dalībnieks noņēma $t(lockRoomPasswordUppercase).",
@@ -978,9 +963,6 @@
"by": "Pēc {{ name }} iniciatīvas",
"closeButton": "Slēgt aptauju",
"create": {
"accessibilityLabel": {
"send": "Nosūtīt aptauju"
},
"addOption": "Pievienot opciju",
"answerPlaceholder": "Opcija {{index}}",
"cancel": "Atcelt",
@@ -989,7 +971,8 @@
"pollQuestion": "Aptaujas Jautājums",
"questionPlaceholder": "Uzdod jautājumu",
"removeOption": "Noņemt opciju",
"save": "Saglabāt"
"save": "Saglabāt",
"send": "Nosūtīt"
},
"errors": {
"notUniqueOption": "Iespējām jābūt unikālām"
@@ -1108,7 +1091,7 @@
}
},
"recording": {
"authDropboxText": "Augšuplādēt uz Dropbox",
"authDropboxText": "Augšupielādēt uz Dropbox",
"availableSpace": "Pieejama vieta: {{spaceLeft}} MB (apmēram {{duration}} ieraksta minūtes)",
"beta": "BETA",
"busy": "Cenšamies nodrošināt ierakstam vairāk resursu. Lūdzu, pēc dažām minūtēm pamēģiniet vēlreiz.",
@@ -1162,7 +1145,7 @@
"title": "Ieraksts",
"unavailable": "Hmm! {{serviceName}} pašlaik nav pieejams. Mēs strādājam pie problēmas risināšanas. Lūdzu, pamēģiniet vēlreiz vēlāk.",
"unavailableTitle": "Ieraksts nav iespējams",
"uploadToCloud": "Augšuplādēt mākonī"
"uploadToCloud": "Augšupielādēt mākonī"
},
"screenshareDisplayName": "{{name}} ekrāns",
"sectionList": {
@@ -1317,7 +1300,7 @@
"closeChat": "Aizvērt tērzēšanu",
"closeMoreActions": "Aizvērt vairāk darbību izvēlni",
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
"closedCaptions": "Slēgtie subtitri",
"closedCaptions": "Slēptie subtitri",
"collapse": "Sakļaut",
"document": "Kopīgotais dokuments (iesl./izsl.)",
"documentClose": "Aizvērt kopīgoto dokumentu",
@@ -1395,21 +1378,7 @@
"videomuteGUMPending": "Kameras pievienošana",
"videounmute": "Ieslēgt kameru"
},
"addPeople": "Pievienot cilvēkus savam zvanam",
"advancedAudioSettings": {
"aec": {
"label": "Akustiskās atbalss slāpēšana"
},
"agc": {
"label": "Automātiska pastiprinājuma kontrole"
},
"ns": {
"label": "Trokšņu slāpēšana"
},
"stereo": {
"label": "Stereo"
}
},
"addPeople": "Pievienot cilvēkus savai sesijai/zvanam",
"audioOnlyOff": "Atspējot kanāla/trafika taupības režīmu",
"audioOnlyOn": "Iespējot kanāla/trafika taupības režīmu",
"audioRoute": "Izvēlēties audioierīci",
@@ -1422,7 +1391,7 @@
"closeChat": "Aizvērt tērzētavu",
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
"closeReactionsMenu": "Aizvērt reakciju izvēlni",
"closedCaptions": "Slēgtie subtitri",
"closedCaptions": "Slēptie subtitri",
"disableNoiseSuppression": "Atspējot trokšņu slāpēšanu",
"disableReactionSounds": "Šai sapulcei varat atspējot reakcijas skaņas",
"documentClose": "Aizvērt kopīgoto dokumentu",
@@ -1437,7 +1406,6 @@
"exitFullScreen": "Pilnekrāna režīms",
"exitTileView": "Tuvplāna režīms",
"feedback": "Atstāts atsauksmi",
"fileSharing": "Failu kopīgošana",
"giphy": "GIPHY izvēlne (rādīt/nerādīt)",
"hangup": "Iziet no sapulces",
"help": "Palīdzība",
@@ -1473,19 +1441,17 @@
"openReactionsMenu": "Atvērt reakciju izvēlni",
"participants": "Dalībnieki",
"pip": "Iesl. attēls attēlā (PIP) režīmu",
"polls": "Aptaujas",
"privateMessage": "Nosūtīt privātu ziņu",
"profile": "Rediģēt profilu",
"raiseHand": "Pacelt roku",
"raiseYourHand": "Pacelt roku",
"reactionBoo": "Sūtīt būū reakciju",
"reactionClap": "Sūtīt aplausu reakciju",
"reactionHeart": "Sūtīt sirds reakciju",
"reactionLaugh": "Sūtīt smieklu reakciju",
"reactionLike": "Sūtīt īkšķis augšup reakciju",
"reactionLove": "Sūtīt mīlestības reakciju",
"reactionSilence": "Sūtīt klusuma reakciju",
"reactionSurprised": "Sūtīt pārsteiguma reakciju",
"reactionBoo": "Nosūtīt būū reakciju",
"reactionClap": "Nosūtīt aplausu reakciju",
"reactionHeart": "Nosūtīt sirds reakciju",
"reactionLaugh": "Nosūtīt smieklu reakciju",
"reactionLike": "Nosūtīt īkšķi augšup reakciju",
"reactionSilence": "Nosūtīt klusuma reakciju",
"reactionSurprised": "Nosūtīt pārsteigts reakciju",
"reactions": "Reakcijas",
"security": "Drošības iespējas",
"selectBackground": "Izvēlēties fonu",
@@ -1518,7 +1484,7 @@
"failed": "Atšifrējuma izveide neizdevās",
"labelTooltip": "Šajā sapulcē notiek atšifrējuma izveide.",
"labelTooltipExtra": "Turklāt vēlāk būs pieejams atšifrējums.",
"openClosedCaptions": "Atvērt slēgtos subtitrus",
"openClosedCaptions": "Atvērt slēptos subtitrus",
"original": "Oriģināls",
"sourceLanguageDesc": "Pašlaik sapulces valoda ir iestatīta uz <b>{{sourceLanguage}}</b>. <br/> Varat to mainīt no ",
"sourceLanguageHere": "šeit",
@@ -1616,7 +1582,7 @@
"removeBackground": "Noņemt fonu",
"slightBlur": "Viegli izplūdis",
"title": "Virtuālie foni",
"uploadedImage": "Augšuplādēts attēls {{index}}",
"uploadedImage": "Augšupielādēts attēls {{index}}",
"webAssemblyWarning": "WebAssembly netiek atbalstīts",
"webAssemblyWarningDescription": "WebAssemb ir atspējots vai šī pārlūkprogramma to neatbalsta"
},
@@ -1635,8 +1601,6 @@
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
"requestToJoin": "Roka Pacelta",
"requestToJoinDescription": "Jūsu pieprasījums tika nosūtīts moderatoriem. Uzgaidiet!",
"title": "Jūs esat sapulces apmeklētājs"
},
"waitingMessage": "Jūs pievienosities sapulcei, tiklīdz tā sāksies!"

View File

@@ -112,12 +112,7 @@
"disabled": "聊天已禁用",
"enter": "加入会议室",
"error": "错误:你的消息未发送。原因:{{error}}",
"everyone": "所有人",
"fieldPlaceHolder": "在这里输入你的信息",
"fileAccessibleTitle": "{{user}}上传了一个文件",
"fileAccessibleTitleMe": "我上传了一个文件",
"fileDeleted": "文件已被删除",
"guestsChatIndicator": "(访客)",
"lobbyChatMessageTo": "等候室聊天消息发送至{{recipient}}",
"message": "信息",
"messageAccessibleTitle": "{{user}}",
@@ -305,12 +300,6 @@
"alreadySharedVideoTitle": "同一时间只允许一个视频分享",
"applicationWindow": "应用程序窗口",
"authenticationRequired": "需要身份验证",
"cameraCaptureDialog": {
"description": "使用手机摄像头拍照并发送",
"ok": "打开相机",
"reject": "暂不使用",
"title": "拍照"
},
"cameraConstraintFailedError": "你的摄像头未满足某些必要条件。",
"cameraNotFoundError": "找不到摄像头",
"cameraNotSendingData": "我们无法访问你的摄像头,请检查是否有其他应用程序正在使用此设备,从设置菜单中选择另一个设备,或尝试重新加载应用程序。",
@@ -386,34 +375,22 @@
"micTimeoutError": "无法开启音频设备,发生超时!",
"micUnknownError": "由于未知原因,无法使用麦克风。",
"moderationAudioLabel": "允许参会者自己解除静音",
"moderationDesktopLabel": "允许非主持人共享屏幕",
"moderationVideoLabel": "允许参会者自己开启视频",
"muteEveryoneDialog": "参会者可以在任何时候解除自己的静音。",
"muteEveryoneDialogModerationOn": "参会者可以在任何时候请求发言。",
"muteEveryoneElseDialog": "静音后,你将无法为其解除静音,但是他们可以随时解除自己的静音。",
"muteEveryoneElseTitle": "除了{{whom}}以外的将所有人静音?",
"muteEveryoneElsesDesktopDialog": "一旦停止共享,你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的屏幕共享?",
"muteEveryoneElsesVideoDialog": "一旦关闭,你将无法重新开启他们的摄像头,但他们随时可以重新开启。",
"muteEveryoneElsesVideoTitle": "除了{{whom}}以外,关闭所有人的摄像头?",
"muteEveryoneSelf": "你自己",
"muteEveryoneStartMuted": "现在所有人都已静音",
"muteEveryoneTitle": "静音所有人?",
"muteEveryonesDesktopDialog": "参会者可以随时共享他们的屏幕",
"muteEveryonesDesktopDialogModerationOn": "参会者可以随时请求共享他们的屏幕",
"muteEveryonesDesktopTitle": "停止所有人的屏幕共享?",
"muteEveryonesVideoDialog": "参会者可以随时开启他们的摄像头",
"muteEveryonesVideoDialogModerationOn": "参会者可以随时请求开启他们的摄像头",
"muteEveryonesVideoDialogOk": "关闭",
"muteEveryonesVideoTitle": "关闭所有人的摄像头?",
"muteParticipantBody": "你将无法为他们解除静音,但是他们可以随时解除自己的静音。",
"muteParticipantButton": "静音",
"muteParticipantsDesktopBody": "你无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteParticipantsDesktopBodyModerationOn": "你和他们都无法重新开启屏幕共享",
"muteParticipantsDesktopButton": "停止屏幕共享",
"muteParticipantsDesktopDialog": "你确定要停止这个参会者的屏幕共享吗?你将无法重新开启他们的屏幕共享,但他们可以随时重新开启。",
"muteParticipantsDesktopDialogModerationOn": "你确定要停止这个参会者的屏幕共享吗?你和他们都无法重新开启屏幕共享。",
"muteParticipantsDesktopTitle": "停止这个参会者的屏幕共享?",
"muteParticipantsVideoBody": "你无法重新开启摄像头,但他们随时可以重新开启。",
"muteParticipantsVideoBodyModerationOn": "你和他们都无法重新开启摄像头",
"muteParticipantsVideoButton": "关闭摄像头",
@@ -526,7 +503,6 @@
"tokenAuthFailedWithReasons": "抱歉,你无法加入此通话,原因:",
"tokenAuthUnsupported": "Token地址不支持",
"transcribing": "转录中",
"unauthenticatedAccessDisabled": "此会议需要身份验证,请先登录后继续。",
"unlockRoom": "移除会议$t(lockRoomPassword)",
"user": "用户",
"userIdentifier": "用户ID",
@@ -571,17 +547,11 @@
"downloadFailedDescription": "请稍后重试",
"downloadFailedTitle": "下载失败",
"downloadFile": "下载",
"downloadStarted": "文件下载已开始",
"dragAndDrop": "拖拽文件到此处上传",
"fileAlreadyUploaded": "文件已上传至本次会议",
"fileRemovedByOther": "你的文件{{fileName}}已被移除",
"fileTooLargeDescription": "请确保文件不超过 {{ maxFileSize }}",
"fileTooLargeTitle": "文件太大",
"fileUploadProgress": "文件上传进度",
"fileUploadedSuccessfully": "文件上传成功",
"newFileNotification": "{{participantName}}分享了{{fileName}}",
"removeFile": "移除",
"removeFileSuccess": "文件移除成功",
"uploadFailedDescription": "请稍后重试",
"uploadFailedTitle": "上传失败",
"uploadFile": "文件共享"
@@ -754,8 +724,7 @@
"notificationTitle": "等候室",
"passwordJoinButton": "加入",
"title": "等候室",
"toggleLabel": "开启等候室模式",
"waitForModerator": "会议尚未开始,暂无主持人入会。如需成为主持人请先登录,或耐心等待会议开始。"
"toggleLabel": "开启等候室模式"
},
"localRecording": {
"clientState": {
@@ -798,10 +767,8 @@
"me": "我",
"notify": {
"OldElectronAPPTitle": "安全漏洞!",
"allowAll": "允许全部",
"allowAudio": "允许开启麦克风",
"allowBoth": "允许音视频",
"allowDesktop": "允许屏幕共享",
"allowVideo": "允许开启摄像头",
"allowedUnmute": "你可以解除麦克风静音、启动摄像头或共享屏幕。",
"audioUnmuteBlockedDescription": "由于系统限制,麦克风解除静音操作被暂时阻止。",
@@ -815,7 +782,6 @@
"dataChannelClosedDescription": "桥接通道已断开,视频质量可能会被限制为最低设置",
"dataChannelClosedDescriptionWithAudio": "桥接通道已断开,音视频可能会出现卡顿或中断",
"dataChannelClosedWithAudio": "音视频质量可能受影响",
"desktopMutedRemotelyTitle": "你的屏幕共享已被{{participantDisplayName}}停止",
"disabledIframe": "嵌入仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
"disabledIframeSecondaryNative": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开",
"disabledIframeSecondaryWeb": "嵌入{{domain}}仅用于演示,本次通话将在{{timeout}}分钟后自动断开。如需在正式环境嵌入,请使用<a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi服务</a>",
@@ -873,7 +839,6 @@
"oldElectronClientDescription1": "你似乎正在使用存在已知安全漏洞的旧版Jitsi Meet客户端请确保您更新到我们的",
"oldElectronClientDescription2": "最新版本",
"oldElectronClientDescription3": "",
"openChat": "打开聊天",
"participantWantsToJoin": "想要加入会议",
"participantsWantToJoin": "想要加入会议",
"passwordRemovedRemotely": "其他参会者移除了$t(lockRoomPasswordUppercase)",
@@ -897,7 +862,6 @@
"suggestRecordingDescription": "是否需要录制本次会议?",
"suggestRecordingTitle": "录制会议",
"unmute": "解除静音",
"unmuteScreen": "开始屏幕共享",
"unmuteVideo": "开启摄像头",
"videoMutedRemotelyDescription": "你可随时重新开启视频",
"videoMutedRemotelyTitle": "{{participantDisplayName}}已关闭你的视频",
@@ -917,14 +881,11 @@
"admit": "同意加入",
"admitAll": "全部同意加入",
"allow": "允许参会者:",
"allowDesktop": "允许屏幕共享",
"allowVideo": "允许开启摄像头",
"askDesktop": "请求共享屏幕",
"askUnmute": "请求取消静音",
"audioModeration": "自行解除静音",
"blockEveryoneMicCamera": "禁用所有人的麦克风和摄像头",
"breakoutRooms": "分组讨论室",
"desktopModeration": "开始屏幕共享",
"goLive": "开始直播",
"invite": "邀请其他人",
"lowerAllHands": "取消全部举手",
@@ -936,8 +897,6 @@
"muteAll": "全体静音",
"muteEveryoneElse": "静音其他人",
"reject": "拒绝",
"stopDesktop": "停止屏幕共享",
"stopEveryonesDesktop": "停止所有人的屏幕共享",
"stopEveryonesVideo": "关闭所有人摄像头",
"stopVideo": "关闭摄像头",
"unblockEveryoneMicCamera": "允许所有人开启麦克风和摄像头",
@@ -947,11 +906,9 @@
"headings": {
"lobby": "等候室(({{count}}人)",
"participantsList": "会议参会者({{count}}人)",
"viewerRequests": "观众请求({{count}}人)",
"visitorInQueue": "(排队中:{{count}}人)",
"visitorRequests": "(请求加入:{{count}}人)",
"visitors": "观众(({{count}}人)",
"visitorsList": "观众({{count}}人)",
"waitingLobby": "在等候室等待({{count}}人)"
},
"search": "搜索参会者",
@@ -972,9 +929,6 @@
"by": "由{{ name }}发起",
"closeButton": "结束投票",
"create": {
"accessibilityLabel": {
"send": "发送投票"
},
"addOption": "添加选项",
"answerPlaceholder": "选项{{index}}",
"cancel": "取消",
@@ -1391,20 +1345,6 @@
"videounmute": "打开摄像头"
},
"addPeople": "添加成员到通话中",
"advancedAudioSettings": {
"aec": {
"label": "回声消除"
},
"agc": {
"label": "自动增益控制"
},
"ns": {
"label": "降噪"
},
"stereo": {
"label": "立体声"
}
},
"audioOnlyOff": "关闭省流模式",
"audioOnlyOn": "开启省流模式",
"audioRoute": "选择音频设备",
@@ -1476,7 +1416,6 @@
"reactionHeart": "发送爱心",
"reactionLaugh": "发送大笑",
"reactionLike": "发送点赞",
"reactionLove": "发送爱心",
"reactionSilence": "发送沉默",
"reactionSurprised": "发送惊讶",
"reactions": "互动表情",
@@ -1562,8 +1501,6 @@
"connectionInfo": "连接信息",
"demote": "设为观众",
"domute": "静音",
"domuteDesktop": "停止屏幕共享",
"domuteDesktopOfOthers": "停止屏幕共享给其他人",
"domuteOthers": "静音其他人",
"domuteVideo": "关闭摄像头",
"domuteVideoOfOthers": "关闭其他人摄像头",
@@ -1628,8 +1565,6 @@
"noMainParticipantsTitle": "会议尚未开始",
"noVisitorLobby": "当前会议已开启等候室,暂无法加入",
"notAllowedPromotion": "需由会议成员同意才能参与讨论",
"requestToJoin": "举手请求",
"requestToJoinDescription": "你的请求已发送给主持人,请稍候!",
"title": "你当前为会议观众"
},
"waitingMessage": "会议开始后将自动加入"

View File

@@ -112,12 +112,7 @@
"disabled": "聊天訊息已停用",
"enter": "加入聊天室",
"error": "錯誤:您的訊息未被傳送。原因:{{error}}",
"everyone": "所有人",
"fieldPlaceHolder": "在此輸入您的訊息",
"fileAccessibleTitle": "{{user}}上傳了一個檔案",
"fileAccessibleTitleMe": "我上傳了一個檔案",
"fileDeleted": "檔案已被刪除",
"guestsChatIndicator": "(訪客)",
"lobbyChatMessageTo": "大廳聊天訊息傳送至 {{recipient}}",
"message": "訊息",
"messageAccessibleTitle": "{{user}}",
@@ -305,12 +300,6 @@
"alreadySharedVideoTitle": "同一時間只允許一位影像分享",
"applicationWindow": "應用程式視窗",
"authenticationRequired": "需要驗證",
"cameraCaptureDialog": {
"description": "使用手機攝影機拍照並傳送",
"ok": "開啟相機",
"reject": "暫不使用",
"title": "拍照"
},
"cameraConstraintFailedError": "您的網路攝影機不符合要求。",
"cameraNotFoundError": "找不到網路攝影機。",
"cameraNotSendingData": "我們無法存取您的網路攝影機,請檢查是否有其他應用程式正在使用這個裝置,並從裝置選單裡選擇其他設備或者重新載入。",
@@ -386,34 +375,22 @@
"micTimeoutError": "無法啟動音訊裝置,連線逾時!",
"micUnknownError": "不明原因造成麥克風無法使用。",
"moderationAudioLabel": "允許與會者自我解除靜音",
"moderationDesktopLabel": "允許非主持人共享螢幕",
"moderationVideoLabel": "允許與會者開啟視訊",
"muteEveryoneDialog": "與會者可以隨時解除自己的靜音狀態。",
"muteEveryoneDialogModerationOn": "與會者可以隨時請求發言。",
"muteEveryoneElseDialog": "靜音後,您就不能再解除對方的靜音,但對方可以隨時解除自己的靜音狀態。",
"muteEveryoneElseTitle": "是否要讓除了 {{whom}} 以外的人靜音?",
"muteEveryoneElsesDesktopDialog": "一旦停止共享,您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteEveryoneElsesDesktopTitle": "停止除了{{whom}}以外所有人的螢幕共享?",
"muteEveryoneElsesVideoDialog": "一旦停用,您就不能再重新開啟對方的網路攝影機,但對方隨時能重新開啟自己的網路攝影機。",
"muteEveryoneElsesVideoTitle": "是否要關閉除了 {{whom}} 以外的人的網路攝影機?",
"muteEveryoneSelf": "您自己",
"muteEveryoneStartMuted": "現在所有人皆已靜音",
"muteEveryoneTitle": "要將所有人靜音嗎?",
"muteEveryonesDesktopDialog": "與會者可以隨時共享他們的螢幕",
"muteEveryonesDesktopDialogModerationOn": "與會者可以隨時請求共享他們的螢幕",
"muteEveryonesDesktopTitle": "停止所有人的螢幕共享?",
"muteEveryonesVideoDialog": "與會者隨時可以重新開啟自己的網路攝影機。",
"muteEveryonesVideoDialogModerationOn": "與會者可以隨時傳送開啟視訊請求。",
"muteEveryonesVideoDialogOk": "停用",
"muteEveryonesVideoTitle": "要關閉所有人的網路攝影機嗎?",
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
"muteParticipantButton": "靜音",
"muteParticipantsDesktopBody": "您無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteParticipantsDesktopBodyModerationOn": "您和他們都無法重新開啟螢幕共享",
"muteParticipantsDesktopButton": "停止螢幕分享",
"muteParticipantsDesktopDialog": "您確定要停止這位與會者的螢幕共享嗎?您將無法重新開啟他們的螢幕共享,但他們可以隨時重新開啟。",
"muteParticipantsDesktopDialogModerationOn": "您確定要停止這位與會者的螢幕共享嗎?您和他們都無法重新開啟螢幕共享。",
"muteParticipantsDesktopTitle": "停止這位與會者的螢幕共享?",
"muteParticipantsVideoBody": "您無法重新開啟,只有對方能自己重新開啟。",
"muteParticipantsVideoBodyModerationOn": "您和他都無法再將視訊重新開啟。",
"muteParticipantsVideoButton": "停用網路攝影機",
@@ -526,7 +503,6 @@
"tokenAuthFailedWithReasons": "抱歉,您無法參加這個通話,可能原因:{{reason}}",
"tokenAuthUnsupported": "不支援權杖網址。",
"transcribing": "轉錄中",
"unauthenticatedAccessDisabled": "此會議需要身份驗證,請先登入後繼續。",
"unlockRoom": "移除會議 $t(lockRoomPassword)",
"user": "使用者",
"userIdentifier": "使用者 ID",
@@ -571,17 +547,11 @@
"downloadFailedDescription": "請重試",
"downloadFailedTitle": "下載失敗",
"downloadFile": "下載",
"downloadStarted": "檔案下載已開始",
"dragAndDrop": "將檔案拖曳至此或畫面任一處上傳",
"fileAlreadyUploaded": "檔案已上傳至此會議",
"fileRemovedByOther": "您的檔案「{{fileName}}」已被移除",
"fileTooLargeDescription": "請確認檔案未超過 {{ maxFileSize }}",
"fileTooLargeTitle": "檔案過大",
"fileUploadProgress": "檔案上傳進度",
"fileUploadedSuccessfully": "檔案上傳成功",
"newFileNotification": "{{participantName}}分享了「{{fileName}}」",
"removeFile": "移除",
"removeFileSuccess": "檔案移除成功",
"uploadFailedDescription": "請重試",
"uploadFailedTitle": "上傳失敗",
"uploadFile": "分享檔案"
@@ -754,8 +724,7 @@
"notificationTitle": "大廳",
"passwordJoinButton": "加入",
"title": "大廳",
"toggleLabel": "啟用大廳模式",
"waitForModerator": "會議尚未開始,暫無主持人入會。如需成為主持人請先登入,或耐心等待會議開始。"
"toggleLabel": "啟用大廳模式"
},
"localRecording": {
"clientState": {
@@ -798,10 +767,8 @@
"me": "我",
"notify": {
"OldElectronAPPTitle": "安全漏洞!",
"allowAll": "允許全部",
"allowAudio": "允許音訊",
"allowBoth": "允許音訊與視訊",
"allowDesktop": "允許螢幕分享",
"allowVideo": "允許視訊",
"allowedUnmute": "您可以將麥克風解除靜音、開啟視訊,或是分享您的螢幕。",
"audioUnmuteBlockedDescription": "麥克風解除靜音操作由於系統限制而被暫時封鎖。",
@@ -815,7 +782,6 @@
"dataChannelClosedDescription": "橋接通道已斷開,視訊品質降至最低設定。",
"dataChannelClosedDescriptionWithAudio": "橋接通道已斷開,音訊和視訊可能會受到影響。",
"dataChannelClosedWithAudio": "音訊和視訊品質可能會降低。",
"desktopMutedRemotelyTitle": "您的螢幕分享已被{{participantDisplayName}}停止",
"disabledIframe": "嵌入僅供示範使用,此通話將於 {{timeout}} 分鐘後中斷連線。",
"disabledIframeSecondaryNative": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷。",
"disabledIframeSecondaryWeb": "嵌入 {{domain}} 僅供示範,此通話將於 {{timeout}} 分鐘後中斷,請使用 <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi 服務</a> 來進行正式嵌入!",
@@ -873,7 +839,6 @@
"oldElectronClientDescription1": "您似乎正在使用存在已知安全漏洞的過時 Jitsi Meet 用戶端,請盡快更新至我們的",
"oldElectronClientDescription2": "最新版本",
"oldElectronClientDescription3": "",
"openChat": "開啟聊天",
"participantWantsToJoin": "希望加入會議",
"participantsWantToJoin": "希望加入會議",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) 已被其他與會者移除",
@@ -897,7 +862,6 @@
"suggestRecordingDescription": "是否要開始錄製這場會議?",
"suggestRecordingTitle": "錄製此會議",
"unmute": "取消靜音",
"unmuteScreen": "開始螢幕分享",
"unmuteVideo": "啟用視訊",
"videoMutedRemotelyDescription": "您隨時可以再次啟用。",
"videoMutedRemotelyTitle": "您的視訊已被 {{participantDisplayName}} 停用",
@@ -917,14 +881,11 @@
"admit": "準許",
"admitAll": "準許所有人",
"allow": "允許與會者能夠:",
"allowDesktop": "允許螢幕分享",
"allowVideo": "允許視訊",
"askDesktop": "請求共享螢幕",
"askUnmute": "要求解除靜音",
"audioModeration": "自我解除靜音",
"blockEveryoneMicCamera": "停用所有人的麥克風和網路攝影機",
"breakoutRooms": "分組討論室",
"desktopModeration": "開始螢幕分享",
"goLive": "開始直播",
"invite": "邀請他人",
"lowerAllHands": "全部取消舉手",
@@ -936,8 +897,6 @@
"muteAll": "靜音所有人",
"muteEveryoneElse": "靜音其他人",
"reject": "拒絕",
"stopDesktop": "停止螢幕分享",
"stopEveryonesDesktop": "停止所有人的螢幕分享",
"stopEveryonesVideo": "停用所有人的視訊",
"stopVideo": "停用視訊",
"unblockEveryoneMicCamera": "解除封鎖所有人的麥克風及網路攝影機",
@@ -947,11 +906,9 @@
"headings": {
"lobby": "大廳({{count}} 人)",
"participantsList": "會議與會者({{count}} 人)",
"viewerRequests": "觀眾請求({{count}}人)",
"visitorInQueue": "{{count}} 人等候中)",
"visitorRequests": "{{count}} 人申請",
"visitors": "訪客({{count}} 人)",
"visitorsList": "觀眾({{count}}人)",
"waitingLobby": "於大廳等候({{count}} 人)"
},
"search": "搜尋與會者",
@@ -972,9 +929,6 @@
"by": "由 {{ name }}",
"closeButton": "結束投票",
"create": {
"accessibilityLabel": {
"send": "傳送投票"
},
"addOption": "新增選項",
"answerPlaceholder": "選項 {{index}}",
"cancel": "取消",
@@ -1391,20 +1345,6 @@
"videounmute": "啟用網路攝影機"
},
"addPeople": "新增人員到您的通話中",
"advancedAudioSettings": {
"aec": {
"label": "回聲消除"
},
"agc": {
"label": "自動增益控制"
},
"ns": {
"label": "降噪"
},
"stereo": {
"label": "立體聲"
}
},
"audioOnlyOff": "停用低頻寬模式",
"audioOnlyOn": "啟用低頻寬模式",
"audioRoute": "選擇音訊裝置",
@@ -1476,7 +1416,6 @@
"reactionHeart": "傳送愛心反應",
"reactionLaugh": "傳送大笑反應",
"reactionLike": "傳送比讚反應",
"reactionLove": "傳送愛心",
"reactionSilence": "傳送沉默反應",
"reactionSurprised": "傳送驚訝反應",
"reactions": "反應",
@@ -1562,8 +1501,6 @@
"connectionInfo": "連線資訊",
"demote": "轉為訪客",
"domute": "靜音",
"domuteDesktop": "停止螢幕分享",
"domuteDesktopOfOthers": "停止螢幕分享給其他人",
"domuteOthers": "靜音其他人",
"domuteVideo": "停用網路攝影機",
"domuteVideoOfOthers": "停用其他人的網路攝影機",
@@ -1628,8 +1565,6 @@
"noMainParticipantsTitle": "會議尚未開始",
"noVisitorLobby": "此會議啟用大廳,暫時無法加入",
"notAllowedPromotion": "需由與會者同意您的申請",
"requestToJoin": "舉手請求",
"requestToJoinDescription": "您的請求已傳送給主持人,請稍候!",
"title": "您是會議中的訪客"
},
"waitingMessage": "會議開始後您將自動加入!"

View File

@@ -126,16 +126,8 @@
"messagebox": "Type a message",
"newMessages": "New messages",
"nickname": {
"featureChat": "chat",
"featureClosedCaptions": "closed captions",
"featureFileSharing": "file sharing",
"featurePolls": "polls",
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat",
"titleWith1Features": "Enter a nickname to use {{feature1}}",
"titleWith2Features": "Enter a nickname to use {{feature1}} and {{feature2}}",
"titleWith3Features": "Enter a nickname to use {{feature1}}, {{feature2}} and {{feature3}}",
"titleWith4Features": "Enter a nickname to use {{feature1}}, {{feature2}}, {{feature3}} and {{feature4}}",
"titleWithCC": "Enter a nickname to use chat and closed captions",
"titleWithPolls": "Enter a nickname to use chat and polls",
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",
@@ -1437,7 +1429,6 @@
"exitFullScreen": "Exit full screen",
"exitTileView": "Exit tile view",
"feedback": "Leave feedback",
"fileSharing": "File sharing",
"giphy": "Toggle GIPHY menu",
"hangup": "Leave the meeting",
"help": "Help",
@@ -1473,7 +1464,6 @@
"openReactionsMenu": "Open reactions menu",
"participants": "Participants",
"pip": "Enter Picture-in-Picture mode",
"polls": "Polls",
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise your hand",

View File

@@ -340,7 +340,6 @@ function initCommands() {
APP.store.dispatch(setAssumedBandwidthBps(value));
},
'set-blurred-background': blurType => {
const tracks = APP.store.getState()['features/base/tracks'];
const videoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;

View File

@@ -158,10 +158,11 @@ const VideoLayout = {
return;
}
const state = APP.store.getState();
const currentContainer = largeVideo.getCurrentContainer();
const currentContainerType = largeVideo.getCurrentContainerType();
const isOnLarge = this.isCurrentlyOnLarge(id);
const state = APP.store.getState();
const participant = getParticipantById(state, id);
const videoTrack = getVideoTrackByParticipant(state, participant);
const videoStream = videoTrack?.jitsiTrack;

87
package-lock.json generated
View File

@@ -19,10 +19,10 @@
"@giphy/react-components": "6.9.4",
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.6.7",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "3.2.15",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@react-native-async-storage/async-storage": "1.23.1",
@@ -66,7 +66,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -4544,10 +4544,9 @@
}
},
"node_modules/@jitsi/js-utils": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
"license": "Apache-2.0",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
@@ -4690,9 +4689,9 @@
"dev": true
},
"node_modules/@matrix-org/olm": {
"version": "3.2.15",
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==",
"version": "3.2.3",
"resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A==",
"license": "Apache-2.0"
},
"node_modules/@microsoft/microsoft-graph-client": {
@@ -18259,11 +18258,11 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "^2.6.7",
"@jitsi/js-utils": "2.4.6",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
@@ -18279,6 +18278,17 @@
"webrtc-adapter": "8.1.1"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/js-utils": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
"license": "Apache-2.0",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
@@ -18301,6 +18311,12 @@
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
"node_modules/lib-jitsi-meet/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==",
"license": "MIT"
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -20086,10 +20102,9 @@
}
},
"node_modules/node-forge": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"engines": {
"node": ">= 6.13.0"
}
@@ -30001,9 +30016,9 @@
"integrity": "sha512-8fAv3cVEuoukSyu5RBZg0YWcrGEMjSKsdeQJuMmeOL2vVXIBWo0TpaHqys4HNCGRmZKzkhYccqxtmNSTxlBgkQ=="
},
"@jitsi/js-utils": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.6.7.tgz",
"integrity": "sha512-r16J3CjYt325CFIpfHznND4O3b9BE7CZ7cu+Xx0uk1C+ZY6/bDPZFM/0d4t0VH+1/rmfG4I7i18qxXct3xmPrw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"requires": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
@@ -30106,9 +30121,8 @@
"dev": true
},
"@matrix-org/olm": {
"version": "3.2.15",
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q=="
"version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A=="
},
"@microsoft/microsoft-graph-client": {
"version": "3.0.1",
@@ -39675,10 +39689,10 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"requires": {
"@jitsi/js-utils": "^2.6.7",
"@jitsi/js-utils": "2.4.6",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
@@ -39694,6 +39708,16 @@
"webrtc-adapter": "8.1.1"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
"requires": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
@@ -39714,6 +39738,11 @@
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
}
}
},
@@ -41021,9 +41050,9 @@
}
},
"node-forge": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw=="
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"node-int64": {
"version": "0.4.0",

View File

@@ -25,10 +25,10 @@
"@giphy/react-components": "6.9.4",
"@giphy/react-native-sdk": "4.1.0",
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.6.7",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "3.2.15",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@react-native-async-storage/async-storage": "1.23.1",
@@ -72,7 +72,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",

View File

@@ -600,6 +600,31 @@ export function createRemoteVideoMenuButtonEvent(buttonName: string, attributes
};
}
/**
* The rtcstats websocket onclose event. We send this to amplitude in order
* to detect trace ws prematurely closing.
*
* @param {Object} closeEvent - The event with which the websocket closed.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createRTCStatsTraceCloseEvent(closeEvent: { code: string; reason: string; }) {
const event: {
action: string;
code?: string;
reason?: string;
source: string;
} = {
action: 'trace.onclose',
source: 'rtcstats'
};
event.code = closeEvent.code;
event.reason = closeEvent.reason;
return event;
}
/**
* Creates an event indicating that an action related to screen sharing
* occurred (e.g. It was started or stopped).

View File

@@ -139,7 +139,7 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
* @returns {Function}
*/
export function hideLoginDialog() {
return hideDialog('LoginDialog', LoginDialog);
return hideDialog(LoginDialog);
}
/**
@@ -199,7 +199,7 @@ export function enableModeratorLogin() {
* @returns {Action}
*/
export function openWaitForOwnerDialog() {
return openDialog('WaitForOwnerDialog', WaitForOwnerDialog);
return openDialog(WaitForOwnerDialog);
}
@@ -240,7 +240,7 @@ export function waitForOwner() {
* @returns {Action}
*/
export function openLoginDialog() {
return openDialog('LoginDialog', LoginDialog);
return openDialog(LoginDialog);
}
/**

View File

@@ -65,7 +65,7 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
// Show warning for leaving conference only when in a conference.
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
dispatch(openDialog(LoginQuestionDialog, {
handler: () => {
// Give time for the dialog to close.
setTimeout(() => redirect(), 500);

View File

@@ -209,7 +209,7 @@ MiddlewareRegistry.register(store => next => action => {
case STOP_WAIT_FOR_OWNER:
_clearExistingWaitForOwnerTimeout(store);
store.dispatch(hideDialog('WaitForOwnerDialog', WaitForOwnerDialog));
store.dispatch(hideDialog(WaitForOwnerDialog));
break;
case UPGRADE_ROLE_FINISHED: {

View File

@@ -55,28 +55,6 @@ function getFirstGraphemeUpper(word: string) {
return splitter.splitGraphemes(word)[0].toUpperCase();
}
/**
* Strips bracketed annotations from a display name. Handles multiple bracket types like (),
* [], and {}.
*
* @param {string} name - The display name to clean.
* @returns {string} The cleaned display name without bracketed annotations.
*/
function stripBracketedAnnotations(name: string): string {
// Match content within any of the bracket types at the end of the string
// This regex matches: (...) or [...] or {...} at the end
const bracketRegex = /\s*[([{][^)\]}]*[)\]}]$/;
let cleaned = name;
// Remove all trailing bracketed annotations (handle multiple occurrences)
while (bracketRegex.test(cleaned)) {
cleaned = cleaned.replace(bracketRegex, '');
}
return cleaned.trim();
}
/**
* Generates initials for a simple string.
*
@@ -86,15 +64,7 @@ function stripBracketedAnnotations(name: string): string {
export function getInitials(s?: string) {
// We don't want to use the domain part of an email address, if it is one
const initialsBasis = split(s, '@')[0];
// Strip bracketed annotations (e.g., "(Department)", "[Team]", "{Org}")
// to prevent them from being considered as name parts
const cleanedName = stripBracketedAnnotations(initialsBasis);
// Fallback to original if cleaned name is empty
const nameForInitials = cleanedName || initialsBasis;
const [ firstWord, ...remainingWords ] = nameForInitials.split(wordSplitRegex).filter(Boolean);
const [ firstWord, ...remainingWords ] = initialsBasis.split(wordSplitRegex).filter(Boolean);
return getFirstGraphemeUpper(firstWord) + getFirstGraphemeUpper(remainingWords.pop() || '');
}

View File

@@ -285,7 +285,6 @@ export interface IConfig {
disableAudioLevels?: boolean;
disableBeforeUnloadHandlers?: boolean;
disableCameraTintForeground?: boolean;
disableChat?: boolean;
disableChatSmileys?: boolean;
disableDeepLinking?: boolean;
disableFilmstripAutohiding?: boolean;
@@ -394,7 +393,6 @@ export interface IConfig {
disabled?: boolean;
initialWidth?: number;
minParticipantCountForTopPanel?: number;
stageFilmstripParticipants?: number;
};
flags?: {
ssrcRewritingEnabled: boolean;
@@ -618,10 +616,6 @@ export interface IConfig {
toolbarConfig?: {
alwaysVisible?: boolean;
autoHideWhileChatIsOpen?: boolean;
/**
* Background color for the main toolbar. Accepts any valid CSS color.
*/
backgroundColor?: string;
initialTimeout?: number;
timeout?: number;
};

View File

@@ -94,7 +94,6 @@ export default [
'disableAudioLevels',
'disableBeforeUnloadHandlers',
'disableCameraTintForeground',
'disableChat',
'disableChatSmileys',
'disableDeepLinking',
'disabledNotifications',

View File

@@ -14,7 +14,6 @@ import logger from './logger';
/**
* Signals Dialog to close its dialog.
*
* @param {string|undefined} name - The name of the component for logging purposes.
* @param {Object} [component] - The {@code Dialog} component to close/hide. If
* {@code undefined}, closes/hides {@code Dialog} regardless of which
* component it's rendering; otherwise, closes/hides {@code Dialog} only if
@@ -24,8 +23,8 @@ import logger from './logger';
* component: (React.Component | undefined)
* }}
*/
export function hideDialog(name?: string, component?: ComponentType<any>) {
logger.info(`Hide dialog: ${name}`);
export function hideDialog(component?: ComponentType<any>) {
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
return {
type: HIDE_DIALOG,
@@ -49,7 +48,6 @@ export function hideSheet() {
/**
* Signals Dialog to open dialog.
*
* @param {string} name - The name of the component for logging purposes.
* @param {Object} component - The component to display as dialog.
* @param {Object} [componentProps] - The React {@code Component} props of the
* specified {@code component}.
@@ -59,8 +57,8 @@ export function hideSheet() {
* componentProps: (Object | undefined)
* }}
*/
export function openDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${name}`);
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
return {
type: OPEN_DIALOG,
@@ -94,18 +92,35 @@ export function openSheet(component: ComponentType<any>, componentProps?: Object
* is not already open. If it is open, then Dialog is signaled to close its
* dialog.
*
* @param {string} name - The name of the component for logging purposes.
* @param {Object} component - The component to display as dialog.
* @param {Object} [componentProps] - The React {@code Component} props of the
* specified {@code component}.
* @returns {Function}
*/
export function toggleDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
export function toggleDialog(component: ComponentType<any>, componentProps?: Object) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (isDialogOpen(getState, component)) {
dispatch(hideDialog(name, component));
dispatch(hideDialog(component));
} else {
dispatch(openDialog(name, component, componentProps));
dispatch(openDialog(component, componentProps));
}
};
}
/**
* Extracts a printable name for a dialog component.
*
* @param {Object} component - The component to extract the name for.
*
* @returns {string} The display name.
*/
function getComponentDisplayName(component?: ComponentType<any>) {
if (!component) {
return '';
}
const name = component.displayName ?? component.name ?? 'Component';
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
.replace('))', ''); // dialogs with translations suffix
}

View File

@@ -80,7 +80,7 @@ const options: i18next.InitOptions = {
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
load: 'all',
load: 'languageOnly',
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
react: {
// re-render when a new resource bundle is added

View File

@@ -3,14 +3,13 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { pick } from 'lodash-es';
import { browser } from '../lib-jitsi-meet';
import { isEmbedded } from '../util/embedUtils';
import { parseURLParams } from '../util/parseURLParams';
import logger from './logger';
import WHITELIST from './whitelist';
/**
* Handles changes of the fake local storage.
@@ -62,7 +61,7 @@ function setupJitsiLocalStorage() {
if (shouldUseHostPageLocalStorage(urlParams)) {
try {
let localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
// We need to disable the local storage before setting the data in case the browser local storage doesn't
// throw exception (in some cases when this happens the local storage may be cleared for every session.
@@ -72,10 +71,6 @@ function setupJitsiLocalStorage() {
jitsiLocalStorage.setLocalStorageDisabled(true);
if (typeof localStorageContent === 'object') {
if (!isEmbedded()) {
localStorageContent = pick(localStorageContent, WHITELIST);
}
Object.keys(localStorageContent).forEach(key => {
jitsiLocalStorage.setItem(key, localStorageContent[key]);
});

View File

@@ -1,11 +0,0 @@
/**
* Keys of localStorage that are used by jibri.
*/
export default [
'callStatsUserName',
'displayname',
'email',
'xmpp_username_override',
'xmpp_password_override',
'xmpp_conference_password_override'
];

View File

@@ -186,9 +186,6 @@ function _initLogging({ dispatch, getState }: IStore,
Logger.addGlobalTransport(debugLogCollector);
JitsiMeetJS.addGlobalLogTransport(debugLogCollector);
debugLogCollector.start();
Logger.removeGlobalTransport(console);
JitsiMeetJS.removeGlobalLogTransport(console);
}
} else if (logCollector && loggingConfig.disableLogCollector) {
Logger.removeGlobalTransport(logCollector);

View File

@@ -26,7 +26,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
case SET_VIDEO_MUTED: {
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog, { localRecordingVideoStop: true }));
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
return;
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {

View File

@@ -62,65 +62,6 @@ const AVATAR_CHECKER_FUNCTIONS = [
];
/* eslint-enable arrow-body-style */
/**
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
* dominant speaker is visible always on the vertical filmstrip in stage layout.
*
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
* retrieve the state.
* @returns {Array<string>}
*/
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
const state = toState(stateful);
const {
dominantSpeaker,
fakeParticipants,
sortedRemoteVirtualScreenshareParticipants,
speakersList
} = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
let activeSpeakers = new Map(speakersList);
// Do not re-sort the active speakers if dominant speaker is currently visible.
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
return activeSpeakers;
}
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
if (activeSpeakers.has(dominantSpeaker ?? '')) {
activeSpeakers.delete(dominantSpeaker ?? '');
}
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
// alphabetically sorted.
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
const updatedSpeakers = Array.from(activeSpeakers);
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
activeSpeakers = new Map(updatedSpeakers);
}
// Remove screenshares from the count.
if (sortedRemoteVirtualScreenshareParticipants) {
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
activeSpeakers.delete(ownerId);
}
}
// Remove fake participants from the count.
if (fakeParticipants) {
availableSlotsForActiveSpeakers -= fakeParticipants.size;
}
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
return new Map(truncatedSpeakersList);
}
/**
* Resolves the first loadable avatar URL for a participant.
*

View File

@@ -27,7 +27,6 @@ import {
isRemoteScreenshareParticipant,
isScreenShareParticipant
} from './functions';
import logger from './logger';
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
/**
@@ -77,12 +76,12 @@ const DEFAULT_STATE = {
numberOfParticipantsNotSupportingE2EE: 0,
overwrittenNameList: {},
pinnedParticipant: undefined,
previousSpeakers: new Set<string>(),
raisedHandsQueue: [],
remote: new Map(),
remoteVideoSources: new Set<string>(),
sortedRemoteVirtualScreenshareParticipants: new Map(),
sortedRemoteParticipants: new Map(),
speakersList: new Map()
sortedRemoteParticipants: new Map()
};
export interface IParticipantsState {
@@ -95,12 +94,12 @@ export interface IParticipantsState {
numberOfParticipantsNotSupportingE2EE: number;
overwrittenNameList: { [id: string]: string; };
pinnedParticipant?: string;
previousSpeakers: Set<string>;
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
remote: Map<string, IParticipant>;
remoteVideoSources: Set<string>;
sortedRemoteParticipants: Map<string, string>;
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
speakersList: Map<string, string>;
}
/**
@@ -157,22 +156,10 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
const { participant } = action;
const { id, previousSpeakers = [] } = participant;
const { dominantSpeaker, local } = state;
const newSpeakers = [ id, ...previousSpeakers ];
const sortedSpeakersList: Array<Array<string>> = [];
for (const speaker of newSpeakers) {
if (speaker !== local?.id) {
const remoteParticipant = state.remote.get(speaker);
remoteParticipant
&& sortedSpeakersList.push(
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
);
}
}
// Keep the remote speaker list sorted alphabetically.
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
// Build chronologically ordered Set of remote speakers (excluding local)
const previousSpeakersSet: Set<string>
= new Set(previousSpeakers.filter((speaker: string) => speaker !== local?.id));
// Only one dominant speaker is allowed.
if (dominantSpeaker) {
@@ -182,8 +169,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
return {
...state,
dominantSpeaker: id, // @ts-ignore
speakersList: new Map(sortedSpeakersList)
dominantSpeaker: id,
previousSpeakers: previousSpeakersSet
};
}
@@ -365,8 +352,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant joined', id);
}
// Exclude the screenshare participant from the fake participant count to avoid duplicates.
@@ -438,7 +423,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
}
// Remove the participant from the list of speakers.
state.speakersList.has(id) && state.speakersList.delete(id);
state.previousSpeakers.delete(id);
if (pinnedParticipant === id) {
state.pinnedParticipant = undefined;
@@ -451,8 +436,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
sortedRemoteVirtualScreenshareParticipants.delete(id);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant left', id);
}
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {

View File

@@ -21,7 +21,6 @@ import {
getRemoteScreensharesBasedOnPresence,
getVirtualScreenshareParticipantOwnerId
} from './functions';
import logger from './logger';
import { FakeParticipant } from './types';
StateListenerRegistry.register(
@@ -70,19 +69,14 @@ function _createOrRemoveVirtualParticipants(
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => {
logger.debug('Dispatching participantLeft for virtual screenshare', id);
dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
}));
});
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
})));
}
if (addedScreenshareSourceNames.length) {
addedScreenshareSourceNames.forEach(id => {
logger.debug('Creating virtual screenshare participant', id);
dispatch(createVirtualScreenshareParticipant(id, false, conference));
});
addedScreenshareSourceNames.forEach(id => dispatch(
createVirtualScreenshareParticipant(id, false, conference)));
}
}

View File

@@ -1,7 +1,5 @@
import { IReduxState, IStore } from '../../app/types';
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
import { handleToggleVideoMuted } from '../../toolbox/actions.any';
import { muteLocal } from '../../video-menu/actions.any';
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
import {
@@ -80,43 +78,3 @@ export function isRemoteVideoReceived({ getState }: IStore, id: string): boolean
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
}
/**
* Mutes the local audio. Same as clicking the audio mute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function audioMute({ dispatch }: IStore) {
return dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
}
/**
* Unmutes the local audio. Same as clicking the audio unmute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function audioUnmute({ dispatch }: IStore) {
return dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
}
/**
* Mutes the local video. Same as clicking the video mute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function videoMute({ dispatch }: IStore) {
return dispatch(handleToggleVideoMuted(true, true, true));
}
/**
* Unmutes the local video. Same as clicking the video unmute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function videoUnmute({ dispatch }: IStore) {
return dispatch(handleToggleVideoMuted(false, true, true));
}

View File

@@ -8,15 +8,11 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
import { setConnectionState } from './actions';
import {
audioMute,
audioUnmute,
getLocalCameraEncoding,
getRemoteVideoType,
isLargeVideoReceived,
isRemoteVideoReceived,
isTestModeEnabled,
videoMute,
videoUnmute
isTestModeEnabled
} from './functions';
import logger from './logger';
@@ -89,14 +85,10 @@ function _bindTortureHelpers(store: IStore) {
// All torture helper methods go in here
getJitsiMeetGlobalNS().testing = {
audioMute: audioMute.bind(null, store),
audioUnmute: audioUnmute.bind(null, store),
getRemoteVideoType: getRemoteVideoType.bind(null, store),
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store),
videoMute: videoMute.bind(null, store),
videoUnmute: videoUnmute.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
};
}

View File

@@ -6,7 +6,6 @@ import { setScreenshareMuted } from '../media/actions';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import { getLocalDesktopTrack, getTrackState } from './functions.native';
import logger from './logger';
export * from './actions.any';
@@ -64,6 +63,6 @@ async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxSt
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
} catch (error: any) {
logger.error('Error creating screen-sharing stream', error);
console.log('ERROR creating screen-sharing stream ', error);
}
}

View File

@@ -294,7 +294,7 @@ export function setCameraFacingMode(facingMode: string | undefined) {
* @returns {Object} - The open dialog action.
*/
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
return openDialog('AllowToggleCameraDialog', AllowToggleCameraDialog, {
return openDialog(AllowToggleCameraDialog, {
onAllow,
initiatorId
});

View File

@@ -211,21 +211,18 @@ export function getLocalJitsiAudioTrackSettings(state: IReduxState) {
const jitsiTrack = getLocalJitsiAudioTrack(state);
if (!jitsiTrack) {
const {
audioQuality,
disableAEC = false,
disableAGC = false,
disableAP = false,
disableNS = false
} = state['features/base/config'] || {};
const enableStereo = Boolean(audioQuality?.stereo);
const config = state['features/base/config'];
const disableAP = Boolean(config?.disableAP);
const disableAGC = Boolean(config?.disableAGC);
const disableAEC = Boolean(config?.disableAEC);
const disableNS = Boolean(config?.disableNS);
const stereo = Boolean(config?.audioQuality?.stereo);
return {
autoGainControl: enableStereo ? false : !disableAP && !disableAGC,
channelCount: enableStereo ? 2 : 1,
echoCancellation: enableStereo ? false : !disableAP && !disableAEC,
noiseSuppression: enableStereo ? false : !disableAP && !disableNS
autoGainControl: !disableAP && !disableAGC,
channelCount: stereo ? 2 : 1,
echoCancellation: !disableAP && !disableAEC,
noiseSuppression: !disableAP && !disableNS
};
}

View File

@@ -58,7 +58,7 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
}, [ dispatch, room ]);
const onRenameBreakoutRoom = useCallback(() => {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
dispatch(openDialog(BreakoutRoomNamePrompt, {
breakoutRoomJid: room.jid,
initialRoomName: room.name
}));

View File

@@ -22,7 +22,7 @@ export * from './actions.any';
* }}
*/
export function openUpdateCalendarEventDialog(eventId: string) {
return openDialog('UpdateCalendarEventDialog', UpdateCalendarEventDialog, { eventId });
return openDialog(UpdateCalendarEventDialog, { eventId });
}
/**

View File

@@ -220,34 +220,6 @@ export function openCCPanel() {
};
}
/**
* Opens the chat panel with polls tab active.
*
* @returns {Object} The redux action.
*/
export function openPollsPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.POLLS));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Opens the chat panel with file sharing tab active.
*
* @returns {Object} The redux action.
*/
export function openFileSharingPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Initiates the sending of messages between a moderator and a lobby attendee.

View File

@@ -1,34 +1,30 @@
import { IStore } from '../app/types';
import { IParticipant } from '../base/participants/types';
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';
import { setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
export * from './actions.any';
/**
* Displays the chat panel with the CHAT tab active.
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @param {boolean} disablePolls - Checks if polls are disabled.
*
* @returns {Function}
* @returns {{
* participant: participant,
* type: OPEN_CHAT
* }}
*/
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
return (dispatch: IStore['dispatch']) => {
if (disablePolls) {
navigate(screen.conference.chat);
} else {
navigate(screen.conference.chatandpolls.main);
}
if (disablePolls) {
navigate(screen.conference.chat);
}
navigate(screen.conference.chatandpolls.main);
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT
});
return {
participant,
type: OPEN_CHAT
};
}

View File

@@ -8,13 +8,12 @@ import {
SET_CHAT_WIDTH,
SET_USER_CHAT_WIDTH
} from './actionTypes';
import { closeChat, setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
import { closeChat } from './actions.any';
export * from './actions.any';
/**
* Displays the chat panel with the CHAT tab active.
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @param {Object} _disablePolls - Used on native.
@@ -25,7 +24,6 @@ export * from './actions.any';
*/
export function openChat(participant?: Object, _disablePolls?: boolean) {
return function(dispatch: IStore['dispatch']) {
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT

View File

@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount, getUnreadFilesCount, isChatDisabled } from '../../functions';
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
interface IProps extends AbstractButtonProps {
@@ -65,7 +65,7 @@ class ChatButton extends AbstractButton<IProps> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { visible = enabled } = ownProps;
return {

View File

@@ -24,7 +24,7 @@ import {
toggleChat
} from '../../actions.web';
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
import { getChatMaxSize } from '../../functions';
import { IChatProps as AbstractProps } from '../../types';
import ChatHeader from './ChatHeader';
@@ -41,18 +41,13 @@ interface IProps extends AbstractProps {
/**
* The currently focused tab.
*/
_focusedTab?: ChatTabs;
_focusedTab: ChatTabs;
/**
* True if the CC tab is enabled and false otherwise.
*/
_isCCTabEnabled: boolean;
/**
* True if chat is disabled.
*/
_isChatDisabled: boolean;
/**
* True if file sharing tab is enabled.
*/
@@ -222,7 +217,6 @@ const Chat = ({
_isOpen,
_isPollsEnabled,
_isCCTabEnabled,
_isChatDisabled,
_isFileSharingTabEnabled,
_focusedTab,
_isResizing,
@@ -235,11 +229,6 @@ const Chat = ({
dispatch,
t
}: IProps) => {
// If no tabs are available, don't render the chat panel at all.
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
return null;
}
const { classes, cx } = useStyles({ _isResizing, width: _width });
const [ isMouseDown, setIsMouseDown ] = useState(false);
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
@@ -298,6 +287,8 @@ const Chat = ({
// Disable text selection during resize
document.body.style.userSelect = 'none';
console.log('Chat resize: Mouse down', { clientX: e.clientX, initialWidth: _width });
}, [ _width, dispatch ]);
/**
@@ -313,6 +304,8 @@ const Chat = ({
// Restore cursor and text selection
document.body.style.cursor = '';
document.body.style.userSelect = '';
console.log('Chat resize: Mouse up');
}
}, [ isMouseDown, dispatch ]);
@@ -323,6 +316,7 @@ const Chat = ({
* @returns {void}
*/
const onChatResize = useCallback(throttle((e: MouseEvent) => {
// console.log('Chat resize: Mouse move', { clientX: e.clientX, isMouseDown, mousePosition, _width });
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
// For chat panel resizing on the left edge:
// - Dragging left (decreasing X coordinate) should make the panel wider
@@ -422,7 +416,7 @@ const Chat = ({
return (
<>
{renderTabs()}
{!_isChatDisabled && (<div
<div
aria-labelledby = { ChatTabs.CHAT }
className = { cx(
classes.chatPanel,
@@ -448,7 +442,7 @@ const Chat = ({
)}
<ChatInput
onSend = { onSendMessage } />
</div>) }
</div>
{ _isPollsEnabled && (
<>
<div
@@ -490,18 +484,8 @@ const Chat = ({
* @returns {ReactElement}
*/
function renderTabs() {
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
// executed because Chat component won't render anything. This should never happen but adding the check
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
if (!_focusedTab) {
return null;
}
let tabs = [];
// Only add chat tab if chat is not disabled.
if (!_isChatDisabled) {
tabs.push({
let tabs = [
{
accessibilityLabel: t('chat.tabs.chat'),
countBadge:
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
@@ -509,8 +493,8 @@ const Chat = ({
controlsId: `${ChatTabs.CHAT}-panel`,
icon: IconMessage,
title: t('chat.tabs.chat')
});
}
}
];
if (_isPollsEnabled) {
tabs.push({
@@ -580,8 +564,6 @@ const Chat = ({
{_showNamePrompt
? <DisplayNameForm
isCCTabEnabled = { _isCCTabEnabled }
isChatDisabled = { _isChatDisabled }
isFileSharingEnabled = { _isFileSharingTabEnabled }
isPollsEnabled = { _isPollsEnabled } />
: renderChat()}
<div
@@ -620,7 +602,7 @@ const Chat = ({
* }}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { unreadPollsCount } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
@@ -629,9 +611,8 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_isOpen: isOpen,
_isPollsEnabled: !arePollsDisabled(state),
_isCCTabEnabled: isCCTabEnabled(state),
_isChatDisabled: isChatDisabled(state),
_isFileSharingTabEnabled: isFileSharingEnabled(state),
_focusedTab: getFocusedTab(state),
_focusedTab: focusedTab,
_messages: messages,
_unreadMessagesCount: unreadMessagesCount,
_unreadPollsCount: unreadPollsCount,

View File

@@ -9,7 +9,6 @@ import { IconMessage } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
import { toggleChat } from '../../actions.web';
import { isChatDisabled } from '../../functions';
import ChatCounter from './ChatCounter';
@@ -92,8 +91,7 @@ class ChatButton extends AbstractButton<IProps> {
*/
const mapStateToProps = (state: IReduxState) => {
return {
_chatOpen: state['features/chat'].isOpen,
visible: !isChatDisabled(state)
_chatOpen: state['features/chat'].isOpen
};
};

View File

@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconCloseLarge } from '../../../base/icons/svg';
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
import { toggleChat } from '../../actions.web';
import { ChatTabs } from '../../constants';
import { getFocusedTab, isChatDisabled } from '../../functions';
interface IProps {
@@ -40,8 +40,7 @@ interface IProps {
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const _isChatDisabled = useSelector(isChatDisabled);
const focusedTab = useSelector(getFocusedTab);
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
const onCancel = useCallback(() => {
@@ -57,7 +56,7 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
let title = 'chat.title';
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
if (focusedTab === ChatTabs.CHAT) {
title = 'chat.tabs.chat';
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
title = 'chat.tabs.polls';
@@ -65,11 +64,6 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
title = 'chat.tabs.closedCaptions';
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
title = 'chat.tabs.fileSharing';
} else {
// If the focused tab is not enabled, don't render the header.
// This should not happen in normal circumstances since Chat.tsx already checks
// if any tabs are available before rendering.
return null;
}
return (

View File

@@ -25,16 +25,6 @@ interface IProps extends WithTranslation {
*/
isCCTabEnabled: boolean;
/**
* Whether chat is disabled.
*/
isChatDisabled: boolean;
/**
* Whether file sharing is enabled.
*/
isFileSharingEnabled: boolean;
/**
* Whether the polls feature is enabled or not.
*/
@@ -84,31 +74,18 @@ class DisplayNameForm extends Component<IProps, IState> {
* @returns {ReactElement}
*/
override render() {
const { isCCTabEnabled, isChatDisabled, isFileSharingEnabled, isPollsEnabled, t } = this.props;
const { isCCTabEnabled, isPollsEnabled, t } = this.props;
// Build array of enabled feature names (translated).
const features = [
!isChatDisabled ? t('chat.nickname.featureChat') : '',
isPollsEnabled ? t('chat.nickname.featurePolls') : '',
isFileSharingEnabled ? t('chat.nickname.featureFileSharing') : '',
isCCTabEnabled ? t('chat.nickname.featureClosedCaptions') : ''
].filter(Boolean);
let title = 'chat.nickname.title';
// Return null if no features available - component won't render.
if (features.length === 0) {
return null;
if (isCCTabEnabled && isPollsEnabled) {
title = 'chat.nickname.titleWithPollsAndCC';
} else if (isCCTabEnabled) {
title = 'chat.nickname.titleWithCC';
} else if (isPollsEnabled) {
title = 'chat.nickname.titleWithPolls';
}
// Build translation arguments dynamically: { feature1: "chat", feature2: "polls", ... }
const translationArgs = features.reduce((acc, feature, index) => {
acc[`feature${index + 1}`] = feature;
return acc;
}, {} as Record<string, string>);
// Use dynamic translation key: "titleWith1Features", "titleWith2Features", etc.
const title = t(`chat.nickname.titleWith${features.length}Features`, translationArgs);
return (
<div id = 'nickname'>
<form onSubmit = { this._onSubmit }>

View File

@@ -12,7 +12,6 @@ import Button from '../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
import { copyText } from '../../../base/util/copyText.web';
import { handleLobbyChatInitialized, openChat } from '../../actions.web';
import logger from '../../logger';
export interface IProps {
className?: string;
@@ -126,11 +125,11 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
setShowCopiedMessage(false);
}, 2000);
} else {
logger.error('Failed to copy text');
console.error('Failed to copy text');
}
})
.catch((error: Error) => {
logger.error('Error copying text', error);
.catch(error => {
console.error('Error copying text:', error);
});
handleClose();
}, [ message ]);

View File

@@ -12,14 +12,11 @@ import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { getParticipantById, isPrivateChatEnabled } from '../base/participants/functions';
import { IParticipant } from '../base/participants/types';
import { escapeRegexp } from '../base/util/helpers';
import { arePollsDisabled } from '../conference/functions.any';
import { isFileSharingEnabled } from '../file-sharing/functions.any';
import { getParticipantsPaneWidth } from '../participants-pane/functions';
import { isCCTabEnabled } from '../subtitles/functions.any';
import { VIDEO_SPACE_MIN_SIZE } from '../video-layout/constants';
import { IVisitorChatParticipant } from '../visitors/types';
import { ChatTabs, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
import { IMessage } from './types';
/**
@@ -156,53 +153,6 @@ export function areSmileysDisabled(state: IReduxState) {
return disableChatSmileys;
}
/**
* Returns whether the chat feature is disabled.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} True if chat is disabled, false otherwise.
*/
export function isChatDisabled(state: IReduxState): boolean {
return Boolean(state['features/base/config']?.disableChat);
}
/**
* Gets the default focused tab based on what features are enabled.
* Returns the first available tab in priority order: CHAT -> POLLS -> FILE_SHARING -> CLOSED_CAPTIONS.
*
* @param {IReduxState} state - The redux state.
* @returns {ChatTabs | undefined} The default focused tab.
*/
export function getDefaultFocusedTab(state: IReduxState): ChatTabs | undefined {
if (!isChatDisabled(state)) {
return ChatTabs.CHAT;
}
if (!arePollsDisabled(state)) {
return ChatTabs.POLLS;
}
if (isFileSharingEnabled(state)) {
return ChatTabs.FILE_SHARING;
}
if (isCCTabEnabled(state)) {
return ChatTabs.CLOSED_CAPTIONS;
}
return undefined;
}
/**
* Returns the currently focused tab or the default focused tab if none is set.
*
* @param {IReduxState} state - The redux state.
* @returns {ChatTabs | undefined} The focused tab or undefined if no tabs are available.
*/
export function getFocusedTab(state: IReduxState): ChatTabs | undefined {
return state['features/chat'].focusedTab || getDefaultFocusedTab(state);
}
/**
* Returns the timestamp to display for the message.
*

View File

@@ -1,23 +0,0 @@
import { useSelector } from 'react-redux';
import ChatButton from './components/web/ChatButton';
import { isChatDisabled } from './functions';
const chat = {
key: 'chat',
Content: ChatButton,
group: 2
};
/**
* A hook that returns the chat button if chat is not disabled.
*
* @returns {Object | undefined} - The chat button object or undefined.
*/
export function useChatButton() {
const _isChatDisabled = useSelector(isChatDisabled);
if (!_isChatDisabled) {
return chat;
}
}

View File

@@ -1,3 +0,0 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('app:chat');

View File

@@ -25,8 +25,6 @@ import { IParticipant } from '../base/participants/types';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
import { arePollsDisabled } from '../conference/functions.any';
import { isFileSharingEnabled } from '../file-sharing/functions.any';
import { addGif } from '../gifs/actions';
import { extractGifURL, getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
import { showMessageNotification } from '../notifications/actions';
@@ -36,7 +34,6 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
import { pushReactions } from '../reactions/actions.any';
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
import { isCCTabEnabled } from '../subtitles/functions.any';
import { showToolbox } from '../toolbox/actions';
import { getDisplayName } from '../visitors/functions';
@@ -69,9 +66,7 @@ import {
} from './constants';
import {
getDisplayNameSuffix,
getFocusedTab,
getUnreadCount,
isChatDisabled,
isSendGroupChatDisabled,
isVisitorChatParticipant
} from './functions';
@@ -186,28 +181,23 @@ MiddlewareRegistry.register(store => next => action => {
case SET_FOCUSED_TAB:
case OPEN_CHAT: {
const state = store.getState();
const focusedTab = action.tabId || getFocusedTab(state);
const focusedTab = action.tabId || getState()['features/chat'].focusedTab;
if (focusedTab === ChatTabs.CHAT) {
// Don't allow opening chat if it's disabled AND user is trying to open the CHAT tab.
if (isChatDisabled(state)) {
return next(action);
}
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
const { privateMessageRecipient } = state['features/chat'];
const { privateMessageRecipient } = store.getState()['features/chat'];
if (
isSendGroupChatDisabled(state)
isSendGroupChatDisabled(store.getState())
&& privateMessageRecipient
&& !action.participant
) {
const participant = getParticipantById(state, privateMessageRecipient.id);
const participant = getParticipantById(store.getState(), privateMessageRecipient.id);
if (participant) {
action.participant = participant;
@@ -217,21 +207,7 @@ MiddlewareRegistry.register(store => next => action => {
}
}
} else if (focusedTab === ChatTabs.POLLS) {
// Don't allow opening chat panel if polls are disabled AND user is trying to open the POLLS tab.
if (arePollsDisabled(state)) {
return next(action);
}
dispatch(resetUnreadPollsCount());
// Don't allow opening chat panel if file sharing is disabled AND user is trying to open the
// FILE_SHARING tab.
} else if (focusedTab === ChatTabs.FILE_SHARING && !isFileSharingEnabled(state)) {
return next(action);
// Don't allow opening chat panel if closed captions are disabled AND user is trying to open the
// CLOSED_CAPTIONS tab.
} else if (focusedTab === ChatTabs.CLOSED_CAPTIONS && !isCCTabEnabled(state)) {
return next(action);
}
break;
@@ -263,7 +239,7 @@ MiddlewareRegistry.register(store => next => action => {
const participantExists = getParticipantById(state, shouldSendPrivateMessageTo.id);
if (participantExists || shouldSendPrivateMessageTo.isFromVisitor) {
dispatch(openDialog('ChatPrivacyDialog', ChatPrivacyDialog, {
dispatch(openDialog(ChatPrivacyDialog, {
message: action.message,
participantID: shouldSendPrivateMessageTo.id,
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
@@ -600,11 +576,6 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
const { isOpen: isChatOpen } = state['features/chat'];
const { soundsIncomingMessage: soundEnabled, userSelectedNotifications } = state['features/base/settings'];
// Don't play sound or show notifications if chat is disabled.
if (isChatDisabled(state)) {
return;
}
if (soundEnabled && shouldPlaySound && !isChatOpen) {
dispatch(playSound(INCOMING_MSG_SOUND_ID));
}

View File

@@ -35,7 +35,7 @@ const DEFAULT_STATE = {
privateMessageRecipient: undefined,
lobbyMessageRecipient: undefined,
isLobbyChatActive: false,
focusedTab: undefined,
focusedTab: ChatTabs.CHAT,
isResizing: false,
width: {
current: CHAT_SIZE,
@@ -44,7 +44,7 @@ const DEFAULT_STATE = {
};
export interface IChatState {
focusedTab?: ChatTabs;
focusedTab: ChatTabs;
groupChatWithPermissions: boolean;
isLobbyChatActive: boolean;
isOpen: boolean;

View File

@@ -22,7 +22,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
return;
}
dispatch(openDialog('AlertDialog', AlertDialog, {
dispatch(openDialog(AlertDialog, {
contentKey: {
key: participant ? 'dialog.kickTitle' : 'dialog.kickSystemTitle',
params: {
@@ -52,7 +52,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
// we have to push the opening of the dialog to the queue
// so that we make sure it will be visible after the events
// of conference destroyed are done
setTimeout(() => dispatch(openDialog('AlertDialog', AlertDialog, {
setTimeout(() => dispatch(openDialog(AlertDialog, {
contentKey: {
key: reasonKey
},
@@ -60,7 +60,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
},
onSubmit: () => {
submit?.();
dispatch(hideDialog('AlertDialog', AlertDialog));
dispatch(hideDialog(AlertDialog));
}
})));
};

View File

@@ -17,7 +17,7 @@ import logger from './logger';
*/
export function openLeaveReasonDialog(title?: string) {
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
dispatch(openDialog('LeaveReasonDialog', LeaveReasonDialog, {
dispatch(openDialog(LeaveReasonDialog, {
onClose: resolve,
title
}));

View File

@@ -26,7 +26,7 @@ function SpeakerStatsLabel() {
const { t } = useTranslation();
const onClick = () => {
dispatch(openDialog('SpeakerStats', SpeakerStats, { conference }));
dispatch(openDialog(SpeakerStats, { conference }));
};
if (count <= 2 || _isSpeakerStatsDisabled) {

View File

@@ -327,7 +327,7 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
* @returns {void}
*/
_onOpenBandwidthDialog() {
dispatch(openDialog('BandwidthSettingsDialog', BandwidthSettingsDialog));
dispatch(openDialog(BandwidthSettingsDialog));
}
};
}

View File

@@ -18,7 +18,7 @@ type Options = {
export function showDesktopPicker(options: Options = {}, onSourceChoose: Function) {
const { desktopSharingSources } = options;
return openDialog('DesktopPicker', DesktopPicker, {
return openDialog(DesktopPicker, {
desktopSharingSources,
onSourceChoose
});

View File

@@ -259,29 +259,6 @@ class AudioDevicesSelection extends AbstractDialogTab<IProps, {}> {
const newValue = name === 'channelCount' ? (checked ? 2 : 1) : checked;
if (name === 'channelCount' && newValue === 2) {
super._onChange({
audioSettings: {
autoGainControl: false,
channelCount: 2,
echoCancellation: false,
noiseSuppression: false
}
});
return;
} else if (name !== 'channelCount' && newValue === true) {
super._onChange({
audioSettings: {
...audioSettings,
[name]: newValue,
channelCount: 1
}
});
return;
}
super._onChange({
audioSettings: {
...audioSettings,

View File

@@ -14,7 +14,7 @@ export function openDisplayNamePrompt({ onPostSubmit, validateInput }: {
onPostSubmit?: Function;
validateInput?: Function;
}) {
return openDialog('DisplayNamePrompt', DisplayNamePrompt, {
return openDialog(DisplayNamePrompt, {
onPostSubmit,
validateInput
});

View File

@@ -16,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
case SETTINGS_UPDATED: {
if (action.settings.displayName
&& isDialogOpen(getState, DisplayNamePrompt)) {
dispatch(hideDialog('DisplayNamePrompt', DisplayNamePrompt));
dispatch(hideDialog(DisplayNamePrompt));
}
}
}

View File

@@ -159,7 +159,7 @@ StateListenerRegistry.register(
});
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
dispatch(openDialog('ParticipantVerificationDialog', ParticipantVerificationDialog, { pId,
dispatch(openDialog(ParticipantVerificationDialog, { pId,
sas }));
});

View File

@@ -31,7 +31,7 @@ class EmbedMeetingButton extends AbstractButton<AbstractButtonProps> {
const { dispatch } = this.props;
sendAnalytics(createToolbarEvent('embed.meeting'));
dispatch(openDialog('EmbedMeetingDialog', EmbedMeetingDialog));
dispatch(openDialog(EmbedMeetingDialog));
}
}

View File

@@ -109,7 +109,7 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: st
* @returns {Object}
*/
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
return openDialog('FeedbackDialog', FeedbackDialog, {
return openDialog(FeedbackDialog, {
conference,
onClose,
title

View File

@@ -1,45 +0,0 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconShareDoc } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { openFileSharingPanel } from '../../../chat/actions.any';
import { isFileSharingEnabled } from '../../functions.any';
/**
* Component that renders a button to open the file sharing panel.
*
* @augments AbstractButton
*/
class FileSharingButton extends AbstractButton<AbstractButtonProps> {
override icon = IconShareDoc;
override label = 'toolbar.fileSharing';
override tooltip = 'toolbar.fileSharing';
/**
* Handles clicking the button to open the file sharing panel.
*
* @private
* @returns {void}
*/
override _handleClick() {
const { dispatch } = this.props;
dispatch(openFileSharingPanel());
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {Object} - Mapped props.
*/
function mapStateToProps(state: IReduxState) {
return {
visible: isFileSharingEnabled(state)
};
}
export default translate(connect(mapStateToProps)(FileSharingButton));

View File

@@ -1,23 +0,0 @@
import { useSelector } from 'react-redux';
import FileSharingButton from './components/web/FileSharingButton';
import { isFileSharingEnabled } from './functions.any';
const fileSharing = {
key: 'filesharing',
Content: FileSharingButton,
group: 2
};
/**
* A hook that returns the file sharing button if it is enabled and undefined otherwise.
*
* @returns {Object | undefined} - The file sharing button object or undefined.
*/
export function useFileSharingButton() {
const isEnabled = useSelector(isFileSharingEnabled);
if (isEnabled) {
return fileSharing;
}
}

View File

@@ -1,12 +1,28 @@
import { IReduxState, IStore } from '../app/types';
import {
getActiveSpeakersToBeDisplayed,
getVirtualScreenshareParticipantOwnerId
} from '../base/participants/functions';
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
import { setRemoteParticipants } from './actions';
import { isFilmstripScrollVisible } from './functions';
/**
* Returns an array containing the first `count` items from a set.
*
* @param {Set<T>} set - The set from which to take items.
* @param {number} count - The number of items to take.
* @returns {T[]} An array containing the taken items.
* @private
*/
function _takeFirstN<T>(set: Set<T>, count: number): T[] {
const result: T[] = [];
for (const item of set) {
if (result.length >= count) break;
result.push(item);
}
return result;
}
/**
* Computes the reorderd list of the remote participants.
*
@@ -16,7 +32,7 @@ import { isFilmstripScrollVisible } from './functions';
* @returns {void}
* @private
*/
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string) {
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string): void {
const state = store.getState();
let reorderedParticipants = [];
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
@@ -33,14 +49,26 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
}
const {
dominantSpeaker,
fakeParticipants,
previousSpeakers,
sortedRemoteParticipants
} = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
const remoteParticipants = new Map(sortedRemoteParticipants);
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
const speakers = getActiveSpeakersToBeDisplayed(state);
const speakers: Set<string> = new Set();
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
const config = state['features/base/config'];
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
// Generate the remote active speakers list.
if (dominant && !dominant.local) {
speakers.add(dominant.id);
}
previousSpeakers.forEach(id => speakers.add(id));
for (const screenshare of screenShareParticipants) {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
@@ -57,7 +85,16 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
remoteParticipants.delete(speaker);
}
// Always update the order of the thubmnails.
// Calculate the number of slots available for active speakers and then sort them alphabetically to ensure
// consistent order.
const numberOfActiveSpeakerSlots
= visibleRemoteParticipants.size - (screenShareParticipants.length * 2) - sharedVideos.length;
const activeSpeakersDisplayed = _takeFirstN(speakers, numberOfActiveSpeakerSlots)
.sort((a: string, b: string) => {
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
});
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
@@ -67,10 +104,11 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
return acc;
}, []);
// Always update the order of the thumbnails.
reorderedParticipants = [
...participantsWithScreenShare,
...sharedVideos,
...Array.from(speakers.keys()),
...activeSpeakersDisplayed,
...Array.from(remoteParticipants.keys())
];

View File

@@ -559,7 +559,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {void}
*/
_showFailedInviteAlert() {
this.props.dispatch(openDialog('AlertDialog', AlertDialog, {
this.props.dispatch(openDialog(AlertDialog, {
contentKey: {
key: 'inviteDialog.alertText'
}

View File

@@ -95,7 +95,7 @@ class DialInSummary extends PureComponent<IProps> {
* @returns {void}
*/
_onError() {
this.props.dispatch(openDialog('DialInSummaryErrorDialog', DialInSummaryErrorDialog));
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
}
/**

View File

@@ -41,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => {
function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction) {
const result = next(action);
dispatch(openDialog('AddPeopleDialog', AddPeopleDialog));
dispatch(openDialog(AddPeopleDialog));
return result;
}
@@ -60,7 +60,7 @@ function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction
* @returns {*} The value returned by {@code next(action)}.
*/
function _hideAddPeopleDialog({ dispatch }: IStore, next: Function, action: AnyAction) {
dispatch(hideDialog('AddPeopleDialog', AddPeopleDialog));
dispatch(hideDialog(AddPeopleDialog));
return next(action);
}

View File

@@ -15,7 +15,7 @@ import { isFeatureDisabled } from './functions';
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
if (isFeatureDisabled(getState(), feature)) {
dispatch(openDialog('PremiumFeatureDialog', PremiumFeatureDialog));
dispatch(openDialog(PremiumFeatureDialog));
return true;
}

View File

@@ -11,7 +11,6 @@ import {
getVirtualScreenshareParticipantByOwnerId
} from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { getAutoPinSetting } from '../video-layout/functions';
import {
@@ -19,6 +18,7 @@ import {
SET_LARGE_VIDEO_DIMENSIONS,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes';
import { shouldHideLargeVideo } from './functions';
/**
* Action to select the participant to be displayed in LargeVideo based on the
@@ -34,12 +34,8 @@ export function selectParticipantInLargeVideo(participant?: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
if (isStageFilmstripAvailable(state, 2)) {
return;
}
// Keep Etherpad open.
if (state['features/etherpad'].editing) {
// Skip large video updates when the large video container is hidden.
if (shouldHideLargeVideo(state)) {
return;
}
@@ -165,18 +161,26 @@ function _electParticipantInLargeVideo(state: IReduxState) {
}
}
// Next, pick the dominant speaker (other than self).
// Next, pick the dominant speaker or the last active speaker if the dominant speaker is local.
participant = getDominantSpeakerParticipant(state);
if (participant && !participant.local) {
// Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
// auto_pin_latest_screen_share setting is disabled.
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
let speakerId: string | undefined;
return screenshareParticipant?.id ?? participant.id;
if (participant?.local) {
const { previousSpeakers } = state['features/base/participants'];
if (previousSpeakers?.size) {
speakerId = previousSpeakers.keys().next().value;
}
} else if (participant) {
speakerId = participant.id;
}
// In case this is the local participant.
participant = undefined;
// Return the screensharing participant id associated with this endpoint.
if (speakerId) {
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, speakerId);
return screenshareParticipant?.id ?? speakerId;
}
// Next, pick the most recent participant with video.
const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);

View File

@@ -1,5 +1,7 @@
import { IReduxState } from '../app/types';
import { getParticipantById } from '../base/participants/functions';
import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { shouldDisplayTileView } from '../video-layout/functions.any';
/**
* Selector for the participant currently displaying on the large video.
@@ -12,3 +14,17 @@ export function getLargeVideoParticipant(state: IReduxState) {
return getParticipantById(state, participantId ?? '');
}
/**
* Determines whether the large video container should be hidden.
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
* or when editing etherpad.
*
* @param {IReduxState} state - The Redux state.
* @returns {boolean} True if large video should be hidden, false otherwise.
*/
export function shouldHideLargeVideo(state: IReduxState): boolean {
return shouldDisplayTileView(state)
|| isStageFilmstripAvailable(state, 2)
|| Boolean(state['features/etherpad']?.editing);
}

View File

@@ -0,0 +1,26 @@
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
import { selectParticipantInLargeVideo } from './actions.any';
import { shouldHideLargeVideo } from './functions';
/**
* Updates the large video when transitioning from a hidden state to visible state.
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
* whiteboard, or etherpad editing modes.
*/
StateListenerRegistry.register(
/* selector */ state => shouldHideLargeVideo(state),
/* listener */ (isHidden, { dispatch }) => {
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
// Otherwise set it to undefined because we don't show the large video.
if (!isHidden) {
dispatch(selectParticipantInLargeVideo());
} else {
dispatch({
type: SELECT_LARGE_VIDEO_PARTICIPANT,
participantId: undefined
});
}
}
);

View File

@@ -0,0 +1 @@
import './subscriber.any';

View File

@@ -4,6 +4,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
import { getLargeVideoParticipant } from './functions';
import './subscriber.any';
/**
* Updates the on stage participant video.

View File

@@ -467,7 +467,7 @@ export function _mapStateToProps(state: IReduxState) {
_knocking: knocking,
_lobbyChatMessages: messages,
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
_login: showModeratorLogin && !state['features/base/jwt'].jwt,
_login: showModeratorLogin,
_hangUp: showHangUp,
_isLobbyChatActive: isLobbyChatActive,
_meetingName: getConferenceName(state),

View File

@@ -4,6 +4,7 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../../../app/types';
import {
getClientHeight,
getClientWidth
@@ -11,7 +12,6 @@ import {
import { setFocusedTab } from '../../../../../chat/actions.any';
import Chat from '../../../../../chat/components/native/Chat';
import { ChatTabs } from '../../../../../chat/constants';
import { getFocusedTab } from '../../../../../chat/functions';
import { resetUnreadPollsCount } from '../../../../../polls/actions';
import PollsPane from '../../../../../polls/components/native/PollsPane';
import { screen } from '../../../routes';
@@ -23,8 +23,8 @@ const ChatAndPolls = () => {
const clientHeight = useSelector(getClientHeight);
const clientWidth = useSelector(getClientWidth);
const dispatch = useDispatch();
const currentFocusedTab = useSelector(getFocusedTab);
const initialRouteName = currentFocusedTab === ChatTabs.POLLS
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const initialRouteName = focusedTab === ChatTabs.POLLS
? screen.conference.chatandpolls.tab.polls
: screen.conference.chatandpolls.tab.chat;

View File

@@ -1,4 +1,3 @@
import { querySelector, querySelectorAll } from '@jitsi/js-utils/polyfills/querySelectorPolyfill';
import { DOMParser } from '@xmldom/xmldom';
import { atob, btoa } from 'abab';
import { NativeModules, Platform } from 'react-native';
@@ -9,6 +8,7 @@ import 'promise.withresolvers/auto'; // Promise.withResolvers.
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
import Storage from './Storage';
import { querySelector, querySelectorAll } from './querySelectorPolyfill';
const { AppInfo } = NativeModules;
@@ -193,7 +193,7 @@ const { AppInfo } = NativeModules;
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof document.querySelector === 'undefined') {
document.querySelector = function(selectors) {
return querySelector(this, selectors);
return this.documentElement ? querySelector(this.documentElement, selectors) : null;
};
}
@@ -203,7 +203,7 @@ const { AppInfo } = NativeModules;
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof document.querySelectorAll === 'undefined') {
document.querySelectorAll = function(selectors) {
return querySelectorAll(this, selectors);
return this.documentElement ? querySelectorAll(this.documentElement, selectors) : [];
};
}
@@ -217,7 +217,7 @@ const { AppInfo } = NativeModules;
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof documentPrototype.querySelector === 'undefined') {
documentPrototype.querySelector = function(selectors) {
return querySelector(this, selectors);
return this.documentElement ? querySelector(this.documentElement, selectors) : null;
};
}
@@ -227,7 +227,7 @@ const { AppInfo } = NativeModules;
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof documentPrototype.querySelectorAll === 'undefined') {
documentPrototype.querySelectorAll = function(selectors) {
return querySelectorAll(this, selectors);
return this.documentElement ? querySelectorAll(this.documentElement, selectors) : [];
};
}
}

View File

@@ -0,0 +1,282 @@
// Regex constants for efficient reuse across selector parsing
const SIMPLE_TAG_NAME_REGEX = /^[a-zA-Z][\w-]*$/;
const MULTI_ATTRIBUTE_SELECTOR_REGEX = /^([a-zA-Z][\w-]*)?(\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\])+$/;
const SINGLE_ATTRIBUTE_REGEX = /\[(?:\*\|)?([^=\]]+)=["']?([^"'\]]+)["']?\]/g;
const WHITESPACE_AROUND_COMBINATOR_REGEX = /\s*>\s*/g;
/**
* Parses a CSS selector into reusable components.
*
* @param {string} selector - The CSS selector to parse.
* @returns {Object} - Object with tagName and attrConditions properties.
*/
function _parseSelector(selector) {
// Wildcard selector
if (selector === '*') {
return {
tagName: null, // null means match all tag names
attrConditions: []
};
}
// Simple tag name
if (SIMPLE_TAG_NAME_REGEX.test(selector)) {
return {
tagName: selector,
attrConditions: []
};
}
// Attribute selector: tagname[attr="value"] or
// tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
const multiAttrMatch = selector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX);
if (multiAttrMatch) {
const tagName = multiAttrMatch[1];
const attrConditions = [];
let attrMatch;
while ((attrMatch = SINGLE_ATTRIBUTE_REGEX.exec(selector)) !== null) {
attrConditions.push({
name: attrMatch[1], // This properly strips the *| prefix
value: attrMatch[2]
});
}
return {
tagName,
attrConditions
};
}
// Unsupported selector
throw new SyntaxError(`Unsupported selector pattern: '${selector}'`);
}
/**
* Filters elements by selector pattern and handles findFirst logic.
*
* @param {Element[]} elements - Array of elements to filter.
* @param {string} selector - CSS selector to match against.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Filtered results with proper return type.
*/
function _filterAndMatchElements(elements, selector, findFirst) {
const { tagName, attrConditions } = _parseSelector(selector);
const results = [];
for (const element of elements) {
// Check tag name if specified
if (tagName && !(element.localName === tagName || element.tagName === tagName)) {
continue;
}
// Check if all attribute conditions match
const allMatch = attrConditions.every(condition =>
element.getAttribute(condition.name) === condition.value
);
if (allMatch) {
results.push(element);
if (findFirst) {
return element;
}
}
}
return findFirst ? null : results;
}
/**
* Handles direct child traversal for selectors with > combinators.
* This is the shared logic used by both scope selectors and regular direct child selectors.
*
* @param {Element[]} startElements - Array of starting elements to traverse from.
* @param {string[]} selectorParts - Array of selector parts split by '>'.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _traverseDirectChildren(startElements, selectorParts, findFirst) {
let currentElements = startElements;
for (const part of selectorParts) {
const nextElements = [];
currentElements.forEach(el => {
// Get direct children
const directChildren = Array.from(el.children || []);
// Use same helper as handlers
const matchingChildren = _filterAndMatchElements(directChildren, part, false);
nextElements.push(...matchingChildren);
});
currentElements = nextElements;
// If we have no results, we can stop early (applies to both querySelector and querySelectorAll)
if (currentElements.length === 0) {
return findFirst ? null : [];
}
}
return findFirst ? currentElements[0] || null : currentElements;
}
/**
* Handles :scope pseudo-selector cases with direct child combinators.
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _handleScopeSelector(node, selector, findFirst) {
let searchSelector = selector.substring(6);
// Handle :scope > tagname (direct children)
if (searchSelector.startsWith('>')) {
searchSelector = searchSelector.substring(1);
// Split by > and use shared traversal logic
const parts = searchSelector.split('>');
// Start from the node itself (scope)
return _traverseDirectChildren([ node ], parts, findFirst);
}
return null;
}
/**
* Handles nested > selectors (direct child combinators).
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _handleDirectChildSelectors(node, selector, findFirst) {
const parts = selector.split('>');
// First find elements matching the first part (this could be descendants, not just direct children)
const startElements = _querySelectorInternal(node, parts[0], false);
// If no starting elements found, return early
if (startElements.length === 0) {
return findFirst ? null : [];
}
// Use shared traversal logic for the remaining parts
return _traverseDirectChildren(startElements, parts.slice(1), findFirst);
}
/**
* Handles simple tag name selectors.
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _handleSimpleTagSelector(node, selector, findFirst) {
const elements = Array.from(node.getElementsByTagName(selector));
if (findFirst) {
return elements[0] || null;
}
return elements;
}
/**
* Handles attribute selectors: tagname[attr="value"] or tagname[attr1="value1"][attr2="value2"].
* Supports single or multiple attributes with optional wildcard namespace (*|).
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _handleAttributeSelector(node, selector, findFirst) {
const { tagName } = _parseSelector(selector); // Just to get tagName for optimization
// Handler's job: find the right elements to search
const elementsToCheck = tagName
? Array.from(node.getElementsByTagName(tagName))
: Array.from(node.getElementsByTagName('*'));
// Common helper does the matching
return _filterAndMatchElements(elementsToCheck, selector, findFirst);
}
/**
* Internal function that implements the core selector matching logic for both
* querySelector and querySelectorAll. Supports :scope pseudo-selector, direct
* child selectors, and common CSS selectors.
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector to match elements against.
* @param {boolean} findFirst - If true, return after finding the first match.
* @returns {Element[]|Element|null} - Array of Elements for querySelectorAll,
* single Element or null for querySelector.
*/
function _querySelectorInternal(node, selector, findFirst = false) {
// Normalize whitespace around > combinators first
const normalizedSelector = selector.replace(WHITESPACE_AROUND_COMBINATOR_REGEX, '>');
// Handle :scope pseudo-selector
if (normalizedSelector.startsWith(':scope')) {
return _handleScopeSelector(node, normalizedSelector, findFirst);
}
// Handle nested > selectors (direct child combinators)
if (normalizedSelector.includes('>')) {
return _handleDirectChildSelectors(node, normalizedSelector, findFirst);
}
// Fast path: simple tag name
if (normalizedSelector === '*' || SIMPLE_TAG_NAME_REGEX.test(normalizedSelector)) {
return _handleSimpleTagSelector(node, normalizedSelector, findFirst);
}
// Attribute selector: tagname[attr="value"] or
// tagname[attr1="value1"][attr2="value2"] (with optional wildcard namespace)
if (normalizedSelector.match(MULTI_ATTRIBUTE_SELECTOR_REGEX)) {
return _handleAttributeSelector(node, normalizedSelector, findFirst);
}
// Unsupported selector - throw SyntaxError to match browser behavior
throw new SyntaxError(`Failed to execute 'querySelector${
findFirst ? '' : 'All'}' on 'Element': '${selector}' is not a valid selector.`);
}
/**
* Implements querySelector functionality using the shared internal logic.
* Supports the same selectors as querySelectorAll but returns only the first match.
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selectors - The CSS selector to match elements against.
* @returns {Element|null} - The first Element which matches the selector, or null.
*/
export function querySelector(node, selectors) {
return _querySelectorInternal(node, selectors, true);
}
/**
* Implements querySelectorAll functionality using the shared internal logic.
* Supports :scope pseudo-selector, direct child selectors, and common CSS selectors.
*
* @param {Node} node - The Node which is the root of the tree to query.
* @param {string} selector - The CSS selector to match elements against.
* @returns {Element[]} - Array of Elements matching the selector.
*/
export function querySelectorAll(node, selector) {
return _querySelectorInternal(node, selector, false);
}

View File

@@ -13,7 +13,7 @@ import PageReloadDialog from '../base/dialog/components/native/PageReloadDialog'
*/
export function openPageReloadDialog(
conferenceError?: Error, configError?: Error, connectionError?: ConnectionFailedError) {
return openDialog('PageReloadDialog', PageReloadDialog, {
return openDialog(PageReloadDialog, {
conferenceError,
configError,
connectionError

View File

@@ -34,7 +34,7 @@ export default function RenameButton({ breakoutRoomJid, name }: IProps) {
const dispatch = useDispatch();
const { classes, cx } = useStyles();
const onRename = useCallback(() => {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
dispatch(openDialog(BreakoutRoomNamePrompt, {
breakoutRoomJid,
initialRoomName: name
}));

View File

@@ -68,7 +68,7 @@ export const RoomContextMenu = ({
}, [ dispatch, room ]);
const onRenameBreakoutRoom = useCallback(() => {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
dispatch(openDialog(BreakoutRoomNamePrompt, {
breakoutRoomJid: room?.jid,
initialRoomName: room?.name
}));

View File

@@ -32,7 +32,7 @@ import styles from './styles';
export const ContextMenuMore = () => {
const dispatch = useDispatch();
const muteAllVideo = useCallback(() => {
dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog));
dispatch(openDialog(MuteEveryonesVideoDialog));
dispatch(hideSheet());
}, [ dispatch ]);
const conference = useSelector(getCurrentConference);

View File

@@ -42,7 +42,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true)
);
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
const muteAll = useCallback(() => dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog)),
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
[ dispatch ]);
const showMoreActions = useSelector(isMoreActionsVisible);
const showMuteAll = useSelector(isMuteAllVisible);

View File

@@ -112,10 +112,10 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
const { classes } = useStyles();
const muteAllVideo = useCallback(
() => dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog)), [ dispatch ]);
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
const muteAllDesktop = useCallback(
() => dispatch(openDialog('MuteEveryonesDesktopDialog', MuteEveryonesDesktopDialog)), [ dispatch ]);
() => dispatch(openDialog(MuteEveryonesDesktopDialog)), [ dispatch ]);
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));

View File

@@ -181,7 +181,7 @@ const ParticipantsPane = () => {
}, []);
const onMuteAll = useCallback(() => {
dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog));
dispatch(openDialog(MuteEveryoneDialog));
}, []);
const onToggleContext = useCallback(() => {

View File

@@ -1,45 +0,0 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconInfo } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { openPollsPanel } from '../../../chat/actions.any';
import { arePollsDisabled } from '../../../conference/functions.any';
/**
* Component that renders a button to open the polls panel.
*
* @augments AbstractButton
*/
class PollsButton extends AbstractButton<AbstractButtonProps> {
override icon = IconInfo;
override label = 'toolbar.polls';
override tooltip = 'toolbar.polls';
/**
* Handles clicking the button to open the polls panel.
*
* @private
* @returns {void}
*/
override _handleClick() {
const { dispatch } = this.props;
dispatch(openPollsPanel());
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {Object} - Mapped props.
*/
function mapStateToProps(state: IReduxState) {
return {
visible: !arePollsDisabled(state)
};
}
export default translate(connect(mapStateToProps)(PollsButton));

View File

@@ -1,24 +0,0 @@
import { useSelector } from 'react-redux';
import { arePollsDisabled } from '../conference/functions.any';
import PollsButton from './components/web/PollsButton';
const polls = {
key: 'polls',
Content: PollsButton,
group: 2
};
/**
* A hook that returns the polls button if it is enabled and undefined otherwise.
*
* @returns {Object | undefined} - The polls button object or undefined.
*/
export function usePollsButton() {
const isPollsDisabled = useSelector(arePollsDisabled);
if (!isPollsDisabled) {
return polls;
}
}

View File

@@ -11,7 +11,6 @@ import {
RESET_UNREAD_POLLS_COUNT,
SAVE_POLL
} from './actionTypes';
import logger from './logger';
import { IIncomingAnswerData, IPollData } from './types';
const INITIAL_STATE = {
@@ -88,7 +87,7 @@ ReducerRegistry.register<IPollsState>(STORE_NAME, (state = INITIAL_STATE, action
// if the poll doesn't exist
if (!(pollId in state.polls)) {
logger.warn('Requested poll does not exist', { pollId });
console.warn('requested poll does not exist: pollId ', pollId);
return state;
}

View File

@@ -110,7 +110,7 @@ class ReactionMenuDialog extends PureComponent<IProps> {
*/
_onCancel() {
if (this.props._isOpen) {
this.props.dispatch(hideDialog('ReactionMenu_', ReactionMenu_));
this.props.dispatch(hideDialog(ReactionMenu_));
return true;
}

View File

@@ -47,7 +47,7 @@ class ReactionsMenuButton extends AbstractButton<IProps> {
* @returns {void}
*/
override _handleClick() {
this.props.dispatch(openDialog('ReactionMenuDialog', ReactionMenuDialog));
this.props.dispatch(openDialog(ReactionMenuDialog));
}
/**

View File

@@ -76,7 +76,7 @@ export function showRecordingLimitNotification(streamType: string) {
*/
export function showStartRecordingNotification() {
return (dispatch: IStore['dispatch']) => {
const openDialogCallback = () => dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
const openDialogCallback = () => dispatch(openDialog(StartRecordingDialog));
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
};

View File

@@ -34,7 +34,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
const { _isLiveStreamRunning, dispatch } = this.props;
if (_isLiveStreamRunning) {
dispatch(openDialog('StopLiveStreamDialog', StopLiveStreamDialog));
dispatch(openDialog(StopLiveStreamDialog));
} else {
navigate(screen.conference.liveStream);
}

View File

@@ -26,10 +26,10 @@ class LiveStreamButton extends AbstractLiveStreamButton<IProps> {
*/
override _onHandleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
const dialogComponent = _isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog;
const dialogName = _isLiveStreamRunning ? 'StopLiveStreamDialog' : 'StartLiveStreamDialog';
dispatch(openDialog(dialogName, dialogComponent));
dispatch(openDialog(
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
));
}
}

View File

@@ -82,7 +82,7 @@ export default class AbstractHighlightButton<P extends IProps, S={}> extends Com
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
dispatch(openDialog(StartRecordingDialog));
}
} ],
appearance: NOTIFICATION_TYPE.NORMAL

Some files were not shown because too many files have changed in this diff Show More