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 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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"