Compare commits

...
47 Commits
Author SHA1 Message Date
stefan.tollkuehn ebbfa990fb Relabel creator button as ICS download
The form button generates the ICS file, so name it after
the result. Move the Nextcloud button ahead of it, matching
the visual weight of the primary actions.
2026-11-05 17:52:27 +01:00
stefan.tollkuehn cfb48aa3c9 Group help texts under a help key
meetingPrivacySettings nests one level deeper under help.
The umbrella makes room for further help topics and allows
contextualizing which help a visible view offers. All
data-i18n references follow; no logic changes.
2026-11-05 17:37:24 +01:00
stefan.tollkuehn 4df51ca508 Simplify browser permissions wording
Name only the website-permissions icon when describing
where to change camera/mic decisions. The combined
lock-and-permissions phrasing was clunky in German and
duplicated what the icon already shows. Adjust all
translations accordingly.
2026-11-05 17:33:17 +01:00
stefan.tollkuehn c06127ef2b Document i18n markup and help overlay
Add the missing legalNotice parameter to the config table.
Describe how translations resolve keys, which markup the
renderer accepts, and how the OS-keyed help dialog works.
2026-11-05 17:29:31 +01:00
stefan.tollkuehn da2f6b5c25 Move Windows camera help into overlay
The privacy instructions no longer bloat the launcher card.
A "Help" button in the navbar opens them in a native dialog
(Esc, backdrop and button close it). The button only shows
for Windows users, reusing the existing OS detection.

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

All help texts live under meetingPrivacySettings.windows;
sibling keys for other operating systems can follow.
2026-11-05 17:24:15 +01:00
stefan.tollkuehn 7961732323 Show Windows privacy hints in launcher
The launcher sentence around the room name was replaced
by a standalone instruction ("meetingText"); the room name
now sits on its own line below it.

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

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

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

The paths now come from the favicons block in
assets/config.json. Missing keys keep the defaults
from launch.html.
2026-11-02 15:48:19 +01:00
stefan.tollkuehn 9184f60461 cleanup and htaccess fix 2026-11-02 15:09:00 +01:00
stefan.tollkuehn 64c49d210a update README 2026-11-02 15:02:09 +01:00
stefan.tollkuehn 1e3c7d676c rename config.json to config.json.sample and ignore config.json in git repository 2026-11-02 15:00:48 +01:00
stefan.tollkuehn c9accc2111 fix url for nextcloud 2026-10-22 17:23:56 +02:00
stefan.tollkuehn ce80e52923 - fix escaping in ICS generation
- add CRLF line endings in ICS
- add line folding in ICS
- fix downloadICS()
2026-10-22 17:09:16 +02:00
stefan.tollkuehn 86caf9a3a4 use relative urls, remove shortlink 2026-10-22 16:54:26 +02:00
stefan.tollkuehn 2c5e95ce7f translated comments in css 2026-10-22 16:45:24 +02:00
stefan.tollkuehn 0891a60beb .container should not be partially hidden behind .navbar 2026-10-22 16:44:01 +02:00
stefan.tollkuehn 3267b6a6a3 translation of comments and some other german terms 2026-10-22 16:34:01 +02:00
stefan.tollkuehn 5d54f574e0 use localized placeholder values for text input fields 2026-10-22 16:00:30 +02:00
stefan.tollkuehn 3ff88ce67f fix: remove duplicate line 2026-10-22 16:00:02 +02:00
stefan.tollkuehn c5f145a547 introduce setting "allowAdHocMeetings" to show or hide Adhoc-Meetings button 2026-10-22 15:54:31 +02:00
stefan.tollkuehn 7939d384bc display "Create in Nextcloud Calendar" button only if nextcloudBase is set in config.json 2026-10-22 15:51:01 +02:00
stefan.tollkuehn e9ad3882d2 move const CONFIG to json file and add documentation in README.md 2026-10-22 15:33:52 +02:00
stefan.tollkuehn c172c057eb add compatibility to nextcloud app jitsi_calendar 2026-10-07 22:14:56 +02:00
stefan.tollkuehn 88a9827de2 implement button to create event in nextcloud 2026-10-07 22:06:44 +02:00
stefan.tollkuehn b424abe8e4 add android download options for browsers other than chrome 2026-10-07 17:22:49 +02:00
stefan.tollkuehn 226edd8c48 a more robust way to make sure .card is not glued to bottom in any orientation 2026-10-07 17:16:57 +02:00
stefan.tollkuehn 344ecdf820 fix portrait mode 2026-10-07 17:13:47 +02:00
stefan.tollkuehn e08ffe6415 retry to fix glueing to bottom 2026-10-07 17:10:14 +02:00
stefan.tollkuehn 4f99d96bf4 Revert "test margin botton for mobile devices in landscape mode for create meeting view"
This reverts commit fa7a06be38.
2026-10-07 17:06:53 +02:00
stefan.tollkuehn fa7a06be38 test margin botton for mobile devices in landscape mode for create meeting view 2026-10-07 17:03:39 +02:00
stefan.tollkuehn faa5f428d0 fix navToggle in landscape mode 2026-10-07 16:58:15 +02:00
stefan.tollkuehn 1a00e7430a use navtoggle on mobile devices 2026-10-07 16:53:34 +02:00
stefan.tollkuehn 5033215d38 provide direct download paths for windows and macos 2026-10-07 16:42:48 +02:00
stefan.tollkuehn a1d342ad25 fix path in README.md 2026-10-07 16:40:17 +02:00
stefan.tollkuehn e9c766227f move ua-parser.min.js to assets/lib 2026-10-07 16:30:32 +02:00
stefan.tollkuehn 3dd0b8d496 move assets to new location 2026-10-07 16:27:12 +02:00
stefan.tollkuehn 42464bc72e better support for ios 2026-10-07 16:16:24 +02:00
stefan.tollkuehn af7aefd85f make ios detection a bit better, user better naming 2026-10-07 15:05:42 +02:00
stefan.tollkuehn 598ef1a32f load translations from assets/i18n.json 2026-10-07 14:59:43 +02:00
stefan.tollkuehn eb794a6c61 move js code from launch.html in to assets/launch.cs 2026-10-07 14:45:12 +02:00
stefan.tollkuehn ff9658b3bb an additional way to detect android when in desktopmode 2026-10-07 14:38:06 +02:00
stefan.tollkuehn b4ae7fb985 fix for firefox on android 2026-10-07 14:34:28 +02:00
stefan.tollkuehn b2c88f7e62 use intent url for deeplinking on android 2026-10-07 14:26:56 +02:00
stefan.tollkuehn e39a461bfd add link to source code in navigation bar 2026-10-07 14:25:19 +02:00
stefan.tollkuehn 92ed91f63d integrate https://github.com/faisalman/ua-parser-js 2026-10-07 14:22:24 +02:00
19 changed files with 2898 additions and 2149 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
assets/config.json
+1 -5
View File
@@ -2,9 +2,5 @@ DirectoryIndex launch.html
# Hide repository meta files behind 404
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(README\.md|LICENSE|AGENTS\.md)$ - [NC,R=404,L]
# security.txt: redirect to central security contact
RewriteRule ^\.well-known/security\.txt$ https://www.bnvb-ingenieure.de/.well-known/security.txt [NC,R=301,L]
RewriteRule ^(README\.md|LICENSE|AGENTS\.md|.gitignore|.htaccess)$ - [NC,R=404,L]
</IfModule>
+2 -2
View File
@@ -1,4 +1,4 @@
# AGENT.MD
# AGENTS.MD
- When writing something intended for human consumption, (comment, commit message, reply to prompt) use as few words as possible. Pick every word meticulously to reduce the volume to a strict minimum. Be down to the point. Less is more.
@@ -31,7 +31,7 @@ Rule 1: Separate the subject line from the body with a single blank line.
Rule 2: Limit the subject line to 50 characters (72 is the absolute hard limit).
Rule 3: Capitalize the first letter of the subject line.
Rule 4: Do not end the subject line with a period.
Rule 5: Use the imperative mood in the subject line (e.g., "Fix bug," "Add feature,"
Rule 5: Use the imperative mood in the subject line (e.g., "Fix bug," "Add feature,"
not "Fixed" or "Adds"). Test formula: It must complete the sentence: "If applied,
this commit will [your subject line here]".
Rule 6: Wrap the body text manually at 72 characters to prevent Git formatting issues.
+47 -1
View File
@@ -1,3 +1,49 @@
# jitsi-start
A small html/js project to nudge a user to use the desktop client for Jitsi.
A small html/js project to nudge a user to use the desktop client for Jitsi.
## Configuration
All settings live in `assets/config.json`, loaded at startup. The launcher stays
up but links break if the file is missing or malformed.
Rename `assets/config.json.sample` to `assets/config.json`.
| Parameter | Description |
| --- | --- |
| `jitsiLauncher` | Domain of this Jitsi launcher. Used for the meeting URLs created by the meeting creator (Nextcloud, adhoc link). |
| `jitsiHost` | Jitsi domain. Used for the browser fallback URL, the desktop protocol URL and the UID of generated ICS files. |
| `nextcloudBase` | Nextcloud instance for calendar event creation. The "Create in Nextcloud Calendar" button is only shown if this is set. |
| `legalNotice` | URL of the "Legal Notice" link in the navbar. The link stays hidden if unset. |
| `allowAdhocMeetings` | Whether adhoc meetings are allowed. The "Create Adhoc Link" button is only shown if this is `true`. |
| `launchTimeout` | Timeout in ms before the browser fallback is shown after trying to start the jitsi-meet-electron client. |
| `backgroundImage` | Path to the page background image. Empty: no background. |
| `logo` | Path to the logo shown in the logo bar. Empty: logo bar stays hidden. |
| `favicons` | Paths to the page icons: `ico` (fallback icon), `svg` (icon), `appleTouch` (Apple touch icon). The values from `launch.html` are kept if a key is missing. |
| `downloads` | Download URLs for the jitsi-meet clients, shown in the fallback view per detected OS: `windows`, `macos`, `linux`, `ios`, `android`. |
## Translations
All texts live in `assets/i18n.json`, one object per language
(`en`, `de`, `fr`, `zh`). Elements carry the key in `data-i18n`
(dots descend into nested objects), inputs in `data-i18n-placeholder`.
Translations may contain `<kbd>`, `<b>` and `<i>` markup (no
nesting). The renderer builds only these whitelisted elements;
other tags render literally and never execute.
## Help overlay
The "Help" button in the navbar opens a dialog with
camera/microphone hints. It only shows on the detected OS.
All help texts live under `help` in `assets/i18n.json`,
grouped by topic; the overlay uses
`help.meetingPrivacySettings`, keyed by OS (`windows`;
`macos`, `linux`, `ios` and `android` can follow). Further
topics can extend `help` and be contextualized per visible
view. The dialog width is defined in `dialog.privacy`
(`assets/css/launch.css`).
## Third-party assets
- `assets/lib/ua-parser.min.js` — [ua-parser-js](https://github.com/faisalman/ua-parser-js) v2.0.10, © Faisal Salman, licensed under AGPL-3.0.
+22
View File
@@ -0,0 +1,22 @@
{
"jitsiLauncher": "server.domain.tld/subdir",
"jitsiHost": "jit.si",
"nextcloudBase": "https://cloud.domain.tld",
"legalNotice": "https://www.domain.tld/legal-notice",
"allowAdhocMeetings": true,
"launchTimeout": 2000,
"backgroundImage": "path/to/backgroundimage",
"logo": "path/to/logo",
"favicons": {
"ico": "path/to/favicon.ico",
"svg": "path/to/favicon.svg",
"appleTouch": "path/to/apple-touch-icon.png"
},
"downloads": {
"windows": "https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.exe",
"macos": "https://github.com/jitsi/jitsi-meet-electron/releases/latest/download/jitsi-meet.dmg",
"linux": "https://github.com/jitsi/jitsi-meet-electron/releases",
"ios": "https://apps.apple.com/app/jitsi-meet/id1165103905",
"android": "https://play.google.com/store/apps/details?id=org.jitsi.meet"
}
}
+545
View File
@@ -0,0 +1,545 @@
:root {
color-scheme: light;
}
:root.dark {
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
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 {
position: fixed;
top: 16px;
left: 2%;
width: 96%;
box-sizing: border-box;
z-index: 100;
display: flex;
align-items: center;
gap: 8px;
padding: 15px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(7px);
backdrop-filter: blur(7px);
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.30), 0 12px 40px rgba(0, 0, 0, 0.45);
}
.logo-bar {
display: flex;
align-items: center;
flex: 0 1 auto;
min-width: 0;
}
.logo-bar img {
display: block;
height: 60px;
max-width: 100%;
width: auto;
}
.nav-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
margin-left: auto;
}
/* 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;
}
/* Tighter than the global 6px button margin */
.navbar .button {
margin: 0;
}
.lang-menu {
position: relative;
}
.lang-dropdown {
display: none;
position: absolute;
right: 0;
top: 100%;
min-width: 150px;
margin-top: 6px;
padding: 6px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.lang-dropdown.open {
display: block;
}
.lang-dropdown button {
display: block;
width: 100%;
margin: 0;
padding: 10px 14px;
border: 0;
border-radius: 6px;
background: none;
text-align: left;
font-size: 14px;
font-family: inherit;
cursor: pointer;
}
.lang-dropdown button:hover {
background: #f0f0f0;
}
.lang-dropdown button.active {
font-weight: 600;
background: #f0f8ff;
}
.lang-flag {
width: 20px;
height: 13px;
margin-right: 6px;
border-radius: 2px;
vertical-align: -2px;
}
.card {
box-sizing: border-box;
width: min(600px, 100%);
padding: 32px;
background: rgba(255, 255, 255, 0.2);
-webkit-backdrop-filter: blur(7px);
backdrop-filter: blur(7px);
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.30), 0 12px 40px rgba(0, 0, 0, 0.45);
text-align: center;
}
h1 {
margin-top: 0;
}
p {
color: #555;
}
button, .button {
display: inline-block;
border: 0;
border-radius: 8px;
padding: 12px 20px;
margin: 6px;
font-size: 16px;
font-family: inherit;
cursor: pointer;
text-decoration: none;
}
.primary {
background: #1f73e8;
color: white;
}
.secondary {
background: #e9e9e9;
color: #222;
}
.hidden {
display: none !important;
}
.status {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0f0f0;
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
border: 2px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.7s linear infinite;
vertical-align: -3px;
}
.download {
margin-top: 24px;
padding: 20px;
border-radius: 8px;
background: #f5f5f5;
}
/* Windows help overlay – long text needs left
* alignment, the page is centered */
dialog.privacy {
width: min(1000px, calc(100% - 32px));
max-height: calc(100vh - 48px);
border: 0;
border-radius: 14px;
background: #f5f5f5;
padding: 20px;
text-align: left;
overflow-y: auto;
}
dialog.privacy::backdrop {
background: rgba(0, 0, 0, 0.5);
}
.help-actions {
margin-top: 24px;
text-align: center;
}
.room {
display: block;
margin-top: 20px;
font-family: monospace;
word-break: break-all;
}
/* Keyboard keys, marked with <kbd> in i18n.json */
kbd {
padding: 1px 6px;
border: 1px solid #ccc;
border-bottom-width: 3px;
border-radius: 4px;
background: #f0f0f0;
font-family: monospace;
font-size: 0.9em;
}
.error {
padding: 16px;
border-radius: 8px;
background: #fff0f0;
color: #900;
}
.form-group {
margin-bottom: 16px;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
min-height: 80px;
resize: vertical;
}
.form-actions {
margin-top: 24px;
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
.success-message {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0f8ff;
color: #0066cc;
}
.adhoc-result {
margin-top: 24px;
padding: 16px;
border-radius: 8px;
background: #f0fff4;
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;
align-items: center;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.adhoc-link a {
font-family: monospace;
word-break: break-all;
color: #0066cc;
}
/*
* Prevents clipped content when the card is taller
* than the viewport ("center" clips at the top).
*/
@supports (align-items: safe center) {
body {
align-items: safe center;
}
}
/* Very narrow phones: even more compact */
@media (max-width: 400px) {
.navbar {
left: 2%;
width: 96%;
padding: 8px;
gap: 6px;
}
.logo-bar img {
height: 32px;
}
.nav-actions {
gap: 6px;
}
.nav-actions .button {
padding: 8px 12px;
font-size: 14px;
}
}
/*
* Phone: show the navbar actions in both orientations
* as a dropdown behind the hamburger button.
*
* 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 {
/*
* 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;
/* Subtract the 10px bottom padding */
min-height: calc(100vh - 10px);
}
.navbar {
top: 8px;
left: 4%;
width: 92%;
padding: 10px;
}
.logo-bar img {
height: 40px;
}
button, .button {
padding: 10px 14px;
font-size: 15px;
}
.card {
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" sets margin: 0 – hence target
* more specifically: push the button to the right */
.navbar .nav-toggle {
margin-left: auto;
}
.nav-actions {
display: none;
position: absolute;
top: 100%;
right: 0;
margin: 8px 0 0;
padding: 8px;
min-width: 200px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
flex-direction: column;
align-items: stretch;
}
.nav-actions.open {
display: flex;
}
/* All panel buttons equally wide, text centered */
.nav-actions .button {
text-align: center;
}
/* Language button sits inside the .lang-menu wrapper */
.nav-actions .lang-menu .button {
display: block;
width: 100%;
box-sizing: border-box;
}
:root.dark .nav-actions {
background: #242424;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/*
* 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;
color: #eee;
}
:root.dark .navbar {
background: rgba(36, 36, 36, 0.72);
}
:root.dark .card {
background: rgba(36, 36, 36, 0.72);
}
:root.dark .lang-dropdown {
background: #242424;
}
:root.dark .lang-dropdown button:hover {
background: #303030;
}
:root.dark .lang-dropdown button.active {
background: #1a3a5a;
}
:root.dark p {
color: #bbb;
}
:root.dark kbd {
border-color: #555;
background: #404040;
}
:root.dark .secondary {
background: #444;
color: #eee;
}
:root.dark .status,
:root.dark .download,
:root.dark .privacy {
background: #303030;
}
:root.dark .spinner {
border-color: #555;
border-top-color: #ddd;
}
:root.dark .form-group label {
color: #ddd;
}
:root.dark .form-group input,
:root.dark .form-group textarea {
background: #303030;
border-color: #555;
color: #eee;
}
:root.dark .success-message {
background: #1a3a5a;
color: #99ccff;
}
:root.dark .adhoc-result {
background: #1c3527;
color: #7ee2a8;
}
:root.dark .logo-bar img {
filter: url(#logo-dark-tint);
}
:root.dark .adhoc-link a {
color: #99ccff;
}
+446
View File
@@ -0,0 +1,446 @@
{
"en": {
"pageTitle": "Jitsi Meeting",
"legal_notice": "Legal Notice",
"source": "Source",
"launcher": {
"title": "Jitsi Meeting",
"meetingText": "Please click the button, to join the meeting:",
"openButton": "Join meeting"
},
"starting": {
"title": "Starting Jitsi Meet …",
"status": "The Jitsi Meet desktop client is being opened."
},
"fallback": {
"title": "Jitsi Meet could not be opened",
"description": "The Jitsi Meet desktop client may not have been started.",
"retryButton": "Try again"
},
"downloads": {
"windows": {
"title": "Jitsi Meet for Windows",
"description": "Install the Jitsi Meet desktop client and then try opening the meeting again.",
"button": "Download Jitsi for Windows"
},
"macos": {
"title": "Jitsi Meet for macOS",
"description": "Install the Jitsi Meet desktop client and then try opening the meeting again.",
"button": "Download Jitsi for macOS"
},
"linux": {
"title": "Jitsi Meet for Linux",
"description": "Install the Jitsi Meet desktop client and then try opening the meeting again.",
"button": "Download Jitsi for Linux"
},
"other": {
"title": "Jitsi Meet Desktop",
"description": "No desktop download is configured for your operating system."
},
"ios": {
"title": "Jitsi Meet for iOS",
"description": "Install the Jitsi Meet app and then try opening the meeting again.",
"button": "Download Jitsi for iOS"
},
"android": {
"title": "Jitsi Meet for Android",
"description": "Install the Jitsi Meet app and then try opening the meeting again.",
"button": "Download Jitsi for Android"
}
},
"browser": {
"title": "Join in browser",
"description": "You can also join the meeting directly in your browser.",
"button": "Join in browser"
},
"error": {
"title": "No meeting specified",
"description": "Please specify a meeting using the",
"descriptionSuffix": "parameter.",
"invalidTitle": "Invalid meeting name",
"invalidDescription": "Only letters, numbers and hyphens are allowed:"
},
"creator": {
"title": "Create New Meeting",
"meetingNameLabel": "Meeting Name",
"meetingNamePlaceholder": "My Meeting",
"meetingDescriptionLabel": "Description",
"meetingDescriptionPlaceholder": "Meeting description (optional)",
"meetingStartLabel": "Start Date & Time",
"meetingEndLabel": "End Date & Time",
"meetingOrganizerLabel": "Organizer Email",
"createButton": "Download ICS File",
"cancelButton": "Cancel",
"successMessage": "Meeting created! Download your calendar file and open it to import the meeting into your personal calendar.",
"downloadButton": "Download ICS File",
"createNewButton": "Create New Meeting",
"adhocButton": "Create Adhoc Link",
"adhocLinkLabel": "Your adhoc meeting link:",
"copyLinkButton": "Copy Link",
"copiedLabel": "Copied!",
"meetingNameRequired": "Please enter a meeting name",
"meetingNameInvalid": "Meeting name may only contain letters, numbers and hyphens",
"startTimeRequired": "Please select a start time",
"endTimeRequired": "Please select an end time",
"endAfterStart": "End time must be after start time",
"organizerRequired": "Please enter an organizer email",
"organizerInvalid": "Please enter a valid organizer email address",
"nextcloudButton": "Create in Nextcloud Calendar"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Camera and microphone in Windows: How to control your privacy",
"intro": "Your camera and microphone are sensitive devices. Windows therefore ensures that they can only be used with your permission. A few steps give you full control, especially when you use video calls or voice messages directly in your web browser.",
"step1Title": "Step 1: Open the Windows settings",
"step1Description": "Open the <b>Windows Settings</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). There, select the area <b>Privacy & security</b> and then <b>Camera</b> or <b>Microphone</b>.",
"step2Title": "Step 2: Regulate overall access",
"step2Description": "At the top of the page you will find the central switch for camera and microphone access. If it is disabled, no application and no website can access the device. If it is enabled, you decide on the same page whether applications are allowed access in principle.",
"step3Title": "Step 3: Check website permissions in the browser",
"step3Description": "Even if Windows allows access, your browser asks each website individually whether the camera or microphone may be used. This is the most important protection in everyday use: only when you agree does the access actually become possible.",
"browserPermissions": "You can change or revoke these decisions at any time. To do so, click the website-permissions icon in your browser's address bar and select the option for site settings. There you can allow the camera and microphone for the respective site, ask each time, or block them permanently.",
"goodToKnowTitle": "Good to know",
"goodToKnow1": "You can revoke permissions for individual websites at any time without changing Windows settings.",
"goodToKnow2": "An active microphone or camera is indicated in Windows by a small camera or microphone icon in the taskbar. This way you immediately recognize when a site is accessing a device.",
"goodToKnow3": "If a website does not get access despite working hardware, the cause is often not the device itself, but one of the two permission levels, in Windows or in the browser.",
"adminSupport": "If you need support finding the right settings, please ask <b>your administrator</b>.",
"navButton": "Help",
"closeButton": "Close"
}
}
}
},
"de": {
"pageTitle": "Jitsi Meeting öffnen",
"legal_notice": "Impressum",
"source": "Quellcode",
"launcher": {
"title": "Jitsi Meeting",
"meetingText": "Bitte klicken Sie auf die Schaltfläche, um an dem Meeting teilzunehmen:",
"openButton": "An Meeting teilnehmen"
},
"starting": {
"title": "Jitsi Meet wird gestartet …",
"status": "Der Jitsi Meet Desktop-Client wird geöffnet."
},
"fallback": {
"title": "Jitsi Meet konnte nicht geöffnet werden",
"description": "Der Jitsi Meet Desktop-Client wurde möglicherweise nicht gestartet.",
"retryButton": "Erneut versuchen"
},
"downloads": {
"windows": {
"title": "Jitsi Meet für Windows",
"description": "Installieren Sie den Jitsi Meet Desktop-Client und versuchen Sie anschließend erneut, das Meeting zu öffnen.",
"button": "Jitsi für Windows herunterladen"
},
"macos": {
"title": "Jitsi Meet für macOS",
"description": "Installieren Sie den Jitsi Meet Desktop-Client und versuchen Sie anschließend erneut, das Meeting zu öffnen.",
"button": "Jitsi für macOS herunterladen"
},
"linux": {
"title": "Jitsi Meet für Linux",
"description": "Installieren Sie den Jitsi Meet Desktop-Client und versuchen Sie anschließend erneut, das Meeting zu öffnen.",
"button": "Jitsi für Linux herunterladen"
},
"other": {
"title": "Jitsi Meet Desktop",
"description": "Für Ihr Betriebssystem ist kein Desktop-Download hinterlegt."
},
"ios": {
"title": "Jitsi Meet für iOS",
"description": "Installieren Sie die Jitsi Meet App und versuchen Sie anschließend erneut, das Meeting zu öffnen.",
"button": "Jitsi für iOS herunterladen"
},
"android": {
"title": "Jitsi Meet für Android",
"description": "Installieren Sie die Jitsi Meet App und versuchen Sie anschließend erneut, das Meeting zu öffnen.",
"button": "Jitsi für Android herunterladen"
}
},
"browser": {
"title": "Im Browser teilnehmen",
"description": "Sie können dem Meeting auch direkt über Ihren Browser beitreten.",
"button": "Im Browser beitreten"
},
"error": {
"title": "Kein Meeting angegeben",
"description": "Bitte geben Sie ein Meeting über den Parameter",
"descriptionSuffix": "an.",
"invalidTitle": "Ungültiger Meeting-Name",
"invalidDescription": "Erlaubt sind nur Buchstaben, Zahlen und Bindestriche:"
},
"creator": {
"title": "Neues Meeting erstellen",
"meetingNameLabel": "Meeting-Name",
"meetingNamePlaceholder": "Mein Meeting",
"meetingDescriptionLabel": "Beschreibung",
"meetingDescriptionPlaceholder": "Beschreibung des Meetings (optional)",
"meetingStartLabel": "Start Datum & Uhrzeit",
"meetingEndLabel": "Ende Datum & Uhrzeit",
"meetingOrganizerLabel": "Organisator E-Mail",
"createButton": "ICS-Datei herunterladen",
"cancelButton": "Abbrechen",
"successMessage": "Meeting erstellt! Laden Sie Ihre Kalenderdatei herunter und öffnen Sie diese, um das Meeting in Ihren persönlichen Kalender zu importieren.",
"downloadButton": "ICS-Datei herunterladen",
"createNewButton": "Neues Meeting erstellen",
"adhocButton": "Adhoc-Link erstellen",
"adhocLinkLabel": "Ihr Adhoc-Meeting-Link:",
"copyLinkButton": "Link kopieren",
"copiedLabel": "Kopiert!",
"meetingNameRequired": "Bitte geben Sie einen Meeting-Namen ein",
"meetingNameInvalid": "Der Meeting-Name darf nur Buchstaben, Zahlen und Bindestriche enthalten",
"startTimeRequired": "Bitte wählen Sie eine Startzeit",
"endTimeRequired": "Bitte wählen Sie eine Endzeit",
"endAfterStart": "Die Endzeit muss nach der Startzeit liegen",
"organizerRequired": "Bitte geben Sie eine Organisator-E-Mail ein",
"organizerInvalid": "Bitte geben Sie eine gültige Organisator-E-Mail-Adresse ein",
"nextcloudButton": "Im Nextcloud-Kalender erstellen"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Kamera und Mikrofon in Windows: So kontrollieren Sie Ihre Privatsphäre",
"intro": "Ihre Kamera und Ihr Mikrofon sind sensible Geräte. Windows sorgt dafür, dass sie nur mit Ihrer Erlaubnis genutzt werden können. Mit wenigen Schritten behalten Sie die volle Kontrolle, insbesondere wenn Sie Videotelefonie oder Sprachnachrichten direkt im Webbrowser nutzen.",
"step1Title": "Schritt 1: Windows-Einstellungen öffnen",
"step1Description": "Öffnen Sie die <b>Windows-Einstellungen</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). Wählen Sie dort den Bereich <b>Datenschutz und Sicherheit</b> und anschließend <b>Kamera</b> oder <b>Mikrofon</b>.",
"step2Title": "Schritt 2: Gesamtzugriff regeln",
"step2Description": "Oben auf der Seite finden Sie den zentralen Schalter für den Kamera- bzw. Mikrofonzugriff. Ist er deaktiviert, kann keine Anwendung und keine Website auf das Gerät zugreifen. Ist er aktiviert, entscheiden Sie auf der gleichen Seite, ob Anwendungen den Zugriff grundsätzlich erhalten dürfen.",
"step3Title": "Schritt 3: Website-Berechtigungen im Browser prüfen",
"step3Description": "Auch wenn Windows den Zugriff erlaubt, fragt Ihr Browser bei jeder Website einzeln nach, ob Kamera oder Mikrofon genutzt werden dürfen. Das ist der wichtigste Schutz im Alltag: Erst wenn Sie zustimmen, wird der Zugriff tatsächlich möglich.",
"browserPermissions": "Sie können diese Entscheidungen jederzeit ändern oder widerrufen. Klicken Sie dazu in der Adressleiste Ihres Browsers auf das Symbol für die Website-Berechtigungen und wählen Sie die Option für Website-Einstellungen. Dort lassen sich Kamera und Mikrofon für die jeweilige Seite gezielt erlauben, einmalig anfragen oder dauerhaft blockieren.",
"goodToKnowTitle": "Gut zu wissen",
"goodToKnow1": "Sie können Berechtigungen für einzelne Websites jederzeit zurückziehen, ohne Windows-Einstellungen zu ändern.",
"goodToKnow2": "Ein aktives Mikrofon oder eine aktive Kamera wird unter Windows durch ein kleines Kamera- oder Mikrofonsymbol in der Taskleiste angezeigt. So erkennen Sie sofort, wenn eine Seite auf ein Gerät zugreift.",
"goodToKnow3": "Wenn eine Website trotz funktionierender Hardware keinen Zugriff erhält, liegt die Ursache häufig nicht am Gerät selbst, sondern an einer der beiden Berechtigungsebenen, in Windows oder im Browser.",
"adminSupport": "Sollten Sie Unterstützung dabei benötigen, die passenden Einstellungen zu finden, fragen Sie bitte <b>Ihren Administrator</b>.",
"navButton": "Hilfe",
"closeButton": "Schließen"
}
}
}
},
"fr": {
"pageTitle": "Réunion Jitsi",
"legal_notice": "Mentions légales",
"source": "Code source",
"launcher": {
"title": "Réunion Jitsi",
"meetingText": "Veuillez cliquer sur le bouton pour rejoindre la réunion :",
"openButton": "Rejoindre la réunion"
},
"starting": {
"title": "Démarrage de Jitsi Meet…",
"status": "Le client bureau Jitsi Meet est en cours d'ouverture."
},
"fallback": {
"title": "Jitsi Meet n'a pas pu être ouvert",
"description": "Le client bureau Jitsi Meet n'a peut-être pas été démarré.",
"retryButton": "Réessayer"
},
"downloads": {
"windows": {
"title": "Jitsi Meet pour Windows",
"description": "Installez le client bureau Jitsi Meet, puis essayez de rouvrir la réunion.",
"button": "Télécharger Jitsi pour Windows"
},
"macos": {
"title": "Jitsi Meet pour macOS",
"description": "Installez le client bureau Jitsi Meet, puis essayez de rouvrir la réunion.",
"button": "Télécharger Jitsi pour macOS"
},
"linux": {
"title": "Jitsi Meet pour Linux",
"description": "Installez le client bureau Jitsi Meet, puis essayez de rouvrir la réunion.",
"button": "Télécharger Jitsi pour Linux"
},
"other": {
"title": "Jitsi Meet Desktop",
"description": "Aucun téléchargement bureau n'est configuré pour votre système d'exploitation."
},
"ios": {
"title": "Jitsi Meet pour iOS",
"description": "Installez l'application Jitsi Meet, puis essayez de rouvrir la réunion.",
"button": "Télécharger Jitsi pour iOS"
},
"android": {
"title": "Jitsi Meet pour Android",
"description": "Installez l'application Jitsi Meet, puis essayez de rouvrir la réunion.",
"button": "Télécharger Jitsi pour Android"
}
},
"browser": {
"title": "Rejoindre dans le navigateur",
"description": "Vous pouvez également rejoindre la réunion directement dans votre navigateur.",
"button": "Rejoindre dans le navigateur"
},
"error": {
"title": "Aucune réunion spécifiée",
"description": "Veuillez spécifier une réunion à l'aide du paramètre",
"descriptionSuffix": ".",
"invalidTitle": "Nom de réunion invalide",
"invalidDescription": "Seuls les lettres, les chiffres et les tirets sont autorisés :"
},
"creator": {
"title": "Créer une nouvelle réunion",
"meetingNameLabel": "Nom de la réunion",
"meetingNamePlaceholder": "Ma réunion",
"meetingDescriptionLabel": "Description",
"meetingDescriptionPlaceholder": "Description de la réunion (facultatif)",
"meetingStartLabel": "Date et heure de début",
"meetingEndLabel": "Date et heure de fin",
"meetingOrganizerLabel": "Email de l'organisateur",
"createButton": "Télécharger le fichier ICS",
"cancelButton": "Annuler",
"successMessage": "Réunion créée ! Téléchargez votre fichier calendrier et ouvrez-le pour importer la réunion dans votre calendrier personnel.",
"downloadButton": "Télécharger le fichier ICS",
"createNewButton": "Créer une nouvelle réunion",
"adhocButton": "Créer un lien adhoc",
"adhocLinkLabel": "Votre lien de réunion adhoc :",
"copyLinkButton": "Copier le lien",
"copiedLabel": "Copié !",
"meetingNameRequired": "Veuillez entrer un nom de réunion",
"meetingNameInvalid": "Le nom de la réunion ne peut contenir que des lettres, des chiffres et des tirets",
"startTimeRequired": "Veuillez sélectionner une heure de début",
"endTimeRequired": "Veuillez sélectionner une heure de fin",
"endAfterStart": "L'heure de fin doit être après l'heure de début",
"organizerRequired": "Veuillez entrer un email d'organisateur",
"organizerInvalid": "Veuillez entrer une adresse email d'organisateur valide",
"nextcloudButton": "Créer dans le calendrier Nextcloud"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Caméra et microphone sous Windows : comment contrôler votre vie privée",
"intro": "Votre caméra et votre microphone sont des appareils sensibles. Windows veille donc à ce qu'ils ne puissent être utilisés qu'avec votre autorisation. En quelques étapes, vous gardez le contrôle total, surtout lorsque vous utilisez la visioconférence ou les messages vocaux directement dans votre navigateur.",
"step1Title": "Étape 1 : Ouvrir les paramètres Windows",
"step1Description": "Ouvrez les <b>paramètres Windows</b> (<kbd>⊞ Windows</kbd> + <kbd>I</kbd>). Sélectionnez ensuite la section <b>Confidentialité et sécurité</b>, puis <b>Caméra</b> ou <b>Micro</b>.",
"step2Title": "Étape 2 : Régler l'accès global",
"step2Description": "En haut de la page se trouve l'interrupteur central de l'accès à la caméra ou au microphone. S'il est désactivé, aucune application ni aucun site web ne peut accéder à l'appareil. S'il est activé, vous décidez sur la même page si les applications peuvent y accéder en principe.",
"step3Title": "Étape 3 : Vérifier les autorisations du site dans le navigateur",
"step3Description": "Même si Windows autorise l'accès, votre navigateur demande site par site si la caméra ou le microphone peuvent être utilisés. C'est la protection la plus importante au quotidien : seul votre consentement rend l'accès réellement possible.",
"browserPermissions": "Vous pouvez modifier ou révoquer ces décisions à tout moment. Pour cela, cliquez dans la barre d'adresse de votre navigateur sur l'icône des autorisations du site, puis choisissez l'option des paramètres du site. Vous y pouvez autoriser, demander à chaque fois ou bloquer définitivement la caméra et le microphone pour le site concerné.",
"goodToKnowTitle": "Bon à savoir",
"goodToKnow1": "Vous pouvez retirer à tout moment les autorisations de sites individuels sans modifier les paramètres Windows.",
"goodToKnow2": "Un microphone ou une caméra active est signalé sous Windows par une petite icône de caméra ou de microphone dans la barre des tâches. Vous reconnaissez ainsi immédiatement quand un site accède à un appareil.",
"goodToKnow3": "Si un site n'obtient pas l'accès malgré un matériel fonctionnel, la cause n'est souvent pas l'appareil lui-même, mais l'un des deux niveaux d'autorisation, sous Windows ou dans le navigateur.",
"adminSupport": "Si vous avez besoin d'aide pour trouver les bons réglages, veuillez demander à <b>votre administrateur</b>.",
"navButton": "Aide",
"closeButton": "Fermer"
}
}
}
},
"zh": {
"pageTitle": "Jitsi 会议",
"legal_notice": "法律声明",
"source": "源代码",
"launcher": {
"title": "Jitsi 会议",
"meetingText": "请点击按钮加入会议:",
"openButton": "加入会议"
},
"starting": {
"title": "正在启动 Jitsi Meet …",
"status": "正在打开 Jitsi Meet 桌面客户端。"
},
"fallback": {
"title": "无法打开 Jitsi Meet",
"description": "Jitsi Meet 桌面客户端可能尚未启动。",
"retryButton": "重试"
},
"downloads": {
"windows": {
"title": "适用于 Windows 的 Jitsi Meet",
"description": "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。",
"button": "下载 Windows 版 Jitsi"
},
"macos": {
"title": "适用于 macOS 的 Jitsi Meet",
"description": "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。",
"button": "下载 macOS 版 Jitsi"
},
"linux": {
"title": "适用于 Linux 的 Jitsi Meet",
"description": "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。",
"button": "下载 Linux 版 Jitsi"
},
"other": {
"title": "Jitsi Meet 桌面版",
"description": "没有为您的操作系统配置桌面版下载。"
},
"ios": {
"title": "适用于 iOS 的 Jitsi Meet",
"description": "请安装 Jitsi Meet 应用,然后再次尝试打开会议。",
"button": "下载 iOS 版 Jitsi"
},
"android": {
"title": "适用于 Android 的 Jitsi Meet",
"description": "请安装 Jitsi Meet 应用,然后再次尝试打开会议。",
"button": "下载 Android 版 Jitsi"
}
},
"browser": {
"title": "在浏览器中加入",
"description": "您也可以直接通过浏览器加入会议。",
"button": "在浏览器中加入"
},
"error": {
"title": "未指定会议",
"description": "请使用参数指定会议",
"descriptionSuffix": "。",
"invalidTitle": "会议名称无效",
"invalidDescription": "仅允许使用字母、数字和连字符:"
},
"creator": {
"title": "创建新会议",
"meetingNameLabel": "会议名称",
"meetingNamePlaceholder": "我的会议",
"meetingDescriptionLabel": "描述",
"meetingDescriptionPlaceholder": "会议描述(可选)",
"meetingStartLabel": "开始日期和时间",
"meetingEndLabel": "结束日期和时间",
"meetingOrganizerLabel": "组织者邮箱",
"createButton": "下载 ICS 文件",
"cancelButton": "取消",
"successMessage": "会议已创建!请下载您的日历文件并打开它,将会议导入您的个人日历。",
"downloadButton": "下载 ICS 文件",
"createNewButton": "创建新会议",
"adhocButton": "创建临时链接",
"adhocLinkLabel": "您的临时会议链接:",
"copyLinkButton": "复制链接",
"copiedLabel": "已复制!",
"meetingNameRequired": "请输入会议名称",
"meetingNameInvalid": "会议名称只能包含字母、数字和连字符",
"startTimeRequired": "请选择开始时间",
"endTimeRequired": "请选择结束时间",
"endAfterStart": "结束时间必须晚于开始时间",
"organizerRequired": "请输入组织者邮箱",
"organizerInvalid": "请输入有效的组织者邮箱地址",
"nextcloudButton": "在 Nextcloud 日历中创建"
},
"help": {
"meetingPrivacySettings": {
"windows": {
"title": "Windows 中的摄像头和麦克风:如何掌控您的隐私",
"intro": "摄像头和麦克风是敏感设备。因此,Windows 确保只有获得您的许可才能使用它们。只需几个步骤,您就能完全掌控,尤其是在浏览器中直接使用视频通话或语音消息时。",
"step1Title": "步骤 1:打开 Windows 设置",
"step1Description": "打开 <b>Windows 设置</b>(<kbd>⊞ Windows</kbd> + <kbd>I</kbd>)。选择<b>隐私和安全性</b>,然后选择<b>摄像头</b>或<b>麦克风</b>。",
"step2Title": "步骤 2:管理总体访问权限",
"step2Description": "页面顶部有控制摄像头或麦克风访问的总开关。如果关闭,任何应用和网站都无法访问设备。如果开启,您可以在同一页面决定是否允许应用访问。",
"step3Title": "步骤 3:检查浏览器中的网站权限",
"step3Description": "即使 Windows 允许访问,浏览器也会逐个询问每个网站是否可以使用摄像头或麦克风。这是日常使用中最重要的保护:只有您同意后,访问才会真正生效。",
"browserPermissions": "您可以随时更改或撤销这些决定。为此,请点击浏览器地址栏中的网站权限图标,并选择网站设置选项。在那里可以为相应网站专门允许、每次询问或永久阻止摄像头和麦克风。",
"goodToKnowTitle": "实用提示",
"goodToKnow1": "您可以随时撤销单个网站的权限,而无需更改 Windows 设置。",
"goodToKnow2": "在 Windows 中,使用中的麦克风或摄像头会通过任务栏中的小摄像头或麦克风图标显示。这样您可以立即发现某个网站正在访问设备。",
"goodToKnow3": "如果网站在硬件正常的情况下仍无法获得访问权限,原因往往不在设备本身,而在两个权限层级之一:Windows 或浏览器。",
"adminSupport": "如果您需要帮助查找相应的设置,请咨询<b>您的管理员</b>。",
"navButton": "帮助",
"closeButton": "关闭"
}
}
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1742
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -0,0 +1,4 @@
/* UAParser.js v2.0.10
Copyright © 2012-2026 Faisal Salman <f@faisalman.com>
AGPLv3 License */
(function(window,undefined){"use strict";var LIBVERSION="2.0.10",UA_MAX_LENGTH=500,USER_AGENT="user-agent",EMPTY="",UNKNOWN="?",TYPEOF={FUNCTION:"function",OBJECT:"object",STRING:"string",UNDEFINED:"undefined"},BROWSER="browser",CPU="cpu",DEVICE="device",ENGINE="engine",OS="os",RESULT="result",NAME="name",TYPE="type",VENDOR="vendor",VERSION="version",ARCHITECTURE="architecture",MAJOR="major",MODEL="model",CONSOLE="console",MOBILE="mobile",TABLET="tablet",SMARTTV="smarttv",WEARABLE="wearable",XR="xr",EMBEDDED="embedded",FETCHER="fetcher",INAPP="inapp",BRANDS="brands",FORMFACTORS="formFactors",FULLVERLIST="fullVersionList",PLATFORM="platform",PLATFORMVER="platformVersion",BITNESS="bitness",CH="sec-ch-ua",CH_FULL_VER_LIST=CH+"-full-version-list",CH_ARCH=CH+"-arch",CH_BITNESS=CH+"-"+BITNESS,CH_FORM_FACTORS=CH+"-form-factors",CH_MOBILE=CH+"-"+MOBILE,CH_MODEL=CH+"-"+MODEL,CH_PLATFORM=CH+"-"+PLATFORM,CH_PLATFORM_VER=CH_PLATFORM+"-version",CH_ALL_VALUES=[BRANDS,FULLVERLIST,MOBILE,MODEL,PLATFORM,PLATFORMVER,ARCHITECTURE,FORMFACTORS,BITNESS],AMAZON="Amazon",APPLE="Apple",ASUS="ASUS",BLACKBERRY="BlackBerry",GOOGLE="Google",HUAWEI="Huawei",LENOVO="Lenovo",HONOR="Honor",LG="LG",MICROSOFT="Microsoft",MOTOROLA="Motorola",NVIDIA="Nvidia",ONEPLUS="OnePlus",OPPO="OPPO",SAMSUNG="Samsung",SHARP="Sharp",SONY="Sony",XIAOMI="Xiaomi",ZEBRA="Zebra",CHROME="Chrome",CHROMIUM="Chromium",CHROMECAST="Chromecast",EDGE="Edge",FIREFOX="Firefox",OPERA="Opera",FACEBOOK="Facebook",SOGOU="Sogou",PREFIX_MOBILE="Mobile ",SUFFIX_BROWSER=" Browser",WINDOWS="Windows";var isWindow=typeof window!==TYPEOF.UNDEFINED,NAVIGATOR=isWindow&&window.navigator?window.navigator:undefined,NAVIGATOR_UADATA=NAVIGATOR&&NAVIGATOR.userAgentData?NAVIGATOR.userAgentData:undefined;var extend=function(defaultRgx,extensions){var mergedRgx={};var extraRgx=extensions;if(!isExtensions(extensions)){extraRgx={};for(var i in extensions){for(var j in extensions[i]){extraRgx[j]=extensions[i][j].concat(extraRgx[j]?extraRgx[j]:[])}}}for(var k in defaultRgx){mergedRgx[k]=extraRgx[k]&&extraRgx[k].length%2===0?extraRgx[k].concat(defaultRgx[k]):defaultRgx[k]}return mergedRgx},enumerize=function(arr){var enums={};for(var i=0;i<arr.length;i++){enums[arr[i].toUpperCase()]=arr[i]}return enums},has=function(str1,str2){if(typeof str1===TYPEOF.OBJECT&&str1.length>0){for(var i in str1){if(lowerize(str2)==lowerize(str1[i]))return true}return false}return isString(str1)?lowerize(str2)==lowerize(str1):false},isExtensions=function(obj,deep){for(var prop in obj){return/^(browser|cpu|device|engine|os)$/.test(prop)||(deep?isExtensions(obj[prop]):false)}},isString=function(val){return typeof val===TYPEOF.STRING},itemListToArray=function(header){if(!header)return undefined;var arr=[];var tokens=normalizeHeaderValue(header).split(",");for(var i=0;i<tokens.length;i++){if(tokens[i].indexOf(";")>-1){var token=trim(tokens[i]).split(";v=");arr[i]={brand:token[0],version:token[1]}}else{arr[i]=trim(tokens[i])}}return arr},lowerize=function(str){return isString(str)?str.toLowerCase():str},majorize=function(version){return isString(version)?strip(/[^\d\.]/g,version).split(".")[0]:undefined},normalizeHeaderValue=function(str){return isString(str)?trim(strip(/\\?\"/g,str),UA_MAX_LENGTH):undefined},setProps=function(arr){for(var i in arr){if(!arr.hasOwnProperty(i))continue;var propName=arr[i];if(typeof propName==TYPEOF.OBJECT&&propName.length==2){this[propName[0]]=propName[1]}else{this[propName]=undefined}}return this},strip=function(pattern,str){return isString(str)?str.replace(pattern,EMPTY):str},trim=function(str,len){str=strip(/^\s\s*/,String(str));return typeof len===TYPEOF.UNDEFINED?str:str.substring(0,len)};var rgxMapper=function(ua,arrays){if(!ua||!arrays)return;var i=0,j,k,p,q,matches,match;while(i<arrays.length&&!matches){var regex=arrays[i],props=arrays[i+1];j=k=0;while(j<regex.length&&!matches){if(!regex[j]){break}matches=regex[j++].exec(ua);if(!!matches){for(p=0;p<props.length;p++){match=matches[++k];q=props[p];if(typeof q===TYPEOF.OBJECT&&q.length>0){if(q.length===2){if(typeof q[1]==TYPEOF.FUNCTION){this[q[0]]=q[1].call(this,match)}else{this[q[0]]=q[1]}}else if(q.length>=3){if(typeof q[1]===TYPEOF.FUNCTION&&!(q[1].exec&&q[1].test)){if(q.length>3){this[q[0]]=match?q[1].apply(this,q.slice(2)):undefined}else{this[q[0]]=match?q[1].call(this,match,q[2]):undefined}}else{if(q.length==3){this[q[0]]=match?match.replace(q[1],q[2]):undefined}else if(q.length==4){this[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}else if(q.length>4){this[q[0]]=match?q[3].apply(this,[match.replace(q[1],q[2])].concat(q.slice(4))):undefined}}}}else{this[q]=match?match:undefined}}}}i+=2}},strTest=function(str,map){return map.test.test(str)?map.ifTrue:map.ifFalse},strMapper=function(str,map){for(var i in map){if(typeof map[i]===TYPEOF.OBJECT&&map[i].length>0){for(var j=0;j<map[i].length;j++){if(has(map[i][j],str)){return i===UNKNOWN?undefined:i}}}else if(has(map[i],str)){return i===UNKNOWN?undefined:i}}return map.hasLine truncated
+88 -2141
View File
File diff suppressed because it is too large. Load diff