diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 06f9ef4..4c37e96 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to the KoalaSync browser extension and relay server. --- +## [v3.0.0] — 2026-07-26 + +### Added +- **Extension: Optional encrypted room chat** — Rooms now support live-only, end-to-end encrypted text messages. Chat is disabled and hidden by default and can be enabled explicitly in the extension options. +- **Extension: Persistent room chat key** — Every room generates and retains a chat key even while chat is disabled, so chat can be enabled later without creating a new room. +- **Extension: Floating player chat** — When enabled, chat is available from a floating control over the selected player and opens in an overlay without replacing the synchronized video. +- **Extension: Dedicated chat settings** — Chat now has its own settings section for enablement, left/right/free-floating placement, compact/standard/large/custom sizing, and bubble-versus-open startup behavior. + +### Security +- **Ciphertext-only relay** — The relay receives and forwards encrypted message payloads without storing chat history or accepting client-supplied plaintext, sender identities, timestamps, or message IDs as authoritative. +- **Backward-compatible rollout** — Versioned `chat-v1` capabilities ensure old non-chat extensions never receive unknown chat events, while current extensions continue to work with older chatless relay versions. + +### Changed +- **Build dependencies** — Updated the supported build and validation toolchain and refreshed compatible transitive dependencies. +- **Relay container runtime** — Moved the production container from end-of-life Node.js 20 to Node.js 24 LTS and made production dependency installation deterministic with `npm ci --omit=dev`. + ## [v2.6.4] — 2026-07-16 ### Changed diff --git a/extension/background.js b/extension/background.js index 3403995..50ada81 100644 --- a/extension/background.js +++ b/extension/background.js @@ -452,7 +452,8 @@ function emitEpisodeLobbyForCurrentPrivacy() { // removes legacy keys that older versions wrote to sync (and that would // otherwise be redistributed across devices and resurrected on reinstall). const LEGACY_SYNC_KEYS = [ - 'serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', 'username', + 'serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', + 'chatEnabled', 'chatPosition', 'chatSize', 'chatStartMode', 'username', 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'titlePrivacyMode', 'sendTabTitle', 'mediaTitlePrivacyMode' diff --git a/extension/chat-overlay.js b/extension/chat-overlay.js index 0870bd3..d9695d7 100644 --- a/extension/chat-overlay.js +++ b/extension/chat-overlay.js @@ -9,6 +9,11 @@ const MIN_HEIGHT = 320; const DEFAULT_WIDTH = 360; const DEFAULT_HEIGHT = 520; + const SIZE_PRESETS = Object.freeze({ + compact: Object.freeze({ width: 320, height: 400 }), + standard: Object.freeze({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }), + large: Object.freeze({ width: 440, height: 640 }) + }); const storageKey = `chatOverlayLayout:${location.origin}`; const systemTheme = window.matchMedia('(prefers-color-scheme: light)'); let context = null; @@ -16,10 +21,15 @@ let destroyed = false; let saveTimer = null; let applyingLayout = false; + let preferencesLoaded = false; + let startStateApplied = false; let refreshGeneration = 0; let sendGeneration = 0; let sending = false; let layout = { mode: 'right', x: 24, y: 72, width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, detachedInitialized: false }; + let chatPosition = 'right'; + let chatSize = 'standard'; + let chatStartMode = 'bubble'; let themeMode = 'system'; let themePalette = 'eucalyptus'; @@ -243,6 +253,21 @@ return { width: Math.max(1, window.innerWidth), height: Math.max(1, window.innerHeight) }; } + function normalizePosition(value) { + return ['left', 'right', 'detached'].includes(value) ? value : 'right'; + } + + function normalizeSize(value) { + return ['compact', 'standard', 'large', 'custom'].includes(value) ? value : 'standard'; + } + + function preferredSize() { + return SIZE_PRESETS[chatSize] || { + width: Number(layout.width) || DEFAULT_WIDTH, + height: Number(layout.height) || DEFAULT_HEIGHT + }; + } + function clampDetached() { const viewport = viewportBounds(); const maxWidth = Math.max(1, Math.min(600, viewport.width - 16)); @@ -265,6 +290,7 @@ function applyLayout() { applyingLayout = true; + const size = preferredSize(); panel.style.right = 'auto'; panel.style.left = 'auto'; panel.style.bottom = 'auto'; @@ -288,8 +314,8 @@ const gutter = Math.min(16, Math.max(0, Math.floor((viewport.width - 1) / 2))); const panelTop = viewport.height < MIN_HEIGHT + 80 ? Math.min(8, Math.max(0, viewport.height - 1)) : 64; panel.style.top = `${panelTop}px`; - panel.style.width = `${Math.max(1, Math.min(DEFAULT_WIDTH, viewport.width - gutter * 2))}px`; - panel.style.height = `${Math.max(1, Math.min(560, viewport.height - panelTop - Math.min(16, Math.max(0, viewport.height - panelTop - 1))))}px`; + panel.style.width = `${Math.max(1, Math.min(size.width, viewport.width - gutter * 2))}px`; + panel.style.height = `${Math.max(1, Math.min(size.height, viewport.height - panelTop - Math.min(16, Math.max(0, viewport.height - panelTop - 1))))}px`; panel.style[layout.mode] = `${gutter}px`; launcher.style.top = `${Math.max(0, Math.min(viewport.height - 48, viewport.height / 2 - 24))}px`; launcher.style.left = layout.mode === 'left' ? `${gutter}px` : 'auto'; @@ -298,16 +324,32 @@ globalThis.queueMicrotask(() => { applyingLayout = false; }); } - function setMode(mode) { - if (!['left', 'right', 'detached'].includes(mode)) return; + function setMode(mode, persistPreference = true) { + mode = normalizePosition(mode); if (mode === 'detached' && !layout.detachedInitialized) { - layout.x = Math.max(8, Math.round((window.innerWidth - DEFAULT_WIDTH) / 2)); - layout.y = Math.max(8, Math.round((window.innerHeight - DEFAULT_HEIGHT) / 2)); + const size = preferredSize(); + layout.x = Math.max(8, Math.round((window.innerWidth - size.width) / 2)); + layout.y = Math.max(8, Math.round((window.innerHeight - size.height) / 2)); layout.detachedInitialized = true; } + chatPosition = mode; layout.mode = mode; applyLayout(); saveLayout(); + if (persistPreference) chrome.storage.local.set({ chatPosition: mode }).catch(() => {}); + } + + function setSize(size, persistPreference = true) { + chatSize = normalizeSize(size); + const preset = SIZE_PRESETS[chatSize]; + if (preset) { + layout.width = preset.width; + layout.height = preset.height; + if (layout.mode === 'detached') clampDetached(); + } + applyLayout(); + saveLayout(); + if (persistPreference) chrome.storage.local.set({ chatSize }).catch(() => {}); } function setOpened(next) { @@ -328,10 +370,19 @@ const hasKey = !!context?.hasKey; const connected = !!context?.connected; context = context ? { ...context, enabled: supported && optedIn && hasKey && connected } : null; - if (previousRoomId && previousRoomId !== context?.roomId) clearMessages(); + if (previousRoomId && previousRoomId !== context?.roomId) { + clearMessages(); + startStateApplied = false; + } host.style.display = supported && optedIn ? '' : 'none'; launcher.setAttribute('aria-disabled', String(!context?.enabled)); - if (!context?.enabled) setOpened(false); + if (!optedIn) startStateApplied = false; + if (!context?.enabled) { + setOpened(false); + } else if (preferencesLoaded && !startStateApplied) { + startStateApplied = true; + setOpened(chatStartMode === 'open'); + } applyStrings(); applyLayout(); } @@ -481,6 +532,15 @@ if (changes.themeMode) themeMode = changes.themeMode.newValue || 'system'; if (changes.themePalette) themePalette = changes.themePalette.newValue || 'eucalyptus'; if (changes.themeMode || changes.themePalette) applyTheme(); + if (changes.chatPosition) setMode(changes.chatPosition.newValue, false); + if (changes.chatSize) setSize(changes.chatSize.newValue, false); + if (changes.chatStartMode) { + chatStartMode = changes.chatStartMode.newValue === 'open' ? 'open' : 'bubble'; + if (context?.enabled) { + startStateApplied = true; + setOpened(chatStartMode === 'open'); + } + } if (changes.locale) refresh(); } @@ -516,16 +576,25 @@ systemTheme.addEventListener('change', handleSystemTheme); chrome.storage.onChanged.addListener(handleStorage); chrome.runtime.onMessage.addListener(handleRuntime); - chrome.storage.local.get([storageKey, 'themeMode', 'themePalette'], data => { + chrome.storage.local.get([storageKey, 'themeMode', 'themePalette', 'chatPosition', 'chatSize', 'chatStartMode'], data => { if (data[storageKey] && typeof data[storageKey] === 'object') layout = { ...layout, ...data[storageKey] }; - if (!['left', 'right', 'detached'].includes(layout.mode)) layout.mode = 'right'; + chatPosition = normalizePosition(data.chatPosition); + chatSize = normalizeSize(data.chatSize); + chatStartMode = data.chatStartMode === 'open' ? 'open' : 'bubble'; + layout.mode = chatPosition; if (layout.mode === 'detached') layout.detachedInitialized = true; + const preset = SIZE_PRESETS[chatSize]; + if (preset) { + layout.width = preset.width; + layout.height = preset.height; + } themeMode = data.themeMode || 'system'; themePalette = data.themePalette || 'eucalyptus'; + preferencesLoaded = true; applyTheme(); applyLayout(); + refresh(); }); window.koalaSyncChatOverlay = { refresh, destroy }; - refresh(); })(); diff --git a/extension/locales/de.json b/extension/locales/de.json index bbbd366..6cc5489 100644 --- a/extension/locales/de.json +++ b/extension/locales/de.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Passe Wiedergabe-, Benachrichtigungs- und Audioeinstellungen an.", "LABEL_CHAT_ENABLED": "Raum-Chat aktivieren", "LABEL_CHAT_ENABLED_TOOLTIP": "Verschlüsselten Raum-Chat im ausgewählten Video-Tab anzeigen.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Darstellung und Position des verschlüsselten Raum-Chats festlegen.", + "CHAT_SETTINGS_NOTE": "Optional und Ende-zu-Ende verschlüsselt. Nachrichten sind nur live sichtbar und werden vom Relay nicht gespeichert.", + "LABEL_CHAT_POSITION": "Position", + "LABEL_CHAT_POSITION_TOOLTIP": "Lege fest, wo der Chat am ausgewählten Player angezeigt wird.", + "LABEL_CHAT_SIZE": "Overlay-Größe", + "LABEL_CHAT_SIZE_TOOLTIP": "Wähle die Standardgröße des Chat-Overlays.", + "OPTION_CHAT_SIZE_COMPACT": "Kompakt", + "OPTION_CHAT_SIZE_STANDARD": "Standard", + "OPTION_CHAT_SIZE_LARGE": "Groß", + "OPTION_CHAT_SIZE_CUSTOM": "Letzte eigene Größe", + "LABEL_CHAT_START_MODE": "Startansicht", + "LABEL_CHAT_START_MODE_TOOLTIP": "Lege fest, ob der Chat als schwebende Blase oder geöffnetes Overlay startet.", + "OPTION_CHAT_START_BUBBLE": "Schwebende Chatblase", + "OPTION_CHAT_START_OPEN": "Geöffnetes Overlay", "CHAT_TITLE": "Raum-Chat", "CHAT_LIVE_ONLY": "Nur live. Kein Verlauf.", "CHAT_OPEN": "Raum-Chat öffnen", diff --git a/extension/locales/en.json b/extension/locales/en.json index 6d7e478..e1d23bc 100644 --- a/extension/locales/en.json +++ b/extension/locales/en.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Customize playback, notifications, and audio preferences.", "LABEL_CHAT_ENABLED": "Enable Room Chat", "LABEL_CHAT_ENABLED_TOOLTIP": "Show encrypted room chat on the selected video tab.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure encrypted room chat appearance and placement.", + "CHAT_SETTINGS_NOTE": "Optional and end-to-end encrypted. Messages are live only and are not stored by the relay.", + "LABEL_CHAT_POSITION": "Position", + "LABEL_CHAT_POSITION_TOOLTIP": "Choose where chat is attached to the selected player.", + "LABEL_CHAT_SIZE": "Overlay size", + "LABEL_CHAT_SIZE_TOOLTIP": "Choose the default size of the chat overlay.", + "OPTION_CHAT_SIZE_COMPACT": "Compact", + "OPTION_CHAT_SIZE_STANDARD": "Standard", + "OPTION_CHAT_SIZE_LARGE": "Large", + "OPTION_CHAT_SIZE_CUSTOM": "Last custom size", + "LABEL_CHAT_START_MODE": "Start as", + "LABEL_CHAT_START_MODE_TOOLTIP": "Choose whether chat starts as a floating bubble or an open overlay.", + "OPTION_CHAT_START_BUBBLE": "Floating bubble", + "OPTION_CHAT_START_OPEN": "Open overlay", "CHAT_TITLE": "Room Chat", "CHAT_LIVE_ONLY": "Live only. No history.", "CHAT_OPEN": "Open room chat", diff --git a/extension/locales/es.json b/extension/locales/es.json index a88b151..37b2317 100644 --- a/extension/locales/es.json +++ b/extension/locales/es.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personaliza la reproducción, las notificaciones y las preferencias de audio.", "LABEL_CHAT_ENABLED": "Activar chat de la sala", "LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar el chat cifrado de la sala en la pestaña de vídeo seleccionada.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configura la apariencia y la posición del chat cifrado de la sala.", + "CHAT_SETTINGS_NOTE": "Opcional y cifrado de extremo a extremo. Los mensajes solo se muestran en directo y el relé no los almacena.", + "LABEL_CHAT_POSITION": "Posición", + "LABEL_CHAT_POSITION_TOOLTIP": "Elige dónde se acopla el chat al reproductor seleccionado.", + "LABEL_CHAT_SIZE": "Tamaño de la superposición", + "LABEL_CHAT_SIZE_TOOLTIP": "Elige el tamaño predeterminado de la superposición del chat.", + "OPTION_CHAT_SIZE_COMPACT": "Compacto", + "OPTION_CHAT_SIZE_STANDARD": "Estándar", + "OPTION_CHAT_SIZE_LARGE": "Grande", + "OPTION_CHAT_SIZE_CUSTOM": "Último tamaño personalizado", + "LABEL_CHAT_START_MODE": "Iniciar como", + "LABEL_CHAT_START_MODE_TOOLTIP": "Elige si el chat comienza como una burbuja flotante o una superposición abierta.", + "OPTION_CHAT_START_BUBBLE": "Burbuja flotante", + "OPTION_CHAT_START_OPEN": "Superposición abierta", "CHAT_TITLE": "Chat de la sala", "CHAT_LIVE_ONLY": "Solo en directo. Sin historial.", "CHAT_OPEN": "Abrir chat de la sala", diff --git a/extension/locales/fr.json b/extension/locales/fr.json index b8c63f2..be95e7a 100644 --- a/extension/locales/fr.json +++ b/extension/locales/fr.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personnalisez la lecture, les notifications et les préférences audio.", "LABEL_CHAT_ENABLED": "Activer le chat du salon", "LABEL_CHAT_ENABLED_TOOLTIP": "Afficher le chat chiffré du salon dans l'onglet vidéo sélectionné.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configurez l'apparence et l'emplacement du chat chiffré du salon.", + "CHAT_SETTINGS_NOTE": "Facultatif et chiffré de bout en bout. Les messages sont uniquement en direct et ne sont pas stockés par le relais.", + "LABEL_CHAT_POSITION": "Position", + "LABEL_CHAT_POSITION_TOOLTIP": "Choisissez où le chat est ancré au lecteur sélectionné.", + "LABEL_CHAT_SIZE": "Taille de la fenêtre", + "LABEL_CHAT_SIZE_TOOLTIP": "Choisissez la taille par défaut de la fenêtre de chat.", + "OPTION_CHAT_SIZE_COMPACT": "Compacte", + "OPTION_CHAT_SIZE_STANDARD": "Standard", + "OPTION_CHAT_SIZE_LARGE": "Grande", + "OPTION_CHAT_SIZE_CUSTOM": "Dernière taille personnalisée", + "LABEL_CHAT_START_MODE": "Affichage initial", + "LABEL_CHAT_START_MODE_TOOLTIP": "Choisissez si le chat démarre sous forme de bulle flottante ou de fenêtre ouverte.", + "OPTION_CHAT_START_BUBBLE": "Bulle flottante", + "OPTION_CHAT_START_OPEN": "Fenêtre ouverte", "CHAT_TITLE": "Chat du salon", "CHAT_LIVE_ONLY": "En direct uniquement. Aucun historique.", "CHAT_OPEN": "Ouvrir le chat du salon", diff --git a/extension/locales/it.json b/extension/locales/it.json index 7fbe1f3..d09157f 100644 --- a/extension/locales/it.json +++ b/extension/locales/it.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalizza la riproduzione, le notifiche e le preferenze audio.", "LABEL_CHAT_ENABLED": "Attiva chat della stanza", "LABEL_CHAT_ENABLED_TOOLTIP": "Mostra la chat crittografata della stanza nella scheda video selezionata.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configura l'aspetto e la posizione della chat crittografata della stanza.", + "CHAT_SETTINGS_NOTE": "Opzionale e crittografata end-to-end. I messaggi sono solo in diretta e non vengono memorizzati dal relay.", + "LABEL_CHAT_POSITION": "Posizione", + "LABEL_CHAT_POSITION_TOOLTIP": "Scegli dove agganciare la chat al lettore selezionato.", + "LABEL_CHAT_SIZE": "Dimensione overlay", + "LABEL_CHAT_SIZE_TOOLTIP": "Scegli la dimensione predefinita dell'overlay della chat.", + "OPTION_CHAT_SIZE_COMPACT": "Compatto", + "OPTION_CHAT_SIZE_STANDARD": "Standard", + "OPTION_CHAT_SIZE_LARGE": "Grande", + "OPTION_CHAT_SIZE_CUSTOM": "Ultima dimensione personalizzata", + "LABEL_CHAT_START_MODE": "Avvio come", + "LABEL_CHAT_START_MODE_TOOLTIP": "Scegli se la chat si avvia come bolla mobile o overlay aperto.", + "OPTION_CHAT_START_BUBBLE": "Bolla mobile", + "OPTION_CHAT_START_OPEN": "Overlay aperto", "CHAT_TITLE": "Chat della stanza", "CHAT_LIVE_ONLY": "Solo in diretta. Nessuna cronologia.", "CHAT_OPEN": "Apri la chat della stanza", diff --git a/extension/locales/ja.json b/extension/locales/ja.json index 708f970..70b1ce6 100644 --- a/extension/locales/ja.json +++ b/extension/locales/ja.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "再生、通知、オーディオ設定をカスタマイズします。", "LABEL_CHAT_ENABLED": "ルームチャットを有効化", "LABEL_CHAT_ENABLED_TOOLTIP": "選択した動画タブに暗号化されたルームチャットを表示します。", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "暗号化されたルームチャットの外観と位置を設定します。", + "CHAT_SETTINGS_NOTE": "任意で利用でき、エンドツーエンドで暗号化されます。メッセージはリアルタイムのみで、リレーには保存されません。", + "LABEL_CHAT_POSITION": "位置", + "LABEL_CHAT_POSITION_TOOLTIP": "選択したプレーヤーのどこにチャットを配置するか選択します。", + "LABEL_CHAT_SIZE": "オーバーレイサイズ", + "LABEL_CHAT_SIZE_TOOLTIP": "チャットオーバーレイの標準サイズを選択します。", + "OPTION_CHAT_SIZE_COMPACT": "コンパクト", + "OPTION_CHAT_SIZE_STANDARD": "標準", + "OPTION_CHAT_SIZE_LARGE": "大", + "OPTION_CHAT_SIZE_CUSTOM": "前回のカスタムサイズ", + "LABEL_CHAT_START_MODE": "開始表示", + "LABEL_CHAT_START_MODE_TOOLTIP": "フローティングバブルまたは開いたオーバーレイで開始するか選択します。", + "OPTION_CHAT_START_BUBBLE": "フローティングバブル", + "OPTION_CHAT_START_OPEN": "開いたオーバーレイ", "CHAT_TITLE": "ルームチャット", "CHAT_LIVE_ONLY": "ライブのみ。履歴はありません。", "CHAT_OPEN": "ルームチャットを開く", diff --git a/extension/locales/ko.json b/extension/locales/ko.json index 57b4a76..b6d68dc 100644 --- a/extension/locales/ko.json +++ b/extension/locales/ko.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "재생, 알림 및 오디오 설정을 맞춤설정합니다.", "LABEL_CHAT_ENABLED": "방 채팅 사용", "LABEL_CHAT_ENABLED_TOOLTIP": "선택한 동영상 탭에 암호화된 방 채팅을 표시합니다.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "암호화된 방 채팅의 모양과 위치를 설정합니다.", + "CHAT_SETTINGS_NOTE": "선택 기능이며 종단 간 암호화됩니다. 메시지는 실시간으로만 표시되고 릴레이에 저장되지 않습니다.", + "LABEL_CHAT_POSITION": "위치", + "LABEL_CHAT_POSITION_TOOLTIP": "선택한 플레이어에서 채팅을 고정할 위치를 선택합니다.", + "LABEL_CHAT_SIZE": "오버레이 크기", + "LABEL_CHAT_SIZE_TOOLTIP": "채팅 오버레이의 기본 크기를 선택합니다.", + "OPTION_CHAT_SIZE_COMPACT": "작게", + "OPTION_CHAT_SIZE_STANDARD": "표준", + "OPTION_CHAT_SIZE_LARGE": "크게", + "OPTION_CHAT_SIZE_CUSTOM": "마지막 사용자 크기", + "LABEL_CHAT_START_MODE": "시작 화면", + "LABEL_CHAT_START_MODE_TOOLTIP": "플로팅 버블 또는 열린 오버레이로 시작할지 선택합니다.", + "OPTION_CHAT_START_BUBBLE": "플로팅 채팅 버블", + "OPTION_CHAT_START_OPEN": "열린 오버레이", "CHAT_TITLE": "방 채팅", "CHAT_LIVE_ONLY": "실시간 전용. 기록 없음.", "CHAT_OPEN": "방 채팅 열기", diff --git a/extension/locales/nl.json b/extension/locales/nl.json index c5f67b7..b1acfd4 100644 --- a/extension/locales/nl.json +++ b/extension/locales/nl.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Pas afspelen, meldingen en audiovoorkeuren aan.", "LABEL_CHAT_ENABLED": "Kamerchat inschakelen", "LABEL_CHAT_ENABLED_TOOLTIP": "Versleutelde kamerchat tonen in het geselecteerde videotabblad.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Pas het uiterlijk en de positie van de versleutelde kamerchat aan.", + "CHAT_SETTINGS_NOTE": "Optioneel en end-to-end versleuteld. Berichten zijn alleen live en worden niet door de relay opgeslagen.", + "LABEL_CHAT_POSITION": "Positie", + "LABEL_CHAT_POSITION_TOOLTIP": "Kies waar de chat aan de geselecteerde speler wordt gekoppeld.", + "LABEL_CHAT_SIZE": "Overlaygrootte", + "LABEL_CHAT_SIZE_TOOLTIP": "Kies de standaardgrootte van de chatoverlay.", + "OPTION_CHAT_SIZE_COMPACT": "Compact", + "OPTION_CHAT_SIZE_STANDARD": "Standaard", + "OPTION_CHAT_SIZE_LARGE": "Groot", + "OPTION_CHAT_SIZE_CUSTOM": "Laatste aangepaste grootte", + "LABEL_CHAT_START_MODE": "Start als", + "LABEL_CHAT_START_MODE_TOOLTIP": "Kies of de chat start als zwevende knop of als geopende overlay.", + "OPTION_CHAT_START_BUBBLE": "Zwevende chatknop", + "OPTION_CHAT_START_OPEN": "Geopende overlay", "CHAT_TITLE": "Kamerchat", "CHAT_LIVE_ONLY": "Alleen live. Geen geschiedenis.", "CHAT_OPEN": "Kamerchat openen", diff --git a/extension/locales/pl.json b/extension/locales/pl.json index e8ab859..4fea8df 100644 --- a/extension/locales/pl.json +++ b/extension/locales/pl.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Dostosuj odtwarzanie, powiadomienia i preferencje audio.", "LABEL_CHAT_ENABLED": "Włącz czat pokoju", "LABEL_CHAT_ENABLED_TOOLTIP": "Pokaż zaszyfrowany czat pokoju na wybranej karcie wideo.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Skonfiguruj wygląd i położenie zaszyfrowanego czatu pokoju.", + "CHAT_SETTINGS_NOTE": "Opcjonalny i szyfrowany od końca do końca. Wiadomości są tylko na żywo i nie są przechowywane przez serwer pośredniczący.", + "LABEL_CHAT_POSITION": "Położenie", + "LABEL_CHAT_POSITION_TOOLTIP": "Wybierz, gdzie czat ma być przypięty do wybranego odtwarzacza.", + "LABEL_CHAT_SIZE": "Rozmiar nakładki", + "LABEL_CHAT_SIZE_TOOLTIP": "Wybierz domyślny rozmiar nakładki czatu.", + "OPTION_CHAT_SIZE_COMPACT": "Kompaktowy", + "OPTION_CHAT_SIZE_STANDARD": "Standardowy", + "OPTION_CHAT_SIZE_LARGE": "Duży", + "OPTION_CHAT_SIZE_CUSTOM": "Ostatni własny rozmiar", + "LABEL_CHAT_START_MODE": "Uruchom jako", + "LABEL_CHAT_START_MODE_TOOLTIP": "Wybierz, czy czat ma startować jako pływający przycisk, czy otwarta nakładka.", + "OPTION_CHAT_START_BUBBLE": "Pływający przycisk", + "OPTION_CHAT_START_OPEN": "Otwarta nakładka", "CHAT_TITLE": "Czat pokoju", "CHAT_LIVE_ONLY": "Tylko na żywo. Bez historii.", "CHAT_OPEN": "Otwórz czat pokoju", diff --git a/extension/locales/pt-BR.json b/extension/locales/pt-BR.json index fe4d327..7861967 100644 --- a/extension/locales/pt-BR.json +++ b/extension/locales/pt-BR.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio.", "LABEL_CHAT_ENABLED": "Ativar chat da sala", "LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar o chat criptografado da sala na guia de vídeo selecionada.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure a aparência e a posição do chat criptografado da sala.", + "CHAT_SETTINGS_NOTE": "Opcional e criptografado de ponta a ponta. As mensagens são apenas ao vivo e não são armazenadas pelo relay.", + "LABEL_CHAT_POSITION": "Posição", + "LABEL_CHAT_POSITION_TOOLTIP": "Escolha onde o chat fica fixado no player selecionado.", + "LABEL_CHAT_SIZE": "Tamanho da sobreposição", + "LABEL_CHAT_SIZE_TOOLTIP": "Escolha o tamanho padrão da sobreposição do chat.", + "OPTION_CHAT_SIZE_COMPACT": "Compacto", + "OPTION_CHAT_SIZE_STANDARD": "Padrão", + "OPTION_CHAT_SIZE_LARGE": "Grande", + "OPTION_CHAT_SIZE_CUSTOM": "Último tamanho personalizado", + "LABEL_CHAT_START_MODE": "Iniciar como", + "LABEL_CHAT_START_MODE_TOOLTIP": "Escolha se o chat começa como uma bolha flutuante ou uma sobreposição aberta.", + "OPTION_CHAT_START_BUBBLE": "Bolha flutuante", + "OPTION_CHAT_START_OPEN": "Sobreposição aberta", "CHAT_TITLE": "Chat da sala", "CHAT_LIVE_ONLY": "Somente ao vivo. Sem histórico.", "CHAT_OPEN": "Abrir chat da sala", diff --git a/extension/locales/pt.json b/extension/locales/pt.json index 494f363..539eb43 100644 --- a/extension/locales/pt.json +++ b/extension/locales/pt.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio.", "LABEL_CHAT_ENABLED": "Ativar chat da sala", "LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar o chat encriptado da sala no separador de vídeo selecionado.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure o aspeto e a posição do chat encriptado da sala.", + "CHAT_SETTINGS_NOTE": "Opcional e encriptado ponto a ponto. As mensagens são apenas em direto e não são guardadas pelo relay.", + "LABEL_CHAT_POSITION": "Posição", + "LABEL_CHAT_POSITION_TOOLTIP": "Escolha onde o chat fica fixado no leitor selecionado.", + "LABEL_CHAT_SIZE": "Tamanho da sobreposição", + "LABEL_CHAT_SIZE_TOOLTIP": "Escolha o tamanho predefinido da sobreposição do chat.", + "OPTION_CHAT_SIZE_COMPACT": "Compacto", + "OPTION_CHAT_SIZE_STANDARD": "Padrão", + "OPTION_CHAT_SIZE_LARGE": "Grande", + "OPTION_CHAT_SIZE_CUSTOM": "Último tamanho personalizado", + "LABEL_CHAT_START_MODE": "Iniciar como", + "LABEL_CHAT_START_MODE_TOOLTIP": "Escolha se o chat começa como uma bolha flutuante ou uma sobreposição aberta.", + "OPTION_CHAT_START_BUBBLE": "Bolha flutuante", + "OPTION_CHAT_START_OPEN": "Sobreposição aberta", "CHAT_TITLE": "Chat da sala", "CHAT_LIVE_ONLY": "Apenas em direto. Sem histórico.", "CHAT_OPEN": "Abrir chat da sala", diff --git a/extension/locales/ru.json b/extension/locales/ru.json index 373b433..d51f982 100644 --- a/extension/locales/ru.json +++ b/extension/locales/ru.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Настройте воспроизведение, уведомления и параметры звука.", "LABEL_CHAT_ENABLED": "Включить чат комнаты", "LABEL_CHAT_ENABLED_TOOLTIP": "Показывать зашифрованный чат комнаты на выбранной вкладке с видео.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Настройте внешний вид и расположение зашифрованного чата комнаты.", + "CHAT_SETTINGS_NOTE": "Необязательный чат со сквозным шифрованием. Сообщения доступны только в реальном времени и не сохраняются ретранслятором.", + "LABEL_CHAT_POSITION": "Положение", + "LABEL_CHAT_POSITION_TOOLTIP": "Выберите, где чат будет закреплён на выбранном проигрывателе.", + "LABEL_CHAT_SIZE": "Размер окна", + "LABEL_CHAT_SIZE_TOOLTIP": "Выберите стандартный размер окна чата.", + "OPTION_CHAT_SIZE_COMPACT": "Компактный", + "OPTION_CHAT_SIZE_STANDARD": "Стандартный", + "OPTION_CHAT_SIZE_LARGE": "Большой", + "OPTION_CHAT_SIZE_CUSTOM": "Последний пользовательский размер", + "LABEL_CHAT_START_MODE": "Начальный вид", + "LABEL_CHAT_START_MODE_TOOLTIP": "Выберите, запускать чат как плавающую кнопку или открытое окно.", + "OPTION_CHAT_START_BUBBLE": "Плавающая кнопка", + "OPTION_CHAT_START_OPEN": "Открытое окно", "CHAT_TITLE": "Чат комнаты", "CHAT_LIVE_ONLY": "Только в реальном времени. Без истории.", "CHAT_OPEN": "Открыть чат комнаты", diff --git a/extension/locales/tr.json b/extension/locales/tr.json index d0fc2ce..8f49b35 100644 --- a/extension/locales/tr.json +++ b/extension/locales/tr.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Oynatma, bildirim ve ses tercihlerini özelleştirin.", "LABEL_CHAT_ENABLED": "Oda sohbetini etkinleştir", "LABEL_CHAT_ENABLED_TOOLTIP": "Şifreli oda sohbetini seçili video sekmesinde göster.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Şifreli oda sohbetinin görünümünü ve konumunu yapılandırın.", + "CHAT_SETTINGS_NOTE": "İsteğe bağlı ve uçtan uca şifrelidir. Mesajlar yalnızca canlıdır ve aktarıcı tarafından saklanmaz.", + "LABEL_CHAT_POSITION": "Konum", + "LABEL_CHAT_POSITION_TOOLTIP": "Sohbetin seçili oynatıcıda nereye sabitleneceğini seçin.", + "LABEL_CHAT_SIZE": "Kaplama boyutu", + "LABEL_CHAT_SIZE_TOOLTIP": "Sohbet kaplamasının varsayılan boyutunu seçin.", + "OPTION_CHAT_SIZE_COMPACT": "Kompakt", + "OPTION_CHAT_SIZE_STANDARD": "Standart", + "OPTION_CHAT_SIZE_LARGE": "Büyük", + "OPTION_CHAT_SIZE_CUSTOM": "Son özel boyut", + "LABEL_CHAT_START_MODE": "Başlangıç görünümü", + "LABEL_CHAT_START_MODE_TOOLTIP": "Sohbetin kayan balon veya açık kaplama olarak başlamasını seçin.", + "OPTION_CHAT_START_BUBBLE": "Kayan sohbet balonu", + "OPTION_CHAT_START_OPEN": "Açık kaplama", "CHAT_TITLE": "Oda Sohbeti", "CHAT_LIVE_ONLY": "Yalnızca canlı. Geçmiş yok.", "CHAT_OPEN": "Oda sohbetini aç", diff --git a/extension/locales/uk.json b/extension/locales/uk.json index 84850f5..af15714 100644 --- a/extension/locales/uk.json +++ b/extension/locales/uk.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Налаштуйте відтворення, сповіщення та параметри звуку.", "LABEL_CHAT_ENABLED": "Увімкнути чат кімнати", "LABEL_CHAT_ENABLED_TOOLTIP": "Показувати зашифрований чат кімнати на вибраній вкладці з відео.", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Налаштуйте вигляд і розташування зашифрованого чату кімнати.", + "CHAT_SETTINGS_NOTE": "Необов'язковий чат із наскрізним шифруванням. Повідомлення доступні лише наживо й не зберігаються ретранслятором.", + "LABEL_CHAT_POSITION": "Розташування", + "LABEL_CHAT_POSITION_TOOLTIP": "Виберіть, де чат буде закріплено на вибраному програвачі.", + "LABEL_CHAT_SIZE": "Розмір вікна", + "LABEL_CHAT_SIZE_TOOLTIP": "Виберіть стандартний розмір вікна чату.", + "OPTION_CHAT_SIZE_COMPACT": "Компактний", + "OPTION_CHAT_SIZE_STANDARD": "Стандартний", + "OPTION_CHAT_SIZE_LARGE": "Великий", + "OPTION_CHAT_SIZE_CUSTOM": "Останній власний розмір", + "LABEL_CHAT_START_MODE": "Початковий вигляд", + "LABEL_CHAT_START_MODE_TOOLTIP": "Виберіть, запускати чат як плаваючу кнопку чи відкрите вікно.", + "OPTION_CHAT_START_BUBBLE": "Плаваюча кнопка", + "OPTION_CHAT_START_OPEN": "Відкрите вікно", "CHAT_TITLE": "Чат кімнати", "CHAT_LIVE_ONLY": "Лише наживо. Без історії.", "CHAT_OPEN": "Відкрити чат кімнати", diff --git a/extension/locales/zh.json b/extension/locales/zh.json index 9aa97ae..92a6d70 100644 --- a/extension/locales/zh.json +++ b/extension/locales/zh.json @@ -248,6 +248,20 @@ "LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "自定义播放、通知和音频首选项。", "LABEL_CHAT_ENABLED": "启用房间聊天", "LABEL_CHAT_ENABLED_TOOLTIP": "在选定的视频标签页中显示加密的房间聊天。", + "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "配置加密房间聊天的外观和位置。", + "CHAT_SETTINGS_NOTE": "可选并采用端到端加密。消息仅实时显示,且不会由中继服务器存储。", + "LABEL_CHAT_POSITION": "位置", + "LABEL_CHAT_POSITION_TOOLTIP": "选择聊天在当前播放器上的固定位置。", + "LABEL_CHAT_SIZE": "浮层大小", + "LABEL_CHAT_SIZE_TOOLTIP": "选择聊天浮层的默认大小。", + "OPTION_CHAT_SIZE_COMPACT": "紧凑", + "OPTION_CHAT_SIZE_STANDARD": "标准", + "OPTION_CHAT_SIZE_LARGE": "大", + "OPTION_CHAT_SIZE_CUSTOM": "上次自定义大小", + "LABEL_CHAT_START_MODE": "启动方式", + "LABEL_CHAT_START_MODE_TOOLTIP": "选择以悬浮气泡或打开的浮层启动聊天。", + "OPTION_CHAT_START_BUBBLE": "悬浮聊天气泡", + "OPTION_CHAT_START_OPEN": "打开浮层", "CHAT_TITLE": "房间聊天", "CHAT_LIVE_ONLY": "仅实时显示,无历史记录。", "CHAT_OPEN": "打开房间聊天", diff --git a/extension/popup.html b/extension/popup.html index de0a429..0ed6cc2 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -1215,6 +1215,12 @@ line-height: 1.45; text-align: left; } + [data-chat-setting] { + transition: opacity 0.2s; + } + [data-chat-setting].is-disabled { + opacity: 0.5; + } .settings-issue-link { margin: 8px 0 2px; padding-top: 9px; @@ -1615,6 +1621,50 @@ +
+ Room Chat +
+

Optional and end-to-end encrypted. Messages are live only and are not stored by the relay.

+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+
+
Playback & Sync
@@ -1625,13 +1675,6 @@
-
- - -
'.length); +} + +const chatSection = detailsSection('data-i18n="CHAT_TITLE"'); +const syncSection = detailsSection('data-i18n="LABEL_SETTINGS_GROUP_SYNC"'); + +for (const id of ['chatEnabled', 'chatPosition', 'chatSize', 'chatStartMode']) { + assert.match(chatSection, new RegExp(`id="${id}"`), `${id} belongs in the dedicated chat settings section`); +} +assert.doesNotMatch(syncSection, /id="chatEnabled"/, 'chat enablement must not remain under Playback & Sync'); + +for (const value of ['right', 'left', 'detached']) { + assert.match(chatSection, new RegExp(`