Compare commits

...
4 Commits
4 changed files with 167 additions and 148 deletions

No files matched your search

+44 -25
View File
@@ -17,11 +17,16 @@ body {
color: #222; color: #222;
} }
/*
* The fixed navbar overlaps the top of the card – keep
* 80px of clearance (all modes, including portrait).
*/
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: min(700px, calc(100% - 32px)); width: min(700px, calc(100% - 32px));
padding-top: 80px;
} }
.navbar { .navbar {
@@ -65,14 +70,14 @@ body {
margin-left: auto; margin-left: auto;
} }
/* Hamburger: nur im Phone-Portrait sichtbar. /* Hamburger: only visible on phone portrait.
* "button.nav-toggle" ist spezifischer als ".button", * "button.nav-toggle" is more specific than ".button",
* sonst überschreibt display: inline-block diese Regel. */ * otherwise display: inline-block overrides this rule. */
button.nav-toggle { button.nav-toggle {
display: none; display: none;
} }
/* Enger als der globale 6px-Margin der Buttons */ /* Tighter than the global 6px button margin */
.navbar .button { .navbar .button {
margin: 0; margin: 0;
} }
@@ -264,6 +269,16 @@ button, .button {
color: #1a7f37; color: #1a7f37;
} }
/*
* scrollIntoView() aligns the target with the viewport
* edge, where the fixed navbar sits. scroll-margin-top
* keeps the target clear below the navbar.
*/
.adhoc-result,
.success-message {
scroll-margin-top: 110px;
}
.adhoc-link { .adhoc-link {
margin-top: 12px; margin-top: 12px;
display: flex; display: flex;
@@ -280,8 +295,8 @@ button, .button {
} }
/* /*
* Verhindert abgeschnittene Inhalte, wenn die Karte * Prevents clipped content when the card is taller
* höher als der Viewport ist ("center" schneidet oben ab). * than the viewport ("center" clips at the top).
*/ */
@supports (align-items: safe center) { @supports (align-items: safe center) {
body { body {
@@ -289,7 +304,7 @@ button, .button {
} }
} }
/* Sehr schmale Telefone: noch kompakter */ /* Very narrow phones: even more compact */
@media (max-width: 400px) { @media (max-width: 400px) {
.navbar { .navbar {
left: 2%; left: 2%;
@@ -313,25 +328,23 @@ button, .button {
} }
/* /*
* Phone: Navbar-Aktionen in beiden Ausrichtungen * Phone: show the navbar actions in both orientations
* hinter dem Hamburger-Button als Dropdown anzeigen. * as a dropdown behind the hamburger button.
* *
* Landscape-Phone: Viewport breiter als 600px – kurze * Landscape phone: viewport wider than 600px – short
* Seite (Höhe) und Touch-Eingabe entscheiden. * side (height) and touch input decide.
*/ */
@media (max-width: 600px), @media (max-width: 600px),
(orientation: landscape) and (max-height: 500px) and (pointer: coarse) { (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
body { body {
/* Navbar ist hier einzeilig */
padding-top: 80px;
/* /*
* Padding statt margin-bottom an der Karte: * Padding instead of margin-bottom on the card:
* Ränder am Scroll-Ende werden vom Scrollbereich * margins at the scroll end are not covered by the
* nicht erfasst, Padding schon. * scroll area, padding is.
*/ */
padding-bottom: 10px; padding-bottom: 10px;
/* 80px Navbar + 10px unten abziehen */ /* Subtract the 10px bottom padding */
min-height: calc(100vh - 90px); min-height: calc(100vh - 10px);
} }
.navbar { .navbar {
@@ -354,12 +367,18 @@ button, .button {
padding: 20px; padding: 20px;
} }
/* Navbar is flatter here than on the desktop */
.adhoc-result,
.success-message {
scroll-margin-top: 80px;
}
button.nav-toggle { button.nav-toggle {
display: inline-flex; display: inline-flex;
} }
/* ".navbar .button" setzt margin: 0 – deshalb /* ".navbar .button" sets margin: 0 – hence target
* höher spezifisch ans Ziel: Button nach rechts */ * more specifically: push the button to the right */
.navbar .nav-toggle { .navbar .nav-toggle {
margin-left: auto; margin-left: auto;
} }
@@ -383,12 +402,12 @@ button, .button {
display: flex; display: flex;
} }
/* Alle Panel-Buttons gleich breit, Text zentriert */ /* All panel buttons equally wide, text centered */
.nav-actions .button { .nav-actions .button {
text-align: center; text-align: center;
} }
/* Sprach-Button liegt im .lang-menu-Wrapper */ /* Language button sits inside the .lang-menu wrapper */
.nav-actions .lang-menu .button { .nav-actions .lang-menu .button {
display: block; display: block;
width: 100%; width: 100%;
@@ -407,9 +426,9 @@ button, .button {
} }
/* /*
* Dark-Theme über die Klasse "dark" am html-Element. * Dark theme via the "dark" class on the html element.
* JS setzt sie anhand der gespeicherten Wahl oder der * JS sets it based on the saved choice or the system
* System-Einstellung (siehe detectTheme). * setting (see detectTheme).
*/ */
:root.dark body { :root.dark body {
background: #181818; background: #181818;
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"en": { "en": {
"pageTitle": "Jitsi Meeting", "pageTitle": "Jitsi Meeting",
"impressum": "Legal Notice", "legal_notice": "Legal Notice",
"source": "Source", "source": "Source",
"launcher": { "launcher": {
"title": "Jitsi Meeting", "title": "Jitsi Meeting",
@@ -91,7 +91,7 @@
}, },
"de": { "de": {
"pageTitle": "Jitsi Meeting öffnen", "pageTitle": "Jitsi Meeting öffnen",
"impressum": "Impressum", "legal_notice": "Impressum",
"source": "Quellcode", "source": "Quellcode",
"launcher": { "launcher": {
"title": "Jitsi Meeting", "title": "Jitsi Meeting",
@@ -181,7 +181,7 @@
}, },
"fr": { "fr": {
"pageTitle": "Réunion Jitsi", "pageTitle": "Réunion Jitsi",
"impressum": "Mentions légales", "legal_notice": "Mentions légales",
"source": "Code source", "source": "Code source",
"launcher": { "launcher": {
"title": "Réunion Jitsi", "title": "Réunion Jitsi",
@@ -271,7 +271,7 @@
}, },
"zh": { "zh": {
"pageTitle": "Jitsi 会议", "pageTitle": "Jitsi 会议",
"impressum": "法律声明", "legal_notice": "法律声明",
"source": "源代码", "source": "源代码",
"launcher": { "launcher": {
"title": "Jitsi 会议", "title": "Jitsi 会议",
+114 -113
View File
@@ -1,13 +1,13 @@
"use strict"; "use strict";
/* ============================================================ /* ============================================================
* KONFIGURATION * CONFIGURATION
* ============================================================ * ============================================================
*/ */
/* /*
* Konfiguration liegt in assets/config.json und wird beim * Configuration lives in assets/config.json and is loaded
* Start asynchron geladen (siehe START). * asynchronously at startup (see START).
*/ */
let CONFIG = {}; let CONFIG = {};
const CONFIG_URL = "assets/config.json"; const CONFIG_URL = "assets/config.json";
@@ -18,8 +18,8 @@ const CONFIG_URL = "assets/config.json";
*/ */
/* /*
* Sprachen liegen in assets/i18n.json und werden beim * Translations live in assets/i18n.json and are loaded
* Start asynchron geladen (siehe START). * asynchronously at startup (see START).
*/ */
let TRANSLATIONS = {}; let TRANSLATIONS = {};
const I18N_URL = "assets/i18n.json"; const I18N_URL = "assets/i18n.json";
@@ -31,7 +31,7 @@ const I18N_URL = "assets/i18n.json";
function detectLanguage() { function detectLanguage() {
/* /*
* Gespeicherte Sprachwahl hat Vorrang. * Saved language choice takes precedence.
*/ */
try { try {
const saved = localStorage.getItem("jitsiLauncherLanguage"); const saved = localStorage.getItem("jitsiLauncherLanguage");
@@ -41,7 +41,7 @@ function detectLanguage() {
} }
} catch (error) { } catch (error) {
/* /*
* localStorage kann z.B. im Privat-Modus blockiert sein. * localStorage can be blocked, e.g. in private mode.
*/ */
} }
@@ -62,7 +62,7 @@ function detectLanguage() {
} }
/* /*
* Englisch ist der Standard/Fallback. * English is the default/fallback.
*/ */
return "en"; return "en";
} }
@@ -93,7 +93,7 @@ function applyTranslations() {
}); });
/* /*
* Platzhalter von Eingabefeldern übersetzen. * Translate placeholders of input fields.
*/ */
document document
.querySelectorAll("[data-i18n-placeholder]") .querySelectorAll("[data-i18n-placeholder]")
@@ -107,13 +107,13 @@ function applyTranslations() {
} }
/* /*
* Übersetzung anhand eines Pfades lesen. * Read a translation by path.
* *
* Beispiel: * Example:
* *
* "downloads.windows.title" * "downloads.windows.title"
* *
* ergibt: * resolves to:
* *
* translation.downloads.windows.title * translation.downloads.windows.title
*/ */
@@ -182,7 +182,7 @@ function setLanguage(language) {
localStorage.setItem("jitsiLauncherLanguage", language); localStorage.setItem("jitsiLauncherLanguage", language);
} catch (error) { } catch (error) {
/* /*
* localStorage kann z.B. im Privat-Modus blockiert sein. * localStorage can be blocked, e.g. in private mode.
*/ */
} }
@@ -236,7 +236,7 @@ function savedTheme() {
} }
} catch (error) { } catch (error) {
/* /*
* localStorage kann z.B. im Privat-Modus blockiert sein. * localStorage can be blocked, e.g. in private mode.
*/ */
} }
@@ -249,7 +249,7 @@ function systemIsDark() {
function detectTheme() { function detectTheme() {
/* /*
* Gespeicherte Wahl hat Vorrang, sonst System-Einstellung. * Saved choice takes precedence, otherwise system setting.
*/ */
return savedTheme() || (systemIsDark() ? "dark" : "light"); return savedTheme() || (systemIsDark() ? "dark" : "light");
} }
@@ -273,7 +273,7 @@ function toggleTheme() {
localStorage.setItem(THEME_KEY, currentTheme); localStorage.setItem(THEME_KEY, currentTheme);
} catch (error) { } catch (error) {
/* /*
* localStorage kann z.B. im Privat-Modus blockiert sein. * localStorage can be blocked, e.g. in private mode.
*/ */
} }
@@ -315,8 +315,8 @@ function applyLogo() {
*/ */
/* /*
* Nur anzeigen, wenn eine Nextcloud-Instanz konfiguriert * Only show if a Nextcloud instance is configured
* ist (CONFIG.nextcloudBase in assets/config.json). * (CONFIG.nextcloudBase in assets/config.json).
*/ */
function applyNextcloudButton() { function applyNextcloudButton() {
if (!CONFIG.nextcloudBase || !createNextcloudButton) { if (!CONFIG.nextcloudBase || !createNextcloudButton) {
@@ -332,7 +332,7 @@ function applyNextcloudButton() {
*/ */
/* /*
* Nur anzeigen, wenn explizit aktiviert * Only show if explicitly enabled
* (CONFIG.allowAdhocMeetings in assets/config.json). * (CONFIG.allowAdhocMeetings in assets/config.json).
*/ */
function applyAdhocButton() { function applyAdhocButton() {
@@ -421,11 +421,11 @@ const themeIconMoon = document.getElementById("themeIconMoon");
* GET ROOM FROM QUERY-PARAMETER * GET ROOM FROM QUERY-PARAMETER
* ============================================================ * ============================================================
* *
* Beispiel: * Example:
* *
* https://<YOURJITSIINSTANCEADDRESS>/launch.html?room=abc-defg-hij * https://<YOURJITSIINSTANCEADDRESS>/launch.html?room=abc-defg-hij
* *
* ergibt: * results in:
* *
* abc-defg-hij * abc-defg-hij
* *
@@ -438,16 +438,16 @@ function getRoom() {
); );
/* /*
* Parameter nicht vorhanden. * Parameter missing.
*/ */
if (!params.has("room")) { if (!params.has("room")) {
return null; return null;
} }
/* /*
* Leerzeichen etc. verhindern. * Prevent whitespace etc.
* *
* "?room=" ohne Wert ergibt einen leeren String. * "?room=" without a value yields an empty string.
*/ */
return params.get("room").trim(); return params.get("room").trim();
} }
@@ -456,11 +456,11 @@ function getRoom() {
* VALIDATE ROOM * VALIDATE ROOM
* ============================================================ * ============================================================
* *
* Jitsi-Roomnamen können je nach Konfiguration relativ * Jitsi room names can contain rather different
* unterschiedliche Zeichen enthalten. * characters depending on the configuration.
* *
* Wir verhindern hier lediglich Steuerzeichen und * We only prevent control characters and obvious
* offensichtliche URL-Manipulation. * URL manipulation here.
* *
* ============================================================ * ============================================================
*/ */
@@ -473,7 +473,7 @@ function isValidRoom(room) {
} }
/* /*
* Nur Buchstaben, Zahlen und Bindestriche. * Only letters, numbers and hyphens.
*/ */
return ROOM_PATTERN.test(room); return ROOM_PATTERN.test(room);
} }
@@ -511,10 +511,10 @@ function createAppUrl(room) {
* ============================================================ */ * ============================================================ */
/* /*
* Die Android-App registriert "org.jitsi.meet", nicht das * The Android app registers "org.jitsi.meet", not the
* Desktop-Schema. Chrome & Co. verlangen intent://, ohne App * desktop scheme. Chrome & co. require intent://; without
* bietet Chrome den Play-Store-Eintrag aus "package=" an. * the app Chrome offers the Play Store entry from "package=".
* Firefox ignoriert intent://, kennt aber das Custom-Schema. * Firefox ignores intent:// but knows the custom scheme.
*/ */
function createAndroidIntentUrl(room) { function createAndroidIntentUrl(room) {
@@ -528,8 +528,8 @@ function createAndroidIntentUrl(room) {
} }
/* /*
* Die Mobile-Apps (iOS, Firefox auf Android) registrieren * The mobile apps (iOS, Firefox on Android) register
* "org.jitsi.meet", nicht das Desktop-Schema. * "org.jitsi.meet", not the desktop scheme.
*/ */
const APP_SCHEME = "org.jitsi.meet://"; const APP_SCHEME = "org.jitsi.meet://";
@@ -543,14 +543,14 @@ function createAppSchemeUrl(room) {
} }
/* ============================================================ /* ============================================================
* URL NACH PLATTFORM/BROWSER * URL BY PLATFORM/BROWSER
* ============================================================ */ * ============================================================ */
function selectProtocolUrl(room) { function selectProtocolUrl(room) {
const os = detectOperatingSystem(); const os = detectOperatingSystem();
/* /*
* iOS-App kennt nur das App-Schema. * The iOS app only knows the app scheme.
*/ */
if (os === "ios") { if (os === "ios") {
return createAppSchemeUrl(room); return createAppSchemeUrl(room);
@@ -571,8 +571,8 @@ function selectProtocolUrl(room) {
*/ */
/* /*
* Einmal parsen (assets/lib/ua-parser.min.js). * Parse once (assets/lib/ua-parser.min.js).
* Fällt die Bibliothek aus, bleibt die Seite bedienbar. * If the library is missing, the page stays usable.
*/ */
const uaResult = const uaResult =
typeof UAParser === "function" typeof UAParser === "function"
@@ -580,8 +580,8 @@ const uaResult =
: { os: {}, browser: {} }; : { os: {}, browser: {} };
/* /*
* UAParser-Namen -> interne Kürzel. * UAParser names -> internal shortcuts.
* iPadOS meldet sich als macOS – Touchpunkte gelten als iOS. * iPadOS reports as macOS – touch points count as iOS.
*/ */
const OS_NAMES = { const OS_NAMES = {
Android: "android", Android: "android",
@@ -614,9 +614,9 @@ const BROWSER_NAMES = {
function detectOperatingSystem() { function detectOperatingSystem() {
/* /*
* Chrome im "Desktop-Modus" auf Android sendet einen * Chrome in "desktop mode" on Android sends a desktop
* Desktop-UA (z.B. X11; Linux). Client Hints verraten * UA (e.g. X11; Linux). Client hints reveal the real
* das echte System. Firefox/Safari kennen sie nicht. * system. Firefox/Safari do not support them.
*/ */
const hints = navigator.userAgentData; const hints = navigator.userAgentData;
if (hints && hints.platform === "Android") { if (hints && hints.platform === "Android") {
@@ -624,8 +624,9 @@ function detectOperatingSystem() {
} }
/* /*
* iPadOS tarnt sich als macOS – Touchpunkte verraten das iPad. * iPadOS masquerades as macOS – touch points give the
* Der Plattform-Check greift auch ohne geladenen ua-parser. * iPad away. The platform check also works without the
* ua-parser loaded.
*/ */
if ( if (
navigator.maxTouchPoints > 1 && navigator.maxTouchPoints > 1 &&
@@ -638,7 +639,7 @@ function detectOperatingSystem() {
} }
/* ============================================================ /* ============================================================
* BROWSER ERKENNEN * DETECT BROWSER
* ============================================================ * ============================================================
*/ */
@@ -696,12 +697,12 @@ function configureDownloads() {
return; return;
} }
/* Alles andere */ /* Everything else */
otherDownload.classList.remove("hidden"); otherDownload.classList.remove("hidden");
} }
/* ============================================================ /* ============================================================
* FALLBACK ANZEIGEN * SHOW FALLBACK
* ============================================================ * ============================================================
*/ */
@@ -714,7 +715,7 @@ function showFallback() {
} }
/* ============================================================ /* ============================================================
* JITSI DESKTOP STARTEN * START JITSI DESKTOP
* ============================================================ * ============================================================
*/ */
@@ -729,12 +730,12 @@ function launchJitsi() {
const browserUrl = createBrowserUrl(room); const browserUrl = createBrowserUrl(room);
/* /*
* Browser-Fallback vorbereiten. * Prepare the browser fallback.
*/ */
browserLink.href = browserUrl; browserLink.href = browserUrl;
/* /*
* UI auf "Startvorgang" setzen. * Set the UI to the "starting" state.
*/ */
launcher.classList.add("hidden"); launcher.classList.add("hidden");
fallback.classList.add("hidden"); fallback.classList.add("hidden");
@@ -744,13 +745,13 @@ function launchJitsi() {
let timeout = null; let timeout = null;
/* /*
* Unsichtbarer iframe für den Protocol Handler. * Invisible iframe for the protocol handler.
*/ */
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.style.display = "none"; iframe.style.display = "none";
/* /*
* Aufräumen. * Clean up.
*/ */
function cleanup() { function cleanup() {
if (completed) { if (completed) {
@@ -768,25 +769,25 @@ function launchJitsi() {
/* /*
* -------------------------------------------------------- * --------------------------------------------------------
* WICHTIG: * IMPORTANT:
* *
* Kein "blur" mehr als Erfolgskriterium verwenden. * Do not use "blur" as a success criterion anymore.
* *
* Der Browser kann beim Protocol-Dialog ebenfalls * The browser can also trigger blur when showing
* blur auslösen. * the protocol dialog.
* *
* Wenn der Benutzer den Dialog abbricht, bleibt die * If the user cancels the dialog, the page stays
* Seite sichtbar und unser Timeout läuft weiter. * visible and our timeout keeps running.
* -------------------------------------------------------- * --------------------------------------------------------
*/ */
document.body.appendChild(iframe); document.body.appendChild(iframe);
/* /*
* Protocol Handler starten. * Start the protocol handler.
* *
* Firefox ignoriert externe Protokolle aus iframes – * Firefox ignores external protocols from iframes –
* das Custom-Schema direkt in der Hauptnavigation starten. * start the custom scheme directly in the main navigation.
*/ */
try { try {
if (protocolUrl.startsWith(APP_SCHEME)) { if (protocolUrl.startsWith(APP_SCHEME)) {
@@ -811,19 +812,19 @@ function launchJitsi() {
* FALLBACK * FALLBACK
* -------------------------------------------------------- * --------------------------------------------------------
* *
* Wir warten unabhängig davon, ob der Browser kurz * We wait regardless of whether the browser briefly
* den Fokus verliert. * loses focus.
* *
* Wenn Jitsi erfolgreich startet: * If Jitsi starts successfully:
* *
* -> Jitsi kommt in den Vordergrund * -> Jitsi comes to the foreground
* -> Benutzer sieht diese Seite nicht mehr * -> the user no longer sees this page
* *
* Wenn der Benutzer den Browser-Dialog abbricht: * If the user cancels the browser dialog:
* *
* -> diese Seite bleibt sichtbar * -> this page stays visible
* -> Timeout läuft ab * -> the timeout expires
* -> Fallback wird angezeigt * -> the fallback is shown
* -------------------------------------------------------- * --------------------------------------------------------
*/ */
@@ -843,13 +844,13 @@ function launchJitsi() {
*/ */
/* /*
* Sonderzeichen in das nächstliegende ASCII-Zeichen * Convert special characters to the closest ASCII
* umwandeln. * character.
* *
* Beispiel: * Example:
* *
* "Grundstück" ergibt "Grundstueck" * "Grundstück" becomes "Grundstueck"
* "François" ergibt "Francois" * "François" becomes "Francois"
*/ */
function transliterate(text) { function transliterate(text) {
const extras = { const extras = {
@@ -870,11 +871,11 @@ function transliterate(text) {
} }
/* /*
* Aus dem Meeting-Namen einen gültigen Room-Namen ableiten. * Derive a valid room name from the meeting name.
* *
* Beispiel: * Example:
* *
* "Grund Stück Planung" ergibt "grund-stuck-planung" * "Grund Stück Planung" becomes "grund-stuck-planung"
*/ */
function createRoomName(meetingName) { function createRoomName(meetingName) {
return transliterate(meetingName) return transliterate(meetingName)
@@ -954,12 +955,12 @@ const MINUTES_STEP = 15;
const DEFAULT_DURATION_MINUTES = 30; const DEFAULT_DURATION_MINUTES = 30;
/* /*
* Minuten auf das 15-Minuten-Raster aufrunden. * Round minutes up to the 15-minute grid.
* *
* Beispiel: * Example:
* *
* 14:07 ergibt 14:15 * 14:07 becomes 14:15
* 14:50 ergibt 15:00 * 14:50 becomes 15:00
*/ */
function roundMinutes(date) { function roundMinutes(date) {
const rounded = new Date(date); const rounded = new Date(date);
@@ -973,11 +974,11 @@ function roundMinutes(date) {
} }
/* /*
* Lokale Zeit für datetime-local formatieren. * Format local time for datetime-local.
* *
* Beispiel: * Example:
* *
* 1. Januar 2026, 14:15 ergibt "2026-01-01T14:15" * January 1, 2026, 14:15 becomes "2026-01-01T14:15"
*/ */
function formatLocal(date) { function formatLocal(date) {
const pad = (num) => num.toString().padStart(2, "0"); const pad = (num) => num.toString().padStart(2, "0");
@@ -1066,8 +1067,8 @@ function openNextcloudCalendarCreator() {
} }
/* /*
* Nextcloud validiert die E-Mail serverseitig und * Nextcloud validates the email server-side and
* bricht sonst mit einer Fehlerseite ab. * aborts with an error page otherwise.
*/ */
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -1177,17 +1178,17 @@ function createMeeting() {
*/ */
/* /*
* Zeitspanne, nach der der "Kopiert!"-Text zum * Time span after which the "Copied!" text returns
* normalen Button-Label zurückkehrt. * to the normal button label.
*/ */
const COPY_RESET_MS = 2000; const COPY_RESET_MS = 2000;
/* /*
* Launch-URL, die das Meeting im Jitsi-Client öffnet. * Launch URL that opens the meeting in the Jitsi client.
* *
* Beispiel: * Example:
* *
* "projekt-kickoff" ergibt * "projekt-kickoff" becomes
* https://meet.bnvb.eu/launch.html?room=projekt-kickoff * https://meet.bnvb.eu/launch.html?room=projekt-kickoff
*/ */
function createLaunchUrl(roomName) { function createLaunchUrl(roomName) {
@@ -1221,7 +1222,7 @@ function createAdhocLink() {
async function copyAdhocLink() { async function copyAdhocLink() {
const url = adhocLink.href; const url = adhocLink.href;
/* Clipboard-API, mit Fallback für ältere Browser */ /* Clipboard API, with a fallback for older browsers */
try { try {
await navigator.clipboard.writeText(url); await navigator.clipboard.writeText(url);
} catch (error) { } catch (error) {
@@ -1233,7 +1234,7 @@ async function copyAdhocLink() {
document.body.removeChild(input); document.body.removeChild(input);
} }
/* Kurzes Feedback, dann zurück zum normalen Label */ /* Brief feedback, then back to the normal label */
copyAdhocLinkButton.textContent = getTranslation('creator.copiedLabel') || 'Copied!'; copyAdhocLinkButton.textContent = getTranslation('creator.copiedLabel') || 'Copied!';
setTimeout(() => { setTimeout(() => {
copyAdhocLinkButton.textContent = getTranslation('creator.copyLinkButton') || 'Copy Link'; copyAdhocLinkButton.textContent = getTranslation('creator.copyLinkButton') || 'Copy Link';
@@ -1255,7 +1256,7 @@ function cancelCreateMeeting() {
} }
/* ============================================================ /* ============================================================
* INITIALISIERUNG * INITIALIZATION
* ============================================================ * ============================================================
*/ */
@@ -1263,8 +1264,8 @@ function initialize() {
const room = getRoom(); const room = getRoom();
/* /*
* Kein room-Parameter: Meldungen ausblenden, * No room parameter: hide the messages,
* nur den Create-Button anzeigen. * only show the Create button.
*/ */
if (room === null) { if (room === null) {
missingRoomTitle.classList.add("hidden"); missingRoomTitle.classList.add("hidden");
@@ -1277,8 +1278,8 @@ function initialize() {
} }
/* /*
* Leerer room-Parameter ("?room="): * Empty room parameter ("?room="):
* "No meeting specified" anzeigen. * show "No meeting specified".
*/ */
if (room === "") { if (room === "") {
missingRoomTitle.classList.remove("hidden"); missingRoomTitle.classList.remove("hidden");
@@ -1291,8 +1292,8 @@ function initialize() {
} }
/* /*
* Ungültiger room-Parameter: nur die * Invalid room parameter: only show
* Invalid-Name-Meldung anzeigen. * the invalid-name message.
*/ */
if (!isValidRoom(room)) { if (!isValidRoom(room)) {
missingRoomTitle.classList.add("hidden"); missingRoomTitle.classList.add("hidden");
@@ -1306,17 +1307,17 @@ function initialize() {
} }
/* /*
* Room anzeigen. * Show the room.
*/ */
roomName.textContent = room; roomName.textContent = room;
/* /*
* Browser-Fallback vorbereiten. * Prepare the browser fallback.
*/ */
browserLink.href = createBrowserUrl(room); browserLink.href = createBrowserUrl(room);
/* /*
* Launcher anzeigen. * Show the launcher.
*/ */
launcher.classList.remove("hidden"); launcher.classList.remove("hidden");
@@ -1326,7 +1327,7 @@ function initialize() {
createNewFromLauncherButton.classList.add("hidden"); createNewFromLauncherButton.classList.add("hidden");
/* /*
* Debug-Informationen. * Debug information.
*/ */
console.log( console.log(
"Jitsi Launcher", "Jitsi Launcher",
@@ -1441,7 +1442,7 @@ navToggle.addEventListener(
); );
/* /*
* Ohne gespeicherte Wahl dem System folgen. * Follow the system if no choice is saved.
*/ */
window window
.matchMedia("(prefers-color-scheme: dark)") .matchMedia("(prefers-color-scheme: dark)")
@@ -1463,8 +1464,8 @@ async function loadConfig() {
CONFIG = await response.json(); CONFIG = await response.json();
} catch (error) { } catch (error) {
/* /*
* Ohne Datei bleiben Launcher unbrauchbar – Fehler * Without the file the launcher stays unusable –
* melden und mit leerer Konfiguration fortfahren. * report the error and continue with an empty config.
*/ */
console.error("Error loading config:", error); console.error("Error loading config:", error);
} }
@@ -1476,7 +1477,7 @@ async function loadTranslations() {
TRANSLATIONS = await response.json(); TRANSLATIONS = await response.json();
} catch (error) { } catch (error) {
/* /*
* Ohne Datei bleibt der englische Text aus dem HTML stehen. * Without the file the English text from the HTML stays.
*/ */
console.error("Error loading translations:", error); console.error("Error loading translations:", error);
} }
@@ -1486,8 +1487,8 @@ async function loadTranslations() {
} }
/* /*
* Theme/Logo sofort anwenden, damit nichts aufblinkt – * Apply theme/logo immediately so nothing flickers –
* Text erst nach dem Laden der Übersetzungen. * text only after the translations have loaded.
*/ */
async function start() { async function start() {
applyTheme(); applyTheme();
+5 -6
View File
@@ -4,10 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jitsi Meeting</title> <title>Jitsi Meeting</title>
<link rel='shortlink' href='https://meet.bnvb.eu/' /> <link href="assets/images/favicon.ico" rel="alternate icon">
<link href="https://meet.bnvb.eu/assets/images/favicon.ico" rel="alternate icon"> <link href="assets/images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="https://meet.bnvb.eu/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 href="https://meet.bnvb.eu/assets/images/favicon.svg" rel="icon" type="image/svg+xml">
<link rel="stylesheet" href="assets/css/launch.css"> <link rel="stylesheet" href="assets/css/launch.css">
</head> </head>
@@ -31,8 +30,8 @@
</svg> </svg>
</button> </button>
<div id="navActions" class="nav-actions"> <div id="navActions" class="nav-actions">
<a class="button secondary" href="https://www.bnvb-ingenieure.de/impressum" target="_blank" rel="noopener noreferrer" data-i18n="impressum"> <a class="button secondary" href="https://www.bnvb-ingenieure.de/impressum" target="_blank" rel="noopener noreferrer" data-i18n="legal_notice">
Impressum Legal Notice
</a> </a>
<a class="button secondary" href="https://git.bnvb.eu/stefan.tollkuehn/jitsi-launch" target="_blank" rel="noopener noreferrer" data-i18n="source"> <a class="button secondary" href="https://git.bnvb.eu/stefan.tollkuehn/jitsi-launch" target="_blank" rel="noopener noreferrer" data-i18n="source">
Source Source