diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8805773
--- /dev/null
+++ b/.editorconfig
@@ -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}]
diff --git a/.eslintrc.js b/.eslintrc.js
index aabe8c1..24bbb04 100644
--- a/.eslintrc.js
+++ b/.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"
+ }
+};
diff --git a/.gitignore b/.gitignore
index 19a9ac3..dd87e2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,31 +1,2 @@
-# 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
-.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
+node_modules
+build
diff --git a/css/client.css b/css/client.css
new file mode 100644
index 0000000..f4f63ff
--- /dev/null
+++ b/css/client.css
@@ -0,0 +1,82 @@
+.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 .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: 8px;
+}
+
+.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');
+}
+
diff --git a/css/create.css b/css/create.css
new file mode 100644
index 0000000..c11e273
--- /dev/null
+++ b/css/create.css
@@ -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;
+}
diff --git a/css/main.css b/css/main.css
new file mode 100644
index 0000000..a9f7a77
--- /dev/null
+++ b/css/main.css
@@ -0,0 +1,219 @@
+/*
+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;
+}
+
+p {
+ line-height: 150%;
+}
+
+a {
+ text-decoration: none;
+}
+
+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;
+}
diff --git a/css/open.css b/css/open.css
new file mode 100644
index 0000000..560f7a3
--- /dev/null
+++ b/css/open.css
@@ -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;
+}
diff --git a/css/preview.css b/css/preview.css
new file mode 100644
index 0000000..8816178
--- /dev/null
+++ b/css/preview.css
@@ -0,0 +1,128 @@
+.PreviewView {
+ text-align: center;
+ margin-bottom: 32px;
+}
+
+.PreviewView h1 {
+ font-size: 24px;
+ line-height: 32px;
+ margin-bottom: 8px;
+}
+
+.PreviewView .avatarContainer {
+ display: flex;
+ justify-content: center;
+ margin: 0;
+}
+
+.PreviewView .avatar {
+ border-radius: 100%;
+ width: 64px;
+ height: 64px;
+}
+
+.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%; }
+}
diff --git a/css/spinner.css b/css/spinner.css
new file mode 100644
index 0000000..4802dfc
--- /dev/null
+++ b/css/spinner.css
@@ -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));
+}
diff --git a/images/app-store-us-alt.svg b/images/app-store-us-alt.svg
new file mode 100644
index 0000000..83437ba
--- /dev/null
+++ b/images/app-store-us-alt.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/images/background.svg b/images/background.svg
new file mode 100644
index 0000000..37c31f0
--- /dev/null
+++ b/images/background.svg
@@ -0,0 +1,226 @@
+
diff --git a/images/chat-icon.svg b/images/chat-icon.svg
new file mode 100644
index 0000000..c2b2913
--- /dev/null
+++ b/images/chat-icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/images/client-icons/element.svg b/images/client-icons/element.svg
new file mode 100644
index 0000000..5d1f7ba
--- /dev/null
+++ b/images/client-icons/element.svg
@@ -0,0 +1,9 @@
+
diff --git a/images/client-icons/fractal.png b/images/client-icons/fractal.png
new file mode 100644
index 0000000..e60c89c
Binary files /dev/null and b/images/client-icons/fractal.png differ
diff --git a/images/client-icons/nheko.svg b/images/client-icons/nheko.svg
new file mode 100644
index 0000000..ce3ec40
--- /dev/null
+++ b/images/client-icons/nheko.svg
@@ -0,0 +1,155 @@
+
+
+
+
diff --git a/images/client-icons/quaternion.svg b/images/client-icons/quaternion.svg
new file mode 100644
index 0000000..e20f3bd
--- /dev/null
+++ b/images/client-icons/quaternion.svg
@@ -0,0 +1,464 @@
+
+
+
+
diff --git a/images/client-icons/tensor.png b/images/client-icons/tensor.png
new file mode 100644
index 0000000..f4437c2
Binary files /dev/null and b/images/client-icons/tensor.png differ
diff --git a/images/client-icons/weechat.svg b/images/client-icons/weechat.svg
new file mode 100644
index 0000000..96b92b1
--- /dev/null
+++ b/images/client-icons/weechat.svg
@@ -0,0 +1,170 @@
+
+
diff --git a/images/copy.svg b/images/copy.svg
new file mode 100644
index 0000000..1d60511
--- /dev/null
+++ b/images/copy.svg
@@ -0,0 +1,4 @@
+
diff --git a/images/fdroid-badge.png b/images/fdroid-badge.png
new file mode 100644
index 0000000..7c8c3c5
Binary files /dev/null and b/images/fdroid-badge.png differ
diff --git a/images/flathub-badge.svg b/images/flathub-badge.svg
new file mode 100644
index 0000000..ad3b04a
--- /dev/null
+++ b/images/flathub-badge.svg
@@ -0,0 +1,62 @@
+
+
+
\ No newline at end of file
diff --git a/images/google-play-us.svg b/images/google-play-us.svg
new file mode 100644
index 0000000..888691a
--- /dev/null
+++ b/images/google-play-us.svg
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/images/link.svg b/images/link.svg
new file mode 100644
index 0000000..f41a673
--- /dev/null
+++ b/images/link.svg
@@ -0,0 +1,3 @@
+
diff --git a/images/matrix-logo.svg b/images/matrix-logo.svg
new file mode 100644
index 0000000..9f2c322
--- /dev/null
+++ b/images/matrix-logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/images/member-icon.svg b/images/member-icon.svg
new file mode 100644
index 0000000..f969c17
--- /dev/null
+++ b/images/member-icon.svg
@@ -0,0 +1,7 @@
+
diff --git a/images/platform-icon.svg b/images/platform-icon.svg
new file mode 100644
index 0000000..125b671
--- /dev/null
+++ b/images/platform-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/tick-dark.svg b/images/tick-dark.svg
new file mode 100644
index 0000000..e19a7b1
--- /dev/null
+++ b/images/tick-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/images/tick.svg b/images/tick.svg
new file mode 100644
index 0000000..b459490
--- /dev/null
+++ b/images/tick.svg
@@ -0,0 +1,3 @@
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c4caf02
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ You're invited to talk on Matrix
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index d6e7859..6d5702e 100644
--- a/package.json
+++ b/package.json
@@ -1,4 +1,37 @@
{
- "name": "matrix.to",
- "version": "1.1.3",
+ "name": "matrix.to",
+ "version": "1.1.3",
+ "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.1.1",
+ "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"
+ }
}
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 0000000..633c841
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,317 @@
+/*
+Copyright 2020 Bruno Windels
+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, ".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-esm.js`, await buildJs("src/main.js", assets));
+ await assets.write(`bundle-legacy.js`, await buildJsLegacy("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));
+ 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 = [
+ ``,
+ ``,
+ ``
+ ];
+ 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 = []) {
+ // create js bundle
+ const bundle = await rollup({
+ input: extraFiles.concat(mainFile),
+ plugins: [multi(), terser(), createReplaceUrlPlugin(assets)],
+ });
+ const {output} = await bundle.generate({
+ format: 'es',
+ });
+ const code = output[0].code;
+ return code;
+}
+
+async function buildJsLegacy(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).filter(id => !!id);
+ return JSON.stringify({
+ "applinks": {
+ "apps": [],
+ "details": {
+ appIDs: appIds,
+ components: [
+ {
+ "#": "/*", // only open urls with a fragment, so you can still create links
+ }
+ ]
+ },
+ },
+ "webcredentials": {
+ "apps": appIds
+ }
+ });
+}
+
+async function buildCss(entryPath, targetDir, assets) {
+ entryPath = path.join(projectDir, entryPath);
+ const assetUrlMapper = ({absolutePath}) => {
+ const relPath = absolutePath.substr(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.substr(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.substr(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));
diff --git a/scripts/serve-local.js b/scripts/serve-local.js
new file mode 100644
index 0000000..02581f9
--- /dev/null
+++ b/scripts/serve-local.js
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 Bruno Windels
+
+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");
+ },
+ 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);
diff --git a/src/Link.js b/src/Link.js
new file mode 100644
index 0000000..44d769e
--- /dev/null
+++ b/src/Link.js
@@ -0,0 +1,163 @@
+/*
+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);
+ }
+}
+
+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 class Link {
+ static validateIdentifier(identifier) {
+ return !!(
+ USERID_PATTERN.exec(identifier) ||
+ ROOMALIAS_PATTERN.exec(identifier) ||
+ ROOMID_PATTERN.exec(identifier) ||
+ GROUPID_PATTERN.exec(identifier)
+ );
+ }
+
+ static parse(fragment) {
+ if (!fragment) {
+ return null;
+ }
+ let [linkStr, queryParamsStr] = fragment.split("?");
+
+ let viaServers = [];
+ let clientId = null;
+ if (queryParamsStr) {
+ const queryParams = queryParamsStr.split("&").map(pair => pair.split("="));
+ viaServers = queryParams
+ .filter(([key, value]) => key === "via")
+ .map(([,value]) => value);
+ const clientParam = queryParams.find(([key]) => key === "client");
+ if (clientParam) {
+ clientId = clientParam[1];
+ }
+ }
+
+ if (linkStr.startsWith("#/")) {
+ linkStr = linkStr.substr(2);
+ }
+
+ const [identifier, eventId] = linkStr.split("/");
+
+ let matches;
+ matches = USERID_PATTERN.exec(identifier);
+ if (matches) {
+ const server = matches[2];
+ const localPart = matches[1];
+ return new Link(clientId, viaServers, IdentifierKind.UserId, localPart, server);
+ }
+ matches = ROOMALIAS_PATTERN.exec(identifier);
+ if (matches) {
+ const server = matches[2];
+ const localPart = matches[1];
+ return new Link(clientId, viaServers, IdentifierKind.RoomAlias, localPart, server, 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, 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);
+ }
+ return null;
+ }
+
+ constructor(clientId, viaServers, identifierKind, localPart, server, eventId) {
+ const servers = [server];
+ servers.push(...viaServers);
+ 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);
+ }
+
+ toFragment() {
+ if (this.eventId) {
+ return `/${this.identifier}/${this.eventId}`;
+ } else {
+ return `/${this.identifier}`;
+ }
+ }
+}
diff --git a/src/Platform.js b/src/Platform.js
new file mode 100644
index 0000000..21de264
--- /dev/null
+++ b/src/Platform.js
@@ -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;
+}
diff --git a/src/Preferences.js b/src/Preferences.js
new file mode 100644
index 0000000..53f5511
--- /dev/null
+++ b/src/Preferences.js
@@ -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;
+ }
+}
diff --git a/src/RootView.js b/src/RootView.js
new file mode 100644
index 0000000..183724d
--- /dev/null
+++ b/src/RootView.js
@@ -0,0 +1,44 @@
+/*
+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";
+
+export class RootView extends TemplateView {
+ render(t, vm) {
+ return t.div({className: "RootView"}, [
+ t.mapView(vm => vm.openLinkViewModel, vm => vm ? new OpenLinkView(vm) : null),
+ t.mapView(vm => vm.createLinkViewModel, vm => vm ? new CreateLinkView(vm) : null),
+ 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/clients", "Add your app")),
+ t.li({className: {hidden: vm => !vm.hasPreferences}},
+ t.button({className: "text", onClick: () => vm.clearPreferences()}, "Clear preferences")),
+ ])
+ ])
+ ]);
+ }
+}
+
+function externalLink(t, href, label) {
+ return t.a({href, target: "_blank", rel: "noopener noreferrer"}, label);
+}
diff --git a/src/RootViewModel.js b/src/RootViewModel.js
new file mode 100644
index 0000000..a77b82b
--- /dev/null
+++ b/src/RootViewModel.js
@@ -0,0 +1,73 @@
+/*
+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 {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.preferences.on("canClear", () => {
+ this.emitChange();
+ });
+ }
+
+ _updateChildVMs(oldLink) {
+ if (this.link) {
+ this.createLinkViewModel = null;
+ if (!oldLink || !oldLink.equals(this.link)) {
+ this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
+ link: this.link,
+ clients: createClients(),
+ }));
+ }
+ } else {
+ this.openLinkViewModel = null;
+ this.createLinkViewModel = new CreateLinkViewModel(this.childOptions());
+ }
+ this.emitChange();
+ }
+
+ updateHash(hash) {
+ if (hash.startsWith("#/policy/")) {
+ const server = hash.substr(9);
+ this.loadServerPolicyViewModel = new LoadServerPolicyViewModel(this.childOptions({server}));
+ this.loadServerPolicyViewModel.load();
+ } else {
+ const oldLink = this.link;
+ this.link = Link.parse(hash);
+ this._updateChildVMs(oldLink);
+ }
+ }
+
+ clearPreferences() {
+ this.preferences.clear();
+ this._updateChildVMs();
+ }
+
+ get hasPreferences() {
+ return this.preferences.canClear;
+ }
+}
diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js
new file mode 100644
index 0000000..784cb66
--- /dev/null
+++ b/src/create/CreateLinkView.js
@@ -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("");
+ }
+ }
+}
diff --git a/src/create/CreateLinkViewModel.js b/src/create/CreateLinkViewModel.js
new file mode 100644
index 0000000..21f4c81
--- /dev/null
+++ b/src/create/CreateLinkViewModel.js
@@ -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.parse(identifier);
+ if (this._link) {
+ this.openLink("#" + this._link.toFragment());
+ }
+ }
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..16c324a
--- /dev/null
+++ b/src/main.js
@@ -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(location.hash);
+ window.__rootvm = vm;
+ const view = new RootView(vm);
+ container.appendChild(view.mount());
+ window.addEventListener('hashchange', () => {
+ vm.updateHash(location.hash);
+ });
+}
diff --git a/src/open/ClientListView.js b/src/open/ClientListView.js
new file mode 100644
index 0000000..3fc43ab
--- /dev/null
+++ b/src/open/ClientListView.js
@@ -0,0 +1,69 @@
+/*
+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.mapView(vm => vm.clientList, () => {
+ return new TemplateView(vm, t => {
+ return t.div({className: "list"}, vm.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)))
+ ]);
+ }
+}
diff --git a/src/open/ClientListViewModel.js b/src/open/ClientListViewModel.js
new file mode 100644
index 0000000..df0c166
--- /dev/null
+++ b/src/open/ClientListViewModel.js
@@ -0,0 +1,86 @@
+/*
+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() {
+ this.clientList = 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)
+ })));
+ 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();
+ }
+}
diff --git a/src/open/ClientView.js b/src/open/ClientView.js
new file mode 100644
index 0000000..96c585f
--- /dev/null
+++ b/src/open/ClientView.js
@@ -0,0 +1,144 @@
+/*
+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"}, [
+ 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"}, [
+ t.a({
+ className: "primary fullwidth",
+ href: vm.deepLink,
+ rel: "noopener noreferrer",
+ onClick: () => vm.deepLinkActivated(),
+ }, "Continue"),
+ 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.actions.map(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);
+ }));
+ children.push(actions);
+
+ if (vm.showDeepLinkInInstall) {
+ const deepLink = t.a({
+ rel: "noopener noreferrer",
+ href: vm.deepLink,
+ onClick: () => vm.deepLinkActivated(),
+ }, "open it here");
+ children.push(t.p([`If you already have ${vm.name} installed, you can `, deepLink, "."]))
+ }
+
+ 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"),
+ ]);
+}
diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js
new file mode 100644
index 0000000..eb21f15
--- /dev/null
+++ b/src/open/ClientViewModel.js
@@ -0,0 +1,177 @@
+/*
+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;
+
+ const matchingPlatforms = getMatchingPlatforms(client, this.platforms);
+ const nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p));
+ const webPlatform = matchingPlatforms.find(p => isWebPlatform(p));
+
+ this._proposedPlatform = this.preferences.platform || nativePlatform || webPlatform;
+ this._nativePlatform = nativePlatform || this._proposedPlatform;
+
+ this.actions = this._createActions(client, link, nativePlatform, webPlatform);
+ this._clientCanIntercept = !!(nativePlatform && client.canInterceptMatrixToLinks(nativePlatform));
+ this._showOpen = this.deepLink && !this._clientCanIntercept;
+ }
+
+ _createActions(client, link, nativePlatform, webPlatform) {
+ let actions = [];
+ if (nativePlatform) {
+ const nativeActions = (client.getInstallLinks(nativePlatform) || []).map(installLink => {
+ return {
+ label: installLink.getDescription(nativePlatform),
+ url: installLink.createInstallURL(link),
+ kind: installLink.channelId,
+ primary: true,
+ activated: () => this.preferences.setClient(client.id, nativePlatform),
+ };
+ });
+ actions.push(...nativeActions);
+ }
+ if (webPlatform) {
+ const webDeepLink = client.getDeepLink(webPlatform, link);
+ if (webDeepLink) {
+ actions.push({
+ label: `Continue in your browser`,
+ url: webDeepLink,
+ kind: "open-in-web",
+ activated: () => this.preferences.setClient(client.id, webPlatform),
+ });
+ }
+ }
+ return actions;
+ }
+
+ 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() {
+ return this._clientCanIntercept && this.deepLink;
+ }
+
+ get availableOnPlatformNames() {
+ const platforms = this._client.platforms;
+ const textPlatforms = [];
+ const hasWebPlatform = platforms.some(p => isWebPlatform(p));
+ if (hasWebPlatform) {
+ textPlatforms.push("Web");
+ }
+ const desktopPlatforms = platforms.filter(p => isDesktopPlatform(p));
+ if (desktopPlatforms.length === 1) {
+ textPlatforms.push(desktopPlatforms[0]);
+ } else {
+ textPlatforms.push("Desktop");
+ }
+ if (platforms.includes(Platform.Android)) {
+ textPlatforms.push("Android");
+ }
+ if (platforms.includes(Platform.iOS)) {
+ textPlatforms.push("iOS");
+ }
+ return textPlatforms;
+ }
+
+ get deepLink() {
+ const platform = this.showBack ? this._proposedPlatform : this._nativePlatform;
+ return this._client.getDeepLink(platform, this._link);
+ }
+
+ deepLinkActivated() {
+ this._pickClient(this._client);
+ this.preferences.setClient(this._client.id, this._proposedPlatform);
+ if (this._showOpen) {
+ this._showOpen = false;
+ this.emitChange();
+ }
+ }
+
+ pick(clientListViewModel) {
+ this._clientListViewModel = clientListViewModel;
+ this.emitChange();
+ }
+
+ get showBack() {
+ // if we're not only showing this client, don't show back (see pick())
+ return !!this._clientListViewModel;
+ }
+
+ back() {
+ if (this._clientListViewModel) {
+ const vm = this._clientListViewModel;
+ this._clientListViewModel = null;
+ this.emitChange();
+ vm.showAll();
+ }
+ }
+}
diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js
new file mode 100644
index 0000000..8a2dbd4
--- /dev/null
+++ b/src/open/OpenLinkView.js
@@ -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"),
+ ]),
+ ]);
+ }
+}
diff --git a/src/open/OpenLinkViewModel.js b/src/open/OpenLinkViewModel.js
new file mode 100644
index 0000000..ebd6210
--- /dev/null
+++ b/src/open/OpenLinkViewModel.js
@@ -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();
+ }
+}
diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js
new file mode 100644
index 0000000..e35c257
--- /dev/null
+++ b/src/open/ServerConsentView.js
@@ -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);
+ }
+}
diff --git a/src/open/ServerConsentViewModel.js b/src/open/ServerConsentViewModel.js
new file mode 100644
index 0000000..2cb6e07
--- /dev/null
+++ b/src/open/ServerConsentViewModel.js
@@ -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();
+ }
+}
diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js
new file mode 100644
index 0000000..2c28af8
--- /dev/null
+++ b/src/open/clients/Element.js
@@ -0,0 +1,80 @@
+/*
+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";
+
+/**
+ * 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"; }
+ get name() {return "Element"; }
+ get description() { return 'Fully-featured Matrix client, used by millions.'; }
+ get homepage() { return "https://element.io"; }
+ get author() { return "Element"; }
+ getMaturity(platform) { return Maturity.Stable; }
+
+ getDeepLink(platform, link) {
+ let fragmentPath;
+ switch (link.kind) {
+ case LinkKind.User:
+ fragmentPath = `user/${link.identifier}`;
+ break;
+ case LinkKind.Room:
+ fragmentPath = `room/${link.identifier}`;
+ break;
+ case LinkKind.Group:
+ fragmentPath = `group/${link.identifier}`;
+ break;
+ case LinkKind.Event:
+ fragmentPath = `room/${link.identifier}/${link.eventId}`;
+ break;
+ }
+ if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb || platform === Platform.iOS) {
+ return `https://app.element.io/#/${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/get-started")];
+ }
+ }
+
+ canInterceptMatrixToLinks(platform) {
+ return platform === Platform.iOS || platform === Platform.Android;
+ }
+}
diff --git a/src/open/clients/Fractal.js b/src/open/clients/Fractal.js
new file mode 100644
index 0000000..4c860ee
--- /dev/null
+++ b/src/open/clients/Fractal.js
@@ -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, 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.png"; }
+ get author() { return "Daniel Garcia Moreno"; }
+ get homepage() { return "https://gitlab.gnome.org/GNOME/fractal"; }
+ get platforms() { return [Platform.Linux]; }
+ get description() { return 'Fractal is a Matrix Client written in Rust.'; }
+ getMaturity(platform) { return Maturity.Beta; }
+ getDeepLink(platform, link) {}
+ canInterceptMatrixToLinks(platform) { return false; }
+
+ getLinkInstructions(platform, link) {
+ if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
+ return "Click the '+' button in the top right and paste the identifier";
+ }
+ }
+
+ getCopyString(platform, link) {
+ if (link.kind === LinkKind.User || link.kind === LinkKind.Room) {
+ return link.identifier;
+ }
+ }
+
+ getInstallLinks(platform) {
+ if (platform === Platform.Linux) {
+ return [new FlathubLink("org.gnome.Fractal")];
+ }
+ }
+}
diff --git a/src/open/clients/Nheko.js b/src/open/clients/Nheko.js
new file mode 100644
index 0000000..0aa35ad
--- /dev/null
+++ b/src/open/clients/Nheko.js
@@ -0,0 +1,53 @@
+/*
+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";
+
+/**
+ * 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) {}
+ 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("io.github.NhekoReborn.Nheko")];
+ }
+ }
+}
diff --git a/src/open/clients/Quaternion.js b/src/open/clients/Quaternion.js
new file mode 100644
index 0000000..ce6a184
--- /dev/null
+++ b/src/open/clients/Quaternion.js
@@ -0,0 +1,50 @@
+/*
+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 Quaternion {
+ get id() { return "quaternion"; }
+ get name() { return "Quaternion"; }
+ get icon() { return "images/client-icons/quaternion.svg"; }
+ get author() { return "Felix Rohrbach"; }
+ get homepage() { return "https://github.com/Fxrh/Quaternion"; }
+ get 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) {
+ if (platform === Platform.Linux) {
+ return [new FlathubLink("com.github.quaternion")];
+ }
+ }
+}
diff --git a/src/open/clients/Tensor.js b/src/open/clients/Tensor.js
new file mode 100644
index 0000000..ce22116
--- /dev/null
+++ b/src/open/clients/Tensor.js
@@ -0,0 +1,50 @@
+/*
+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")];
+ }
+ }
+}
diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js
new file mode 100644
index 0000000..49ceb7a
--- /dev/null
+++ b/src/open/clients/Weechat.js
@@ -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 {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) {}
+}
diff --git a/src/open/clients/index.js b/src/open/clients/index.js
new file mode 100644
index 0000000..91ce46b
--- /dev/null
+++ b/src/open/clients/index.js
@@ -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.
+*/
+
+import {Element} from "./Element.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";
+
+export function createClients() {
+ return [
+ new Element(),
+ new Weechat(),
+ new Nheko(),
+ new Quaternion(),
+ new Tensor(),
+ ];
+}
diff --git a/src/open/types.js b/src/open/types.js
new file mode 100644
index 0000000..eb99b2b
--- /dev/null
+++ b/src/open/types.js
@@ -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};
+ }
+}
diff --git a/src/policy/LoadServerPolicyView.js b/src/policy/LoadServerPolicyView.js
new file mode 100644
index 0000000..b6d2f08
--- /dev/null
+++ b/src/policy/LoadServerPolicyView.js
@@ -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)
+ ]);
+ }
+}
diff --git a/src/policy/LoadServerPolicyViewModel.js b/src/policy/LoadServerPolicyViewModel.js
new file mode 100644
index 0000000..4e6a119
--- /dev/null
+++ b/src/policy/LoadServerPolicyViewModel.js
@@ -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();
+ }
+}
diff --git a/src/polyfill.js b/src/polyfill.js
new file mode 100644
index 0000000..c0a1411
--- /dev/null
+++ b/src/polyfill.js
@@ -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";
diff --git a/src/preview/HomeServer.js b/src/preview/HomeServer.js
new file mode 100644
index 0000000..d0c19dd
--- /dev/null
+++ b/src/preview/HomeServer.js
@@ -0,0 +1,122 @@
+/*
+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 async function resolveServer(request, baseURL) {
+ if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) {
+ baseURL = `https://${baseURL}`;
+ }
+ {
+ const {status, body} = await request(`${baseURL}/.well-known/matrix/client`, {method: "GET"}).response();
+ if (status === 200) {
+ const proposedBaseURL = body?.['m.homeserver']?.base_url;
+ if (typeof proposedBaseURL === "string") {
+ baseURL = proposedBaseURL;
+ }
+ }
+ }
+ {
+ const {status} = await request(`${baseURL}/_matrix/client/versions`, {method: "GET"}).response();
+ if (status !== 200) {
+ throw new Error(`Invalid versions response from ${baseURL}`);
+ }
+ }
+ return new HomeServer(request, baseURL);
+}
+
+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;
+ }
+
+ 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.substr(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("&");
+}
diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js
new file mode 100644
index 0000000..4d16075
--- /dev/null
+++ b/src/preview/PreviewView.js
@@ -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.mapView(vm => vm.avatarUrl, avatarUrl => {
+ if (avatarUrl) {
+ return new TemplateView(avatarUrl, (t, src) => t.img({className: "avatar", src}));
+ } else {
+ return new TemplateView(null, t => t.div({className: "defaultAvatar"}));
+ }
+ });
+ return t.div([
+ t.div({className: "avatarContainer"}, avatar),
+ t.h1(vm => vm.name),
+ t.p({className: {identifier: true, hidden: vm => !vm.identifier}}, vm => vm.identifier),
+ t.div({className: {memberCount: true, hidden: vm => !vm.memberCount}}, t.p([vm => vm.memberCount, " members"])),
+ t.p({className: {topic: true, hidden: vm => !vm.topic}}, [vm => vm.topic]),
+ ]);
+ }
+}
diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js
new file mode 100644
index 0000000..50843e6
--- /dev/null
+++ b/src/preview/PreviewViewModel.js
@@ -0,0 +1,116 @@
+/*
+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;
+ }
+
+ 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) {
+ 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;
+ if (this.identifier === this.name) {
+ this.identifier = null;
+ }
+ }
+
+ _setNoPreview(link) {
+ this.name = link.identifier;
+ this.identifier = null;
+ this.avatarUrl = null;
+ }
+}
diff --git a/src/utils/TemplateView.js b/src/utils/TemplateView.js
new file mode 100644
index 0000000..c414c37
--- /dev/null
+++ b/src/utils/TemplateView.js
@@ -0,0 +1,347 @@
+/*
+Copyright 2020 Bruno Windels
+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, tag } 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;
+ this._render = render;
+ this._eventListeners = null;
+ this._bindings = null;
+ this._subViews = null;
+ this._root = null;
+ 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;
+ }
+
+ update(value) {
+ this._value = value;
+ if (this._bindings) {
+ for (const binding of this._bindings) {
+ binding();
+ }
+ }
+ }
+
+ _updateFromValue(changedProps) {
+ this.update(this._value, changedProps);
+ }
+
+ _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);
+ }
+}
+
+// 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.substr(2, 1).toLowerCase() + key.substr(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);
+ } else {
+ console.warn("Could not update parent of node binding");
+ }
+ 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;
+ }
+
+ // sugar
+ createTemplate(render) {
+ return vm => new TemplateView(vm, render);
+ }
+
+ // 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");
+ }
+ });
+ }
+
+ // creates a conditional subtemplate
+ if(fn, viewCreator) {
+ return this.mapView(
+ value => !!fn(value),
+ enabled => enabled ? viewCreator(this._value) : null
+ );
+ }
+}
+
+
+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);
+ };
+ }
+}
diff --git a/src/utils/ViewModel.js b/src/utils/ViewModel.js
new file mode 100644
index 0000000..b9afbc1
--- /dev/null
+++ b/src/utils/ViewModel.js
@@ -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);
+ }
+}
diff --git a/src/utils/copy.js b/src/utils/copy.js
new file mode 100644
index 0000000..c417bfd
--- /dev/null
+++ b/src/utils/copy.js
@@ -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);
+}
diff --git a/src/utils/enum.js b/src/utils/enum.js
new file mode 100644
index 0000000..4defcfd
--- /dev/null
+++ b/src/utils/enum.js
@@ -0,0 +1,26 @@
+/*
+Copyright 2020 Bruno Windels
+
+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);
+}
diff --git a/src/utils/error.js b/src/utils/error.js
new file mode 100644
index 0000000..d61e803
--- /dev/null
+++ b/src/utils/error.js
@@ -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";
+ }
+}
\ No newline at end of file
diff --git a/src/utils/html.js b/src/utils/html.js
new file mode 100644
index 0000000..16607ed
--- /dev/null
+++ b/src/utils/html.js
@@ -0,0 +1,111 @@
+/*
+Copyright 2020 Bruno Windels
+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);
+ }
+ }
+}
diff --git a/src/utils/unique.js b/src/utils/unique.js
new file mode 100644
index 0000000..d8f0974
--- /dev/null
+++ b/src/utils/unique.js
@@ -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;
+}
\ No newline at end of file
diff --git a/src/utils/xhr.js b/src/utils/xhr.js
new file mode 100644
index 0000000..2c0a865
--- /dev/null
+++ b/src/utils/xhr.js
@@ -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);
+}
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..ff5ba27
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,1924 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+ integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
+ dependencies:
+ "@babel/highlight" "^7.10.4"
+
+"@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41"
+ integrity sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==
+
+"@babel/core@^7.11.1":
+ version "7.12.9"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8"
+ integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.12.5"
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helpers" "^7.12.5"
+ "@babel/parser" "^7.12.7"
+ "@babel/template" "^7.12.7"
+ "@babel/traverse" "^7.12.9"
+ "@babel/types" "^7.12.7"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.19"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
+"@babel/generator@^7.12.5":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de"
+ integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==
+ dependencies:
+ "@babel/types" "^7.12.5"
+ jsesc "^2.5.1"
+ source-map "^0.5.0"
+
+"@babel/helper-annotate-as-pure@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
+ integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3"
+ integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==
+ dependencies:
+ "@babel/helper-explode-assignable-expression" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-compilation-targets@^7.12.5":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz#cb470c76198db6a24e9dbc8987275631e5d29831"
+ integrity sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==
+ dependencies:
+ "@babel/compat-data" "^7.12.5"
+ "@babel/helper-validator-option" "^7.12.1"
+ browserslist "^4.14.5"
+ semver "^5.5.0"
+
+"@babel/helper-create-class-features-plugin@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e"
+ integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-member-expression-to-functions" "^7.12.1"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.12.1"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+
+"@babel/helper-create-regexp-features-plugin@^7.12.1":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz#2084172e95443fa0a09214ba1bb328f9aea1278f"
+ integrity sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ regexpu-core "^4.7.1"
+
+"@babel/helper-define-map@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30"
+ integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/types" "^7.10.5"
+ lodash "^4.17.19"
+
+"@babel/helper-explode-assignable-expression@^7.10.4":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633"
+ integrity sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==
+ dependencies:
+ "@babel/types" "^7.12.1"
+
+"@babel/helper-function-name@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+ integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-get-function-arity@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+ integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-hoist-variables@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
+ integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-member-expression-to-functions@^7.12.1":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz#aa77bd0396ec8114e5e30787efa78599d874a855"
+ integrity sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==
+ dependencies:
+ "@babel/types" "^7.12.7"
+
+"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.5":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb"
+ integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==
+ dependencies:
+ "@babel/types" "^7.12.5"
+
+"@babel/helper-module-transforms@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c"
+ integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==
+ dependencies:
+ "@babel/helper-module-imports" "^7.12.1"
+ "@babel/helper-replace-supers" "^7.12.1"
+ "@babel/helper-simple-access" "^7.12.1"
+ "@babel/helper-split-export-declaration" "^7.11.0"
+ "@babel/helper-validator-identifier" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.12.1"
+ "@babel/types" "^7.12.1"
+ lodash "^4.17.19"
+
+"@babel/helper-optimise-call-expression@^7.10.4":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz#7f94ae5e08721a49467346aa04fd22f750033b9c"
+ integrity sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw==
+ dependencies:
+ "@babel/types" "^7.12.7"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+ integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
+
+"@babel/helper-remap-async-to-generator@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd"
+ integrity sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-wrap-function" "^7.10.4"
+ "@babel/types" "^7.12.1"
+
+"@babel/helper-replace-supers@^7.12.1":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz#f009a17543bbbbce16b06206ae73b63d3fca68d9"
+ integrity sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.12.1"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/traverse" "^7.12.5"
+ "@babel/types" "^7.12.5"
+
+"@babel/helper-simple-access@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136"
+ integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==
+ dependencies:
+ "@babel/types" "^7.12.1"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf"
+ integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==
+ dependencies:
+ "@babel/types" "^7.12.1"
+
+"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
+ integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
+ dependencies:
+ "@babel/types" "^7.11.0"
+
+"@babel/helper-validator-identifier@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+ integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+
+"@babel/helper-validator-option@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9"
+ integrity sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==
+
+"@babel/helper-wrap-function@^7.10.4":
+ version "7.12.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz#3332339fc4d1fbbf1c27d7958c27d34708e990d9"
+ integrity sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helpers@^7.12.5":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e"
+ integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==
+ dependencies:
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.12.5"
+ "@babel/types" "^7.12.5"
+
+"@babel/highlight@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+ integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056"
+ integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==
+
+"@babel/plugin-proposal-async-generator-functions@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e"
+ integrity sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-remap-async-to-generator" "^7.12.1"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+
+"@babel/plugin-proposal-class-properties@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de"
+ integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-dynamic-import@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc"
+ integrity sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+
+"@babel/plugin-proposal-export-namespace-from@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz#8b9b8f376b2d88f5dd774e4d24a5cc2e3679b6d4"
+ integrity sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-proposal-json-strings@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz#d45423b517714eedd5621a9dfdc03fa9f4eb241c"
+ integrity sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+
+"@babel/plugin-proposal-logical-assignment-operators@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz#f2c490d36e1b3c9659241034a5d2cd50263a2751"
+ integrity sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c"
+ integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+
+"@babel/plugin-proposal-numeric-separator@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz#8bf253de8139099fea193b297d23a9d406ef056b"
+ integrity sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-proposal-object-rest-spread@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069"
+ integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-transform-parameters" "^7.12.1"
+
+"@babel/plugin-proposal-optional-catch-binding@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz#ccc2421af64d3aae50b558a71cede929a5ab2942"
+ integrity sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+
+"@babel/plugin-proposal-optional-chaining@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz#e02f0ea1b5dc59d401ec16fb824679f683d3303c"
+ integrity sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+
+"@babel/plugin-proposal-private-methods@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389"
+ integrity sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.12.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072"
+ integrity sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-async-generators@^7.8.0":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978"
+ integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-dynamic-import@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
+ integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+ integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-json-strings@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-top-level-await@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0"
+ integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-arrow-functions@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3"
+ integrity sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-async-to-generator@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1"
+ integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==
+ dependencies:
+ "@babel/helper-module-imports" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-remap-async-to-generator" "^7.12.1"
+
+"@babel/plugin-transform-block-scoped-functions@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz#f2a1a365bde2b7112e0a6ded9067fdd7c07905d9"
+ integrity sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-block-scoping@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz#f0ee727874b42a208a48a586b84c3d222c2bbef1"
+ integrity sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-classes@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz#65e650fcaddd3d88ddce67c0f834a3d436a32db6"
+ integrity sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-define-map" "^7.10.4"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.12.1"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz#d68cf6c9b7f838a8a4144badbe97541ea0904852"
+ integrity sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-destructuring@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz#b9a570fe0d0a8d460116413cb4f97e8e08b2f847"
+ integrity sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz#a1d16c14862817b6409c0a678d6f9373ca9cd975"
+ integrity sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-duplicate-keys@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz#745661baba295ac06e686822797a69fbaa2ca228"
+ integrity sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-exponentiation-operator@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz#b0f2ed356ba1be1428ecaf128ff8a24f02830ae0"
+ integrity sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-for-of@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz#07640f28867ed16f9511c99c888291f560921cfa"
+ integrity sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-function-name@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz#2ec76258c70fe08c6d7da154003a480620eba667"
+ integrity sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-literals@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz#d73b803a26b37017ddf9d3bb8f4dc58bfb806f57"
+ integrity sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz#496038602daf1514a64d43d8e17cbb2755e0c3ad"
+ integrity sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-modules-amd@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz#3154300b026185666eebb0c0ed7f8415fefcf6f9"
+ integrity sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-commonjs@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz#fa403124542636c786cf9b460a0ffbb48a86e648"
+ integrity sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-simple-access" "^7.12.1"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-systemjs@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086"
+ integrity sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.10.4"
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-validator-identifier" "^7.10.4"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-umd@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz#eb5a218d6b1c68f3d6217b8fa2cc82fec6547902"
+ integrity sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz#b407f5c96be0d9f5f88467497fa82b30ac3e8753"
+ integrity sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+
+"@babel/plugin-transform-new-target@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz#80073f02ee1bb2d365c3416490e085c95759dec0"
+ integrity sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-object-super@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e"
+ integrity sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.12.1"
+
+"@babel/plugin-transform-parameters@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz#d2e963b038771650c922eff593799c96d853255d"
+ integrity sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-property-literals@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz#41bc81200d730abb4456ab8b3fbd5537b59adecd"
+ integrity sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-regenerator@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753"
+ integrity sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==
+ dependencies:
+ regenerator-transform "^0.14.2"
+
+"@babel/plugin-transform-reserved-words@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz#6fdfc8cc7edcc42b36a7c12188c6787c873adcd8"
+ integrity sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-shorthand-properties@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3"
+ integrity sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-spread@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz#527f9f311be4ec7fdc2b79bb89f7bf884b3e1e1e"
+ integrity sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
+
+"@babel/plugin-transform-sticky-regex@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz#560224613ab23987453948ed21d0b0b193fa7fad"
+ integrity sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-template-literals@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz#b43ece6ed9a79c0c71119f576d299ef09d942843"
+ integrity sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-typeof-symbol@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a"
+ integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-escapes@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709"
+ integrity sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-regex@^7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz#cc9661f61390db5c65e3febaccefd5c6ac3faecb"
+ integrity sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/preset-env@^7.11.0":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.7.tgz#54ea21dbe92caf6f10cb1a0a576adc4ebf094b55"
+ integrity sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew==
+ dependencies:
+ "@babel/compat-data" "^7.12.7"
+ "@babel/helper-compilation-targets" "^7.12.5"
+ "@babel/helper-module-imports" "^7.12.5"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-validator-option" "^7.12.1"
+ "@babel/plugin-proposal-async-generator-functions" "^7.12.1"
+ "@babel/plugin-proposal-class-properties" "^7.12.1"
+ "@babel/plugin-proposal-dynamic-import" "^7.12.1"
+ "@babel/plugin-proposal-export-namespace-from" "^7.12.1"
+ "@babel/plugin-proposal-json-strings" "^7.12.1"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1"
+ "@babel/plugin-proposal-numeric-separator" "^7.12.7"
+ "@babel/plugin-proposal-object-rest-spread" "^7.12.1"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.12.1"
+ "@babel/plugin-proposal-optional-chaining" "^7.12.7"
+ "@babel/plugin-proposal-private-methods" "^7.12.1"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.12.1"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+ "@babel/plugin-syntax-class-properties" "^7.12.1"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+ "@babel/plugin-syntax-top-level-await" "^7.12.1"
+ "@babel/plugin-transform-arrow-functions" "^7.12.1"
+ "@babel/plugin-transform-async-to-generator" "^7.12.1"
+ "@babel/plugin-transform-block-scoped-functions" "^7.12.1"
+ "@babel/plugin-transform-block-scoping" "^7.12.1"
+ "@babel/plugin-transform-classes" "^7.12.1"
+ "@babel/plugin-transform-computed-properties" "^7.12.1"
+ "@babel/plugin-transform-destructuring" "^7.12.1"
+ "@babel/plugin-transform-dotall-regex" "^7.12.1"
+ "@babel/plugin-transform-duplicate-keys" "^7.12.1"
+ "@babel/plugin-transform-exponentiation-operator" "^7.12.1"
+ "@babel/plugin-transform-for-of" "^7.12.1"
+ "@babel/plugin-transform-function-name" "^7.12.1"
+ "@babel/plugin-transform-literals" "^7.12.1"
+ "@babel/plugin-transform-member-expression-literals" "^7.12.1"
+ "@babel/plugin-transform-modules-amd" "^7.12.1"
+ "@babel/plugin-transform-modules-commonjs" "^7.12.1"
+ "@babel/plugin-transform-modules-systemjs" "^7.12.1"
+ "@babel/plugin-transform-modules-umd" "^7.12.1"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1"
+ "@babel/plugin-transform-new-target" "^7.12.1"
+ "@babel/plugin-transform-object-super" "^7.12.1"
+ "@babel/plugin-transform-parameters" "^7.12.1"
+ "@babel/plugin-transform-property-literals" "^7.12.1"
+ "@babel/plugin-transform-regenerator" "^7.12.1"
+ "@babel/plugin-transform-reserved-words" "^7.12.1"
+ "@babel/plugin-transform-shorthand-properties" "^7.12.1"
+ "@babel/plugin-transform-spread" "^7.12.1"
+ "@babel/plugin-transform-sticky-regex" "^7.12.7"
+ "@babel/plugin-transform-template-literals" "^7.12.1"
+ "@babel/plugin-transform-typeof-symbol" "^7.12.1"
+ "@babel/plugin-transform-unicode-escapes" "^7.12.1"
+ "@babel/plugin-transform-unicode-regex" "^7.12.1"
+ "@babel/preset-modules" "^0.1.3"
+ "@babel/types" "^7.12.7"
+ core-js-compat "^3.7.0"
+ semver "^5.5.0"
+
+"@babel/preset-modules@^0.1.3":
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
+ integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@babel/runtime@^7.8.4":
+ version "7.12.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
+ integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@babel/template@^7.10.4", "@babel/template@^7.12.7":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
+ integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/parser" "^7.12.7"
+ "@babel/types" "^7.12.7"
+
+"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9":
+ version "7.12.9"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f"
+ integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.12.5"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.11.0"
+ "@babel/parser" "^7.12.7"
+ "@babel/types" "^7.12.7"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.19"
+
+"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.4.4":
+ version "7.12.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13"
+ integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ lodash "^4.17.19"
+ to-fast-properties "^2.0.0"
+
+"@rollup/plugin-babel@^5.1.0":
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.2.2.tgz#e5623a01dd8e37e004ba87f2de218c611727d9b2"
+ integrity sha512-MjmH7GvFT4TW8xFdIeFS3wqIX646y5tACdxkTO+khbHvS3ZcVJL6vkAHLw2wqPmkhwCfWHoNsp15VYNwW6JEJA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@rollup/pluginutils" "^3.1.0"
+
+"@rollup/plugin-commonjs@^15.0.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz#1e7d076c4f1b2abf7e65248570e555defc37c238"
+ integrity sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ commondir "^1.0.1"
+ estree-walker "^2.0.1"
+ glob "^7.1.6"
+ is-reference "^1.2.1"
+ magic-string "^0.25.7"
+ resolve "^1.17.0"
+
+"@rollup/plugin-multi-entry@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-multi-entry/-/plugin-multi-entry-4.0.0.tgz#8e105f16ec1bb26639eb3302c8db5665f44b9939"
+ integrity sha512-1Sw86rwFxrNS7ECY3iSZ7T940xKnruNGpmQDgSDVTp+VTa1g5cPXNzBgp+IoOer41CiVeGFLwYwvicVoJLHEDQ==
+ dependencies:
+ "@rollup/plugin-virtual" "^2.0.3"
+ matched "^5.0.0"
+
+"@rollup/plugin-node-resolve@^9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6"
+ integrity sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ "@types/resolve" "1.17.1"
+ builtin-modules "^3.1.0"
+ deepmerge "^4.2.2"
+ is-module "^1.0.0"
+ resolve "^1.17.0"
+
+"@rollup/plugin-replace@^2.3.4":
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz#7dd84c17755d62b509577f2db37eb524d7ca88ca"
+ integrity sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ magic-string "^0.25.7"
+
+"@rollup/plugin-virtual@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz#0afc88d75c1e1378ab290b8e9898d4edb5be0d74"
+ integrity sha512-pw6ziJcyjZtntQ//bkad9qXaBx665SgEL8C8KI5wO8G5iU5MPxvdWrQyVaAvjojGm9tJoS8M9Z/EEepbqieYmw==
+
+"@rollup/pluginutils@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
+ integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
+ dependencies:
+ "@types/estree" "0.0.39"
+ estree-walker "^1.0.1"
+ picomatch "^2.2.2"
+
+"@types/estree@*":
+ version "0.0.45"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
+ integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
+
+"@types/estree@0.0.39":
+ version "0.0.39"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
+"@types/node@*":
+ version "14.14.10"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785"
+ integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==
+
+"@types/resolve@1.17.1":
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
+ integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
+ dependencies:
+ "@types/node" "*"
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+autoprefixer@^10.0.1:
+ version "10.0.4"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.0.4.tgz#f87ac6105d7861e31af794b8ebb1c6d4390d3d55"
+ integrity sha512-hmjYejN/WTyPP9cdNmiwtwqM8/ACVJPD5ExtwoOceQohNbgnFNiwpL2+U4bXS8aXozBL00WvH6WhqbuHf0Fgfg==
+ dependencies:
+ browserslist "^4.14.7"
+ caniuse-lite "^1.0.30001161"
+ colorette "^1.2.1"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss-value-parser "^4.1.0"
+
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+boolbase@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+ integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+browserslist@^4.14.5, browserslist@^4.14.7:
+ version "4.15.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.15.0.tgz#3d48bbca6a3f378e86102ffd017d9a03f122bdb0"
+ integrity sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==
+ dependencies:
+ caniuse-lite "^1.0.30001164"
+ colorette "^1.2.1"
+ electron-to-chromium "^1.3.612"
+ escalade "^3.1.1"
+ node-releases "^1.1.67"
+
+buffer-from@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+ integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+builtin-modules@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
+ integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
+
+call-bind@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce"
+ integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.0"
+
+caniuse-lite@^1.0.30001161, caniuse-lite@^1.0.30001164:
+ version "1.0.30001164"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001164.tgz#5bbfd64ca605d43132f13cc7fdabb17c3036bfdc"
+ integrity sha512-G+A/tkf4bu0dSp9+duNiXc7bGds35DioCyC6vgK2m/rjA4Krpy5WeZgZyfH2f0wj2kI6yAWWucyap6oOwmY1mg==
+
+chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+cheerio@^1.0.0-rc.3:
+ version "1.0.0-rc.3"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
+ integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
+ dependencies:
+ css-select "~1.2.0"
+ dom-serializer "~0.1.1"
+ entities "~1.1.1"
+ htmlparser2 "^3.9.1"
+ lodash "^4.15.0"
+ parse5 "^3.0.1"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+colorette@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
+ integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+ integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+convert-source-map@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
+ integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
+ dependencies:
+ safe-buffer "~5.1.1"
+
+core-js-compat@^3.7.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.8.0.tgz#3248c6826f4006793bd637db608bca6e4cd688b1"
+ integrity sha512-o9QKelQSxQMYWHXc/Gc4L8bx/4F7TTraE5rhuN8I7mKBt5dBIUpXpIR3omv70ebr8ST5R3PqbDQr+ZI3+Tt1FQ==
+ dependencies:
+ browserslist "^4.14.7"
+ semver "7.0.0"
+
+core-js@^3.6.5:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.0.tgz#0fc2d4941cadf80538b030648bb64d230b4da0ce"
+ integrity sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==
+
+css-select@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
+ integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
+ dependencies:
+ boolbase "~1.0.0"
+ css-what "2.1"
+ domutils "1.5.1"
+ nth-check "~1.0.1"
+
+css-what@2.1:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
+ integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
+
+cuint@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
+ integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^4.1.0:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+ integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
+ dependencies:
+ ms "2.1.2"
+
+deepmerge@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+ integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
+define-properties@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+ integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+ dependencies:
+ object-keys "^1.0.12"
+
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+ integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+ integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+
+dom-serializer@0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
+ integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
+ dependencies:
+ domelementtype "^2.0.1"
+ entities "^2.0.0"
+
+dom-serializer@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+ integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+ dependencies:
+ domelementtype "^1.3.0"
+ entities "^1.1.1"
+
+domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
+ integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
+
+domelementtype@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
+ integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
+
+domhandler@^2.3.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
+ integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
+ dependencies:
+ domelementtype "1"
+
+domutils@1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
+ integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=
+ dependencies:
+ dom-serializer "0"
+ domelementtype "1"
+
+domutils@^1.5.1:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
+ integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
+ dependencies:
+ dom-serializer "0"
+ domelementtype "1"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+
+electron-to-chromium@^1.3.612:
+ version "1.3.613"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.613.tgz#5ad7ec1e19d28c81edb6d61b9d4990d1c9716182"
+ integrity sha512-c3gkahddiUalk7HLhTC7PsKzPZmovYFtgh+g3rZJ+dGokk4n4dzEoOBnoV8VU8ptvnGJMhrjM/lyXKSltqf2hQ==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+
+entities@^1.1.1, entities@~1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
+ integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
+
+entities@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
+ integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+
+escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+estree-walker@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
+ integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
+
+estree-walker@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0"
+ integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+
+extend@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+finalhandler@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
+ integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ statuses "~1.5.0"
+ unpipe "~1.0.0"
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+fsevents@~2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+ integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+gensync@^1.0.0-beta.1:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-intrinsic@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be"
+ integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+
+glob@^7.1.6:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-symbols@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+ integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+htmlparser2@^3.9.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
+ integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
+ dependencies:
+ domelementtype "^1.3.1"
+ domhandler "^2.3.0"
+ domutils "^1.5.1"
+ entities "^1.1.1"
+ inherits "^2.0.1"
+ readable-stream "^3.1.1"
+
+http-errors@~1.7.2:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+ integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.4"
+ setprototypeof "1.1.1"
+ statuses ">= 1.5.0 < 2"
+ toidentifier "1.0.0"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-core-module@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+ integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+ dependencies:
+ has "^1.0.3"
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
+
+is-reference@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
+ integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
+ dependencies:
+ "@types/estree" "*"
+
+jest-worker@^26.2.1:
+ version "26.6.2"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
+ integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^7.0.0"
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+ integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
+
+json5@^2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+ integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
+ dependencies:
+ minimist "^1.2.5"
+
+lodash@^4.15.0, lodash@^4.17.19:
+ version "4.17.20"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+ integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+
+magic-string@^0.25.7:
+ version "0.25.7"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
+ integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
+ dependencies:
+ sourcemap-codec "^1.4.4"
+
+matched@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/matched/-/matched-5.0.1.tgz#620606d9dac6b7f4e955354b82e02ef4e3a62dc3"
+ integrity sha512-E1fhSTPRyhAlNaNvGXAgZQlq1hL0bgYMTk/6bktVlIhzUnX/SZs7296ACdVeNJE8xFNGSuvd9IpI7vSnmcqLvw==
+ dependencies:
+ glob "^7.1.6"
+ picomatch "^2.2.1"
+
+mdn-polyfills@^5.20.0:
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/mdn-polyfills/-/mdn-polyfills-5.20.0.tgz#ca8247edf20a4f60dec6804372229812b348260b"
+ integrity sha512-AbTv1ytcoOUAkxw6u5oo2QPf27kEZgxBAQr49jFb4i2VnTnFGfJbcIQ9UDBOdfNECeXsgkYFwB2BkdeTfOzztw==
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mime@^2.3.1:
+ version "2.4.6"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
+ integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+mkdirp@^0.5.0:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
+ integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanoid@^3.1.18:
+ version "3.1.20"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
+ integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
+
+node-releases@^1.1.67:
+ version "1.1.67"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12"
+ integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+ integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
+
+nth-check@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
+ integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
+ dependencies:
+ boolbase "~1.0.0"
+
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+ integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
+
+object-keys@^1.0.12, object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
+ integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+ has-symbols "^1.0.1"
+ object-keys "^1.1.1"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+parse5@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
+ integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
+ dependencies:
+ "@types/node" "*"
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-parse@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+ integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+
+picomatch@^2.2.1, picomatch@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+ integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
+pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+ integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+
+postcss-css-variables@^0.17.0:
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/postcss-css-variables/-/postcss-css-variables-0.17.0.tgz#56cba1d9f0360609136cfbfda8bbd2c1ed2e4082"
+ integrity sha512-/ZpFnJgksNOrQA72b3DKhExYh+0e2P5nEc3aPZ62G7JLmdDjWRFv3k/q4LxV7uzXFnmvkhXRbdVIiH5tKgfFNA==
+ dependencies:
+ balanced-match "^1.0.0"
+ escape-string-regexp "^1.0.3"
+ extend "^3.0.1"
+ postcss "^6.0.8"
+
+postcss-flexbugs-fixes@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690"
+ integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==
+ dependencies:
+ postcss "^7.0.26"
+
+postcss-import@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153"
+ integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==
+ dependencies:
+ postcss "^7.0.1"
+ postcss-value-parser "^3.2.3"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-url@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-8.0.0.tgz#7b10059bd12929cdbb1971c60f61a0e5af86b4ca"
+ integrity sha512-E2cbOQ5aii2zNHh8F6fk1cxls7QVFZjLPSrqvmiza8OuXLzIpErij8BDS5Y3STPfJgpIMNCPEr8JlKQWEoozUw==
+ dependencies:
+ mime "^2.3.1"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.0"
+ postcss "^7.0.2"
+ xxhashjs "^0.2.1"
+
+postcss-value-parser@^3.2.3:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
+ integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
+
+postcss-value-parser@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
+ integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
+
+postcss@^6.0.8:
+ version "6.0.23"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
+ integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
+ dependencies:
+ chalk "^2.4.1"
+ source-map "^0.6.1"
+ supports-color "^5.4.0"
+
+postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.26:
+ version "7.0.35"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
+ integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
+postcss@^8.1.1:
+ version "8.1.10"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.10.tgz#129834f94c720554d2cfdaeb27d5542ac4a026ea"
+ integrity sha512-iBXEV5VTTYaRRdxiFYzTtuv2lGMQBExqkZKSzkJe+Fl6rvQrA/49UVGKqB+LG54hpW/TtDBMGds8j33GFNW7pg==
+ dependencies:
+ colorette "^1.2.1"
+ nanoid "^3.1.18"
+ source-map "^0.6.1"
+ vfile-location "^3.2.0"
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+ integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=
+ dependencies:
+ pify "^2.3.0"
+
+readable-stream@^3.1.1:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+regenerate-unicode-properties@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
+ integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
+ dependencies:
+ regenerate "^1.4.0"
+
+regenerate@^1.4.0:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
+ integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
+
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+ integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+
+regenerator-transform@^0.14.2:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+ integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
+regexpu-core@^4.7.1:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+ integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
+ dependencies:
+ regenerate "^1.4.0"
+ regenerate-unicode-properties "^8.2.0"
+ regjsgen "^0.5.1"
+ regjsparser "^0.6.4"
+ unicode-match-property-ecmascript "^1.0.4"
+ unicode-match-property-value-ecmascript "^1.2.0"
+
+regjsgen@^0.5.1:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+ integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
+
+regjsparser@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272"
+ integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==
+ dependencies:
+ jsesc "~0.5.0"
+
+resolve@^1.1.7, resolve@^1.17.0, resolve@^1.3.2:
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
+ integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
+ dependencies:
+ is-core-module "^2.1.0"
+ path-parse "^1.0.6"
+
+rollup-plugin-terser@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
+ integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ jest-worker "^26.2.1"
+ serialize-javascript "^4.0.0"
+ terser "^5.0.0"
+
+rollup@^2.26.4:
+ version "2.34.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.34.0.tgz#ecc7f1d4ce2cb88bb51bec2f56b984f3c35b8271"
+ integrity sha512-dW5iLvttZzdVehjEuNJ1bWvuMEJjOWGmnuFS82WeKHTGXDkRHQeq/ExdifkSyJv9dLcR86ysKRmrIDyR6O0X8g==
+ optionalDependencies:
+ fsevents "~2.1.2"
+
+safe-buffer@^5.1.0, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+ integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+
+semver@^5.4.1, semver@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+send@0.17.1:
+ version "0.17.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
+ integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
+ dependencies:
+ debug "2.6.9"
+ depd "~1.1.2"
+ destroy "~1.0.4"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "~1.7.2"
+ mime "1.6.0"
+ ms "2.1.1"
+ on-finished "~2.3.0"
+ range-parser "~1.2.1"
+ statuses "~1.5.0"
+
+serialize-javascript@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+ integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+ dependencies:
+ randombytes "^2.1.0"
+
+serve-static@^1.14.1:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
+ integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.17.1"
+
+setprototypeof@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
+ integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+
+source-map-support@~0.5.19:
+ version "0.5.19"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+ integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.5.0:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
+
+source-map@^0.6.0, source-map@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+source-map@~0.7.2:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
+ integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+
+sourcemap-codec@^1.4.4:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+supports-color@^5.3.0, supports-color@^5.4.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+ integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+terser@^5.0.0:
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
+ integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
+ dependencies:
+ commander "^2.20.0"
+ source-map "~0.7.2"
+ source-map-support "~0.5.19"
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
+
+toidentifier@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+ integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+
+unicode-canonical-property-names-ecmascript@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
+ integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
+
+unicode-match-property-ecmascript@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
+ integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==
+ dependencies:
+ unicode-canonical-property-names-ecmascript "^1.0.4"
+ unicode-property-aliases-ecmascript "^1.0.4"
+
+unicode-match-property-value-ecmascript@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
+ integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
+
+unicode-property-aliases-ecmascript@^1.0.4:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
+ integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
+
+unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+
+util-deprecate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+vfile-location@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c"
+ integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+xxhashjs@^0.2.1, xxhashjs@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"
+ integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==
+ dependencies:
+ cuint "^0.2.2"