Compare commits

...
11 Commits
Author SHA1 Message Date
stefan.tollkuehn ebbfa990fb Relabel creator button as ICS download
The form button generates the ICS file, so name it after
the result. Move the Nextcloud button ahead of it, matching
the visual weight of the primary actions.
2026-11-05 17:52:27 +01:00
stefan.tollkuehn cfb48aa3c9 Group help texts under a help key
meetingPrivacySettings nests one level deeper under help.
The umbrella makes room for further help topics and allows
contextualizing which help a visible view offers. All
data-i18n references follow; no logic changes.
2026-11-05 17:37:24 +01:00
stefan.tollkuehn 4df51ca508 Simplify browser permissions wording
Name only the website-permissions icon when describing
where to change camera/mic decisions. The combined
lock-and-permissions phrasing was clunky in German and
duplicated what the icon already shows. Adjust all
translations accordingly.
2026-11-05 17:33:17 +01:00
stefan.tollkuehn c06127ef2b Document i18n markup and help overlay
Add the missing legalNotice parameter to the config table.
Describe how translations resolve keys, which markup the
renderer accepts, and how the OS-keyed help dialog works.
2026-11-05 17:29:31 +01:00
stefan.tollkuehn da2f6b5c25 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.
2026-11-05 17:24:15 +01:00
stefan.tollkuehn 7961732323 Show Windows privacy hints in launcher
The launcher sentence around the room name was replaced
by a standalone instruction ("meetingText"); the room name
now sits on its own line below it.

Windows users additionally get camera/microphone
permission instructions in the launcher view. The block
reuses the existing i18n/OS detection and only appears
when translations loaded, so a failed i18n.json fetch
cannot render an empty box.
2026-11-05 16:36:43 +01:00
stefan.tollkuehn f746508d90 fix title of AGENTS.md 2026-11-05 15:59:53 +01:00
stefan.tollkuehn 217a076120 Make legal notice link configurable
The legal notice link in the navbar was hardcoded to the
bnvb website. Deployments without that legal notice showed
a dead link to a foreign site.

Move the URL into assets/config.json (legalNotice), like
the Nextcloud and adhoc buttons. The link stays hidden
unless the entry is configured.
2026-11-03 11:09:22 +01:00
stefan.tollkuehn 49a3d90dc5 Scope localStorage keys per app directory
localStorage is scoped per origin, not per path. Multiple
jitsi-launch instances on one origin (e.g. meet.bnvb.eu/i
and /p) therefore shared one language and one theme
setting, and changing it in one instance changed it in
all others.

Prefix the language and theme keys with the app directory
derived from the page path. Each instance keeps its own
state; a root deployment gets the "/" prefix and behaves
as before. Previously saved choices reset once because
the key names change.
2026-11-03 11:06:50 +01:00
stefan.tollkuehn 0e4ba86c38 Make favicon paths configurable
The favicons were hardcoded in launch.html, so
rebranding the page required editing the template.

The paths now come from the favicons block in
assets/config.json. Missing keys keep the defaults
from launch.html.
2026-11-02 15:48:19 +01:00
stefan.tollkuehn 9184f60461 cleanup and htaccess fix 2026-11-02 15:09:00 +01:00
8 changed files with 376 additions and 37 deletions

No files matched your search

+1 -5
View File
@@ -2,9 +2,5 @@ DirectoryIndex launch.html
# Hide repository meta files behind 404
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(README\.md|LICENSE|AGENTS\.md)$ - [NC,R=404,L]
# security.txt: redirect to central security contact
RewriteRule ^\.well-known/security\.txt$ https://www.bnvb-ingenieure.de/.well-known/security.txt [NC,R=301,L]
RewriteRule ^(README\.md|LICENSE|AGENTS\.md|.gitignore|.htaccess)$ - [NC,R=404,L]
</IfModule>
+1 -1
View File
@@ -1,4 +1,4 @@
# AGENT.MD
# AGENTS.MD
- When writing something intended for human consumption, (comment, commit message, reply to prompt) use as few words as possible. Pick every word meticulously to reduce the volume to a strict minimum. Be down to the point. Less is more.
+24
View File
@@ -14,12 +14,36 @@ Rename `assets/config.json.sample` to `assets/config.json`.
| `jitsiLauncher` | Domain of this Jitsi launcher. Used for the meeting URLs created by the meeting creator (Nextcloud, adhoc link). |
| `jitsiHost` | Jitsi domain. Used for the browser fallback URL, the desktop protocol URL and the UID of generated ICS files. |
| `nextcloudBase` | Nextcloud instance for calendar event creation. The "Create in Nextcloud Calendar" button is only shown if this is set. |
| `legalNotice` | URL of the "Legal Notice" link in the navbar. The link stays hidden if unset. |
| `allowAdhocMeetings` | Whether adhoc meetings are allowed. The "Create Adhoc Link" button is only shown if this is `true`. |
| `launchTimeout` | Timeout in ms before the browser fallback is shown after trying to start the jitsi-meet-electron client. |
| `backgroundImage` | Path to the page background image. Empty: no background. |
| `logo` | Path to the logo shown in the logo bar. Empty: logo bar stays hidden. |
| `favicons` | Paths to the page icons: `ico` (fallback icon), `svg` (icon), `appleTouch` (Apple touch icon). The values from `launch.html` are kept if a key is missing. |
| `downloads` | Download URLs for the jitsi-meet clients, shown in the fallback view per detected OS: `windows`, `macos`, `linux`, `ios`, `android`. |
## Translations
All texts live in `assets/i18n.json`, one object per language
(`en`, `de`, `fr`, `zh`). Elements carry the key in `data-i18n`
(dots descend into nested objects), inputs in `data-i18n-placeholder`.
Translations may contain `<kbd>`, `<b>` and `<i>` markup (no
nesting). The renderer builds only these whitelisted elements;
other tags render literally and never execute.
## Help overlay
The "Help" button in the navbar opens a dialog with
camera/microphone hints. It only shows on the detected OS.
All help texts live under `help` in `assets/i18n.json`,
grouped by topic; the overlay uses
`help.meetingPrivacySettings`, keyed by OS (`windows`;
`macos`, `linux`, `ios` and `android` can follow). Further
topics can extend `help` and be contextualized per visible
view. The dialog width is defined in `dialog.privacy`
(`assets/css/launch.css`).
## Third-party assets
- `assets/lib/ua-parser.min.js` — [ua-parser-js](https://github.com/faisalman/ua-parser-js) v2.0.10, © Faisal Salman, licensed under AGPL-3.0.
+6
View File
@@ -2,10 +2,16 @@
"jitsiLauncher": "server.domain.tld/subdir",
"jitsiHost": "jit.si",
"nextcloudBase": "https://cloud.domain.tld",
"legalNotice": "https://www.domain.tld/legal-notice",
"allowAdhocMeetings": true,
"launchTimeout": 2000,
"backgroundImage": "path/to/backgroundimage",
"logo": "path/to/logo",
"favicons": {
"ico": "path/to/favicon.ico",
"svg": "path/to/favicon.svg",
"appleTouch": "path/to/apple-touch-icon.png"
},
"downloads": {
"windows": "https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe",
"macos": "https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg",
+42 -1
View File
@@ -206,11 +206,46 @@ button, .button {
background: #f5f5f5;
}
/* Windows help overlay – long text needs left
* alignment, the page is centered */
dialog.privacy {
width: min(1000px, calc(100% - 32px));
max-height: calc(100vh - 48px);
border: 0;
border-radius: 14px;
background: #f5f5f5;
padding: 20px;
text-align: left;
overflow-y: auto;
}
dialog.privacy::backdrop {
background: rgba(0, 0, 0, 0.5);
}
.help-actions {
margin-top: 24px;
text-align: center;
}
.room {
display: block;
margin-top: 20px;
font-family: monospace;
word-break: break-all;
}
/* Keyboard keys, marked with <kbd> in i18n.json */
kbd {
padding: 1px 6px;
border: 1px solid #ccc;
border-bottom-width: 3px;
border-radius: 4px;
background: #f0f0f0;
font-family: monospace;
font-size: 0.9em;
}
.error {
padding: 16px;
border-radius: 8px;
@@ -459,13 +494,19 @@ button, .button {
color: #bbb;
}
:root.dark kbd {
border-color: #555;
background: #404040;
}
:root.dark .secondary {
background: #444;
color: #eee;
}
:root.dark .status,
:root.dark .download {
:root.dark .download,
:root.dark .privacy {
background: #303030;
}
+96 -12
View File
@@ -5,8 +5,7 @@
"source": "Source",
"launcher": {
"title": "Jitsi Meeting",
"meetingPrefix": "The meeting",
"meetingSuffix": "will be joined.",
"meetingText": "Please click the button, to join the meeting:",
"openButton": "Join meeting"
},
"starting": {
@@ -70,7 +69,7 @@
"meetingStartLabel": "Start Date & Time",
"meetingEndLabel": "End Date & Time",
"meetingOrganizerLabel": "Organizer Email",
"createButton": "Create Meeting",
"createButton": "Download ICS File",
"cancelButton": "Cancel",
"successMessage": "Meeting created! Download your calendar file and open it to import the meeting into your personal calendar.",
"downloadButton": "Download ICS File",
@@ -87,6 +86,28 @@
"organizerRequired": "Please enter an organizer email",
"organizerInvalid": "Please enter a valid organizer email address",
"nextcloudButton": "Create in Nextcloud Calendar"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Camera and microphone in Windows: How to control your privacy",
"intro": "Your camera and microphone are sensitive devices. Windows therefore ensures that they can only be used with your permission. A few steps give you full control, especially when you use video calls or voice messages directly in your web browser.",
"step1Title": "Step 1: Open the Windows settings",
"step1Description": "Open the <b>Windows Settings</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). There, select the area <b>Privacy & security</b> and then <b>Camera</b> or <b>Microphone</b>.",
"step2Title": "Step 2: Regulate overall access",
"step2Description": "At the top of the page you will find the central switch for camera and microphone access. If it is disabled, no application and no website can access the device. If it is enabled, you decide on the same page whether applications are allowed access in principle.",
"step3Title": "Step 3: Check website permissions in the browser",
"step3Description": "Even if Windows allows access, your browser asks each website individually whether the camera or microphone may be used. This is the most important protection in everyday use: only when you agree does the access actually become possible.",
"browserPermissions": "You can change or revoke these decisions at any time. To do so, click the website-permissions icon in your browser's address bar and select the option for site settings. There you can allow the camera and microphone for the respective site, ask each time, or block them permanently.",
"goodToKnowTitle": "Good to know",
"goodToKnow1": "You can revoke permissions for individual websites at any time without changing Windows settings.",
"goodToKnow2": "An active microphone or camera is indicated in Windows by a small camera or microphone icon in the taskbar. This way you immediately recognize when a site is accessing a device.",
"goodToKnow3": "If a website does not get access despite working hardware, the cause is often not the device itself, but one of the two permission levels, in Windows or in the browser.",
"adminSupport": "If you need support finding the right settings, please ask <b>your administrator</b>.",
"navButton": "Help",
"closeButton": "Close"
}
}
}
},
"de": {
@@ -95,8 +116,7 @@
"source": "Quellcode",
"launcher": {
"title": "Jitsi Meeting",
"meetingPrefix": "Das Meeting",
"meetingSuffix": "wird geöffnet.",
"meetingText": "Bitte klicken Sie auf die Schaltfläche, um an dem Meeting teilzunehmen:",
"openButton": "An Meeting teilnehmen"
},
"starting": {
@@ -160,7 +180,7 @@
"meetingStartLabel": "Start Datum & Uhrzeit",
"meetingEndLabel": "Ende Datum & Uhrzeit",
"meetingOrganizerLabel": "Organisator E-Mail",
"createButton": "Meeting erstellen",
"createButton": "ICS-Datei herunterladen",
"cancelButton": "Abbrechen",
"successMessage": "Meeting erstellt! Laden Sie Ihre Kalenderdatei herunter und öffnen Sie diese, um das Meeting in Ihren persönlichen Kalender zu importieren.",
"downloadButton": "ICS-Datei herunterladen",
@@ -177,6 +197,28 @@
"organizerRequired": "Bitte geben Sie eine Organisator-E-Mail ein",
"organizerInvalid": "Bitte geben Sie eine gültige Organisator-E-Mail-Adresse ein",
"nextcloudButton": "Im Nextcloud-Kalender erstellen"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Kamera und Mikrofon in Windows: So kontrollieren Sie Ihre Privatsphäre",
"intro": "Ihre Kamera und Ihr Mikrofon sind sensible Geräte. Windows sorgt dafür, dass sie nur mit Ihrer Erlaubnis genutzt werden können. Mit wenigen Schritten behalten Sie die volle Kontrolle, insbesondere wenn Sie Videotelefonie oder Sprachnachrichten direkt im Webbrowser nutzen.",
"step1Title": "Schritt 1: Windows-Einstellungen öffnen",
"step1Description": "Öffnen Sie die <b>Windows-Einstellungen</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). Wählen Sie dort den Bereich <b>Datenschutz und Sicherheit</b> und anschließend <b>Kamera</b> oder <b>Mikrofon</b>.",
"step2Title": "Schritt 2: Gesamtzugriff regeln",
"step2Description": "Oben auf der Seite finden Sie den zentralen Schalter für den Kamera- bzw. Mikrofonzugriff. Ist er deaktiviert, kann keine Anwendung und keine Website auf das Gerät zugreifen. Ist er aktiviert, entscheiden Sie auf der gleichen Seite, ob Anwendungen den Zugriff grundsätzlich erhalten dürfen.",
"step3Title": "Schritt 3: Website-Berechtigungen im Browser prüfen",
"step3Description": "Auch wenn Windows den Zugriff erlaubt, fragt Ihr Browser bei jeder Website einzeln nach, ob Kamera oder Mikrofon genutzt werden dürfen. Das ist der wichtigste Schutz im Alltag: Erst wenn Sie zustimmen, wird der Zugriff tatsächlich möglich.",
"browserPermissions": "Sie können diese Entscheidungen jederzeit ändern oder widerrufen. Klicken Sie dazu in der Adressleiste Ihres Browsers auf das Symbol für die Website-Berechtigungen und wählen Sie die Option für Website-Einstellungen. Dort lassen sich Kamera und Mikrofon für die jeweilige Seite gezielt erlauben, einmalig anfragen oder dauerhaft blockieren.",
"goodToKnowTitle": "Gut zu wissen",
"goodToKnow1": "Sie können Berechtigungen für einzelne Websites jederzeit zurückziehen, ohne Windows-Einstellungen zu ändern.",
"goodToKnow2": "Ein aktives Mikrofon oder eine aktive Kamera wird unter Windows durch ein kleines Kamera- oder Mikrofonsymbol in der Taskleiste angezeigt. So erkennen Sie sofort, wenn eine Seite auf ein Gerät zugreift.",
"goodToKnow3": "Wenn eine Website trotz funktionierender Hardware keinen Zugriff erhält, liegt die Ursache häufig nicht am Gerät selbst, sondern an einer der beiden Berechtigungsebenen, in Windows oder im Browser.",
"adminSupport": "Sollten Sie Unterstützung dabei benötigen, die passenden Einstellungen zu finden, fragen Sie bitte <b>Ihren Administrator</b>.",
"navButton": "Hilfe",
"closeButton": "Schließen"
}
}
}
},
"fr": {
@@ -185,8 +227,7 @@
"source": "Code source",
"launcher": {
"title": "Réunion Jitsi",
"meetingPrefix": "La réunion",
"meetingSuffix": "va être ouverte.",
"meetingText": "Veuillez cliquer sur le bouton pour rejoindre la réunion :",
"openButton": "Rejoindre la réunion"
},
"starting": {
@@ -250,7 +291,7 @@
"meetingStartLabel": "Date et heure de début",
"meetingEndLabel": "Date et heure de fin",
"meetingOrganizerLabel": "Email de l'organisateur",
"createButton": "Créer la réunion",
"createButton": "Télécharger le fichier ICS",
"cancelButton": "Annuler",
"successMessage": "Réunion créée ! Téléchargez votre fichier calendrier et ouvrez-le pour importer la réunion dans votre calendrier personnel.",
"downloadButton": "Télécharger le fichier ICS",
@@ -267,6 +308,28 @@
"organizerRequired": "Veuillez entrer un email d'organisateur",
"organizerInvalid": "Veuillez entrer une adresse email d'organisateur valide",
"nextcloudButton": "Créer dans le calendrier Nextcloud"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Caméra et microphone sous Windows : comment contrôler votre vie privée",
"intro": "Votre caméra et votre microphone sont des appareils sensibles. Windows veille donc à ce qu'ils ne puissent être utilisés qu'avec votre autorisation. En quelques étapes, vous gardez le contrôle total, surtout lorsque vous utilisez la visioconférence ou les messages vocaux directement dans votre navigateur.",
"step1Title": "Étape 1 : Ouvrir les paramètres Windows",
"step1Description": "Ouvrez les <b>paramètres Windows</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). Sélectionnez ensuite la section <b>Confidentialité et sécurité</b>, puis <b>Caméra</b> ou <b>Micro</b>.",
"step2Title": "Étape 2 : Régler l'accès global",
"step2Description": "En haut de la page se trouve l'interrupteur central de l'accès à la caméra ou au microphone. S'il est désactivé, aucune application ni aucun site web ne peut accéder à l'appareil. S'il est activé, vous décidez sur la même page si les applications peuvent y accéder en principe.",
"step3Title": "Étape 3 : Vérifier les autorisations du site dans le navigateur",
"step3Description": "Même si Windows autorise l'accès, votre navigateur demande site par site si la caméra ou le microphone peuvent être utilisés. C'est la protection la plus importante au quotidien : seul votre consentement rend l'accès réellement possible.",
"browserPermissions": "Vous pouvez modifier ou révoquer ces décisions à tout moment. Pour cela, cliquez dans la barre d'adresse de votre navigateur sur l'icône des autorisations du site, puis choisissez l'option des paramètres du site. Vous y pouvez autoriser, demander à chaque fois ou bloquer définitivement la caméra et le microphone pour le site concerné.",
"goodToKnowTitle": "Bon à savoir",
"goodToKnow1": "Vous pouvez retirer à tout moment les autorisations de sites individuels sans modifier les paramètres Windows.",
"goodToKnow2": "Un microphone ou une caméra active est signalé sous Windows par une petite icône de caméra ou de microphone dans la barre des tâches. Vous reconnaissez ainsi immédiatement quand un site accède à un appareil.",
"goodToKnow3": "Si un site n'obtient pas l'accès malgré un matériel fonctionnel, la cause n'est souvent pas l'appareil lui-même, mais l'un des deux niveaux d'autorisation, sous Windows ou dans le navigateur.",
"adminSupport": "Si vous avez besoin d'aide pour trouver les bons réglages, veuillez demander à <b>votre administrateur</b>.",
"navButton": "Aide",
"closeButton": "Fermer"
}
}
}
},
"zh": {
@@ -275,8 +338,7 @@
"source": "源代码",
"launcher": {
"title": "Jitsi 会议",
"meetingPrefix": "会议",
"meetingSuffix": "即将打开。",
"meetingText": "请点击按钮加入会议:",
"openButton": "加入会议"
},
"starting": {
@@ -340,7 +402,7 @@
"meetingStartLabel": "开始日期和时间",
"meetingEndLabel": "结束日期和时间",
"meetingOrganizerLabel": "组织者邮箱",
"createButton": "创建会议",
"createButton": "下载 ICS 文件",
"cancelButton": "取消",
"successMessage": "会议已创建!请下载您的日历文件并打开它,将会议导入您的个人日历。",
"downloadButton": "下载 ICS 文件",
@@ -357,6 +419,28 @@
"organizerRequired": "请输入组织者邮箱",
"organizerInvalid": "请输入有效的组织者邮箱地址",
"nextcloudButton": "在 Nextcloud 日历中创建"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Windows 中的摄像头和麦克风:如何掌控您的隐私",
"intro": "摄像头和麦克风是敏感设备。因此,Windows 确保只有获得您的许可才能使用它们。只需几个步骤,您就能完全掌控,尤其是在浏览器中直接使用视频通话或语音消息时。",
"step1Title": "步骤 1:打开 Windows 设置",
"step1Description": "打开 <b>Windows 设置</b>(<kbd>⊞ Windows</kbd> + <kbd>I</kbd>)。选择<b>隐私和安全性</b>,然后选择<b>摄像头</b>或<b>麦克风</b>。",
"step2Title": "步骤 2:管理总体访问权限",
"step2Description": "页面顶部有控制摄像头或麦克风访问的总开关。如果关闭,任何应用和网站都无法访问设备。如果开启,您可以在同一页面决定是否允许应用访问。",
"step3Title": "步骤 3:检查浏览器中的网站权限",
"step3Description": "即使 Windows 允许访问,浏览器也会逐个询问每个网站是否可以使用摄像头或麦克风。这是日常使用中最重要的保护:只有您同意后,访问才会真正生效。",
"browserPermissions": "您可以随时更改或撤销这些决定。为此,请点击浏览器地址栏中的网站权限图标,并选择网站设置选项。在那里可以为相应网站专门允许、每次询问或永久阻止摄像头和麦克风。",
"goodToKnowTitle": "实用提示",
"goodToKnow1": "您可以随时撤销单个网站的权限,而无需更改 Windows 设置。",
"goodToKnow2": "在 Windows 中,使用中的麦克风或摄像头会通过任务栏中的小摄像头或麦克风图标显示。这样您可以立即发现某个网站正在访问设备。",
"goodToKnow3": "如果网站在硬件正常的情况下仍无法获得访问权限,原因往往不在设备本身,而在两个权限层级之一:Windows 或浏览器。",
"adminSupport": "如果您需要帮助查找相应的设置,请咨询<b>您的管理员</b>。",
"navButton": "帮助",
"closeButton": "关闭"
}
}
}
}
}
+160 -5
View File
@@ -12,6 +12,20 @@
let CONFIG = {};
const CONFIG_URL = "assets/config.json";
/*
* localStorage is scoped per origin, not per path.
* Prefix keys with the app directory so parallel
* instances (e.g. meet.bnvb.eu/i and /p) stay
* independent.
*
* "/p/launch.html" becomes "/p/"
*/
const STORAGE_PREFIX =
window.location.pathname.replace(/[^/]*$/, "");
const LANGUAGE_KEY = STORAGE_PREFIX + "jitsiLauncherLanguage";
const THEME_KEY = STORAGE_PREFIX + "jitsiLauncherTheme";
/* ============================================================
* Translations
* ============================================================
@@ -34,7 +48,7 @@ function detectLanguage() {
* Saved language choice takes precedence.
*/
try {
const saved = localStorage.getItem("jitsiLauncherLanguage");
const saved = localStorage.getItem(LANGUAGE_KEY);
if (saved && TRANSLATIONS[saved]) {
return saved;
@@ -75,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) {
@@ -88,7 +142,7 @@ function applyTranslations() {
const value = getTranslation(key);
if (value !== null) {
element.textContent = value;
renderTranslation(element, value);
}
});
@@ -179,7 +233,7 @@ function setLanguage(language) {
translation = TRANSLATIONS[language];
try {
localStorage.setItem("jitsiLauncherLanguage", language);
localStorage.setItem(LANGUAGE_KEY, language);
} catch (error) {
/*
* localStorage can be blocked, e.g. in private mode.
@@ -225,8 +279,6 @@ function closeNavMenu() {
* ============================================================
*/
const THEME_KEY = "jitsiLauncherTheme";
function savedTheme() {
try {
const saved = localStorage.getItem(THEME_KEY);
@@ -309,6 +361,33 @@ function applyLogo() {
logoBar.classList.remove("hidden");
}
/* ============================================================
* FAVICONS
* ============================================================
*/
/*
* Overwrite the default icons from launch.html
* (CONFIG.favicons in assets/config.json).
*/
function applyFavicons() {
if (!CONFIG.favicons) {
return;
}
if (CONFIG.favicons.ico) {
faviconIco.href = CONFIG.favicons.ico;
}
if (CONFIG.favicons.svg) {
faviconSvg.href = CONFIG.favicons.svg;
}
if (CONFIG.favicons.appleTouch) {
appleTouchIcon.href = CONFIG.favicons.appleTouch;
}
}
/* ============================================================
* NEXTCLOUD BUTTON
* ============================================================
@@ -343,6 +422,39 @@ function applyAdhocButton() {
createAdhocButton.classList.remove("hidden");
}
/* ============================================================
* LEGAL NOTICE LINK
* ============================================================ */
/*
* Only show if a legal notice URL is configured
* (CONFIG.legalNotice in assets/config.json).
*/
function applyLegalNoticeLink() {
if (!CONFIG.legalNotice || !legalNoticeLink) {
return;
}
legalNoticeLink.href = CONFIG.legalNotice;
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
* ============================================================
@@ -393,6 +505,8 @@ const copyAdhocLinkButton = document.getElementById("copyAdhocLinkButton");
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");
@@ -412,11 +526,20 @@ const invalidRoomName = document.getElementById("invalidRoomName");
const logoBar = document.getElementById("logoBar");
const logoImage = document.getElementById("logoImage");
/* Favicon DOM elements */
const faviconIco = document.getElementById("faviconIco");
const faviconSvg = document.getElementById("faviconSvg");
const appleTouchIcon = document.getElementById("appleTouchIcon");
/* Theme DOM elements */
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
* ============================================================
@@ -1337,6 +1460,10 @@ function cancelCreateMeeting() {
* ============================================================
*/
/* ============================================================
* INITIALIZATION
* ============================================================ */
function initialize() {
const room = getRoom();
@@ -1518,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.
*/
@@ -1574,8 +1726,11 @@ async function start() {
applyBackgroundImage();
applyLogo();
applyFavicons();
applyNextcloudButton();
applyAdhocButton();
applyLegalNoticeLink();
applyWindowsHelpButton();
await loadTranslations();
+46 -13
View File
@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jitsi Meeting</title>
<link href="assets/images/favicon.ico" rel="alternate icon">
<link href="assets/images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="assets/images/favicon.svg" rel="icon" type="image/svg+xml">
<link id="faviconIco" href="assets/images/favicon.ico" rel="alternate icon">
<link id="appleTouchIcon" href="assets/images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link id="faviconSvg" href="assets/images/favicon.svg" rel="icon" type="image/svg+xml">
<link rel="stylesheet" href="assets/css/launch.css">
</head>
@@ -30,9 +30,12 @@
</svg>
</button>
<div id="navActions" class="nav-actions">
<a class="button secondary" href="https://www.bnvb-ingenieure.de/impressum" target="_blank" rel="noopener noreferrer" data-i18n="legal_notice">
<a id="legalNoticeLink" class="button secondary hidden" href="#" target="_blank" rel="noopener noreferrer" data-i18n="legal_notice">
Legal Notice
</a>
<button id="windowsHelpButton" class="button secondary hidden" type="button" data-i18n="help.meetingPrivacySettings.windows.navButton">
Help
</button>
<a class="button secondary" href="https://git.bnvb.eu/stefan.tollkuehn/jitsi-launch" target="_blank" rel="noopener noreferrer" data-i18n="source">
Source
</a>
@@ -68,19 +71,16 @@
<h1 data-i18n="launcher.title">Jitsi Meeting</h1>
<p>
<span data-i18n="launcher.meetingPrefix">
The meeting
<span data-i18n="launcher.meetingText">
Please click the button, to join the meeting:
</span>
<span class="room">
<strong id="roomName"></strong>
</span>
<span data-i18n="launcher.meetingSuffix">
will be opened.
</span>
</p>
<button id="openButton" class="button primary" type="button" data-i18n="launcher.openButton">
Open meeting
Join meeting
</button>
<button id="createNewFromLauncherButton" class="button secondary hidden" type="button" data-i18n="creator.createNewButton">
@@ -285,12 +285,12 @@
</div>
<div class="form-actions">
<button id="createMeetingButton" class="button primary" type="button" data-i18n="creator.createButton">
Create Meeting
</button>
<button id="createNextcloudButton" class="button primary hidden" type="button" data-i18n="creator.nextcloudButton">
Create in Nextcloud Calendar
</button>
<button id="createMeetingButton" class="button primary" type="button" data-i18n="creator.createButton">
Download ICS File
</button>
<button id="createAdhocButton" class="button secondary hidden" type="button" data-i18n="creator.adhocButton">
Create Adhoc Link
</button>
@@ -319,6 +319,39 @@
</div>
</div>
<!-- WINDOWS-HILFE OVERLAY -->
<dialog id="windowsHelpOverlay" class="privacy">
<h2 data-i18n="help.meetingPrivacySettings.windows.title"></h2>
<p data-i18n="help.meetingPrivacySettings.windows.intro"></p>
<h3 data-i18n="help.meetingPrivacySettings.windows.step1Title"></h3>
<p data-i18n="help.meetingPrivacySettings.windows.step1Description"></p>
<h3 data-i18n="help.meetingPrivacySettings.windows.step2Title"></h3>
<p data-i18n="help.meetingPrivacySettings.windows.step2Description"></p>
<h3 data-i18n="help.meetingPrivacySettings.windows.step3Title"></h3>
<p data-i18n="help.meetingPrivacySettings.windows.step3Description"></p>
<p data-i18n="help.meetingPrivacySettings.windows.browserPermissions"></p>
<h3 data-i18n="help.meetingPrivacySettings.windows.goodToKnowTitle"></h3>
<ul>
<li data-i18n="help.meetingPrivacySettings.windows.goodToKnow1"></li>
<li data-i18n="help.meetingPrivacySettings.windows.goodToKnow2"></li>
<li data-i18n="help.meetingPrivacySettings.windows.goodToKnow3"></li>
</ul>
<p data-i18n="help.meetingPrivacySettings.windows.adminSupport"></p>
<div class="help-actions">
<button id="windowsHelpClose" class="button secondary" type="button" data-i18n="help.meetingPrivacySettings.windows.closeButton">
Close
</button>
</div>
</dialog>
<script src="assets/lib/ua-parser.min.js"></script>
<script src="assets/launch.js"></script>
</body>