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;
|
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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user