Merge pull request #219 from matrix-org/t3chguy/msc3266
This commit is contained in:
commit
c25a9dae4d
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
build
|
||||
*.tar.gz
|
||||
/.idea
|
||||
|
52
css/main.css
52
css/main.css
@ -21,31 +21,31 @@ limitations under the License.
|
||||
@import url('open.css');
|
||||
|
||||
:root {
|
||||
--app-background: #f4f4f4;
|
||||
--background: #ffffff;
|
||||
--foreground: #000000;
|
||||
--font: #333333;
|
||||
--grey: #666666;
|
||||
--accent: #0098d4;
|
||||
--error: #d6001c;
|
||||
--link: #0098d4;
|
||||
--borders: #f4f4f4;
|
||||
--app-background: #f4f4f4;
|
||||
--background: #ffffff;
|
||||
--foreground: #000000;
|
||||
--font: #333333;
|
||||
--grey: #666666;
|
||||
--accent: #0098d4;
|
||||
--error: #d6001c;
|
||||
--link: #0098d4;
|
||||
--borders: #f4f4f4;
|
||||
--lightgrey: #E6E6E6;
|
||||
--spinner-stroke-size: 2px;
|
||||
}
|
||||
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--app-background);
|
||||
background-image: url('../images/background.svg');
|
||||
background-color: var(--app-background);
|
||||
background-image: url('../images/background.svg');
|
||||
background-attachment: fixed;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto;
|
||||
background-position: center -50px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto;
|
||||
background-position: center -50px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
@ -89,12 +89,12 @@ input[type="checkbox"], input[type="radio"] {
|
||||
|
||||
.RootView {
|
||||
margin: 0 auto;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background);
|
||||
background-color: var(--background);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 18px 24px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
@ -104,20 +104,20 @@ input[type="checkbox"], input[type="radio"] {
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
body {
|
||||
background-image: none;
|
||||
background-color: var(--background);
|
||||
padding: 0;
|
||||
background-image: none;
|
||||
background-color: var(--background);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: unset;
|
||||
box-shadow: unset;
|
||||
border-radius: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ input[type="checkbox"], input[type="radio"] {
|
||||
}
|
||||
|
||||
a, button.text {
|
||||
color: var(--link);
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
button.text {
|
||||
|
@ -22,6 +22,10 @@
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.PreviewView .mxSpace .avatar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.PreviewView .defaultAvatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
@ -1,43 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 181.4 181.9" style="enable-background:new 0 0 181.4 181.9;" xml:space="preserve">
|
||||
viewBox="0 0 181.4 181.9" style="enable-background:new 0 0 181.4 181.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#F094BE;}
|
||||
.st2{fill:#4D3F92;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#F094BE;}
|
||||
.st2{fill:#4D3F92;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Capa_1">
|
||||
<rect x="0" y="0" style="color:#FFFFFF" width="181.4" height="181.9" class="st3"/>
|
||||
<rect x="0" y="0" style="color:#FFFFFF" width="181.4" height="181.9" class="st3"/>
|
||||
</g>
|
||||
<g id="Capa_2">
|
||||
<g>
|
||||
<path class="st2" d="M151.6,95.1c1.5-0.3,2.8-1,3.8-2c4-5.3,0.8-11.8-4.5-12.6c-0.8,0-1.5-0.8-1.5-1.5c0-0.3,0-0.5,0-0.5
|
||||
c0.8-0.8,1.5-1.8,2.5-3.3c8.1-10.8,11.8-50.6,3.8-53.7c-9.8-3.3-29.7,6.3-38.3,17.4c-0.5-0.3-1-1-1-1.8c0.3-3-1.3-5.5-3.5-6.8
|
||||
c-4.5-2.3-8.8,0-10.6,3.3c-0.5,0.8-1.3,1.3-2,1c-0.8,0-1.5-0.8-1.5-1.5c-0.5-2.5-2-4.5-4.3-5.5c-4.8-2-9.8,0.8-10.6,5.3
|
||||
c-0.3,0.8-0.8,1.5-1.5,1.5c-0.8,0.3-1.5-0.3-2-1c-1.5-2.3-4-3.8-6.5-3.8c-4,0-7.6,3.3-7.8,7.3v0.3v0.3c0,0.8-0.5,1.5-1,1.8h-0.3
|
||||
c-8.3-10.8-28.5-20.7-38.5-17.4c-8.1,2.8-4.3,42.6,4,53.4c1.5,2,2.8,3.5,3.8,4.5c-0.3,0.8-1,1.5-1.8,1.5c-1.3,0-2.5,0.5-3.5,1.3
|
||||
c-5.3,5-2.3,12.1,3,13.4c0.8,0.3,1.5,1,1.5,1.8c0,0.8-0.5,1.8-1.3,2c-1,0.5-2,1-2.8,2c-4,5.8,0,12.3,5.5,12.3
|
||||
c0.8,0,1.5,0.5,1.8,1.3c0.3,0.8,0.3,1.5-0.5,2c-1.5,1.5-2.3,3.5-2,5.5c0.3,2.8,2,5.3,4.8,6.5c1.5,0.8,3,0.8,4.5,0.5
|
||||
c0.8-0.3,1.5,0,2,0.8c0.5,0.5,0.5,1.5,0.3,2c-0.8,1.5-1,3.3-0.5,5c0.8,2.8,2.8,4.8,5.5,5.5c2.5,0.5,4.3-0.3,5.5-0.8
|
||||
c0.5-0.3-3.3,9.1-6,15.4c-0.8,2,1.3,4.3,3.5,3.3c8.3-3.8,22.2-10.3,22.2-9.8c0.5,5.3,6.5,9.1,12.3,5.3c1.3-0.8,2-2.3,2.3-3.5
|
||||
c0.3-0.8,1-1.5,2-1.5c1,0,1.8,0.5,2,1.5c0.3,1.3,0.8,2.3,1.8,3c5.8,4.5,12.3,0.8,12.8-4.8c0-0.8,0.5-1.5,1.3-1.8
|
||||
c0.8-0.3,1.5,0,2,0.5c1.5,1.5,3.3,2.5,5.3,2.5l0,0c2.5,0,5-1.3,6.5-3.8c1-1.5,1.3-3,1-5c0-0.8,0.3-1.5,0.8-2c0.5-0.5,1.5-0.5,2,0
|
||||
c1.5,0.8,3.3,1.3,5,0.8c2.8-0.5,5-2.8,5.8-5.3c0.5-1.8,0.3-3.5-0.5-5.3c-0.3-0.8-0.3-1.5,0.3-2s1.3-0.8,2-0.8
|
||||
c1.8,0.3,3.3,0.3,4.8-0.5c2.3-1,3.8-3,4.3-5.5c0.5-2.5-0.3-4.8-2-6.5c-0.5-0.5-0.8-1.3-0.5-2s1-1.3,1.8-1.3c1.8,0,3.8-0.5,5-2
|
||||
c4.3-4.5,2.3-10.6-2.5-12.6c-0.8-0.3-1.3-1-1.3-2C150.1,95.8,150.8,95.1,151.6,95.1z"/>
|
||||
<path class="st3" d="M131.4,42.2c0.5,1.5,0.5,3,0,4.5c-0.3,0.8,0,1.5,0.5,2s1.3,0.8,2,0.5c1-0.5,2-0.5,3-0.5c2.3,0,4.3,1,5.8,3
|
||||
c1,1.3,1.8,3,1.5,4.8c0,1.5-0.5,2.8-1.3,4c-0.5,0.5-0.5,1.5,0,2c0.3,0.3,0.5,0.8,1,0.8c1-0.3,2-1,2.8-2c4.5-6.3,5.3-26.2,0.8-27.7
|
||||
c-4.5-1.5-12.3,1.5-17.9,6C130.7,40.1,131.2,40.9,131.4,42.2z"/>
|
||||
<path class="st3" d="M39,63.6c0.3-0.3,0.5-0.5,0.8-0.8c0.5-0.8,0.3-1.5,0-2C38.5,59,38.2,57,38.5,55c0.5-2.8,2.8-5,5.5-5.8
|
||||
c1.5-0.5,3-0.3,4.5,0.3c0.8,0.3,1.5,0,2-0.5c0.5-0.5,0.8-1.3,0.5-2c-0.5-1.5-0.5-3,0-4.5c0.3-1,0.8-2,1.5-2.8
|
||||
c-5.5-4.5-13.9-7.8-18.4-6.3S30.4,54.8,35,61.1C36,62.6,37.2,63.3,39,63.6z"/>
|
||||
<g>
|
||||
<circle class="st3" cx="60.9" cy="94.6" r="9.3"/>
|
||||
<path class="st3" d="M100.7,94.6c0,5.3-4.3,9.3-9.3,9.3c-5.3,0-9.3-4.3-9.3-9.3S100.7,89.3,100.7,94.6z"/>
|
||||
<circle class="st3" cx="121.6" cy="94.6" r="9.3"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M151.6,95.1c1.5-0.3,2.8-1,3.8-2c4-5.3,0.8-11.8-4.5-12.6c-0.8,0-1.5-0.8-1.5-1.5c0-0.3,0-0.5,0-0.5
|
||||
c0.8-0.8,1.5-1.8,2.5-3.3c8.1-10.8,11.8-50.6,3.8-53.7c-9.8-3.3-29.7,6.3-38.3,17.4c-0.5-0.3-1-1-1-1.8c0.3-3-1.3-5.5-3.5-6.8
|
||||
c-4.5-2.3-8.8,0-10.6,3.3c-0.5,0.8-1.3,1.3-2,1c-0.8,0-1.5-0.8-1.5-1.5c-0.5-2.5-2-4.5-4.3-5.5c-4.8-2-9.8,0.8-10.6,5.3
|
||||
c-0.3,0.8-0.8,1.5-1.5,1.5c-0.8,0.3-1.5-0.3-2-1c-1.5-2.3-4-3.8-6.5-3.8c-4,0-7.6,3.3-7.8,7.3v0.3v0.3c0,0.8-0.5,1.5-1,1.8h-0.3
|
||||
c-8.3-10.8-28.5-20.7-38.5-17.4c-8.1,2.8-4.3,42.6,4,53.4c1.5,2,2.8,3.5,3.8,4.5c-0.3,0.8-1,1.5-1.8,1.5c-1.3,0-2.5,0.5-3.5,1.3
|
||||
c-5.3,5-2.3,12.1,3,13.4c0.8,0.3,1.5,1,1.5,1.8c0,0.8-0.5,1.8-1.3,2c-1,0.5-2,1-2.8,2c-4,5.8,0,12.3,5.5,12.3
|
||||
c0.8,0,1.5,0.5,1.8,1.3c0.3,0.8,0.3,1.5-0.5,2c-1.5,1.5-2.3,3.5-2,5.5c0.3,2.8,2,5.3,4.8,6.5c1.5,0.8,3,0.8,4.5,0.5
|
||||
c0.8-0.3,1.5,0,2,0.8c0.5,0.5,0.5,1.5,0.3,2c-0.8,1.5-1,3.3-0.5,5c0.8,2.8,2.8,4.8,5.5,5.5c2.5,0.5,4.3-0.3,5.5-0.8
|
||||
c0.5-0.3-3.3,9.1-6,15.4c-0.8,2,1.3,4.3,3.5,3.3c8.3-3.8,22.2-10.3,22.2-9.8c0.5,5.3,6.5,9.1,12.3,5.3c1.3-0.8,2-2.3,2.3-3.5
|
||||
c0.3-0.8,1-1.5,2-1.5c1,0,1.8,0.5,2,1.5c0.3,1.3,0.8,2.3,1.8,3c5.8,4.5,12.3,0.8,12.8-4.8c0-0.8,0.5-1.5,1.3-1.8
|
||||
c0.8-0.3,1.5,0,2,0.5c1.5,1.5,3.3,2.5,5.3,2.5l0,0c2.5,0,5-1.3,6.5-3.8c1-1.5,1.3-3,1-5c0-0.8,0.3-1.5,0.8-2c0.5-0.5,1.5-0.5,2,0
|
||||
c1.5,0.8,3.3,1.3,5,0.8c2.8-0.5,5-2.8,5.8-5.3c0.5-1.8,0.3-3.5-0.5-5.3c-0.3-0.8-0.3-1.5,0.3-2s1.3-0.8,2-0.8
|
||||
c1.8,0.3,3.3,0.3,4.8-0.5c2.3-1,3.8-3,4.3-5.5c0.5-2.5-0.3-4.8-2-6.5c-0.5-0.5-0.8-1.3-0.5-2s1-1.3,1.8-1.3c1.8,0,3.8-0.5,5-2
|
||||
c4.3-4.5,2.3-10.6-2.5-12.6c-0.8-0.3-1.3-1-1.3-2C150.1,95.8,150.8,95.1,151.6,95.1z"/>
|
||||
<path class="st3" d="M131.4,42.2c0.5,1.5,0.5,3,0,4.5c-0.3,0.8,0,1.5,0.5,2s1.3,0.8,2,0.5c1-0.5,2-0.5,3-0.5c2.3,0,4.3,1,5.8,3
|
||||
c1,1.3,1.8,3,1.5,4.8c0,1.5-0.5,2.8-1.3,4c-0.5,0.5-0.5,1.5,0,2c0.3,0.3,0.5,0.8,1,0.8c1-0.3,2-1,2.8-2c4.5-6.3,5.3-26.2,0.8-27.7
|
||||
c-4.5-1.5-12.3,1.5-17.9,6C130.7,40.1,131.2,40.9,131.4,42.2z"/>
|
||||
<path class="st3" d="M39,63.6c0.3-0.3,0.5-0.5,0.8-0.8c0.5-0.8,0.3-1.5,0-2C38.5,59,38.2,57,38.5,55c0.5-2.8,2.8-5,5.5-5.8
|
||||
c1.5-0.5,3-0.3,4.5,0.3c0.8,0.3,1.5,0,2-0.5c0.5-0.5,0.8-1.3,0.5-2c-0.5-1.5-0.5-3,0-4.5c0.3-1,0.8-2,1.5-2.8
|
||||
c-5.5-4.5-13.9-7.8-18.4-6.3S30.4,54.8,35,61.1C36,62.6,37.2,63.3,39,63.6z"/>
|
||||
<g>
|
||||
<circle class="st3" cx="60.9" cy="94.6" r="9.3"/>
|
||||
<path class="st3" d="M100.7,94.6c0,5.3-4.3,9.3-9.3,9.3c-5.3,0-9.3-4.3-9.3-9.3S100.7,89.3,100.7,94.6z"/>
|
||||
<circle class="st3" cx="121.6" cy="94.6" r="9.3"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.3 KiB |
18
index.html
18
index.html
@ -1,17 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>You're invited to talk on Matrix</title>
|
||||
<meta name="description" content="You're invited to talk on Matrix">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<link rel="stylesheet" type="text/css" href="css/main.css">
|
||||
<meta charset="utf-8">
|
||||
<title>You're invited to talk on Matrix</title>
|
||||
<meta name="description" content="You're invited to talk on Matrix">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<link rel="stylesheet" type="text/css" href="css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<script id="main" type="module">
|
||||
import {main} from "./src/main.js";
|
||||
main(document.body);
|
||||
</script>
|
||||
<script id="main" type="module">
|
||||
import {main} from "./src/main.js";
|
||||
main(document.body);
|
||||
</script>
|
||||
<noscript>
|
||||
<h1>Please enable javascript</h1>
|
||||
<p>Matrix.to is a preview service from chat rooms, people and communities on <a href="https://matrix.org">Matrix</a>.</p>
|
||||
|
@ -23,25 +23,26 @@ const projectDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".
|
||||
|
||||
// Serve up parent directory with cache disabled
|
||||
const serve = serveStatic(
|
||||
projectDir,
|
||||
{
|
||||
etag: false,
|
||||
setHeaders: res => {
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.setHeader("Expires", "Wed, 21 Oct 2015 07:28:00 GMT");
|
||||
projectDir,
|
||||
{
|
||||
etag: false,
|
||||
setHeaders: res => {
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.setHeader("Expires", "Wed, 21 Oct 2015 07:28:00 GMT");
|
||||
// same CSP as matrix.to server is using, so local testing happens under similar environment
|
||||
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * data:; connect-src *; font-src 'self'; manifest-src 'self'; form-action 'self'; navigate-to *;");
|
||||
},
|
||||
index: ['index.html', 'index.htm']
|
||||
}
|
||||
},
|
||||
index: ['index.html', 'index.htm']
|
||||
}
|
||||
);
|
||||
|
||||
// Create server
|
||||
const server = http.createServer(function onRequest (req, res) {
|
||||
console.log(req.method, req.url);
|
||||
serve(req, res, finalhandler(req, res))
|
||||
serve(req, res, finalhandler(req, res))
|
||||
});
|
||||
|
||||
// Listen
|
||||
server.listen(5000);
|
||||
console.log("Listening on port 5000");
|
||||
|
198
src/Link.js
198
src/Link.js
@ -24,20 +24,20 @@ const EVENTID_PATTERN = /^$([^:]+):(.+)$/;
|
||||
const GROUPID_PATTERN = /^\+([^:]+):(.+)$/;
|
||||
|
||||
export const IdentifierKind = createEnum(
|
||||
"RoomId",
|
||||
"RoomAlias",
|
||||
"UserId",
|
||||
"GroupId",
|
||||
"RoomId",
|
||||
"RoomAlias",
|
||||
"UserId",
|
||||
"GroupId",
|
||||
);
|
||||
|
||||
function asPrefix(identifierKind) {
|
||||
switch (identifierKind) {
|
||||
case IdentifierKind.RoomId: return "!";
|
||||
case IdentifierKind.RoomAlias: return "#";
|
||||
case IdentifierKind.GroupId: return "+";
|
||||
case IdentifierKind.UserId: return "@";
|
||||
default: throw new Error("invalid id kind " + identifierKind);
|
||||
}
|
||||
switch (identifierKind) {
|
||||
case IdentifierKind.RoomId: return "!";
|
||||
case IdentifierKind.RoomAlias: return "#";
|
||||
case IdentifierKind.GroupId: return "+";
|
||||
case IdentifierKind.UserId: return "@";
|
||||
default: throw new Error("invalid id kind " + identifierKind);
|
||||
}
|
||||
}
|
||||
|
||||
function getWebInstanceMap(queryParams) {
|
||||
@ -56,19 +56,19 @@ function getWebInstanceMap(queryParams) {
|
||||
}
|
||||
|
||||
export function getLabelForLinkKind(kind) {
|
||||
switch (kind) {
|
||||
case LinkKind.User: return "Start chat";
|
||||
case LinkKind.Room: return "View room";
|
||||
case LinkKind.Group: return "View community";
|
||||
case LinkKind.Event: return "View message";
|
||||
}
|
||||
switch (kind) {
|
||||
case LinkKind.User: return "Start chat";
|
||||
case LinkKind.Room: return "View room";
|
||||
case LinkKind.Group: return "View community";
|
||||
case LinkKind.Event: return "View message";
|
||||
}
|
||||
}
|
||||
|
||||
export const LinkKind = createEnum(
|
||||
"Room",
|
||||
"User",
|
||||
"Group",
|
||||
"Event"
|
||||
"Room",
|
||||
"User",
|
||||
"Group",
|
||||
"Event"
|
||||
)
|
||||
|
||||
export class Link {
|
||||
@ -81,106 +81,106 @@ export class Link {
|
||||
);
|
||||
}
|
||||
|
||||
static parse(fragment) {
|
||||
if (!fragment) {
|
||||
return null;
|
||||
}
|
||||
let [linkStr, queryParamsStr] = fragment.split("?");
|
||||
static parse(fragment) {
|
||||
if (!fragment) {
|
||||
return null;
|
||||
}
|
||||
let [linkStr, queryParamsStr] = fragment.split("?");
|
||||
|
||||
let viaServers = [];
|
||||
let viaServers = [];
|
||||
let clientId = null;
|
||||
let webInstances = {};
|
||||
if (queryParamsStr) {
|
||||
if (queryParamsStr) {
|
||||
const queryParams = queryParamsStr.split("&").map(pair => {
|
||||
const [key, value] = pair.split("=");
|
||||
return [decodeURIComponent(key), decodeURIComponent(value)];
|
||||
});
|
||||
viaServers = queryParams
|
||||
.filter(([key, value]) => key === "via")
|
||||
.map(([,value]) => value);
|
||||
viaServers = queryParams
|
||||
.filter(([key, value]) => key === "via")
|
||||
.map(([,value]) => value);
|
||||
const clientParam = queryParams.find(([key]) => key === "client");
|
||||
if (clientParam) {
|
||||
clientId = clientParam[1];
|
||||
}
|
||||
webInstances = getWebInstanceMap(queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (linkStr.startsWith("#/")) {
|
||||
linkStr = linkStr.substr(2);
|
||||
}
|
||||
if (linkStr.startsWith("#/")) {
|
||||
linkStr = linkStr.substr(2);
|
||||
}
|
||||
|
||||
const [identifier, eventId] = linkStr.split("/");
|
||||
|
||||
let matches;
|
||||
matches = USERID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.UserId, localPart, server, webInstances);
|
||||
}
|
||||
matches = ROOMALIAS_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.RoomAlias, localPart, server, webInstances, eventId);
|
||||
}
|
||||
matches = ROOMID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, webInstances, eventId);
|
||||
}
|
||||
matches = GROUPID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.GroupId, localPart, server, webInstances);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
let matches;
|
||||
matches = USERID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.UserId, localPart, server, webInstances);
|
||||
}
|
||||
matches = ROOMALIAS_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.RoomAlias, localPart, server, webInstances, eventId);
|
||||
}
|
||||
matches = ROOMID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.RoomId, localPart, server, webInstances, eventId);
|
||||
}
|
||||
matches = GROUPID_PATTERN.exec(identifier);
|
||||
if (matches) {
|
||||
const server = matches[2];
|
||||
const localPart = matches[1];
|
||||
return new Link(clientId, viaServers, IdentifierKind.GroupId, localPart, server, webInstances);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(clientId, viaServers, identifierKind, localPart, server, webInstances, eventId) {
|
||||
const servers = [server];
|
||||
servers.push(...viaServers);
|
||||
constructor(clientId, viaServers, identifierKind, localPart, server, webInstances, eventId) {
|
||||
const servers = [server];
|
||||
servers.push(...viaServers);
|
||||
this.webInstances = webInstances;
|
||||
this.servers = orderedUnique(servers);
|
||||
this.identifierKind = identifierKind;
|
||||
this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`;
|
||||
this.eventId = eventId;
|
||||
this.servers = orderedUnique(servers);
|
||||
this.identifierKind = identifierKind;
|
||||
this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`;
|
||||
this.eventId = eventId;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
get kind() {
|
||||
if (this.eventId) {
|
||||
return LinkKind.Event;
|
||||
}
|
||||
switch (this.identifierKind) {
|
||||
case IdentifierKind.RoomId:
|
||||
case IdentifierKind.RoomAlias:
|
||||
return LinkKind.Room;
|
||||
case IdentifierKind.UserId:
|
||||
return LinkKind.User;
|
||||
case IdentifierKind.GroupId:
|
||||
return LinkKind.Group;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
get kind() {
|
||||
if (this.eventId) {
|
||||
return LinkKind.Event;
|
||||
}
|
||||
switch (this.identifierKind) {
|
||||
case IdentifierKind.RoomId:
|
||||
case IdentifierKind.RoomAlias:
|
||||
return LinkKind.Room;
|
||||
case IdentifierKind.UserId:
|
||||
return LinkKind.User;
|
||||
case IdentifierKind.GroupId:
|
||||
return LinkKind.Group;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
equals(link) {
|
||||
return link &&
|
||||
link.identifier === this.identifier &&
|
||||
this.servers.length === link.servers.length &&
|
||||
this.servers.every((s, i) => link.servers[i] === s) &&
|
||||
equals(link) {
|
||||
return link &&
|
||||
link.identifier === this.identifier &&
|
||||
this.servers.length === link.servers.length &&
|
||||
this.servers.every((s, i) => link.servers[i] === s) &&
|
||||
Object.keys(this.webInstances).length === Object.keys(link.webInstances).length &&
|
||||
Object.keys(this.webInstances).every(k => this.webInstances[k] === link.webInstances[k]);
|
||||
}
|
||||
}
|
||||
|
||||
toFragment() {
|
||||
if (this.eventId) {
|
||||
return `/${this.identifier}/${this.eventId}`;
|
||||
} else {
|
||||
return `/${this.identifier}`;
|
||||
}
|
||||
}
|
||||
toFragment() {
|
||||
if (this.eventId) {
|
||||
return `/${this.identifier}/${this.eventId}`;
|
||||
} else {
|
||||
return `/${this.identifier}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,17 +17,17 @@ limitations under the License.
|
||||
import {createEnum} from "./utils/enum.js";
|
||||
|
||||
export const Platform = createEnum(
|
||||
"DesktopWeb",
|
||||
"MobileWeb",
|
||||
"Android",
|
||||
"iOS",
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux"
|
||||
"DesktopWeb",
|
||||
"MobileWeb",
|
||||
"Android",
|
||||
"iOS",
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux"
|
||||
);
|
||||
|
||||
export function guessApplicablePlatforms(userAgent, platform) {
|
||||
// return [Platform.DesktopWeb, Platform.Linux];
|
||||
// return [Platform.DesktopWeb, Platform.Linux];
|
||||
let nativePlatform;
|
||||
let webPlatform;
|
||||
if (/android/i.test(userAgent)) {
|
||||
@ -55,10 +55,10 @@ export function guessApplicablePlatforms(userAgent, platform) {
|
||||
}
|
||||
|
||||
export function isWebPlatform(p) {
|
||||
return p === Platform.DesktopWeb || p === Platform.MobileWeb;
|
||||
return p === Platform.DesktopWeb || p === Platform.MobileWeb;
|
||||
}
|
||||
|
||||
|
||||
export function isDesktopPlatform(p) {
|
||||
return p === Platform.Linux || p === Platform.Windows || p === Platform.macOS;
|
||||
return p === Platform.Linux || p === Platform.Windows || p === Platform.macOS;
|
||||
}
|
||||
|
@ -18,51 +18,51 @@ import {Platform} from "./Platform.js";
|
||||
import {EventEmitter} from "./utils/ViewModel.js";
|
||||
|
||||
export class Preferences extends EventEmitter {
|
||||
constructor(localStorage) {
|
||||
constructor(localStorage) {
|
||||
super();
|
||||
this._localStorage = localStorage;
|
||||
this.clientId = null;
|
||||
// used to differentiate web from native if a client supports both
|
||||
this.platform = null;
|
||||
this.homeservers = null;
|
||||
this._localStorage = localStorage;
|
||||
this.clientId = null;
|
||||
// used to differentiate web from native if a client supports both
|
||||
this.platform = null;
|
||||
this.homeservers = null;
|
||||
|
||||
const prefsStr = localStorage.getItem("preferred_client");
|
||||
if (prefsStr) {
|
||||
const {id, platform} = JSON.parse(prefsStr);
|
||||
this.clientId = id;
|
||||
this.platform = Platform[platform];
|
||||
}
|
||||
const prefsStr = localStorage.getItem("preferred_client");
|
||||
if (prefsStr) {
|
||||
const {id, platform} = JSON.parse(prefsStr);
|
||||
this.clientId = id;
|
||||
this.platform = Platform[platform];
|
||||
}
|
||||
const serversStr = localStorage.getItem("consented_servers");
|
||||
if (serversStr) {
|
||||
this.homeservers = JSON.parse(serversStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setClient(id, platform) {
|
||||
this.clientId = id;
|
||||
platform = Platform[platform];
|
||||
this.platform = platform;
|
||||
this._localStorage.setItem("preferred_client", JSON.stringify({id, platform}));
|
||||
setClient(id, platform) {
|
||||
this.clientId = id;
|
||||
platform = Platform[platform];
|
||||
this.platform = platform;
|
||||
this._localStorage.setItem("preferred_client", JSON.stringify({id, platform}));
|
||||
this.emit("canClear")
|
||||
}
|
||||
}
|
||||
|
||||
setHomeservers(homeservers, persist) {
|
||||
setHomeservers(homeservers, persist) {
|
||||
this.homeservers = homeservers;
|
||||
if (persist) {
|
||||
this._localStorage.setItem("consented_servers", JSON.stringify(homeservers));
|
||||
this.emit("canClear");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._localStorage.removeItem("preferred_client");
|
||||
clear() {
|
||||
this._localStorage.removeItem("preferred_client");
|
||||
this._localStorage.removeItem("consented_servers");
|
||||
this.clientId = null;
|
||||
this.platform = null;
|
||||
this.clientId = null;
|
||||
this.platform = null;
|
||||
this.homeservers = null;
|
||||
}
|
||||
}
|
||||
|
||||
get canClear() {
|
||||
return !!this.clientId || !!this.platform || !!this.homeservers;
|
||||
}
|
||||
get canClear() {
|
||||
return !!this.clientId || !!this.platform || !!this.homeservers;
|
||||
}
|
||||
}
|
||||
|
@ -20,25 +20,25 @@ import {CreateLinkView} from "./create/CreateLinkView.js";
|
||||
import {LoadServerPolicyView} from "./policy/LoadServerPolicyView.js";
|
||||
|
||||
export class RootView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "RootView"}, [
|
||||
t.mapView(vm => vm.openLinkViewModel, vm => vm ? new OpenLinkView(vm) : null),
|
||||
t.mapView(vm => vm.createLinkViewModel, vm => vm ? new CreateLinkView(vm) : null),
|
||||
render(t, vm) {
|
||||
return t.div({className: "RootView"}, [
|
||||
t.mapView(vm => vm.openLinkViewModel, vm => vm ? new OpenLinkView(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.div({className: "footer"}, [
|
||||
t.p(t.img({src: "images/matrix-logo.svg"})),
|
||||
t.p(["This invite uses ", externalLink(t, "https://matrix.org", "Matrix"), ", an open network for secure, decentralized communication."]),
|
||||
t.ul({className: "links"}, [
|
||||
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to", "GitHub project")),
|
||||
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to/tree/main/src/open/clients", "Add your app")),
|
||||
t.li({className: {hidden: vm => !vm.hasPreferences}},
|
||||
t.button({className: "text", onClick: () => vm.clearPreferences()}, "Clear preferences")),
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
t.div({className: "footer"}, [
|
||||
t.p(t.img({src: "images/matrix-logo.svg"})),
|
||||
t.p(["This invite uses ", externalLink(t, "https://matrix.org", "Matrix"), ", an open network for secure, decentralized communication."]),
|
||||
t.ul({className: "links"}, [
|
||||
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to", "GitHub project")),
|
||||
t.li(externalLink(t, "https://github.com/matrix-org/matrix.to/tree/main/src/open/clients", "Add your app")),
|
||||
t.li({className: {hidden: vm => !vm.hasPreferences}},
|
||||
t.button({className: "text", onClick: () => vm.clearPreferences()}, "Clear preferences")),
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function externalLink(t, href, label) {
|
||||
return t.a({href, target: "_blank", rel: "noopener noreferrer"}, label);
|
||||
return t.a({href, target: "_blank", rel: "noopener noreferrer"}, label);
|
||||
}
|
||||
|
@ -23,51 +23,51 @@ import {LoadServerPolicyViewModel} from "./policy/LoadServerPolicyViewModel.js";
|
||||
import {Platform} from "./Platform.js";
|
||||
|
||||
export class RootViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.link = null;
|
||||
this.openLinkViewModel = null;
|
||||
this.createLinkViewModel = null;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.link = null;
|
||||
this.openLinkViewModel = null;
|
||||
this.createLinkViewModel = null;
|
||||
this.loadServerPolicyViewModel = null;
|
||||
this.preferences.on("canClear", () => {
|
||||
this.emitChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_updateChildVMs(oldLink) {
|
||||
if (this.link) {
|
||||
this.createLinkViewModel = null;
|
||||
if (!oldLink || !oldLink.equals(this.link)) {
|
||||
this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
|
||||
link: this.link,
|
||||
clients: createClients(),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
this.openLinkViewModel = null;
|
||||
this.createLinkViewModel = new CreateLinkViewModel(this.childOptions());
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
_updateChildVMs(oldLink) {
|
||||
if (this.link) {
|
||||
this.createLinkViewModel = null;
|
||||
if (!oldLink || !oldLink.equals(this.link)) {
|
||||
this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
|
||||
link: this.link,
|
||||
clients: createClients(),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
this.openLinkViewModel = null;
|
||||
this.createLinkViewModel = new CreateLinkViewModel(this.childOptions());
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
updateHash(hash) {
|
||||
updateHash(hash) {
|
||||
if (hash.startsWith("#/policy/")) {
|
||||
const server = hash.substr(9);
|
||||
this.loadServerPolicyViewModel = new LoadServerPolicyViewModel(this.childOptions({server}));
|
||||
this.loadServerPolicyViewModel.load();
|
||||
} else {
|
||||
const oldLink = this.link;
|
||||
this.link = Link.parse(hash);
|
||||
this._updateChildVMs(oldLink);
|
||||
const oldLink = this.link;
|
||||
this.link = Link.parse(hash);
|
||||
this._updateChildVMs(oldLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearPreferences() {
|
||||
this.preferences.clear();
|
||||
this._updateChildVMs();
|
||||
}
|
||||
clearPreferences() {
|
||||
this.preferences.clear();
|
||||
this._updateChildVMs();
|
||||
}
|
||||
|
||||
get hasPreferences() {
|
||||
return this.preferences.canClear;
|
||||
}
|
||||
get hasPreferences() {
|
||||
return this.preferences.canClear;
|
||||
}
|
||||
}
|
||||
|
@ -19,31 +19,31 @@ import {PreviewView} from "../preview/PreviewView.js";
|
||||
import {copyButton} from "../utils/copy.js";
|
||||
|
||||
export class CreateLinkView extends TemplateView {
|
||||
render(t, vm) {
|
||||
render(t, vm) {
|
||||
const link = t.a({href: vm => vm.linkUrl}, vm => vm.linkUrl);
|
||||
return t.div({className: "CreateLinkView card"}, [
|
||||
t.h1("Create shareable links to Matrix rooms, users or messages without being tied to any app"),
|
||||
t.form({action: "#", onSubmit: evt => this._onSubmit(evt)}, [
|
||||
t.div(t.input({
|
||||
className: "fullwidth large",
|
||||
type: "text",
|
||||
name: "identifier",
|
||||
return t.div({className: "CreateLinkView card"}, [
|
||||
t.h1("Create shareable links to Matrix rooms, users or messages without being tied to any app"),
|
||||
t.form({action: "#", onSubmit: evt => this._onSubmit(evt)}, [
|
||||
t.div(t.input({
|
||||
className: "fullwidth large",
|
||||
type: "text",
|
||||
name: "identifier",
|
||||
required: true,
|
||||
placeholder: "#room:example.com, @user:example.com",
|
||||
placeholder: "#room:example.com, @user:example.com",
|
||||
onChange: evt => this._onIdentifierChange(evt)
|
||||
})),
|
||||
t.div(t.input({className: "primary fullwidth icon link", type: "submit", value: "Create link"}))
|
||||
]),
|
||||
]);
|
||||
}
|
||||
})),
|
||||
t.div(t.input({className: "primary fullwidth icon link", type: "submit", value: "Create link"}))
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
_onSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
const form = evt.target;
|
||||
const {identifier} = form.elements;
|
||||
this.value.createLink(identifier.value);
|
||||
_onSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
const form = evt.target;
|
||||
const {identifier} = form.elements;
|
||||
this.value.createLink(identifier.value);
|
||||
identifier.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
_onIdentifierChange(evt) {
|
||||
const inputField = evt.target;
|
||||
|
@ -19,11 +19,11 @@ import {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
||||
import {Link} from "../Link.js";
|
||||
|
||||
export class CreateLinkViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._link = null;
|
||||
this.previewViewModel = null;
|
||||
}
|
||||
this.previewViewModel = null;
|
||||
}
|
||||
|
||||
validateIdentifier(identifier) {
|
||||
return Link.validateIdentifier(identifier);
|
||||
|
28
src/main.js
28
src/main.js
@ -21,18 +21,18 @@ import {Preferences} from "./Preferences.js";
|
||||
import {guessApplicablePlatforms} from "./Platform.js";
|
||||
|
||||
export async function main(container) {
|
||||
const vm = new RootViewModel({
|
||||
request: xhrRequest,
|
||||
openLink: url => location.href = url,
|
||||
platforms: guessApplicablePlatforms(navigator.userAgent, navigator.platform),
|
||||
preferences: new Preferences(window.localStorage),
|
||||
origin: location.origin,
|
||||
});
|
||||
vm.updateHash(decodeURIComponent(location.hash));
|
||||
window.__rootvm = vm;
|
||||
const view = new RootView(vm);
|
||||
container.appendChild(view.mount());
|
||||
window.addEventListener('hashchange', () => {
|
||||
vm.updateHash(decodeURIComponent(location.hash));
|
||||
});
|
||||
const vm = new RootViewModel({
|
||||
request: xhrRequest,
|
||||
openLink: url => location.href = url,
|
||||
platforms: guessApplicablePlatforms(navigator.userAgent, navigator.platform),
|
||||
preferences: new Preferences(window.localStorage),
|
||||
origin: location.origin,
|
||||
});
|
||||
vm.updateHash(decodeURIComponent(location.hash));
|
||||
window.__rootvm = vm;
|
||||
const view = new RootView(vm);
|
||||
container.appendChild(view.mount());
|
||||
window.addEventListener('hashchange', () => {
|
||||
vm.updateHash(decodeURIComponent(location.hash));
|
||||
});
|
||||
}
|
||||
|
@ -18,50 +18,50 @@ import {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientView} from "./ClientView.js";
|
||||
|
||||
export class ClientListView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.mapView(vm => vm.clientViewModel, () => {
|
||||
if (vm.clientViewModel) {
|
||||
return new ContinueWithClientView(vm);
|
||||
} else {
|
||||
return new AllClientsView(vm);
|
||||
}
|
||||
});
|
||||
}
|
||||
render(t, vm) {
|
||||
return t.mapView(vm => vm.clientViewModel, () => {
|
||||
if (vm.clientViewModel) {
|
||||
return new ContinueWithClientView(vm);
|
||||
} else {
|
||||
return new AllClientsView(vm);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class AllClientsView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.h2("Choose an app to continue"),
|
||||
t.map(vm => vm.clientList, (clientList, t) => {
|
||||
return t.div({className: "list"}, clientList.map(clientViewModel => {
|
||||
return t.view(new ClientView(clientViewModel));
|
||||
}));
|
||||
}),
|
||||
t.div(t.label([
|
||||
t.input({
|
||||
type: "checkbox",
|
||||
checked: vm.showUnsupportedPlatforms,
|
||||
onChange: evt => vm.showUnsupportedPlatforms = evt.target.checked,
|
||||
}),
|
||||
"Show apps not available on my platform"
|
||||
])),
|
||||
t.div(t.label({className: "filterOption"}, [
|
||||
t.input({
|
||||
type: "checkbox",
|
||||
checked: vm.showExperimental,
|
||||
onChange: evt => vm.showExperimental = evt.target.checked,
|
||||
}),
|
||||
"Show experimental apps"
|
||||
])),
|
||||
]);
|
||||
}
|
||||
render(t, vm) {
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.h2("Choose an app to continue"),
|
||||
t.map(vm => vm.clientList, (clientList, t) => {
|
||||
return t.div({className: "list"}, clientList.map(clientViewModel => {
|
||||
return t.view(new ClientView(clientViewModel));
|
||||
}));
|
||||
}),
|
||||
t.div(t.label([
|
||||
t.input({
|
||||
type: "checkbox",
|
||||
checked: vm.showUnsupportedPlatforms,
|
||||
onChange: evt => vm.showUnsupportedPlatforms = evt.target.checked,
|
||||
}),
|
||||
"Show apps not available on my platform"
|
||||
])),
|
||||
t.div(t.label({className: "filterOption"}, [
|
||||
t.input({
|
||||
type: "checkbox",
|
||||
checked: vm.showExperimental,
|
||||
onChange: evt => vm.showExperimental = evt.target.checked,
|
||||
}),
|
||||
"Show experimental apps"
|
||||
])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ContinueWithClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel)))
|
||||
]);
|
||||
}
|
||||
render(t, vm) {
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel)))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -20,70 +20,70 @@ import {ClientViewModel} from "./ClientViewModel.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
|
||||
export class ClientListViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {clients, client, link} = options;
|
||||
this._clients = clients;
|
||||
this._link = link;
|
||||
this.clientList = null;
|
||||
this._showExperimental = false;
|
||||
this._showUnsupportedPlatforms = false;
|
||||
this._filterClients();
|
||||
this.clientViewModel = null;
|
||||
if (client) {
|
||||
this._pickClient(client);
|
||||
}
|
||||
}
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {clients, client, link} = options;
|
||||
this._clients = clients;
|
||||
this._link = link;
|
||||
this.clientList = null;
|
||||
this._showExperimental = false;
|
||||
this._showUnsupportedPlatforms = false;
|
||||
this._filterClients();
|
||||
this.clientViewModel = null;
|
||||
if (client) {
|
||||
this._pickClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
get showUnsupportedPlatforms() {
|
||||
return this._showUnsupportedPlatforms;
|
||||
}
|
||||
get showUnsupportedPlatforms() {
|
||||
return this._showUnsupportedPlatforms;
|
||||
}
|
||||
|
||||
get showExperimental() {
|
||||
return this._showExperimental;
|
||||
}
|
||||
get showExperimental() {
|
||||
return this._showExperimental;
|
||||
}
|
||||
|
||||
set showUnsupportedPlatforms(enabled) {
|
||||
this._showUnsupportedPlatforms = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
set showUnsupportedPlatforms(enabled) {
|
||||
this._showUnsupportedPlatforms = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
|
||||
set showExperimental(enabled) {
|
||||
this._showExperimental = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
set showExperimental(enabled) {
|
||||
this._showExperimental = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
|
||||
_filterClients() {
|
||||
const clientVMs = this._clients.filter(client => {
|
||||
_filterClients() {
|
||||
const clientVMs = this._clients.filter(client => {
|
||||
const platformMaturities = this.platforms.map(p => client.getMaturity(p));
|
||||
const isStable = platformMaturities.includes(Maturity.Stable) || platformMaturities.includes(Maturity.Beta);
|
||||
const isSupported = client.platforms.some(p => this.platforms.includes(p));
|
||||
if (!this._showExperimental && !isStable) {
|
||||
return false;
|
||||
}
|
||||
if (!this._showUnsupportedPlatforms && !isSupported) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(client => new ClientViewModel(this.childOptions({
|
||||
client,
|
||||
link: this._link,
|
||||
pickClient: client => this._pickClient(client)
|
||||
})));
|
||||
const isStable = platformMaturities.includes(Maturity.Stable) || platformMaturities.includes(Maturity.Beta);
|
||||
const isSupported = client.platforms.some(p => this.platforms.includes(p));
|
||||
if (!this._showExperimental && !isStable) {
|
||||
return false;
|
||||
}
|
||||
if (!this._showUnsupportedPlatforms && !isSupported) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(client => new ClientViewModel(this.childOptions({
|
||||
client,
|
||||
link: this._link,
|
||||
pickClient: client => this._pickClient(client)
|
||||
})));
|
||||
const preferredClientVMs = clientVMs.filter(c => c.hasPreferredWebInstance);
|
||||
const otherClientVMs = clientVMs.filter(c => !c.hasPreferredWebInstance);
|
||||
this.clientList = preferredClientVMs.concat(otherClientVMs);
|
||||
this.emitChange();
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
_pickClient(client) {
|
||||
this.clientViewModel = this.clientList.find(vm => vm.clientId === client.id);
|
||||
_pickClient(client) {
|
||||
this.clientViewModel = this.clientList.find(vm => vm.clientId === client.id);
|
||||
this.clientViewModel.pick(this);
|
||||
this.emitChange();
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
showAll() {
|
||||
this.clientViewModel = null;
|
||||
this.emitChange();
|
||||
}
|
||||
showAll() {
|
||||
this.clientViewModel = null;
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ import {copy} from "../utils/copy.js";
|
||||
import {text, tag} from "../utils/html.js";
|
||||
|
||||
function formatPlatforms(platforms) {
|
||||
return platforms.reduce((str, p, i, all) => {
|
||||
const first = i === 0;
|
||||
const last = i === all.length - 1;
|
||||
return str + (first ? "" : last ? " & " : ", ") + p;
|
||||
}, "");
|
||||
return platforms.reduce((str, p, i, all) => {
|
||||
const first = i === 0;
|
||||
const last = i === all.length - 1;
|
||||
return str + (first ? "" : last ? " & " : ", ") + p;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function renderInstructions(parts) {
|
||||
@ -38,46 +38,46 @@ function renderInstructions(parts) {
|
||||
|
||||
export class ClientView extends TemplateView {
|
||||
|
||||
render(t, vm) {
|
||||
return t.div({className: {"ClientView": true, "isPreferred": vm => vm.hasPreferredWebInstance}}, [
|
||||
render(t, vm) {
|
||||
return t.div({className: {"ClientView": true, "isPreferred": vm => vm.hasPreferredWebInstance}}, [
|
||||
... vm.hasPreferredWebInstance ? [t.div({className: "hostedBanner"}, vm.hostedByBannerLabel)] : [],
|
||||
t.div({className: "header"}, [
|
||||
t.div({className: "description"}, [
|
||||
t.h3(vm.name),
|
||||
t.p([vm.description, " ", t.a({
|
||||
t.div({className: "header"}, [
|
||||
t.div({className: "description"}, [
|
||||
t.h3(vm.name),
|
||||
t.p([vm.description, " ", t.a({
|
||||
href: vm.homepage,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
}, "Learn more")]),
|
||||
t.p({className: "platforms"}, formatPlatforms(vm.availableOnPlatformNames)),
|
||||
]),
|
||||
t.img({className: "clientIcon", src: vm.iconUrl})
|
||||
]),
|
||||
t.p({className: "platforms"}, formatPlatforms(vm.availableOnPlatformNames)),
|
||||
]),
|
||||
t.img({className: "clientIcon", src: vm.iconUrl})
|
||||
]),
|
||||
t.mapView(vm => vm.stage, stage => {
|
||||
switch (stage) {
|
||||
case "open": return new OpenClientView(vm);
|
||||
case "install": return new InstallClientView(vm);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "OpenClientView"}, [
|
||||
...vm.openActions.map(a => renderAction(t, a)),
|
||||
render(t, vm) {
|
||||
return t.div({className: "OpenClientView"}, [
|
||||
...vm.openActions.map(a => renderAction(t, a)),
|
||||
showBack(t, vm),
|
||||
]);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class InstallClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
const children = [];
|
||||
render(t, vm) {
|
||||
const children = [];
|
||||
|
||||
const textInstructions = vm.textInstructions;
|
||||
if (textInstructions) {
|
||||
if (textInstructions) {
|
||||
const copyButton = t.button({
|
||||
className: "copy",
|
||||
title: "Copy instructions",
|
||||
@ -91,25 +91,25 @@ class InstallClientView extends TemplateView {
|
||||
}
|
||||
}
|
||||
});
|
||||
children.push(t.p({className: "instructions"}, renderInstructions(textInstructions).concat(copyButton)));
|
||||
}
|
||||
children.push(t.p({className: "instructions"}, renderInstructions(textInstructions).concat(copyButton)));
|
||||
}
|
||||
|
||||
const actions = t.div({className: "actions"}, vm.installActions.map(a => renderAction(t, a)));
|
||||
children.push(actions);
|
||||
const actions = t.div({className: "actions"}, vm.installActions.map(a => renderAction(t, a)));
|
||||
children.push(actions);
|
||||
|
||||
if (vm.showDeepLinkInInstall) {
|
||||
const openItHere = t.a({
|
||||
rel: "noopener noreferrer",
|
||||
href: vm.openActions[0].url,
|
||||
onClick: () => vm.openActions[0].activated(),
|
||||
}, "open it here");
|
||||
children.push(t.p([`If you already have ${vm.name} installed, you can `, openItHere, "."]))
|
||||
}
|
||||
if (vm.showDeepLinkInInstall) {
|
||||
const openItHere = t.a({
|
||||
rel: "noopener noreferrer",
|
||||
href: vm.openActions[0].url,
|
||||
onClick: () => vm.openActions[0].activated(),
|
||||
}, "open it here");
|
||||
children.push(t.p([`If you already have ${vm.name} installed, you can `, openItHere, "."]))
|
||||
}
|
||||
|
||||
children.push(showBack(t, vm));
|
||||
|
||||
return t.div({className: "InstallClientView"}, children);
|
||||
}
|
||||
return t.div({className: "InstallClientView"}, children);
|
||||
}
|
||||
}
|
||||
|
||||
function showBack(t, vm) {
|
||||
|
@ -27,28 +27,28 @@ function getMatchingPlatforms(client, supportedPlatforms) {
|
||||
}
|
||||
|
||||
export class ClientViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {client, link, pickClient} = options;
|
||||
this._client = client;
|
||||
this._link = link;
|
||||
this._pickClient = pickClient;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {client, link, pickClient} = options;
|
||||
this._client = client;
|
||||
this._link = link;
|
||||
this._pickClient = pickClient;
|
||||
// to provide "choose other client" button after calling pick()
|
||||
this._clientListViewModel = null;
|
||||
this._update();
|
||||
}
|
||||
}
|
||||
|
||||
_update() {
|
||||
const matchingPlatforms = getMatchingPlatforms(this._client, this.platforms);
|
||||
this._webPlatform = matchingPlatforms.find(p => isWebPlatform(p));
|
||||
this._nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p));
|
||||
const matchingPlatforms = getMatchingPlatforms(this._client, this.platforms);
|
||||
this._webPlatform = matchingPlatforms.find(p => isWebPlatform(p));
|
||||
this._nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p));
|
||||
const preferredPlatform = matchingPlatforms.find(p => p === this.preferences.platform);
|
||||
this._proposedPlatform = preferredPlatform || this._nativePlatform || this._webPlatform;
|
||||
this._proposedPlatform = preferredPlatform || this._nativePlatform || this._webPlatform;
|
||||
|
||||
this.openActions = this._createOpenActions();
|
||||
this.installActions = this._createInstallActions();
|
||||
this._clientCanIntercept = !!(this._nativePlatform && this._client.canInterceptMatrixToLinks(this._nativePlatform));
|
||||
this._showOpen = this.openActions.length && !this._clientCanIntercept;
|
||||
this.installActions = this._createInstallActions();
|
||||
this._clientCanIntercept = !!(this._nativePlatform && this._client.canInterceptMatrixToLinks(this._nativePlatform));
|
||||
this._showOpen = this.openActions.length && !this._clientCanIntercept;
|
||||
}
|
||||
|
||||
// these are only shown in the open stage
|
||||
@ -93,40 +93,40 @@ export class ClientViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
// these are only shown in the install stage
|
||||
_createInstallActions() {
|
||||
let actions = [];
|
||||
if (this._nativePlatform) {
|
||||
const nativeActions = (this._client.getInstallLinks(this._nativePlatform) || []).map(installLink => {
|
||||
return {
|
||||
label: installLink.getDescription(this._nativePlatform),
|
||||
url: installLink.createInstallURL(this._link),
|
||||
kind: installLink.channelId,
|
||||
primary: true,
|
||||
activated: () => this.preferences.setClient(this._client.id, this._nativePlatform),
|
||||
};
|
||||
});
|
||||
actions.push(...nativeActions);
|
||||
}
|
||||
if (this._webPlatform) {
|
||||
const webDeepLink = this._client.getDeepLink(this._webPlatform, this._link);
|
||||
if (webDeepLink) {
|
||||
_createInstallActions() {
|
||||
let actions = [];
|
||||
if (this._nativePlatform) {
|
||||
const nativeActions = (this._client.getInstallLinks(this._nativePlatform) || []).map(installLink => {
|
||||
return {
|
||||
label: installLink.getDescription(this._nativePlatform),
|
||||
url: installLink.createInstallURL(this._link),
|
||||
kind: installLink.channelId,
|
||||
primary: true,
|
||||
activated: () => this.preferences.setClient(this._client.id, this._nativePlatform),
|
||||
};
|
||||
});
|
||||
actions.push(...nativeActions);
|
||||
}
|
||||
if (this._webPlatform) {
|
||||
const webDeepLink = this._client.getDeepLink(this._webPlatform, this._link);
|
||||
if (webDeepLink) {
|
||||
const webLabel = this.hasPreferredWebInstance ?
|
||||
`Open on ${this._client.getPreferredWebInstance(this._link)}` :
|
||||
`Continue in your browser`;
|
||||
actions.push({
|
||||
label: webLabel,
|
||||
url: webDeepLink,
|
||||
kind: "open-in-web",
|
||||
activated: () => {
|
||||
actions.push({
|
||||
label: webLabel,
|
||||
url: webDeepLink,
|
||||
kind: "open-in-web",
|
||||
activated: () => {
|
||||
if (!this.hasPreferredWebInstance) {
|
||||
this.preferences.setClient(this._client.id, this._webPlatform);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
get hasPreferredWebInstance() {
|
||||
// also check there is a web platform that matches the platforms the user is on (mobile or desktop web)
|
||||
@ -150,17 +150,17 @@ export class ClientViewModel extends ViewModel {
|
||||
return this._client.homepage;
|
||||
}
|
||||
|
||||
get identifier() {
|
||||
return this._link.identifier;
|
||||
}
|
||||
get identifier() {
|
||||
return this._link.identifier;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this._client.description;
|
||||
}
|
||||
get description() {
|
||||
return this._client.description;
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this._client.id;
|
||||
}
|
||||
get clientId() {
|
||||
return this._client.id;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._client.name;
|
||||
@ -174,44 +174,44 @@ export class ClientViewModel extends ViewModel {
|
||||
return this._showOpen ? "open" : "install";
|
||||
}
|
||||
|
||||
get textInstructions() {
|
||||
get textInstructions() {
|
||||
let instructions = this._client.getLinkInstructions(this._proposedPlatform, this._link);
|
||||
if (instructions && !Array.isArray(instructions)) {
|
||||
instructions = [instructions];
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
get copyString() {
|
||||
return this._client.getCopyString(this._proposedPlatform, this._link);
|
||||
}
|
||||
|
||||
get showDeepLinkInInstall() {
|
||||
get showDeepLinkInInstall() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
get availableOnPlatformNames() {
|
||||
const platforms = this._client.platforms;
|
||||
const textPlatforms = [];
|
||||
const hasWebPlatform = platforms.some(p => isWebPlatform(p));
|
||||
if (hasWebPlatform) {
|
||||
textPlatforms.push("Web");
|
||||
}
|
||||
const desktopPlatforms = platforms.filter(p => isDesktopPlatform(p));
|
||||
if (desktopPlatforms.length === 1) {
|
||||
textPlatforms.push(desktopPlatforms[0]);
|
||||
} else {
|
||||
textPlatforms.push("Desktop");
|
||||
}
|
||||
if (platforms.includes(Platform.Android)) {
|
||||
textPlatforms.push("Android");
|
||||
}
|
||||
if (platforms.includes(Platform.iOS)) {
|
||||
textPlatforms.push("iOS");
|
||||
}
|
||||
return textPlatforms;
|
||||
}
|
||||
get availableOnPlatformNames() {
|
||||
const platforms = this._client.platforms;
|
||||
const textPlatforms = [];
|
||||
const hasWebPlatform = platforms.some(p => isWebPlatform(p));
|
||||
if (hasWebPlatform) {
|
||||
textPlatforms.push("Web");
|
||||
}
|
||||
const desktopPlatforms = platforms.filter(p => isDesktopPlatform(p));
|
||||
if (desktopPlatforms.length === 1) {
|
||||
textPlatforms.push(desktopPlatforms[0]);
|
||||
} else {
|
||||
textPlatforms.push("Desktop");
|
||||
}
|
||||
if (platforms.includes(Platform.Android)) {
|
||||
textPlatforms.push("Android");
|
||||
}
|
||||
if (platforms.includes(Platform.iOS)) {
|
||||
textPlatforms.push("iOS");
|
||||
}
|
||||
return textPlatforms;
|
||||
}
|
||||
|
||||
pick(clientListViewModel) {
|
||||
this._clientListViewModel = clientListViewModel;
|
||||
|
@ -20,14 +20,14 @@ import {PreviewView} from "../preview/PreviewView.js";
|
||||
import {ServerConsentView} from "./ServerConsentView.js";
|
||||
|
||||
export class OpenLinkView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "OpenLinkView card"}, [
|
||||
t.mapView(vm => vm.previewViewModel, previewVM => previewVM ?
|
||||
render(t, vm) {
|
||||
return t.div({className: "OpenLinkView card"}, [
|
||||
t.mapView(vm => vm.previewViewModel, previewVM => previewVM ?
|
||||
new ShowLinkView(vm) :
|
||||
new ServerConsentView(vm.serverConsentViewModel)
|
||||
),
|
||||
]);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLinkView extends TemplateView {
|
||||
|
@ -23,21 +23,21 @@ import {getLabelForLinkKind} from "../Link.js";
|
||||
import {orderedUnique} from "../utils/unique.js";
|
||||
|
||||
export class OpenLinkViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {clients, link} = options;
|
||||
this._link = link;
|
||||
this._clients = clients;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {clients, link} = options;
|
||||
this._link = link;
|
||||
this._clients = clients;
|
||||
this.serverConsentViewModel = null;
|
||||
this.previewViewModel = null;
|
||||
this.previewViewModel = null;
|
||||
this.clientsViewModel = null;
|
||||
this.previewLoading = false;
|
||||
this.previewLoading = false;
|
||||
if (this.preferences.homeservers === null) {
|
||||
this._showServerConsent();
|
||||
} else {
|
||||
this._showLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_showServerConsent() {
|
||||
let servers = [];
|
||||
@ -67,24 +67,24 @@ export class OpenLinkViewModel extends ViewModel {
|
||||
link: this._link,
|
||||
consentedServers: this.preferences.homeservers
|
||||
}));
|
||||
this.previewLoading = true;
|
||||
this.emitChange();
|
||||
await this.previewViewModel.load();
|
||||
this.previewLoading = false;
|
||||
this.emitChange();
|
||||
this.previewLoading = true;
|
||||
this.emitChange();
|
||||
await this.previewViewModel.load();
|
||||
this.previewLoading = false;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
get previewDomain() {
|
||||
return this.previewViewModel?.domain;
|
||||
}
|
||||
get previewDomain() {
|
||||
return this.previewViewModel?.domain;
|
||||
}
|
||||
|
||||
get previewFailed() {
|
||||
return this.previewViewModel?.failed;
|
||||
}
|
||||
|
||||
get showClientsLabel() {
|
||||
return getLabelForLinkKind(this._link.kind);
|
||||
}
|
||||
get showClientsLabel() {
|
||||
return getLabelForLinkKind(this._link.kind);
|
||||
}
|
||||
|
||||
changeServer() {
|
||||
this.previewViewModel = null;
|
||||
|
@ -27,7 +27,7 @@ export class ServerConsentView extends TemplateView {
|
||||
className: "text",
|
||||
onClick: () => vm.continueWithoutConsent(this._askEveryTimeChecked)
|
||||
}, "continue without a preview");
|
||||
return t.div({className: "ServerConsentView"}, [
|
||||
return t.div({className: "ServerConsentView"}, [
|
||||
t.p([
|
||||
"Preview this link using the ",
|
||||
t.strong(vm => vm.selectedServer || "…"),
|
||||
@ -56,7 +56,7 @@ export class ServerConsentView extends TemplateView {
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
_onSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
|
@ -22,13 +22,13 @@ import {getLabelForLinkKind} from "../Link.js";
|
||||
import {orderedUnique} from "../utils/unique.js";
|
||||
|
||||
export class ServerConsentViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.servers = options.servers;
|
||||
this.done = options.done;
|
||||
this.selectedServer = this.servers[0];
|
||||
this.showSelectServer = false;
|
||||
}
|
||||
}
|
||||
|
||||
setShowServers() {
|
||||
this.showSelectServer = true;
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import {Maturity, Platform, LinkKind,
|
||||
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
|
||||
const trustedWebInstances = [
|
||||
"app.element.io", // first one is the default one
|
||||
@ -29,69 +29,69 @@ const trustedWebInstances = [
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Element {
|
||||
get id() { return "element.io"; }
|
||||
get id() { return "element.io"; }
|
||||
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android, Platform.iOS,
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
Platform.DesktopWeb
|
||||
];
|
||||
}
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android, Platform.iOS,
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
Platform.DesktopWeb
|
||||
];
|
||||
}
|
||||
|
||||
get icon() { return "images/client-icons/element.svg"; }
|
||||
get appleAssociatedAppId() { return "7J4U792NQT.im.vector.app"; }
|
||||
get name() {return "Element"; }
|
||||
get description() { return 'Fully-featured Matrix client, used by millions.'; }
|
||||
get homepage() { return "https://element.io"; }
|
||||
get author() { return "Element"; }
|
||||
getMaturity(platform) { return Maturity.Stable; }
|
||||
get name() {return "Element"; }
|
||||
get description() { return 'Fully-featured Matrix client, used by millions.'; }
|
||||
get homepage() { return "https://element.io"; }
|
||||
get author() { return "Element"; }
|
||||
getMaturity(platform) { return Maturity.Stable; }
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `user/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `room/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
fragmentPath = `group/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `room/${link.identifier}/${link.eventId}`;
|
||||
break;
|
||||
}
|
||||
getDeepLink(platform, link) {
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `user/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `room/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
fragmentPath = `group/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `room/${link.identifier}/${link.eventId}`;
|
||||
break;
|
||||
}
|
||||
const isWebPlatform = platform === Platform.DesktopWeb || platform === Platform.MobileWeb;
|
||||
if (isWebPlatform || platform === Platform.iOS) {
|
||||
if (isWebPlatform || platform === Platform.iOS) {
|
||||
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.
|
||||
if (isWebPlatform && trustedWebInstances.includes(link.webInstances[this.id])) {
|
||||
instanceHost = link.webInstances[this.id];
|
||||
}
|
||||
return `https://${instanceHost}/#/${fragmentPath}`;
|
||||
} else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) {
|
||||
return `element://vector/webapp/#/${fragmentPath}`;
|
||||
} else {
|
||||
return `https://${instanceHost}/#/${fragmentPath}`;
|
||||
} else if (platform === Platform.Linux || platform === Platform.Windows || platform === Platform.macOS) {
|
||||
return `element://vector/webapp/#/${fragmentPath}`;
|
||||
} else {
|
||||
return `element://${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {}
|
||||
getLinkInstructions(platform, link) {}
|
||||
getCopyString(platform, link) {}
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.iOS: return [new AppleStoreLink('vector', 'id1083446067')];
|
||||
case Platform.Android: return [new PlayStoreLink('im.vector.app'), new FDroidLink('im.vector.app')];
|
||||
default: return [new WebsiteLink("https://element.io/get-started")];
|
||||
}
|
||||
}
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.iOS: return [new AppleStoreLink('vector', 'id1083446067')];
|
||||
case Platform.Android: return [new PlayStoreLink('im.vector.app'), new FDroidLink('im.vector.app')];
|
||||
default: return [new WebsiteLink("https://element.io/get-started")];
|
||||
}
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {
|
||||
const idx = trustedWebInstances.indexOf(link.webInstances[this.id])
|
||||
|
@ -20,22 +20,22 @@ import {Maturity, Platform, LinkKind, FlathubLink} from "../types.js";
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Fractal {
|
||||
get id() { return "fractal"; }
|
||||
get name() { return "Fractal"; }
|
||||
get id() { return "fractal"; }
|
||||
get name() { return "Fractal"; }
|
||||
get icon() { return "images/client-icons/fractal.png"; }
|
||||
get author() { return "Daniel Garcia Moreno"; }
|
||||
get homepage() { return "https://gitlab.gnome.org/GNOME/fractal"; }
|
||||
get platforms() { return [Platform.Linux]; }
|
||||
get description() { return 'Fractal is a Matrix Client written in Rust.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
get platforms() { return [Platform.Linux]; }
|
||||
get description() { return 'Fractal is a Matrix Client written in Rust.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
getLinkInstructions(platform, link) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
return "Click the '+' button in the top right and paste the identifier";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
@ -43,7 +43,7 @@ export class Fractal {
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("org.gnome.Fractal")];
|
||||
}
|
||||
|
@ -20,49 +20,49 @@ import {Maturity, Platform, LinkKind, FlathubLink, style} from "../types.js";
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Nheko {
|
||||
get id() { return "nheko"; }
|
||||
get name() { return "Nheko"; }
|
||||
get id() { return "nheko"; }
|
||||
get name() { return "Nheko"; }
|
||||
get icon() { return "images/client-icons/nheko.svg"; }
|
||||
get author() { return "mujx, red_sky, deepbluev7, Konstantinos Sideris"; }
|
||||
get homepage() { return "https://github.com/Nheko-Reborn/nheko"; }
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux]; }
|
||||
get description() { return 'A native desktop app for Matrix that feels more like a mainstream chat app.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {
|
||||
if (platform === Platform.Linux || platform === Platform.Windows) {
|
||||
let identifier = encodeURIComponent(link.identifier.substring(1));
|
||||
let isRoomid = link.identifier.substring(0, 1) === '!';
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `u/${identifier}?action=chat`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
case LinkKind.Event:
|
||||
if (isRoomid)
|
||||
fragmentPath = `roomid/${identifier}`;
|
||||
else
|
||||
fragmentPath = `r/${identifier}`;
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux]; }
|
||||
get description() { return 'A native desktop app for Matrix that feels more like a mainstream chat app.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {
|
||||
if (platform === Platform.Linux || platform === Platform.Windows) {
|
||||
let identifier = encodeURIComponent(link.identifier.substring(1));
|
||||
let isRoomid = link.identifier.substring(0, 1) === '!';
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `u/${identifier}?action=chat`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
case LinkKind.Event:
|
||||
if (isRoomid)
|
||||
fragmentPath = `roomid/${identifier}`;
|
||||
else
|
||||
fragmentPath = `r/${identifier}`;
|
||||
|
||||
if (link.kind === LinkKind.Event)
|
||||
fragmentPath += `/e/${encodeURIComponent(link.eventId.substring(1))}`;
|
||||
fragmentPath += '?action=join';
|
||||
fragmentPath += link.servers.map(server => `&via=${encodeURIComponent(server)}`).join('');
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
return;
|
||||
}
|
||||
return `matrix:${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
if (link.kind === LinkKind.Event)
|
||||
fragmentPath += `/e/${encodeURIComponent(link.eventId.substring(1))}`;
|
||||
fragmentPath += '?action=join';
|
||||
fragmentPath += link.servers.map(server => `&via=${encodeURIComponent(server)}`).join('');
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
return;
|
||||
}
|
||||
return `matrix:${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
switch (link.kind) {
|
||||
case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)];
|
||||
case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)];
|
||||
}
|
||||
}
|
||||
getLinkInstructions(platform, link) {
|
||||
switch (link.kind) {
|
||||
case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)];
|
||||
case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)];
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
switch (link.kind) {
|
||||
@ -71,7 +71,7 @@ export class Nheko {
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("io.github.NhekoReborn.Nheko")];
|
||||
}
|
||||
|
@ -20,23 +20,23 @@ import {Maturity, Platform, LinkKind, WebsiteLink, style} from "../types.js";
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Weechat {
|
||||
get id() { return "weechat"; }
|
||||
get name() { return "Weechat"; }
|
||||
get id() { return "weechat"; }
|
||||
get name() { return "Weechat"; }
|
||||
get icon() { return "images/client-icons/weechat.svg"; }
|
||||
get author() { return "Poljar"; }
|
||||
get homepage() { return "https://github.com/poljar/weechat-matrix"; }
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux]; }
|
||||
get description() { return 'Command-line Matrix interface using Weechat.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux]; }
|
||||
get description() { return 'Command-line Matrix interface using Weechat.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
getDeepLink(platform, link) {}
|
||||
canInterceptMatrixToLinks(platform) { return false; }
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
switch (link.kind) {
|
||||
case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)];
|
||||
case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)];
|
||||
}
|
||||
}
|
||||
getLinkInstructions(platform, link) {
|
||||
switch (link.kind) {
|
||||
case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)];
|
||||
case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)];
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
switch (link.kind) {
|
||||
@ -45,7 +45,7 @@ export class Weechat {
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {}
|
||||
getInstallLinks(platform) {}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ import {Tensor} from "./Tensor.js";
|
||||
import {Fluffychat} from "./Fluffychat.js";
|
||||
|
||||
export function createClients() {
|
||||
return [
|
||||
new Element(),
|
||||
new Weechat(),
|
||||
new Nheko(),
|
||||
new Fractal(),
|
||||
new Quaternion(),
|
||||
new Tensor(),
|
||||
new Fluffychat(),
|
||||
];
|
||||
return [
|
||||
new Element(),
|
||||
new Weechat(),
|
||||
new Nheko(),
|
||||
new Fractal(),
|
||||
new Quaternion(),
|
||||
new Tensor(),
|
||||
new Fluffychat(),
|
||||
];
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ export {Platform} from "../Platform.js";
|
||||
|
||||
export class AppleStoreLink {
|
||||
constructor(org, appId) {
|
||||
this._org = org;
|
||||
this._appId = appId;
|
||||
this._org = org;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
@ -40,7 +40,7 @@ export class AppleStoreLink {
|
||||
|
||||
export class PlayStoreLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
@ -58,7 +58,7 @@ export class PlayStoreLink {
|
||||
|
||||
export class FDroidLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
@ -94,7 +94,7 @@ export class FlathubLink {
|
||||
|
||||
export class WebsiteLink {
|
||||
constructor(url) {
|
||||
this._url = url;
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
|
@ -17,10 +17,10 @@ limitations under the License.
|
||||
import {TemplateView} from "../utils/TemplateView.js";
|
||||
|
||||
export class LoadServerPolicyView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "LoadServerPolicyView card"}, [
|
||||
t.div({className: {spinner: true, hidden: vm => !vm.loading}}),
|
||||
render(t, vm) {
|
||||
return t.div({className: "LoadServerPolicyView card"}, [
|
||||
t.div({className: {spinner: true, hidden: vm => !vm.loading}}),
|
||||
t.h2(vm => vm.message)
|
||||
]);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ import {ViewModel} from "../utils/ViewModel.js";
|
||||
import {resolveServer} from "../preview/HomeServer.js";
|
||||
|
||||
export class LoadServerPolicyViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.server = options.server;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.server = options.server;
|
||||
this.message = `Looking up ${this.server} privacy policy…`;
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
|
@ -20,61 +20,76 @@ function noTrailingSlash(url) {
|
||||
|
||||
export async function resolveServer(request, baseURL) {
|
||||
baseURL = noTrailingSlash(baseURL);
|
||||
if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) {
|
||||
baseURL = `https://${baseURL}`;
|
||||
}
|
||||
{
|
||||
const {status, body} = await request(`${baseURL}/.well-known/matrix/client`, {method: "GET"}).response();
|
||||
if (status === 200) {
|
||||
const proposedBaseURL = body?.['m.homeserver']?.base_url;
|
||||
if (typeof proposedBaseURL === "string") {
|
||||
baseURL = noTrailingSlash(proposedBaseURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const {status} = await request(`${baseURL}/_matrix/client/versions`, {method: "GET"}).response();
|
||||
if (status !== 200) {
|
||||
throw new Error(`Invalid versions response from ${baseURL}`);
|
||||
}
|
||||
}
|
||||
return new HomeServer(request, baseURL);
|
||||
if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) {
|
||||
baseURL = `https://${baseURL}`;
|
||||
}
|
||||
{
|
||||
try {
|
||||
const {status, body} = await request(`${baseURL}/.well-known/matrix/client`, {method: "GET"}).response();
|
||||
if (status === 200) {
|
||||
const proposedBaseURL = body?.['m.homeserver']?.base_url;
|
||||
if (typeof proposedBaseURL === "string") {
|
||||
baseURL = noTrailingSlash(proposedBaseURL);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to fetch ${baseURL}/.well-known/matrix/client", e);
|
||||
}
|
||||
}
|
||||
{
|
||||
const {status} = await request(`${baseURL}/_matrix/client/versions`, {method: "GET"}).response();
|
||||
if (status !== 200) {
|
||||
throw new Error(`Invalid versions response from ${baseURL}`);
|
||||
}
|
||||
}
|
||||
return new HomeServer(request, baseURL);
|
||||
}
|
||||
|
||||
export class HomeServer {
|
||||
constructor(request, baseURL) {
|
||||
this._request = request;
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
constructor(request, baseURL) {
|
||||
this._request = request;
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
async getUserProfile(userId) {
|
||||
const {body} = await this._request(`${this.baseURL}/_matrix/client/r0/profile/${encodeURIComponent(userId)}`).response();
|
||||
return body;
|
||||
}
|
||||
async getUserProfile(userId) {
|
||||
const {body} = await this._request(`${this.baseURL}/_matrix/client/r0/profile/${encodeURIComponent(userId)}`).response();
|
||||
return body;
|
||||
}
|
||||
|
||||
async findPublicRoomById(roomId) {
|
||||
const {body, status} = await this._request(`${this.baseURL}/_matrix/client/r0/directory/list/room/${encodeURIComponent(roomId)}`).response();
|
||||
if (status !== 200 || body.visibility !== "public") {
|
||||
return;
|
||||
}
|
||||
let nextBatch;
|
||||
do {
|
||||
const queryParams = encodeQueryParams({limit: 10000, since: nextBatch});
|
||||
const {body, status} = await this._request(`${this.baseURL}/_matrix/client/r0/publicRooms?${queryParams}`).response();
|
||||
nextBatch = body.next_batch;
|
||||
const publicRoom = body.chunk.find(c => c.room_id === roomId);
|
||||
if (publicRoom) {
|
||||
return publicRoom;
|
||||
}
|
||||
} while (nextBatch);
|
||||
}
|
||||
// MSC3266 implementation
|
||||
async getRoomSummary(roomIdOrAlias, viaServers) {
|
||||
let query;
|
||||
if (viaServers.length > 0) {
|
||||
query = "?" + viaServers.map(server => `via=${encodeURIComponent(server)}`).join('&');
|
||||
}
|
||||
const {body, status} = await this._request(`${this.baseURL}/_matrix/client/unstable/im.nheko.summary/rooms/${encodeURIComponent(roomIdOrAlias)}/summary${query}`).response();
|
||||
if (status !== 200) return;
|
||||
return body;
|
||||
}
|
||||
|
||||
async getRoomIdFromAlias(alias) {
|
||||
const {status, body} = await this._request(`${this.baseURL}/_matrix/client/r0/directory/room/${encodeURIComponent(alias)}`).response();
|
||||
if (status === 200) {
|
||||
return body.room_id;
|
||||
}
|
||||
}
|
||||
async findPublicRoomById(roomId) {
|
||||
const {body, status} = await this._request(`${this.baseURL}/_matrix/client/r0/directory/list/room/${encodeURIComponent(roomId)}`).response();
|
||||
if (status !== 200 || body.visibility !== "public") {
|
||||
return;
|
||||
}
|
||||
let nextBatch;
|
||||
do {
|
||||
const queryParams = encodeQueryParams({limit: 10000, since: nextBatch});
|
||||
const {body, status} = await this._request(`${this.baseURL}/_matrix/client/r0/publicRooms?${queryParams}`).response();
|
||||
nextBatch = body.next_batch;
|
||||
const publicRoom = body.chunk.find(c => c.room_id === roomId);
|
||||
if (publicRoom) {
|
||||
return publicRoom;
|
||||
}
|
||||
} while (nextBatch);
|
||||
}
|
||||
|
||||
async getRoomIdFromAlias(alias) {
|
||||
const {status, body} = await this._request(`${this.baseURL}/_matrix/client/r0/directory/room/${encodeURIComponent(alias)}`).response();
|
||||
if (status === 200) {
|
||||
return body.room_id;
|
||||
}
|
||||
}
|
||||
|
||||
async getPrivacyPolicyUrl(lang = "en") {
|
||||
const headers = new Map();
|
||||
@ -94,7 +109,7 @@ export class HomeServer {
|
||||
}
|
||||
}
|
||||
|
||||
mxcUrlThumbnail(url, width, height, method) {
|
||||
mxcUrlThumbnail(url, width, height, method) {
|
||||
const parts = parseMxcUrl(url);
|
||||
if (parts) {
|
||||
const [serverName, mediaId] = parts;
|
||||
|
@ -43,7 +43,7 @@ class LoadingPreviewView extends TemplateView {
|
||||
}
|
||||
|
||||
class LoadedPreviewView extends TemplateView {
|
||||
render(t, vm) {
|
||||
render(t, vm) {
|
||||
const avatar = t.map(vm => vm.avatarUrl, (avatarUrl, t) => {
|
||||
if (avatarUrl) {
|
||||
return t.img({className: "avatar", src: avatarUrl});
|
||||
@ -51,12 +51,12 @@ class LoadedPreviewView extends TemplateView {
|
||||
return t.div({className: "defaultAvatar"});
|
||||
}
|
||||
});
|
||||
return t.div([
|
||||
t.div({className: "avatarContainer"}, avatar),
|
||||
t.h1(vm => vm.name),
|
||||
t.p({className: {identifier: true, hidden: vm => !vm.identifier}}, vm => vm.identifier),
|
||||
t.div({className: {memberCount: true, hidden: vm => !vm.memberCount}}, t.p([vm => vm.memberCount, " members"])),
|
||||
t.p({className: {topic: true, hidden: vm => !vm.topic}}, [vm => vm.topic]),
|
||||
]);
|
||||
}
|
||||
return t.div({className: vm.isSpaceRoom ? "mxSpace" : undefined}, [
|
||||
t.div({className: "avatarContainer"}, avatar),
|
||||
t.h1(vm => vm.name),
|
||||
t.p({className: {identifier: true, hidden: vm => !vm.identifier}}, vm => vm.identifier),
|
||||
t.div({className: {memberCount: true, hidden: vm => !vm.memberCount}}, t.p([vm => vm.memberCount, " members"])),
|
||||
t.p({className: {topic: true, hidden: vm => !vm.topic}}, [vm => vm.topic]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -21,92 +21,101 @@ import {ClientListViewModel} from "../open/ClientListViewModel.js";
|
||||
import {ClientViewModel} from "../open/ClientViewModel.js";
|
||||
|
||||
export class PreviewViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const { link, consentedServers } = options;
|
||||
this._link = link;
|
||||
this._consentedServers = consentedServers;
|
||||
this.loading = false;
|
||||
this.name = this._link.identifier;
|
||||
this.avatarUrl = null;
|
||||
this.identifier = null;
|
||||
this.memberCount = null;
|
||||
this.topic = null;
|
||||
this.domain = null;
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const { link, consentedServers } = options;
|
||||
this._link = link;
|
||||
this._consentedServers = consentedServers;
|
||||
this.loading = false;
|
||||
this.name = this._link.identifier;
|
||||
this.avatarUrl = null;
|
||||
this.identifier = null;
|
||||
this.memberCount = null;
|
||||
this.topic = null;
|
||||
this.domain = null;
|
||||
this.failed = false;
|
||||
}
|
||||
this.isSpaceRoom = false;
|
||||
}
|
||||
|
||||
async load() {
|
||||
async load() {
|
||||
const {kind} = this._link;
|
||||
const supportsPreview = kind === LinkKind.User || kind === LinkKind.Room || kind === LinkKind.Event;
|
||||
if (supportsPreview) {
|
||||
this.loading = true;
|
||||
this.emitChange();
|
||||
for (const server of this._consentedServers) {
|
||||
try {
|
||||
const homeserver = await resolveServer(this.request, server);
|
||||
switch (this._link.kind) {
|
||||
case LinkKind.User:
|
||||
await this._loadUserPreview(homeserver, this._link.identifier);
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
this.loading = true;
|
||||
this.emitChange();
|
||||
for (const server of this._consentedServers) {
|
||||
try {
|
||||
const homeserver = await resolveServer(this.request, server);
|
||||
switch (this._link.kind) {
|
||||
case LinkKind.User:
|
||||
await this._loadUserPreview(homeserver, this._link.identifier);
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
case LinkKind.Event:
|
||||
await this._loadRoomPreview(homeserver, this._link);
|
||||
break;
|
||||
}
|
||||
// assume we're done if nothing threw
|
||||
this.domain = server;
|
||||
await this._loadRoomPreview(homeserver, this._link);
|
||||
break;
|
||||
}
|
||||
// assume we're done if nothing threw
|
||||
this.domain = server;
|
||||
this.loading = false;
|
||||
this.emitChange();
|
||||
this.emitChange();
|
||||
return;
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this._setNoPreview(this._link);
|
||||
this._setNoPreview(this._link);
|
||||
if (this._consentedServers.length && supportsPreview) {
|
||||
this.domain = this._consentedServers[this._consentedServers.length - 1];
|
||||
this.failed = true;
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
get hasTopic() { return this._link.kind === LinkKind.Room; }
|
||||
get hasMemberCount() { return this.hasTopic; }
|
||||
|
||||
async _loadUserPreview(homeserver, userId) {
|
||||
const profile = await homeserver.getUserProfile(userId);
|
||||
this.name = profile.displayname || userId;
|
||||
this.avatarUrl = profile.avatar_url ?
|
||||
homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") :
|
||||
null;
|
||||
this.identifier = userId;
|
||||
}
|
||||
async _loadUserPreview(homeserver, userId) {
|
||||
const profile = await homeserver.getUserProfile(userId);
|
||||
this.name = profile.displayname || userId;
|
||||
this.avatarUrl = profile.avatar_url ?
|
||||
homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") :
|
||||
null;
|
||||
this.identifier = userId;
|
||||
}
|
||||
|
||||
async _loadRoomPreview(homeserver, link) {
|
||||
let publicRoom;
|
||||
if (link.identifierKind === IdentifierKind.RoomId) {
|
||||
publicRoom = await homeserver.findPublicRoomById(link.identifier);
|
||||
} else if (link.identifierKind === IdentifierKind.RoomAlias) {
|
||||
const roomId = await homeserver.getRoomIdFromAlias(link.identifier);
|
||||
if (roomId) {
|
||||
publicRoom = await homeserver.findPublicRoomById(roomId);
|
||||
}
|
||||
}
|
||||
this.name = publicRoom?.name || publicRoom?.canonical_alias || link.identifier;
|
||||
this.avatarUrl = publicRoom?.avatar_url ?
|
||||
homeserver.mxcUrlThumbnail(publicRoom.avatar_url, 64, 64, "crop") :
|
||||
null;
|
||||
this.memberCount = publicRoom?.num_joined_members;
|
||||
this.topic = publicRoom?.topic;
|
||||
this.identifier = publicRoom?.canonical_alias || link.identifier;
|
||||
async _loadRoomPreview(homeserver, link) {
|
||||
let publicRoom;
|
||||
if (link.identifierKind === IdentifierKind.RoomId || link.identifierKind === IdentifierKind.RoomAlias) {
|
||||
publicRoom = await homeserver.getRoomSummary(link.identifier, link.servers);
|
||||
}
|
||||
|
||||
if (!publicRoom) {
|
||||
if (link.identifierKind === IdentifierKind.RoomId) {
|
||||
publicRoom = await homeserver.findPublicRoomById(link.identifier);
|
||||
} else if (link.identifierKind === IdentifierKind.RoomAlias) {
|
||||
const roomId = await homeserver.getRoomIdFromAlias(link.identifier);
|
||||
if (roomId) {
|
||||
publicRoom = await homeserver.findPublicRoomById(roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.name = publicRoom?.name || publicRoom?.canonical_alias || link.identifier;
|
||||
this.avatarUrl = publicRoom?.avatar_url ?
|
||||
homeserver.mxcUrlThumbnail(publicRoom.avatar_url, 64, 64, "crop") :
|
||||
null;
|
||||
this.memberCount = publicRoom?.num_joined_members;
|
||||
this.topic = publicRoom?.topic;
|
||||
this.identifier = publicRoom?.canonical_alias || link.identifier;
|
||||
this.isSpaceRoom = publicRoom?.room_type === "m.space";
|
||||
if (this.identifier === this.name) {
|
||||
this.identifier = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setNoPreview(link) {
|
||||
this.name = link.identifier;
|
||||
|
Loading…
Reference in New Issue
Block a user