Compare commits

..

1 Commits

Author SHA1 Message Date
damencho
dfbdb8c409 fix(recording): Fixes inviting jigasi when backend recording is enabled. 2025-12-15 16:41:02 -06:00
73 changed files with 2491 additions and 5655 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/checkout@v6
- 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -42,8 +42,8 @@ jobs:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -103,21 +103,10 @@ jobs:
android-sdk-build:
name: Build mobile SDK (Android)
runs-on: ubuntu-latest
container:
image: reactnativecommunity/react-native-android:v15.0
volumes:
- /usr:/host/usr
container: reactnativecommunity/react-native-android:v15.0
steps:
- name: Make space in image by removing preinstalled, but unused SDKs
run: |
df -h /
rm -rf /host/usr/local/lib/android
rm -rf /host/usr/local/.ghcup
rm -rf /host/usr/share/dotnet
rm -rf /host/usr/share/swift
df -h /
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -137,8 +126,8 @@ jobs:
name: Build mobile SDK (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -160,7 +149,7 @@ jobs:
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
- name: setup-cocoapods
uses: ruby/setup-ruby@4fc31e1c823882afd7ef55985266a526c589de90 #v1.282.0
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
@@ -187,8 +176,8 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 #v6.2.0
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 #v10.1.0
- 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

@@ -264,4 +264,4 @@ react/features/sample/
- [Jitsi Handbook](https://jitsi.github.io/handbook/) - Comprehensive documentation
- [Community Forum](https://community.jitsi.org/) - Ask questions and get support
- [Architecture Guide](https://jitsi.github.io/handbook/docs/architecture) - System overview
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) - Detailed contribution process
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/contributing) - Detailed contribution process

View File

@@ -30,9 +30,6 @@ import com.facebook.react.ReactRootView;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
public class JitsiMeetView extends FrameLayout {
@@ -87,10 +84,6 @@ public class JitsiMeetView extends FrameLayout {
result.putInt(key, (int)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else if (valueType.contentEquals("String[]")) {
// Convert String[] to ArrayList<String> for React Native bridge compatibility
String[] stringArray = (String[]) bValue;
result.putStringArrayList(key, new ArrayList<>(Arrays.asList(stringArray)));
} else {
throw new RuntimeException("Unsupported type: " + valueType);
}

View File

@@ -151,9 +151,6 @@ var config = {
// Disables self-view settings in UI
// disableSelfViewSettings: false,
// Shows/hides the moderator setting for chat permissions.
// showChatPermissionsModeratorSetting: false,
// screenshotCapture : {
// Enables the screensharing capture feature.
// enabled: false,
@@ -927,11 +924,6 @@ var config = {
// [ 'microphone', 'camera' ]
// ],
// Overrides the buttons displayed in the main toolbar for reduced UI.
// When there isn't an override for a certain configuration the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// reducedUImainToolbarButtons: [ 'microphone', 'camera' ],
// Toolbar buttons which have their click/tap event exposed through the API on
// `toolbarButtonClicked`. Passing a string for the button key will
// prevent execution of the click/tap routine; passing an object with `key` and

View File

@@ -10,7 +10,6 @@ no-cli
no-loopback-peers
no-tcp-relay
no-tcp
no-dtls
listening-port=3478
tls-listening-port=5349
no-tlsv1

View File

@@ -31,7 +31,6 @@
"it": "Italiano",
"ja": "日本語",
"kab": "Taqbaylit",
"kk": "Қазақша",
"ko": "한국어",
"lt": "Lietuvių",
"lv": "Latviešu",

File diff suppressed because it is too large Load Diff

View File

@@ -109,15 +109,9 @@
}
},
"chat": {
"disabled": "Chat-Nachrichten sind deaktiviert.",
"enter": "Chat-Raum betreten",
"error": "Fehler: Ihre Nachricht wurde nicht versendet. Grund: {{error}}",
"everyone": "Alle",
"fieldPlaceHolder": "Geben Sie Ihre Nachricht hier ein",
"fileAccessibleTitle": "{{user}} hat eine Datei hochgeladen",
"fileAccessibleTitleMe": "Ich habe eine Datei hochgeladen",
"fileDeleted": "Eine Datei wurde gelöscht",
"guestsChatIndicator": "(Gast)",
"lobbyChatMessageTo": "Lobby-Nachricht an {{recipient}}",
"message": "Nachricht",
"messageAccessibleTitle": "{{user}} sagt:",
@@ -126,20 +120,11 @@
"messagebox": "Nachricht eingeben",
"newMessages": "Neue Nachrichten",
"nickname": {
"featureChat": "Chat",
"featureClosedCaptions": "Untertitel",
"featureFileSharing": "Dateien",
"featurePolls": "Umfragen",
"popover": "Wähle einen Alias",
"title": "Geben Sie einen Alias zum Chatten ein",
"titleWith1Features": "Geben Sie einen Alias ein, um {{feature1}} zu nutzen",
"titleWith2Features": "Geben Sie einen Alias ein, um {{feature1}} und {{feature2}} zu nutzen",
"titleWith3Features": "Geben Sie einen Alias ein, um {{feature1}}, {{feature2}} und {{feature3}} zu nutzen",
"titleWith4Features": "Geben Sie einen Alias ein, um {{feature1}}, {{feature2}}, {{feature3}} und {{feature4}} zu nutzen",
"titleWithCC": "Geben Sie einen Alias zum Chatten und für Untertitel ein",
"titleWithPolls": "Geben Sie einen Alias zum Chatten und für Umfragen ein",
"titleWithPollsAndCC": "Geben Sie einen Alias zum Chatten, für Umfragen und Untertitel ein",
"titleWithPollsAndCCAndFileSharing": "Geben Sie einen Alias zum Chatten, für Umfragen, Untertitel und Dateien ein"
"titleWithPollsAndCC": "Geben Sie einen Alias zum Chatten, für Umfragen und Untertitel ein"
},
"noMessagesMessage": "Es gibt noch keine Nachricht in dieser Konferenz. Starten Sie hier eine Unterhaltung!",
"privateNotice": "Private Nachricht an {{recipient}}",
@@ -147,16 +132,14 @@
"smileysPanel": "Emoji-Auswahl",
"systemDisplayName": "System",
"tabs": {
"chat": "Chat",
"chat": "Chatten",
"closedCaptions": "Untertitel",
"fileSharing": "Dateien",
"polls": "Umfragen"
},
"title": "Chat",
"titleWithCC": "Untertitel",
"titleWithFeatures": "Chat und",
"titleWithFileSharing": "Dateien",
"titleWithPolls": "Umfragen",
"title": "Chatten",
"titleWithCC": "Chatten und Untertitel",
"titleWithPolls": "Chatten und Umfragen",
"titleWithPollsAndCC": "Chatten, Umfragen und Untertitel",
"you": "Sie"
},
"chromeExtensionBanner": {
@@ -291,6 +274,7 @@
"Submit": "OK",
"Understand": "Verstanden, Stummschaltung beibehalten",
"UnderstandAndUnmute": "Verstanden, bitte Stummschaltung aufheben",
"WaitForHostMsg": "Die Konferenz wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostNoAuthMsg": "Die Konferenz wurde noch nicht gestartet. Bitte warten Sie, bis die Konferenz gestartet wird.",
"WaitingForHostButton": "Auf Moderation warten",
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
@@ -312,12 +296,6 @@
"alreadySharedVideoTitle": "Nur ein geteiltes Video gleichzeitig",
"applicationWindow": "Anwendungsfenster",
"authenticationRequired": "Authentifizierung benötigt",
"cameraCaptureDialog": {
"description": "Ein Bild mit Ihrer Kamera aufnehmen und senden",
"ok": "Kamera starten",
"reject": "Jetzt nicht",
"title": "Ein Bild aufnehmen"
},
"cameraConstraintFailedError": "Ihre Kamera erfüllt die notwendigen Anforderungen nicht.",
"cameraNotFoundError": "Kamera nicht gefunden.",
"cameraNotSendingData": "Die Kamera ist nicht verfügbar. Bitte prüfen, ob eine andere Applikation die Kamera verwendet, eine andere Kamera vom Einstellungs-Menü auswählen oder die Applikation neu laden.",
@@ -393,34 +371,22 @@
"micTimeoutError": "Audioquelle konnte nicht gestartet werden. Zeitüberschreitung",
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
"moderationAudioLabel": "Erlaube Anwesenden die Stummschaltung für sich aufzuheben",
"moderationDesktopLabel": "Erlaube Anwesenden ihren Bildschirm freizugeben",
"moderationVideoLabel": "Erlaube Anwesenden ihre Kamera einzuschalten",
"muteEveryoneDialog": "Wollen Sie wirklich alle stummschalten? Sie können deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
"muteEveryoneDialogModerationOn": "Die Anwesenden können eine Anfrage zum Sprechen jederzeit senden.",
"muteEveryoneElseDialog": "Einmal stummgeschaltet, können Sie deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschalten?",
"muteEveryoneElsesDesktopDialog": "Sobald die Bildschirmfreigaben beendet sind, können Sie diese nicht mehr starten, aber die anderen können sie jederzeit wieder starten.",
"muteEveryoneElsesDesktopTitle": "Alle Bildschirmfreigaben außer {{whom}} beenden?",
"muteEveryoneElsesVideoDialog": "Sobald die Kamera für alle anderen Personen deaktiviert ist, können Sie diese nicht wieder für alle einschalten, die anderen Personen können ihre Kamera aber jederzeit wieder einschalten.",
"muteEveryoneElsesVideoTitle": "Die Kamera von allen außer {{whom}} ausschalten?",
"muteEveryoneSelf": "sich selbst",
"muteEveryoneStartMuted": "Alle beginnen von jetzt an stummgeschaltet",
"muteEveryoneTitle": "Alle stummschalten?",
"muteEveryonesDesktopDialog": "Die Anwesenden können ihren Bildschirm jederzeit freigeben.",
"muteEveryonesDesktopDialogModerationOn": "Die Anwesenden können jederzeit eine Anfrage zur Bildschirmfreigabe senden.",
"muteEveryonesDesktopTitle": "Alle Bildschirmfreigaben beenden?",
"muteEveryonesVideoDialog": "Sind Sie sicher, dass Sie die Kamera von allen Personen deaktivieren möchten? Sie können dies nicht wieder rückgängig machen, jede Personen kann ihre Kamera aber jederzeit wieder einschalten.",
"muteEveryonesVideoDialogModerationOn": "Die Anwesenden können jederzeit eine Anfrage senden, um ihre Kamera einzuschalten.",
"muteEveryonesVideoDialogOk": "deaktivieren",
"muteEveryonesVideoTitle": "Die Kamera von allen anderen ausschalten?",
"muteParticipantBody": "Sie können die Stummschaltung anderer Personen nicht aufheben, aber eine Person kann ihre eigene Stummschaltung jederzeit beenden.",
"muteParticipantButton": "Stummschalten",
"muteParticipantsDesktopBody": "Sie können Bildschirmfreigaben von anderen nicht starten, sie können dies aber jederzeit.",
"muteParticipantsDesktopBodyModerationOn": "Weder Sie noch andere Anwesende können Bildschirmfreigaben starten.",
"muteParticipantsDesktopButton": "Bildschirmfreigabe beenden",
"muteParticipantsDesktopDialog": "Sind Sie sicher, dass Sie die Bildschirmfreigabe von dieser Person beenden möchten? Sie können diese nicht mehr starten, die Person aber jederzeit.",
"muteParticipantsDesktopDialogModerationOn": "Sind Sie sicher, dass Sie die Bildschirmfreigabe von dieser Person beenden möchten? Weder Sie noch die Person kann die Bildschirmfreigabe wieder starten.",
"muteParticipantsDesktopTitle": "Bildschirmfreigabe von dieser Person beenden?",
"muteParticipantsVideoBody": "Sie können die Kamera nicht wieder einschalten, die Person kann ihre Kamera aber jederzeit wieder einschalten.",
"muteParticipantsVideoBodyModerationOn": "Sie können die Kamera nicht wieder aktivieren und die Person selbst auch nicht.",
"muteParticipantsVideoButton": "Kamera ausschalten",
@@ -533,7 +499,6 @@
"tokenAuthFailedWithReasons": "Teilnahme an der Konferenz fehlgeschlagen. Möglicher Grund: {{reason}}",
"tokenAuthUnsupported": "Token-Authentifizierung wird nicht unterstützt.",
"transcribing": "Wird transkribiert",
"unauthenticatedAccessDisabled": "Zur Teilnahme an dieser Konferenz müssen Sie sich anmelden.",
"unlockRoom": "Konferenz$t(lockRoomPassword) entfernen",
"user": "Anmeldename",
"userIdentifier": "Benutzername",
@@ -574,25 +539,6 @@
"veryBad": "Sehr schlecht",
"veryGood": "Sehr gut"
},
"fileSharing": {
"downloadFailedDescription": "Bitte nochmals versuchen.",
"downloadFailedTitle": "Download fehlgeschlagen",
"downloadFile": "Download",
"downloadStarted": "Download gestartet",
"dragAndDrop": "Dateien hier oder irgendwo auf dem Bildschirm loslassen",
"fileAlreadyUploaded": "Datei wurde schon zur Konferenz hochgeladen.",
"fileRemovedByOther": "Ihre Datei '{{ fileName }}' wurde entfernt",
"fileTooLargeDescription": "Bitte stellen Sie sicher, dass Ihre Datei nicht die Maximalgröße von {{ maxFileSize }} überschreitet.",
"fileTooLargeTitle": "Die ausgewählte Datei ist zu groß",
"fileUploadProgress": "Datei wird hochgeladen",
"fileUploadedSuccessfully": "Datei erfolgreich hochgeladen",
"newFileNotification": "{{ participantName }} hat Datei '{{ fileName }}' hochgeladen",
"removeFile": "Entfernen",
"removeFileSuccess": "Datei erfolgreich entfernt",
"uploadFailedDescription": "Bitte versuchen Sie es erneut.",
"uploadFailedTitle": "Dateiupload fehlgeschlagen",
"uploadFile": "Datei hochladen"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Videominiaturen"
@@ -761,8 +707,7 @@
"notificationTitle": "Lobby",
"passwordJoinButton": "Beitreten",
"title": "Lobby",
"toggleLabel": "Lobby aktivieren",
"waitForModerator": "Die Konferenz wurde noch nicht gestartet, da noch keine Moderation anwesend ist. Wenn Sie zur Moderation gehören, melden Sie sich bitte an, ansonsten warten Sie bitte."
"toggleLabel": "Lobby aktivieren"
},
"localRecording": {
"clientState": {
@@ -805,9 +750,8 @@
"me": "ich",
"notify": {
"OldElectronAPPTitle": "Sicherheitslücke!",
"allowAll": "Alles einschalten",
"allowAudio": "Mikrofon einschalten",
"allowDesktop": "Bildschirmfreigabe einschalten",
"allowBoth": "Beides",
"allowVideo": "Kamera einschalten",
"allowedUnmute": "Sie können die Stummschaltung aufheben, Ihre Kamera einschalten oder Ihren Bildschirm teilen.",
"audioUnmuteBlockedDescription": "Díe Stummschaltung kann aus Überlastungsschutzgründen temporär nicht aufgehoben werden.",
@@ -821,7 +765,6 @@
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
"dataChannelClosedDescriptionWithAudio": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher können Video- und Tonprobleme auftreten.",
"dataChannelClosedWithAudio": "Ton- und Videoqualität können beeinträchtigt sein",
"desktopMutedRemotelyTitle": "Ihre Bildschirmfreigabe wurde von {{participantDisplayName}} gestoppt",
"disabledIframe": "Die Einbettung ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondaryNative": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondaryWeb": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet. Bitte nutzen Sie <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> für produktive Zwecke!",
@@ -879,7 +822,6 @@
"oldElectronClientDescription1": "Sie scheinen eine alte Version des Jitsi-Meet-Clients zu nutzen. Diese hat bekannte Schwachstellen. Bitte aktualisieren Sie auf unsere ",
"oldElectronClientDescription2": "aktuelle Version",
"oldElectronClientDescription3": "!",
"openChat": "Chat öffnen",
"participantWantsToJoin": "Möchte an der Konferenz teilnehmen",
"participantsWantToJoin": "Möchten an der Konferenz teilnehmen",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person entfernt",
@@ -903,7 +845,6 @@
"suggestRecordingDescription": "Möchten Sie eine Aufzeichnung starten?",
"suggestRecordingTitle": "Konferenz aufzeichnen",
"unmute": "Stummschaltung aufheben",
"unmuteScreen": "Bildschirmfreigabe starten",
"unmuteVideo": "Kamera einschalten",
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
@@ -923,14 +864,11 @@
"admit": "Zulassen",
"admitAll": "Alle zulassen",
"allow": "Anwesenden erlauben:",
"allowDesktop": "Bildschirm freizugeben",
"allowVideo": "Kamera einschalten",
"askDesktop": "Anfragen, Bildschirm freizugeben",
"askUnmute": "Anfragen, Stummschaltung aufzuheben",
"audioModeration": "Für sich selbst die Stummschaltung aufzuheben",
"blockEveryoneMicCamera": "Kamera und Mikrofon von allen sperren",
"breakoutRooms": "Breakout-Räume",
"desktopModeration": "Bildschirmfreigabe",
"goLive": "Live gehen",
"invite": "Person einladen",
"lowerAllHands": "Alle Hände senken",
@@ -942,8 +880,6 @@
"muteAll": "Alle stummschalten",
"muteEveryoneElse": "Alle anderen stummschalten",
"reject": "Ablehnen",
"stopDesktop": "Bildschirmfreigabe beenden",
"stopEveryonesDesktop": "Alle Bildschirmfreigaben beenden",
"stopEveryonesVideo": "Alle Kameras ausschalten",
"stopVideo": "Kamera ausschalten",
"unblockEveryoneMicCamera": "Kamera und Mikrofon von allen entsperren",
@@ -953,11 +889,9 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Anwesende ({{count}})",
"viewerRequests": "Zuschaueranfragen {{count}}",
"visitorInQueue": " (Wartende Gäste {{count}})",
"visitorRequests": " (Anfragen {{count}})",
"visitors": "Gäste ({{count}})",
"visitorsList": "Zuschauer ({{count}})",
"waitingLobby": "In der Lobby ({{count}})"
},
"search": "Suche Anwesende",
@@ -978,9 +912,6 @@
"by": "Von {{ name }}",
"closeButton": "Umfrage schließen",
"create": {
"accessibilityLabel": {
"send": "Umfrage erstellen"
},
"addOption": "Antwort hinzufügen",
"answerPlaceholder": "Antwort {{index}}",
"cancel": "Abbrechen",
@@ -989,7 +920,8 @@
"pollQuestion": "Frage",
"questionPlaceholder": "Eine Frage stellen",
"removeOption": "Antwort entfernen",
"save": "Erstellen"
"save": "Erstellen",
"send": "Senden"
},
"errors": {
"notUniqueOption": "Optionen müssen einzigartig sein"
@@ -1190,7 +1122,7 @@
"signedIn": "Momentan wird auf Kalendertermine von {{email}} zugegriffen. Klicken Sie auf die folgende Schaltfläche „Trennen“, um den Zugriff auf die Kalendertermine zu stoppen.",
"title": "Kalender"
},
"chatWithPermissions": "Chat nur für Moderation erlauben",
"chatWithPermissions": "Chat mit Freigaben",
"desktopShareFramerate": "Framerate für Bildschirmfreigabe",
"desktopShareHighFpsWarning": "Eine höhere Framerate könnte sich auf Ihre Datenrate auswirken. Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
"desktopShareWarning": "Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
@@ -1396,20 +1328,6 @@
"videounmute": "Kamera einschalten"
},
"addPeople": "Personen zur Konferenz hinzufügen",
"advancedAudioSettings": {
"aec": {
"label": "Echounterdrückung"
},
"agc": {
"label": "Automatische Mikrofonlautstärke"
},
"ns": {
"label": "Rauschunterdrückung"
},
"stereo": {
"label": "Stereo"
}
},
"audioOnlyOff": "Modus „Nur Audio“ deaktivieren",
"audioOnlyOn": "Modus „Nur Audio“ aktivieren",
"audioRoute": "Audiogerät auswählen",
@@ -1437,7 +1355,6 @@
"exitFullScreen": "Vollbildmodus verlassen",
"exitTileView": "Kachelansicht ausschalten",
"feedback": "Feedback hinterlassen",
"fileSharing": "Dateien",
"giphy": "GIPHY ein-/ausschalten",
"hangup": "Konferenz verlassen",
"help": "Hilfe",
@@ -1473,7 +1390,6 @@
"openReactionsMenu": "Interaktionsmenü öffnen",
"participants": "Anwesende",
"pip": "Bild-in-Bild-Modus einschalten",
"polls": "Umfragen",
"privateMessage": "Private Nachricht senden",
"profile": "Profil bearbeiten",
"raiseHand": "Hand heben",
@@ -1483,7 +1399,6 @@
"reactionHeart": "Herz senden",
"reactionLaugh": "Lachen senden",
"reactionLike": "Daumen hoch senden",
"reactionLove": "Liebe senden",
"reactionSilence": "Stille senden",
"reactionSurprised": "Überrascht senden",
"reactions": "Interaktionen",
@@ -1569,8 +1484,7 @@
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteDesktop": "Bildschirmfreigabe beenden",
"domuteDesktopOfOthers": "Bildschirmfreigabe für alle beenden",
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
@@ -1635,8 +1549,6 @@
"noMainParticipantsTitle": "Diese Konferenz wurde noch nicht gestartet.",
"noVisitorLobby": "Sie können nicht teilnehmen, solange die Lobby für diese Konferenz aktiviert ist.",
"notAllowedPromotion": "Eine Person muss Ihre Anfrage erst erlauben.",
"requestToJoin": "Hand gehoben",
"requestToJoinDescription": "Ihre Anfrage wurde an die Moderation gesendet, bitte warten Sie.",
"title": "Sie sind Gast in der Konferenz"
},
"waitingMessage": "Sie werden der Konferenz beitreten, sobald sie gestartet ist!"

View File

@@ -126,16 +126,8 @@
"messagebox": "Digita un messaggio",
"newMessages": "Nuovi messaggi",
"nickname": {
"featureChat": "la chat",
"featureClosedCaptions": "i sottotitoli",
"featureFileSharing": "la condivisione file",
"featurePolls": "i sondaggi",
"popover": "Scegli un nickname",
"title": "Inserisci un nickname per usare la chat",
"titleWith1Features": "Inserisci un nickname per usare {{feature1}}",
"titleWith2Features": "Inserisci un nickname per usare {{feature1}} e {{feature2}}",
"titleWith3Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}} e {{feature3}}",
"titleWith4Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
"titleWithCC": "Inserisci un nickname per usare la chat e i sottotitoli",
"titleWithPolls": "Inserisci un nickname per usare la chat e i sondaggi",
"titleWithPollsAndCC": "Inserisci un nickname per usare la chat, i sondaggi e i sottotitoli",
@@ -167,7 +159,7 @@
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365"
},
"closedCaptionsTab": {
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che un moderatore lo attiverà",
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che l'organizzatore lo attiverà",
"startClosedCaptionsButton": "Attiva sottotitoli"
},
"connectingOverlay": {
@@ -291,9 +283,9 @@
"Submit": "Invia",
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun moderatore si è ancora collegato. Si prega di attendere.",
"WaitingForHostButton": "Attendi un moderatore",
"WaitingForHostTitle": "In attesa di un moderatore…",
"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…",
"Yes": "Sì",
"accessibilityLabel": {
"Cancel": "Annulla (chiudi dialogo)",
@@ -356,8 +348,8 @@
"error": "Errore",
"errorRoomCreationRestriction": "Hai provato ad accedere alla riunione troppo presto, torna tra un po'.",
"gracefulShutdown": "Il nostro servizio è al momento inattivo per manutenzione. Si prega di riprovare più tardi.",
"grantModeratorDialog": "Desideri di concedere i permessi da moderatore a {{participantName}}?",
"grantModeratorTitle": "Concedi permessi da moderatore",
"grantModeratorDialog": "Vuoi rendere relatore questo partecipante?",
"grantModeratorTitle": "Fai diventare relatore",
"hide": "Nascondi",
"hideShareAudioHelper": "Non mostrare più questa finestra",
"incorrectPassword": "Nome utente o password errati",
@@ -735,12 +727,12 @@
"emailField": "Inserisci il tuo indirizzo email",
"enableDialogPasswordField": "Imposta password (opzionale)",
"enableDialogSubmit": "Attiva",
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un moderatore.",
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un organizzatore.",
"enterPasswordButton": "Inserisci password riunione",
"enterPasswordTitle": "Inserisci la password per entrare nella riunione",
"errorMissingPassword": "Inserisci la password della riunione",
"invalidPassword": "Password errata",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un organizzatore.",
"joinRejectedTitle": "Richiesta d'accesso respinta.",
"joinTitle": "Entra nella riunione",
"joinWithPasswordMessage": "Tentativo di accesso con password in corso, si prega di attendere…",
@@ -762,7 +754,7 @@
"passwordJoinButton": "Entra",
"title": "Sala d'attesa",
"toggleLabel": "Attiva sala d'attesa",
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun moderatore. Se vuoi diventarlo autenticati, altrimenti si prega di attendere."
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
},
"localRecording": {
"clientState": {
@@ -780,11 +772,11 @@
"me": "Io",
"messages": {
"engaged": "Registrazione avviata.",
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione al moderatore.",
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia locale è stata salvata. Si prega di chiedere ai partecipanti di inviare le loro registrazioni.",
"notModerator": "Non sei un moderatore. Non puoi avviare o interrompere la registrazione."
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione all'organizzatore.",
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia è stata salvata. Chiedi ai partecipanti di inviare le loro registrazioni.",
"notModerator": "Non sei un relatore. Non puoi avviare o interrompere la registrazione."
},
"moderator": "Moderatore",
"moderator": "Relatore",
"no": "No",
"participant": "Partecipante",
"participantStats": "Statistiche partecipante",
@@ -832,7 +824,7 @@
"focusFail": "{{component}} non disponibile - nuovo tentativo tra {{ms}} sec",
"gifsMenu": "GIPHY",
"groupTitle": "Notifiche",
"hostAskedUnmute": "Il moderatore ti chiede di intervenire.",
"hostAskedUnmute": "Il relatore ti chiede di intervenire.",
"invalidTenant": "Nome non valido",
"invalidTenantHyphenDescription": "Il nome che hai scelto non è valido (inizia o finisce con '-').",
"invalidTenantLengthDescription": "Il nome che hai scelto è troppo lungo.",
@@ -854,17 +846,17 @@
"localRecordingStopped": "{{name}} ha smesso di registrare.",
"me": "Io",
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo.",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal moderatore",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal relatore",
"moderationInEffectDescription": "Alza la mano, se vuoi prendere la parola.",
"moderationInEffectTitle": "Il tuo microfono è stato spento dal moderatore",
"moderationInEffectTitle": "Il tuo microfono è stato spento dal relatore",
"moderationInEffectVideoDescription": "Alza la mano, se vuoi avviare la tua videocamera.",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal moderatore",
"moderationRequestFromModerator": "L'organizzatore ti chiede di accendere il microfono",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal relatore",
"moderationRequestFromModerator": "Il relatore vorrebbe che tu accendessi il microfono",
"moderationRequestFromParticipant": "Vuole parlare",
"moderationStartedTitle": "Moderazione in corso",
"moderationStoppedTitle": "Moderazione interrotta",
"moderationToggleDescription": "da {{participantDisplayName}}",
"moderator": "Ora sei un moderatore!",
"moderator": "Ora sei un relatore!",
"muted": "Hai iniziato la conversazione con il microfono disattivato.",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono quando vuoi parlare. Spegni il microfono quando hai finito, per evitare rumori di fondo nella riunione.",
"mutedRemotelyTitle": "{{participantDisplayName}} ti ha spento il microfono",
@@ -1170,7 +1162,7 @@
},
"security": {
"about": "Puoi aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"aboutReadOnly": "I moderatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"aboutReadOnly": "Gli organizzatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"insecureRoomNameWarningNative": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione ",
"insecureRoomNameWarningWeb": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">clicca qui</a>.",
"title": "Impostazioni di sicurezza",
@@ -1190,7 +1182,7 @@
"signedIn": "Accesso agli eventi del calendario per {{email}} in corso. Clicca sul pulsante Disconnetti per interrompere laccesso agli eventi sul calendario.",
"title": "Calendario"
},
"chatWithPermissions": "Disattiva la chat per i partecipanti",
"chatWithPermissions": "La chat richiede il permesso",
"desktopShareFramerate": "Frequenza di aggiornamento condivisone schermo",
"desktopShareHighFpsWarning": "Una frequenza di aggiornamento della condivisione dello schermo più alta può influire sulla tua connessione. Devi riavviare la condivisione schermo, per applicare le modifiche.",
"desktopShareWarning": "Devi riavviare la condivisione schermo, per applicare le modifiche.",
@@ -1203,8 +1195,8 @@
"loggedIn": "Connesso come {{name}}",
"maxStageParticipants": "Numero massimo di partecipanti che possono essere messi in evidenza nella schermata principale",
"microphones": "Microfoni",
"moderator": "Moderatore",
"moderatorOptions": "Opzioni moderatore",
"moderator": "Relatore",
"moderatorOptions": "Opzioni relatore",
"more": "Generali",
"name": "Nome",
"noDevice": "Nessuno",
@@ -1333,7 +1325,7 @@
"feedback": "Lascia un feedback",
"fullScreen": "Attiva modalità a schermo intero",
"giphy": "Mostra menu GIPHY",
"grantModerator": "Concedi permessi da moderatore",
"grantModerator": "Concedi permessi da relatore",
"hangup": "Lascia la riunione",
"heading": "Barra degli strumenti",
"help": "Aiuto",
@@ -1358,7 +1350,7 @@
"muteEveryoneElsesVideoStream": "Spegni la videocamera a tutti gli altri",
"muteEveryonesVideoStream": "Spegni la videocamera a tutti",
"muteGUMPending": "Connessione del microfono in corso",
"noiseSuppression": "Cancellazione del rumore",
"noiseSuppression": "Cancellazione del rumore (BETA)",
"openChat": "Apri chat",
"participants": "Apri pannello partecipanti. {{participantsCount}} partecipanti",
"pip": "Attiva modalità Picture-in-Picture",
@@ -1423,21 +1415,20 @@
"closeParticipantsPane": "Chiudi pannello partecipanti",
"closeReactionsMenu": "Chiudi menu reazioni",
"closedCaptions": "Sottotitoli",
"disableNoiseSuppression": "Disattiva cancellazione del rumore",
"disableNoiseSuppression": "Disattiva cancellazione del rumore (BETA)",
"disableReactionSounds": "Puoi disattivare i suoni delle reazioni in questa riunione",
"documentClose": "Chiudi documento condiviso",
"documentOpen": "Apri documento condiviso",
"download": "Scarica le nostre app",
"e2ee": "Crittografia End-to-End",
"embedMeeting": "Incorpora riunione",
"enableNoiseSuppression": "Attiva cancellazione del rumore",
"enableNoiseSuppression": "Attiva cancellazione del rumore (BETA)",
"endConference": "Termina la riunione per tutti",
"enterFullScreen": "Mostra a schermo intero",
"enterTileView": "Mostra vista a mosaico",
"exitFullScreen": "Esci dalla modalità a schermo intero",
"exitTileView": "Esci dalla vista a mosaico",
"feedback": "Lascia un feedback",
"fileSharing": "Condivisione file",
"giphy": "Menu GIPHY",
"hangup": "Lascia la riunione",
"help": "Aiuto",
@@ -1466,14 +1457,13 @@
"noAudioSignalDialInDesc": "Puoi anche chiamare usando:",
"noAudioSignalDialInLinkDesc": "Numeri di telefono",
"noAudioSignalTitle": "Nessun suono rilevato dal tuo microfono!",
"noiseSuppression": "Cancellazione del rumore",
"noiseSuppression": "Cancellazione del rumore (BETA)",
"noisyAudioInputDesc": "Sembra che il tuo microfono faccia rumore, si prega di spegnerlo o cambiarlo.",
"noisyAudioInputTitle": "Il tuo microfono sembra fare rumore!",
"openChat": "Apri chat",
"openReactionsMenu": "Apri il menu reazioni",
"participants": "Partecipanti",
"pip": "Abilita modalità Picture-in-Picture",
"polls": "Sondaggi",
"privateMessage": "Invia un messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza la mano",
@@ -1575,11 +1565,11 @@
"domuteVideo": "Disattiva videocamera",
"domuteVideoOfOthers": "Disattiva videocamera a tutti gli altri",
"flip": "Specchia",
"grantModerator": "Concedi permessi da moderatore",
"grantModerator": "Concedi permessi da relatore",
"hideSelfView": "Nascondi la tua immagine",
"kick": "Espelli",
"mirrorVideo": "Specchia il tuo video",
"moderator": "Moderatore",
"moderator": "Relatore",
"mute": "Il partecipante ha il microfono spento",
"muted": "Microfono spento",
"pinToStage": "Metti in primo piano",
@@ -1626,7 +1616,7 @@
"description": "Adesso sei uno spettatore in questa riunione.",
"raiseHand": "Alza la mano",
"title": "Ingresso nella riunione in corso",
"wishToSpeak": "Per parlare si prega di alzare la mano sotto e aspettare l'autorizzazione del moderatore."
"wishToSpeak": "Se vuoi parlare, si prega di alzare la mano sotto e aspettare l'autorizzazione del relatore."
},
"labelTooltip": "Numero di spettatori: {{count}}",
"notification": {
@@ -1636,7 +1626,7 @@
"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 moderatori. Tieni duro!",
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
"title": "Sei uno spettatore nella riunione"
},
"waitingMessage": "Ti unirai alla riunione quando inizierà!"
@@ -1677,7 +1667,7 @@
"mobileDownLoadLinkAndroid": "Scarica applicazione per Android",
"mobileDownLoadLinkFDroid": "Scarica applicazione da F-Droid",
"mobileDownLoadLinkIos": "Scarica applicazione per iOS",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico moderatore.",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico organizzatore.",
"privacy": "Privacy",
"recentList": "Recenti",
"recentListDelete": "Cancella",

File diff suppressed because it is too large Load Diff

View File

@@ -114,10 +114,6 @@
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
"everyone": "Todos",
"fieldPlaceHolder": "Aa",
"fileAccessibleTitle": "{{user}} carregou um ficheiro",
"fileAccessibleTitleMe": "carreguei um arquivo",
"fileDeleted": "Um ficheiro foi eliminado",
"guestsChatIndicator": "(convidado)",
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
"message": "Mensagem",
"messageAccessibleTitle": "{{user}} disse:",
@@ -126,16 +122,8 @@
"messagebox": "Escreva uma mensagem",
"newMessages": "Novas mensagens",
"nickname": {
"featureChat": "chat",
"featureClosedCaptions": "legendas ocultas",
"featureFileSharing": "partilha de ficheiros",
"featurePolls": "sondagens",
"popover": "Escolha um apelido",
"title": "Introduza um apelido para usar o chat",
"titleWith1Features": "Insira um apelido para usar {{feature1}}",
"titleWith2Features": "Insira um apelido para usar {{feature1}} e {{feature2}}",
"titleWith3Features": "Insira um apelido para usar {{feature1}}, {{feature2}} e {{feature3}}",
"titleWith4Features": "Insira um apelido para usar {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
"titleWithCC": "Insira um apelido para usar o chat e as legendas ocultas",
"titleWithPolls": "Digite um apelido para usar o chat e as sondagens",
"titleWithPollsAndCC": "Insira um apelido para utilizar o chat, as sondagens e as legendas ocultas",
@@ -291,6 +279,7 @@
"Submit": "Submeter",
"Understand": "Entendo, mantenha-me em silêncio por enquanto.",
"UnderstandAndUnmute": "Entendo, por favor, desative o silêncio.",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
"WaitingForHostTitle": "À espera de um moderador…",
@@ -533,7 +522,6 @@
"tokenAuthFailedWithReasons": "Lamentamos, mas não está autorizado a participar nesta chamada. Razões possíveis: {{reason}}",
"tokenAuthUnsupported": "O URL de token não é suportado.",
"transcribing": "Transcrição",
"unauthenticatedAccessDisabled": "Esta chamada requer autenticação. Por favor, inicie sessão para prosseguir.",
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
"user": "Utilizador",
"userIdentifier": "Identificador do utilizador",
@@ -578,17 +566,13 @@
"downloadFailedDescription": "Por favor, tente novamente.",
"downloadFailedTitle": "Falha no descarregar",
"downloadFile": "Descarregar",
"downloadStarted": "O download do ficheiro foi iniciado",
"dragAndDrop": "Arraste e solte os ficheiros aqui ou em qualquer lugar do ecrã",
"fileAlreadyUploaded": "O ficheiro já foi carregado para esta reunião.",
"fileRemovedByOther": "O seu ficheiro '{{ fileName }}' foi removido",
"fileTooLargeDescription": "Certifique-se de que o ficheiro não exceda {{ maxFileSize }}.",
"fileTooLargeTitle": "O ficheiro selecionado é muito grande",
"fileUploadProgress": "Progresso do envio do ficheiro",
"fileUploadedSuccessfully": "Ficheiro carregado com sucesso",
"newFileNotification": "{{ participantName }} partilhou '{{ fileName }}'",
"removeFile": "Remover",
"removeFileSuccess": "Ficheiro removido com sucesso",
"uploadFailedDescription": "Por favor, tente novamente.",
"uploadFailedTitle": "Falha ao carregar",
"uploadFile": "Partilhar ficheiro"
@@ -761,8 +745,7 @@
"notificationTitle": "Sala de espera",
"passwordJoinButton": "Solicitar",
"title": "Sala de espera",
"toggleLabel": "Ativar sala de espera",
"waitForModerator": "A conferência ainda não começou porque não chegou nenhum moderador. Se deseja tornar-se um moderador, faça login. Caso contrário, aguarde."
"toggleLabel": "Ativar sala de espera"
},
"localRecording": {
"clientState": {
@@ -879,7 +862,6 @@
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
"oldElectronClientDescription2": "compilação mais recente",
"oldElectronClientDescription3": " agora!",
"openChat": "Abrir chat",
"participantWantsToJoin": "Deseja juntar-se à reunião",
"participantsWantToJoin": "Desejam juntar-se à reunião",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
@@ -978,9 +960,6 @@
"by": "Por {{ name }}",
"closeButton": "Fechar sondagem",
"create": {
"accessibilityLabel": {
"send": "Enviar sondagem"
},
"addOption": "Adicionar opção",
"answerPlaceholder": "Opção {{index}}",
"cancel": "Cancelar",
@@ -989,7 +968,8 @@
"pollQuestion": "Pergunta de Sondagem",
"questionPlaceholder": "Faça uma pergunta",
"removeOption": "Remover opção",
"save": "Guardar"
"save": "Guardar",
"send": "Enviar"
},
"errors": {
"notUniqueOption": "As opções devem ser únicas"
@@ -1358,7 +1338,7 @@
"muteEveryoneElsesVideo": "Parar o vídeo de todos os outros",
"muteEveryonesVideo": "Parar o vídeo de todos",
"muteGUMPending": "A ligar o seu microfone",
"noiseSuppression": "Supressão extra de ruído",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"openChat": "Abrir chat",
"participants": "Abrir painel de participantes. {{participantsCount}} participantes",
"pip": "Mudar para o modo Picture-in-Picture",
@@ -1396,20 +1376,6 @@
"videounmute": "Iniciar câmara"
},
"addPeople": "Adicione pessoas à sua chamada",
"advancedAudioSettings": {
"aec": {
"label": "Cancelamento de eco acústico"
},
"agc": {
"label": "Controlo automático de ganho"
},
"ns": {
"label": "Supressão de ruído"
},
"stereo": {
"label": "Estéreo"
}
},
"audioOnlyOff": "Desativar modo de largura de banda baixa",
"audioOnlyOn": "Ativar modo de largura de banda baixa",
"audioRoute": "Selecionar o dispositivo de som",
@@ -1423,21 +1389,20 @@
"closeParticipantsPane": "Fechar painel de participantes",
"closeReactionsMenu": "Fechar menu de reações",
"closedCaptions": "Legendas ocultas",
"disableNoiseSuppression": "Desativar supressão de ruído extra",
"disableNoiseSuppression": "Desativar supressão de ruído extra (BETA)",
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
"documentClose": "Fechar documento partilhado",
"documentOpen": "Abrir documento partilhado",
"download": "Descarregar as nossas aplicações",
"e2ee": "Criptografia ponta a ponta",
"embedMeeting": "Incorporar reunião",
"enableNoiseSuppression": "Ativar supressão extra de ruído",
"enableNoiseSuppression": "Ativar supressão extra de ruído (BETA)",
"endConference": "Terminar reunião para todos",
"enterFullScreen": "Ver em ecrã completo",
"enterTileView": "Ver em quadrícula",
"exitFullScreen": "Sair de ecrã completo",
"exitTileView": "Sair de quadrícula",
"feedback": "Deixar comentários",
"fileSharing": "Partilha de ficheiros",
"giphy": "Ativar/Desativar o menu GIPHY",
"hangup": "Sair da reunião",
"help": "Ajuda",
@@ -1466,14 +1431,13 @@
"noAudioSignalDialInDesc": "Também pode marcar usando:",
"noAudioSignalDialInLinkDesc": "Números de marcação",
"noAudioSignalTitle": "Não há nenhuma entrada vinda do seu microfone!",
"noiseSuppression": "Supressão extra de ruído",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noisyAudioInputDesc": "Parece que o seu microfone está a fazer barulho, por favor considere silenciar ou mudar de dispositivo.",
"noisyAudioInputTitle": "Seu microfone parece estar barulhento!",
"openChat": "Abrir chat",
"openReactionsMenu": "Abrir menu de reações",
"participants": "Participantes",
"pip": "Entrar no modo Picture-in-Picture",
"polls": "Sondagens",
"privateMessage": "Enviar mensagem privada",
"profile": "Editar o seu perfil",
"raiseHand": "Levantar a mão",
@@ -1483,7 +1447,6 @@
"reactionHeart": "Enviar reação com coração",
"reactionLaugh": "Enviar reação de risos",
"reactionLike": "Enviar reação de aprovado",
"reactionLove": "Enviar reação de amor",
"reactionSilence": "Enviar reação de silêncio",
"reactionSurprised": "Enviar reação de surpreendido",
"reactions": "Reações",
@@ -1635,8 +1598,6 @@
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
"requestToJoin": "Mão levantada",
"requestToJoinDescription": "A sua solicitação foi enviada aos moderadores. Aguarde um momento!",
"title": "É um espectador na reunião"
},
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"

View File

@@ -1190,7 +1190,7 @@
"signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
"title": "Calendar"
},
"chatWithPermissions": "Disable chat for non-moderators",
"chatWithPermissions": "Chat requires permission",
"desktopShareFramerate": "Desktop sharing frame rate",
"desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
"desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",

View File

@@ -126,7 +126,7 @@ import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functio
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
import { muteAllParticipants, muteRemote } from '../../react/features/video-menu/actions';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { toggleBackgroundEffect, toggleBlurredBackgroundEffect } from '../../react/features/virtual-background/actions';
import { VIRTUAL_BACKGROUND_TYPE } from '../../react/features/virtual-background/constants';
@@ -239,17 +239,6 @@ function initCommands() {
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
},
'mute-remote-participant': (participantId, mediaType) => {
if (!isLocalParticipantModerator(APP.store.getState())) {
logger.error('Missing moderator rights to mute remote participant');
return;
}
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
APP.store.dispatch(muteRemote(participantId, muteMediaType));
},
'toggle-lobby': isLobbyEnabled => {
APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
},
@@ -790,7 +779,7 @@ function initCommands() {
}
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(true, false, null));
APP.store.dispatch(setRequestingSubtitles(true, false, null, true));
}
},
@@ -812,7 +801,7 @@ function initCommands() {
}
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(false, false, null));
APP.store.dispatch(setRequestingSubtitles(false, false, null, true));
}
if (mode === 'local') {
@@ -1412,25 +1401,6 @@ class API {
});
}
/**
* Notify the external application that a participant's mute status changed.
*
* @param {string} participantId - The ID of the participant.
* @param {boolean} isMuted - True if muted, false if unmuted.
* @param {string} mediaType - Media type that was muted ('audio', 'video', or 'desktop').
* @param {boolean} isSelfMuted - True if participant muted themselves, false if muted by moderator.
* @returns {void}
*/
notifyParticipantMuted(participantId, isMuted, mediaType, isSelfMuted = true) {
this._sendEvent({
name: 'participant-muted',
id: participantId,
isMuted,
mediaType,
isSelfMuted
});
}
/**
* Notify the external app that a notification has been triggered.
*

View File

@@ -47,7 +47,6 @@ const commands = {
localSubject: 'local-subject',
kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone',
muteRemoteParticipant: 'mute-remote-participant',
overwriteConfig: 'overwrite-config',
overwriteNames: 'overwrite-names',
password: 'password',
@@ -151,7 +150,6 @@ const events = {
'participant-joined': 'participantJoined',
'participant-kicked-out': 'participantKickedOut',
'participant-left': 'participantLeft',
'participant-muted': 'participantMuted',
'participant-role-changed': 'participantRoleChanged',
'participants-pane-toggled': 'participantsPaneToggled',
'password-required': 'passwordRequired',

3260
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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/v2118.0.0+67fd2c84/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2116.0.0+40ad2744/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -164,12 +164,12 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@wdio/allure-reporter": "9.22.0",
"@wdio/cli": "9.22.0",
"@wdio/globals": "9.17.0",
"@wdio/junit-reporter": "9.21.0",
"@wdio/local-runner": "9.22.0",
"@wdio/mocha-framework": "9.22.0",
"@wdio/allure-reporter": "9.16.0",
"@wdio/cli": "9.16.0",
"@wdio/globals": "9.16.0",
"@wdio/junit-reporter": "9.16.0",
"@wdio/local-runner": "9.16.0",
"@wdio/mocha-framework": "9.16.0",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -193,7 +193,7 @@
"ts-loader": "9.4.2",
"typescript": "5.7.2",
"unorm": "1.6.0",
"webdriverio": "9.22.0",
"webdriverio": "9.16.0",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",

View File

@@ -559,7 +559,6 @@ export interface IConfig {
skipConsentInMeeting?: boolean;
suggestRecording?: boolean;
};
reducedUImainToolbarButtons?: Array<string>;
remoteVideoMenu?: {
disableDemote?: boolean;
disableGrantModerator?: boolean;
@@ -582,7 +581,6 @@ export interface IConfig {
};
serviceUrl?: string;
sharedVideoAllowedURLDomains?: Array<string>;
showChatPermissionsModeratorSetting?: boolean;
sipInviteUrl?: string;
speakerStats?: {
disableSearch?: boolean;

View File

@@ -215,12 +215,10 @@ export default [
'recordings.showPrejoinWarning',
'recordings.showRecordingLink',
'recordings.suggestRecording',
'reducedUImainToolbarButtons',
'replaceParticipant',
'resolution',
'screenshotCapture',
'securityUi',
'showChatPermissionsModeratorSetting',
'speakerStats',
'startAudioMuted',
'startAudioOnly',

View File

@@ -8,14 +8,13 @@ import i18next from './i18next';
*
* @param {string} language - The language e.g. 'en', 'fr'.
* @param {string} url - The url of the translation bundle.
* @param {string} ns - The namespace of the translation bundle.
* @returns {void}
*/
export async function changeLanguageBundle(language: string, url: string, ns = 'main') {
export async function changeLanguageBundle(language: string, url: string) {
const res = await fetch(url);
const bundle = await res.json();
i18next.addResourceBundle(language, ns, bundle, true, true);
i18next.addResourceBundle(language, 'main', bundle, true, true);
}
/**

View File

@@ -58,14 +58,6 @@ export const DEFAULT_LANGUAGE = 'en';
*/
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ DEFAULT_LANGUAGE ];
/**
* The available/supported i18n namespaces.
*
* @public
* @type {Array<string>}
*/
export const SUPPORTED_NS = [ 'main', 'languages', 'countries', 'translation-languages' ];
/**
* The options to initialize i18next with.
*
@@ -89,7 +81,7 @@ const options: i18next.InitOptions = {
escapeValue: false // not needed for react as it escapes by default
},
load: 'all',
ns: SUPPORTED_NS,
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
react: {
// re-render when a new resource bundle is added
// @ts-expect-error. Fixed in i18next 19.6.1.

View File

@@ -4,7 +4,7 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { I18NEXT_INITIALIZED, LANGUAGE_CHANGED } from './actionTypes';
import { changeLanguageBundle } from './functions';
import i18next, { SUPPORTED_NS } from './i18next';
import i18next from './i18next';
import logger from './logger';
/**
@@ -19,10 +19,9 @@ MiddlewareRegistry.register(store => next => action => {
case LANGUAGE_CHANGED:
case SET_DYNAMIC_BRANDING_DATA: {
const { language } = i18next;
const data = action.type === SET_DYNAMIC_BRANDING_DATA
const { labels } = action.type === SET_DYNAMIC_BRANDING_DATA
? action.value
: store.getState()['features/dynamic-branding'];
const labels = data?.labels;
if (language && labels?.[language]) {
changeLanguageBundle(language, labels[language])
@@ -31,17 +30,6 @@ MiddlewareRegistry.register(store => next => action => {
});
}
SUPPORTED_NS.forEach(ns => {
const nsLabels = data?.[`labels-${ns}`];
if (language && nsLabels?.[language]) {
changeLanguageBundle(language, nsLabels[language], ns)
.catch(err => {
logger.log(`Error setting dynamic language bundle for ${ns}`, err);
});
}
});
// Update transcription language, if applicable.
if (action.type === SET_DYNAMIC_BRANDING_DATA) {
const { defaultTranscriptionLanguage } = action.value;

View File

@@ -10,10 +10,6 @@ import { parseURLParams } from '../util/parseURLParams';
import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
import logger from './logger';
/**
* Note that this is just client-side code and it intentionally does not verify the signature of the JWT.
*/
/**
* Retrieves the JSON Web Token (JWT), if any, defined by a specific
* {@link URL}.

View File

@@ -818,7 +818,7 @@ export const addPeopleFeatureControl = (stateful: IStateful) => {
* @param {Function} dispatch - The Redux dispatch function.
* @returns {Function}
*/
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean | undefined, dispatch: IStore['dispatch']) => {
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispatch: IStore['dispatch']) => {
if (addPeopleFeatureEnabled) {
dispatch(toggleShareDialog(false));
} else {

View File

@@ -22,35 +22,17 @@ export function preloadImage(
return new Promise((resolve, reject) => {
const image = document.createElement('img');
// Cleanup function to release resources and prevent memory leaks
const cleanup = () => {
// Clear event handlers to break circular references
image.onload = null;
image.onerror = null;
// Clear src to stop any pending load and allow GC
image.src = '';
};
if (useCORS) {
image.setAttribute('crossOrigin', '');
}
image.onload = () => {
cleanup();
resolve({
src,
isUsingCORS: useCORS
});
};
image.onload = () => resolve({
src,
isUsingCORS: useCORS
});
image.onerror = error => {
cleanup();
if (tryOnce) {
reject(error);
} else {
// Retry with different CORS mode
preloadImage(src, !useCORS, true)
.then(resolve)
.catch(reject);

View File

@@ -24,7 +24,6 @@ import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
* determine whether and how to render it.
*/
const REDUCED_UI_THRESHOLD = 300;
const WEB_REDUCED_UI_THRESHOLD = 320;
/**
* Indicates a resize of the window.
@@ -50,8 +49,6 @@ export function clientResized(clientWidth: number, clientHeight: number) {
}
availableWidth -= getParticipantsPaneWidth(state);
dispatch(setReducedUI(availableWidth, clientHeight));
}
batch(() => {
@@ -109,10 +106,7 @@ export function setAspectRatio(width: number, height: number) {
*/
export function setReducedUI(width: number, height: number) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const threshold = navigator.product === 'ReactNative'
? REDUCED_UI_THRESHOLD
: WEB_REDUCED_UI_THRESHOLD;
const reducedUI = Math.min(width, height) < threshold;
const reducedUI = Math.min(width, height) < REDUCED_UI_THRESHOLD;
if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) {
return dispatch({

View File

@@ -29,7 +29,6 @@ import {
TRACK_UPDATED,
TRACK_WILL_CREATE
} from './actionTypes';
import { toggleScreensharing } from './actions';
import {
createLocalTracksF,
getCameraFacingMode,
@@ -386,22 +385,8 @@ export function trackAdded(track: any) {
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP
? MEDIA_TYPE.SCREENSHARE
: track.getType();
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
// Make screen share toggle off listen to MediaStreamTrack "ended" event
// when it's terminated via Android status bar chip.
if (navigator.product === 'ReactNative') {
const mediaStreamTrack = track?.getTrack?.();
if (mediaType === MEDIA_TYPE.SCREENSHARE) {
const onEnded = () => dispatch(toggleScreensharing(false));
mediaStreamTrack.addEventListener('ended', onEnded);
track._onEnded = onEnded;
}
}
if (local) {
// Reset the no data from src notification state when we change the track, as it's context is set
// on a per device basis.
@@ -583,16 +568,6 @@ export function trackRemoved(track: any): {
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
// Remove MediaStreamTrack "ended" event.
if (navigator.product === 'ReactNative') {
const mediaStreamTrack = track?.getTrack?.();
if (track._onEnded) {
mediaStreamTrack.removeEventListener('ended', track._onEnded);
delete track._onEnded;
}
}
return {
type: TRACK_REMOVED,
track: {

View File

@@ -33,6 +33,7 @@ import {
isUserInteractionRequiredForUnmute,
setTrackMuted
} from './functions';
import './subscriber';
/**
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,

View File

@@ -38,7 +38,6 @@ import {
import { ITrack, ITrackOptions } from './types';
import './middleware.any';
import './subscriber.web';
/**
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
@@ -143,13 +142,6 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof action.track?.muted !== 'undefined' && participantID && !local) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated');
// Notify external API when remote participant mutes/unmutes themselves
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType, true);
}
return result;

View File

@@ -0,0 +1,40 @@
import { isEqual, sortBy } from 'lodash-es';
import { MEDIA_TYPE } from '../media/constants';
import { getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
/**
* Notifies when the list of currently sharing participants changes.
*/
StateListenerRegistry.register(
/* selector */ state => getScreenshareParticipantIds(state),
/* listener */ (participantIDs, store, previousParticipantIDs) => {
if (typeof APP !== 'object') {
return;
}
if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}
);
/**
* Notifies when the local video mute state changes.
*/
StateListenerRegistry.register(
/* selector */ state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO),
/* listener */ (muted, store, previousMuted) => {
if (typeof APP !== 'object') {
return;
}
if (muted !== previousMuted) {
APP.API.notifyVideoMutedStatusChanged(muted);
}
}
);

View File

@@ -1,52 +0,0 @@
import { isEqual, sortBy } from 'lodash-es';
// @ts-expect-error
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
import { getAutoPinSetting } from '../../video-layout/functions.any';
import { MEDIA_TYPE } from '../media/constants';
import { getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
/**
* Notifies when the list of currently sharing participants changes.
*/
StateListenerRegistry.register(
/* selector */ state => getScreenshareParticipantIds(state),
/* listener */ (participantIDs, store, previousParticipantIDs) => {
if (getAutoPinSetting() && participantIDs !== previousParticipantIDs) {
const { participantId } = store.getState()['features/large-video'];
// Check if any new screenshare participants were added
const newParticipants = participantIDs.filter((id: string) => !previousParticipantIDs.includes(id));
// If the current large video participant is a new screensharer, update the display. This is needed when
// the track is created much later after the action for auto-pinning is dispatched. This usually happens in
// very large meetings if the screenshare was already ongoing when the participant joined. The track is
// signaled only after the receiver constraints with SS source id is processed by the bridge but the
// auto-pinning action is dispatched when the participant tile is created as soon as the presence is
// received.
if (participantId && newParticipants.includes(participantId)) {
VideoLayout.updateLargeVideo(participantId, true);
}
}
if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}
);
/**
* Notifies when the local video mute state changes.
*/
StateListenerRegistry.register(
/* selector */ state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO),
/* listener */ (muted, store, previousMuted) => {
if (muted !== previousMuted) {
APP.API.notifyVideoMutedStatusChanged(muted);
}
}
);

View File

@@ -78,11 +78,6 @@ interface IProps extends AbstractProps {
*/
_isResizing: boolean;
/**
* The indicator which determines whether the UI is reduced.
*/
_reducedUI: boolean;
/**
* Whether or not to block chat access with a nickname input form.
*/
@@ -232,7 +227,6 @@ const Chat = ({
_focusedTab,
_isResizing,
_messages,
_reducedUI,
_unreadMessagesCount,
_unreadPollsCount,
_unreadFilesCount,
@@ -573,10 +567,6 @@ const Chat = ({
);
}
if (_reducedUI) {
return null;
}
return (
_isOpen ? <div
className = { classes.container }
@@ -633,7 +623,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { unreadPollsCount } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
const { reducedUI } = state['features/base/responsive-ui'];
return {
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
@@ -644,7 +633,6 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_isFileSharingTabEnabled: isFileSharingEnabled(state),
_focusedTab: getFocusedTab(state),
_messages: messages,
_reducedUI: reducedUI,
_unreadMessagesCount: unreadMessagesCount,
_unreadPollsCount: unreadPollsCount,
_unreadFilesCount: unreadFilesCount,

View File

@@ -90,8 +90,7 @@ export default function ClosedCaptionsTab() {
const _canStartSubtitles = useSelector(canStartSubtitles);
const [ isButtonPressed, setButtonPressed ] = useState(false);
const subtitlesError = useSelector((state: IReduxState) => state['features/subtitles']._hasError);
const isAsyncTranscriptionEnabled = useSelector((state: IReduxState) =>
state['features/base/conference'].conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription);
const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
const filteredSubtitles = useMemo(() => {
// First, create a map of transcription messages by message ID
@@ -125,21 +124,22 @@ export default function ClosedCaptionsTab() {
groupMessagesBySender(filteredSubtitles), [ filteredSubtitles ]);
const startClosedCaptions = useCallback(() => {
if (isAsyncTranscriptionEnabled) {
if (isButtonPressed) {
return;
}
if (conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription) {
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog, {
recordAudioAndVideo: false
}));
} else {
if (isButtonPressed) {
return;
}
dispatch(setRequestingSubtitles(true, false, null));
setButtonPressed(true);
}
}, [ isAsyncTranscriptionEnabled, dispatch, isButtonPressed, openDialog, setButtonPressed ]);
setButtonPressed(true);
}, [ conference, dispatch, isButtonPressed, openDialog, setButtonPressed ]);
if (subtitlesError && isButtonPressed && !isAsyncTranscriptionEnabled) {
if (subtitlesError && isButtonPressed) {
setButtonPressed(false);
}
@@ -159,7 +159,7 @@ export default function ClosedCaptionsTab() {
);
}
if (isButtonPressed && !isAsyncTranscriptionEnabled) {
if (isButtonPressed) {
setButtonPressed(false);
}
@@ -176,7 +176,7 @@ export default function ClosedCaptionsTab() {
);
}
if (isButtonPressed && !isAsyncTranscriptionEnabled) {
if (isButtonPressed) {
setButtonPressed(false);
}

View File

@@ -240,13 +240,6 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_JOINED:
case PARTICIPANT_LEFT:
case PARTICIPANT_UPDATED: {
if (action.type === PARTICIPANT_LEFT) {
const { privateMessageRecipient } = store.getState()['features/chat'];
if (action.participant?.id === privateMessageRecipient?.id) {
store.dispatch(setPrivateMessageRecipient());
}
}
if (_shouldNotifyPrivateRecipientsChanged(store, action)) {
const result = next(action);
@@ -262,7 +255,7 @@ MiddlewareRegistry.register(store => next => action => {
const conference = getCurrentConference(state);
if (conference) {
// There may be cases when we intend to send a private message, but we forgot to set the
// There may be cases when we intend to send a private message but we forget to set the
// recipient. This logic tries to mitigate this risk.
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
@@ -276,32 +269,29 @@ MiddlewareRegistry.register(store => next => action => {
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
displayName: shouldSendPrivateMessageTo.name
}));
// the dialog will take care of sending the message after user confirmation
break;
}
}
// Sending the message if privacy notice doesn't need to be shown.
const { privateMessageRecipient, isLobbyChatActive, lobbyMessageRecipient }
= state['features/chat'];
if (typeof APP !== 'undefined') {
APP.API.notifySendingChatMessage(action.message, Boolean(privateMessageRecipient));
}
if (isLobbyChatActive && lobbyMessageRecipient) {
conference.sendLobbyMessage({
type: LOBBY_CHAT_MESSAGE,
message: action.message
}, lobbyMessageRecipient.id);
_persistSentPrivateMessage(store, lobbyMessageRecipient, action.message, true);
} else if (privateMessageRecipient) {
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message, 'body', isVisitorChatParticipant(privateMessageRecipient));
_persistSentPrivateMessage(store, privateMessageRecipient, action.message);
} else {
conference.sendTextMessage(action.message);
// Sending the message if privacy notice doesn't need to be shown.
const { privateMessageRecipient, isLobbyChatActive, lobbyMessageRecipient }
= state['features/chat'];
if (typeof APP !== 'undefined') {
APP.API.notifySendingChatMessage(action.message, Boolean(privateMessageRecipient));
}
if (isLobbyChatActive && lobbyMessageRecipient) {
conference.sendLobbyMessage({
type: LOBBY_CHAT_MESSAGE,
message: action.message
}, lobbyMessageRecipient.id);
_persistSentPrivateMessage(store, lobbyMessageRecipient, action.message, true);
} else if (privateMessageRecipient) {
conference.sendPrivateTextMessage(privateMessageRecipient.id, action.message, 'body', isVisitorChatParticipant(privateMessageRecipient));
_persistSentPrivateMessage(store, privateMessageRecipient, action.message);
} else {
conference.sendTextMessage(action.message);
}
}
}
break;

View File

@@ -90,11 +90,6 @@ interface IProps extends AbstractProps, WithTranslation {
*/
_overflowDrawer: boolean;
/**
* The indicator which determines whether the UI is reduced.
*/
_reducedUI: boolean;
/**
* Name for this conference room.
*/
@@ -231,45 +226,12 @@ class Conference extends AbstractConference<IProps, any> {
_layoutClassName,
_notificationsVisible,
_overflowDrawer,
_reducedUI,
_showLobby,
_showPrejoin,
_showVisitorsQueue,
t
} = this.props;
if (_reducedUI) {
return (
<div
id = 'layout_wrapper'
onMouseEnter = { this._onMouseEnter }
onMouseLeave = { this._onMouseLeave }
onMouseMove = { this._onMouseMove }
ref = { this._setBackground }>
<Chat />
<div
className = { _layoutClassName }
id = 'videoconference_page'
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
<ConferenceInfo />
<Notice />
<div
id = 'videospace'
onTouchStart = { this._onVideospaceTouchStart }>
<LargeVideo />
</div>
<span
aria-level = { 1 }
className = 'sr-only'
role = 'heading'>
{ t('toolbar.accessibilityLabel.heading') }
</span>
<Toolbox />
</div>
</div>
);
}
return (
<div
id = 'layout_wrapper'
@@ -456,7 +418,6 @@ class Conference extends AbstractConference<IProps, any> {
function _mapStateToProps(state: IReduxState) {
const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config'];
const { overflowDrawer } = state['features/toolbox'];
const { reducedUI } = state['features/base/responsive-ui'];
return {
...abstractMapStateToProps(state),
@@ -465,7 +426,6 @@ function _mapStateToProps(state: IReduxState) {
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state) ?? ''],
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
_overflowDrawer: overflowDrawer,
_reducedUI: reducedUI,
_roomName: getConferenceNameForTitle(state),
_showLobby: getIsLobbyVisible(state),
_showPrejoin: isPrejoinPageVisible(state),

View File

@@ -34,11 +34,6 @@ interface IProps {
autoHide?: string[];
};
/**
* The indicator which determines whether the UI is reduced.
*/
_reducedUI: boolean;
/**
* Indicates whether the component should be visible or not.
*/
@@ -199,12 +194,6 @@ class ConferenceInfo extends Component<IProps> {
* @returns {ReactElement}
*/
override render() {
const { _reducedUI } = this.props;
if (_reducedUI) {
return null;
}
return (
<div
className = 'details-container'
@@ -228,12 +217,9 @@ class ConferenceInfo extends Component<IProps> {
* }}
*/
function _mapStateToProps(state: IReduxState) {
const { reducedUI } = state['features/base/responsive-ui'];
return {
_conferenceInfo: getConferenceInfo(state),
_reducedUI: reducedUI,
_visible: isToolboxVisible(state),
_conferenceInfo: getConferenceInfo(state)
};
}

View File

@@ -25,10 +25,9 @@ const useStyles = makeStyles()(theme => {
const Notice = () => {
const message = useSelector((state: IReduxState) => state['features/base/config'].noticeMessage);
const { reducedUI } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const { classes } = useStyles();
if (!message || reducedUI) {
if (!message) {
return null;
}

View File

@@ -588,7 +588,7 @@ function _registerForNativeEvents(store: IStore) {
}
if (transcription) {
store.dispatch(setRequestingSubtitles(true, false, null));
store.dispatch(setRequestingSubtitles(true, false, null, true));
}
});
@@ -603,7 +603,7 @@ function _registerForNativeEvents(store: IStore) {
}
if (transcription) {
store.dispatch(setRequestingSubtitles(false, false, null));
store.dispatch(setRequestingSubtitles(false, false, null, true));
}
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {

View File

@@ -1,7 +1,8 @@
import React, { useCallback } from 'react';
import { connect, useDispatch } from 'react-redux';
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { IReduxState, IStore } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import {
getLocalParticipant,
getParticipantById,
@@ -9,18 +10,14 @@ import {
hasRaisedHand,
isParticipantModerator
} from '../../../base/participants/functions';
import { FakeParticipant } from '../../../base/participants/types';
import { FakeParticipant, IParticipant } from '../../../base/participants/types';
import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks/functions.native';
import { showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
import type { MediaState } from '../../constants';
import {
getParticipantAudioMediaState,
getParticipantVideoMediaState,
participantMatchesSearch
} from '../../functions';
import { getParticipantAudioMediaState, getParticipantVideoMediaState } from '../../functions';
import ParticipantItem from './ParticipantItem';
@@ -62,9 +59,9 @@ interface IProps {
_localVideoOwner: boolean;
/**
* Whether or not the participant name matches the search string.
* The participant ID.
*/
_matchesSearch: boolean;
_participantID: string;
/**
* True if the participant have raised hand.
@@ -77,55 +74,85 @@ interface IProps {
_videoMediaState: MediaState;
/**
* The participant ID.
* The redux dispatch function.
*/
participantID: string;
dispatch: IStore['dispatch'];
/**
* Name of the participant we search for.
* The participant.
*/
searchString: string;
participant?: IParticipant;
}
const MeetingParticipantItem = ({
_audioMediaState,
_disableModeratorIndicator,
_displayName,
_fakeParticipant,
_isModerator,
_local,
_localVideoOwner,
_matchesSearch,
_raisedHand,
_videoMediaState,
participantID
}: IProps) => {
const dispatch = useDispatch();
const onPress = useCallback(() => {
if (_fakeParticipant && _localVideoOwner) {
dispatch(showSharedVideoMenu(participantID));
} else if (!_fakeParticipant) {
dispatch(showContextMenuDetails(participantID, _local));
} // else no-op
}, [ dispatch ]);
/**
* Implements the MeetingParticipantItem component.
*/
class MeetingParticipantItem extends PureComponent<IProps> {
if (!_matchesSearch) {
return null;
/**
* Creates new MeetingParticipantItem instance.
*
* @param {IProps} props - The props of the component.
*/
constructor(props: IProps) {
super(props);
this._onPress = this._onPress.bind(this);
}
return (
<ParticipantItem
audioMediaState = { _audioMediaState }
disableModeratorIndicator = { _disableModeratorIndicator }
displayName = { _displayName }
isModerator = { _isModerator }
local = { _local }
onPress = { onPress }
participantID = { participantID }
raisedHand = { _raisedHand }
videoMediaState = { _videoMediaState } />
);
};
/**
* Handles MeetingParticipantItem press events.
*
* @returns {void}
*/
_onPress() {
const {
_fakeParticipant,
_local,
_localVideoOwner,
_participantID,
dispatch
} = this.props;
if (_fakeParticipant && _localVideoOwner) {
dispatch(showSharedVideoMenu(_participantID));
} else if (!_fakeParticipant) {
dispatch(showContextMenuDetails(_participantID, _local));
} // else no-op
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
override render() {
const {
_audioMediaState,
_disableModeratorIndicator,
_displayName,
_isModerator,
_local,
_participantID,
_raisedHand,
_videoMediaState
} = this.props;
return (
<ParticipantItem
audioMediaState = { _audioMediaState }
disableModeratorIndicator = { _disableModeratorIndicator }
displayName = { _displayName }
isModerator = { _isModerator }
local = { _local }
onPress = { this._onPress }
participantID = { _participantID }
raisedHand = { _raisedHand }
videoMediaState = { _videoMediaState } />
);
}
}
/**
* Maps (parts of) the redux state to the associated props for this component.
@@ -136,9 +163,8 @@ const MeetingParticipantItem = ({
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState, ownProps: any) {
const { participantID, searchString } = ownProps;
const { participant } = ownProps;
const { ownerId } = state['features/shared-video'];
const participant = getParticipantById(state, participantID);
const localParticipantId = getLocalParticipant(state)?.id;
const _isAudioMuted = isParticipantAudioMuted(participant, state);
const _isVideoMuted = isParticipantVideoMuted(participant, state);
@@ -147,24 +173,23 @@ function mapStateToProps(state: IReduxState, ownProps: any) {
const { disableModeratorIndicator } = state['features/base/config'];
const raisedHand = hasRaisedHand(participant?.local
? participant
: getParticipantById(state, participantID)
: getParticipantById(state, participant?.id)
);
const _matchesSearch = participantMatchesSearch(participant, searchString);
return {
_audioMediaState: audioMediaState,
_disableModeratorIndicator: disableModeratorIndicator,
_displayName: getParticipantDisplayName(state, participantID),
_displayName: getParticipantDisplayName(state, participant?.id),
_fakeParticipant: participant?.fakeParticipant,
_isAudioMuted,
_isModerator: isParticipantModerator(participant),
_local: Boolean(participant?.local),
_localVideoOwner: Boolean(ownerId === localParticipantId),
_matchesSearch,
_participantID: participant?.id,
_raisedHand: raisedHand,
_videoMediaState: videoMediaState
};
}
// @ts-ignore
export default connect(mapStateToProps)(MeetingParticipantItem);
export default translate(connect(mapStateToProps)(MeetingParticipantItem));

View File

@@ -1,15 +1,16 @@
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Text, TextStyle, View } from 'react-native';
import { connect, useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconAddUser } from '../../../base/icons/svg';
import {
addPeopleFeatureControl,
getParticipantById,
isScreenShareParticipant,
getLocalParticipant,
getParticipantCountWithFake,
getRemoteParticipants,
setShareDialogVisiblity
} from '../../../base/participants/functions';
import Button from '../../../base/ui/components/native/Button';
@@ -22,70 +23,57 @@ import {
import { doInvitePeople } from '../../../invite/actions.native';
import { getInviteOthersControl } from '../../../share-room/functions';
import { iAmVisitor } from '../../../visitors/functions';
import { getSortedParticipantIds, shouldRenderInviteButton } from '../../functions';
import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
import MeetingParticipantItem from './MeetingParticipantItem';
import styles from './styles';
interface IProps {
currentRoom?: {
jid: string;
name: string;
};
iconColor: string;
isAddPeopleFeatureEnabled?: boolean | undefined;
isShareDialogVisible: boolean;
participantsCount?: number;
showInviteButton?: boolean;
sortedParticipantIds?: Array<string>;
visitorsCount?: number | undefined;
}
const MeetingParticipantList = ({
currentRoom,
iconColor,
isAddPeopleFeatureEnabled,
isShareDialogVisible,
participantsCount,
showInviteButton,
sortedParticipantIds = [],
visitorsCount
}: IProps): any => {
const { t } = useTranslation();
const [ searchString, setSearchString ] = useState('');
const MeetingParticipantList = () => {
const currentRoomId = useSelector(getCurrentRoomId);
const currentRoom = useSelector(getBreakoutRooms)[currentRoomId];
const dispatch = useDispatch();
const keyExtractor = useCallback((e: undefined, i: number) => i.toString(), []);
const inviteOthersControl = useSelector(getInviteOthersControl);
const isAddPeopleFeatureEnabled = useSelector(addPeopleFeatureControl);
const keyExtractor
= useCallback((e: undefined, i: number) => i.toString(), []);
const localParticipant = useSelector(getLocalParticipant);
const _iAmVisitor = useSelector(iAmVisitor);
const onInvite = useCallback(() => {
setShareDialogVisiblity(isAddPeopleFeatureEnabled, dispatch);
dispatch(doInvitePeople());
}, [ dispatch ]);
const [ searchString, setSearchString ] = useState('');
const onSearchStringChange = useCallback((text: string) =>
setSearchString(text), []);
const participantsCount = useSelector(getParticipantCountWithFake);
const remoteParticipants = useSelector(getRemoteParticipants);
const renderParticipant = ({ item/* , index, separators */ }: any) => {
const participant = item === localParticipant?.id
? localParticipant : remoteParticipants.get(item);
if (participantMatchesSearch(participant, searchString)) {
return (
<MeetingParticipantItem
key = { item }
participant = { participant } />
);
}
return null;
};
const showInviteButton = useSelector(shouldRenderInviteButton);
const sortedRemoteParticipants = useSelector(
(state: IReduxState) => state['features/filmstrip'].remoteParticipants);
const { t } = useTranslation();
const title = currentRoom?.name
? `${currentRoom.name} (${participantsCount})`
: t('participantsPane.headings.participantsList',
{ count: participantsCount });
const visitorsLabelText = visitorsCount && visitorsCount > 0
? t('participantsPane.headings.visitors', { count: visitorsCount })
: undefined;
const renderParticipant = ({ item }: any) => (
<MeetingParticipantItem
key = { item }
participantID = { item }
searchString = { searchString } />
);
const { color, shareDialogVisible } = inviteOthersControl;
return (
<View style = { styles.meetingListContainer }>
<Text style = { styles.visitorsLabel as TextStyle }>
{ visitorsLabelText }
</Text>
<Text
style = { styles.meetingListDescription as TextStyle }>
{ title }
@@ -94,12 +82,12 @@ const MeetingParticipantList = ({
showInviteButton
&& <Button
accessibilityLabel = 'participantsPane.actions.invite'
disabled = { isShareDialogVisible }
disabled = { shareDialogVisible }
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
icon = { () => (
<Icon
color = { iconColor }
color = { color }
size = { 20 }
src = { IconAddUser } />
) }
@@ -117,7 +105,10 @@ const MeetingParticipantList = ({
placeholder = { t('participantsPane.search') }
value = { searchString } />
<FlatList
data = { sortedParticipantIds as Array<any> }
data = { _iAmVisitor
? [ ...sortedRemoteParticipants ]
: [ localParticipant?.id, ...sortedRemoteParticipants ] as Array<any>
}
keyExtractor = { keyExtractor }
/* eslint-disable react/jsx-no-bind */
@@ -127,48 +118,4 @@ const MeetingParticipantList = ({
);
};
/**
* Maps (parts of) the redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
let sortedParticipantIds: any = getSortedParticipantIds(state);
const _iAmVisitor = iAmVisitor(state);
sortedParticipantIds = sortedParticipantIds.filter((id: any) => {
const participant = getParticipantById(state, id);
if (_iAmVisitor && participant?.local) {
return false;
}
return !isScreenShareParticipant(participant);
});
const currentRoomId = getCurrentRoomId(state);
const currentRoom = getBreakoutRooms(state)[currentRoomId];
const inviteOthersControl = getInviteOthersControl(state);
const { color, shareDialogVisible } = inviteOthersControl;
const isAddPeopleFeatureEnabled = addPeopleFeatureControl(state);
const participantsCount = sortedParticipantIds.length;
const showInviteButton = shouldRenderInviteButton(state);
const visitorsCount = state['features/visitors']?.count || 0;
return {
currentRoom,
iconColor: color,
inviteOthersControl,
isAddPeopleFeatureEnabled,
isShareDialogVisible: shareDialogVisible,
participantsCount,
showInviteButton,
sortedParticipantIds,
visitorsCount
};
}
export default connect(_mapStateToProps)(MeetingParticipantList);
export default MeetingParticipantList;

View File

@@ -12,6 +12,11 @@ import VisitorsList from './VisitorsList';
import styles from './styles';
/**
* Participants pane.
*
* @returns {React$Element<any>}
*/
const ParticipantsPane = () => {
const isLocalModerator = useSelector(isLocalParticipantModerator);
const keyExtractor

View File

@@ -9,7 +9,7 @@ const participantListDescription = {
fontSize: 15,
fontWeight: 'bold',
marginLeft: BaseTheme.spacing[2],
marginVertical: BaseTheme.spacing[2],
paddingVertical: BaseTheme.spacing[2],
position: 'relative',
width: '70%'
};
@@ -275,11 +275,13 @@ export default {
},
inputContainer: {
marginHorizontal: BaseTheme.spacing[3],
marginBottom: BaseTheme.spacing[3]
marginLeft: BaseTheme.spacing[3],
marginRight: BaseTheme.spacing[3],
marginBottom: BaseTheme.spacing[4]
},
centerInput: {
paddingRight: BaseTheme.spacing[3],
textAlign: 'center'
},

View File

@@ -13,7 +13,7 @@ import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../b
import { isButtonEnabled, showOverflowDrawer } from '../../../toolbox/functions.web';
import { iAmVisitor } from '../../../visitors/functions';
import { getSortedParticipantIds, isCurrentRoomRenamable, shouldRenderInviteButton } from '../../functions';
import { useParticipantDrawer } from '../../hooks.web';
import { useParticipantDrawer } from '../../hooks';
import RenameButton from '../breakout-rooms/components/web/RenameButton';
import { InviteButton } from './InviteButton';

View File

@@ -41,8 +41,7 @@ const useStyles = makeStyles<{ deviceStatusType?: string; }>()((theme, { deviceS
width: '16px',
height: '16px',
borderRadius: '100%',
backgroundColor: deviceStatusType === 'ok' ? theme.palette.success01 : ColorPalette.darkGrey,
flexShrink: 0
backgroundColor: deviceStatusType === 'ok' ? theme.palette.success01 : ColorPalette.darkGrey
}
};
});

View File

@@ -459,7 +459,7 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
});
if (autoTranscribeOnRecord) {
dispatch(setRequestingSubtitles(true, false, null));
dispatch(setRequestingSubtitles(true, false, null, true));
}
} else {
openRecordingDialog();

View File

@@ -415,7 +415,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
if (this.state.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE
&& this.state.shouldRecordTranscription) {
dispatch(setRequestingSubtitles(true, _displaySubtitles, _subtitlesLanguage));
dispatch(setRequestingSubtitles(true, _displaySubtitles, _subtitlesLanguage, true));
} else {
_conference?.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
isTranscribingEnabled: this.state.shouldRecordTranscription

View File

@@ -180,8 +180,8 @@ const LocalRecordingManager: ILocalRecordingManager = {
this.selfRecording.withVideo = Boolean(videoTrack);
const localTracks: MediaStreamTrack[] = [];
audioTrack && localTracks.push(audioTrack.clone());
videoTrack && localTracks.push(videoTrack.clone());
audioTrack && localTracks.push(audioTrack);
videoTrack && localTracks.push(videoTrack);
this.stream = new MediaStream(localTracks);
} else {
if (supportsCaptureHandle) {
@@ -282,9 +282,6 @@ const LocalRecordingManager: ILocalRecordingManager = {
this.audioContext = undefined;
this.audioDestination = undefined;
this.startTime = undefined;
this.stream = undefined;
this.selfRecording.on = false;
this.selfRecording.withVideo = false;
if (this.writableStream) {
try {

View File

@@ -27,6 +27,11 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
*/
classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
/**
* Whether to hide chat with permissions.
*/
disableChatWithPermissions: boolean;
/**
* If set hides the reactions moderation setting.
*/
@@ -52,11 +57,6 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
*/
followMeRecorderEnabled: boolean;
/**
* Whether to hide chat with permissions.
*/
hideChatWithPermissions: boolean;
/**
* Whether or not the user has selected the Start Audio Muted feature to be
* enabled.
@@ -210,12 +210,12 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
const {
audioModerationEnabled,
chatWithPermissionsEnabled,
disableChatWithPermissions,
disableReactionsModeration,
followMeActive,
followMeEnabled,
followMeRecorderActive,
followMeRecorderEnabled,
hideChatWithPermissions,
startAudioMuted,
startVideoMuted,
startReactionsMuted,
@@ -266,7 +266,7 @@ class ModeratorTab extends AbstractDialogTab<IProps, any> {
label = { t('settings.startReactionsMuted') }
name = 'start-reactions-muted'
onChange = { this._onStartReactionsMutedChanged } /> }
{ !hideChatWithPermissions
{ !disableChatWithPermissions
&& <Checkbox
checked = { chatWithPermissionsEnabled }
className = { classes.checkbox }

View File

@@ -162,12 +162,12 @@ export function getModeratorTabProps(stateful: IStateful) {
} = state['features/base/conference'];
const { followMeEnabled, followMeRecorderEnabled } = state['features/follow-me'];
const { groupChatWithPermissions } = state['features/chat'];
const { showChatPermissionsModeratorSetting, disableReactionsModeration } = state['features/base/config'];
const { disableReactionsModeration } = state['features/base/config'];
const followMeActive = isFollowMeActive(state);
const followMeRecorderActive = isFollowMeRecorderActive(state);
const showModeratorSettings = shouldShowModeratorSettings(state);
const conferenceMetadata = conference?.getMetadataHandler()?.getMetadata();
const hideChatWithPermissions = !showChatPermissionsModeratorSetting || conferenceMetadata?.allownersEnabled;
const disableChatWithPermissions = !conferenceMetadata?.allownersEnabled;
const isAudioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
const isVideoModerationEnabled = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
@@ -177,12 +177,12 @@ export function getModeratorTabProps(stateful: IStateful) {
videoModerationEnabled: isVideoModerationEnabled,
chatWithPermissionsEnabled: Boolean(groupChatWithPermissions),
showModeratorSettings: Boolean(conference && showModeratorSettings),
disableChatWithPermissions: Boolean(disableChatWithPermissions),
disableReactionsModeration: Boolean(disableReactionsModeration),
followMeActive: Boolean(conference && followMeActive),
followMeEnabled: Boolean(conference && followMeEnabled),
followMeRecorderActive: Boolean(conference && followMeRecorderActive),
followMeRecorderEnabled: Boolean(conference && followMeRecorderEnabled),
hideChatWithPermissions: Boolean(hideChatWithPermissions),
startReactionsMuted: Boolean(conference && startReactionsMuted),
startAudioMuted: Boolean(conference && startAudioMutedPolicy),
startVideoMuted: Boolean(conference && startVideoMutedPolicy)

View File

@@ -95,9 +95,11 @@ export function toggleRequestingSubtitles() {
export function setRequestingSubtitles(
enabled: boolean,
displaySubtitles = true,
language: string | null = `translation-languages:${DEFAULT_LANGUAGE}`) {
language: string | null = `translation-languages:${DEFAULT_LANGUAGE}`,
backendRecordingOn = false) {
return {
type: SET_REQUESTING_SUBTITLES,
backendRecordingOn,
displaySubtitles,
enabled,
language

View File

@@ -98,7 +98,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case SET_REQUESTING_SUBTITLES:
_requestingSubtitlesChange(store, action.enabled, action.language);
_requestingSubtitlesChange(store, action.enabled, action.language, action.backendRecordingOn);
break;
}
@@ -344,16 +344,17 @@ function _getPrimaryLanguageCode(language: string) {
* @param {Store} store - The redux store.
* @param {boolean} enabled - Whether subtitles should be enabled or not.
* @param {string} language - The language to use for translation.
* @param {boolean} backendRecordingOn - Whether backend recording is on or not.
* @private
* @returns {void}
*/
function _requestingSubtitlesChange(
{ dispatch, getState }: IStore,
enabled: boolean,
language?: string | null) {
language?: string | null,
backendRecordingOn = false) {
const state = getState();
const { conference } = state['features/base/conference'];
const backendRecordingOn = conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription;
conference?.setLocalParticipantProperty(
P_NAME_REQUESTING_TRANSCRIPTION,
@@ -362,7 +363,7 @@ function _requestingSubtitlesChange(
if (enabled && conference?.getTranscriptionStatus() === JitsiMeetJS.constants.transcriptionStatus.OFF
&& isJwtFeatureEnabled(getState(), MEET_FEATURES.TRANSCRIPTION, false)) {
if (!backendRecordingOn) {
if (!conference?.getMetadataHandler()?.getMetadata()?.asyncTranscription) {
conference?.dial(TRANSCRIBER_DIAL_NUMBER)
.catch((e: any) => {
logger.error('Error dialing', e);
@@ -375,7 +376,9 @@ function _requestingSubtitlesChange(
}));
dispatch(setSubtitlesError(true));
});
} else {
}
if (backendRecordingOn) {
conference?.getMetadataHandler()?.setMetadata(RECORDING_METADATA_ID, {
isTranscribingEnabled: true
});

View File

@@ -19,7 +19,6 @@ import {
import {
getJwtDisabledButtons,
getVisibleButtons,
getVisibleButtonsForReducedUI,
isButtonEnabled,
isToolboxVisible
} from '../../functions.web';
@@ -83,7 +82,8 @@ export default function Toolbox({
const isNarrowLayout = useSelector((state: IReduxState) => state['features/base/responsive-ui'].isNarrowLayout);
const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth);
const isModerator = useSelector(isLocalParticipantModerator);
const customToolbarButtons = useSelector((state: IReduxState) => state['features/base/config'].customToolbarButtons);
const customToolbarButtons = useSelector(
(state: IReduxState) => state['features/base/config'].customToolbarButtons);
const iAmRecorder = useSelector((state: IReduxState) => state['features/base/config'].iAmRecorder);
const iAmSipGateway = useSelector((state: IReduxState) => state['features/base/config'].iAmSipGateway);
const overflowDrawer = useSelector((state: IReduxState) => state['features/toolbox'].overflowDrawer);
@@ -110,8 +110,6 @@ export default function Toolbox({
const toolbarVisible = useSelector(isToolboxVisible);
const mainToolbarButtonsThresholds
= useSelector((state: IReduxState) => state['features/toolbox'].mainToolbarButtonsThresholds);
const { reducedUImainToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
const reducedUI = useSelector((state: IReduxState) => state['features/base/responsive-ui'].reducedUI);
const allButtons = useToolboxButtons(customToolbarButtons);
const isMobile = isMobileBrowser();
const endConferenceSupported = Boolean(conference?.isEndConferenceSupported() && isModerator);
@@ -235,7 +233,7 @@ export default function Toolbox({
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
const containerClassName = `toolbox-content${isMobile || isNarrowLayout ? ' toolbox-content-mobile' : ''}`;
const normalUIButtons = getVisibleButtons({
const { mainMenuButtons, overflowMenuButtons } = getVisibleButtons({
allButtons,
buttonsWithNotifyClick,
toolbarButtons: toolbarButtonsToUse,
@@ -243,20 +241,6 @@ export default function Toolbox({
jwtDisabledButtons,
mainToolbarButtonsThresholds
});
const reducedUIButtons = getVisibleButtonsForReducedUI({
allButtons,
buttonsWithNotifyClick,
jwtDisabledButtons,
reducedUImainToolbarButtons,
});
const mainMenuButtons = reducedUI
? reducedUIButtons.mainMenuButtons
: normalUIButtons.mainMenuButtons;
const overflowMenuButtons = reducedUI
? []
: normalUIButtons.overflowMenuButtons;
const raiseHandInOverflowMenu = overflowMenuButtons.some(({ key }) => key === 'raisehand');
const showReactionsInOverflowMenu = _shouldDisplayReactionsButtons
&& (

View File

@@ -12,8 +12,6 @@ export const DUMMY_9_BUTTONS_THRESHOLD_VALUE = Symbol('9_BUTTONS_THRESHOLD_VALUE
*/
export const DUMMY_10_BUTTONS_THRESHOLD_VALUE = Symbol('10_BUTTONS_THRESHOLD_VALUE');
export const DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS = [ 'microphone', 'camera' ];
/**
* Thresholds for displaying toolbox buttons.
*/

View File

@@ -6,9 +6,9 @@ import { IGUMPendingState } from '../base/media/types';
import { isScreenMediaShared } from '../screen-share/functions';
import { isWhiteboardVisible } from '../whiteboard/functions';
import { DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS, MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants';
import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants';
import { isButtonEnabled } from './functions.any';
import { IGetVisibleButtonsForReducedUIParams, IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
import { IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
export * from './functions.any';
@@ -201,41 +201,6 @@ export function getVisibleButtons({
};
}
/**
* Returns buttons that need to be rendered for reduced UI mode.
*
* @param {IGetVisibleButtonsForReducedUIParams} params - The parameters needed to extract the visible buttons.
* @returns {Object} - The visible buttons for reduced ui.
*/
export function getVisibleButtonsForReducedUI({
allButtons,
buttonsWithNotifyClick,
jwtDisabledButtons,
reducedUImainToolbarButtons
}: IGetVisibleButtonsForReducedUIParams) {
setButtonsNotifyClickMode(allButtons, buttonsWithNotifyClick);
if (!Array.isArray(reducedUImainToolbarButtons) || reducedUImainToolbarButtons.length === 0) {
const defaultButtons = DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS.map(key => allButtons[key]);
return {
mainMenuButtons: defaultButtons
};
}
const filteredButtons = reducedUImainToolbarButtons.filter(key =>
typeof key !== 'undefined'
&& !jwtDisabledButtons.includes(key)
&& isButtonEnabled(key, reducedUImainToolbarButtons)
&& allButtons[key]);
const mainMenuButtons = filteredButtons.map(key => allButtons[key]);
return {
mainMenuButtons
};
}
/**
* Returns the list of participant menu buttons that have that notify the api when clicked.
*

View File

@@ -107,10 +107,3 @@ export interface IGetVisibleButtonsParams {
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
toolbarButtons: string[];
}
export interface IGetVisibleButtonsForReducedUIParams {
allButtons: { [key: string]: IToolboxButton; };
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
jwtDisabledButtons: string[];
reducedUImainToolbarButtons?: string[];
}

View File

@@ -7,7 +7,6 @@
"cs": "cs-CZ",
"da": "da-DK",
"de": "de-DE",
"dsb": "dsb-DE",
"el": "el-GR",
"enGB": "en-GB",
"es": "es-ES",

View File

@@ -73,11 +73,6 @@ export function muteRemote(participantId: string, mediaType: MediaType) {
const muteMediaType = mediaType === MEDIA_TYPE.SCREENSHARE ? 'desktop' : mediaType;
dispatch(muteRemoteParticipant(participantId, muteMediaType));
// Notify external API that participant was muted by moderator
if (typeof APP !== 'undefined') {
APP.API.notifyParticipantMuted(participantId, true, muteMediaType, false);
}
};
}

View File

@@ -6,7 +6,6 @@
- breakout_rooms - A table containing breakout rooms created in the main room. The keys are the JIDs of the breakout rooms, and the values are their subjects.
- breakout_rooms_active - Whether there was a breakout room created in the main room.
- breakout_rooms_counter - A counter for breakout rooms created in the main room.
- lobby_disabled - Whether lobby was disabled for the room by the backend.
- flip_participant_nick - Used in mod_muc_flip, when flipping a participant we store the nick of the second device/participant. Same processing as kicked_participant_nick.
- hideDisplayNameForGuests - When set to true, the display name of participants is hidden for guests.
- jicofo_lock - A boolean value, when set to true the room is locked waiting for Jicofo to join. All attempts to join will be queued until Jicofo joins.

View File

@@ -203,7 +203,7 @@ function on_message(event)
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end

View File

@@ -57,7 +57,7 @@ function on_message(event)
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end

View File

@@ -19,7 +19,7 @@ local function on_message(event)
-- this should already been through domain mapper and this should be the real room jid [tenant]name format
local room = get_room_from_jid(stanza.attr.to);
if not room then
module:log('warn', 'No room found for %s', stanza.attr.to);
module:log('warn', 'No room found found for %s', stanza.attr.to);
return;
end

View File

@@ -106,7 +106,7 @@ function on_message(event)
-- get room name with tenant and find room.
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return;
end
@@ -138,7 +138,7 @@ function poll_created(event)
-- get room name with tenant and find room.
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end

View File

@@ -30,8 +30,7 @@ module:hook("muc-room-created", function(event)
local room = event.room;
if room.jitsiMetadata then
-- indicates whether all participants in the room will be moderators
room.jitsiMetadata.allownersEnabled = not is_moderated(room.jid);
room.jitsiMetadata.allownersEnabled = true;
end
end, -2); -- room_metadata should run before this module on -1

View File

@@ -55,7 +55,7 @@ function on_message(event)
-- get room name with tenant and find room
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end

View File

@@ -413,16 +413,11 @@ function process_lobby_muc_loaded(lobby_muc, host_module)
host_module:hook('host-disco-info-node', function (event)
local session, reply, node = event.origin, event.reply, event.node;
if node == LOBBY_IDENTITY_TYPE
and session.jitsi_web_query_room then
and session.jitsi_web_query_room
and check_display_name_required then
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if room and room._data.lobby_disabled then
-- we cannot remove the child from the stanza so let's just change the type
local lobby_identity = reply:get_child_with_attr('identity', nil, 'type', LOBBY_IDENTITY_TYPE);
lobby_identity.attr.type = 'DISABLED_'..LOBBY_IDENTITY_TYPE;
end
if check_display_name_required and room and room._data.lobbyroom then
if room and room._data.lobbyroom then
reply:tag('feature', { var = DISPLAY_NAME_REQUIRED_FEATURE }):up();
end
end
@@ -494,11 +489,6 @@ process_host_module(main_muc_component_config, function(host_module, host)
end
local members_only = event.fields['muc#roomconfig_membersonly'] and true or nil;
if members_only then
-- if lobby disabled just ignore and return
if room._data.lobby_disabled then
module:log('warn', 'Lobby is disabled for room %s, cannot enable members only', room.jid);
return;
end
local lobby_created = attach_lobby_room(room, actor);
if lobby_created then
module:fire_event('jitsi-lobby-enabled', { room = room; });

View File

@@ -175,7 +175,7 @@ local function filterTranscriptionResult(event)
local room = get_room_from_jid(stanza.attr.to);
if not room then
module:log('warn', 'No room found for %s', stanza.attr.to);
module:log('warn', 'No room found found for %s', stanza.attr.to);
return;
end

View File

@@ -33,6 +33,15 @@ local main_domain = module:get_option_string('main_domain');
-- only the visitor prosody has main_domain setting
local is_visitor_prosody = main_domain ~= nil;
-- Logs a warning and returns true if a room does not
-- have poll data associated with it.
local function check_polls(room)
if room.polls == nil then
module:log("warn", "no polls data in room");
return true;
end
return false;
end
local function validate_polls(data)
if type(data) ~= 'table' then
@@ -44,7 +53,7 @@ local function validate_polls(data)
if data.command ~= 'new-poll' and data.command ~= 'answer-poll' then
return false;
end
if type(data.answers) ~= 'table' or #data.answers == 0 then
if type(data.answers) ~= 'table' then
return false;
end
@@ -143,15 +152,17 @@ end
occupant = main_room:get_occupant_by_real_jid(occupant_jid);
if main_room._data.breakout_rooms_active and not occupant then
if main_room._data.breakout_rooms_active then
-- let's find is this participant in the main room or in some breakout room
-- not in main room, let's check breakout rooms
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
local breakout_room = get_room_from_jid(breakout_room_jid);
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
if occupant then
room = breakout_room;
break;
if not occupant then
-- not in main room, let's check breakout rooms
for breakout_room_jid, subject in pairs(main_room._data.breakout_rooms or {}) do
local breakout_room = get_room_from_jid(breakout_room_jid);
occupant = breakout_room:get_occupant_by_real_jid(occupant_jid);
if occupant then
room = breakout_room;
break;
end
end
end
else
@@ -165,7 +176,7 @@ end
end
if not room then
module:log('warn', 'No room found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix);
module:log('warn', 'No room found found for %s %s', session.jitsi_web_query_room, session.jitsi_web_query_prefix);
return;
end
@@ -211,6 +222,8 @@ end
return true;
end
if check_polls(room) then return end
local poll_creator = occupant_details;
if room.polls.count >= POLLS_LIMIT then
@@ -273,6 +286,8 @@ end
module:context(jid.host(room.jid)):fire_event('poll-created', pollData);
elseif data.command == "answer-poll" then
if check_polls(room) then return end
local poll = room.polls.by_id[data.pollId];
if poll == nil then
module:log("warn", "answering inexistent poll %s", data.pollId);

View File

@@ -151,7 +151,7 @@ function on_message(event)
local room = get_room_from_jid(room_jid_match_rewrite(roomJid));
if not room then
module:log('warn', 'No room found for %s/%s',
module:log('warn', 'No room found found for %s/%s',
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end

View File

@@ -74,7 +74,17 @@ local function verify_user(session, stanza)
or allowlist:contains(user_bare_jid)
-- allow main participants in visitor mode
or session.type == 's2sin' then
or session.type == 's2sin'
-- Let Jigasi or transcriber pass throw
or util.is_sip_jigasi(stanza)
or util.is_transcriber_jigasi(stanza)
-- is jibri
or util.is_jibri(user_jid)
-- Let Sip Jibri pass through
or util.is_sip_jibri_join(stanza) then
if DEBUG then module:log("debug", "Token not required from user in allow list: %s", user_jid); end
return true;
end

View File

@@ -1,35 +0,0 @@
import { setTestProperties } from '../../helpers/TestProperties';
import { ensureOneParticipant } from '../../helpers/participants';
setTestProperties(__filename, {
description: 'Test that conference requests work over XMPP',
usesBrowsers: [ 'p1' ]
});
describe('XMPP Conference Request', () => {
it('join with conferenceRequestUrl disabled', async () => {
await ensureOneParticipant({
skipWaitToJoin: true,
configOverwrite: {
prejoinConfig: {
enabled: true
}
}
});
const { p1 } = ctx;
// Update config before joining, because this option cannot be overridden with URL params.
await p1.driver.execute(async () => {
config.conferenceRequestUrl = '';
APP.store.dispatch({
type: 'OVERWRITE_CONFIG',
config
});
});
await p1.getPreJoinScreen().getJoinButton().click();
await p1.waitForMucJoinedOrError();
expect(await p1.isInMuc()).toBe(true);
});
});

View File

@@ -110,6 +110,7 @@ describe('Lobby', () => {
await p3.waitForSendReceiveData();
await p3.waitForRemoteStreams(2);
// now check third one display name in the room, is the one set in the prejoin screen
// now check third one display name in the room, is the one set in the prejoin screen
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());

View File

@@ -323,7 +323,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
* @param {Object} context - The context object.
*/
beforeTest(test, context) {
// Extract directory to use as parent suite and describe block name as suite
// Use the directory under 'tests/specs' as the parent suite
const dirMatch = test.file.match(/.*\/tests\/specs\/([^\/]+)\//);
const dir = dirMatch ? dirMatch[1] : false;
const fileMatch = test.file.match(/.*\/tests\/specs\/(.*)/);
@@ -337,10 +337,8 @@ export const config: WebdriverIO.MultiremoteConfig = {
AllureReporter.addLink(`https://github.com/jitsi/jitsi-meet/blob/master/tests/specs/${file}`, 'Code');
}
// For Allure v3: set directory as parent suite and describe block as suite
if (dir && test.parent) {
if (dir) {
AllureReporter.addParentSuite(dir);
AllureReporter.addSuite(test.parent);
}
if (ctx.skipSuiteTests) {
@@ -457,34 +455,6 @@ export const config: WebdriverIO.MultiremoteConfig = {
* @returns {Promise<void>}
*/
onComplete() {
// Clean up duplicate parentSuite labels from Allure results
const resultsDir = `${TEST_RESULTS_DIR}/allure-results`;
const resultFiles = fs.readdirSync(resultsDir).filter(f => f.endsWith('-result.json'));
resultFiles.forEach(file => {
const filePath = path.join(resultsDir, file);
const result = JSON.parse(fs.readFileSync(filePath, 'utf8'));
// Keep only the LAST parentSuite label (the manual one with directory name)
// Remove the automatic one from WebDriverIO (describe block name)
const parentSuiteLabels: any[] = [];
const otherLabels: any[] = [];
result.labels.forEach((label: any) => {
if (label.name === 'parentSuite') {
parentSuiteLabels.push(label);
} else {
otherLabels.push(label);
}
});
// Keep only the last parentSuite (the directory name we manually added)
if (parentSuiteLabels.length > 1) {
result.labels = [ ...otherLabels, parentSuiteLabels[parentSuiteLabels.length - 1] ];
fs.writeFileSync(filePath, JSON.stringify(result));
}
});
const reportError = new Error('Could not generate Allure report');
const generation = allure([
'generate', `${TEST_RESULTS_DIR}/allure-results`,