Move Windows camera help into overlay

The privacy instructions no longer bloat the launcher card.
A "Help" button in the navbar opens them in a native dialog
(Esc, backdrop and button close it). The button only shows
for Windows users, reusing the existing OS detection.

Translations may contain <kbd>, <b> and <i> markup now. The
renderer builds nodes from a tag whitelist instead of using
innerHTML, so smuggled tags from i18n.json cannot execute.

All help texts live under meetingPrivacySettings.windows;
sibling keys for other operating systems can follow.
This commit is contained in:
stefan.tollkuehn committed 2026-11-05 17:24:15 +01:00
1 parent 7961732323
commit da2f6b5c25
4 files changed
+231 -109

No files matched your search

+89 -22
View File
@@ -89,6 +89,46 @@ let translation = {};
* ============================================================
*/
/*
* "<b>", "<i>" and "<kbd>" are the only markup allowed
* in translations (no nesting). Hand-built nodes instead
* of innerHTML: a smuggled tag from i18n.json can never
* execute.
*
* "Settings (<kbd>I</kbd>)" becomes
*
* Settings ("I")[text](kbd)
*/
const MARKUP_PATTERN = /<(b|i|kbd)>(.*?)<\/\1>/g;
function renderTranslation(element, text) {
element.replaceChildren();
let lastIndex = 0;
for (const match of text.matchAll(MARKUP_PATTERN)) {
/*
* The pattern restricts the tag to the whitelist,
* so createElement() cannot create foreign elements.
*/
element.append(
document.createTextNode(
text.slice(lastIndex, match.index)
),
Object.assign(
document.createElement(match[1]),
{ textContent: match[2] }
)
);
lastIndex = match.index + match[0].length;
}
element.append(
document.createTextNode(text.slice(lastIndex))
);
}
function applyTranslations() {
document.documentElement.lang = currentLanguage;
if (translation.pageTitle) {
@@ -102,7 +142,7 @@ function applyTranslations() {
const value = getTranslation(key);
if (value !== null) {
element.textContent = value;
renderTranslation(element, value);
}
});
@@ -399,6 +439,22 @@ function applyLegalNoticeLink() {
legalNoticeLink.classList.remove("hidden");
}
/* ============================================================
* WINDOWS HELP BUTTON
* ============================================================ */
/*
* The camera/microphone help (opens windowsHelpOverlay)
* only concerns Windows users.
*/
function applyWindowsHelpButton() {
if (detectOperatingSystem() !== OS_NAMES.Windows) {
return;
}
windowsHelpButton.classList.remove("hidden");
}
/* ============================================================
* DOM elements
* ============================================================
@@ -426,7 +482,6 @@ const iosLink = document.getElementById("iosLink");
const androidLink = document.getElementById("androidLink");
const createNewMeetingButton = document.getElementById("createNewMeetingButton");
const createNewFromLauncherButton = document.getElementById("createNewFromLauncherButton");
const privacyInfo = document.getElementById("privacyInfo");
/* Meeting Creator DOM elements */
const meetingNameInput = document.getElementById("meetingName");
@@ -451,6 +506,7 @@ const navbar = document.querySelector(".navbar");
const navToggle = document.getElementById("navToggle");
const navActions = document.getElementById("navActions");
const legalNoticeLink = document.getElementById("legalNoticeLink");
const windowsHelpButton = document.getElementById("windowsHelpButton");
/* Language menu DOM elements */
const langMenu = document.getElementById("langMenu");
@@ -480,6 +536,10 @@ const themeButton = document.getElementById("themeButton");
const themeIconSun = document.getElementById("themeIconSun");
const themeIconMoon = document.getElementById("themeIconMoon");
/* Windows help overlay DOM elements */
const windowsHelpOverlay = document.getElementById("windowsHelpOverlay");
const windowsHelpClose = document.getElementById("windowsHelpClose");
/* ============================================================
* GET ROOM FROM QUERY-PARAMETER
* ============================================================
@@ -1401,26 +1461,9 @@ function cancelCreateMeeting() {
*/
/* ============================================================
* SHOW LAUNCHER
* INITIALIZATION
* ============================================================ */
/*
* Camera/microphone hints (meetingPrivacySettings in
* i18n.json) only matter on Windows – and only with
* loaded translations, otherwise the box would be empty.
*/
function showPrivacyInfo() {
if (detectOperatingSystem() !== OS_NAMES.Windows) {
return;
}
if (getTranslation("meetingPrivacySettings.title") === null) {
return;
}
privacyInfo.classList.remove("hidden");
}
function initialize() {
const room = getRoom();
@@ -1482,8 +1525,6 @@ function initialize() {
*/
launcher.classList.remove("hidden");
showPrivacyInfo();
/*
* Hide the "Create New Meeting" button when a valid room is specified
*/
@@ -1604,6 +1645,31 @@ navToggle.addEventListener(
toggleNavMenu
);
/* Windows help events */
windowsHelpButton.addEventListener(
"click",
() => {
windowsHelpOverlay.showModal();
}
);
windowsHelpClose.addEventListener(
"click",
() => {
windowsHelpOverlay.close();
}
);
/* Clicks on the backdrop close the overlay */
windowsHelpOverlay.addEventListener(
"click",
(event) => {
if (event.target === windowsHelpOverlay) {
windowsHelpOverlay.close();
}
}
);
/*
* Follow the system if no choice is saved.
*/
@@ -1664,6 +1730,7 @@ async function start() {
applyNextcloudButton();
applyAdhocButton();
applyLegalNoticeLink();
applyWindowsHelpButton();
await loadTranslations();