Compare commits
287 commits
bwindels/c
...
main
Author | SHA1 | Date | |
---|---|---|---|
a72b9b0e05 | |||
4ed6d78212 | |||
1679bae1c1 | |||
|
3fe1016715 | ||
|
cba383bc32 | ||
|
d2219ba4b6 | ||
|
ab5f922acf | ||
|
a51e28e3fd | ||
|
e97ac79e05 | ||
|
0123ab4988 | ||
|
9750d5e245 | ||
|
861d683af1 | ||
|
fac2f9bf29 | ||
|
6928941868 | ||
|
657fd9cc1a | ||
|
d3458399c5 | ||
|
00ccd16f19 | ||
|
713ab3ee74 | ||
|
dbc054eda7 | ||
|
e04f48135f | ||
|
356e7eb439 | ||
|
6dd6732001 | ||
|
cacaae6d15 | ||
|
7cf14ade52 | ||
|
7f34b74f88 | ||
|
890a7f7536 | ||
|
246cd5e941 | ||
|
95fa6d7245 | ||
|
0c19eceb0e | ||
|
990bef4d17 | ||
|
6091a1af32 | ||
|
7b6233217c | ||
|
2cf8119800 | ||
|
68e81ddd79 | ||
|
97f77d141a | ||
|
e11e4e73e8 | ||
|
7c4e0f3c23 | ||
|
d7a132d88c | ||
|
636d899b65 | ||
|
23a0f4e876 | ||
|
fde53099eb | ||
|
d487c936ab | ||
|
4932e49874 | ||
|
776e337d9b | ||
|
13d9145fbe | ||
|
43a3fb9628 | ||
|
b5fcd1dfd7 | ||
|
6280514a53 | ||
|
015b9db853 | ||
|
3b3e99f937 | ||
|
b3fb015bcd | ||
|
56d523c804 | ||
|
a2b6e0bf5a | ||
|
a99e918c1f | ||
|
d2d69f4c06 | ||
|
626eda057f | ||
|
44a7ce8821 | ||
|
f08c12066e | ||
|
c39e7e1f3a | ||
|
58f63c4afc | ||
|
d7c108588e | ||
|
0ca4d66a50 | ||
|
0a15f14af0 | ||
|
2c3e1daaa4 | ||
|
eefb744467 | ||
|
b7b1a33258 | ||
|
783630b89b | ||
|
d20d7734cd | ||
|
3a8cc2d4d5 | ||
|
1839eaae3d | ||
|
9f28514046 | ||
|
a1bb51ad2d | ||
|
95cc966e28 | ||
|
5f4c9a7c07 | ||
|
a39c40539f | ||
|
aa272ece4d | ||
|
85678ea5cc | ||
|
4d779d5f52 | ||
|
ded399d35d | ||
|
c378eb4b4a | ||
|
b302bd6829 | ||
|
72374d91b1 | ||
|
d52e84ee23 | ||
|
994ad17fc9 | ||
|
e71021e8aa | ||
|
b5b8e9a743 | ||
|
b0281fde72 | ||
|
7d22a57874 | ||
|
cb63e2d8d7 | ||
|
c22b2f942b | ||
|
27c74448d6 | ||
|
b32b4b8533 | ||
|
1b6d1de059 | ||
|
41d7308ba8 | ||
|
a460b52e03 | ||
|
3802829557 | ||
|
874635e92a | ||
|
385c8532eb | ||
|
5f5359ae65 | ||
|
e2fb5de595 | ||
|
b57fbd8b6d | ||
|
0844279c58 | ||
|
e4aeadecd7 | ||
|
628e99ba2d | ||
|
878663ca07 | ||
|
88116bdfa6 | ||
|
a773daa0cd | ||
|
55d60cb368 | ||
|
24f8987972 | ||
|
725a7efb3c | ||
|
f862ed1403 | ||
|
34b30de288 | ||
|
c25a9dae4d | ||
|
defc8b9cfd | ||
|
acd881385c | ||
|
9caf2ce268 | ||
|
7d2dd5cc68 | ||
|
4bc70d99f4 | ||
|
eb9ebaf28e | ||
|
5c2f2b0684 | ||
|
2acec4f124 | ||
|
128164aa23 | ||
|
57737a4655 | ||
|
3738594fbc | ||
|
bfffe55fcc | ||
|
9b6a282e5f | ||
|
cffba9252a | ||
|
3c84eb81b3 | ||
|
b99e3d3099 | ||
|
b6585a6a52 | ||
|
42251a102b | ||
|
c9aec3bf56 | ||
|
2931fdb548 | ||
|
690fb3400c | ||
|
496c680cd2 | ||
|
1f611122d1 | ||
|
356ad93a2e | ||
|
fd208ee4dc | ||
|
f02dc5aae9 | ||
|
ef2acf81ef | ||
|
d8f1371b60 | ||
|
0ee064fbe1 | ||
|
fb03a3313a | ||
|
a02bc8aa2c | ||
|
98dd339fad | ||
|
5923b13fc1 | ||
|
dc4ccaef09 | ||
|
3eb777983c | ||
|
a7519d3e1e | ||
|
260d2b61cd | ||
|
163f405ae5 | ||
|
533b13746d | ||
|
bbc4b44de5 | ||
|
8a3896eef5 | ||
|
df9087ba17 | ||
|
cd559e14ea | ||
|
daa6e21d75 | ||
|
8a23b9a49e | ||
|
573076c263 | ||
|
cd4f62d178 | ||
|
28fb6dfe9d | ||
|
82f16b9231 | ||
|
09ba14e7fc | ||
|
1cab51249e | ||
|
93081b9909 | ||
|
5aa2858f4b | ||
|
034426ed45 | ||
|
a4642540b4 | ||
|
dc23d9f584 | ||
|
623512c2b5 | ||
|
163c3a3f62 | ||
|
53d1018340 | ||
|
1c520a68a3 | ||
|
47d0db89c1 | ||
|
0aa9c2e766 | ||
|
87229630d7 | ||
|
e13da505ed | ||
|
bf5e26dbb5 | ||
|
c3b2254b9d | ||
|
46b6495e71 | ||
|
03db5be985 | ||
|
66c51828b0 | ||
|
75ff7e1b23 | ||
|
9464bdb499 | ||
|
70398ebb95 | ||
|
9455d49124 | ||
|
993e0ba403 | ||
|
b18123d00a | ||
|
efdf0125c3 | ||
|
25be231beb | ||
|
c3256a8277 | ||
|
adffdb33bb | ||
|
4aff6a96ea | ||
|
7782a9d197 | ||
|
cc643257a9 | ||
|
63b9a4c8b4 | ||
|
9395d7f3f5 | ||
|
9712ecf974 | ||
|
34410059b1 | ||
|
639a189730 | ||
|
e5fb5709f7 | ||
|
4e253697de | ||
|
e9224c4d66 | ||
|
42190df040 | ||
|
ca601d0e73 | ||
|
ef073de306 | ||
|
42994c4474 | ||
|
732b8a48ff | ||
|
09c3f09d8b | ||
|
e13c943d3f | ||
|
1896fde3a0 | ||
|
614388e663 | ||
|
bcd1cbc1b1 | ||
|
bc0fb89846 | ||
|
b5daf8fe0a | ||
|
166f5ded77 | ||
|
e5743471f4 | ||
|
0c68a70130 | ||
|
7b5796ce03 | ||
|
f42820e4ba | ||
|
7333fba6a1 | ||
|
f538be6748 | ||
|
920a95296c | ||
|
a705621dd5 | ||
|
f52867f2e8 | ||
|
c8bd529173 | ||
|
19ce0c8b69 | ||
|
7bb2ba91c3 | ||
|
77fad9406b | ||
|
081451252d | ||
|
2ecdf32356 | ||
|
e69b92418f | ||
|
aa62f1fedc | ||
|
4d57b3c5da | ||
|
85b7d570e6 | ||
|
85ec4ab962 | ||
|
9760b78f16 | ||
|
c68e00f7a2 | ||
|
285ef27b0b | ||
|
670afcc8c3 | ||
|
bc8540c3f5 | ||
|
7f460caf6b | ||
|
4ffefa0473 | ||
|
72722dd9a8 | ||
|
db2c2b0b25 | ||
|
822431ac14 | ||
|
5704ade5dc | ||
|
8eb6ea6d6c | ||
|
a342293b39 | ||
|
604dda7380 | ||
|
e8e2f04f01 | ||
|
32fdb6c9c3 | ||
|
fda01e0bc4 | ||
|
685ff40ea3 | ||
|
2dc3ad70a3 | ||
|
fd9bd48925 | ||
|
36710eccc4 | ||
|
0fc203dff5 | ||
|
5d40d01360 | ||
|
60b280bbf9 | ||
|
0141f5b896 | ||
|
7114e5a1ef | ||
|
173696f46e | ||
|
a646eb251a | ||
|
4f14b70f60 | ||
|
a32c3d9641 | ||
|
ca74aaf4d1 | ||
|
1957277463 | ||
|
517a5ccbea | ||
|
8fb016a4f3 | ||
|
9b7c3c2389 | ||
|
08afeaf248 | ||
|
1caa9b1bf3 | ||
|
cf489284c9 | ||
|
64657c196a | ||
|
c1bc2546fd | ||
|
fcf1087eaf | ||
|
8659594c22 | ||
|
eded08595a | ||
|
67cc43f3c0 | ||
|
bb4450e562 | ||
|
91b328f540 | ||
|
b14b404534 | ||
|
f4cd2a5112 | ||
|
4d15ce40c1 | ||
|
ead94695f1 | ||
|
7a6efbcf90 |
14
.editorconfig
Normal file
|
@ -0,0 +1,14 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
# [*.{js,py}]
|
19
.eslintrc.js
|
@ -1,7 +1,14 @@
|
|||
module.exports = {
|
||||
"extends": [
|
||||
"matrix-org/ts",
|
||||
"matrix-org/react",
|
||||
],
|
||||
}
|
||||
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
};
|
||||
|
|
34
.gitignore
vendored
|
@ -1,31 +1,5 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# development
|
||||
bundle.js
|
||||
bundle.js.map
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
node_modules
|
||||
build
|
||||
*.tar.gz
|
||||
/.idea
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.vercel
|
||||
|
||||
storybook-static
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
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 React from 'react';
|
||||
import { addDecorator } from '@storybook/react';
|
||||
import { withA11y } from '@storybook/addon-a11y';
|
||||
import { withKnobs } from '@storybook/addon-knobs';
|
||||
import { withDesign } from 'storybook-addon-designs'
|
||||
import { addParameters } from '@storybook/react';
|
||||
|
||||
import SingleColumn from '../src/layouts/SingleColumn';
|
||||
|
||||
// Default styles
|
||||
import "../src/index.scss";
|
||||
|
||||
addDecorator(
|
||||
storyFn => <SingleColumn>{storyFn()}</SingleColumn>
|
||||
);
|
||||
|
||||
addDecorator(withA11y);
|
||||
|
||||
addDecorator(withKnobs);
|
||||
|
||||
addDecorator(withDesign);
|
||||
|
||||
addParameters({
|
||||
backgrounds: [
|
||||
{name: 'light', value: '#F4F4F4', default: true},
|
||||
{name: 'white', value: '#FFFFFF'},
|
||||
],
|
||||
});
|
||||
|
59
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Contributing to Matrix.to
|
||||
|
||||
Thank you for taking the time to contribute to Matrix!
|
||||
|
||||
This is the repository for Matrix.to, a simple url redirection service for the Matrix.org ecosystem which lets users share links to matrix entities without being tied to a specific app.
|
||||
|
||||
## Sign off
|
||||
|
||||
We ask that everybody who contributes to this project signs off their contributions, as explained below.
|
||||
|
||||
We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is Apache Software License v2 (see [LICENSE](./LICENSE)).
|
||||
|
||||
In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix:
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
If you agree to this for your contribution, then all that's needed is to include the line in your commit or pull request comment:
|
||||
|
||||
```
|
||||
Signed-off-by: Your Name <your@email.example.org>
|
||||
```
|
||||
|
||||
Git allows you to add this signoff automatically when using the `-s` flag to `git commit`, which uses the name and email set in your `user.name` and `user.email` git configs.
|
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM node:latest
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files first to leverage Docker cache
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
# Install dependencies
|
||||
RUN yarn install
|
||||
|
||||
# Copy the rest of the application
|
||||
COPY . .
|
||||
|
||||
# Expose port 5000
|
||||
EXPOSE 5000
|
||||
|
||||
# Start the application
|
||||
CMD ["yarn", "start"]
|
177
LICENSE
Normal file
|
@ -0,0 +1,177 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
67
README.md
|
@ -1,10 +1,54 @@
|
|||
# Matrix.to
|
||||
# dm.sij.law
|
||||
This repo is cloned from the official Matrix.to repo on Github. I've added a Dockerfile and will make modifications in the future. The current version is live at [dm.sij.law](https://dm.sij.law)
|
||||
|
||||
## First clone the repo
|
||||
```bash
|
||||
git clone https://sij.ai/sij/env.esq.git
|
||||
```
|
||||
|
||||
## Next build the image
|
||||
```bash
|
||||
docker build -t matrix-to .
|
||||
```
|
||||
|
||||
## Then run it
|
||||
```bash
|
||||
docker run -d \
|
||||
--name matrix-to \
|
||||
-p 3636:5000 \
|
||||
matrix-to
|
||||
```
|
||||
|
||||
# EVERYTHING BELOW HERE IS ORIGINAL DOCUMENTATION
|
||||
|
||||
## Matrix.to
|
||||
|
||||
Matrix.to is a simple url redirection service for the Matrix.org ecosystem
|
||||
which lets users share links to matrix entities without being tied to a
|
||||
specific app.
|
||||
Stylistically it serves as a landing page for rooms and communities.
|
||||
|
||||
## How can I put a badge on my website linking to my matrix room?
|
||||
|
||||
You can use the badge image we've put up at https://matrix.to/img/matrix-badge.svg, and use it in a link like this:
|
||||
|
||||
[![Chat on Matrix](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#matrix.to:matrix.org)
|
||||
|
||||
You can use this Markdown:
|
||||
```md
|
||||
[![Chat on Matrix](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#matrix.to:matrix.org)
|
||||
```
|
||||
|
||||
Or this HTML:
|
||||
|
||||
```html
|
||||
<a href="https://matrix.to/#/#matrix.to:matrix.org" rel="noopener" target="_blank"><img src="https://matrix.to/img/matrix-badge.svg" alt="Chat on Matrix"></a>
|
||||
```
|
||||
|
||||
to show the badge.
|
||||
|
||||
## How does matrix.to work?
|
||||
|
||||
Matrix.to preserves user privacy by not sharing any information about the links
|
||||
being followed with the Matrix.to server - the redirection is calculated
|
||||
entirely clientside using JavaScript, and the link details is hidden behind a
|
||||
|
@ -21,6 +65,8 @@ point at an alternative deployment of the service. The Matrix.to service could
|
|||
also be hosted in an immutable/signed environment such as IPFS to further
|
||||
increase its availability and avoid tampering.
|
||||
|
||||
## URL Scheme
|
||||
|
||||
The matrix.to URL scheme is
|
||||
|
||||
| Entity type: | Example URL |
|
||||
|
@ -40,10 +86,23 @@ 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)
|
||||
|
||||
A development build of matrix-two can be found at https://matrix-to.vercel.app
|
||||
## Build Instructions
|
||||
|
||||
A preview of all components can be found at
|
||||
https://matrix-to-storybook.vercel.app
|
||||
1. Install [yarn](https://classic.yarnpkg.com/en/docs/install)
|
||||
1. `git clone https://github.com/matrix-org/matrix.to`
|
||||
1. `cd matrix.to`
|
||||
1. `yarn`
|
||||
1. `yarn start`
|
||||
1. Go to http://localhost:5000 in your browser
|
||||
|
|
98
css/client.css
Normal file
|
@ -0,0 +1,98 @@
|
|||
.ClientListView h2 {
|
||||
text-align: center;
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
.ClientListView .filterOption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.ClientView {
|
||||
border: 1px solid #E6E6E6;
|
||||
border-radius: 8px;
|
||||
margin: 16px 0;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ClientView.isPreferred {
|
||||
border: 3px solid var(--link);
|
||||
box-shadow: 0px 8px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.ClientView .hostedBanner {
|
||||
text-align: center;
|
||||
margin-bottom: 29px;
|
||||
padding: 4px 0;
|
||||
line-height: 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
background-color: var(--lightgrey);
|
||||
}
|
||||
|
||||
.ClientView .header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ClientView .description {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ClientView h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ClientView .clientIcon {
|
||||
border-radius: 8px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.ClientView .platforms {
|
||||
background-image: url('../images/platform-icon.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 center;
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.ClientView .actions a.badge {
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
margin: 8px 16px 8px 0;
|
||||
}
|
||||
|
||||
.ClientView .actions img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ClientView .back {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.InstallClientView .instructions button {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.InstallClientView .instructions button.copy {
|
||||
background-image: url('../images/copy.svg');
|
||||
}
|
||||
|
||||
.InstallClientView .instructions button.tick {
|
||||
background-image: url('../images/tick-dark.svg');
|
||||
}
|
||||
|
13
css/create.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
.CreateLinkView h2 {
|
||||
padding: 0 40px;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.CreateLinkView form {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.CreateLinkView form > *:not(:first-child) {
|
||||
margin-top: 24px;
|
||||
}
|
223
css/main.css
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
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 url('spinner.css');
|
||||
@import url('client.css');
|
||||
@import url('preview.css');
|
||||
@import url('create.css');
|
||||
@import url('open.css');
|
||||
|
||||
:root {
|
||||
--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;
|
||||
}
|
||||
|
||||
body {
|
||||
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;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: var(--font);
|
||||
padding: 120px 0 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
noscript {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
p { line-height: 150%; }
|
||||
a { text-decoration: none; }
|
||||
|
||||
h1 { font-size: 24px; }
|
||||
h2 { font-size: 21px; }
|
||||
h3 { font-size: 16px; }
|
||||
|
||||
body,
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
button, input[type=submit] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button, input {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.RootView {
|
||||
margin: 0 auto;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 18px 24px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card, .footer {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
body {
|
||||
background-image: none;
|
||||
background-color: var(--background);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.footer .links li:not(:first-child) {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.footer .links li:not(:first-child)::before {
|
||||
content: "·";
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.footer .links li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footer .links {
|
||||
font-size: 12px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a, button.text {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
button.text {
|
||||
background: none;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: inherit;
|
||||
padding: 8px 0;
|
||||
margin: -8px 0;
|
||||
}
|
||||
|
||||
button.text:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary, .secondary {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: var(--background);
|
||||
color: var(--link);
|
||||
border: 1px solid var(--link);
|
||||
border-radius: 32px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: var(--link);
|
||||
color: var(--background);
|
||||
border-radius: 32px;
|
||||
}
|
||||
|
||||
.primary.icon, .secondary.icon {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 12px center;
|
||||
}
|
||||
|
||||
.icon.link { background-image: url('../images/link.svg'); }
|
||||
.icon.tick { background-image: url('../images/tick.svg'); }
|
||||
.icon.copy { background-image: url('../images/copy.svg'); }
|
||||
|
||||
button.primary, input[type='submit'].primary, button.secondary, input[type='submit'].secondary {
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
input[type='text'].large {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--background);
|
||||
border: 1px solid var(--foreground);
|
||||
border-radius: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.LoadServerPolicyView {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.LoadServerPolicyView .spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.LoadServerPolicyView h2 {
|
||||
margin-top: 0;
|
||||
}
|
52
css/open.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.OpenLinkView .caption {
|
||||
color: var(--grey);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ServerConsentView .actions label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ServerConsentView .actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ServerConsentView input[type=submit] {
|
||||
flex: 1;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.ServerOptions div {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.ServerOptions label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ServerOptions label > .line {
|
||||
flex: 1;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--grey);
|
||||
padding: 4px 0;
|
||||
}
|
133
css/preview.css
Normal file
|
@ -0,0 +1,133 @@
|
|||
.PreviewView {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.PreviewView h1 {
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 8px;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.PreviewView .avatarContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.PreviewView .avatar {
|
||||
border-radius: 100%;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.PreviewView .mxSpace .avatar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.PreviewView .defaultAvatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-image: url('../images/chat-icon.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 85%;
|
||||
}
|
||||
|
||||
.PreviewView .spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.PreviewView .avatar.loading {
|
||||
border: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.PreviewView .identifier {
|
||||
color: var(--grey);
|
||||
font-size: 12px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.PreviewView .identifier.placeholder {
|
||||
height: 1em;
|
||||
margin: 1em 30%;
|
||||
}
|
||||
|
||||
.PreviewView .memberCount {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.PreviewView .memberCount.loading {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.PreviewView .memberCount p {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.PreviewView .memberCount p:not(.placeholder) {
|
||||
padding: 4px 8px 4px 24px;
|
||||
border-radius: 14px;
|
||||
background-image: url(../images/member-icon.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px center;
|
||||
background-color: var(--lightgrey);
|
||||
}
|
||||
|
||||
.PreviewView .memberCount p.placeholder {
|
||||
height: 1.5em;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.PreviewView .topic {
|
||||
font-size: 12px;
|
||||
color: var(--grey);
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.PreviewView .topic.loading {
|
||||
display: block;
|
||||
margin: 24px 12px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.PreviewView .topic.loading .placeholder {
|
||||
height: 0.8em;
|
||||
display: block;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.PreviewView .topic.loading .placeholder:nth-child(2) {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
border-radius: 1em;
|
||||
--flash-bg: #ddd;
|
||||
--flash-fg: #eee;
|
||||
background: linear-gradient(120deg,
|
||||
var(--flash-bg),
|
||||
var(--flash-bg) 10%,
|
||||
var(--flash-fg) calc(10% + 25px),
|
||||
var(--flash-bg) calc(10% + 50px)
|
||||
);
|
||||
animation: flash 2s linear infinite;
|
||||
background-size: 200%;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { background-position-x: 0; }
|
||||
50% { background-position-x: -80%; }
|
||||
51% { background-position-x: 80%; }
|
||||
100% { background-position-x: 0%; }
|
||||
}
|
27
css/spinner.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100%;
|
||||
border: var(--spinner-stroke-size) solid var(--app-background);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.spinner::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 100%;
|
||||
border-width: var(--spinner-stroke-size);
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-top-color: var(--grey);
|
||||
animation: rotate 0.8s linear infinite;
|
||||
box-sizing: border-box;
|
||||
margin: calc(-1 * var(--spinner-stroke-size));
|
||||
}
|
6
docker-compose.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: '3'
|
||||
services:
|
||||
matrix-to:
|
||||
build: .
|
||||
ports:
|
||||
- "3636:5000"
|
20
images-nohash/matrix-badge.svg
Normal file
|
@ -0,0 +1,20 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
|
||||
<linearGradient id="b" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<mask id="a">
|
||||
<rect width="99" height="20" rx="3" fill="#fff"/>
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<path fill="#555" d="M0 0h42v20H0z"/>
|
||||
<path fill="#46BC99" d="M42 0h59v20H42z"/>
|
||||
<path fill="url(#b)" d="M0 0h99v20H0z"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="21" y="15" fill="#010101" fill-opacity=".3">matrix</text>
|
||||
<text x="21" y="14">matrix</text>
|
||||
<text x="68.5" y="15" fill="#010101" fill-opacity=".3">join chat</text>
|
||||
<text x="68.5" y="14">join chat</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 781 B |
7
images/app-store-us-alt.svg
Normal file
After Width: | Height: | Size: 13 KiB |
226
images/background.svg
Normal file
|
@ -0,0 +1,226 @@
|
|||
<svg width="1440" height="1505" viewBox="0 0 1440 1505" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<g>
|
||||
<path opacity="0.26" d="M1027.99 602.979C1262.01 860.194 1465.48 1242.7 1528.69 1544.36C1592.2 1847.63 1503.31 2018.17 1310.8 1964.61C1117.48 1910.83 851.081 1638.68 658.35 1297.66C466.355 957.9 378.93 607.541 443.1 434.715C506.167 264.833 707.508 296.249 938.653 512.115C968.31 539.852 998.261 570.239 1027.99 602.979Z" stroke="url(#paint0_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1044.18 604.303C1277.61 867.698 1476.23 1255.51 1532.89 1558.78C1589.85 1863.67 1493.74 2031.71 1297.33 1973.07C1100.11 1914.14 833.789 1635 645.032 1288.91C457.01 944.068 376.871 591.501 447.59 420.442C517.133 252.325 723.184 289.112 954.844 511.085C984.648 539.632 1014.53 570.827 1044.18 604.303Z" stroke="url(#paint1_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1060.81 606.069C1293.5 875.644 1486.97 1268.6 1536.79 1573.27C1586.9 1879.56 1483.51 2045.03 1283.27 1981.09C1082.22 1916.94 816.124 1630.66 631.709 1279.64C447.955 930.015 375.249 575.389 452.665 406.316C528.757 240.112 739.517 282.417 971.546 510.496C1001.42 539.779 1031.3 571.784 1060.81 606.069Z" stroke="url(#paint2_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1077.88 608.276C1309.61 883.958 1497.64 1281.99 1540.46 1587.91C1583.51 1895.45 1472.69 2058.05 1268.62 1988.82C1063.75 1919.29 798.166 1626.02 618.239 1270.22C439.048 915.668 374.068 559.276 458.328 392.263C541.117 228.12 756.587 276.09 988.836 510.349C1018.64 540.441 1048.44 573.255 1077.88 608.276Z" stroke="url(#paint3_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1095.4 610.925C1325.96 892.714 1508.24 1295.6 1543.86 1602.56C1579.62 1911.12 1461.21 2070.71 1253.4 1996.03C1044.7 1921.06 779.774 1620.73 604.704 1260.22C430.223 901.101 373.265 543.09 464.516 378.432C554.148 216.569 774.329 270.278 1006.5 510.644C1036.31 541.545 1066.11 575.168 1095.4 610.925Z" stroke="url(#paint4_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1113.28 614.089C1342.51 901.984 1518.76 1309.58 1546.94 1617.42C1575.2 1926.87 1449.14 2083.21 1237.57 2002.94C1025.12 1922.38 761.152 1615.14 591.086 1250.06C421.609 886.313 372.967 526.904 471.209 364.747C567.832 205.385 792.575 264.907 1024.68 511.453C1054.48 543.163 1084.14 577.522 1113.28 614.089Z" stroke="url(#paint5_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1131.6 617.694C1359.29 911.622 1529.21 1323.86 1549.67 1632.28C1570.2 1942.39 1436.27 2095.28 1221.02 2009.42C1004.88 1923.19 741.947 1608.96 577.253 1239.47C413.148 871.378 373.042 510.718 478.496 351.283C582.184 194.497 811.636 260.051 1043.3 512.704C1073.1 545.224 1102.68 580.392 1131.6 617.694Z" stroke="url(#paint6_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1150.29 621.814C1376.21 921.702 1539.51 1338.35 1552.02 1647.21C1564.6 1957.77 1422.72 2106.98 1203.79 2015.38C983.98 1923.41 722.443 1602.26 563.342 1228.43C404.83 856.074 373.554 494.531 486.367 337.966C597.267 184.049 831.282 255.71 1062.5 514.543C1092.23 547.799 1121.59 583.703 1150.29 621.814Z" stroke="url(#paint7_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1169.43 626.449C1393.36 932.296 1549.74 1353.14 1554.08 1662.3C1558.42 1973.07 1408.52 2118.46 1186.06 2021.04C962.716 1923.19 702.724 1595.12 549.436 1217.25C396.738 840.622 374.514 478.418 494.833 324.869C613.018 173.969 851.596 251.884 1082.15 516.823C1111.73 550.888 1140.95 587.528 1169.43 626.449Z" stroke="url(#paint8_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1188.93 631.527C1410.65 943.26 1559.82 1368.15 1555.77 1677.31C1551.73 1988.08 1393.58 2129.42 1167.59 2026.05C940.711 1922.23 682.485 1587.33 535.38 1205.48C388.863 824.953 375.984 462.233 503.883 312.069C629.574 164.406 872.567 248.721 1102.24 519.694C1131.75 554.421 1160.74 591.944 1188.93 631.527Z" stroke="url(#paint9_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1208.87 637.192C1428.24 954.737 1569.83 1383.53 1557.1 1692.39C1544.37 2002.95 1377.91 2139.94 1148.53 2030.61C918.192 1920.76 662.027 1579.09 521.323 1193.41C381.135 809.134 377.971 446.194 513.523 299.414C646.794 155.209 894.276 245.925 1122.85 523.005C1152.21 558.615 1180.98 596.799 1208.87 637.192Z" stroke="url(#paint10_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1229.18 643.371C1445.9 966.729 1579.62 1399.12 1558.05 1707.55C1536.42 2017.66 1361.5 2150.17 1128.73 2034.65C895.012 1918.7 641.127 1570.26 507.12 1180.9C373.702 793.021 380.399 430.154 523.752 287.126C664.676 146.526 916.573 243.865 1143.89 526.904C1173.11 563.249 1201.66 602.243 1229.18 643.371Z" stroke="url(#paint11_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1249.94 650.066C1463.79 979.089 1589.26 1414.87 1558.65 1722.63C1527.81 2032.01 1344.43 2159.8 1108.35 2038.11C871.318 1915.91 619.936 1560.84 492.994 1168.03C366.493 776.761 383.345 414.188 534.645 275.06C683.37 138.286 939.609 242.319 1165.46 531.391C1194.52 568.546 1222.78 608.276 1249.94 650.066Z" stroke="url(#paint12_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1271.06 657.351C1481.74 991.965 1598.75 1430.98 1558.79 1737.78C1518.61 2046.21 1326.54 2169.07 1087.3 2041.13C847.033 1912.6 598.447 1550.91 478.791 1154.86C359.502 760.354 386.877 398.37 546.199 263.288C702.871 130.561 963.305 241.437 1187.53 536.395C1216.31 574.359 1244.27 614.825 1271.06 657.351Z" stroke="url(#paint13_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1292.55 665.149C1499.92 1005.28 1608.1 1447.31 1558.5 1752.94C1508.68 2060.19 1307.93 2177.83 1065.52 2043.56C822.088 1908.62 576.52 1540.39 464.443 1141.32C352.734 743.726 390.853 382.552 558.417 251.811C723.11 123.277 987.739 241.142 1210.05 542.06C1238.61 580.686 1266.2 621.961 1292.55 665.149Z" stroke="url(#paint14_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1314.47 673.537C1518.24 1019.11 1617.22 1463.87 1557.91 1768.1C1498.23 2073.94 1288.64 2186.14 1043.15 2045.4C796.623 1904.06 554.44 1529.35 450.163 1127.42C346.328 726.878 395.413 366.955 571.218 240.702C744.007 116.656 1012.83 241.585 1233.01 548.314C1261.27 587.677 1288.57 629.687 1314.47 673.537Z" stroke="url(#paint15_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1336.7 682.587C1536.64 1033.54 1626.13 1480.72 1556.73 1783.25C1486.97 2087.33 1268.48 2193.94 1019.97 2046.65C770.502 1898.76 531.85 1517.65 435.816 1113.07C340.149 709.956 400.566 351.43 584.688 229.96C765.718 110.549 1038.59 242.614 1256.41 555.157C1284.45 595.328 1311.31 638.001 1336.7 682.587Z" stroke="url(#paint16_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1359.36 692.151C1555.18 1048.32 1634.81 1497.79 1555.18 1798.34C1475.12 2100.5 1247.58 2201.15 996.27 2047.24C743.931 1892.66 509.106 1505.36 421.534 1098.35C334.257 692.813 406.228 336.053 598.885 219.512C788.232 105.031 1065.08 244.38 1280.4 562.588C1308.07 603.495 1334.49 646.903 1359.36 692.151Z" stroke="url(#paint17_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1382.32 702.304C1573.8 1063.55 1643.27 1515 1553.12 1813.27C1462.46 2113.16 1225.8 2207.7 971.765 2047.09C716.629 1885.81 485.999 1492.42 407.185 1083.12C328.665 675.523 412.483 320.823 613.751 209.506C811.56 100.101 1092.23 246.808 1304.76 570.754C1332.06 612.323 1358.04 656.394 1382.32 702.304Z" stroke="url(#paint18_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1405.73 713.12C1592.57 1079.37 1651.51 1532.51 1550.62 1828.28C1449.22 2125.59 1203.35 2213.81 946.599 2046.43C688.815 1878.38 462.6 1478.88 392.837 1067.67C323.442 658.087 419.329 305.741 629.281 199.868C835.626 95.8343 1120.05 249.972 1329.63 579.436C1356.49 621.815 1381.96 666.548 1405.73 713.12Z" stroke="url(#paint19_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1429.42 724.524C1611.33 1095.7 1659.53 1550.24 1547.6 1843.22C1435.16 2137.73 1180.02 2219.33 920.768 2045.1C660.407 1870.22 438.903 1464.82 378.559 1051.85C318.51 640.502 426.834 290.879 645.469 190.671C860.424 92.229 1148.53 253.944 1354.8 588.927C1381.37 631.967 1406.31 677.363 1429.42 724.524Z" stroke="url(#paint20_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1453.49 736.589C1630.25 1112.48 1667.19 1568.2 1544.15 1858C1420.45 2149.43 1155.96 2224.11 894.279 2043.04C631.49 1861.24 414.99 1450.04 364.36 1035.67C313.951 622.844 434.933 276.237 662.472 181.915C886.111 89.2121 1177.82 258.653 1380.63 599.006C1406.68 642.783 1431.04 688.84 1453.49 736.589Z" stroke="url(#paint21_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1477.91 749.245C1649.23 1129.77 1674.69 1586.22 1540.24 1872.72C1405.06 2160.69 1131.16 2228.3 867.12 2040.32C602.05 1851.53 390.848 1434.66 350.226 1019.11C309.752 605.04 443.612 261.818 680.129 173.603C912.525 86.9324 1207.69 264.098 1406.83 609.749C1432.36 654.261 1456.13 700.907 1477.91 749.245Z" stroke="url(#paint22_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1502.64 762.635C1668.29 1147.57 1681.84 1604.62 1535.69 1887.36C1388.8 2171.58 1105.41 2231.83 839.233 2036.86C571.956 1841.08 366.346 1418.69 336.101 1002.19C305.929 587.161 452.961 247.617 698.456 165.803C939.757 85.3863 1238.24 270.425 1433.32 621.299C1458.42 666.4 1481.6 713.708 1502.64 762.635Z" stroke="url(#paint23_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1527.74 776.614C1687.35 1165.89 1688.68 1623.08 1530.75 1901.78C1371.95 2182.02 1078.99 2234.63 810.753 2032.52C541.415 1829.6 341.767 1401.92 322.118 984.681C302.47 569.135 463.042 233.638 717.589 158.445C967.794 84.5771 1269.51 277.488 1460.4 633.439C1484.91 679.202 1507.43 727.172 1527.74 776.614Z" stroke="url(#paint24_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1553.05 791.255C1706.41 1184.66 1695.15 1641.77 1525.09 1916.13C1354.14 2191.96 1051.54 2236.76 781.463 2027.59C510.212 1817.54 316.745 1384.63 308.061 967.097C299.451 551.036 473.711 219.953 737.383 151.676C996.566 84.5034 1301.45 285.434 1487.78 646.461C1511.77 692.813 1533.62 741.298 1553.05 791.255Z" stroke="url(#paint25_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1578.73 806.632C1725.47 1204.01 1701.41 1660.61 1519.05 1930.33C1335.74 2201.52 1023.5 2238.16 751.66 2021.78C478.642 1804.52 291.725 1366.6 294.3 948.924C296.802 532.863 485.045 206.636 757.989 145.349C1026.3 85.1659 1334.05 294.19 1515.67 659.999C1539 707.087 1560.12 756.161 1578.73 806.632Z" stroke="url(#paint26_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1604.71 822.671C1744.6 1223.8 1707.22 1679.66 1512.36 1944.38C1316.46 2210.57 994.506 2238.82 721.046 2015.23C446.41 1790.76 266.409 1347.99 280.539 930.53C294.594 514.69 497.113 193.54 779.329 139.61C1056.76 86.6367 1367.31 303.827 1543.93 674.419C1566.59 722.021 1586.9 771.684 1604.71 822.671Z" stroke="url(#paint27_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1630.98 839.372C1763.74 1244.1 1712.74 1698.79 1505.14 1958.21C1296.44 2219.03 964.776 2238.68 689.845 2007.87C413.81 1776.12 240.874 1328.71 266.925 911.769C292.829 496.371 509.771 180.812 801.407 134.46C1088.04 88.9179 1401.24 314.349 1572.55 689.576C1594.56 737.767 1614.06 787.944 1630.98 839.372Z" stroke="url(#paint28_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1657.48 856.736C1782.8 1264.92 1717.82 1717.99 1497.27 1971.82C1275.55 2226.98 934.089 2237.65 657.833 1999.63C380.474 1760.59 215.118 1308.63 253.385 892.566C291.504 478.051 523.238 168.378 824.22 129.898C1120.05 92.0815 1435.82 325.826 1601.55 705.468C1622.89 754.247 1641.58 804.866 1657.48 856.736Z" stroke="url(#paint29_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1684.26 874.91C1801.78 1286.26 1722.53 1737.49 1488.88 1985.29C1253.98 2234.41 902.667 2235.95 625.307 1990.59C346.77 1744.19 189.289 1288.03 240.065 873.07C290.695 459.732 537.368 156.386 847.842 125.927C1152.94 96.0556 1471.07 338.188 1630.98 722.096C1651.51 771.465 1669.32 822.599 1684.26 874.91Z" stroke="url(#paint30_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1711.27 893.67C1820.77 1308.04 1726.87 1756.99 1479.83 1998.38C1231.54 2241.1 870.434 2233.23 592.045 1980.5C312.551 1726.75 163.238 1266.47 226.893 853.131C290.327 441.337 552.233 144.687 872.127 122.615C1186.58 100.91 1506.91 351.503 1660.64 739.532C1680.44 789.416 1697.36 841.065 1711.27 893.67Z" stroke="url(#paint31_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1738.5 913.241C1839.61 1330.41 1730.7 1776.63 1470.19 2011.41C1208.28 2247.36 837.319 2229.85 558.193 1969.76C277.89 1708.58 137.113 1244.47 213.941 832.972C290.474 423.018 567.834 133.504 897.295 119.967C1221.09 106.65 1543.41 365.777 1690.74 757.78C1709.72 808.178 1725.69 860.268 1738.5 913.241Z" stroke="url(#paint32_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1766.02 933.474C1858.45 1353.21 1734.23 1796.35 1459.96 2024.06C1184.22 2252.95 803.394 2225.51 523.68 1957.99C242.788 1689.37 110.842 1221.74 201.136 812.371C291.137 404.624 584.244 122.689 923.198 117.98C1256.41 113.345 1580.57 381.007 1721.2 776.835C1739.31 827.748 1754.25 880.206 1766.02 933.474Z" stroke="url(#paint33_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1793.69 954.516C1877.14 1376.54 1737.24 1816.29 1449.07 2036.49C1159.34 2257.88 768.73 2220.28 488.574 1945.41C207.241 1669.36 84.567 1198.34 188.623 791.549C292.311 386.304 601.314 112.314 949.908 116.655C1292.54 120.996 1618.32 397.193 1751.96 796.7C1769.18 848.054 1783.09 900.88 1793.69 954.516Z" stroke="url(#paint34_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1821.58 976.22C1895.68 1400.37 1739.82 1836.15 1437.51 2048.56C1133.59 2262.15 733.114 2214.03 452.737 1931.72C171.183 1648.32 58.076 1174.13 176.261 770.286C294.005 367.984 619.197 102.382 977.358 116.067C1329.41 129.604 1656.66 414.409 1783.02 817.3C1799.28 869.17 1812.16 922.364 1821.58 976.22Z" stroke="url(#paint35_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1849.62 998.66C1914.08 1424.65 1741.88 1856.09 1425.3 2060.33C1107.03 2265.68 696.764 2206.89 416.313 1917.16C134.759 1626.32 31.5131 1149.27 164.195 748.729C296.289 349.737 637.818 92.964 1005.62 116.287C1367.09 139.242 1695.67 432.729 1814.37 838.857C1829.68 891.094 1841.45 944.583 1849.62 998.66Z" stroke="url(#paint36_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1877.8 1021.91C1932.33 1449.45 1743.43 1876.1 1412.42 2071.81C1079.65 2268.55 659.526 2198.8 379.297 1901.71C97.8897 1603.36 4.94593 1123.73 152.346 726.877C299.084 331.564 657.245 83.9876 1034.61 117.169C1405.5 149.763 1735.11 451.931 1846.01 861.149C1860.29 913.755 1870.89 967.537 1877.8 1021.91Z" stroke="url(#paint37_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1906.21 1045.82C1950.51 1474.68 1744.53 1896.12 1398.88 2082.92C1051.39 2270.68 621.478 2189.68 341.69 1885.15C60.6513 1579.38 -21.622 1097.47 140.717 704.732C302.54 313.465 677.48 75.6009 1064.41 118.862C1444.73 161.388 1775.22 472.312 1877.95 884.326C1891.12 937.299 1900.54 991.376 1906.21 1045.82Z" stroke="url(#paint38_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1934.69 1070.54C1968.39 1500.43 1745.05 1916.2 1384.68 2093.66C1022.32 2272.08 582.699 2179.52 303.426 1867.64C23.0499 1554.44 -48.3321 1070.47 129.387 682.218C306.517 295.514 698.456 67.7286 1095.03 121.364C1484.76 174.043 1815.91 493.722 1910.11 908.385C1922.18 961.652 1930.42 1015.95 1934.69 1070.54Z" stroke="url(#paint39_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1963.32 1096C1986.13 1526.63 1745.12 1936.29 1369.82 2104.04C992.449 2272.67 543.11 2168.41 264.721 1849.1C-14.9201 1528.54 -74.8957 1042.8 118.498 659.484C311.156 277.635 720.314 60.4446 1126.46 124.675C1525.61 187.801 1857.13 516.235 1942.56 933.253C1953.46 986.815 1960.37 1041.33 1963.32 1096Z" stroke="url(#paint40_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M1992.02 1122.26C2003.57 1553.33 1744.61 1956.37 1354.14 2114.04C961.613 2272.52 502.634 2156.2 225.275 1829.6C-53.2619 1501.61 -101.537 1014.48 107.826 636.455C316.379 259.904 742.905 53.7497 1158.61 128.795C1567.11 202.59 1898.85 539.779 1975.09 958.93C1984.88 1012.79 1990.54 1067.45 1992.02 1122.26Z" stroke="url(#paint41_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
<path opacity="0.26" d="M2020.79 1149.2C2020.79 1580.41 1743.5 1976.39 1337.88 2123.46C930.117 2271.35 461.498 2142.81 185.39 1808.86C-91.8961 1473.66 -128.102 985.346 97.5236 613.136C322.193 242.249 766.306 47.6461 1191.51 133.801C1609.42 218.558 1941.02 564.503 2007.91 985.493C2016.52 1039.64 2020.79 1094.46 2020.79 1149.2Z" stroke="url(#paint42_linear)" stroke-opacity="0.5" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="985.633" y1="325.888" x2="985.633" y2="1974.38" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="985.644" y1="315.588" x2="985.644" y2="1984.65" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="985.667" y1="305.466" x2="985.667" y2="1994.71" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear" x1="985.745" y1="295.445" x2="985.745" y2="2004.63" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear" x1="985.811" y1="285.689" x2="985.811" y2="2014.24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="985.893" y1="276.126" x2="985.893" y2="2023.79" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear" x1="985.932" y1="266.75" x2="985.932" y2="2033.08" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear" x1="985.934" y1="257.604" x2="985.934" y2="2042.09" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear" x1="985.952" y1="248.661" x2="985.952" y2="2051.01" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear" x1="985.954" y1="240.04" x2="985.954" y2="2059.54" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear" x1="985.935" y1="231.549" x2="985.935" y2="2067.84" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear" x1="985.894" y1="223.397" x2="985.894" y2="2075.94" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint12_linear" x1="985.853" y1="215.451" x2="985.853" y2="2083.67" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint13_linear" x1="985.78" y1="207.783" x2="985.78" y2="2091.22" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint14_linear" x1="985.655" y1="200.339" x2="985.655" y2="2098.48" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint15_linear" x1="985.579" y1="193.255" x2="985.579" y2="2105.48" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint16_linear" x1="985.428" y1="186.433" x2="985.428" y2="2112.18" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint17_linear" x1="985.288" y1="179.882" x2="985.288" y2="2118.57" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint18_linear" x1="985.116" y1="173.645" x2="985.116" y2="2124.54" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint19_linear" x1="984.964" y1="167.714" x2="984.964" y2="2130.35" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint20_linear" x1="984.807" y1="162.115" x2="984.807" y2="2135.86" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint21_linear" x1="984.669" y1="156.799" x2="984.669" y2="2140.99" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint22_linear" x1="984.581" y1="151.807" x2="984.581" y2="2145.83" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint23_linear" x1="984.47" y1="147.17" x2="984.47" y2="2150.37" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint24_linear" x1="984.413" y1="142.818" x2="984.413" y2="2154.54" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint25_linear" x1="984.278" y1="138.833" x2="984.278" y2="2158.44" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint26_linear" x1="984.286" y1="135.096" x2="984.286" y2="2162.04" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint27_linear" x1="984.263" y1="131.706" x2="984.263" y2="2165.35" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint28_linear" x1="984.295" y1="128.646" x2="984.295" y2="2168.32" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint29_linear" x1="984.293" y1="125.905" x2="984.293" y2="2170.95" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint30_linear" x1="984.353" y1="123.444" x2="984.353" y2="2173.38" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint31_linear" x1="984.425" y1="121.309" x2="984.425" y2="2175.36" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint32_linear" x1="984.506" y1="119.477" x2="984.506" y2="2177.26" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint33_linear" x1="984.625" y1="117.921" x2="984.625" y2="2178.83" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint34_linear" x1="984.762" y1="116.604" x2="984.762" y2="2180.17" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint35_linear" x1="984.858" y1="115.562" x2="984.858" y2="2181.23" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint36_linear" x1="984.956" y1="114.82" x2="984.956" y2="2182.09" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint37_linear" x1="985.041" y1="114.2" x2="985.041" y2="2182.8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint38_linear" x1="985.161" y1="113.81" x2="985.161" y2="2183.32" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint39_linear" x1="985.213" y1="113.588" x2="985.213" y2="2183.7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint40_linear" x1="985.322" y1="113.5" x2="985.322" y2="2183.99" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint41_linear" x1="985.357" y1="113.5" x2="985.357" y2="2184.22" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint42_linear" x1="985.395" y1="113.578" x2="985.395" y2="2184.23" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#999999"/>
|
||||
<stop offset="1" stop-color="#7D92FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="1440" height="1505" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 26 KiB |
4
images/chat-icon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="36" height="38" viewBox="0 0 36 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.4184 32.9995C20.2122 31.5062 20.6613 29.8061 20.6613 28.0024C20.6613 22.0566 15.7808 17.2365 9.76039 17.2365C6.93884 17.2365 4.36767 18.2952 2.43193 20.0323C2.21698 18.9773 2.10413 17.8851 2.10413 16.7666C2.10413 7.78278 9.38427 0.5 18.3648 0.5C27.3453 0.5 34.6254 7.78278 34.6254 16.7666C34.6254 19.283 34.0542 21.6659 33.0345 23.7928L35.874 33.0245C36.1106 33.7938 35.3879 34.5131 34.6198 34.273L25.4569 31.4085C23.6119 32.3044 21.5722 32.8617 19.4184 32.9995Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.8203 37.1184C13.4204 37.1184 17.1495 33.4167 17.1495 28.8504C17.1495 24.2842 13.4204 20.5825 8.8203 20.5825C4.22021 20.5825 0.491095 24.2842 0.491095 28.8504C0.491095 30.1291 0.783484 31.3399 1.30551 32.4206L0.126628 36.2238C-0.111642 36.9925 0.609435 37.7134 1.37807 37.475L5.18834 36.293C6.28601 36.8218 7.51826 37.1184 8.8203 37.1184Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
19
images/client-icons/cinny.svg
Normal file
|
@ -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 |
9
images/client-icons/element.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="64" height="64" fill="#0dbd8b" stop-color="#000000" stroke="#000" stroke-dasharray="0.188976, 0.37795299999999998" stroke-width=".18898"/>
|
||||
<g clip-rule="evenodd" fill="#fff" fill-rule="evenodd">
|
||||
<path d="m25.28 10.88c0-1.5906 1.2894-2.88 2.88-2.88 10.604 0 19.2 8.5961 19.2 19.2 0 1.5906-1.2894 2.88-2.88 2.88s-2.88-1.2894-2.88-2.88c0-7.4227-6.0173-13.44-13.44-13.44-1.5906 0-2.88-1.2894-2.88-2.88z"/>
|
||||
<path d="m38.72 53.12c0 1.5906-1.2894 2.88-2.88 2.88-10.604 0-19.2-8.5961-19.2-19.2 0-1.5906 1.2894-2.88 2.88-2.88 1.5905 0 2.88 1.2894 2.88 2.88 0 7.4227 6.0173 13.44 13.44 13.44 1.5906 0 2.88 1.2894 2.88 2.88z"/>
|
||||
<path d="m10.88 38.72c-1.5906 0-2.88-1.2894-2.88-2.88 0-10.604 8.5961-19.2 19.2-19.2 1.5906 0 2.88 1.2894 2.88 2.88 0 1.5905-1.2894 2.88-2.88 2.88-7.4227 0-13.44 6.0173-13.44 13.44 0 1.5906-1.2894 2.88-2.88 2.88z"/>
|
||||
<path d="m53.12 25.28c1.5906 0 2.88 1.2894 2.88 2.88 0 10.604-8.5961 19.2-19.2 19.2-1.5906 0-2.88-1.2894-2.88-2.88 0-1.5905 1.2894-2.88 2.88-2.88 7.4227 0 13.44-6.0173 13.44-13.44 0-1.5906 1.2894-2.88 2.88-2.88z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
43
images/client-icons/fluffychat.svg
Normal file
|
@ -0,0 +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">
|
||||
<style type="text/css">
|
||||
.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"/>
|
||||
</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>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
55
images/client-icons/fractal.svg
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="8" x2="58" y1="69.999985" y2="69.999985">
|
||||
<stop offset="0" stop-color="#4aaac9"/>
|
||||
<stop offset="0.16" stop-color="#8bddf7"/>
|
||||
<stop offset="0.32" stop-color="#4aaac9"/>
|
||||
<stop offset="1" stop-color="#4aaac9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" gradientUnits="userSpaceOnUse" x1="31.462524" x2="39" y1="113.997253" y2="113.997253">
|
||||
<stop offset="0" stop-color="#4aaac9"/>
|
||||
<stop offset="0.469318" stop-color="#74d7f7"/>
|
||||
<stop offset="1" stop-color="#4aaac9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" gradientUnits="userSpaceOnUse" x1="104" x2="120" y1="84" y2="84">
|
||||
<stop offset="0" stop-color="#1a5fb4"/>
|
||||
<stop offset="0.5" stop-color="#4296ff"/>
|
||||
<stop offset="1" stop-color="#1a5fb4"/>
|
||||
</linearGradient>
|
||||
<clipPath id="d">
|
||||
<path d="m 8 24 h 97 v 84 h -97 z m 0 0"/>
|
||||
</clipPath>
|
||||
<clipPath id="e">
|
||||
<path d="m 24 24 h 80 c 8.835938 0 16 7.164062 16 16 v 52 c 0 8.835938 -7.164062 16 -16 16 h -80 c -8.835938 0 -16 -7.164062 -16 -16 v -52 c 0 -8.835938 7.164062 -16 16 -16 z m 0 0"/>
|
||||
</clipPath>
|
||||
<linearGradient id="f" gradientUnits="userSpaceOnUse" x1="55.608135" x2="71.783539" y1="100" y2="48.532928">
|
||||
<stop offset="0" stop-color="#81dffe"/>
|
||||
<stop offset="1" stop-color="#9bf8fe"/>
|
||||
</linearGradient>
|
||||
<filter id="g" height="100%" width="100%" x="0%" y="0%">
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<mask id="h">
|
||||
<g filter="url(#g)">
|
||||
<rect fill-opacity="0.35" height="128" width="128"/>
|
||||
</g>
|
||||
</mask>
|
||||
<clipPath id="i">
|
||||
<rect height="152" width="192"/>
|
||||
</clipPath>
|
||||
<path d="m 24 28 h 72 c 8.835938 0 16 7.164062 16 16 v 52 c 0 8.835938 -7.164062 16 -16 16 h -72 c -8.835938 0 -16 -7.164062 -16 -16 v -52 c 0 -8.835938 7.164062 -16 16 -16 z m 0 0" fill="url(#a)"/>
|
||||
<path d="m 24 28 h 80 c 8.835938 0 16 7.164062 16 16 v 48 c 0 8.835938 -7.164062 16 -16 16 h -80 c -8.835938 0 -16 -7.164062 -16 -16 v -48 c 0 -8.835938 7.164062 -16 16 -16 z m 0 0" fill="#53bde0"/>
|
||||
<path d="m 24 100 v 12 h 4 c 2.210938 0 4 1.789062 4 4 v 7 c 0 1.992188 1.183594 3.792969 3.011719 4.585938 c 1.828125 0.789062 3.953125 0.417968 5.40625 -0.945313 l 13.523437 -12.707031 c 1.324219 -1.242188 3.070313 -1.933594 4.882813 -1.933594 h 9.175781 v -12 z m 0 0" fill="url(#b)" fill-rule="evenodd"/>
|
||||
<path d="m 102 58.566406 h 2 c 8.835938 0 16 7.164063 16 16 v 21.433594 c 0 8.835938 -7.164062 16 -16 16 h -2 c -8.835938 0 -16 -7.164062 -16 -16 v -21.433594 c 0 -8.835937 7.164062 -16 16 -16 z m 0 0" fill="url(#c)"/>
|
||||
<path d="m 86 87 h 18 v 25 h -18 z m 0 0" fill="#1a5fb4"/>
|
||||
<path d="m 48 24 h 56 c 8.835938 0 16 7.164062 16 16 v 52 c 0 8.835938 -7.164062 16 -16 16 h -56 c -8.835938 0 -16 -7.164062 -16 -16 v -52 c 0 -8.835938 7.164062 -16 16 -16 z m 0 0" fill="#3584e4"/>
|
||||
<g clip-path="url(#d)">
|
||||
<g clip-path="url(#e)">
|
||||
<path d="m 78.804688 16.023438 l 0.527343 2.460937 c -1.207031 -0.082031 -2.417969 4.964844 -3.621093 4.988281 c -19.335938 0.371094 -38.003907 14.230469 -39.148438 34.546875 c -0.835938 14.761719 9.570312 29.839844 25.15625 30.488281 c 10.371094 0.433594 20.96875 -6.957031 21.242188 -17.925781 c 0.179687 -7.078125 -4.953126 -14.3125 -12.488282 -14.355469 c -4.683594 -0.027343 -9.484375 3.425782 -9.398437 8.429688 c 0.074219 2.980469 2.300781 6.042969 5.511719 5.902344 c 1.8125 -0.082032 3.691406 -1.488282 3.539062 -3.453125 c -0.078125 -1.042969 -0.921875 -2.128907 -2.0625 -1.996094 c -0.5625 0.066406 -1.148438 0.539063 -1.046875 1.15625 c 0.070313 0.273437 0.285156 0.570313 0.597656 0.5 c 0.121094 -0.03125 0.25 -0.144531 0.214844 -0.28125 c 0 -0.042969 -0.070313 -0.09375 -0.113281 -0.074219 c 0 0.003906 -0.070313 0.023438 0 0.035156 v 0.007813 v -0.003906 v 0.035156 c 0 0.050781 -0.09375 0.050781 -0.136719 0.03125 c -0.121094 -0.066406 -0.113281 -0.242187 -0.070313 -0.347656 c 0.164063 -0.265625 0.542969 -0.230469 0.777344 -0.074219 c 0.519532 0.367188 0.429688 1.117188 0.070313 1.558594 c -0.710938 0.898437 -2.074219 0.726562 -2.867188 0.042968 c -1.5 -1.28125 -1.167969 -3.601562 0.070313 -4.941406 c 2.167968 -2.367187 5.929687 -1.792968 8.074218 0.28125 c 3.601563 3.476563 2.652344 9.308594 -0.675781 12.597656 c -5.359375 5.292969 -14.109375 3.800782 -18.992187 -1.324218 c -7.570313 -7.953125 -5.304688 -20.664063 2.335937 -27.6875 c 11.480469 -10.550782 29.507813 -7.242188 39.363281 3.785156 c 14.414063 16.121094 9.6875 41.066406 -5.855468 54.582031 c -11.121094 9.226563 -22.246094 15.429688 -32.949219 19.4375 c -13.058594 75.445313 -75.230469 6.835938 -81.039063 -4.195312 l 0.285157 -105.054688 z m 0 0" fill="url(#f)"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m 24 106 v 2 h 4 c 2.210938 0 4 1.789062 4 4 v 7 c 0 1.992188 1.183594 3.792969 3.011719 4.585938 c 1.828125 0.789062 3.953125 0.417968 5.40625 -0.945313 l 13.523437 -12.707031 c 1.324219 -1.242188 3.070313 -1.933594 4.882813 -1.933594 h 9.175781 v -2 z m 0 0" fill="#81dffe" fill-rule="evenodd"/>
|
||||
<g clip-path="url(#i)" mask="url(#h)" transform="matrix(1 0 0 1 -8 -16)">
|
||||
<path d="m 173 17 h 8 c 1.65625 0 3 1.34375 3 3 v 7 c 0 1.65625 -1.34375 3 -3 3 h -8 c -1.65625 0 -3 -1.34375 -3 -3 v -7 c 0 -1.65625 1.34375 -3 3 -3 z m 0 0" fill="#241f31"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
155
images/client-icons/nheko.svg
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1024"
|
||||
height="1024"
|
||||
viewBox="0 0 270.93333 270.93333"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="nheko.svg"
|
||||
inkscape:export-filename="/home/nicolas/Dokumente/devel/open-source/nheko/resources/nheko-rebuild-round-corners.svg.png"
|
||||
inkscape:export-xdpi="130.048"
|
||||
inkscape:export-ydpi="130.048">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35355339"
|
||||
inkscape:cx="852.07808"
|
||||
inkscape:cy="-60.410565"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1019"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="true"
|
||||
inkscape:snap-grids="true"
|
||||
gridtolerance="10"
|
||||
inkscape:snap-bbox="false"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:lockguides="false"
|
||||
units="px">
|
||||
<sodipodi:guide
|
||||
position="0,0"
|
||||
orientation="0,793.70079"
|
||||
id="guide4797"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="0,297"
|
||||
orientation="1122.5197,0"
|
||||
id="guide4803"
|
||||
inkscape:locked="false" />
|
||||
<inkscape:grid
|
||||
type="axonomgrid"
|
||||
id="grid4805"
|
||||
units="px"
|
||||
empspacing="2"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingy="1.0583333" />
|
||||
<sodipodi:guide
|
||||
position="0,0"
|
||||
orientation="0,755.90551"
|
||||
id="guide4807"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="200,0"
|
||||
orientation="-755.90551,0"
|
||||
id="guide4809"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="200,200"
|
||||
orientation="0,-755.90551"
|
||||
id="guide4811"
|
||||
inkscape:locked="false" />
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid871"
|
||||
empspacing="2"
|
||||
color="#d43fff"
|
||||
opacity="0.1254902"
|
||||
empcolor="#cf3fff"
|
||||
empopacity="0.25098039"
|
||||
units="px"
|
||||
spacingx="1.0583333"
|
||||
spacingy="1.0583333"
|
||||
enabled="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Logo"
|
||||
style="display:inline"
|
||||
transform="translate(0,-26.066668)">
|
||||
<circle
|
||||
id="path3792"
|
||||
cx="135.46666"
|
||||
cy="161.53333"
|
||||
style="display:inline;fill:#333333;fill-opacity:1;stroke:none;stroke-width:0.3584221"
|
||||
inkscape:transform-center-x="-57.929751"
|
||||
inkscape:transform-center-y="532.03976"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
inkscape:export-ydpi="96.000008"
|
||||
r="135.46666" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.32663074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 48.965212,110.73276 H 239.52342 c 4.88824,0 4.88824,0 0,8.46688 L 180.59519,221.2662 c -4.6188,8.00001 -4.6188,8.00001 -9.50702,8.00001 h -19.55294 c -4.88824,0 -4.88824,0 -0.26944,-8.00001 l 44.2635,-76.66608 h -29.41224 l -43.91123,76.19952 c -4.88823,8.46657 -4.88823,8.46657 -9.77646,8.46657 H 29.329398 l 49.299816,-84.66609 h -49.29982 z"
|
||||
id="path4834"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccc"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
inkscape:export-ydpi="96.000008" />
|
||||
<path
|
||||
style="fill:#c0def5;fill-opacity:1;stroke:none;stroke-width:0.3584221px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 97.764652,110.73276 H 127.09406 L 58.658797,229.26621 H 29.329398 Z"
|
||||
id="path4836"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
inkscape:export-ydpi="96.000008" />
|
||||
<path
|
||||
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.3584221px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 58.658797,229.26621 127.09406,110.73276 h 29.3294 L 87.988193,229.26621 Z"
|
||||
id="path4838"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc"
|
||||
inkscape:export-xdpi="96.000008"
|
||||
inkscape:export-ydpi="96.000008" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline"
|
||||
transform="translate(0,-26.066668)" />
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
34
images/client-icons/org.kde.neochat.svg
Normal file
|
@ -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 |
464
images/client-icons/quaternion.svg
Normal file
|
@ -0,0 +1,464 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 48 48.000001"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="quaternion2.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="b"
|
||||
y1="23.774559"
|
||||
x1="22.540125"
|
||||
y2="44.054428"
|
||||
x2="42.645557"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(30,0)">
|
||||
<stop
|
||||
stop-color="#292c2f"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#292c2f"
|
||||
stop-opacity="0"
|
||||
id="stop14" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4416">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="0"
|
||||
id="stop4418" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4420" />
|
||||
</linearGradient>
|
||||
<style
|
||||
id="current-color-scheme"
|
||||
type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
.ColorScheme-Highlight {
|
||||
color:#3daee9;
|
||||
}
|
||||
</style>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#b"
|
||||
id="linearGradient4384-2"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(18.78125,-16.0625)"
|
||||
x1="-21.260931"
|
||||
y1="13.89889"
|
||||
x2="10.555012"
|
||||
y2="48.902145" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4416"
|
||||
id="linearGradient4422-7"
|
||||
x1="-30.500504"
|
||||
y1="28.249998"
|
||||
x2="-10.500504"
|
||||
y2="7.8749971"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4.0005053,-4.0000137)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#b"
|
||||
id="linearGradient5173"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.80454501,0,0,0.80454501,29.039903,1009.4479)"
|
||||
x1="-8.4545803"
|
||||
y1="4.9617052"
|
||||
x2="10.555012"
|
||||
y2="48.902145" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4416"
|
||||
id="linearGradient5175"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.89628968,0,0,0.89628968,2.535611,1907.0938)"
|
||||
x1="34.2505"
|
||||
y1="-976.61401"
|
||||
x2="8.5004978"
|
||||
y2="-1002.614" />
|
||||
<linearGradient
|
||||
gradientTransform="translate(-384.57,-499.8)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="0"
|
||||
y2="503.8"
|
||||
y1="543.8"
|
||||
id="a">
|
||||
<stop
|
||||
id="stop7"
|
||||
stop-color="#197cf1" />
|
||||
<stop
|
||||
id="stop9"
|
||||
stop-color="#20bcfa"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="432.5705"
|
||||
id="a-6"
|
||||
y1="547.79999"
|
||||
y2="500.04578"
|
||||
x2="432.5705"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-432.5705,-499.79999)">
|
||||
<stop
|
||||
style="stop-color:#1e2b35;stop-opacity:1"
|
||||
stop-color="#18222a"
|
||||
id="stop4216" />
|
||||
<stop
|
||||
style="stop-color:#525c64;stop-opacity:1"
|
||||
offset="1"
|
||||
stop-color="#566069"
|
||||
id="stop4218" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-344.5695,-499.79999)"
|
||||
id="b-7"
|
||||
y1="517.8"
|
||||
x1="399.57"
|
||||
y2="534.8"
|
||||
x2="416.57"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
id="stop4221" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-opacity="0"
|
||||
id="stop4223" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-344.5695,-499.79999)"
|
||||
id="c"
|
||||
y1="537.8"
|
||||
y2="508.8"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="0">
|
||||
<stop
|
||||
stop-color="#026ddc"
|
||||
id="stop4226" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#28b0fd"
|
||||
id="stop4228" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="d"
|
||||
y1="525.28"
|
||||
x1="408.65"
|
||||
y2="533.28"
|
||||
x2="416.65"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-.081-.48)">
|
||||
<stop
|
||||
stop-opacity=".065"
|
||||
id="stop4231" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-opacity="0"
|
||||
id="stop4233" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#d"
|
||||
id="e"
|
||||
y1="517.96"
|
||||
x1="417.79"
|
||||
y2="525.91"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="426.22"
|
||||
gradientTransform="matrix(-1,0,0,1,817.2,-0.48)" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="39.279999"
|
||||
y2="36.632999"
|
||||
x1="22.285"
|
||||
y1="18.709999"
|
||||
id="b-5"
|
||||
gradientTransform="translate(-13.182,-16.463)">
|
||||
<stop
|
||||
id="stop12-3"
|
||||
stop-color="#292c2f" />
|
||||
<stop
|
||||
id="stop14-5"
|
||||
stop-opacity="0"
|
||||
stop-color="#292c2f"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="463.30493"
|
||||
id="b-3"
|
||||
y1="791.61914"
|
||||
y2="721.56116"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="520.69641"
|
||||
gradientTransform="matrix(0.71874732,0,0,0.68157806,-378.12417,-491.55024)">
|
||||
<stop
|
||||
style="stop-color:#00945a;stop-opacity:1"
|
||||
stop-color="#26c281"
|
||||
id="stop4252" />
|
||||
<stop
|
||||
style="stop-color:#38fa95;stop-opacity:1"
|
||||
offset="1"
|
||||
stop-color="#3fc380"
|
||||
id="stop4254" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="a-67"
|
||||
y1="517.79999"
|
||||
y2="533.79999"
|
||||
x1="404.98001"
|
||||
x2="420.98001"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.98828127,0,0,0.98828127,-383.20097,-495.43517)">
|
||||
<stop
|
||||
stop-color="#383e51"
|
||||
id="stop4247" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#655c6f"
|
||||
stop-opacity="0"
|
||||
id="stop4249" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.98828127,0,0,0.98828127,-378.84265,-493.45861)"
|
||||
id="c-5"
|
||||
y1="531.79999"
|
||||
y2="515.79999"
|
||||
x2="0"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#70e4b3"
|
||||
id="stop4257" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#c8f0dc"
|
||||
id="stop4259" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b-3-3"
|
||||
y1="785.71002"
|
||||
y2="727.71002"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="0"
|
||||
gradientTransform="matrix(0.71874732,0,0,0.68157806,-387.2511,-495.98773)">
|
||||
<stop
|
||||
stop-color="#26c281"
|
||||
id="stop4252-5" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#3fc380"
|
||||
id="stop4254-6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-13.130467,-10.269952)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="42.645557"
|
||||
y2="44.054428"
|
||||
x1="22.540125"
|
||||
y1="23.774559"
|
||||
id="b-1">
|
||||
<stop
|
||||
id="stop12-2"
|
||||
stop-color="#292c2f" />
|
||||
<stop
|
||||
id="stop14-7"
|
||||
stop-opacity="0"
|
||||
stop-color="#292c2f"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="39.179428"
|
||||
x2="3.3955555"
|
||||
y1="32.924278"
|
||||
x1="-2.8595951"
|
||||
gradientTransform="translate(7.947106,-34.03464)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4318-9"
|
||||
xlink:href="#b"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
y2="39.179428"
|
||||
x2="3.3955555"
|
||||
y1="39.799278"
|
||||
x1="-13.672095"
|
||||
gradientTransform="translate(14.488281,-20.003906)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4345-7"
|
||||
xlink:href="#b"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
y2="48.902145"
|
||||
x2="10.555012"
|
||||
y1="12.39889"
|
||||
x1="-22.510931"
|
||||
gradientTransform="translate(18.78125,-16.0625)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4384-2-2"
|
||||
xlink:href="#b"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="42.645557"
|
||||
y2="44.054428"
|
||||
x1="22.540125"
|
||||
y1="23.774559"
|
||||
id="b-36"
|
||||
gradientTransform="translate(-71.999501,32.001813)">
|
||||
<stop
|
||||
id="stop12-7"
|
||||
stop-color="#292c2f" />
|
||||
<stop
|
||||
id="stop14-53"
|
||||
stop-opacity="0"
|
||||
stop-color="#292c2f"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="389.3205"
|
||||
id="a-6-5"
|
||||
y1="547.67499"
|
||||
y2="499.92078"
|
||||
x2="426.6955"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-360.571,-467.79817)">
|
||||
<stop
|
||||
style="stop-color:#1e2b35;stop-opacity:1"
|
||||
stop-color="#18222a"
|
||||
id="stop4216-6" />
|
||||
<stop
|
||||
style="stop-color:#6d7983;stop-opacity:1"
|
||||
offset="1"
|
||||
stop-color="#566069"
|
||||
id="stop4218-2" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="32.789359"
|
||||
inkscape:cy="42.117635"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="false"
|
||||
showborder="false"
|
||||
borderlayer="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5026" />
|
||||
<sodipodi:guide
|
||||
position="13,11.25"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide5089" />
|
||||
<sodipodi:guide
|
||||
position="7.875,26.375001"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide5091" />
|
||||
<sodipodi:guide
|
||||
position="29.875,39.500001"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide5093" />
|
||||
<sodipodi:guide
|
||||
position="35,36.250001"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide5097" />
|
||||
<sodipodi:guide
|
||||
position="9.875,20.5"
|
||||
orientation="0.70710678,0.70710678"
|
||||
id="guide5126" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Livello 2">
|
||||
<path
|
||||
id="path5159"
|
||||
d="m 24.04876,6.1174361 c -9.930889,0 -17.925793,7.9949039 -17.925793,17.9257939 0,9.930889 7.994904,17.925793 17.925793,17.925793 0.02411,0 0.04771,-0.0017 0.07178,-0.0018 l 16.955978,4.3e-5 c 0.496545,0 0.89629,-0.399737 0.89629,-0.89629 l 0,-16.961232 c 8.1e-5,-0.02232 0.0018,-0.04418 0.0018,-0.06652 0,-9.930889 -7.994904,-17.9257934 -17.925795,-17.9257934 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#1d99f3;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Livello 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1004.3622)">
|
||||
<path
|
||||
style="opacity:0.3;fill:#ffffff;fill-opacity:1;stroke-width:2.79999995;stroke-opacity:0.55"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 6.1310739,1028.8503 c -0.00358,-0.1498 -0.010755,-0.2976 -0.010755,-0.4483 0,-9.9308 7.994904,-17.9257 17.925794,-17.9257 9.93089,0 17.925795,7.9949 17.925795,17.9257 0,0.1507 -0.0072,0.2985 -0.01076,0.4483 -0.23662,-9.7221 -8.138312,-17.4777 -17.91504,-17.4777 -9.780313,0 -17.6784177,7.7556 -17.9150382,17.4777"
|
||||
id="path5163"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5165"
|
||||
d="m 11.232155,1026.802 14.222481,14.2935 -3.520064,-0.3911 -9.191278,-9.1912 2.684492,7.5202 6.204245,6.2684 2.49952,0.1156 17.241676,-0.023 0.640018,-1.0668 -0.05709,-14.8384 -0.0649,-1.7857 -8.756475,-8.7243 -0.64001,0.8532 -3.555621,-3.6266 -10.240187,0.64 -4.76453,4.1244 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.18800001;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient5173);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5167"
|
||||
d="m 6.1334706,1027.9574 c -0.00367,0.1495 -0.010504,0.2976 -0.010504,0.448 0,9.9309 7.9949037,17.9258 17.9257927,17.9258 0.02411,0 0.04771,0 0.07178,0 l 16.955981,0 c 0.496544,0 0.896289,-0.3996 0.896289,-0.8962 l 0,-0.8945 c 0,0.4965 -0.399745,0.8963 -0.896289,0.8963 l -16.955983,0 -0.07178,0 c -9.78071,0 -17.6785062,-7.7561 -17.9152898,-17.4777 z m 35.8393324,0.3763 0,0.1383 c 8.2e-5,-0.022 0.0018,-0.044 0.0018,-0.066 0,-0.024 -0.0018,-0.048 -0.0018,-0.072 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#161e25;fill-opacity:1;fill-rule:nonzero;stroke-width:2.79999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.55;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5169"
|
||||
d="m 24.850998,1015.3211 -2.3e-4,0 c -4.221957,-0.2137 -8.462616,1.6173 -11.185311,5.279 -1.410567,1.8989 -2.22395,4.0471 -2.493121,6.2301 l 1.696347,-0.2507 3.220344,-0.4718 2.68403,-2.0567 -5.463182,0.8056 c 0.374562,-1.1444 0.916754,-2.2533 1.67664,-3.2759 2.961145,-3.9843 7.972375,-5.4929 12.459008,-4.168 l 1.563639,-1.1933 c -1.347181,-0.5332 -2.750838,-0.8302 -4.158164,-0.9015 z m 6.710874,2.5126 -4.6e-4,0.01 -0.521308,1.6335 -0.859253,2.6992 0.499033,3.8383 1.715908,-5.384 c 0.863373,0.8389 1.619917,1.8132 2.204556,2.9452 2.442167,4.7279 1.298074,10.3196 -2.445266,13.7365 l 0.0072,0.053 c -0.02757,0.026 -0.05527,0.055 -0.0831,0.081 l 4.152714,4.8087 1.274973,-1.1026 -3.204232,-3.7103 c 3.427251,-3.9365 4.309935,-9.6879 1.761636,-14.6216 -1.086112,-2.1028 -2.658768,-3.7804 -4.502389,-4.9823 z m -20.627884,12.8598 c 0.795585,5.8959 5.518184,10.6684 11.708243,11.2806 2.355072,0.2329 4.622679,-0.1736 6.634024,-1.0664 l -1.086358,-1.3241 -0.0062,-5e-4 -2.138088,-2.6048 -3.22492,-1.339 3.657256,4.4524 c -1.173938,0.2676 -2.40585,0.375 -3.673603,0.2496 -5.070346,-0.501 -9.016389,-4.197 -10.072229,-8.8986 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.63318729;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.45;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient5175);fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 24.04876,1010.4797 c -9.930889,0 -17.925793,7.9949 -17.925793,17.9257 0,9.9309 7.994904,17.9258 17.925793,17.9258 0.02411,0 0.04771,0 0.07178,0 l 16.955978,0 c 0.496545,0 0.89629,-0.3996 0.89629,-0.8962 l 0,-16.9612 c 8.1e-5,-0.022 0.0018,-0.044 0.0018,-0.066 0,-9.931 -7.994902,-17.9259 -17.925793,-17.9259 z"
|
||||
id="path5171"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 18 KiB |
1282
images/client-icons/schildichat.svg
Normal file
After Width: | Height: | Size: 43 KiB |
3531
images/client-icons/syphon.svg
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
images/client-icons/tensor.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
65
images/client-icons/thunderbird.svg
Normal file
|
@ -0,0 +1,65 @@
|
|||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M452.1 306.5H452.16C474.2 229.45 570.24 191.84 668.11 191.84C735.74 191.84 796.45 213.25 838.07 247.24C813.232 248.411 788.708 253.285 765.31 261.7C798.94 274.2 827.83 293.43 849.24 317.22C833.087 314.439 816.707 313.201 800.32 313.52C840.596 371.863 862.114 441.105 862 512C862 705.3 705.3 862 512 862C321.68 862 162 702.65 162 512C162 481.89 166 450.8 173.77 421.64C175.81 415.52 178.65 409.65 182.42 407.5C187.14 404.81 191.44 412.83 192.13 415.44C197.248 434.61 204.134 453.263 212.7 471.16C211.95 431.2 229.02 394.8 252.5 363.32C268.16 342.33 282.68 322.88 289.38 266.75C289.83 262.98 293.4 260.27 297.01 261.45C347.97 278.13 375.21 362.99 370.98 433.95C399.13 437.98 399 408.57 399 408.57C390 380.91 396 329.5 452 306.5H452.1Z" fill="url(#paint0_linear_1_4)"/>
|
||||
<path opacity="0.9" d="M850.55 422.87C859.06 615.37 700.81 781.34 507.84 781.34C327.19 781.34 179.16 641.71 165.77 464.49C163.381 481.085 162.125 497.824 162.01 514.59C163.38 704.21 322.67 862 512.01 862C705.31 862 862.01 705.3 862.01 512C862.01 481.2 858.02 451.33 850.55 422.87Z" fill="url(#paint1_radial_1_4)"/>
|
||||
<path style="mix-blend-mode:screen" d="M503.47 332.01C499.7 325.34 482.29 315.47 474.68 313.75C503.48 221.5 650.2 193.18 740 209.5C777.37 216.3 823.91 236.66 838.07 247.24C796.45 213.25 735.73 191.84 668.11 191.84C570.24 191.84 474.2 229.45 452.16 306.5H452C396 329.5 390 380.93 399 408.58C407.64 375.59 448.75 335.05 503.47 332.01Z" fill="url(#paint2_radial_1_4)"/>
|
||||
<path d="M605.18 268.3C526.56 283.77 500.87 288.83 474.56 313.84C504.11 235.61 579.54 219.75 669.42 255.41C648.028 259.82 626.615 264.12 605.18 268.31V268.3Z" fill="url(#paint3_linear_1_4)"/>
|
||||
<path d="M181 410.93C159.52 498.87 176.12 602.23 273.7 688.97C244.65 657.2 209.18 539.9 287.45 456.1C292.72 450.45 301.79 454.6 302.08 462.32C308.53 636.48 449.06 742.85 611.08 722.96C560.88 720.14 394.86 661.99 518.36 638.99C582.91 626.96 684.12 608.11 684.12 517.29C684.12 370.07 570.29 327.03 501.26 333.43C454.02 337.81 411.97 367.79 399.03 408.56C404 424.63 384.19 435.88 370.98 433.99C375.22 363.04 347.98 278.13 297.01 261.45C293.41 260.27 289.83 262.98 289.38 266.75C282.68 322.88 268.16 342.33 252.51 363.32C229.02 394.81 211.95 431.2 212.7 471.16C204.134 453.263 197.248 434.61 192.13 415.44C191.56 413.28 188.43 407.25 184.65 406.97C182.6 406.82 181.51 408.82 181 410.93Z" fill="url(#paint4_radial_1_4)"/>
|
||||
<path style="mix-blend-mode:screen" d="M474.99 647.9C570.03 725.07 761.16 667.21 761.16 479.58C684 596.54 585.72 677.22 475 647.9H474.99Z" fill="url(#paint5_linear_1_4)"/>
|
||||
<path style="mix-blend-mode:screen" d="M287.45 456.1C288.314 455.133 289.403 454.394 290.62 453.947C291.838 453.501 293.146 453.361 294.43 453.54C224.37 538.99 280.88 689.06 319.66 725.93C321.83 732.07 282.87 700.14 277.5 692.67C248 667.64 205.72 543.61 287.45 456.1Z" fill="url(#paint6_linear_1_4)"/>
|
||||
<path d="M512 654.97C607.06 654.97 684.13 592.09 684.13 514.52C684.13 436.95 607.06 374.07 512 374.07C430.9 374.07 339.83 426.83 339.87 516.57C339.91 655.24 486.41 735.02 611.29 722.94C601.91 721.85 543.39 718.74 503.84 674C500.27 669.97 494.08 662.93 496.89 658.36C499.69 653.79 507.41 654.96 511.99 654.96L512 654.97Z" fill="url(#paint7_linear_1_4)"/>
|
||||
<path opacity="0.6" d="M665.38 450.7L529.34 580.88C517.27 589.46 504.42 590.08 491.74 582.3L358.31 451.18C362.117 445.062 366.396 439.251 371.11 433.8L385.11 446.9C420.15 479.74 448.48 506.27 488.41 540.35C506.43 555.73 512.03 555.43 529.71 540.35C575.39 501.35 608.8 471.85 652.23 433.06C657.07 438.586 661.464 444.488 665.37 450.71L665.38 450.7Z" fill="white"/>
|
||||
<mask id="mask0_1_4" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="339" y="448" width="346" height="277">
|
||||
<path d="M684.13 514.52C684.13 592.09 607.06 654.97 512 654.97C507.41 654.97 499.7 653.79 496.89 658.37C494.08 662.93 500.27 669.97 503.84 674C541 716.04 594.91 721.33 609.06 722.72L611.29 722.94C486.41 735.02 339.91 655.24 339.87 516.57C339.74 493.482 346.165 470.831 358.4 451.25L492.48 573.03C502.02 581.69 518.09 581.69 527.63 573.03L664.25 448.94C676.95 468.52 684.13 490.84 684.13 514.52Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1_4)">
|
||||
<path opacity="0.7" d="M300 352.53H735.07V747H300V352.53Z" fill="url(#paint8_linear_1_4)"/>
|
||||
<g filter="url(#filter0_f_1_4)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M472.04 573.85C446.69 547.47 370.77 459.82 370.77 459.82L376.64 460.08L495.5 548.85C504.4 555.32 517.1 555.25 525.92 548.7L642.47 460.2L648.65 459.71C648.65 459.71 575.21 545.28 547.08 573.47C518.95 601.66 497.39 600.23 472.04 573.85Z" fill="#458FCD"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M536.15 303.81C554.57 298.01 552.95 279.78 552.95 279.78C552.95 279.78 543.74 268.93 525.49 274.94C508.41 280.57 505.76 292.74 505.76 292.74C505.76 292.74 515.09 310.44 536.15 303.81Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_f_1_4" x="338.77" y="427.71" width="341.88" height="198.416" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_1_4"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1_4" x1="283.48" y1="307.2" x2="776.88" y2="767.42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1B91F3"/>
|
||||
<stop offset="1" stop-color="#0B68CB"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_1_4" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(282.57 465.55) rotate(66.5177) scale(295.96 283.519)">
|
||||
<stop offset="0.53" stop-color="#0B4186" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#0B4186" stop-opacity="0.45"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_1_4" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(521 344) rotate(-127.997) scale(63.2702 104.698)">
|
||||
<stop stop-color="#EF3ACC" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#EF3ACC" stop-opacity="0.64"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint3_linear_1_4" x1="420.77" y1="425.01" x2="598.31" y2="227.37" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0F5DB0"/>
|
||||
<stop offset="1" stop-color="#0F5DB0" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_1_4" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 702.7) rotate(-64.2624) scale(461.716 570.355)">
|
||||
<stop offset="0.02" stop-color="#094188"/>
|
||||
<stop offset="0.97" stop-color="#0B4186" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_1_4" x1="731.92" y1="568.11" x2="649.37" y2="770.8" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E247C4" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#E247C4" stop-opacity="0.64"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_1_4" x1="220.01" y1="386.34" x2="292.74" y2="679.06" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1" stop-color="#EF3ACC"/>
|
||||
<stop offset="1" stop-color="#EF3ACC" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_1_4" x1="512" y1="425.5" x2="512" y2="721.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="0.91" stop-color="#BEE1FE"/>
|
||||
<stop offset="1" stop-color="#96CEFD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_1_4" x1="517.54" y1="593" x2="517.54" y2="717" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#BCE0FD"/>
|
||||
<stop offset="1" stop-color="#88CCFC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 7.3 KiB |
170
images/client-icons/weechat.svg
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:export-ydpi="797.66998"
|
||||
inkscape:export-xdpi="797.66998"
|
||||
inkscape:export-filename="/tmp/weechat512.png"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="weechat2.svg"
|
||||
width="64"
|
||||
height="64"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="42"
|
||||
inkscape:window-x="148"
|
||||
inkscape:cy="32"
|
||||
inkscape:cx="11.643564"
|
||||
inkscape:zoom="12.625"
|
||||
showgrid="false"
|
||||
id="namedview1443"
|
||||
inkscape:window-height="968"
|
||||
inkscape:window-width="1638"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient891">
|
||||
<stop
|
||||
style="stop-color:#0e16be;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop887" />
|
||||
<stop
|
||||
style="stop-color:#63a4e1;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop889" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient883">
|
||||
<stop
|
||||
style="stop-color:#008726;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop879" />
|
||||
<stop
|
||||
style="stop-color:#7ecc6f;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop881" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient883"
|
||||
id="linearGradient885"
|
||||
x1="115.09375"
|
||||
y1="42.862499"
|
||||
x2="26.987501"
|
||||
y2="42.862499"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient891"
|
||||
id="linearGradient893"
|
||||
x1="60.854168"
|
||||
y1="97.102089"
|
||||
x2="60.854168"
|
||||
y2="62.706253"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter5519">
|
||||
<feFlood
|
||||
flood-opacity="0.60422568746132421"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood5509" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite5511" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur5513" />
|
||||
<feOffset
|
||||
dx="1.9"
|
||||
dy="1.4"
|
||||
result="offset"
|
||||
id="feOffset5515" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite5517" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter5531">
|
||||
<feFlood
|
||||
flood-opacity="0.60101983098700495"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood5521" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite5523" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="0.5"
|
||||
result="blur"
|
||||
id="feGaussianBlur5525" />
|
||||
<feOffset
|
||||
dx="1.9"
|
||||
dy="1.4"
|
||||
result="offset"
|
||||
id="feOffset5527" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="fbSourceGraphic"
|
||||
id="feComposite5529" />
|
||||
</filter>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer2"
|
||||
style="display:inline"
|
||||
transform="translate(-59.26667,-59.26667)">
|
||||
<path
|
||||
style="fill:url(#linearGradient885);fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5519)"
|
||||
d="m 121.70834,29.368751 -68.262505,1.5875 c -6.711747,0.156087 -13.464507,2.719336 -18.785501,7.005035 -5.124508,4.228038 -8.190215,10.463836 -9.7895,16.807465 l 24.341667,-1.058333 c 0.214884,-2.67862 1.733902,-5.109484 3.689147,-6.885223 1.76299,-1.5286 4.013062,-2.554657 6.365024,-2.639795 l 58.472918,-2.116649 z"
|
||||
id="path195"
|
||||
transform="matrix(0.47244092,0,0,0.47244092,59.26667,59.26667)" />
|
||||
<path
|
||||
style="fill:url(#linearGradient893);fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5531)"
|
||||
d="m 22.489584,61.647918 -10.31875,27.252084 c -1.362514,3.598435 -1.128016,8.11836 1.70141,10.913234 3.337143,3.296424 8.403518,3.779984 12.85067,3.374224 l 72.495838,-6.614541 16.139588,-47.625001 -20.637504,0.79375 -12.435417,35.189584 -11.112501,0.79375 12.435417,-35.454167 -22.225,1.058333 -13.229167,36.512501 -7.408314,0.264613 c -2.145162,0.07662 -3.30664,-2.908063 -2.645853,-4.76253 l 8.202083,-23.01875 z"
|
||||
id="path32"
|
||||
transform="matrix(0.47244092,0,0,0.47244092,59.26667,59.26667)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
4
images/copy.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 15H6C4.89543 15 4 14.1046 4 13V6C4 4.89543 4.89543 4 6 4H13C14.1046 4 15 4.89543 15 6V9.5" stroke="#0098d4" stroke-width="1.5"/>
|
||||
<rect x="9" y="9" width="11" height="11" rx="2" stroke="#0098d4" stroke-width="1.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 332 B |
BIN
images/fdroid-badge.png
Normal file
After Width: | Height: | Size: 26 KiB |
1
images/flathub-badge.svg
Normal file
After Width: | Height: | Size: 8.1 KiB |
41
images/google-play-us.svg
Normal file
After Width: | Height: | Size: 18 KiB |
3
images/link.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5285 6.54089L13.0273 6.04207C14.4052 4.66426 16.6259 4.65104 17.9874 6.01253C19.349 7.37402 19.3357 9.59466 17.9579 10.9725L15.5878 13.3425C14.21 14.7203 11.9893 14.7335 10.6277 13.372M11.4717 17.4589L10.9727 17.9579C9.59481 19.3357 7.37409 19.349 6.01256 17.9875C4.65102 16.626 4.66426 14.4053 6.04211 13.0275L8.41203 10.6577C9.78988 9.27988 12.0106 9.26665 13.3721 10.6281" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 549 B |
10
images/matrix-logo.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="66" height="28" viewBox="0 0 66 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.975097 0.640961V27.359H2.89517V28H0.238281V0H2.89517V0.640961H0.975097Z" fill="#2D2D2D"/>
|
||||
<path d="M8.37266 9.11071V10.4628H8.4111C8.7712 9.94812 9.20494 9.54849 9.71306 9.26518C10.2208 8.98235 10.8029 8.84036 11.4586 8.84036C12.0885 8.84036 12.664 8.96298 13.1846 9.2074C13.7054 9.45223 14.1009 9.88336 14.371 10.5015C14.6665 10.0638 15.0683 9.67744 15.5764 9.34266C16.0842 9.00804 16.6852 8.84036 17.3797 8.84036C17.9069 8.84036 18.3953 8.90487 18.8457 9.03365C19.2955 9.16242 19.6812 9.36843 20.0027 9.65166C20.3239 9.93515 20.5746 10.3053 20.755 10.7621C20.9349 11.2196 21.025 11.7698 21.025 12.4139V19.0966H18.2861V13.4373C18.2861 13.1027 18.2734 12.7872 18.2475 12.4908C18.2216 12.1949 18.1512 11.9375 18.0354 11.7183C17.9196 11.4996 17.7491 11.3256 17.5243 11.1967C17.2993 11.0684 16.9938 11.0037 16.6081 11.0037C16.2225 11.0037 15.9106 11.0782 15.6727 11.2257C15.4346 11.374 15.2483 11.5673 15.1134 11.8052C14.9784 12.0438 14.8884 12.314 14.8435 12.6168C14.7982 12.9192 14.7759 13.2252 14.7759 13.5342V19.0966H12.0372V13.4955C12.0372 13.1994 12.0305 12.9063 12.0181 12.6168C12.005 12.3269 11.9506 12.0598 11.8539 11.815C11.7575 11.5706 11.5967 11.374 11.3717 11.2257C11.1467 11.0782 10.8156 11.0037 10.3785 11.0037C10.2497 11.0037 10.0794 11.0327 9.86746 11.0908C9.65528 11.1487 9.44941 11.2584 9.25027 11.4191C9.05071 11.5802 8.88053 11.812 8.73908 12.1143C8.59754 12.4171 8.5269 12.8128 8.5269 13.3021V19.0966H5.78833V9.11071H8.37266Z" fill="#2D2D2D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8596 9.55506C23.4223 9.81286 23.0621 10.1539 22.7794 10.5789C22.4962 11.0036 22.3357 11.5382 22.2974 12.1818H25.036C25.0872 11.6412 25.2676 11.2547 25.5761 11.023C25.8847 10.7912 26.309 10.6752 26.8491 10.6752C27.0931 10.6752 27.3215 10.6917 27.5338 10.7234C27.7458 10.7558 27.9322 10.8202 28.093 10.9167C28.2537 11.0132 28.3823 11.1487 28.4787 11.3224C28.5752 11.4962 28.6233 11.7313 28.6233 12.0273C28.6359 12.3108 28.5523 12.5264 28.3726 12.6745C28.1924 12.8227 27.9483 12.9352 27.6397 13.0124C27.3311 13.0897 26.9774 13.1477 26.5789 13.1864C26.1802 13.225 25.7753 13.2766 25.3638 13.3408C24.9523 13.4056 24.5441 13.4923 24.1392 13.6016C23.734 13.711 23.374 13.8752 23.0592 14.094C22.7437 14.3131 22.4867 14.6059 22.2876 14.9731C22.0879 15.3398 21.9884 15.8067 21.9884 16.3731C21.9884 16.8879 22.0753 17.3326 22.2489 17.706C22.4225 18.0793 22.6635 18.3884 22.9722 18.6327C23.2807 18.8778 23.6406 19.0579 24.0522 19.1739C24.4636 19.2896 24.9072 19.3476 25.3831 19.3476C26.0003 19.3476 26.6046 19.2572 27.1963 19.0774C27.7873 18.897 28.3018 18.5815 28.739 18.1308C28.7517 18.2983 28.7741 18.4625 28.8065 18.6232C28.8385 18.7843 28.8804 18.9418 28.932 19.0965H31.7091C31.5805 18.8906 31.4903 18.5815 31.4393 18.1693C31.3877 17.7573 31.362 17.3264 31.362 16.8751V11.6798C31.362 11.0745 31.227 10.5883 30.957 10.2214C30.6868 9.85459 30.3398 9.56787 29.9155 9.36194C29.4911 9.15619 29.0217 9.0176 28.5074 8.94652C27.9931 8.87594 27.4854 8.84036 26.9838 8.84036C26.431 8.84036 25.8812 8.89531 25.3348 9.00463C24.7882 9.1142 24.2966 9.2976 23.8596 9.55506ZM27.6302 14.5965C27.8293 14.5578 28.0159 14.5096 28.1893 14.4518C28.363 14.3937 28.5076 14.3134 28.6235 14.21V15.2339C28.6235 15.3884 28.6072 15.5944 28.5754 15.8519C28.5431 16.1098 28.4562 16.3636 28.3149 16.6146C28.1732 16.8659 27.9548 17.0817 27.6592 17.2618C27.3632 17.4423 26.9455 17.5322 26.4055 17.5322C26.1868 17.5322 25.9747 17.5129 25.7692 17.4742C25.5632 17.4358 25.3833 17.368 25.2291 17.2715C25.0748 17.175 24.9525 17.0431 24.8625 16.8754C24.7724 16.7084 24.7275 16.502 24.7275 16.2576C24.7275 16.0001 24.7724 15.7876 24.8625 15.6201C24.9525 15.4531 25.0713 15.3145 25.2194 15.205C25.3671 15.0956 25.5407 15.0089 25.7402 14.9441C25.9393 14.88 26.1418 14.828 26.3476 14.7897C26.566 14.7511 26.7846 14.719 27.0034 14.693C27.2219 14.6674 27.4308 14.6352 27.6302 14.5965Z" fill="#2D2D2D"/>
|
||||
<path d="M38.5753 9.11176V10.9467H36.5696V15.8914C36.5696 16.3547 36.6467 16.6639 36.8011 16.8183C36.9552 16.9728 37.264 17.05 37.7268 17.05C37.8812 17.05 38.0288 17.0437 38.1704 17.0307C38.3117 17.0181 38.4468 16.9985 38.5753 16.9729V19.0975C38.3439 19.1362 38.0866 19.1618 37.8039 19.1749C37.521 19.1873 37.2446 19.194 36.9746 19.194C36.5503 19.194 36.1484 19.1649 35.7692 19.1069C35.3897 19.0491 35.0555 18.9367 34.7663 18.7691C34.4769 18.602 34.2486 18.3635 34.0816 18.0544C33.9143 17.7457 33.8308 17.3399 33.8308 16.8375V10.9467H32.1722V9.11176H33.8308V6.11795H36.5696V9.11176H38.5753Z" fill="#2D2D2D"/>
|
||||
<path d="M42.4905 9.11088V10.9652H42.5291C42.6575 10.6559 42.831 10.3697 43.0498 10.1055C43.2684 9.84179 43.519 9.61625 43.8019 9.42953C44.0845 9.24315 44.3869 9.09824 44.7086 8.99491C45.0297 8.89207 45.3642 8.84036 45.7115 8.84036C45.8914 8.84036 46.0905 8.87278 46.3093 8.93705V11.4868C46.1806 11.4608 46.0263 11.4382 45.8465 11.4191C45.6663 11.3997 45.4928 11.39 45.3256 11.39C44.8242 11.39 44.3999 11.474 44.0529 11.6411C43.7057 11.8086 43.4262 12.0369 43.2139 12.3267C43.0018 12.6166 42.8504 12.9544 42.7605 13.3408C42.6706 13.727 42.6256 14.1457 42.6256 14.5963V19.0966H39.8869V9.11088H42.4905Z" fill="#2D2D2D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.467 5.3064V7.56622H50.2059V5.3064H47.467ZM50.2054 19.0974V9.11166H47.4665V19.0974H50.2054Z" fill="#2D2D2D"/>
|
||||
<path d="M51.6319 9.1106H54.7563L56.5115 11.7181L58.2473 9.1106H61.2753L57.9966 13.7849L61.6805 19.0964H58.5559L56.4729 15.9482L54.3898 19.0964H51.3235L54.9107 13.843L51.6319 9.1106Z" fill="#2D2D2D"/>
|
||||
<path d="M65.0246 27.359V0.640961H63.1046V0H65.7616V28H63.1046V27.359H65.0246Z" fill="#2D2D2D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
7
images/member-icon.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-inside-1" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1332 16.3632C11.1709 16.7731 10.112 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9C17 11.6173 15.7432 13.941 13.8001 15.4005V15.4005C13.4881 15.6349 13.1583 15.847 12.8133 16.0344C12.5926 16.1543 12.3657 16.2641 12.1332 16.3632ZM9.00004 9.39998C10.3255 9.39998 11.4 8.23592 11.4 6.79998C11.4 5.36404 10.3255 4.19998 9.00004 4.19998C7.67456 4.19998 6.60004 5.36404 6.60004 6.79998C6.60004 8.23592 7.67456 9.39998 9.00004 9.39998ZM8.99989 15.4001C10.7295 15.4001 12.2989 14.7139 13.4507 13.599C12.7384 11.8404 11.0141 10.6 9.00009 10.6C6.98597 10.6 5.26159 11.8406 4.54932 13.5992C5.7011 14.714 7.27038 15.4001 8.99989 15.4001Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1332 16.3632C11.1709 16.7731 10.112 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9C17 11.6173 15.7432 13.941 13.8001 15.4005V15.4005C13.4881 15.6349 13.1583 15.847 12.8133 16.0344C12.5926 16.1543 12.3657 16.2641 12.1332 16.3632ZM9.00004 9.39998C10.3255 9.39998 11.4 8.23592 11.4 6.79998C11.4 5.36404 10.3255 4.19998 9.00004 4.19998C7.67456 4.19998 6.60004 5.36404 6.60004 6.79998C6.60004 8.23592 7.67456 9.39998 9.00004 9.39998ZM8.99989 15.4001C10.7295 15.4001 12.2989 14.7139 13.4507 13.599C12.7384 11.8404 11.0141 10.6 9.00009 10.6C6.98597 10.6 5.26159 11.8406 4.54932 13.5992C5.7011 14.714 7.27038 15.4001 8.99989 15.4001Z" fill="#666666"/>
|
||||
<path d="M12.1332 16.3632L11.3492 14.5232L12.1332 16.3632ZM13.8001 15.4005H11.8001V19.4042L15.0013 16.9996L13.8001 15.4005ZM13.8001 15.4005H15.8001V11.3967L12.5989 13.8014L13.8001 15.4005ZM12.8133 16.0344L13.768 17.7919L13.768 17.7919L12.8133 16.0344ZM13.4507 13.599L14.8418 15.036L15.8107 14.098L15.3044 12.8481L13.4507 13.599ZM4.54932 13.5992L2.69558 12.8485L2.18935 14.0984L3.15837 15.0363L4.54932 13.5992ZM9 19C10.3863 19 11.7115 18.7168 12.9171 18.2031L11.3492 14.5232C10.6303 14.8295 9.83767 15 9 15V19ZM-1 9C-1 14.5228 3.47715 19 9 19V15C5.68629 15 3 12.3137 3 9H-1ZM9 -1C3.47715 -1 -1 3.47715 -1 9H3C3 5.68629 5.68629 3 9 3V-1ZM19 9C19 3.47715 14.5228 -1 9 -1V3C12.3137 3 15 5.68629 15 9H19ZM15.0013 16.9996C17.4256 15.1786 19 12.2729 19 9H15C15 10.9617 14.0607 12.7034 12.5989 13.8014L15.0013 16.9996ZM15.8001 15.4005V15.4005H11.8001V15.4005H15.8001ZM12.5989 13.8014C12.3645 13.9774 12.1171 14.1365 11.8586 14.277L13.768 17.7919C14.1995 17.5574 14.6116 17.2923 15.0013 16.9996L12.5989 13.8014ZM12.9171 18.2031C13.2082 18.0791 13.4921 17.9418 13.768 17.7919L11.8586 14.277C11.6932 14.3668 11.5233 14.449 11.3492 14.5232L12.9171 18.2031ZM9.40004 6.79998C9.40004 7.01655 9.31985 7.18185 9.22749 7.2819C9.13763 7.37925 9.05614 7.39998 9.00004 7.39998V11.4C11.5778 11.4 13.4 9.18675 13.4 6.79998H9.40004ZM9.00004 6.19998C9.05614 6.19998 9.13763 6.22071 9.22749 6.31806C9.31985 6.41811 9.40004 6.58341 9.40004 6.79998H13.4C13.4 4.41321 11.5778 2.19998 9.00004 2.19998V6.19998ZM8.60004 6.79998C8.60004 6.58341 8.68024 6.41811 8.77259 6.31806C8.86246 6.22071 8.94395 6.19998 9.00004 6.19998V2.19998C6.42228 2.19998 4.60004 4.41321 4.60004 6.79998H8.60004ZM9.00004 7.39998C8.94395 7.39998 8.86246 7.37925 8.77259 7.28189C8.68024 7.18184 8.60004 7.01655 8.60004 6.79998H4.60004C4.60004 9.18675 6.42228 11.4 9.00004 11.4V7.39998ZM12.0597 12.162C11.2659 12.9304 10.1898 13.4001 8.99989 13.4001V17.4001C11.2693 17.4001 13.332 16.4975 14.8418 15.036L12.0597 12.162ZM9.00009 12.6C10.1718 12.6 11.18 13.3204 11.597 14.3498L15.3044 12.8481C14.2968 10.3605 11.8564 8.60004 9.00009 8.60004V12.6ZM6.40306 14.35C6.82004 13.3204 7.82833 12.6 9.00009 12.6V8.60004C6.1436 8.60004 3.70313 10.3607 2.69558 12.8485L6.40306 14.35ZM8.99989 13.4001C7.81008 13.4001 6.73407 12.9304 5.94027 12.1621L3.15837 15.0363C4.66812 16.4976 6.73068 17.4001 8.99989 17.4001V13.4001Z" fill="#666666" mask="url(#path-1-inside-1)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
1
images/platform-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="22" height="22" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.358 14.752H4.97a1.048 1.048 0 01-1.047-1.048V6.286c0-.579.469-1.048 1.047-1.048H17.08c.579 0 1.048.47 1.048 1.048v1.33M11.424 15.152v1.21M9.304 16.362h3.653" stroke="#666" stroke-width=".8" stroke-linecap="round" stroke-linejoin="round"/><rect x="14.543" y="8.955" width="3.914" height="7.371" rx=".648" stroke="#666" stroke-width=".8"/><path stroke="#666" stroke-width=".8" d="M16.028 15.174h1.048"/></svg>
|
After Width: | Height: | Size: 495 B |
3
images/tick-dark.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 12.5L8.84497 15.845C9.71398 16.714 11.1538 16.601 11.8767 15.6071L18.5 6.5" stroke="#333" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 249 B |
3
images/tick.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 12.5L8.84497 15.845C9.71398 16.714 11.1538 16.601 11.8767 15.6071L18.5 6.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 250 B |
26
index.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!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 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>
|
||||
<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>
|
||||
<p>It preserves your privacy by only processing what you view on the client side. For this to work, it needs javascript.</p>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
35
package.json
|
@ -1,4 +1,37 @@
|
|||
{
|
||||
"name": "matrix.to",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.17",
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/serve-local.js",
|
||||
"build": "node scripts/build.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@rollup/plugin-babel": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^15.0.0",
|
||||
"@rollup/plugin-multi-entry": "^4.0.0",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-replace": "^2.3.4",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"core-js": "^3.6.5",
|
||||
"finalhandler": "^1.1.2",
|
||||
"mdn-polyfills": "^5.20.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-css-variables": "^0.17.0",
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-url": "^8.0.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rollup": "^2.26.4",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"serve-static": "^1.14.1",
|
||||
"xxhashjs": "^0.2.2"
|
||||
}
|
||||
}
|
||||
|
|
307
scripts/build.js
Normal file
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 cheerio from "cheerio";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import xxhash from 'xxhashjs';
|
||||
import { rollup } from 'rollup';
|
||||
import postcss from "postcss";
|
||||
import postcssImport from "postcss-import";
|
||||
// needed for legacy bundle
|
||||
import babel from '@rollup/plugin-babel';
|
||||
// needed to find the polyfill modules in the main-legacy.js bundle
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
// needed because some of the polyfills are written as commonjs modules
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
// multi-entry plugin so we can add polyfill file to main
|
||||
import multi from '@rollup/plugin-multi-entry';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
// replace urls of asset names with content hashed version
|
||||
import postcssUrl from "postcss-url";
|
||||
import cssvariables from "postcss-css-variables";
|
||||
import autoprefixer from "autoprefixer";
|
||||
import flexbugsFixes from "postcss-flexbugs-fixes";
|
||||
|
||||
import {createClients} from "../src/open/clients/index.js";
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
const projectDir = path.join(dirname(fileURLToPath(import.meta.url)), "../");
|
||||
|
||||
async function build() {
|
||||
// get version number
|
||||
const version = JSON.parse(await fs.readFile(path.join(projectDir, "package.json"), "utf8")).version;
|
||||
// clear target dir
|
||||
const targetDir = path.join(projectDir, "build/");
|
||||
await removeDirIfExists(targetDir);
|
||||
await fs.mkdir(targetDir);
|
||||
await fs.mkdir(path.join(targetDir, "images"));
|
||||
await fs.mkdir(path.join(targetDir, "img")); // contains the badge image for historical reasons, unhashed
|
||||
await fs.mkdir(path.join(targetDir, ".well-known"));
|
||||
const assets = new AssetMap(targetDir);
|
||||
const imageAssets = await copyFolder(path.join(projectDir, "images"), path.join(targetDir, "images"));
|
||||
assets.addSubMap(imageAssets);
|
||||
await assets.write(`bundle.js`, await buildJs("src/main.js", assets, ["src/polyfill.js"]));
|
||||
await assets.write(`bundle.css`, await buildCss("css/main.css", targetDir, assets));
|
||||
await assets.writeUnhashed(".well-known/apple-app-site-association", buildAppleAssociatedAppsFile(createClients()));
|
||||
await assets.writeUnhashed("index.html", await buildHtml(assets));
|
||||
await assets.writeUnhashed("img/matrix-badge.svg", await fs.readFile(path.join(projectDir, "images-nohash/matrix-badge.svg")));
|
||||
const globalHash = assets.hashForAll();
|
||||
console.log(`built matrix.to ${version} (${globalHash}) successfully with ${assets.size} files`);
|
||||
}
|
||||
|
||||
async function buildHtml(assets) {
|
||||
const devHtml = await fs.readFile(path.join(projectDir, "index.html"), "utf8");
|
||||
const doc = cheerio.load(devHtml);
|
||||
doc("link[rel=stylesheet]").attr("href", assets.resolve(`bundle.css`));
|
||||
const mainScripts = [
|
||||
// this is needed to avoid hitting https://github.com/facebook/regenerator/issues/378
|
||||
// which prevents the whole bundle to load, as our CSP headers don't allow unsafe-eval
|
||||
// and I preferred this over disabling strict mode for the whole bundle
|
||||
`<script type="text/javascript">window.regeneratorRuntime = undefined;</script>`,
|
||||
`<script type="text/javascript" src="${assets.resolve(`bundle.js`)}"></script>`,
|
||||
`<script type="text/javascript">bundle.main(document.body);</script>`
|
||||
];
|
||||
doc("script#main").replaceWith(mainScripts.join(""));
|
||||
return doc.html();
|
||||
}
|
||||
|
||||
function createReplaceUrlPlugin(assets) {
|
||||
const replacements = {};
|
||||
for (const [key, value] of assets) {
|
||||
replacements[key] = value;
|
||||
}
|
||||
return replace(replacements);
|
||||
}
|
||||
|
||||
async function buildJs(mainFile, assets, extraFiles = []) {
|
||||
// compile down to whatever IE 11 needs
|
||||
const babelPlugin = babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "entry",
|
||||
corejs: "3",
|
||||
targets: "IE 11",
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
// create js bundle
|
||||
const rollupConfig = {
|
||||
// important the extraFiles come first,
|
||||
// so polyfills are available in the global scope
|
||||
// if needed for the mainfile
|
||||
input: extraFiles.concat(mainFile),
|
||||
plugins: [multi(), commonjs(), nodeResolve(), createReplaceUrlPlugin(assets), babelPlugin, terser()]
|
||||
};
|
||||
const bundle = await rollup(rollupConfig);
|
||||
const {output} = await bundle.generate({
|
||||
format: 'iife',
|
||||
name: `bundle`
|
||||
});
|
||||
const code = output[0].code;
|
||||
return code;
|
||||
}
|
||||
|
||||
function buildAppleAssociatedAppsFile(clients) {
|
||||
const appIds = clients.map(c => c.appleAssociatedAppId).flat().filter(id => !!id);
|
||||
return JSON.stringify({
|
||||
"applinks": {
|
||||
"details": [
|
||||
{
|
||||
appIDs: appIds,
|
||||
components: [
|
||||
{
|
||||
"#": "/*",
|
||||
"comment": "Only open urls with a fragment, so you can still create links"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function buildCss(entryPath, targetDir, assets) {
|
||||
entryPath = path.join(projectDir, entryPath);
|
||||
const assetUrlMapper = ({absolutePath}) => {
|
||||
const relPath = absolutePath.slice(projectDir.length);
|
||||
return assets.resolve(path.join(targetDir, relPath));
|
||||
};
|
||||
|
||||
const preCss = await fs.readFile(entryPath, "utf8");
|
||||
const options = [
|
||||
postcssImport,
|
||||
cssvariables(),
|
||||
autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}),
|
||||
flexbugsFixes(),
|
||||
postcssUrl({url: assetUrlMapper}),
|
||||
];
|
||||
const cssBundler = postcss(options);
|
||||
const result = await cssBundler.process(preCss, {from: entryPath});
|
||||
return result.css;
|
||||
}
|
||||
|
||||
async function removeDirIfExists(targetDir) {
|
||||
try {
|
||||
await fs.rmdir(targetDir, {recursive: true});
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyFolder(srcRoot, dstRoot, filter = null, assets = null) {
|
||||
assets = assets || new AssetMap(dstRoot);
|
||||
const dirEnts = await fs.readdir(srcRoot, {withFileTypes: true});
|
||||
for (const dirEnt of dirEnts) {
|
||||
const dstPath = path.join(dstRoot, dirEnt.name);
|
||||
const srcPath = path.join(srcRoot, dirEnt.name);
|
||||
if (dirEnt.isDirectory()) {
|
||||
await fs.mkdir(dstPath);
|
||||
await copyFolder(srcPath, dstPath, filter, assets);
|
||||
} else if ((dirEnt.isFile() || dirEnt.isSymbolicLink()) && (!filter || filter(srcPath))) {
|
||||
const content = await fs.readFile(srcPath);
|
||||
await assets.write(dstPath, content);
|
||||
}
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
function contentHash(str) {
|
||||
var hasher = new xxhash.h32(0);
|
||||
hasher.update(str);
|
||||
return hasher.digest();
|
||||
}
|
||||
|
||||
class AssetMap {
|
||||
constructor(targetDir) {
|
||||
// remove last / if any, so substr in create works well
|
||||
this._targetDir = path.resolve(targetDir);
|
||||
this._assets = new Map();
|
||||
// hashes for unhashed resources so changes in these resources also contribute to the hashForAll
|
||||
this._unhashedHashes = [];
|
||||
}
|
||||
|
||||
_toRelPath(resourcePath) {
|
||||
let relPath = resourcePath;
|
||||
if (path.isAbsolute(resourcePath)) {
|
||||
if (!resourcePath.startsWith(this._targetDir)) {
|
||||
throw new Error(`absolute path ${resourcePath} that is not within target dir ${this._targetDir}`);
|
||||
}
|
||||
relPath = resourcePath.slice(this._targetDir.length + 1); // + 1 for the /
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
||||
_create(resourcePath, content) {
|
||||
const relPath = this._toRelPath(resourcePath);
|
||||
const hash = contentHash(Buffer.from(content));
|
||||
const dir = path.dirname(relPath);
|
||||
const extname = path.extname(relPath);
|
||||
const basename = path.basename(relPath, extname);
|
||||
const dstRelPath = path.join(dir, `${basename}-${hash}${extname}`);
|
||||
this._assets.set(relPath, dstRelPath);
|
||||
return dstRelPath;
|
||||
}
|
||||
|
||||
async write(resourcePath, content) {
|
||||
const relPath = this._create(resourcePath, content);
|
||||
const fullPath = path.join(this.directory, relPath);
|
||||
if (typeof content === "string") {
|
||||
await fs.writeFile(fullPath, content, "utf8");
|
||||
} else {
|
||||
await fs.writeFile(fullPath, content);
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
||||
async writeUnhashed(resourcePath, content) {
|
||||
const relPath = this._toRelPath(resourcePath);
|
||||
this._assets.set(relPath, relPath);
|
||||
const fullPath = path.join(this.directory, relPath);
|
||||
if (typeof content === "string") {
|
||||
await fs.writeFile(fullPath, content, "utf8");
|
||||
} else {
|
||||
await fs.writeFile(fullPath, content);
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
||||
get directory() {
|
||||
return this._targetDir;
|
||||
}
|
||||
|
||||
resolve(resourcePath) {
|
||||
const relPath = this._toRelPath(resourcePath);
|
||||
const result = this._assets.get(relPath);
|
||||
if (!result) {
|
||||
throw new Error(`unknown path: ${relPath}, only know ${Array.from(this._assets.keys()).join(", ")}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
addSubMap(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.slice(this.directory.length + 1);
|
||||
for (const [key, value] of assetMap._assets.entries()) {
|
||||
this._assets.set(path.join(relSubRoot, key), path.join(relSubRoot, value));
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._assets.entries();
|
||||
}
|
||||
|
||||
isUnhashed(relPath) {
|
||||
const resolvedPath = this._assets.get(relPath);
|
||||
if (!resolvedPath) {
|
||||
throw new Error("Unknown asset: " + relPath);
|
||||
}
|
||||
return relPath === resolvedPath;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._assets.size;
|
||||
}
|
||||
|
||||
has(relPath) {
|
||||
return this._assets.has(relPath);
|
||||
}
|
||||
|
||||
hashForAll() {
|
||||
const globalHashAssets = Array.from(this).map(([, resolved]) => resolved);
|
||||
globalHashAssets.push(...this._unhashedHashes);
|
||||
globalHashAssets.sort();
|
||||
return contentHash(globalHashAssets.join(","));
|
||||
}
|
||||
|
||||
addToHashForAll(resourcePath, content) {
|
||||
this._unhashedHashes.push(`${resourcePath}-${contentHash(Buffer.from(content))}`);
|
||||
}
|
||||
}
|
||||
|
||||
build().catch(err => console.error(err));
|
8
scripts/package.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
set -e
|
||||
VERSION=$(jq -r ".version" package.json)
|
||||
PACKAGE=matrixto-$VERSION.tar.gz
|
||||
yarn build
|
||||
pushd build
|
||||
tar -czvf ../$PACKAGE ./
|
||||
popd
|
||||
echo $PACKAGE
|
48
scripts/serve-local.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 finalhandler from "finalhandler"
|
||||
import http from "http"
|
||||
import serveStatic from "serve-static"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url";
|
||||
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");
|
||||
// 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']
|
||||
}
|
||||
);
|
||||
|
||||
// Create server
|
||||
const server = http.createServer(function onRequest (req, res) {
|
||||
console.log(req.method, req.url);
|
||||
serve(req, res, finalhandler(req, res))
|
||||
});
|
||||
|
||||
// Listen
|
||||
server.listen(5000);
|
||||
console.log("Listening on port 5000");
|
56
src/InvalidUrlView.js
Normal file
|
@ -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)
|
||||
])
|
||||
))
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -14,19 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.tsx'],
|
||||
addons: [
|
||||
'@storybook/preset-create-react-app',
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-storysource',
|
||||
'@storybook/addon-viewport/register',
|
||||
'@storybook/addon-a11y/register',
|
||||
'@storybook/addon-knobs/register',
|
||||
'@storybook/addon-actions/register',
|
||||
'storybook-addon-designs',
|
||||
'@storybook/addon-backgrounds/register',
|
||||
],
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
212
src/Link.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
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 {createEnum} from "./utils/enum.js";
|
||||
import {orderedUnique} from "./utils/unique.js";
|
||||
|
||||
const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/;
|
||||
const ROOMID_PATTERN = /^!([^:]*):(.+)$/;
|
||||
const USERID_PATTERN = /^@([^:]+):(.+)$/;
|
||||
const EVENTID_PATTERN = /^$([^:]+):(.+)$/;
|
||||
const GROUPID_PATTERN = /^\+([^:]+):(.+)$/;
|
||||
|
||||
export const IdentifierKind = createEnum(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
function getWebInstanceMap(queryParams) {
|
||||
const prefix = "web-instance[";
|
||||
const postfix = "]";
|
||||
const webInstanceParams = queryParams.filter(([key]) => key.startsWith(prefix) && key.endsWith(postfix));
|
||||
const webInstances = webInstanceParams.map(([key, value]) => {
|
||||
const noPrefix = key.slice(prefix.length);
|
||||
const clientId = noPrefix.slice(0, -postfix.length);
|
||||
return [clientId, value];
|
||||
});
|
||||
return webInstances.reduce((map, [clientId, host]) => {
|
||||
map[clientId] = host;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
export const LinkKind = createEnum(
|
||||
"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 !!(
|
||||
USERID_PATTERN.exec(identifier) ||
|
||||
ROOMALIAS_PATTERN.exec(identifier) ||
|
||||
ROOMID_PATTERN.exec(identifier) ||
|
||||
GROUPID_PATTERN.exec(identifier)
|
||||
);
|
||||
}
|
||||
|
||||
static parseIdentifier(identifier) {
|
||||
return Link._parse(identifier);
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
const clientParam = queryParams.find(([key]) => key === "client");
|
||||
if (clientParam) {
|
||||
clientId = clientParam[1];
|
||||
}
|
||||
webInstances = getWebInstanceMap(queryParams);
|
||||
}
|
||||
return Link._parse(identifier, eventId, clientId, viaServers, webInstances);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
}
|
64
src/Platform.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
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 {createEnum} from "./utils/enum.js";
|
||||
|
||||
export const Platform = createEnum(
|
||||
"DesktopWeb",
|
||||
"MobileWeb",
|
||||
"Android",
|
||||
"iOS",
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux"
|
||||
);
|
||||
|
||||
export function guessApplicablePlatforms(userAgent, platform) {
|
||||
// return [Platform.DesktopWeb, Platform.Linux];
|
||||
let nativePlatform;
|
||||
let webPlatform;
|
||||
if (/android/i.test(userAgent)) {
|
||||
nativePlatform = Platform.Android;
|
||||
webPlatform = Platform.MobileWeb;
|
||||
} else if ( // https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885
|
||||
(
|
||||
/iPad|iPhone|iPod/.test(navigator.platform) ||
|
||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
||||
) && !window.MSStream
|
||||
) {
|
||||
nativePlatform = Platform.iOS;
|
||||
webPlatform = Platform.MobileWeb;
|
||||
} else if (platform.toLowerCase().indexOf("linux") !== -1) {
|
||||
nativePlatform = Platform.Linux;
|
||||
webPlatform = Platform.DesktopWeb;
|
||||
} else if (platform.toLowerCase().indexOf("mac") !== -1) {
|
||||
nativePlatform = Platform.macOS;
|
||||
webPlatform = Platform.DesktopWeb;
|
||||
} else {
|
||||
nativePlatform = Platform.Windows;
|
||||
webPlatform = Platform.DesktopWeb;
|
||||
}
|
||||
return [nativePlatform, webPlatform];
|
||||
}
|
||||
|
||||
export function isWebPlatform(p) {
|
||||
return p === Platform.DesktopWeb || p === Platform.MobileWeb;
|
||||
}
|
||||
|
||||
|
||||
export function isDesktopPlatform(p) {
|
||||
return p === Platform.Linux || p === Platform.Windows || p === Platform.macOS;
|
||||
}
|
68
src/Preferences.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
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 {Platform} from "./Platform.js";
|
||||
import {EventEmitter} from "./utils/ViewModel.js";
|
||||
|
||||
export class Preferences extends EventEmitter {
|
||||
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;
|
||||
|
||||
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}));
|
||||
this.emit("canClear")
|
||||
}
|
||||
|
||||
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");
|
||||
this._localStorage.removeItem("consented_servers");
|
||||
this.clientId = null;
|
||||
this.platform = null;
|
||||
this.homeservers = null;
|
||||
}
|
||||
|
||||
get canClear() {
|
||||
return !!this.clientId || !!this.platform || !!this.homeservers;
|
||||
}
|
||||
}
|
49
src/RootView.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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 {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.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.li(t.a({href: "#/disclaimer/"}, "Disclaimer")),
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function externalLink(t, href, label) {
|
||||
return t.a({href, target: "_blank", rel: "noopener noreferrer"}, label);
|
||||
}
|
98
src/RootViewModel.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 {Link} from "./Link.js";
|
||||
import {ViewModel} from "./utils/ViewModel.js";
|
||||
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;
|
||||
this.loadServerPolicyViewModel = null;
|
||||
this.invalidUrlViewModel = null;
|
||||
this.showDisclaimer = false;
|
||||
this.preferences.on("canClear", () => {
|
||||
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(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_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.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 {
|
||||
this._updateChildVMs(null, oldLink);
|
||||
this.invalidUrlViewModel = new InvalidUrlViewModel(this.childOptions({
|
||||
fragment: hash
|
||||
}));
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
clearPreferences() {
|
||||
this.preferences.clear();
|
||||
this._updateChildVMs();
|
||||
}
|
||||
|
||||
get hasPreferences() {
|
||||
return this.preferences.canClear;
|
||||
}
|
||||
}
|
56
src/create/CreateLinkView.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
import {PreviewView} from "../preview/PreviewView.js";
|
||||
import {copyButton} from "../utils/copy.js";
|
||||
|
||||
export class CreateLinkView extends TemplateView {
|
||||
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",
|
||||
required: true,
|
||||
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"}))
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
_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;
|
||||
if (!this.value.validateIdentifier(inputField.value)) {
|
||||
inputField.setCustomValidity("That doesn't seem valid. Try #room:example.com, @user:example.com or +group:example.com.");
|
||||
} else {
|
||||
inputField.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
}
|
38
src/create/CreateLinkViewModel.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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 {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
||||
import {Link} from "../Link.js";
|
||||
|
||||
export class CreateLinkViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._link = null;
|
||||
this.previewViewModel = null;
|
||||
}
|
||||
|
||||
validateIdentifier(identifier) {
|
||||
return Link.validateIdentifier(identifier);
|
||||
}
|
||||
|
||||
async createLink(identifier) {
|
||||
this._link = Link.parseIdentifier(identifier);
|
||||
if (this._link) {
|
||||
this.openLink("#" + this._link.toFragment());
|
||||
}
|
||||
}
|
||||
}
|
33
src/disclaimer/DisclaimerView.js
Normal file
|
@ -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.'
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
38
src/main.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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 {xhrRequest} from "./utils/xhr.js";
|
||||
import {RootViewModel} from "./RootViewModel.js";
|
||||
import {RootView} from "./RootView.js";
|
||||
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));
|
||||
});
|
||||
}
|
67
src/open/ClientListView.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
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 {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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ContinueWithClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel)))
|
||||
]);
|
||||
}
|
||||
}
|
89
src/open/ClientListViewModel.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
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 {isWebPlatform, Platform} from "../Platform.js";
|
||||
import {Maturity} from "./types.js";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
get showUnsupportedPlatforms() {
|
||||
return this._showUnsupportedPlatforms;
|
||||
}
|
||||
|
||||
get showExperimental() {
|
||||
return this._showExperimental;
|
||||
}
|
||||
|
||||
set showUnsupportedPlatforms(enabled) {
|
||||
this._showUnsupportedPlatforms = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
|
||||
set showExperimental(enabled) {
|
||||
this._showExperimental = enabled;
|
||||
this._filterClients();
|
||||
}
|
||||
|
||||
_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 preferredClientVMs = clientVMs.filter(c => c.hasPreferredWebInstance);
|
||||
const otherClientVMs = clientVMs.filter(c => !c.hasPreferredWebInstance);
|
||||
this.clientList = preferredClientVMs.concat(otherClientVMs);
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
_pickClient(client) {
|
||||
this.clientViewModel = this.clientList.find(vm => vm.clientId === client.id);
|
||||
this.clientViewModel.pick(this);
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
showAll() {
|
||||
this.clientViewModel = null;
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
142
src/open/ClientView.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
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;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function renderInstructions(parts) {
|
||||
return parts.map(p => {
|
||||
if (p.type === "code") {
|
||||
return tag.code(p.text);
|
||||
} else {
|
||||
return text(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class ClientView extends TemplateView {
|
||||
|
||||
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({
|
||||
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.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)),
|
||||
showBack(t, vm),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class InstallClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
const children = [];
|
||||
|
||||
const textInstructions = vm.textInstructions;
|
||||
if (textInstructions) {
|
||||
const copyButton = t.button({
|
||||
className: "copy",
|
||||
title: "Copy instructions",
|
||||
"aria-label": "Copy instructions",
|
||||
onClick: evt => {
|
||||
if (copy(vm.copyString, copyButton.parentElement)) {
|
||||
copyButton.className = "tick";
|
||||
setTimeout(() => {
|
||||
copyButton.className = "copy";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function showBack(t, vm) {
|
||||
return t.p({className: {caption: true, "back": true, hidden: vm => !vm.showBack}}, [
|
||||
`Continue with ${vm.name} · `,
|
||||
t.button({className: "text", onClick: () => vm.back()}, "Change"),
|
||||
]);
|
||||
}
|
||||
|
||||
function renderAction(t, a) {
|
||||
let badgeUrl;
|
||||
switch (a.kind) {
|
||||
case "play-store": badgeUrl = "images/google-play-us.svg"; break;
|
||||
case "fdroid": badgeUrl = "images/fdroid-badge.png"; break;
|
||||
case "apple-app-store": badgeUrl = "images/app-store-us-alt.svg"; break;
|
||||
case "flathub": badgeUrl = "images/flathub-badge.svg"; break;
|
||||
}
|
||||
return t.a({
|
||||
href: a.url,
|
||||
className: {
|
||||
fullwidth: !badgeUrl,
|
||||
primary: a.primary && !badgeUrl,
|
||||
secondary: !a.primary && !badgeUrl,
|
||||
badge: !!badgeUrl,
|
||||
},
|
||||
rel: "noopener noreferrer",
|
||||
["aria-label"]: a.label,
|
||||
onClick: () => a.activated()
|
||||
}, badgeUrl ? t.img({src: badgeUrl}) : a.label);
|
||||
}
|
239
src/open/ClientViewModel.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
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 {isWebPlatform, isDesktopPlatform, Platform} from "../Platform.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
import {IdentifierKind} from "../Link.js";
|
||||
|
||||
function getMatchingPlatforms(client, supportedPlatforms) {
|
||||
const clientPlatforms = client.platforms;
|
||||
const matchingPlatforms = supportedPlatforms.filter(p => {
|
||||
return clientPlatforms.includes(p);
|
||||
});
|
||||
return matchingPlatforms;
|
||||
}
|
||||
|
||||
export class ClientViewModel extends ViewModel {
|
||||
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 preferredPlatform = matchingPlatforms.find(p => p === this.preferences.platform);
|
||||
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;
|
||||
}
|
||||
|
||||
// these are only shown in the open stage
|
||||
_createOpenActions() {
|
||||
const hasPreferredWebInstance = this.hasPreferredWebInstance;
|
||||
let deepLinkLabel = "Continue";
|
||||
if (hasPreferredWebInstance) {
|
||||
if (this._proposedPlatform === this._nativePlatform) {
|
||||
deepLinkLabel = "Open in app";
|
||||
} else {
|
||||
deepLinkLabel = `Open on ${this._client.getPreferredWebInstance(this._link)}`;
|
||||
}
|
||||
}
|
||||
const actions = [];
|
||||
const proposedDeepLink = this._client.getDeepLink(this._proposedPlatform, this._link);
|
||||
if (proposedDeepLink) {
|
||||
actions.push({
|
||||
label: deepLinkLabel,
|
||||
url: proposedDeepLink,
|
||||
primary: true,
|
||||
activated: () => {
|
||||
this._pickClient(this._client);
|
||||
this.preferences.setClient(this._client.id, this._proposedPlatform);
|
||||
// only show install screen if we tried to open a native deeplink
|
||||
if (this._showOpen && this._proposedPlatform === this._nativePlatform) {
|
||||
this._showOpen = false;
|
||||
this.emitChange();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
// show only if there is a preferred instance, and if we don't already link to it in the first button
|
||||
if (hasPreferredWebInstance && this._webPlatform && this._proposedPlatform !== this._webPlatform) {
|
||||
actions.push({
|
||||
label: `Open on ${this._client.getPreferredWebInstance(this._link)}`,
|
||||
url: this._client.getDeepLink(this._webPlatform, this._link),
|
||||
kind: "open-in-web",
|
||||
activated: () => {} // don't persist this choice as we don't persist the preferred web instance, it's in the url
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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: () => {
|
||||
if (!this.hasPreferredWebInstance) {
|
||||
this.preferences.setClient(this._client.id, this._webPlatform);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
get hasPreferredWebInstance() {
|
||||
// also check there is a web platform that matches the platforms the user is on (mobile or desktop web)
|
||||
return this._webPlatform && typeof this._client.getPreferredWebInstance(this._link) === "string";
|
||||
}
|
||||
|
||||
get hostedByBannerLabel() {
|
||||
const preferredWebInstance = this._client.getPreferredWebInstance(this._link);
|
||||
if (this._webPlatform && preferredWebInstance) {
|
||||
let label = preferredWebInstance;
|
||||
const subDomainIdx = preferredWebInstance.lastIndexOf(".", preferredWebInstance.lastIndexOf("."));
|
||||
if (subDomainIdx !== -1) {
|
||||
label = preferredWebInstance.slice(preferredWebInstance.length - subDomainIdx + 1);
|
||||
}
|
||||
return `Hosted by ${label}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
get homepage() {
|
||||
return this._client.homepage;
|
||||
}
|
||||
|
||||
get identifier() {
|
||||
return this._link.identifier;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this._client.description;
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this._client.id;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._client.name;
|
||||
}
|
||||
|
||||
get iconUrl() {
|
||||
return this._client.icon;
|
||||
}
|
||||
|
||||
get stage() {
|
||||
return this._showOpen ? "open" : "install";
|
||||
}
|
||||
|
||||
get textInstructions() {
|
||||
let instructions = this._client.getLinkInstructions(this._proposedPlatform, this._link);
|
||||
if (instructions && !Array.isArray(instructions)) {
|
||||
instructions = [instructions];
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
get copyString() {
|
||||
return this._client.getCopyString(this._proposedPlatform, this._link);
|
||||
}
|
||||
|
||||
get showDeepLinkInInstall() {
|
||||
// we can assume this._nativePlatform as this._clientCanIntercept already checks it
|
||||
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;
|
||||
}
|
||||
|
||||
pick(clientListViewModel) {
|
||||
this._clientListViewModel = clientListViewModel;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
// whether or not we are only showing this client (in open or install stage)
|
||||
get showBack() {
|
||||
return !!this._clientListViewModel;
|
||||
}
|
||||
|
||||
back() {
|
||||
if (this._clientListViewModel) {
|
||||
const vm = this._clientListViewModel;
|
||||
this._clientListViewModel = null;
|
||||
// clear the client preference so we default back to to native link if any
|
||||
// in the list with all clients, and also if we refresh, we get the list with
|
||||
// all clients rather than having our "change client" click reverted.
|
||||
this.preferences.setClient(undefined, undefined);
|
||||
this._update();
|
||||
this.emitChange();
|
||||
vm.showAll();
|
||||
}
|
||||
}
|
||||
}
|
45
src/open/OpenLinkView.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientListView} from "./ClientListView.js";
|
||||
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 ?
|
||||
new ShowLinkView(vm) :
|
||||
new ServerConsentView(vm.serverConsentViewModel)
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowLinkView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div([
|
||||
t.view(new PreviewView(vm.previewViewModel)),
|
||||
t.view(new ClientListView(vm.clientsViewModel)),
|
||||
t.p({className: {caption: true, hidden: vm => !vm.previewDomain}}, [
|
||||
vm => vm.previewFailed ? `${vm.previewDomain} has not returned a preview.` : `Preview provided by ${vm.previewDomain}`,
|
||||
" · ",
|
||||
t.button({className: "text", onClick: () => vm.changeServer()}, "Change"),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
95
src/open/OpenLinkViewModel.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
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 {ClientListViewModel} from "./ClientListViewModel.js";
|
||||
import {ClientViewModel} from "./ClientViewModel.js";
|
||||
import {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
||||
import {ServerConsentViewModel} from "./ServerConsentViewModel.js";
|
||||
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;
|
||||
this.serverConsentViewModel = null;
|
||||
this.previewViewModel = null;
|
||||
this.clientsViewModel = null;
|
||||
this.previewLoading = false;
|
||||
if (this.preferences.homeservers === null) {
|
||||
this._showServerConsent();
|
||||
} else {
|
||||
this._showLink();
|
||||
}
|
||||
}
|
||||
|
||||
_showServerConsent() {
|
||||
let servers = [];
|
||||
if (this.preferences.homeservers) {
|
||||
servers.push(...this.preferences.homeservers);
|
||||
}
|
||||
servers.push(...this._link.servers);
|
||||
servers = orderedUnique(servers);
|
||||
this.serverConsentViewModel = new ServerConsentViewModel(this.childOptions({
|
||||
servers,
|
||||
done: () => {
|
||||
this.serverConsentViewModel = null;
|
||||
this._showLink();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async _showLink() {
|
||||
const clientId = this.preferences.clientId || this._link.clientId;
|
||||
const preferredClient = clientId ? this._clients.find(c => c.id === clientId) : null;
|
||||
this.clientsViewModel = new ClientListViewModel(this.childOptions({
|
||||
clients: this._clients,
|
||||
link: this._link,
|
||||
client: preferredClient,
|
||||
}));
|
||||
this.previewViewModel = new PreviewViewModel(this.childOptions({
|
||||
link: this._link,
|
||||
consentedServers: this.preferences.homeservers
|
||||
}));
|
||||
this.previewLoading = true;
|
||||
this.emitChange();
|
||||
await this.previewViewModel.load();
|
||||
this.previewLoading = false;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
get previewDomain() {
|
||||
return this.previewViewModel?.domain;
|
||||
}
|
||||
|
||||
get previewFailed() {
|
||||
return this.previewViewModel?.failed;
|
||||
}
|
||||
|
||||
get showClientsLabel() {
|
||||
return getLabelForLinkKind(this._link.kind);
|
||||
}
|
||||
|
||||
changeServer() {
|
||||
this.previewViewModel = null;
|
||||
this.clientsViewModel = null;
|
||||
this._showServerConsent();
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
134
src/open/ServerConsentView.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientListView} from "./ClientListView.js";
|
||||
import {PreviewView} from "../preview/PreviewView.js";
|
||||
|
||||
export class ServerConsentView extends TemplateView {
|
||||
render(t, vm) {
|
||||
const useAnotherServer = t.button({
|
||||
className: "text",
|
||||
onClick: () => vm.setShowServers()}, "use another server");
|
||||
const continueWithoutPreview = t.button({
|
||||
className: "text",
|
||||
onClick: () => vm.continueWithoutConsent(this._askEveryTimeChecked)
|
||||
}, "continue without a preview");
|
||||
return t.div({className: "ServerConsentView"}, [
|
||||
t.p([
|
||||
"Preview this link using the ",
|
||||
t.strong(vm => vm.selectedServer || "…"),
|
||||
" homeserver ",
|
||||
t.span({className: {hidden: vm => !vm.selectedServer}}, [
|
||||
" (",
|
||||
t.a({
|
||||
href: vm => `#/policy/${vm.selectedServer}`,
|
||||
target: "_blank",
|
||||
}, "privacy policy"),
|
||||
")",
|
||||
]),
|
||||
t.span({className: {hidden: vm => vm.showSelectServer}}, [
|
||||
", ",
|
||||
useAnotherServer,
|
||||
]),
|
||||
" or ",
|
||||
continueWithoutPreview,
|
||||
"."
|
||||
]),
|
||||
t.form({action: "#", id: "serverConsentForm", onSubmit: evt => this._onSubmit(evt)}, [
|
||||
t.mapView(vm => vm.showSelectServer, show => show ? new ServerOptions(vm) : null),
|
||||
t.div({className: "actions"}, [
|
||||
t.label([t.input({type: "checkbox", name: "askEveryTime"}), "Ask every time"]),
|
||||
t.input({type: "submit", value: "Continue", className: "primary fullwidth"})
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
_onSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
this.value.continueWithSelection(this._askEveryTimeChecked);
|
||||
}
|
||||
|
||||
get _askEveryTimeChecked() {
|
||||
const form = document.getElementById("serverConsentForm");
|
||||
const {askEveryTime} = form.elements;
|
||||
return askEveryTime.checked;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerOptions extends TemplateView {
|
||||
render(t, vm) {
|
||||
const options = vm.servers.map(server => {
|
||||
return t.div(t.label([t.input({
|
||||
type: "radio",
|
||||
name: "selectedServer",
|
||||
value: server,
|
||||
checked: server === vm.selectedServer
|
||||
}), t.span(server)]))
|
||||
});
|
||||
options.push(t.div({className: "other"}, t.label([
|
||||
t.input({type: "radio", name: "selectedServer", value: "other"}),
|
||||
t.input({
|
||||
type: "text",
|
||||
className: "line",
|
||||
placeholder: "Other",
|
||||
name: "otherServer",
|
||||
onClick: evt => this._onClickOther(evt),
|
||||
})
|
||||
])));
|
||||
return t.div({
|
||||
className: "ServerOptions",
|
||||
onChange: evt => this._onChange(evt),
|
||||
}, options);
|
||||
}
|
||||
|
||||
_onClickOther(evt) {
|
||||
const textField = evt.target;
|
||||
const radio = Array.from(textField.form.elements.selectedServer).find(r => r.value === "other");
|
||||
if (!radio.checked) {
|
||||
radio.checked = true;
|
||||
this._onChangeServerRadio(radio);
|
||||
}
|
||||
}
|
||||
|
||||
_onChange(evt) {
|
||||
let {name, value} = evt.target;
|
||||
if (name === "selectedServer") {
|
||||
this._onChangeServerRadio(evt.target);
|
||||
} else if (name === "otherServer") {
|
||||
const textField = evt.target;
|
||||
if(!this.value.selectOtherServer(value)) {
|
||||
textField.setCustomValidity("Please enter a valid domain name");
|
||||
} else {
|
||||
textField.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onChangeServerRadio(radio) {
|
||||
let {value, form} = radio;
|
||||
const {otherServer} = form.elements;
|
||||
if (value === "other") {
|
||||
otherServer.required = true;
|
||||
value = otherServer.value;
|
||||
} else {
|
||||
otherServer.required = false;
|
||||
otherServer.setCustomValidity("");
|
||||
}
|
||||
this.value.selectServer(value);
|
||||
}
|
||||
}
|
71
src/open/ServerConsentViewModel.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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 {ClientListViewModel} from "./ClientListViewModel.js";
|
||||
import {ClientViewModel} from "./ClientViewModel.js";
|
||||
import {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
||||
import {getLabelForLinkKind} from "../Link.js";
|
||||
import {orderedUnique} from "../utils/unique.js";
|
||||
|
||||
export class ServerConsentViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.servers = options.servers;
|
||||
this.done = options.done;
|
||||
this.selectedServer = this.servers[0];
|
||||
this.showSelectServer = false;
|
||||
}
|
||||
|
||||
setShowServers() {
|
||||
this.showSelectServer = true;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
selectServer(server) {
|
||||
this.selectedServer = server;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
selectOtherServer(domainOrUrl) {
|
||||
let urlStr = domainOrUrl;
|
||||
if (!urlStr.startsWith("http://") && !urlStr.startsWith("https://")) {
|
||||
urlStr = `https://${domainOrUrl}`;
|
||||
}
|
||||
try {
|
||||
const domain = new URL(urlStr).hostname;
|
||||
if (/((?:[0-9a-zA-Z][0-9a-zA-Z-]{1,61}\.)+)(xn--[a-z0-9]+|[a-z]+)/.test(domain) || domain === "localhost") {
|
||||
this.selectServer(domainOrUrl);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {}
|
||||
this.selectServer(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
continueWithSelection(askEveryTime) {
|
||||
// keep previously consented servers
|
||||
const homeservers = this.preferences.homeservers || [];
|
||||
homeservers.unshift(this.selectedServer);
|
||||
this.preferences.setHomeservers(orderedUnique(homeservers), !askEveryTime);
|
||||
this.done();
|
||||
}
|
||||
|
||||
continueWithoutConsent(askEveryTime) {
|
||||
this.preferences.setHomeservers([], !askEveryTime);
|
||||
this.done();
|
||||
}
|
||||
}
|
84
src/open/clients/Cinny.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
let fragmentPath;
|
||||
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `direct/create?userId=${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `home/${encodeURIComponent(link.identifier)}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `home/${encodeURIComponent(link.identifier)}/${encodeURIComponent(link.eventId)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((link.kind === LinkKind.Event || link.kind === LinkKind.Room) && link.servers.length > 0) {
|
||||
fragmentPath += `?via=${link.servers.map(server => encodeURIComponent(server)).join(',')}`;
|
||||
}
|
||||
|
||||
return `https://app.cinny.in/${fragmentPath}`
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {}
|
||||
|
||||
getCopyString(platform, link) {}
|
||||
|
||||
getInstallLinks(platform) {}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
114
src/open/clients/Element.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
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, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
|
||||
const trustedWebInstances = [
|
||||
"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 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",
|
||||
"7J4U792NQT.io.element.elementx",
|
||||
"7J4U792NQT.io.element.elementx.nightly",
|
||||
"7J4U792NQT.io.element.elementx.pr"
|
||||
];
|
||||
}
|
||||
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('&');
|
||||
}
|
||||
|
||||
const isWebPlatform = platform === Platform.DesktopWeb || platform === Platform.MobileWeb;
|
||||
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 `element://${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
|
||||
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/download")];
|
||||
}
|
||||
}
|
||||
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {
|
||||
const idx = trustedWebInstances.indexOf(link.webInstances[this.id])
|
||||
return idx === -1 ? undefined : trustedWebInstances[idx];
|
||||
}
|
||||
}
|
94
src/open/clients/Fluffychat.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
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, FlathubLink, AppleStoreLink, PlayStoreLink, FDroidLink, WebsiteLink } from "../types.js";
|
||||
|
||||
/**
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Fluffychat {
|
||||
get id() { return "im.fluffychat"; }
|
||||
get name() { return "FluffyChat"; }
|
||||
get icon() { return "images/client-icons/fluffychat.svg"; }
|
||||
get author() { return "Krille Fear"; }
|
||||
get homepage() { return "https://fluffychat.im"; }
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android, Platform.iOS,
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
Platform.DesktopWeb,
|
||||
];
|
||||
}
|
||||
get description() { return "Chat with your friends using the cutest messenger in the Matrix network"; }
|
||||
getMaturity(platform) {
|
||||
switch (platform) {
|
||||
case Platform.Android: return Maturity.Stable;
|
||||
case Platform.iOS: return Maturity.Stable;
|
||||
case Platform.DesktopWeb: return Maturity.Stable;
|
||||
case Platform.Linux: return Maturity.Stable;
|
||||
case Platform.macOS: return Maturity.Beta;
|
||||
case Platform.Windows: return Maturity.Beta;
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.iOS: return [new AppleStoreLink("fluffychat", "id1551469600")];
|
||||
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")];
|
||||
}
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {
|
||||
if (link.kind === LinkKind.User) {
|
||||
switch (platform) {
|
||||
case Platform.Android: return;
|
||||
case Platform.DesktopWeb: return "Open the web app at https://fluffychat.im/web/ and log in to your account. Click on '+' and paste the username.";
|
||||
default: return "Open the app and click on '+' and paste the username.";
|
||||
}
|
||||
}
|
||||
if (link.kind === LinkKind.Room) {
|
||||
switch (platform) {
|
||||
case Platform.Android: return;
|
||||
case Platform.DesktopWeb: return "Open the web app at https://fluffychat.im/web/ and log in to your account. Click on 'Discover' and paste the identifier.";
|
||||
default: return "Open the app on your device. Click on 'Discover' and 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 `im.fluffychat://chat/${link.identifier}`;
|
||||
case Platform.iOS: return `im.fluffychat://chat/${link.identifier}`;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
canInterceptMatrixToLinks(platform) {
|
||||
return platform === Platform.Android;
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
81
src/open/clients/Fractal.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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, 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 icon() { return "images/client-icons/fractal.svg"; }
|
||||
get author() { return "Daniel Garcia Moreno"; }
|
||||
get homepage() { return "https://gitlab.gnome.org/World/fractal"; }
|
||||
get platforms() { return [Platform.Linux]; }
|
||||
get description() { return 'GNOME client, suitable for desktop and mobile. Written in Rust.'; }
|
||||
getMaturity(platform) { return Maturity.Beta; }
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
if (platform === Platform.Linux) {
|
||||
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) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
return "Click the menu button above the list of rooms, select the Join Room entry, and paste the identifier";
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
|
||||
return link.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
if (platform === Platform.Linux) {
|
||||
return [new FlathubLink("org.gnome.Fractal")];
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
79
src/open/clients/NeoChat.js
Normal file
|
@ -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) {}
|
||||
}
|
85
src/open/clients/Nheko.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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, 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 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}`;
|
||||
|
||||
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) {
|
||||
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")];
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
56
src/open/clients/Quaternion.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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, 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 "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; }
|
||||
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}`)];
|
||||
}
|
||||
}
|
||||
|
||||
getCopyString(platform, link) {
|
||||
switch (link.kind) {
|
||||
case LinkKind.User: return `/invite ${link.identifier}`;
|
||||
case LinkKind.Room: return `/join ${link.identifier}`;
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
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")];
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
112
src/open/clients/SchildiChat.js
Normal file
|
@ -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];
|
||||
}
|
||||
}
|
91
src/open/clients/Syphon.js
Normal file
|
@ -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) {}
|
||||
}
|
52
src/open/clients/Tensor.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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, FlathubLink, style} from "../types.js";
|
||||
|
||||
export class Tensor {
|
||||
get id() { return "tensor"; }
|
||||
get name() { return "Tensor"; }
|
||||
get icon() { return "images/client-icons/tensor.png"; }
|
||||
get author() { return "David A Roberts"; }
|
||||
get homepage() { return "https://github.com/davidar/tensor"; }
|
||||
get platforms() { return [Platform.Windows, Platform.macOS, Platform.Linux, Platform.Android, Platform.iOS]; }
|
||||
get description() { return 'QML and JS cross-platform desktop Matrix client'; }
|
||||
getMaturity(platform) { return Maturity.Alpha; }
|
||||
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}`)];
|
||||
}
|
||||
}
|
||||
|
||||
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.Android) {
|
||||
return [new FDroidLink("io.davidar.tensor")];
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
70
src/open/clients/Thunderbird.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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, FlathubLink, 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) {
|
||||
const links = [];
|
||||
if (platform === Platform.Linux) {
|
||||
links.push(new FlathubLink("org.mozilla.Thunderbird"));
|
||||
}
|
||||
links.push(new WebsiteLink(this.homepage));
|
||||
return links;
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
51
src/open/clients/Weechat.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
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 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; }
|
||||
|
||||
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) {}
|
||||
|
||||
getPreferredWebInstance(link) {}
|
||||
}
|
45
src/open/clients/index.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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 {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 SchildiChat(),
|
||||
new Weechat(),
|
||||
new Nheko(),
|
||||
new Fractal(),
|
||||
new Quaternion(),
|
||||
new Tensor(),
|
||||
new Fluffychat(),
|
||||
new NeoChat(),
|
||||
new Syphon(),
|
||||
new Thunderbird(),
|
||||
new Cinny(),
|
||||
];
|
||||
}
|
117
src/open/types.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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 {createEnum} from "../utils/enum.js";
|
||||
export const Maturity = createEnum("Alpha", "Beta", "Stable");
|
||||
export {LinkKind} from "../Link.js";
|
||||
export {Platform} from "../Platform.js";
|
||||
|
||||
export class AppleStoreLink {
|
||||
constructor(org, appId) {
|
||||
this._org = org;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://apps.apple.com/app/${encodeURIComponent(this._org)}/${encodeURIComponent(this._appId)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "apple-app-store";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Download on the App Store";
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayStoreLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://play.google.com/store/apps/details?id=${encodeURIComponent(this._appId)}&referrer=${encodeURIComponent(link.identifier)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "play-store";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Get it on Google Play";
|
||||
}
|
||||
}
|
||||
|
||||
export class FDroidLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://f-droid.org/packages/${encodeURIComponent(this._appId)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "fdroid";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Get it on F-Droid";
|
||||
}
|
||||
}
|
||||
|
||||
export class FlathubLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://flathub.org/apps/details/${encodeURIComponent(this._appId)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "flathub";
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return "Get it on Flathub";
|
||||
}
|
||||
}
|
||||
|
||||
export class WebsiteLink {
|
||||
constructor(url) {
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "website";
|
||||
}
|
||||
|
||||
getDescription(platform) {
|
||||
return `Download for ${platform}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const style = {
|
||||
code(text) {
|
||||
return {type: "code", text};
|
||||
}
|
||||
}
|
26
src/policy/LoadServerPolicyView.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
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 {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}}),
|
||||
t.h2(vm => vm.message)
|
||||
]);
|
||||
}
|
||||
}
|
52
src/policy/LoadServerPolicyViewModel.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 {resolveServer} from "../preview/HomeServer.js";
|
||||
|
||||
export class LoadServerPolicyViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.server = options.server;
|
||||
this.message = `Looking up ${this.server} privacy policy…`;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
this.emitChange();
|
||||
try {
|
||||
const homeserver = await resolveServer(this.request, this.server);
|
||||
if (homeserver) {
|
||||
const url = await homeserver.getPrivacyPolicyUrl();
|
||||
if (url) {
|
||||
this.message = `Loading ${this.server} privacy policy now…`;
|
||||
this.openLink(url);
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.message = `${this.server} does not declare a privacy policy.`;
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.message = `${this.server} does not look like a matrix homeserver.`;
|
||||
}
|
||||
} catch (err) {
|
||||
this.loading = false;
|
||||
this.message = `Failed to get the privacy policy for ${this.server}`;
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
18
src/polyfill.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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 "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
142
src/preview/HomeServer.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
function noTrailingSlash(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}`;
|
||||
}
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
async getUserProfile(userId) {
|
||||
const {body} = await this._request(`${this.baseURL}/_matrix/client/r0/profile/${encodeURIComponent(userId)}`).response();
|
||||
return body;
|
||||
}
|
||||
|
||||
// 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 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();
|
||||
headers.set("Content-Type", "application/json");
|
||||
const options = {method: "POST", body: "{}", headers};
|
||||
const {status, body} = await this._request(`${this.baseURL}/_matrix/client/r0/register`, options).response();
|
||||
if (status === 401 && body) { // Unauthorized
|
||||
const hasTermsStage = body.flows.some(flow => flow.stages.includes("m.login.terms"));
|
||||
if (hasTermsStage) {
|
||||
const privacyPolicy = body.params?.["m.login.terms"]?.policies?.privacy_policy;
|
||||
if (privacyPolicy) {
|
||||
const firstLang = Object.keys(privacyPolicy).find(k => k !== "version");
|
||||
let languagePolicy = privacyPolicy[lang] || privacyPolicy[firstLang];
|
||||
return languagePolicy?.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mxcUrlThumbnail(url, width, height, method) {
|
||||
const parts = parseMxcUrl(url);
|
||||
if (parts) {
|
||||
const [serverName, mediaId] = parts;
|
||||
const httpUrl = `${this.baseURL}/_matrix/media/r0/thumbnail/${encodeURIComponent(serverName)}/${encodeURIComponent(mediaId)}`;
|
||||
return httpUrl + `?width=${width}&height=${height}&method=${method}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseMxcUrl(url) {
|
||||
const prefix = "mxc://";
|
||||
if (url.startsWith(prefix)) {
|
||||
return url.slice(prefix.length).split("/", 2);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function encodeQueryParams(queryParams) {
|
||||
return Object.entries(queryParams || {})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([name, value]) => {
|
||||
if (typeof value === "object") {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
return `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join("&");
|
||||
}
|
62
src/preview/PreviewView.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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 {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientListView} from "../open/ClientListView.js";
|
||||
import {ClientView} from "../open/ClientView.js";
|
||||
|
||||
export class PreviewView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "PreviewView"}, t.mapView(vm => vm.loading, loading => {
|
||||
return loading ? new LoadingPreviewView(vm) : new LoadedPreviewView(vm);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingPreviewView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div([
|
||||
t.div({className: "avatarContainer"}, t.div({className: "avatar loading"}, t.div({className: "spinner"}))),
|
||||
t.h1(vm => vm.name),
|
||||
t.p({className: "identifier placeholder"}),
|
||||
t.div({className: {memberCount: true, loading: true, hidden: !vm.hasMemberCount}}, t.p({className: "placeholder"})),
|
||||
t.p({className: {topic: true, loading: true, hidden: !vm.hasTopic}}, [
|
||||
t.div({className: "placeholder"}),
|
||||
t.div({className: "placeholder"}),
|
||||
t.div({className: "placeholder"}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedPreviewView extends TemplateView {
|
||||
render(t, vm) {
|
||||
const avatar = t.map(vm => vm.avatarUrl, (avatarUrl, t) => {
|
||||
if (avatarUrl) {
|
||||
return t.img({className: "avatar", src: avatarUrl});
|
||||
} else {
|
||||
return t.div({className: "defaultAvatar"});
|
||||
}
|
||||
});
|
||||
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]),
|
||||
]);
|
||||
}
|
||||
}
|
125
src/preview/PreviewViewModel.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
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 {LinkKind, IdentifierKind} from "../Link.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
import {resolveServer} from "./HomeServer.js";
|
||||
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;
|
||||
this.failed = false;
|
||||
this.isSpaceRoom = false;
|
||||
}
|
||||
|
||||
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:
|
||||
case LinkKind.Event:
|
||||
await this._loadRoomPreview(homeserver, this._link);
|
||||
break;
|
||||
}
|
||||
// assume we're done if nothing threw
|
||||
this.domain = server;
|
||||
this.loading = false;
|
||||
this.emitChange();
|
||||
return;
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
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 _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;
|
||||
this.identifier = null;
|
||||
this.avatarUrl = null;
|
||||
}
|
||||
}
|
370
src/utils/TemplateView.js
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS } from "./html.js";
|
||||
|
||||
/**
|
||||
Bindable template. Renders once, and allows bindings for given nodes. If you need
|
||||
to change the structure on a condition, use a subtemplate (if)
|
||||
|
||||
supports
|
||||
- event handlers (attribute fn value with name that starts with on)
|
||||
- one way binding of attributes (other attribute fn value)
|
||||
- one way binding of text values (child fn value)
|
||||
- refs to get dom nodes
|
||||
- className binding returning object with className => enabled map
|
||||
- add subviews inside the template
|
||||
*/
|
||||
// TODO: should we rename this to BoundView or something? As opposed to StaticView ...
|
||||
export class TemplateView {
|
||||
constructor(value, render = undefined) {
|
||||
this._value = value;
|
||||
// TODO: can avoid this if we have a separate class for inline templates vs class template views
|
||||
this._render = render;
|
||||
this._eventListeners = null;
|
||||
this._bindings = null;
|
||||
this._subViews = null;
|
||||
this._root = null;
|
||||
// TODO: can avoid this if we adopt the handleEvent pattern in our EventListener
|
||||
this._boundUpdateFromValue = null;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
_subscribe() {
|
||||
if (typeof this._value?.on === "function") {
|
||||
this._boundUpdateFromValue = this._updateFromValue.bind(this);
|
||||
this._value.on("change", this._boundUpdateFromValue);
|
||||
}
|
||||
}
|
||||
|
||||
_unsubscribe() {
|
||||
if (this._boundUpdateFromValue) {
|
||||
if (typeof this._value.off === "function") {
|
||||
this._value.off("change", this._boundUpdateFromValue);
|
||||
}
|
||||
this._boundUpdateFromValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
_attach() {
|
||||
if (this._eventListeners) {
|
||||
for (let {node, name, fn, useCapture} of this._eventListeners) {
|
||||
node.addEventListener(name, fn, useCapture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_detach() {
|
||||
if (this._eventListeners) {
|
||||
for (let {node, name, fn, useCapture} of this._eventListeners) {
|
||||
node.removeEventListener(name, fn, useCapture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mount(options) {
|
||||
const builder = new TemplateBuilder(this);
|
||||
if (this._render) {
|
||||
this._root = this._render(builder, this._value);
|
||||
} else if (this.render) { // overriden in subclass
|
||||
this._root = this.render(builder, this._value);
|
||||
} else {
|
||||
throw new Error("no render function passed in, or overriden in subclass");
|
||||
}
|
||||
const parentProvidesUpdates = options && options.parentProvidesUpdates;
|
||||
if (!parentProvidesUpdates) {
|
||||
this._subscribe();
|
||||
}
|
||||
this._attach();
|
||||
return this._root;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this._detach();
|
||||
this._unsubscribe();
|
||||
if (this._subViews) {
|
||||
for (const v of this._subViews) {
|
||||
v.unmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root() {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
_updateFromValue(changedProps) {
|
||||
this.update(this._value, changedProps);
|
||||
}
|
||||
|
||||
update(value) {
|
||||
this._value = value;
|
||||
if (this._bindings) {
|
||||
for (const binding of this._bindings) {
|
||||
binding();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addEventListener(node, name, fn, useCapture = false) {
|
||||
if (!this._eventListeners) {
|
||||
this._eventListeners = [];
|
||||
}
|
||||
this._eventListeners.push({node, name, fn, useCapture});
|
||||
}
|
||||
|
||||
_addBinding(bindingFn) {
|
||||
if (!this._bindings) {
|
||||
this._bindings = [];
|
||||
}
|
||||
this._bindings.push(bindingFn);
|
||||
}
|
||||
|
||||
addSubView(view) {
|
||||
if (!this._subViews) {
|
||||
this._subViews = [];
|
||||
}
|
||||
this._subViews.push(view);
|
||||
}
|
||||
|
||||
removeSubView(view) {
|
||||
const idx = this._subViews.indexOf(view);
|
||||
if (idx !== -1) {
|
||||
this._subViews.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// what is passed to render
|
||||
class TemplateBuilder {
|
||||
constructor(templateView) {
|
||||
this._templateView = templateView;
|
||||
}
|
||||
|
||||
get _value() {
|
||||
return this._templateView._value;
|
||||
}
|
||||
|
||||
addEventListener(node, name, fn, useCapture = false) {
|
||||
this._templateView._addEventListener(node, name, fn, useCapture);
|
||||
}
|
||||
|
||||
_addAttributeBinding(node, name, fn) {
|
||||
let prevValue = undefined;
|
||||
const binding = () => {
|
||||
const newValue = fn(this._value);
|
||||
if (prevValue !== newValue) {
|
||||
prevValue = newValue;
|
||||
setAttribute(node, name, newValue);
|
||||
}
|
||||
};
|
||||
this._templateView._addBinding(binding);
|
||||
binding();
|
||||
}
|
||||
|
||||
_addClassNamesBinding(node, obj) {
|
||||
this._addAttributeBinding(node, "className", value => classNames(obj, value));
|
||||
}
|
||||
|
||||
_addTextBinding(fn) {
|
||||
const initialValue = fn(this._value);
|
||||
const node = text(initialValue);
|
||||
let prevValue = initialValue;
|
||||
const binding = () => {
|
||||
const newValue = fn(this._value);
|
||||
if (prevValue !== newValue) {
|
||||
prevValue = newValue;
|
||||
node.textContent = newValue+"";
|
||||
}
|
||||
};
|
||||
|
||||
this._templateView._addBinding(binding);
|
||||
return node;
|
||||
}
|
||||
|
||||
_setNodeAttributes(node, attributes) {
|
||||
for(let [key, value] of Object.entries(attributes)) {
|
||||
const isFn = typeof value === "function";
|
||||
// binding for className as object of className => enabled
|
||||
if (key === "className" && typeof value === "object" && value !== null) {
|
||||
if (objHasFns(value)) {
|
||||
this._addClassNamesBinding(node, value);
|
||||
} else {
|
||||
setAttribute(node, key, classNames(value));
|
||||
}
|
||||
} else if (key.startsWith("on") && key.length > 2 && isFn) {
|
||||
const eventName = key.slice(2, 3).toLowerCase() + key.slice(3);
|
||||
const handler = value;
|
||||
this._templateView._addEventListener(node, eventName, handler);
|
||||
} else if (isFn) {
|
||||
this._addAttributeBinding(node, key, value);
|
||||
} else {
|
||||
setAttribute(node, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setNodeChildren(node, children) {
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
}
|
||||
for (let child of children) {
|
||||
if (typeof child === "function") {
|
||||
child = this._addTextBinding(child);
|
||||
} else if (!child.nodeType) {
|
||||
// not a DOM node, turn into text
|
||||
child = text(child);
|
||||
}
|
||||
node.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
_addReplaceNodeBinding(fn, renderNode) {
|
||||
let prevValue = fn(this._value);
|
||||
let node = renderNode(null);
|
||||
|
||||
const binding = () => {
|
||||
const newValue = fn(this._value);
|
||||
if (prevValue !== newValue) {
|
||||
prevValue = newValue;
|
||||
const newNode = renderNode(node);
|
||||
if (node.parentNode) {
|
||||
node.parentNode.replaceChild(newNode, node);
|
||||
}
|
||||
node = newNode;
|
||||
}
|
||||
};
|
||||
this._templateView._addBinding(binding);
|
||||
return node;
|
||||
}
|
||||
|
||||
el(name, attributes, children) {
|
||||
return this.elNS(HTML_NS, name, attributes, children);
|
||||
}
|
||||
|
||||
elNS(ns, name, attributes, children) {
|
||||
if (attributes && isChildren(attributes)) {
|
||||
children = attributes;
|
||||
attributes = null;
|
||||
}
|
||||
|
||||
const node = document.createElementNS(ns, name);
|
||||
|
||||
if (attributes) {
|
||||
this._setNodeAttributes(node, attributes);
|
||||
}
|
||||
if (children) {
|
||||
this._setNodeChildren(node, children);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// this insert a view, and is not a view factory for `if`, so returns the root element to insert in the template
|
||||
// you should not call t.view() and not use the result (e.g. attach the result to the template DOM tree).
|
||||
view(view) {
|
||||
let root;
|
||||
try {
|
||||
root = view.mount();
|
||||
} catch (err) {
|
||||
return errorToDOM(err);
|
||||
}
|
||||
this._templateView.addSubView(view);
|
||||
return root;
|
||||
}
|
||||
|
||||
// map a value to a view, every time the value changes
|
||||
mapView(mapFn, viewCreator) {
|
||||
return this._addReplaceNodeBinding(mapFn, (prevNode) => {
|
||||
if (prevNode && prevNode.nodeType !== Node.COMMENT_NODE) {
|
||||
const subViews = this._templateView._subViews;
|
||||
const viewIdx = subViews.findIndex(v => v.root() === prevNode);
|
||||
if (viewIdx !== -1) {
|
||||
const [view] = subViews.splice(viewIdx, 1);
|
||||
view.unmount();
|
||||
}
|
||||
}
|
||||
const view = viewCreator(mapFn(this._value));
|
||||
if (view) {
|
||||
return this.view(view);
|
||||
} else {
|
||||
return document.createComment("node binding placeholder");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Special case of mapView for a TemplateView.
|
||||
// Always creates a TemplateView, if this is optional depending
|
||||
// on mappedValue, use `if` or `mapView`
|
||||
map(mapFn, renderFn) {
|
||||
return this.mapView(mapFn, mappedValue => {
|
||||
return new TemplateView(this._value, (t, vm) => {
|
||||
const rootNode = renderFn(mappedValue, t, vm);
|
||||
if (!rootNode) {
|
||||
// TODO: this will confuse mapView which assumes that
|
||||
// a comment node means there is no view to clean up
|
||||
return document.createComment("map placeholder");
|
||||
}
|
||||
return rootNode;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ifView(predicate, viewCreator) {
|
||||
return this.mapView(
|
||||
value => !!predicate(value),
|
||||
enabled => enabled ? viewCreator(this._value) : null
|
||||
);
|
||||
}
|
||||
|
||||
// creates a conditional subtemplate
|
||||
// use mapView if you need to map to a different view class
|
||||
if(predicate, renderFn) {
|
||||
return this.ifView(predicate, vm => new TemplateView(vm, renderFn));
|
||||
}
|
||||
}
|
||||
|
||||
function errorToDOM(error) {
|
||||
const stack = new Error().stack;
|
||||
const callee = stack.split("\n")[1];
|
||||
return tag.div([
|
||||
tag.h2("Something went wrong…"),
|
||||
tag.h3(error.message),
|
||||
tag.p(`This occurred while running ${callee}.`),
|
||||
tag.pre(error.stack),
|
||||
]);
|
||||
}
|
||||
|
||||
function objHasFns(obj) {
|
||||
for(const value of Object.values(obj)) {
|
||||
if (typeof value === "function") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [ns, tags] of Object.entries(TAG_NAMES)) {
|
||||
for (const tag of tags) {
|
||||
TemplateBuilder.prototype[tag] = function(attributes, children) {
|
||||
return this.elNS(ns, tag, attributes, children);
|
||||
};
|
||||
}
|
||||
}
|
78
src/utils/ViewModel.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export class EventEmitter {
|
||||
constructor() {
|
||||
this._handlersByName = {};
|
||||
}
|
||||
|
||||
emit(name, ...values) {
|
||||
const handlers = this._handlersByName[name];
|
||||
if (handlers) {
|
||||
for(const h of handlers) {
|
||||
h(...values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
on(name, callback) {
|
||||
let handlers = this._handlersByName[name];
|
||||
if (!handlers) {
|
||||
this._handlersByName[name] = handlers = new Set();
|
||||
}
|
||||
handlers.add(callback);
|
||||
return () => {
|
||||
this.off(name, callback);
|
||||
}
|
||||
}
|
||||
|
||||
off(name, callback) {
|
||||
const handlers = this._handlersByName[name];
|
||||
if (handlers) {
|
||||
handlers.delete(callback);
|
||||
if (handlers.length === 0) {
|
||||
delete this._handlersByName[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewModel extends EventEmitter {
|
||||
constructor(options) {
|
||||
super();
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
this.emit("change");
|
||||
}
|
||||
|
||||
get request() { return this._options.request; }
|
||||
get origin() { return this._options.origin; }
|
||||
get openLink() { return this._options.openLink; }
|
||||
get platforms() { return this._options.platforms; }
|
||||
get preferences() { return this._options.preferences; }
|
||||
|
||||
childOptions(options = {}) {
|
||||
return Object.assign({
|
||||
request: this.request,
|
||||
origin: this.origin,
|
||||
openLink: this.openLink,
|
||||
platforms: this.platforms,
|
||||
preferences: this.preferences,
|
||||
}, options);
|
||||
}
|
||||
}
|
49
src/utils/copy.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
function selectNode(node) {
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
const range = document.createRange();
|
||||
range.selectNode(node);
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
export function copy(text, parent) {
|
||||
const span = document.createElement("span");
|
||||
span.innerText = text;
|
||||
parent.appendChild(span);
|
||||
selectNode(span);
|
||||
const result = document.execCommand("copy");
|
||||
parent.removeChild(span);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function copyButton(t, getCopyText, label, classNames) {
|
||||
return t.button({className: `${classNames} icon copy`, onClick: evt => {
|
||||
const button = evt.target;
|
||||
if (copy(getCopyText(), button)) {
|
||||
button.innerText = "Copied!";
|
||||
button.classList.remove("copy");
|
||||
button.classList.add("tick");
|
||||
setTimeout(() => {
|
||||
button.classList.remove("tick");
|
||||
button.classList.add("copy");
|
||||
button.innerText = label;
|
||||
}, 2000);
|
||||
}
|
||||
}}, label);
|
||||
}
|
26
src/utils/enum.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
export function createEnum(...values) {
|
||||
const obj = {};
|
||||
for (const value of values) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Invalid enum value name" + value?.toString());
|
||||
}
|
||||
obj[value] = value;
|
||||
}
|
||||
return Object.freeze(obj);
|
||||
}
|
32
src/utils/error.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export class ConnectionError extends Error {
|
||||
constructor(message, isTimeout) {
|
||||
super(message || "ConnectionError");
|
||||
this.isTimeout = isTimeout;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "ConnectionError";
|
||||
}
|
||||
}
|
||||
|
||||
export class AbortError extends Error {
|
||||
get name() {
|
||||
return "AbortError";
|
||||
}
|
||||
}
|
111
src/utils/html.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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.
|
||||
*/
|
||||
|
||||
// DOM helper functions
|
||||
|
||||
export function isChildren(children) {
|
||||
// children should be an not-object (that's the attributes), or a domnode, or an array
|
||||
return typeof children !== "object" || !!children.nodeType || Array.isArray(children);
|
||||
}
|
||||
|
||||
export function classNames(obj, value) {
|
||||
return Object.entries(obj).reduce((cn, [name, enabled]) => {
|
||||
if (typeof enabled === "function") {
|
||||
enabled = enabled(value);
|
||||
}
|
||||
if (enabled) {
|
||||
return cn + (cn.length ? " " : "") + name;
|
||||
} else {
|
||||
return cn;
|
||||
}
|
||||
}, "");
|
||||
}
|
||||
|
||||
export function setAttribute(el, name, value) {
|
||||
if (name === "className") {
|
||||
name = "class";
|
||||
}
|
||||
if (value === false) {
|
||||
el.removeAttribute(name);
|
||||
} else {
|
||||
if (value === true) {
|
||||
value = name;
|
||||
}
|
||||
el.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function el(elementName, attributes, children) {
|
||||
return elNS(HTML_NS, elementName, attributes, children);
|
||||
}
|
||||
|
||||
export function elNS(ns, elementName, attributes, children) {
|
||||
if (attributes && isChildren(attributes)) {
|
||||
children = attributes;
|
||||
attributes = null;
|
||||
}
|
||||
|
||||
const e = document.createElementNS(ns, elementName);
|
||||
|
||||
if (attributes) {
|
||||
for (let [name, value] of Object.entries(attributes)) {
|
||||
if (name === "className" && typeof value === "object" && value !== null) {
|
||||
value = classNames(value);
|
||||
}
|
||||
setAttribute(e, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (children) {
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
}
|
||||
for (let c of children) {
|
||||
if (!c.nodeType) {
|
||||
c = text(c);
|
||||
}
|
||||
e.appendChild(c);
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
export function text(str) {
|
||||
return document.createTextNode(str);
|
||||
}
|
||||
|
||||
export const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
export const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
export const TAG_NAMES = {
|
||||
[HTML_NS]: [
|
||||
"br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"p", "strong", "em", "span", "img", "section", "main", "article", "aside",
|
||||
"pre", "button", "time", "input", "textarea", "label", "form", "progress", "output", "code"],
|
||||
[SVG_NS]: ["svg", "circle"]
|
||||
};
|
||||
|
||||
export const tag = {};
|
||||
|
||||
|
||||
for (const [ns, tags] of Object.entries(TAG_NAMES)) {
|
||||
for (const tagName of tags) {
|
||||
tag[tagName] = function(attributes, children) {
|
||||
return elNS(ns, tagName, attributes, children);
|
||||
}
|
||||
}
|
||||
}
|
25
src/utils/unique.js
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
export function orderedUnique(array) {
|
||||
const copy = [];
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
if (i === 0 || array.lastIndexOf(array[i], i - 1) === -1) {
|
||||
copy.push(array[i]);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
100
src/utils/xhr.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
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 {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
} from "./error.js";
|
||||
|
||||
function addCacheBuster(urlStr, random = Math.random) {
|
||||
// XHR doesn't have a good way to disable cache,
|
||||
// so add a random query param
|
||||
// see https://davidtranscend.com/blog/prevent-ie11-cache-ajax-requests/
|
||||
if (urlStr.includes("?")) {
|
||||
urlStr = urlStr + "&";
|
||||
} else {
|
||||
urlStr = urlStr + "?";
|
||||
}
|
||||
return urlStr + `_cacheBuster=${Math.ceil(random() * Number.MAX_SAFE_INTEGER)}`;
|
||||
}
|
||||
|
||||
class RequestResult {
|
||||
constructor(promise, xhr) {
|
||||
this._promise = promise;
|
||||
this._xhr = xhr;
|
||||
}
|
||||
|
||||
abort() {
|
||||
this._xhr.abort();
|
||||
}
|
||||
|
||||
response() {
|
||||
return this._promise;
|
||||
}
|
||||
}
|
||||
|
||||
function createXhr(url, {method, headers, timeout, uploadProgress}) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
|
||||
if (headers) {
|
||||
for(const [name, value] of headers.entries()) {
|
||||
try {
|
||||
xhr.setRequestHeader(name, value);
|
||||
} catch (err) {
|
||||
console.info(`Could not set ${name} header: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timeout) {
|
||||
xhr.timeout = timeout;
|
||||
}
|
||||
|
||||
if (uploadProgress) {
|
||||
xhr.upload.addEventListener("progress", evt => uploadProgress(evt.loaded));
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function xhrAsPromise(xhr, method, url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.addEventListener("load", () => resolve(xhr));
|
||||
xhr.addEventListener("abort", () => reject(new AbortError()));
|
||||
xhr.addEventListener("error", () => reject(new ConnectionError(`Error ${method} ${url}`)));
|
||||
xhr.addEventListener("timeout", () => reject(new ConnectionError(`Timeout ${method} ${url}`, true)));
|
||||
});
|
||||
}
|
||||
|
||||
export function xhrRequest(url, options = {}) {
|
||||
if (!options.method) {
|
||||
options.method = "GET";
|
||||
}
|
||||
let {cache, body, method} = options;
|
||||
if (!cache) {
|
||||
url = addCacheBuster(url);
|
||||
}
|
||||
const xhr = createXhr(url, options);
|
||||
const promise = xhrAsPromise(xhr, method, url).then(xhr => {
|
||||
const {status} = xhr;
|
||||
const body = JSON.parse(xhr.responseText);
|
||||
return {status, body};
|
||||
});
|
||||
|
||||
xhr.send(body || null);
|
||||
|
||||
return new RequestResult(promise, xhr);
|
||||
}
|