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