91 Commits

Author SHA1 Message Date
Dome b86b69d171 Hide null values in user profiles in CSS 2026-04-08 00:28:09 +02:00
Dome e009496885 Update docker-image.yml 2026-04-08 00:09:14 +02:00
Dome 690692b494 Fix media query formatting in main.css 2026-04-07 23:49:04 +02:00
Dome 5df8bdf932 Update client.css 2026-04-07 23:48:49 +02:00
Dome 030966c506 Update main.css 2026-04-07 23:37:59 +02:00
Dome 5a633a0ebb Update client.css 2026-04-07 23:37:46 +02:00
Dome 50d03aad1d Update client.css 2026-04-07 21:40:18 +02:00
Dome 29b754c9f8 Update main.css 2026-04-07 21:38:55 +02:00
Dome 9c90ede9c4 Update main.css 2026-04-07 21:34:53 +02:00
Dome 35bdc7473f Update client.css 2026-04-07 21:28:49 +02:00
Dome 3255e2ec29 Update client.css 2026-04-07 21:24:23 +02:00
Dome a550178de7 Update main.css 2026-04-07 21:22:26 +02:00
Dome 18284ef0f2 Update main.css 2026-04-07 21:15:53 +02:00
Dome a6b30c1b19 Refactor button styles and layout adjustments
Updated button styles for improved layout and transitions.
2026-04-07 21:08:06 +02:00
Dome 4a41e3f114 Update button styles for consistency and effects
Enhanced button styles with improved transitions and transformations.
2026-04-07 21:01:36 +02:00
Dome 106bece811 Update main.css 2026-04-07 20:53:19 +02:00
Dome ad022b9472 Update main.css 2026-04-07 20:45:31 +02:00
Dome da2f07783d Update main.css 2026-04-07 20:38:54 +02:00
Dome d11fd87696 Reduce margin for button styling 2026-04-07 20:31:35 +02:00
Dome 7cf278beb1 Adjust button margin in main.css 2026-04-07 20:23:59 +02:00
Dome ab2b2c940a Add responsive styles for PreviewView component 2026-04-07 20:17:42 +02:00
Dome 3ca9c00725 Refine styles for Store-Buttons in ClientView
Updated comment for clarity and adjusted margin.
2026-04-07 20:16:15 +02:00
Dome edd095efc4 Enhance CSS for mobile responsiveness and layout
Added mobile fixes and improved box-sizing to prevent horizontal scrolling.
2026-04-07 20:15:08 +02:00
Dome 5832cd0449 Add responsive styles for ClientView actions 2026-04-07 20:13:24 +02:00
Dome d821417a3f Update comments and improve instance handling 2026-04-07 19:55:13 +02:00
Dome 8a2bfff1d9 Update Element.js 2026-04-07 19:50:20 +02:00
Dome e6c548611b Delete images/favicon.png 2026-04-07 19:39:39 +02:00
Dome 47b834779a Update index.html 2026-04-07 19:39:19 +02:00
Dome eb0a7d4522 Update index.html 2026-04-07 19:36:20 +02:00
Dome 61ce605627 Add files via upload 2026-04-07 19:35:20 +02:00
Dome cda2271e9e Reload page after clearing preferences 2026-04-07 19:32:44 +02:00
Dome ef43b377c1 Update RootView.js 2026-04-07 19:29:21 +02:00
Dome 5605404bae Update RootView.js 2026-04-07 19:18:46 +02:00
Dome 211b14db78 Update RootView.js 2026-04-07 19:16:16 +02:00
Dome 160ddead30 Update main.css 2026-04-07 18:53:08 +02:00
Dome 2a3f7f2dec Update client.css 2026-04-07 18:44:47 +02:00
Dome 52dbfeadda Update preview.css 2026-04-07 18:44:27 +02:00
Dome 876b4c742c Update main.css 2026-04-07 18:44:10 +02:00
Dome 12040df452 Update client.css 2026-04-07 17:42:54 +02:00
Dome 80c28bc155 Refine avatar styles and update text appearance
Updated avatar styles and adjusted dimensions for a more modern look. Improved placeholder animations and refined text styles for better readability.
2026-04-07 17:42:40 +02:00
Dome dbae95dca4 Revamp CSS variables and styles for improved design 2026-04-07 17:42:25 +02:00
Dome 45681069cd Merge pull request #3 from matrix-org/main
sync to upstream
2026-04-07 17:28:24 +02:00
Michael Telatynski de7992d60c Merge pull request #394 from vorburger/patch-1 2026-01-02 14:58:25 +00:00
Michael Vorburger 88a7eb03a7 Update README.md
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2026-01-02 14:57:19 +01:00
Michael Vorburger 3474e341a5 docs: Simplified clarification re. Spaces on README
Based on https://github.com/matrix-org/matrix.to/pull/394#discussion_r2425555065 feedback.
2025-10-13 18:14:53 +02:00
Michael Vorburger 8e82c4687e docs: Clarify matrix.to URL scheme for Spaces on README (fixes #393) 2025-10-12 01:04:30 +02:00
Matthew Hodgson e8fcc3d2b9 Merge pull request #380 from bartvdbraak/patch-1
Add Blender Chat to Trusted Instances
2025-09-17 23:36:28 +01:00
Travis Ralston 2b44055047 Merge pull request #384 from matrix-org/travis/v12-v2
Fix capture group index on non-v12 rooms
2025-07-16 12:32:01 -06:00
Travis Ralston f8c30eabde regex is hard 2025-07-16 12:30:19 -06:00
Travis Ralston 3e521505c0 Merge pull request #383 from matrix-org/travis/v12
Support v12 room IDs
2025-07-16 12:24:39 -06:00
Travis Ralston 442ed2f1d1 Support v12 room IDs
MSC: https://github.com/matrix-org/matrix-spec-proposals/pull/4291
Pre-disclosure: https://matrix.org/blog/2025/07/security-predisclosure/
2025-07-16 12:09:19 -06:00
Bart van der Braak 26020e3e95 Add Blender Chat to Trusted Instances 2025-07-09 13:04:41 +02:00
Dome 28d77a898c Merge pull request #2 from matrix-org/main
Change the Element mobile download links to Element X. (#377)
2025-04-17 17:29:38 +02:00
Doug 5b4b57306b Change the Element mobile download links to Element X. (#377)
* Change the Element mobile download links to Element X.

* retrigger checks

---------

Co-authored-by: David Langley <davidl@element.io>
2025-04-11 13:48:02 +01:00
Dome 2e4ce08874 Update Element.js 2025-03-29 00:42:39 +01:00
Dome b49724d83b Update Element.js 2025-03-29 00:35:20 +01:00
Dome fe21222d33 Merge pull request #1 from luelista/custom-web-instances
Custom web instances
2025-03-29 00:19:32 +01:00
Dome f1d33f3f63 Update README.md 2025-03-28 16:37:55 +01:00
Dome 1205705555 Update compose.yaml 2025-03-28 16:37:41 +01:00
Dome 4259af79bd Update README.md 2025-03-28 16:36:58 +01:00
Dome 05ebb904b2 Add files via upload 2025-03-28 16:24:32 +01:00
Dome 73200e8c27 Update compose.yaml 2025-03-28 16:23:32 +01:00
Dome 27ff8f19c7 Update Dockerfile 2025-03-28 16:23:19 +01:00
Dome 8124698775 Update README.md 2025-03-28 16:22:52 +01:00
Dome 58890a7c9d Update Dockerfile 2025-03-28 14:52:15 +01:00
Dome 521b2adf67 Update Dockerfile 2025-03-28 14:49:27 +01:00
Dome 84db9ecc9e Update Dockerfile 2025-03-28 14:48:24 +01:00
Dome b75ca64d22 Update README.md 2025-03-28 14:43:32 +01:00
Dome 8fee384fc0 Update compose.yaml 2025-03-28 14:43:16 +01:00
Dome 8c2db8fdc5 Update README.md 2025-03-28 14:41:22 +01:00
Dome bda105ceeb Update compose.yaml 2025-03-28 14:41:04 +01:00
Dome 4e4e034db2 Update Dockerfile 2025-03-28 14:40:47 +01:00
Dome b8047a7988 Update Dockerfile 2025-03-28 14:34:21 +01:00
Dome ef85f6782d Update Dockerfile 2025-03-28 14:33:46 +01:00
Dome 46a14f0e46 Update Dockerfile 2025-03-28 14:29:04 +01:00
Dome ed1230eee8 Update Dockerfile 2025-03-28 14:24:39 +01:00
Dome 334393b9de Update Dockerfile 2025-03-28 14:22:19 +01:00
Dome 684f3f70dd Update Dockerfile 2025-03-28 14:18:21 +01:00
Dome 70b5a4979d Update Dockerfile 2025-03-28 14:04:49 +01:00
Dome 1898808f79 Update README.md 2025-03-28 13:51:57 +01:00
Dome df2c426d76 Update compose.yaml 2025-03-28 13:49:34 +01:00
Dome f04e9484e7 Update Dockerfile 2025-03-28 13:45:30 +01:00
Dome 3fa1ad7269 Update README.md 2025-03-28 13:44:59 +01:00
Dome 66cc550949 Update Dockerfile 2025-03-28 13:38:35 +01:00
Dome 3a0359c1b6 Update compose.yaml 2025-03-28 13:32:08 +01:00
Dome b560e2dc1e Update Dockerfile 2025-03-28 13:27:33 +01:00
Mira Nord 6dd9a0213c Trim whitespace and protocol / path information 2025-03-16 21:40:52 +01:00
Mira Nord d993157cfa Add form to configure custom web instance 2025-03-16 21:36:47 +01:00
Mira Nord 06237b1b8b Add link to change custom web instance 2025-03-16 20:55:20 +01:00
Mira Nord 50a25dd04c Add preference for custom web instances, use it for Element 2025-03-16 20:54:38 +01:00
Mira Nord 22ad0e9289 Improve domain splitting for "Hosted by" header 2025-03-16 19:16:06 +01:00
14 changed files with 539 additions and 378 deletions
+3 -3
View File
@@ -1,9 +1,7 @@
name: Build and Push Matrix-to Docker Image name: Build and Push Matrix-to Docker Image
on: on:
push: workflow_dispatch: # Erlaubt den manuellen Start über das GitHub UI
branches:
- main
jobs: jobs:
build: build:
@@ -25,5 +23,7 @@ jobs:
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: .
push: true push: true
tags: domoel/matrix-to:latest tags: domoel/matrix-to:latest
no-cache: true
+18 -14
View File
@@ -1,19 +1,23 @@
# Verwenden Sie ein Node.js-Image als Basis # Stage 1: Build
FROM node:20-alpine FROM node:20.2-alpine AS build
# Setzen Sie das Arbeitsverzeichnis im Container
WORKDIR /app WORKDIR /app
# Kopieren Sie die package.json und yarn.lock Dateien und installieren Sie die Abhängigkeiten
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile RUN yarn install
# Kopieren Sie den Rest des Codes in das Arbeitsverzeichnis
COPY . . COPY . .
RUN yarn build
# Exponieren Sie den Port 5000 # Stage 2: Production
EXPOSE 5000 FROM nginx:alpine
WORKDIR /etc/nginx
COPY ./nginx.conf /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html
COPY --from=build /app/build .
# Starten Sie die Anwendung und setzen Sie die PORT-Umgebungsvariable auf 5000 # Expose port 80
ENV PORT=5000 EXPOSE 80
CMD ["yarn", "start"]
# Healthcheck
HEALTHCHECK CMD curl --fail http://localhost:80 || exit 1
# Start Nginx server
CMD ["nginx", "-g", "daemon off;"]
+4 -3
View File
@@ -56,6 +56,8 @@ The matrix.to URL scheme is
The #/ component is mandatory and exists to avoid leaking the target URL to the The #/ component is mandatory and exists to avoid leaking the target URL to the
server hosting matrix.to. server hosting matrix.to.
There is no _Entity type_ for **Spaces**, as they are technically just rooms.
Note that linking to rooms by ID should only be used for rooms to which the Note that linking to rooms by ID should only be used for rooms to which the
target user has been invited: these links cannot be assumed to work for all target user has been invited: these links cannot be assumed to work for all
visitors. visitors.
@@ -97,8 +99,7 @@ services:
container_name: Matrix-to container_name: Matrix-to
image: domoel/matrix-to:latest image: domoel/matrix-to:latest
ports: ports:
- "5000:5000" - "1336:80"
restart: unless-stopped
environment: environment:
- PORT=5000 - NODE_ENV=production
``` ```
+2 -3
View File
@@ -5,7 +5,6 @@ services:
container_name: Matrix-to container_name: Matrix-to
image: domoel/matrix-to:latest image: domoel/matrix-to:latest
ports: ports:
- "5000:5000" - "1336:80"
restart: unless-stopped
environment: environment:
- PORT=5000 - NODE_ENV=production
+106 -71
View File
@@ -1,98 +1,133 @@
.ClientListView h2 { .ClientListView h2 {
text-align: center; text-align: center;
margin: 18px 0; margin: 24px 0;
} font-weight: 600;
.ClientListView .filterOption {
display: flex;
align-items: center;
margin: 8px 0;
} }
.ClientView { .ClientView {
border: 1px solid #E6E6E6; background: var(--app-background);
border-radius: 8px; border: 1px solid var(--borders);
border-radius: 4px;
margin: 16px 0; margin: 16px 0;
padding: 16px; padding: 24px;
transition: all 0.3s ease;
border-left: 4px solid transparent;
display: block;
text-align: left;
}
.ClientView:hover {
background: #2f313d !important;
border-left-color: var(--ztfr-purple) !important;
transform: translateX(5px);
} }
.ClientView.isPreferred { .ClientView.isPreferred {
border: 3px solid var(--link); border-color: var(--ztfr-purple);
box-shadow: 0px 8px 4px rgba(0, 0, 0, 0.05); background: rgba(189, 147, 249, 0.05);
} }
.ClientView .hostedBanner { .ClientView .hostedBanner {
text-align: center; text-align: center;
margin-bottom: 29px; margin-bottom: 20px;
padding: 4px 0; padding: 6px 0;
line-height: 20px; border-radius: 4px;
border-radius: 8px;
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 13px;
background-color: var(--lightgrey); background-color: var(--ztfr-purple);
color: var(--app-background);
text-transform: uppercase;
} }
.ClientView .header { .ClientView .header { display: flex; align-items: flex-start; }
display: flex; .ClientView .description { flex: 1; }
} .ClientView h3 { margin: 0 0 8px 0; font-size: 18px; }
.ClientView .description p { margin: 0; font-size: 13px; color: var(--font); }
.ClientView .description {
flex: 1;
}
.ClientView h3 {
margin-top: 0;
}
.ClientView .clientIcon { .ClientView .clientIcon {
border-radius: 8px; border-radius: 4px;
background-repeat: no-repeat; background-color: #ffffff;
background-size: cover; padding: 4px;
width: 60px; width: 50px;
height: 60px; height: 50px;
overflow: hidden;
display: block;
margin-left: 16px; margin-left: 16px;
flex-shrink: 0;
} }
.ClientView .platforms { .ClientView .platforms {
background-image: url('../images/platform-icon.svg'); margin-top: 12px;
background-repeat: no-repeat; font-size: 12px;
background-position: 0 center; color: var(--grey);
padding-left: 28px; opacity: 0.8;
}
.ClientView .actions a.badge {
display: inline-block;
height: 40px;
margin: 8px 16px 8px 0;
}
.ClientView .actions img {
height: 100%;
}
.ClientView .back {
margin-top: 22px;
}
.InstallClientView .instructions button {
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
padding: 4px;
border: none;
width: 24px;
height: 24px;
margin: 8px;
vertical-align: middle;
}
.InstallClientView .instructions button.copy {
background-image: url('../images/copy.svg');
} }
.InstallClientView .instructions button.tick { .InstallClientView .instructions button.tick {
background-image: url('../images/tick-dark.svg'); filter: invert(1); /* Macht schwarze Icons weiß */
} }
.ClientView .back {
display: block;
margin-top: 20px;
font-size: 12px;
color: var(--grey);
}
.ClientView .actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.ClientView .actions a.badge {
height: auto !important;
max-width: 100%;
display: block;
}
.ClientView .actions img {
max-width: 160px;
height: auto;
display: block;
margin: 5px auto;
}
/* --- ANPASSUNG FÜR DEN DEZENTEN LOOK --- */
.ClientView .actions .footer {
display: block !important; /* Kein Flex mehr, damit Links wie Text fließen */
text-align: center !important;
margin-top: 20px !important;
width: 100%;
line-height: 1.8 !important; /* Gibt den Zeilen etwas Platz */
color: var(--sub-text);
}
/* Der "Use Custom Web Instance" Bereich bekommt eine eigene Zeile */
.ClientView .actions .footer .custom {
display: block !important;
margin-top: 6px !important;
}
/* Sicherstellen, dass der "Change" Button im Textfluss bleibt */
.ClientView .actions .footer button.change {
display: inline !important;
margin: 0 4px !important;
}
@media screen and (max-width: 480px) {
.ClientView {
padding: 15px;
margin: 8px 0;
}
.ClientView .header {
flex-direction: column;
align-items: center;
text-align: center;
}
.ClientView .clientIcon {
margin: 0 0 15px 0;
}
}
+168 -157
View File
@@ -1,17 +1,6 @@
/* /*
Copyright 2020 The Matrix.org Foundation C.I.C. Copyright 2020 The Matrix.org Foundation C.I.C.
Modified 2026 for Zeitfresser Matrix Community Look & Mobile Fixes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
@import url('spinner.css'); @import url('spinner.css');
@@ -20,57 +9,63 @@ limitations under the License.
@import url('create.css'); @import url('create.css');
@import url('open.css'); @import url('open.css');
/* Globaler Fix für Box-Berechnungen */
* {
box-sizing: border-box;
}
:root { :root {
--app-background: #f4f4f4; --app-background: #1e1f29; /* Tief-Anthrazit */
--background: #ffffff; --background: #282a36; /* Card Hintergrund */
--foreground: #000000; --foreground: #f7f7fa; /* Weiß */
--font: #333333; --font: #bdc3c7; /* Grau */
--grey: #666666; --grey: #64748b; /* Dunkles Grau */
--accent: #0098d4; --accent: #0dbd8b; /* Matrix-Green */
--error: #d6001c; --ztfr-purple: #bd93f9; /* Zeitfresser-Lila */
--link: #0098d4; --error: #ff5555;
--borders: #f4f4f4; --link: #f7f7fa;
--lightgrey: #E6E6E6; --borders: #2f313d;
--lightgrey: #383a59;
--spinner-stroke-size: 2px; --spinner-stroke-size: 2px;
--sub-text: #bdc3c7;
} }
html { html {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%;
overflow-x: hidden;
} }
body { body {
background-color: var(--app-background); background-color: var(--app-background);
background-image: url('../images/background.svg'); background-image: none !important;
background-attachment: fixed; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-repeat: no-repeat;
background-size: auto;
background-position: center -50px;
height: 100%;
width: 100%;
font-size: 14px;
color: var(--font); color: var(--font);
padding: 120px 0 0 0; padding: 80px 20px 0 20px;
margin: 0; margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
} }
noscript { h1, h2, h3 {
display: block; color: var(--foreground);
padding: 20px; letter-spacing: -0.5px;
word-wrap: break-word;
} }
p { line-height: 150%; } p {
a { text-decoration: none; } line-height: 1.7;
word-wrap: break-word;
}
h1 { font-size: 24px; } a { text-decoration: none; color: var(--link); }
h2 { font-size: 21px; }
h3 { font-size: 16px; }
body, body, button, input, textarea {
button, font-size: 14px;
input,
textarea {
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal; font-style: normal;
} }
@@ -78,13 +73,14 @@ button, input[type=submit] {
cursor: pointer; cursor: pointer;
} }
button, input { /* Die zentrale Kachel */
font-size: inherit; .card {
font-weight: inherit; background-color: var(--background);
} border-radius: 4px;
border-left: 4px solid var(--ztfr-purple);
input[type="checkbox"], input[type="radio"] { box-shadow: 0px 20px 40px rgba(0, 0, 0, 0.3);
margin: 0 8px 0 0; padding: 2.5rem;
width: 100%;
} }
.RootView { .RootView {
@@ -93,131 +89,146 @@ input[type="checkbox"], input[type="radio"] {
width: 100%; width: 100%;
} }
.card { /* --- GROSSE BUTTONS (z.B. Download, Continue) --- */
background-color: var(--background);
border-radius: 16px;
box-shadow: 0px 18px 24px rgba(0, 0, 0, 0.06);
}
.card, .footer {
padding: 2rem;
}
.hidden {
display: none !important;
}
@media screen and (max-width: 480px) {
body {
background-image: none;
background-color: var(--background);
padding: 0;
}
.card {
border-radius: unset;
box-shadow: unset;
}
}
.footer .links li:not(:first-child) {
margin-left: 0.5em;
}
.footer .links li:not(:first-child)::before {
content: "·";
margin-right: 0.5em;
}
.footer .links li {
display: inline-block;
}
.footer .links {
font-size: 12px;
list-style: none;
padding: 0;
}
a, button.text {
color: var(--link);
}
button.text {
background: none;
border: none;
font-style: normal;
font-weight: normal;
font-size: inherit;
padding: 8px 0;
margin: -8px 0;
}
button.text:hover {
cursor: pointer;
}
.primary, .secondary { .primary, .secondary {
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
padding: 12px 8px; padding: 14px 20px;
margin: 8px 0; margin: 4px 0 !important;
} display: block;
text-transform: uppercase;
letter-spacing: 1px;
width: 100%;
.secondary { /* Simplifizierung: Nur Farbe, keine Bewegung */
background: var(--background); transition: background-color 0.2s ease, filter 0.2s ease;
color: var(--link); transform: none !important;
border: 1px solid var(--link); -webkit-font-smoothing: antialiased;
border-radius: 32px;
} }
.primary { .primary {
background: var(--link); background: var(--foreground);
color: var(--background); color: var(--app-background) !important;
border-radius: 32px; border-radius: 4px;
}
.primary.icon, .secondary.icon {
background-repeat: no-repeat;
background-position: 12px center;
}
.icon.link { background-image: url('../images/link.svg'); }
.icon.tick { background-image: url('../images/tick.svg'); }
.icon.copy { background-image: url('../images/copy.svg'); }
button.primary, input[type='submit'].primary, button.secondary, input[type='submit'].secondary {
border: none; border: none;
font-size: inherit;
} }
.primary:hover {
filter: brightness(0.9);
}
.secondary {
background: transparent;
color: var(--foreground);
border: 1px solid var(--foreground);
border-radius: 4px;
}
.secondary:hover {
background: rgba(255,255,255,0.05);
}
/* --- DEZENTE BUTTONS (Change, Custom Instance, Footer-Style) --- */
.ClientListView button.change,
.PreviewView button.change,
.ClientView button.custom,
.ClientView .footer button,
.CustomInstanceView .actions button.secondary,
.footer button,
.footer a {
background: none !important;
border: none !important;
color: var(--grey) !important;
padding: 0 !important;
margin: 0 !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: normal !important;
text-transform: none !important;
letter-spacing: normal !important;
cursor: pointer !important;
display: inline !important;
width: auto !important;
border-bottom: 1px solid rgba(189, 195, 199, 0.3) !important;
transition: all 0.3s ease !important;
}
.ClientListView button.change:hover,
.PreviewView button.change:hover,
.ClientView button.custom:hover,
.ClientView .footer button:hover,
.CustomInstanceView .actions button.secondary:hover,
.footer button:hover,
.footer a:hover {
color: var(--foreground) !important;
border-bottom-color: var(--ztfr-purple) !important;
}
/* Fix für Button-Container */
.actions,
.ClientView .actions,
.ClientListView .actions,
.CustomInstanceView .actions {
display: flex !important;
flex-direction: column !important;
gap: 0px !important;
}
.actions > *:last-child {
margin-bottom: 0 !important;
}
/* Input Felder */
input[type='text'].large { input[type='text'].large {
width: 100%; width: 100%;
padding: 12px; padding: 14px;
background: var(--background); background: #1a1a1a !important;
border: 1px solid var(--foreground); border: 1px solid var(--borders);
border-radius: 16px; border-radius: 4px;
font-size: 14px; font-size: 14px;
} color: var(--foreground);
.fullwidth {
display: block;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 10px;
} }
.LoadServerPolicyView { .footer {
display: flex; margin-top: 60px;
padding: 40px 20px;
text-align: center;
font-size: 0.85rem;
color: var(--sub-text);
opacity: 0.7;
border-top: 1px solid rgba(255, 255, 255, 0.05);
width: 100%;
} }
.LoadServerPolicyView .spinner { .footer ul { list-style: none; padding: 0; margin: 0; }
width: 32px; .footer li { display: inline; margin: 0; }
height: 32px;
margin-right: 12px; @media screen and (max-width: 480px) {
body { padding: 40px 10px 0 10px; }
.card { padding: 1.5rem; border-radius: 2px; }
h1 { font-size: 1.5rem; }
} }
.LoadServerPolicyView h2 { /* --- FIX FÜR NUTZERPROFILE (null-Werte verstecken) --- */
margin-top: 0;
.PreviewView .members[data-count="null"],
.PreviewView .members:empty {
display: none !important;
}
.PreviewView .topic:empty,
.PreviewView .topic:contains("null") {
display: none !important;
}
.PreviewView p:empty,
.PreviewView span:empty {
display: none !important;
}
.PreviewView h2 + p {
margin-bottom: 0 !important;
} }
+54 -89
View File
@@ -4,130 +4,95 @@
} }
.PreviewView h1 { .PreviewView h1 {
font-size: 24px; font-size: 26px;
line-height: 32px; line-height: 1.2;
margin-bottom: 8px; margin-bottom: 8px;
word-wrap: anywhere; font-weight: 700;
} }
.PreviewView .avatarContainer { .PreviewView .avatarContainer {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: 0; margin-bottom: 20px;
} }
.PreviewView .avatar { .PreviewView .avatar {
border-radius: 100%; border-radius: 8px; /* Eckig */
width: 64px; width: 80px;
height: 64px; height: 80px;
} border: 3px solid var(--borders);
object-fit: cover;
.PreviewView .mxSpace .avatar {
border-radius: 12px;
} }
.PreviewView .defaultAvatar { .PreviewView .defaultAvatar {
width: 64px; width: 80px;
height: 64px; height: 80px;
border-radius: 8px;
background-color: var(--lightgrey);
background-image: url('../images/chat-icon.svg'); background-image: url('../images/chat-icon.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: 85%; opacity: 0.6;
}
.PreviewView .spinner {
width: 32px;
height: 32px;
}
.PreviewView .avatar.loading {
border: 1px solid #eee;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
} }
.PreviewView .identifier { .PreviewView .identifier {
color: var(--grey); color: var(--ztfr-purple);
font-size: 12px; font-family: monospace;
font-size: 13px;
margin: 8px 0; margin: 8px 0;
} letter-spacing: 0.5px;
.PreviewView .identifier.placeholder {
height: 1em;
margin: 1em 30%;
}
.PreviewView .memberCount {
display: flex;
justify-content: center;
margin: 8px 0;
}
.PreviewView .memberCount.loading {
margin: 16px 0;
}
.PreviewView .memberCount p {
font-size: 12px;
margin: 0;
} }
.PreviewView .memberCount p:not(.placeholder) { .PreviewView .memberCount p:not(.placeholder) {
padding: 4px 8px 4px 24px; padding: 6px 12px 6px 28px;
border-radius: 14px; border-radius: 4px;
color: var(--foreground);
background-image: url(../images/member-icon.svg); background-image: url(../images/member-icon.svg);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 2px center; background-position: 8px center;
background-color: var(--lightgrey); background-color: var(--lightgrey);
} display: inline-block;
font-size: 12px;
.PreviewView .memberCount p.placeholder {
height: 1.5em;
width: 100px;
} }
.PreviewView .topic { .PreviewView .topic {
font-size: 12px; font-size: 14px;
color: var(--grey); line-height: 1.6;
margin: 32px 0; color: var(--font);
} margin: 24px 0;
.PreviewView .topic.loading {
display: block;
margin: 24px 12px;
padding: 4px 0;
}
.PreviewView .topic.loading .placeholder {
height: 0.8em;
display: block;
margin: 12px 0;
}
.PreviewView .topic.loading .placeholder:nth-child(2) {
margin-left: 5%;
margin-right: 5%;
} }
/* Dark Mode Placeholders */
.placeholder { .placeholder {
border-radius: 1em; border-radius: 4px;
--flash-bg: #ddd; --flash-bg: #2f313d;
--flash-fg: #eee; --flash-fg: #383a59;
background: linear-gradient(120deg, background: linear-gradient(120deg, var(--flash-bg), var(--flash-bg) 10%, var(--flash-fg) calc(10% + 25px), var(--flash-bg) calc(10% + 50px));
var(--flash-bg),
var(--flash-bg) 10%,
var(--flash-fg) calc(10% + 25px),
var(--flash-bg) calc(10% + 50px)
);
animation: flash 2s linear infinite; animation: flash 2s linear infinite;
background-size: 200%; background-size: 200%;
} }
@keyframes flash { @keyframes flash {
0% { background-position-x: 0; } 0% { background-position-x: 0; }
50% { background-position-x: -80%; } 100% { background-position-x: -200%; }
51% { background-position-x: 80%; } }
100% { background-position-x: 0%; }
.PreviewView h1,
.PreviewView .identifier,
.PreviewView .topic {
word-wrap: break-word; /* Bricht extrem lange Wörter/IDs um */
overflow-wrap: break-word;
max-width: 100%;
}
@media screen and (max-width: 480px) {
.PreviewView .avatar,
.PreviewView .defaultAvatar {
width: 64px;
height: 64px;
}
.PreviewView h1 {
font-size: 20px; /* Titel etwas dezenter mobil */
}
} }
+34
View File
@@ -0,0 +1,34 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
+10 -3
View File
@@ -18,7 +18,7 @@ import {createEnum} from "./utils/enum.js";
import {orderedUnique} from "./utils/unique.js"; import {orderedUnique} from "./utils/unique.js";
const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/; const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/;
const ROOMID_PATTERN = /^!([^:]*):(.+)$/; const ROOMID_PATTERN = /^!([^:]*)(:(.+))?$/; // As of room version 12, room IDs don't have domains
const USERID_PATTERN = /^@([^:]+):(.+)$/; const USERID_PATTERN = /^@([^:]+):(.+)$/;
const EVENTID_PATTERN = /^$([^:]+):(.+)$/; const EVENTID_PATTERN = /^$([^:]+):(.+)$/;
const GROUPID_PATTERN = /^\+([^:]+):(.+)$/; const GROUPID_PATTERN = /^\+([^:]+):(.+)$/;
@@ -152,7 +152,7 @@ export class Link {
} }
matches = ROOMID_PATTERN.exec(identifier); matches = ROOMID_PATTERN.exec(identifier);
if (matches) { if (matches) {
const server = matches[2]; const server = matches[3]; // group 2 is an optional over `:domain`, group 3 is just `domain`
const localPart = matches[1]; const localPart = matches[1];
return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, webInstances, eventId); return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, webInstances, eventId);
} }
@@ -166,12 +166,19 @@ export class Link {
} }
constructor(clientId, viaServers, identifierKind, localPart, server, webInstances, eventId) { constructor(clientId, viaServers, identifierKind, localPart, server, webInstances, eventId) {
const servers = [server]; const servers = [];
if (server !== undefined) {
servers.push(server); // v12 rooms don't have domains, and therefore no server
}
servers.push(...viaServers); servers.push(...viaServers);
this.webInstances = webInstances; this.webInstances = webInstances;
this.servers = orderedUnique(servers); this.servers = orderedUnique(servers);
this.identifierKind = identifierKind; this.identifierKind = identifierKind;
if (identifierKind === IdentifierKind.RoomId && !server) {
this.identifier = `${asPrefix(identifierKind)}${localPart}`;
} else {
this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`; this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`;
}
this.eventId = eventId; this.eventId = eventId;
this.clientId = clientId; this.clientId = clientId;
} }
+18 -1
View File
@@ -25,6 +25,7 @@ export class Preferences extends EventEmitter {
// used to differentiate web from native if a client supports both // used to differentiate web from native if a client supports both
this.platform = null; this.platform = null;
this.homeservers = null; this.homeservers = null;
this.customWebInstances = {};
const prefsStr = localStorage.getItem("preferred_client"); const prefsStr = localStorage.getItem("preferred_client");
if (prefsStr) { if (prefsStr) {
@@ -36,6 +37,10 @@ export class Preferences extends EventEmitter {
if (serversStr) { if (serversStr) {
this.homeservers = JSON.parse(serversStr); this.homeservers = JSON.parse(serversStr);
} }
const customWebInstancesStr = localStorage.getItem("custom_web_instances");
if (customWebInstancesStr) {
this.customWebInstances = JSON.parse(customWebInstancesStr);
}
} }
setClient(id, platform) { setClient(id, platform) {
@@ -54,15 +59,27 @@ export class Preferences extends EventEmitter {
} }
} }
setCustomWebInstance(client_id, instance_url) {
this.customWebInstances[client_id] = instance_url;
this._localStorage.setItem("custom_web_instances", JSON.stringify(this.customWebInstances));
this.emit("canClear");
}
getCustomWebInstance(client_id) {
return this.customWebInstances[client_id];
}
clear() { clear() {
this._localStorage.removeItem("preferred_client"); this._localStorage.removeItem("preferred_client");
this._localStorage.removeItem("consented_servers"); this._localStorage.removeItem("consented_servers");
this._localStorage.removeItem("custom_web_instances");
this.clientId = null; this.clientId = null;
this.platform = null; this.platform = null;
this.homeservers = null; this.homeservers = null;
this.customWebInstances = {};
} }
get canClear() { get canClear() {
return !!this.clientId || !!this.platform || !!this.homeservers; return !!this.clientId || !!this.platform || !!this.homeservers || !!this.customWebInstances;
} }
} }
+8 -8
View File
@@ -30,14 +30,14 @@ export class RootView extends TemplateView {
t.mapView(vm => vm.createLinkViewModel, vm => vm ? new CreateLinkView(vm) : null), t.mapView(vm => vm.createLinkViewModel, vm => vm ? new CreateLinkView(vm) : null),
t.mapView(vm => vm.loadServerPolicyViewModel, vm => vm ? new LoadServerPolicyView(vm) : null), t.mapView(vm => vm.loadServerPolicyViewModel, vm => vm ? new LoadServerPolicyView(vm) : null),
t.div({className: "footer"}, [ t.div({className: "footer"}, [
t.p(t.img({src: "images/matrix-logo.svg"})), t.p([
t.p(["This invite uses ", externalLink(t, "https://matrix.org", "Matrix"), ", an open network for secure, decentralized communication."]), "© 2026 ",
t.ul({className: "links"}, [ t.a({href: "https://ztfr.eu"}, "Zeitfresser"),
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to", "GitHub project")), " | Powered by ",
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to/tree/main/src/open/clients", "Add your app")), externalLink(t, "https://github.com/matrix-org/matrix.to", "Matrix-to")
t.li({className: {hidden: vm => !vm.hasPreferences}}, ]),
t.button({className: "text", onClick: () => vm.clearPreferences()}, "Clear preferences")), t.p({className: {hidden: vm => !vm.hasPreferences}}, [
t.li(t.a({href: "#/disclaimer/"}, "Disclaimer")), t.button({className: "text", onClick: () => { vm.clearPreferences(); location.reload(); }}, "Clear preferences")
]) ])
]) ])
]); ]);
+47
View File
@@ -39,6 +39,14 @@ function renderInstructions(parts) {
export class ClientView extends TemplateView { export class ClientView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.mapView(vm => vm.customWebInstanceFormOpen, open => {
switch (open) {
case true: return new SetCustomWebInstanceView(vm);
case false: return new TemplateView(vm, t => this.renderContent(t, vm));
}
});
}
renderContent(t, vm) {
return t.div({className: {"ClientView": true, "isPreferred": vm => vm.hasPreferredWebInstance}}, [ return t.div({className: {"ClientView": true, "isPreferred": vm => vm.hasPreferredWebInstance}}, [
... vm.hasPreferredWebInstance ? [t.div({className: "hostedBanner"}, vm.hostedByBannerLabel)] : [], ... vm.hasPreferredWebInstance ? [t.div({className: "hostedBanner"}, vm.hostedByBannerLabel)] : [],
t.div({className: "header"}, [ t.div({className: "header"}, [
@@ -112,10 +120,49 @@ class InstallClientView extends TemplateView {
} }
} }
export class SetCustomWebInstanceView extends TemplateView {
render(t, vm) {
return t.div({className: "SetCustomWebInstanceView"}, [
t.p([
"Use a custom web instance for the ", t.strong(vm.name), " client:",
]),
t.form({action: "#", id: "setCustomWebInstanceForm", onSubmit: evt => this._onSubmit(evt)}, [
t.input({
type: "text",
className: "fullwidth large",
placeholder: "chat.example.org",
name: "instanceHostname",
value: vm.preferredWebInstance || "",
}),
t.input({type: "submit", value: "Save", className: "primary fullwidth"}),
t.input({type: "button", value: "Use Default Instance", className: "secondary fullwidth", onClick: evt => this._onReset(evt)}),
])
]);
}
_onSubmit(evt) {
evt.preventDefault();
const form = evt.target;
const {instanceHostname} = form.elements;
this.value.setCustomWebInstance(instanceHostname.value);
this.value.closeCustomWebInstanceForm();
}
_onReset(evt) {
this.value.setCustomWebInstance(undefined);
this.value.closeCustomWebInstanceForm();
}
}
function showBack(t, vm) { function showBack(t, vm) {
return t.p({className: {caption: true, "back": true, hidden: vm => !vm.showBack}}, [ return t.p({className: {caption: true, "back": true, hidden: vm => !vm.showBack}}, [
`Continue with ${vm.name} · `, `Continue with ${vm.name} · `,
t.button({className: "text", onClick: () => vm.back()}, "Change"), t.button({className: "text", onClick: () => vm.back()}, "Change"),
t.span({hidden: vm => !vm.supportsCustomWebInstances}, [
' · ',
t.button({className: "text", onClick: () => vm.configureCustomWebInstance()}, "Use Custom Web Instance"),
])
]); ]);
} }
+46 -13
View File
@@ -35,6 +35,7 @@ export class ClientViewModel extends ViewModel {
this._pickClient = pickClient; this._pickClient = pickClient;
// to provide "choose other client" button after calling pick() // to provide "choose other client" button after calling pick()
this._clientListViewModel = null; this._clientListViewModel = null;
this.customWebInstanceFormOpen = false;
this._update(); this._update();
} }
@@ -59,11 +60,11 @@ export class ClientViewModel extends ViewModel {
if (this._proposedPlatform === this._nativePlatform) { if (this._proposedPlatform === this._nativePlatform) {
deepLinkLabel = "Open in app"; deepLinkLabel = "Open in app";
} else { } else {
deepLinkLabel = `Open on ${this._client.getPreferredWebInstance(this._link)}`; deepLinkLabel = `Open on ${this.preferredWebInstance}`;
} }
} }
const actions = []; const actions = [];
const proposedDeepLink = this._client.getDeepLink(this._proposedPlatform, this._link); const proposedDeepLink = this._client.getDeepLink(this._proposedPlatform, this._link, this.preferredWebInstance);
if (proposedDeepLink) { if (proposedDeepLink) {
actions.push({ actions.push({
label: deepLinkLabel, label: deepLinkLabel,
@@ -83,8 +84,8 @@ export class ClientViewModel extends ViewModel {
// show only if there is a preferred instance, and if we don't already link to it in the first button // show only if there is a preferred instance, and if we don't already link to it in the first button
if (hasPreferredWebInstance && this._webPlatform && this._proposedPlatform !== this._webPlatform) { if (hasPreferredWebInstance && this._webPlatform && this._proposedPlatform !== this._webPlatform) {
actions.push({ actions.push({
label: `Open on ${this._client.getPreferredWebInstance(this._link)}`, label: `Open on ${this.preferredWebInstance}`,
url: this._client.getDeepLink(this._webPlatform, this._link), url: this._client.getDeepLink(this._webPlatform, this._link, this.preferredWebInstance),
kind: "open-in-web", kind: "open-in-web",
activated: () => {} // don't persist this choice as we don't persist the preferred web instance, it's in the url activated: () => {} // don't persist this choice as we don't persist the preferred web instance, it's in the url
}); });
@@ -108,10 +109,10 @@ export class ClientViewModel extends ViewModel {
actions.push(...nativeActions); actions.push(...nativeActions);
} }
if (this._webPlatform) { if (this._webPlatform) {
const webDeepLink = this._client.getDeepLink(this._webPlatform, this._link); const webDeepLink = this._client.getDeepLink(this._webPlatform, this._link, this.preferredWebInstance);
if (webDeepLink) { if (webDeepLink) {
const webLabel = this.hasPreferredWebInstance ? const webLabel = this.hasPreferredWebInstance ?
`Open on ${this._client.getPreferredWebInstance(this._link)}` : `Open on ${this.preferredWebInstance}` :
`Continue in your browser`; `Continue in your browser`;
actions.push({ actions.push({
label: webLabel, label: webLabel,
@@ -128,18 +129,26 @@ export class ClientViewModel extends ViewModel {
return actions; return actions;
} }
get hasPreferredWebInstance() { get preferredWebInstance() {
// also check there is a web platform that matches the platforms the user is on (mobile or desktop web) // also check there is a web platform that matches the platforms the user is on (mobile or desktop web)
return this._webPlatform && typeof this._client.getPreferredWebInstance(this._link) === "string"; if (!this._webPlatform) return undefined;
return (
this.preferences.getCustomWebInstance(this._client.id)
|| this._client.getPreferredWebInstance(this._link)
);
}
get hasPreferredWebInstance() {
return typeof this.preferredWebInstance === "string";
} }
get hostedByBannerLabel() { get hostedByBannerLabel() {
const preferredWebInstance = this._client.getPreferredWebInstance(this._link); if (this.hasPreferredWebInstance) {
if (this._webPlatform && preferredWebInstance) { const preferredWebInstance = this.preferredWebInstance;
let label = preferredWebInstance; let label = preferredWebInstance;
const subDomainIdx = preferredWebInstance.lastIndexOf(".", preferredWebInstance.lastIndexOf(".")); const subDomainIdx = preferredWebInstance.lastIndexOf(".", preferredWebInstance.lastIndexOf(".") - 1);
if (subDomainIdx !== -1) { if (subDomainIdx !== -1) {
label = preferredWebInstance.slice(preferredWebInstance.length - subDomainIdx + 1); label = preferredWebInstance.slice(subDomainIdx + 1);
} }
return `Hosted by ${label}`; return `Hosted by ${label}`;
} }
@@ -188,7 +197,7 @@ export class ClientViewModel extends ViewModel {
get showDeepLinkInInstall() { get showDeepLinkInInstall() {
// we can assume this._nativePlatform as this._clientCanIntercept already checks it // we can assume this._nativePlatform as this._clientCanIntercept already checks it
return this._clientCanIntercept && !!this._client.getDeepLink(this._nativePlatform, this._link); return this._clientCanIntercept && !!this._client.getDeepLink(this._nativePlatform, this._link, this.preferredWebInstance);
} }
get availableOnPlatformNames() { get availableOnPlatformNames() {
@@ -223,6 +232,10 @@ export class ClientViewModel extends ViewModel {
return !!this._clientListViewModel; return !!this._clientListViewModel;
} }
get supportsCustomWebInstances() {
return !!this._client.supportsCustomInstances;
}
back() { back() {
if (this._clientListViewModel) { if (this._clientListViewModel) {
const vm = this._clientListViewModel; const vm = this._clientListViewModel;
@@ -231,9 +244,29 @@ export class ClientViewModel extends ViewModel {
// in the list with all clients, and also if we refresh, we get the list with // in the list with all clients, and also if we refresh, we get the list with
// all clients rather than having our "change client" click reverted. // all clients rather than having our "change client" click reverted.
this.preferences.setClient(undefined, undefined); this.preferences.setClient(undefined, undefined);
this.preferences.setCustomWebInstance(this._client.id, undefined);
this._update(); this._update();
this.emitChange(); this.emitChange();
vm.showAll(); vm.showAll();
} }
} }
configureCustomWebInstance() {
this.customWebInstanceFormOpen = true;
this.emitChange();
}
closeCustomWebInstanceForm() {
this.customWebInstanceFormOpen = false;
this.emitChange();
}
setCustomWebInstance(hostname) {
if (hostname) {
hostname = hostname.trim().replace(/^https:\/\//, '').replace(/\/.*$/, '');
}
this.preferences.setClient(this._client.id, hostname ? this._webPlatform : (this._nativePlatform || this._webPlatform));
this.preferences.setCustomWebInstance(this._client.id, hostname || undefined);
this._update();
}
} }
+16 -8
View File
@@ -18,13 +18,15 @@ import {Maturity, Platform, LinkKind,
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js"; FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
const trustedWebInstances = [ const trustedWebInstances = [
"app.element.io", // first one is the default one "chat.ztfr.eu", // Zeitfresser ist der gesetzte Standard
"app.element.io",
"develop.element.io", "develop.element.io",
"chat.fedoraproject.org", "chat.fedoraproject.org",
"chat.fosdem.org", "chat.fosdem.org",
"chat.mozilla.org", "chat.mozilla.org",
"webchat.kde.org", "webchat.kde.org",
"app.gitter.im", "app.gitter.im",
"chat.blender.org",
]; ];
/** /**
@@ -56,8 +58,9 @@ export class Element {
get homepage() { return "https://element.io"; } get homepage() { return "https://element.io"; }
get author() { return "Element"; } get author() { return "Element"; }
getMaturity(platform) { return Maturity.Stable; } getMaturity(platform) { return Maturity.Stable; }
get supportsCustomInstances() { return true; }
getDeepLink(platform, link) { getDeepLink(platform, link, preferredWebInstance) {
let fragmentPath; let fragmentPath;
switch (link.kind) { switch (link.kind) {
case LinkKind.User: case LinkKind.User:
@@ -80,12 +83,15 @@ export class Element {
const isWebPlatform = platform === Platform.DesktopWeb || platform === Platform.MobileWeb; const isWebPlatform = platform === Platform.DesktopWeb || platform === Platform.MobileWeb;
if (isWebPlatform || platform === Platform.iOS) { if (isWebPlatform || platform === Platform.iOS) {
// Standardmäßig deine Instanz nehmen
let instanceHost = trustedWebInstances[0]; let instanceHost = trustedWebInstances[0];
// we use app.element.io which iOS will intercept, but it likely won't intercept any other trusted instances
// so only use a preferred web instance for true web links. // Falls der Nutzer über den "Change"-Dialog eine bevorzugte Instanz
if (isWebPlatform && trustedWebInstances.includes(link.webInstances[this.id])) { // oder eine Custom-URL gewählt hat, nutzen wir diese:
instanceHost = link.webInstances[this.id]; if (isWebPlatform && preferredWebInstance) {
instanceHost = preferredWebInstance;
} }
return `https://${instanceHost}/#/${fragmentPath}`; return `https://${instanceHost}/#/${fragmentPath}`;
} else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) { } else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) {
return `element://vector/webapp/#/${fragmentPath}`; return `element://vector/webapp/#/${fragmentPath}`;
@@ -98,8 +104,8 @@ export class Element {
getCopyString(platform, link) {} getCopyString(platform, link) {}
getInstallLinks(platform) { getInstallLinks(platform) {
switch (platform) { switch (platform) {
case Platform.iOS: return [new AppleStoreLink('vector', 'id1083446067')]; case Platform.iOS: return [new AppleStoreLink('element-x-secure-chat-call', 'id1631335820')];
case Platform.Android: return [new PlayStoreLink('im.vector.app'), new FDroidLink('im.vector.app')]; case Platform.Android: return [new PlayStoreLink('io.element.android.x'), new FDroidLink('io.element.android.x')];
default: return [new WebsiteLink("https://element.io/download")]; default: return [new WebsiteLink("https://element.io/download")];
} }
} }
@@ -109,6 +115,8 @@ export class Element {
} }
getPreferredWebInstance(link) { getPreferredWebInstance(link) {
// Hier geben wir dem System die Erlaubnis, gespeicherte Präferenzen zu finden.
// Wenn keine da sind, greift oben automatisch trustedWebInstances[0].
const idx = trustedWebInstances.indexOf(link.webInstances[this.id]) const idx = trustedWebInstances.indexOf(link.webInstances[this.id])
return idx === -1 ? undefined : trustedWebInstances[idx]; return idx === -1 ? undefined : trustedWebInstances[idx];
} }