Compare commits
99 Commits
Author | SHA1 | Date |
---|---|---|
stryan | 145569fcc2 | |
stryan | c1dc85ec72 | |
Michael Telatynski | 95fa6d7245 | |
dependabot[bot] | 0c19eceb0e | |
AJ Jordan | 990bef4d17 | |
Felix Stupp | 6091a1af32 | |
3nt3 | 7b6233217c | |
Travis Ralston | 2cf8119800 | |
Eric Eastwood | 68e81ddd79 | |
Eric Eastwood | 97f77d141a | |
Michael Telatynski | 7c4e0f3c23 | |
dependabot[bot] | d7a132d88c | |
Michael Telatynski | 636d899b65 | |
dependabot[bot] | 23a0f4e876 | |
Michael Telatynski | fde53099eb | |
Michael Telatynski | d487c936ab | |
Thibault Martin | 4932e49874 | |
Andrew Morgan | 776e337d9b | |
Thibault Martin | 13d9145fbe | |
Thibault Martin | 43a3fb9628 | |
Thibault Martin | b5fcd1dfd7 | |
Thibault Martin | 6280514a53 | |
Thibault Martin | 015b9db853 | |
Thibault Martin | 3b3e99f937 | |
Thibault Martin | b3fb015bcd | |
0x1a8510f2 | 56d523c804 | |
Michael Telatynski | a2b6e0bf5a | |
dependabot[bot] | a99e918c1f | |
Martin Giger | d2d69f4c06 | |
Thib | 44a7ce8821 | |
Thibault Martin | f08c12066e | |
Michael Telatynski | c39e7e1f3a | |
Michael Telatynski | 58f63c4afc | |
Michael Telatynski | d7c108588e | |
Nicolas Werner | 0ca4d66a50 | |
Thibault Martin | 0a15f14af0 | |
Carl Schwan | 2c3e1daaa4 | |
Thibault Martin | eefb744467 | |
Thibault Martin | b7b1a33258 | |
Maze | 783630b89b | |
Tobias Speicher | d20d7734cd | |
Bruno Windels | 3a8cc2d4d5 | |
Andrew Ryan | 1839eaae3d | |
Carl Schwan | 9f28514046 | |
Carl Schwan | a1bb51ad2d | |
Carl Schwan | 95cc966e28 | |
stryan | fbd45c3326 | |
stryan | fbdde7eab3 | |
Aaron Raimist | 5f4c9a7c07 | |
Bruno Windels | a39c40539f | |
Bruno Windels | aa272ece4d | |
Bruno Windels | 85678ea5cc | |
Bruno Windels | 4d779d5f52 | |
Bruno Windels | ded399d35d | |
James Salter | c378eb4b4a | |
James Salter | b302bd6829 | |
Caleb Connolly | 72374d91b1 | |
Krille Fear | d52e84ee23 | |
Krille Fear | 994ad17fc9 | |
Aaron Raimist | e71021e8aa | |
Aaron Raimist | b5b8e9a743 | |
Paul Tötterman | b0281fde72 | |
Bruno Windels | 7d22a57874 | |
Bruno Windels | cb63e2d8d7 | |
Twilight Sparkle | c22b2f942b | |
Bruno Windels | 27c74448d6 | |
Bruno Windels | b32b4b8533 | |
Bruno Windels | 1b6d1de059 | |
Bruno Windels | 41d7308ba8 | |
Bruno Windels | a460b52e03 | |
Danila Fedorin | 3802829557 | |
Bruno Windels | 874635e92a | |
Bruno Windels | 385c8532eb | |
Danila Fedorin | 5f5359ae65 | |
Danila Fedorin | e2fb5de595 | |
Danila Fedorin | b57fbd8b6d | |
Danila Fedorin | 0844279c58 | |
Danila Fedorin | e4aeadecd7 | |
Danila Fedorin | 628e99ba2d | |
Danila Fedorin | 878663ca07 | |
Bruno Windels | 88116bdfa6 | |
Danila Fedorin | a773daa0cd | |
Bruno Windels | 55d60cb368 | |
Bruno Windels | 24f8987972 | |
Danila Fedorin | 725a7efb3c | |
Danila Fedorin | f862ed1403 | |
Danila Fedorin | 34b30de288 | |
Michael Telatynski | c25a9dae4d | |
dependabot[bot] | defc8b9cfd | |
Michael Telatynski | acd881385c | |
Michael Telatynski | 9caf2ce268 | |
Michael Telatynski | 7d2dd5cc68 | |
Michael Telatynski | 4bc70d99f4 | |
Michael Telatynski | eb9ebaf28e | |
Michael Telatynski | 5c2f2b0684 | |
Bruno Windels | 2acec4f124 | |
Bruno Windels | 128164aa23 | |
Bruno Windels | 57737a4655 | |
dependabot[bot] | cffba9252a |
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
build
|
||||
*.tar.gz
|
||||
/.idea
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# originally from https://github.com/henryclw/matrix.to/blob/dev-docker/Dockerfile
|
||||
FROM node:lts-alpine
|
||||
|
||||
RUN apk update
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . ./
|
||||
|
||||
RUN yarn
|
||||
|
||||
RUN yarn build
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
ENTRYPOINT ["yarn", "start"]
|
|
@ -63,6 +63,15 @@ visitors.
|
|||
(Technically the # and @ in the URL fragment should probably be escaped, but in
|
||||
practice for legibility we bend the rules and include it verbatim)
|
||||
|
||||
### Optional parameters
|
||||
|
||||
https://matrix.to/#/#matrix:matrix.org?web-instance[element.io]=chat.mozilla.org
|
||||
|
||||
- `client`, e.g. `client=im.fluffychat`, `client=element.io`
|
||||
- `web-instance[]`, e.g. `web-instance[element.io]=chat.mozilla.org`.
|
||||
- For [matrix.to](https://matrix.to/), we have a list of [trusted web instances configured](src/open/clients/Element.js) (see `trustedWebInstances`).
|
||||
- `via`, e.g. `via=mozilla.org`
|
||||
|
||||
You can discuss matrix.to in
|
||||
[`#matrix.to:matrix.org`](https://matrix.to/#/#matrix.to:matrix.org)
|
||||
|
||||
|
|
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;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In -->
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||
x="0px" y="0px" width="18px" height="18px" viewBox="0 0 18 18" enable-background="new 0 0 18 18" xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<g>
|
||||
<g>
|
||||
<circle fill="#FFFFFF" cx="9" cy="9" r="8.5"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M9,0C4,0,0,4,0,9c0,5,4,9,9,9c5,0,9-4,9-9C18,4,14,0,9,0z M1.2,10.8l3.5-2.3c0-0.1,0-0.2,0-0.3c0-1.8,1.3-3.2,3.1-3.4
|
||||
c0.1,0,0.2,0,0.4,0c1.2,0,2.3,0.6,2.9,1.6c0.3-0.1,0.6-0.1,0.9-0.1c0.4,0,0.8,0,1.2,0.1c0.7,0.2,1.4,0.5,2,0.9
|
||||
C14.6,7.1,14,7,13.3,7c-1.2,0-2.2,0.4-2.9,1.4c-0.7,0.9-1.1,2-1.1,3.2c0,1.5-0.4,2.9-1.3,4.2c-0.3,0.4-0.5,0.7-0.8,1
|
||||
C4.2,16.1,1.9,13.8,1.2,10.8z"/>
|
||||
<circle cx="9.5" cy="6.4" r="0.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 871 B |
|
@ -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 |
|
@ -0,0 +1,34 @@
|
|||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M51 12C51 10.8954 50.1046 10 49 10H13C11.8954 10 11 10.8954 11 12V38C11 39.1046 11.8954 40 13 40H30.5858C30.851 40 31.1054 40.1054 31.2929 40.2929L39.2929 48.2929C39.9229 48.9229 41 48.4767 41 47.5858V41C41 40.4477 41.4477 40 42 40H49C50.1046 40 51 39.1046 51 38V12Z" fill="url(#paint0_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 37V38C11 39.1046 11.8954 40 13 40H30.5858C30.851 40 31.1054 40.1054 31.2929 40.2929L39.2929 48.2929C39.9229 48.9229 41 48.4767 41 47.5858V46.5858C41 47.4767 39.9229 47.9229 39.2929 47.2929L31.2929 39.2929C31.1054 39.1054 30.851 39 30.5858 39H13C11.8954 39 11 38.1046 11 37ZM41 41C41 40.4477 41.4477 40 42 40H49C50.1046 40 51 39.1046 51 38V37C51 38.1046 50.1046 39 49 39H42C41.4477 39 41 39.4477 41 40V41Z" fill="#000405" fill-opacity="0.1"/>
|
||||
<path d="M8 10C8 8.89543 8.89543 8 10 8H46C47.1046 8 48 8.89543 48 10V36C48 37.1046 47.1046 38 46 38H28.4142C28.149 38 27.8946 38.1054 27.7071 38.2929L19.7071 46.2929C19.0771 46.9229 18 46.4767 18 45.5858V39C18 38.4477 17.5523 38 17 38H10C8.89543 38 8 37.1046 8 36V10Z" fill="url(#paint1_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 35V36C8 37.1046 8.89543 38 10 38H17C17.5523 38 18 38.4477 18 39V38C18 37.4477 17.5523 37 17 37H10C8.89543 37 8 36.1046 8 35ZM18 44.5858V45.5858C18 46.4767 19.0771 46.9229 19.7071 46.2929L27.7071 38.2929C27.8946 38.1054 28.149 38 28.4142 38H46C47.1046 38 48 37.1046 48 36V35C48 36.1046 47.1046 37 46 37H28.4142C28.149 37 27.8946 37.1054 27.7071 37.2929L19.7071 45.2929C19.0771 45.9229 18 45.4767 18 44.5858Z" fill="#031C5A" fill-opacity="0.1"/>
|
||||
<rect x="13" y="14" width="30" height="4" rx="2" fill="url(#paint2_linear)"/>
|
||||
<rect x="12.5" y="13.5" width="31" height="5" rx="2.5" stroke="#004E6E" stroke-opacity="0.1"/>
|
||||
<rect x="13" y="21" width="25" height="4" rx="2" fill="url(#paint3_linear)"/>
|
||||
<rect x="12.5" y="20.5" width="26" height="5" rx="2.5" stroke="#004E6E" stroke-opacity="0.1"/>
|
||||
<rect x="13" y="28" width="20" height="4" rx="2" fill="url(#paint4_linear)"/>
|
||||
<rect x="12.5" y="27.5" width="21" height="5" rx="2.5" stroke="#004E6E" stroke-opacity="0.1"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="51" y1="10" x2="11" y2="50" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6EB4D9"/>
|
||||
<stop offset="1" stop-color="#004E6E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="8" y1="8" x2="48" y2="48" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#44F0D3"/>
|
||||
<stop offset="1" stop-color="#3DAEE9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="43" y1="18" x2="19.5854" y2="-11.2683" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D1D5D9"/>
|
||||
<stop offset="1" stop-color="#FCFFFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear" x1="38" y1="25" x2="14.02" y2="0.0208158" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D1D5D9"/>
|
||||
<stop offset="1" stop-color="#FCFFFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear" x1="33" y1="32" x2="9.39344" y2="12.3279" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D1D5D9"/>
|
||||
<stop offset="1" stop-color="#FCFFFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 43 KiB |
23
index.html
|
@ -1,17 +1,22 @@
|
|||
<!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">
|
||||
<meta property="twitter:card" content="summary"/>
|
||||
<meta property="twitter:image" content="https://matrix.org/blog/img/matrix-logo.png"/>
|
||||
<meta property="twitter:site" content="@matrixdotorg"/>
|
||||
<meta property="twitter:title" content="Matrix - Decentralised and secure communication"/>
|
||||
<meta property="twitter:description" content="You're invited to talk on Matrix. If you don't already have a client this link will help you pick one, and join the conversation. If you already have one, this link will help you join the conversation"/>
|
||||
</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>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=matrix.to for Saintnet
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/yarn start
|
||||
Restart=always
|
||||
User=node_matrix_to
|
||||
Environment=PATH=/usr/bin:/usr/local/bin
|
||||
Environment=NODE_ENV=production
|
||||
WorkingDirectory=/srv/matrix.to
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix.to",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.16",
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
|
|
|
@ -146,7 +146,7 @@ function buildAppleAssociatedAppsFile(clients) {
|
|||
async function buildCss(entryPath, targetDir, assets) {
|
||||
entryPath = path.join(projectDir, entryPath);
|
||||
const assetUrlMapper = ({absolutePath}) => {
|
||||
const relPath = absolutePath.substr(projectDir.length);
|
||||
const relPath = absolutePath.slice(projectDir.length);
|
||||
return assets.resolve(path.join(targetDir, relPath));
|
||||
};
|
||||
|
||||
|
@ -211,7 +211,7 @@ class AssetMap {
|
|||
if (!resourcePath.startsWith(this._targetDir)) {
|
||||
throw new Error(`absolute path ${resourcePath} that is not within target dir ${this._targetDir}`);
|
||||
}
|
||||
relPath = resourcePath.substr(this._targetDir.length + 1); // + 1 for the /
|
||||
relPath = resourcePath.slice(this._targetDir.length + 1); // + 1 for the /
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ class AssetMap {
|
|||
if (!assetMap.directory.startsWith(this.directory)) {
|
||||
throw new Error(`map directory doesn't start with this directory: ${assetMap.directory} ${this.directory}`);
|
||||
}
|
||||
const relSubRoot = assetMap.directory.substr(this.directory.length + 1);
|
||||
const relSubRoot = assetMap.directory.slice(this.directory.length + 1);
|
||||
for (const [key, value] of assetMap._assets.entries()) {
|
||||
this._assets.set(path.join(relSubRoot, key), path.join(relSubRoot, value));
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {TemplateView} from "./utils/TemplateView.js";
|
||||
import {LinkKind, IdentifierKind} from "./Link.js";
|
||||
|
||||
export class InvalidUrlView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({ className: "DisclaimerView card" }, [
|
||||
t.h1("Invalid URL"),
|
||||
t.p([
|
||||
'The link you have entered is not valid. If you like, you can ',
|
||||
t.a({ href: "#/" }, 'return to the home page.')
|
||||
]),
|
||||
vm.validFixes.length ? this._renderValidFixes(t, vm.validFixes) : [],
|
||||
]);
|
||||
}
|
||||
|
||||
_describeRoom(identifierKind) {
|
||||
return identifierKind === IdentifierKind.RoomAlias ? "room alias" : "room";
|
||||
}
|
||||
|
||||
_describeLinkKind(linkKind, identifierKind) {
|
||||
switch (linkKind) {
|
||||
case LinkKind.Room: return `The ${this._describeRoom(identifierKind)} `;
|
||||
case LinkKind.User: return "The user ";
|
||||
case LinkKind.Group: return "The group ";
|
||||
case LinkKind.Event: return `An event in ${this._describeRoom(identifierKind)} `;
|
||||
}
|
||||
}
|
||||
|
||||
_renderValidFixes(t, validFixes) {
|
||||
return t.p([
|
||||
'Did you mean any of the following?',
|
||||
t.ul(validFixes.map(fix =>
|
||||
t.li([
|
||||
this._describeLinkKind(fix.link.kind, fix.link.identifierKind),
|
||||
t.a({ href: fix.url }, fix.link.identifier)
|
||||
])
|
||||
))
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {ViewModel} from "./utils/ViewModel.js";
|
||||
import {tryFixUrl} from "./Link.js";
|
||||
|
||||
export class InvalidUrlViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.validFixes = tryFixUrl(options.fragment);
|
||||
}
|
||||
}
|
234
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) {
|
||||
|
@ -45,8 +45,8 @@ function getWebInstanceMap(queryParams) {
|
|||
const postfix = "]";
|
||||
const webInstanceParams = queryParams.filter(([key]) => key.startsWith(prefix) && key.endsWith(postfix));
|
||||
const webInstances = webInstanceParams.map(([key, value]) => {
|
||||
const noPrefix = key.substr(prefix.length);
|
||||
const clientId = noPrefix.substr(0, noPrefix.length - postfix.length);
|
||||
const noPrefix = key.slice(prefix.length);
|
||||
const clientId = noPrefix.slice(0, -postfix.length);
|
||||
return [clientId, value];
|
||||
});
|
||||
return webInstances.reduce((map, [clientId, host]) => {
|
||||
|
@ -56,21 +56,38 @@ 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 function tryFixUrl(fragment) {
|
||||
const attempts = [];
|
||||
const afterHash = fragment.substring(fragment.startsWith("#/") ? 2 : 1);
|
||||
attempts.push('#/@' + afterHash);
|
||||
attempts.push('#/#' + afterHash);
|
||||
attempts.push('#/!' + afterHash);
|
||||
|
||||
const validAttempts = [];
|
||||
for (const attempt of [...new Set(attempts)]) {
|
||||
const link = Link.parseFragment(attempt);
|
||||
if (link) {
|
||||
validAttempts.push({ url: attempt, link });
|
||||
}
|
||||
}
|
||||
return validAttempts;
|
||||
}
|
||||
|
||||
export class Link {
|
||||
static validateIdentifier(identifier) {
|
||||
return !!(
|
||||
|
@ -81,106 +98,115 @@ export class Link {
|
|||
);
|
||||
}
|
||||
|
||||
static parse(fragment) {
|
||||
if (!fragment) {
|
||||
return null;
|
||||
}
|
||||
let [linkStr, queryParamsStr] = fragment.split("?");
|
||||
static parseIdentifier(identifier) {
|
||||
return Link._parse(identifier);
|
||||
}
|
||||
|
||||
let viaServers = [];
|
||||
static parseFragment(fragment) {
|
||||
if (!fragment) {
|
||||
return null;
|
||||
}
|
||||
let [linkStr, queryParamsStr] = fragment.split("?");
|
||||
if (!linkStr.startsWith("#/")) {
|
||||
return null;
|
||||
}
|
||||
linkStr = linkStr.slice(2);
|
||||
const [identifier, eventId] = linkStr.split("/");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return Link._parse(identifier, eventId, clientId, viaServers, webInstances);
|
||||
}
|
||||
|
||||
if (linkStr.startsWith("#/")) {
|
||||
linkStr = linkStr.substr(2);
|
||||
}
|
||||
static _parse(identifier, eventId = undefined, clientId = null, viaServers = [], webInstances = {}) {
|
||||
if (!identifier) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,27 +18,32 @@ import {TemplateView} from "./utils/TemplateView.js";
|
|||
import {OpenLinkView} from "./open/OpenLinkView.js";
|
||||
import {CreateLinkView} from "./create/CreateLinkView.js";
|
||||
import {LoadServerPolicyView} from "./policy/LoadServerPolicyView.js";
|
||||
import {DisclaimerView} from "./disclaimer/DisclaimerView.js";
|
||||
import {InvalidUrlView} from "./InvalidUrlView.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.invalidUrlViewModel, invalidVM => invalidVM ? new InvalidUrlView(invalidVM) : null),
|
||||
t.mapView(vm => vm.showDisclaimer, disclaimer => disclaimer ? new DisclaimerView() : null),
|
||||
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")),
|
||||
t.li(t.a({href: "#/disclaimer/"}, "Disclaimer")),
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -20,54 +20,79 @@ import {OpenLinkViewModel} from "./open/OpenLinkViewModel.js";
|
|||
import {createClients} from "./open/clients/index.js";
|
||||
import {CreateLinkViewModel} from "./create/CreateLinkViewModel.js";
|
||||
import {LoadServerPolicyViewModel} from "./policy/LoadServerPolicyViewModel.js";
|
||||
import {InvalidUrlViewModel} from "./InvalidUrlViewModel.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.invalidUrlViewModel = null;
|
||||
this.showDisclaimer = false;
|
||||
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(newLink, oldLink) {
|
||||
this.link = newLink;
|
||||
if (!newLink) {
|
||||
this.openLinkViewModel = null;
|
||||
} else if (!oldLink || !oldLink.equals(newLink)) {
|
||||
this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
|
||||
link: newLink,
|
||||
clients: createClients(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
updateHash(hash) {
|
||||
_hideLinks() {
|
||||
this.link = null;
|
||||
this.openLinkViewModel = null;
|
||||
this.createLinkViewModel = null;
|
||||
}
|
||||
|
||||
updateHash(hash) {
|
||||
// All view models except openLink are re-created anyway. Might as well
|
||||
// clear them to avoid having to manually reset (n-1)/n view models in every case.
|
||||
// That just doesn't scale well when we add new views.
|
||||
const oldLink = this.link;
|
||||
this.invalidUrlViewModel = null;
|
||||
this.showDisclaimer = false;
|
||||
this.loadServerPolicyViewModel = null;
|
||||
this.createLinkViewModel = null;
|
||||
let newLink;
|
||||
if (hash.startsWith("#/policy/")) {
|
||||
const server = hash.substr(9);
|
||||
const server = hash.slice(9);
|
||||
this._updateChildVMs(null, oldLink);
|
||||
this.loadServerPolicyViewModel = new LoadServerPolicyViewModel(this.childOptions({server}));
|
||||
this.loadServerPolicyViewModel.load();
|
||||
} else if (hash.startsWith("#/disclaimer/")) {
|
||||
this._updateChildVMs(null, oldLink);
|
||||
this.showDisclaimer = true;
|
||||
} else if (hash === "" || hash === "#" || hash === "#/") {
|
||||
this._updateChildVMs(null, oldLink);
|
||||
this.createLinkViewModel = new CreateLinkViewModel(this.childOptions());
|
||||
} else if (newLink = Link.parseFragment(hash)) {
|
||||
this._updateChildVMs(newLink, oldLink);
|
||||
} else {
|
||||
const oldLink = this.link;
|
||||
this.link = Link.parse(hash);
|
||||
this._updateChildVMs(oldLink);
|
||||
this._updateChildVMs(null, oldLink);
|
||||
this.invalidUrlViewModel = new InvalidUrlViewModel(this.childOptions({
|
||||
fragment: hash
|
||||
}));
|
||||
}
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
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,18 +19,18 @@ 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);
|
||||
}
|
||||
|
||||
async createLink(identifier) {
|
||||
this._link = Link.parse(identifier);
|
||||
this._link = Link.parseIdentifier(identifier);
|
||||
if (this._link) {
|
||||
this.openLink("#" + this._link.toFragment());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
|
||||
export class DisclaimerView extends TemplateView {
|
||||
render(t) {
|
||||
return t.div({ className: "DisclaimerView card" }, [
|
||||
t.h1("Disclaimer"),
|
||||
t.p(
|
||||
'Matrix.to is a service provided by the Matrix.org Foundation ' +
|
||||
'which allows you to easily create invites to Matrix rooms and accounts, ' +
|
||||
'regardless of your Matrix homeserver. The service is provided "as is" without ' +
|
||||
'warranty of any kind, either express, implied, statutory or otherwise. ' +
|
||||
'The Matrix.org Foundation shall not be responsible or liable for the room ' +
|
||||
'and account contents shared via this service.'
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
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)
|
||||
|
@ -139,7 +139,7 @@ export class ClientViewModel extends ViewModel {
|
|||
let label = preferredWebInstance;
|
||||
const subDomainIdx = preferredWebInstance.lastIndexOf(".", preferredWebInstance.lastIndexOf("."));
|
||||
if (subDomainIdx !== -1) {
|
||||
label = preferredWebInstance.substr(preferredWebInstance.length - subDomainIdx + 1);
|
||||
label = preferredWebInstance.slice(preferredWebInstance.length - subDomainIdx + 1);
|
||||
}
|
||||
return `Hosted by ${label}`;
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022 3nt3 <gott@3nt3.de>
|
||||
|
||||
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 { Maturity, Platform, LinkKind, FlathubLink, style } from "../types.js";
|
||||
|
||||
export class Cinny {
|
||||
get id() {
|
||||
return "cinny";
|
||||
}
|
||||
get name() {
|
||||
return "Cinny";
|
||||
}
|
||||
get icon() {
|
||||
return "images/client-icons/cinny.svg";
|
||||
}
|
||||
get author() {
|
||||
return "Copyright (c) 2021-present Ajay Bura (ajbura) and contributors";
|
||||
}
|
||||
get homepage() {
|
||||
return "https://cinny.in";
|
||||
}
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.DesktopWeb,
|
||||
Platform.Linux,
|
||||
Platform.macOS,
|
||||
Platform.Windows,
|
||||
];
|
||||
}
|
||||
get description() {
|
||||
return "A Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have an instant messaging application that is easy on people and has a modern touch.";
|
||||
}
|
||||
getMaturity(platform) {
|
||||
return Maturity.Stable;
|
||||
}
|
||||
|
||||
// cinny doesn't support deep links yet
|
||||
getDeepLink(platform, link) {}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
return [
|
||||
"While in Home, Click on '+' in the top left corner, then 'Join with address' and paste the ",
|
||||
style.code(`${link.identifier} `),
|
||||
link.kind === LinkKind.User ? "username" : "identifier",
|
||||
];
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
return link.identifier;
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
|
@ -15,82 +15,91 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {Maturity, Platform, LinkKind,
|
||||
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
|
||||
const trustedWebInstances = [
|
||||
"element.saintnet.tech",
|
||||
"app.element.io", // first one is the default one
|
||||
"develop.element.io",
|
||||
"chat.fedoraproject.org",
|
||||
"chat.fosdem.org",
|
||||
"chat.mozilla.org",
|
||||
"webchat.kde.org",
|
||||
"app.gitter.im",
|
||||
];
|
||||
|
||||
/**
|
||||
* 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/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `room/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
fragmentPath = `group/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `room/${encodeURIComponent(link.identifier)}/${encodeURIComponent(link.eventId)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((link.kind === LinkKind.Event || link.kind === LinkKind.Room) && link.servers.length > 0) {
|
||||
fragmentPath += '?' + link.servers.map(server => `via=${encodeURIComponent(server)}`).join('&');
|
||||
}
|
||||
|
||||
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/download")];
|
||||
}
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {
|
||||
const idx = trustedWebInstances.indexOf(link.webInstances[this.id])
|
||||
|
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Maturity, Platform, LinkKind, FlathubLink, AppleStoreLink, PlayStoreLink, WebsiteLink } from "../types.js";
|
||||
import { Maturity, Platform, LinkKind, FlathubLink, AppleStoreLink, PlayStoreLink, FDroidLink, WebsiteLink } from "../types.js";
|
||||
|
||||
/**
|
||||
* Information on how to deep link to a given matrix client.
|
||||
|
@ -44,8 +44,14 @@ export class Fluffychat {
|
|||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.iOS: return [new AppleStoreLink("fluffychat", "id1551469600")];
|
||||
case Platform.Android: return [new PlayStoreLink("chat.fluffy.fluffychat")];
|
||||
case Platform.Linux: return [new FlathubLink("im.fluffychat.Fluffychat")];
|
||||
case Platform.Android: return [
|
||||
new PlayStoreLink("chat.fluffy.fluffychat"),
|
||||
new FDroidLink('chat.fluffy.fluffychat'),
|
||||
];
|
||||
case Platform.Linux: return [
|
||||
new FlathubLink("im.fluffychat.Fluffychat"),
|
||||
new WebsiteLink("https://fluffychat.im"),
|
||||
];
|
||||
default: return [new WebsiteLink("https://fluffychat.im")];
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +79,13 @@ export class Fluffychat {
|
|||
}
|
||||
}
|
||||
|
||||
getDeepLink(platform, link) { }
|
||||
getDeepLink(platform, link) {
|
||||
switch (platform) {
|
||||
case Platform.Android: return `im.fluffychat://chat/${link.identifier}`;
|
||||
case Platform.iOS: return `im.fluffychat://chat/${link.identifier}`;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
|
|
@ -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")];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
|
||||
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 {Maturity, Platform, LinkKind, FlathubLink, style} from "../types.js";
|
||||
|
||||
export class NeoChat {
|
||||
get id() { return "neochat"; }
|
||||
get name() { return "NeoChat"; }
|
||||
get icon() { return "images/client-icons/org.kde.neochat.svg"; }
|
||||
get author() { return "Tobias Fella and Carl Schwan"; }
|
||||
get homepage() { return "https://apps.kde.org/neochat/"; }
|
||||
get platforms() { return [Platform.Linux]; }
|
||||
get description() { return 'NeoChat is a convergent, cross-platform Matrix client.'; }
|
||||
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; }
|
||||
|
||||
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) {
|
||||
case LinkKind.User: return `/invite ${link.identifier}`;
|
||||
case LinkKind.Room: return `/join ${link.identifier}`;
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("org.kde.neochat")];
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
|
@ -14,55 +14,55 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {Maturity, Platform, LinkKind, FlathubLink, style} from "../types.js";
|
||||
import {Maturity, Platform, LinkKind, FlathubLink, WebsiteLink, 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,9 +71,13 @@ export class Nheko {
|
|||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("io.github.NhekoReborn.Nheko")];
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.Linux: return [
|
||||
new FlathubLink("io.github.NhekoReborn.Nheko"),
|
||||
new WebsiteLink("https://github.com/Nheko-Reborn/nheko/releases/latest"),
|
||||
];
|
||||
default: return [new WebsiteLink("https://github.com/Nheko-Reborn/nheko/releases/latest")];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {Maturity, Platform, LinkKind, FlathubLink, style} from "../types.js";
|
||||
import {Maturity, Platform, LinkKind, FlathubLink, WebsiteLink, style} from "../types.js";
|
||||
|
||||
export class Quaternion {
|
||||
get id() { return "quaternion"; }
|
||||
get name() { return "Quaternion"; }
|
||||
get icon() { return "images/client-icons/quaternion.svg"; }
|
||||
get author() { return "Felix Rohrbach"; }
|
||||
get homepage() { return "https://github.com/Fxrh/Quaternion"; }
|
||||
get author() { return "The Quotient project"; }
|
||||
get homepage() { return "https://github.com/quotient-im/Quaternion"; }
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux]; }
|
||||
get description() { return 'Qt5 and C++ cross-platform desktop Matrix client.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
|
@ -43,8 +43,12 @@ export class Quaternion {
|
|||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("com.github.quaternion")];
|
||||
switch (platform) {
|
||||
case Platform.Linux: return [
|
||||
new FlathubLink("com.github.quaternion"),
|
||||
new WebsiteLink("https://github.com/quotient-im/Quaternion/releases/latest"),
|
||||
];
|
||||
default: return [new WebsiteLink("https://github.com/quotient-im/Quaternion/releases/latest")];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {
|
||||
Maturity, Platform, LinkKind,
|
||||
FDroidLink, FlathubLink, PlayStoreLink, WebsiteLink
|
||||
} from "../types.js";
|
||||
|
||||
const trustedWebInstances = [
|
||||
"app.schildi.chat", // first one is the default one
|
||||
"test.schildi.chat",
|
||||
];
|
||||
|
||||
/**
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class SchildiChat {
|
||||
get id() { return "schildi.chat"; }
|
||||
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android,
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
Platform.DesktopWeb
|
||||
];
|
||||
}
|
||||
|
||||
get icon() { return "images/client-icons/schildichat.svg"; }
|
||||
get name() { return "SchildiChat"; }
|
||||
get description() { return 'Feature-rich messenger for Matrix based on Element with some extras and tweaks.'; }
|
||||
get homepage() { return "https://schildi.chat"; }
|
||||
get author() { return "SchildiChat team"; }
|
||||
getMaturity(platform) { return Maturity.Stable; }
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `user/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `room/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
fragmentPath = `group/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `room/${encodeURIComponent(link.identifier)}/${encodeURIComponent(link.eventId)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((link.kind === LinkKind.Event || link.kind === LinkKind.Room) && link.servers.length > 0) {
|
||||
fragmentPath += '?' + link.servers.map(server => `via=${encodeURIComponent(server)}`).join('&');
|
||||
}
|
||||
|
||||
const isWebPlatform = platform === Platform.DesktopWeb || platform === Platform.MobileWeb;
|
||||
if (isWebPlatform) {
|
||||
let instanceHost = trustedWebInstances[0];
|
||||
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 `schildichat://vector/webapp/#/${fragmentPath}`;
|
||||
} else {
|
||||
return `schildichat://${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) { }
|
||||
getCopyString(platform, link) { }
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.Linux:
|
||||
return [
|
||||
new FlathubLink("chat.schildi.desktop"),
|
||||
new WebsiteLink("https://schildi.chat/desktop/"),
|
||||
]
|
||||
case Platform.Windows || Platform.macOS:
|
||||
return [new WebsiteLink("https://schildi.chat/desktop/")];
|
||||
case Platform.Android:
|
||||
return [
|
||||
new PlayStoreLink('de.spiritcroc.riotx'),
|
||||
new FDroidLink('de.spiritcroc.riotx'),
|
||||
new WebsiteLink("https://schildi.chat/android/"),
|
||||
];
|
||||
default: return [new WebsiteLink("https://schildi.chat")];
|
||||
}
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {
|
||||
const idx = trustedWebInstances.indexOf(link.webInstances[this.id])
|
||||
return idx === -1 ? undefined : trustedWebInstances[idx];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022 0x1a8510f2 <admin@0x1a8510f2.space>
|
||||
|
||||
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 { Maturity, Platform, LinkKind, FlathubLink, AppleStoreLink, PlayStoreLink, FDroidLink, WebsiteLink } from "../types.js";
|
||||
|
||||
export class Syphon {
|
||||
get id() { return "org.syphon.syphon"; }
|
||||
get name() { return "Syphon"; }
|
||||
get icon() { return "images/client-icons/syphon.svg"; }
|
||||
get author() { return "Taylor Ereio"; }
|
||||
get homepage() { return "https://syphon.org"; }
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android,
|
||||
Platform.iOS,
|
||||
Platform.Linux,
|
||||
Platform.Windows,
|
||||
Platform.macOS,
|
||||
//Platform.DesktopWeb, // Supported by flutter but no builds yet
|
||||
];
|
||||
}
|
||||
get description() {
|
||||
return "chat with your privacy and freedom intact";
|
||||
}
|
||||
|
||||
getMaturity(platform) {
|
||||
return Maturity.Alpha;
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.Android: return [
|
||||
new PlayStoreLink("org.tether.tether"),
|
||||
new FDroidLink("org.tether.tether"),
|
||||
];
|
||||
case Platform.iOS: return [
|
||||
new AppleStoreLink("syphon", "id1496285352")
|
||||
];
|
||||
case Platform.Linux: return [
|
||||
new FlathubLink("org.syphon.Syphon"),
|
||||
new WebsiteLink("https://syphon.org"),
|
||||
];
|
||||
default: return [new WebsiteLink("https://syphon.org")];
|
||||
}
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
if (link.kind === LinkKind.User) {
|
||||
return "Open the app, click on the direct message button (inside the floating button \
|
||||
at the bottom), then paste the identifier.";
|
||||
}
|
||||
if (link.kind === LinkKind.Room) {
|
||||
return "Open the app, click on the search public rooms button (inside the floating button \
|
||||
at the bottom), then paste the identifier.";
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
return link.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
/*switch (platform) {
|
||||
case Platform.Android: return `org.tether.tether://chat/${link.identifier}`;
|
||||
case Platform.iOS: return `org.tether.tether://chat/${link.identifier}`;
|
||||
default: break;
|
||||
}*/
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
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 { Maturity, Platform, LinkKind, WebsiteLink, style} from "../types.js";
|
||||
|
||||
/**
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Thunderbird {
|
||||
get id() { return "thunderbird"; }
|
||||
get name() { return "Thunderbird"; }
|
||||
get icon() { return "images/client-icons/thunderbird.svg"; }
|
||||
get author() { return "MZLA Technologies Corporation"; }
|
||||
get homepage() { return "https://www.thunderbird.net"; }
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
];
|
||||
}
|
||||
get description() { return "Thunderbird is a free open-source email, calendar & chat app."; }
|
||||
getMaturity(platform) {
|
||||
return Maturity.Beta;
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
return [new WebsiteLink(this.homepage)];
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
if (link.kind === LinkKind.User) {
|
||||
return "Open the Chat tab, click on 'Add Contact' and paste the username.";
|
||||
}
|
||||
if (link.kind === LinkKind.Room) {
|
||||
return [
|
||||
"Open the Chat tab, click on 'Join Chat' and paste the identifier or type ",
|
||||
style.code(`/join ${link.identifier}`),
|
||||
" in an existing Matrix conversation."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
return link.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
getDeepLink(platform, link) {}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -15,21 +15,31 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {Element} from "./Element.js";
|
||||
import {SchildiChat} from "./SchildiChat.js";
|
||||
import {Weechat} from "./Weechat.js";
|
||||
import {Nheko} from "./Nheko.js";
|
||||
import {Fractal} from "./Fractal.js";
|
||||
import {Quaternion} from "./Quaternion.js";
|
||||
import {Tensor} from "./Tensor.js";
|
||||
import {Fluffychat} from "./Fluffychat.js";
|
||||
import {NeoChat} from "./NeoChat.js";
|
||||
import {Syphon} from "./Syphon.js";
|
||||
import {Thunderbird} from "./Thunderbird.js";
|
||||
import {Cinny} from "./Cinny.js"
|
||||
|
||||
export function createClients() {
|
||||
return [
|
||||
new Element(),
|
||||
new Weechat(),
|
||||
new Nheko(),
|
||||
new Fractal(),
|
||||
new Quaternion(),
|
||||
new Tensor(),
|
||||
new Fluffychat(),
|
||||
];
|
||||
return [
|
||||
new Element(),
|
||||
new SchildiChat(),
|
||||
new Weechat(),
|
||||
new Nheko(),
|
||||
new Fractal(),
|
||||
new Quaternion(),
|
||||
new Tensor(),
|
||||
new Fluffychat(),
|
||||
new NeoChat(),
|
||||
new Syphon(),
|
||||
new Thunderbird(),
|
||||
new Cinny(),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -15,66 +15,81 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
function noTrailingSlash(url) {
|
||||
return url.endsWith("/") ? url.substr(0, url.length - 1) : url;
|
||||
return url.endsWith("/") ? url.slice(0, -1) : 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;
|
||||
|
@ -108,7 +123,7 @@ export class HomeServer {
|
|||
function parseMxcUrl(url) {
|
||||
const prefix = "mxc://";
|
||||
if (url.startsWith(prefix)) {
|
||||
return url.substr(prefix.length).split("/", 2);
|
||||
return url.slice(prefix.length).split("/", 2);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -210,7 +210,7 @@ class TemplateBuilder {
|
|||
setAttribute(node, key, classNames(value));
|
||||
}
|
||||
} else if (key.startsWith("on") && key.length > 2 && isFn) {
|
||||
const eventName = key.substr(2, 1).toLowerCase() + key.substr(3);
|
||||
const eventName = key.slice(2, 3).toLowerCase() + key.slice(3);
|
||||
const handler = value;
|
||||
this._templateView._addEventListener(node, eventName, handler);
|
||||
} else if (isFn) {
|
||||
|
|
146
yarn.lock
|
@ -815,6 +815,46 @@
|
|||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
|
||||
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.0.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||
|
||||
"@jridgewell/set-array@^1.0.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
|
||||
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.9":
|
||||
version "0.3.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
|
||||
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@rollup/plugin-babel@^5.1.0":
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.2.2.tgz#e5623a01dd8e37e004ba87f2de218c611727d9b2"
|
||||
|
@ -900,6 +940,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
acorn@^8.5.0:
|
||||
version "8.7.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
||||
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
|
@ -945,20 +990,20 @@ brace-expansion@^1.1.7:
|
|||
concat-map "0.0.1"
|
||||
|
||||
browserslist@^4.14.5, browserslist@^4.15.0:
|
||||
version "4.15.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.15.0.tgz#3d48bbca6a3f378e86102ffd017d9a03f122bdb0"
|
||||
integrity sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==
|
||||
version "4.16.6"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
|
||||
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001164"
|
||||
colorette "^1.2.1"
|
||||
electron-to-chromium "^1.3.612"
|
||||
caniuse-lite "^1.0.30001219"
|
||||
colorette "^1.2.2"
|
||||
electron-to-chromium "^1.3.723"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.67"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
builtin-modules@^3.1.0:
|
||||
version "3.1.0"
|
||||
|
@ -973,10 +1018,10 @@ call-bind@^1.0.0:
|
|||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.0"
|
||||
|
||||
caniuse-lite@^1.0.30001164, caniuse-lite@^1.0.30001165:
|
||||
version "1.0.30001165"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz#32955490d2f60290bb186bb754f2981917fa744f"
|
||||
integrity sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==
|
||||
caniuse-lite@^1.0.30001165, caniuse-lite@^1.0.30001219:
|
||||
version "1.0.30001230"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
|
||||
integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
|
||||
|
||||
chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
|
@ -1011,12 +1056,7 @@ color-name@1.1.3:
|
|||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
colorette@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
||||
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
||||
|
||||
colorette@^1.2.2:
|
||||
colorette@^1.2.1, colorette@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
|
||||
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
|
||||
|
@ -1166,10 +1206,10 @@ ee-first@1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
electron-to-chromium@^1.3.612:
|
||||
version "1.3.619"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.619.tgz#4dc529ae802f5c9c31e7eea830144340539b62b4"
|
||||
integrity sha512-WFGatwtk7Fw0QcKCZzfGD72hvbcXV8kLY8aFuj0Ip0QRnOtyLYMsc+wXbSjb2w4lk1gcAeNU1/lQ20A+tvuypQ==
|
||||
electron-to-chromium@^1.3.723:
|
||||
version "1.3.739"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9"
|
||||
integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
|
@ -1397,11 +1437,9 @@ jsesc@~0.5.0:
|
|||
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
|
||||
|
||||
json5@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
||||
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
lodash@^4.15.0, lodash@^4.17.19:
|
||||
version "4.17.21"
|
||||
|
@ -1451,9 +1489,9 @@ minimatch@^3.0.4:
|
|||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
|
||||
mkdirp@^0.5.0:
|
||||
version "0.5.5"
|
||||
|
@ -1482,10 +1520,10 @@ nanoid@^3.1.23:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
|
||||
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
|
||||
|
||||
node-releases@^1.1.67:
|
||||
version "1.1.67"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12"
|
||||
integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==
|
||||
node-releases@^1.1.71:
|
||||
version "1.1.72"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
||||
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
||||
|
||||
normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
|
@ -1546,9 +1584,9 @@ path-is-absolute@^1.0.0:
|
|||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picomatch@^2.2.1, picomatch@^2.2.2:
|
||||
version "2.2.2"
|
||||
|
@ -1752,9 +1790,9 @@ semver@7.0.0:
|
|||
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
|
||||
|
||||
semver@^5.4.1, semver@^5.5.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
|
||||
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
|
@ -1802,10 +1840,10 @@ source-map-js@^0.6.2:
|
|||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||
|
||||
source-map-support@~0.5.19:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
@ -1820,11 +1858,6 @@ source-map@^0.6.0, source-map@^0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@~0.7.2:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sourcemap-codec@^1.4.4:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
|
@ -1864,13 +1897,14 @@ supports-color@^7.0.0:
|
|||
has-flag "^4.0.0"
|
||||
|
||||
terser@^5.0.0:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
|
||||
integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
|
||||
version "5.14.2"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
|
||||
integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.2"
|
||||
acorn "^8.5.0"
|
||||
commander "^2.20.0"
|
||||
source-map "~0.7.2"
|
||||
source-map-support "~0.5.19"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
|