From 7a6efbcf90244e7d9c4f84b96bafcdc2800d4281 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Nov 2020 17:05:20 +0100 Subject: [PATCH 001/101] initial commit --- .gitignore | 1 + index.html | 12 ++ package.json | 6 + scripts/serve-local.js | 43 ++++ src/Link.js | 151 +++++++++++++ src/TemplateView.js | 345 ++++++++++++++++++++++++++++++ src/ViewModel.js | 59 +++++ src/create/CreateLinkViewModel.js | 26 +++ src/html.js | 111 ++++++++++ src/main.js | 16 ++ src/matrix/HomeServer.js | 76 +++++++ src/utils/enum.js | 26 +++ src/utils/error.js | 32 +++ src/utils/xhr.js | 97 +++++++++ yarn.lock | 155 ++++++++++++++ 15 files changed, 1156 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package.json create mode 100644 scripts/serve-local.js create mode 100644 src/Link.js create mode 100644 src/TemplateView.js create mode 100644 src/ViewModel.js create mode 100644 src/create/CreateLinkViewModel.js create mode 100644 src/html.js create mode 100644 src/main.js create mode 100644 src/matrix/HomeServer.js create mode 100644 src/utils/enum.js create mode 100644 src/utils/error.js create mode 100644 src/utils/xhr.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..fc78de8 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b09efe --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "finalhandler": "^1.1.2", + "serve-static": "^1.14.1" + } +} diff --git a/scripts/serve-local.js b/scripts/serve-local.js new file mode 100644 index 0000000..ae07d18 --- /dev/null +++ b/scripts/serve-local.js @@ -0,0 +1,43 @@ +/* +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. +*/ + +const finalhandler = require('finalhandler') +const http = require('http') +const serveStatic = require('serve-static') +const path = require('path'); + +// Serve up parent directory with cache disabled +const serve = serveStatic( + path.resolve(__dirname, "../"), + { + 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(3000); diff --git a/src/Link.js b/src/Link.js new file mode 100644 index 0000000..a9fd0b7 --- /dev/null +++ b/src/Link.js @@ -0,0 +1,151 @@ +/* +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"; + +const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/; +const ROOMID_PATTERN = /^!([^:]*):(.+)$/; +const EVENT_WITH_ROOMID_PATTERN = /^[!]([^:]*):(.+)\/\$([^:]+):(.+)$/; +const EVENT_WITH_ROOMALIAS_PATTERN = /^[#]([^:]*):(.+)\/\$([^:]+):(.+)$/; +const USERID_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 const LinkKind = createEnum( + "Room", + "User", + "Group", + "Event" +) + +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; +} + +export class Link { + static parseFragment(fragment) { + let [identifier, queryParams] = fragment.split("?"); + + let viaServers = []; + if (queryParams) { + viaServers = queryParams.split("&") + .map(pair => pair.split("=")) + .filter(([key, value]) => key === "via") + .map(([,value]) => value); + } + + if (identifier.startsWith("#/")) { + identifier = identifier.substr(2); + } + + let kind; + let matches; + // longest first, so they dont get caught by ROOMALIAS_PATTERN and ROOMID_PATTERN + matches = EVENT_WITH_ROOMID_PATTERN.exec(identifier); + if (matches) { + const roomServer = matches[2]; + const messageServer = matches[4]; + const roomLocalPart = matches[1]; + const messageLocalPart = matches[3]; + return new Link(viaServers, IdentifierKind.RoomId, roomLocalPart, roomServer, messageLocalPart, messageServer); + } + matches = EVENT_WITH_ROOMALIAS_PATTERN.exec(identifier); + if (matches) { + const roomServer = matches[2]; + const messageServer = matches[4]; + const roomLocalPart = matches[1]; + const messageLocalPart = matches[3]; + return new Link(viaServers, IdentifierKind.RoomAlias, roomLocalPart, roomServer, messageLocalPart, messageServer); + } + matches = USERID_PATTERN.exec(identifier); + if (matches) { + const server = matches[2]; + const localPart = matches[1]; + return new Link(viaServers, IdentifierKind.UserId, localPart, server); + } + matches = ROOMALIAS_PATTERN.exec(identifier); + if (matches) { + const server = matches[2]; + const localPart = matches[1]; + return new Link(viaServers, IdentifierKind.RoomAlias, localPart, server); + } + matches = ROOMID_PATTERN.exec(identifier); + if (matches) { + const server = matches[2]; + const localPart = matches[1]; + return new Link(viaServers, IdentifierKind.RoomId, localPart, server); + } + matches = GROUPID_PATTERN.exec(identifier); + if (matches) { + const server = matches[2]; + const localPart = matches[1]; + return new Link(viaServers, IdentifierKind.GroupId, localPart, server); + } + return; + } + + constructor(viaServers, identifierKind, localPart, server, messageLocalPart = null, messageServer = null) { + const servers = [server]; + if (messageServer) { + servers.push(messageServer); + } + servers.push(...viaServers); + this.servers = orderedUnique(servers); + this.identifierKind = identifierKind; + this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`; + this.eventId = messageLocalPart ? `$${messageLocalPart}:${messageServer}` : null; + } + + get kind() { + if (this.eventId) { + return LinkKind.Event; + } + switch (this.identifierKind) { + case IdentifierKind.RoomId: + case IdentifierKind.RoomAlias: + return LinkKind.Room; + case IdentifierKind.UserId: + return LinkKind.User; + case IdentifierKind.GroupId: + return LinkKind.Group; + default: + return null; + } + } +} diff --git a/src/TemplateView.js b/src/TemplateView.js new file mode 100644 index 0000000..07dea01 --- /dev/null +++ b/src/TemplateView.js @@ -0,0 +1,345 @@ +/* +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 } 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); + } + 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); + }; + } +} \ No newline at end of file diff --git a/src/ViewModel.js b/src/ViewModel.js new file mode 100644 index 0000000..3d00dec --- /dev/null +++ b/src/ViewModel.js @@ -0,0 +1,59 @@ +/* +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. +*/ + +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.onFirstSubscriptionAdded(name); + 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]; + this.onLastSubscriptionRemoved(name); + } + } + } +} + +export class ViewModel extends EventEmitter { + emitChange() { + this.emit("change"); + } +} \ No newline at end of file diff --git a/src/create/CreateLinkViewModel.js b/src/create/CreateLinkViewModel.js new file mode 100644 index 0000000..4318a45 --- /dev/null +++ b/src/create/CreateLinkViewModel.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. +*/ + +export class CreateLinkViewModel extends ViewModel { + createLink(identifier) { + this._link = Link.fromIdentifier(identifier); + this.emitChange(); + } + + get link() { + this._link.toURL(); + } +} \ No newline at end of file diff --git a/src/html.js b/src/html.js new file mode 100644 index 0000000..c391a00 --- /dev/null +++ b/src/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"], + [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/main.js b/src/main.js new file mode 100644 index 0000000..b1433ab --- /dev/null +++ b/src/main.js @@ -0,0 +1,16 @@ +import {xhrRequest} from "./utils/xhr.js"; +import {validateHomeServer} from "./matrix/HomeServer.js"; +import {Link, LinkKind} from "./Link.js"; + +export async function main() { + const link = Link.parseFragment(location.hash); + if (!link) { + throw new Error("bad link"); + } + const hs = await validateHomeServer(xhrRequest, link.servers[0]); + if (link.kind === LinkKind.User) { + const profile = await hs.getUserProfile(link.identifier); + const imageURL = hs.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop"); + console.log(imageURL); + } +} \ No newline at end of file diff --git a/src/matrix/HomeServer.js b/src/matrix/HomeServer.js new file mode 100644 index 0000000..016825b --- /dev/null +++ b/src/matrix/HomeServer.js @@ -0,0 +1,76 @@ +/* +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 validateHomeServer(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/${userId}`, {method: "GET"}).response(); + return body; + } + + getGroupProfile(groupId) { + //`/_matrix/client/r0/groups/${groupId}/profile` + } + + getPublicRooms() { + + } + + 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; + } +} \ No newline at end of file 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/xhr.js b/src/utils/xhr.js new file mode 100644 index 0000000..555542d --- /dev/null +++ b/src/utils/xhr.js @@ -0,0 +1,97 @@ +/* +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) { + 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..9a9801b --- /dev/null +++ b/yarn.lock @@ -0,0 +1,155 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +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" + +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= + +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= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +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= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +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= + +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" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +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== + +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== + +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" + +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== + +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== + +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" + +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== + +"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= + +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== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= From ead94695f144fe6f808cc67b07480c0e66b4a33f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 27 Nov 2020 21:28:54 +0100 Subject: [PATCH 002/101] preview view model --- src/Link.js | 7 +++- src/RootViewModel.js | 36 +++++++++++++++++ src/main.js | 20 ++++----- src/{matrix => preview}/HomeServer.js | 2 +- src/preview/PreviewViewModel.js | 58 +++++++++++++++++++++++++++ src/{ => utils}/TemplateView.js | 0 src/{ => utils}/html.js | 0 7 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 src/RootViewModel.js rename src/{matrix => preview}/HomeServer.js (97%) create mode 100644 src/preview/PreviewViewModel.js rename src/{ => utils}/TemplateView.js (100%) rename src/{ => utils}/html.js (100%) diff --git a/src/Link.js b/src/Link.js index a9fd0b7..5b60048 100644 --- a/src/Link.js +++ b/src/Link.js @@ -24,7 +24,7 @@ const EVENT_WITH_ROOMALIAS_PATTERN = /^[#]([^:]*):(.+)\/\$([^:]+):(.+)$/; const USERID_PATTERN = /^@([^:]+):(.+)$/; const GROUPID_PATTERN = /^\+([^:]+):(.+)$/; -export const IdentifierKind = createEnum( +const IdentifierKind = createEnum( "RoomId", "RoomAlias", "UserId", @@ -60,6 +60,9 @@ function orderedUnique(array) { export class Link { static parseFragment(fragment) { + if (!fragment) { + return null; + } let [identifier, queryParams] = fragment.split("?"); let viaServers = []; @@ -117,7 +120,7 @@ export class Link { const localPart = matches[1]; return new Link(viaServers, IdentifierKind.GroupId, localPart, server); } - return; + return null; } constructor(viaServers, identifierKind, localPart, server, messageLocalPart = null, messageServer = null) { diff --git a/src/RootViewModel.js b/src/RootViewModel.js new file mode 100644 index 0000000..ea4a9bf --- /dev/null +++ b/src/RootViewModel.js @@ -0,0 +1,36 @@ +/* +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 "./ViewModel.js"; +import {PreviewViewModel} from "./preview/PreviewViewModel.js"; + +export class RootViewModel extends ViewModel { + constructor(request, hash) { + super(); + this._request = request; + this.link = Link.parseFragment(hash); + this.previewViewModel = null; + } + + load() { + if (this.link) { + this.previewViewModel = new PreviewViewModel(this._request, this.link); + this.emitChange(); + this.previewViewModel.load(); + } + } +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index b1433ab..901eb55 100644 --- a/src/main.js +++ b/src/main.js @@ -1,16 +1,10 @@ import {xhrRequest} from "./utils/xhr.js"; -import {validateHomeServer} from "./matrix/HomeServer.js"; -import {Link, LinkKind} from "./Link.js"; +import {RootViewModel} from "./RootViewModel.js"; -export async function main() { - const link = Link.parseFragment(location.hash); - if (!link) { - throw new Error("bad link"); - } - const hs = await validateHomeServer(xhrRequest, link.servers[0]); - if (link.kind === LinkKind.User) { - const profile = await hs.getUserProfile(link.identifier); - const imageURL = hs.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop"); - console.log(imageURL); - } +export async function main(container) { + const vm = new RootViewModel(xhrRequest, location.hash); + vm.load(); + window.__rootvm = vm; + // const view = new RootView(vm); + // container.appendChild(view.mount()); } \ No newline at end of file diff --git a/src/matrix/HomeServer.js b/src/preview/HomeServer.js similarity index 97% rename from src/matrix/HomeServer.js rename to src/preview/HomeServer.js index 016825b..ac9a597 100644 --- a/src/matrix/HomeServer.js +++ b/src/preview/HomeServer.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -export async function validateHomeServer(request, baseURL) { +export async function resolveServer(request, baseURL) { if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) { baseURL = `https://${baseURL}`; } diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js new file mode 100644 index 0000000..e6fd7e8 --- /dev/null +++ b/src/preview/PreviewViewModel.js @@ -0,0 +1,58 @@ +/* +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} from "../Link.js"; +import {ViewModel} from "../ViewModel.js"; +import {resolveServer} from "./HomeServer.js"; + +export class PreviewViewModel extends ViewModel { + constructor(request, link) { + super(); + this._link = link; + this._request = request; + this.loading = false; + this.name = null; + this.avatarURL = null; + } + + async load() { + this.loading = true; + this.emitChange(); + for (const server of this._link.servers) { + try { + const homeserver = await resolveServer(this._request, server); + switch (this._link.kind) { + case LinkKind.User: + await this._loadUserPreview(homeserver, this._link.identifier); + } + // assume we're done if nothing threw + break; + } catch (err) { + continue; + } + } + this.loading = false; + this.emitChange(); + } + + 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; + } +} \ No newline at end of file diff --git a/src/TemplateView.js b/src/utils/TemplateView.js similarity index 100% rename from src/TemplateView.js rename to src/utils/TemplateView.js diff --git a/src/html.js b/src/utils/html.js similarity index 100% rename from src/html.js rename to src/utils/html.js From 4d15ce40c1e1c06ae3683fb3297f5e84b6a98662 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Nov 2020 10:57:39 +0100 Subject: [PATCH 003/101] minimal working version of user preview --- index.html | 2 +- src/Link.js | 1 - src/RootView.js | 29 +++++++++++++++++++++++++++++ src/RootViewModel.js | 15 ++++++++++++--- src/ViewModel.js | 15 +++++++++++++-- src/main.js | 5 +++-- src/preview/PreviewView.js | 27 +++++++++++++++++++++++++++ src/preview/PreviewViewModel.js | 15 ++++++++------- 8 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 src/RootView.js create mode 100644 src/preview/PreviewView.js diff --git a/index.html b/index.html index fc78de8..df58e7c 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ \ No newline at end of file diff --git a/src/Link.js b/src/Link.js index 5b60048..64827e2 100644 --- a/src/Link.js +++ b/src/Link.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - import {createEnum} from "./utils/enum.js"; const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/; diff --git a/src/RootView.js b/src/RootView.js new file mode 100644 index 0000000..2065d00 --- /dev/null +++ b/src/RootView.js @@ -0,0 +1,29 @@ +/* +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"; + +export class RootView extends TemplateView { + render(t, vm) { + return t.div({className: "RootView"}, t.mapView(vm => vm.activeSection, activeSection => { + switch (activeSection) { + case "preview": return new PreviewView(vm.previewViewModel); + default: return null; + } + })); + } +} \ No newline at end of file diff --git a/src/RootViewModel.js b/src/RootViewModel.js index ea4a9bf..4cc2fed 100644 --- a/src/RootViewModel.js +++ b/src/RootViewModel.js @@ -20,17 +20,26 @@ import {PreviewViewModel} from "./preview/PreviewViewModel.js"; export class RootViewModel extends ViewModel { constructor(request, hash) { - super(); - this._request = request; + super({request}); this.link = Link.parseFragment(hash); this.previewViewModel = null; } load() { if (this.link) { - this.previewViewModel = new PreviewViewModel(this._request, this.link); + this.previewViewModel = new PreviewViewModel(this.childOptions({ + link: this.link, + consentedServers: this.link.servers + })); this.emitChange(); this.previewViewModel.load(); } } + + get activeSection() { + if (this.previewViewModel) { + return "preview"; + } + return ""; + } } \ No newline at end of file diff --git a/src/ViewModel.js b/src/ViewModel.js index 3d00dec..4cc14e1 100644 --- a/src/ViewModel.js +++ b/src/ViewModel.js @@ -31,7 +31,6 @@ class EventEmitter { on(name, callback) { let handlers = this._handlersByName[name]; if (!handlers) { - this.onFirstSubscriptionAdded(name); this._handlersByName[name] = handlers = new Set(); } handlers.add(callback); @@ -46,14 +45,26 @@ class EventEmitter { handlers.delete(callback); if (handlers.length === 0) { delete this._handlersByName[name]; - this.onLastSubscriptionRemoved(name); } } } } export class ViewModel extends EventEmitter { + constructor(options) { + super(); + this._options = options; + } + emitChange() { this.emit("change"); } + + get request() { + return this._options.request; + } + + childOptions(options = {}) { + return Object.assign({request: this.request}, options); + } } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 901eb55..472fd6f 100644 --- a/src/main.js +++ b/src/main.js @@ -1,10 +1,11 @@ import {xhrRequest} from "./utils/xhr.js"; import {RootViewModel} from "./RootViewModel.js"; +import {RootView} from "./RootView.js"; export async function main(container) { const vm = new RootViewModel(xhrRequest, location.hash); vm.load(); window.__rootvm = vm; - // const view = new RootView(vm); - // container.appendChild(view.mount()); + const view = new RootView(vm); + container.appendChild(view.mount()); } \ No newline at end of file diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js new file mode 100644 index 0000000..ed31ee2 --- /dev/null +++ b/src/preview/PreviewView.js @@ -0,0 +1,27 @@ +/* +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 PreviewView extends TemplateView { + render(t, vm) { + return t.div({className: "PreviewView"}, [ + t.p(t.img({src: vm => vm.avatarUrl})), + t.p(vm => vm.name), + t.p(["loading: ", vm => vm.loading]) + ]); + } +} \ No newline at end of file diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index e6fd7e8..109679f 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -19,21 +19,22 @@ import {ViewModel} from "../ViewModel.js"; import {resolveServer} from "./HomeServer.js"; export class PreviewViewModel extends ViewModel { - constructor(request, link) { - super(); + constructor(options) { + super(options); + const {link, consentedServers} = options; this._link = link; - this._request = request; + this._consentedServers = consentedServers; this.loading = false; this.name = null; - this.avatarURL = null; + this.avatarUrl = null; } async load() { this.loading = true; this.emitChange(); - for (const server of this._link.servers) { + for (const server of this._consentedServers) { try { - const homeserver = await resolveServer(this._request, server); + const homeserver = await resolveServer(this.request, server); switch (this._link.kind) { case LinkKind.User: await this._loadUserPreview(homeserver, this._link.identifier); @@ -51,7 +52,7 @@ export class PreviewViewModel extends ViewModel { async _loadUserPreview(homeserver, userId) { const profile = await homeserver.getUserProfile(userId); this.name = profile.displayname || userId; - this.avatarURL = profile.avatar_url ? + this.avatarUrl = profile.avatar_url ? homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") : null; } From f4cd2a51123dc92f787971273ed7908420a8e3fc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Nov 2020 11:24:08 +0100 Subject: [PATCH 004/101] update UI when hash changes --- src/Link.js | 7 +++++++ src/RootView.js | 9 +++------ src/RootViewModel.js | 30 ++++++++++++++++++++---------- src/main.js | 7 +++++-- src/preview/PreviewView.js | 2 ++ src/preview/PreviewViewModel.js | 6 +++++- src/{ => utils}/ViewModel.js | 0 7 files changed, 42 insertions(+), 19 deletions(-) rename src/{ => utils}/ViewModel.js (100%) diff --git a/src/Link.js b/src/Link.js index 64827e2..91e4851 100644 --- a/src/Link.js +++ b/src/Link.js @@ -150,4 +150,11 @@ export class Link { 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); + } } diff --git a/src/RootView.js b/src/RootView.js index 2065d00..aecac4d 100644 --- a/src/RootView.js +++ b/src/RootView.js @@ -19,11 +19,8 @@ import {PreviewView} from "./preview/PreviewView.js"; export class RootView extends TemplateView { render(t, vm) { - return t.div({className: "RootView"}, t.mapView(vm => vm.activeSection, activeSection => { - switch (activeSection) { - case "preview": return new PreviewView(vm.previewViewModel); - default: return null; - } - })); + return t.div({className: "RootView"}, [ + t.mapView(vm => vm.previewViewModel, vm => vm ? new PreviewView(vm) : null), + ]); } } \ No newline at end of file diff --git a/src/RootViewModel.js b/src/RootViewModel.js index 4cc2fed..6e69d96 100644 --- a/src/RootViewModel.js +++ b/src/RootViewModel.js @@ -15,25 +15,35 @@ limitations under the License. */ import {Link} from "./Link.js"; -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./utils/ViewModel.js"; import {PreviewViewModel} from "./preview/PreviewViewModel.js"; export class RootViewModel extends ViewModel { - constructor(request, hash) { + constructor(request) { super({request}); - this.link = Link.parseFragment(hash); + this.link = null; this.previewViewModel = null; } - load() { + _updateChildVMs(oldLink) { if (this.link) { - this.previewViewModel = new PreviewViewModel(this.childOptions({ - link: this.link, - consentedServers: this.link.servers - })); - this.emitChange(); - this.previewViewModel.load(); + if (!oldLink || !oldLink.equals(this.link)) { + this.previewViewModel = new PreviewViewModel(this.childOptions({ + link: this.link, + consentedServers: this.link.servers + })); + this.previewViewModel.load(); + } + } else { + this.previewViewModel = null; } + this.emitChange(); + } + + updateHash(hash) { + const oldLink = this.link; + this.link = Link.parseFragment(hash); + this._updateChildVMs(oldLink); } get activeSection() { diff --git a/src/main.js b/src/main.js index 472fd6f..4089973 100644 --- a/src/main.js +++ b/src/main.js @@ -3,9 +3,12 @@ import {RootViewModel} from "./RootViewModel.js"; import {RootView} from "./RootView.js"; export async function main(container) { - const vm = new RootViewModel(xhrRequest, location.hash); - vm.load(); + const vm = new RootViewModel(xhrRequest); + vm.updateHash(location.hash); window.__rootvm = vm; const view = new RootView(vm); container.appendChild(view.mount()); + window.addEventListener('hashchange', event => { + vm.updateHash(location.hash); + }); } \ No newline at end of file diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index ed31ee2..f806e6f 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -21,6 +21,8 @@ export class PreviewView extends TemplateView { return t.div({className: "PreviewView"}, [ t.p(t.img({src: vm => vm.avatarUrl})), t.p(vm => vm.name), + t.p(vm => vm.identifier), + t.p(["Can preview from ", vm => vm._consentedServers.join(", ")]), t.p(["loading: ", vm => vm.loading]) ]); } diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 109679f..f5de306 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {LinkKind} from "../Link.js"; -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../utils/ViewModel.js"; import {resolveServer} from "./HomeServer.js"; export class PreviewViewModel extends ViewModel { @@ -56,4 +56,8 @@ export class PreviewViewModel extends ViewModel { homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") : null; } + + get identifier() { + return this._link.identifier; + } } \ No newline at end of file diff --git a/src/ViewModel.js b/src/utils/ViewModel.js similarity index 100% rename from src/ViewModel.js rename to src/utils/ViewModel.js From b14b404534e33b92d9ec25c4a9010def3c211554 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Nov 2020 11:24:18 +0100 Subject: [PATCH 005/101] start script shortcut --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 5b09efe..6e08b53 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "scripts": { + "start": "node scripts/serve-local.js" + }, "dependencies": { "finalhandler": "^1.1.2", "serve-static": "^1.14.1" From 91b328f5406be8117d8c8a3733ab58db0b235021 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 30 Nov 2020 12:49:16 +0100 Subject: [PATCH 006/101] basic styling --- css/main.css | 85 ++++++++++++ images/background.svg | 226 ++++++++++++++++++++++++++++++++ index.html | 1 + src/preview/PreviewView.js | 14 +- src/preview/PreviewViewModel.js | 2 + 5 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 css/main.css create mode 100644 images/background.svg diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..c7be786 --- /dev/null +++ b/css/main.css @@ -0,0 +1,85 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +:root { + --app-background: #f4f4f4; + --background: #ffffff; + --foreground: #000000; + --font: #333333; + --grey: #666666; + --accent: #0098d4; + --error: #d6001c; + --link: #0098d4; + --borders: #f4f4f4; +} + +html { + margin: 0; + padding: 0; +} + +body { + background-color: var(--app-background); + background-image: url('../images/background.svg'); + background-repeat: no-repeat; + background-size: auto; + background-position: center -50px; + height: 100%; + width: 100%; + font-size: 14px; + line-height: 24px; + color: var(--font); + padding: 120px 0 0 0; + margin: 0; +} + +body, +button, +input, +textarea { + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + font-style: normal; +} + +.RootView { + margin: 0 auto; + max-width: 480px; + width: 100%; +} + +.PreviewView { + background-color: var(--background); + border-radius: 16px; + box-shadow: 0px 18px 24px rgba(0, 0, 0, 0.06); + padding: 2rem; +} + +.PreviewView .avatar { + border-radius: 100%; +} + +.PreviewView .preview { + display: flex; +} + +.PreviewView .profileInfo { + flex: 1; + margin-left: 12px; +} + +.hidden { + display: none !important; +} \ No newline at end of file diff --git a/images/background.svg b/images/background.svg new file mode 100644 index 0000000..bdca144 --- /dev/null +++ b/images/background.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html index df58e7c..c929d18 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,7 @@ + diff --git a/package.json b/package.json index 6e08b53..f375573 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,31 @@ { + "version": "0.0.1", "scripts": { - "start": "node scripts/serve-local.js" + "start": "node scripts/serve-local.js", + "build": "node scripts/build.js" }, - "dependencies": { + "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", - "serve-static": "^1.14.1" + "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..a12e368 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,297 @@ +/* +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. +*/ + +const cheerio = require("cheerio"); +const fsRoot = require("fs"); +const fs = fsRoot.promises; +const path = require("path"); +const xxhash = require('xxhashjs'); +const { rollup } = require('rollup'); +const postcss = require("postcss"); +const postcssImport = require("postcss-import"); +// needed for legacy bundle +const babel = require('@rollup/plugin-babel'); +// needed to find the polyfill modules in the main-legacy.js bundle +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +// needed because some of the polyfills are written as commonjs modules +const commonjs = require('@rollup/plugin-commonjs'); +// multi-entry plugin so we can add polyfill file to main +const multi = require('@rollup/plugin-multi-entry'); +const { terser } = require("rollup-plugin-terser"); +const replace = require("@rollup/plugin-replace"); +// replace urls of asset names with content hashed version +const postcssUrl = require("postcss-url"); + +const cssvariables = require("postcss-css-variables"); +const autoprefixer = require("autoprefixer"); +const flexbugsFixes = require("postcss-flexbugs-fixes"); + +const projectDir = path.join(__dirname, "../"); + +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, "target/"); + await removeDirIfExists(targetDir); + await fs.mkdir(targetDir); + await fs.mkdir(path.join(targetDir, "images")); + 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", assets)); + const globalHash = assets.hashForAll(); + + await buildHtml(assets); + 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("")); + const html = doc.html(); + // include in the global hash, even not hashed itself + assets.addToHashForAll("index.html", html); + await assets.writeUnhashed("index.html", 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; +} + +async function buildCss(entryPath, assets) { + entryPath = path.join(projectDir, entryPath); + const assetUrlMapper = ({absolutePath}) => { + const relPath = absolutePath.substr(projectDir.length); + return assets.resolve(path.join(projectDir, "target", 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/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/yarn.lock b/yarn.lock index 9a9801b..ff5ba27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,1075 @@ # 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" @@ -9,6 +1078,25 @@ debug@2.6.9: 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" @@ -19,26 +1107,120 @@ destroy@~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" @@ -57,6 +1239,86 @@ fresh@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" @@ -68,16 +1330,128 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -inherits@2.0.4: +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" @@ -88,6 +1462,53 @@ ms@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" @@ -95,16 +1516,242 @@ on-finished@~2.3.0: 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" @@ -124,6 +1771,13 @@ send@0.17.1: 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" @@ -139,17 +1793,132 @@ setprototypeof@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" From 2dc3ad70a37464288f58bad61d20a0ce2b4a2c2e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 11:06:25 +0100 Subject: [PATCH 033/101] editor and lint settings --- .editorconfig | 14 ++++++++++++++ .eslintrc.js | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintrc.js 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 new file mode 100644 index 0000000..24bbb04 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "rules": { + "no-console": "off" + } +}; From 685ff40ea3c5a33ba76aeb68a1d932e9ac9359e6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 11:06:41 +0100 Subject: [PATCH 034/101] use port 5000 for local dev otherwise hydrogen sw kicks in for me --- scripts/serve-local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/serve-local.js b/scripts/serve-local.js index ae07d18..671a842 100644 --- a/scripts/serve-local.js +++ b/scripts/serve-local.js @@ -40,4 +40,4 @@ const server = http.createServer(function onRequest (req, res) { }); // Listen -server.listen(3000); +server.listen(5000); From fda01e0bc4e9f2d011b9bee8e2f521d362560ad0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 11:07:06 +0100 Subject: [PATCH 035/101] header, cleanup --- src/main.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index c768282..3ffd2f1 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,19 @@ +/* +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"; @@ -16,7 +32,7 @@ export async function main(container) { window.__rootvm = vm; const view = new RootView(vm); container.appendChild(view.mount()); - window.addEventListener('hashchange', event => { + window.addEventListener('hashchange', () => { vm.updateHash(location.hash); }); -} \ No newline at end of file +} From 32fdb6c9c3babcd88591ed847709be5e2ef8499b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 11:07:15 +0100 Subject: [PATCH 036/101] adjust desktop element:/ schema --- src/open/clients/Element.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 465e44e..2f426a6 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -57,9 +57,11 @@ export class Element { } 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}`; - } + return `element://${fragmentPath}`; + } } getLinkInstructions(platform, link) {} @@ -83,4 +85,4 @@ export class Element { canInterceptMatrixToLinks(platform) { return platform === Platform.iOS || platform === Platform.Android; } -} \ No newline at end of file +} From e8e2f04f0117adedd4983ff9a9cf9128f3bcadbd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 11:07:38 +0100 Subject: [PATCH 037/101] newlines --- src/RootView.js | 2 +- src/open/ClientView.js | 2 +- src/open/ClientViewModel.js | 2 +- src/utils/ViewModel.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RootView.js b/src/RootView.js index c55c3ab..303deb9 100644 --- a/src/RootView.js +++ b/src/RootView.js @@ -39,4 +39,4 @@ export class RootView extends TemplateView { function externalLink(t, href, label) { return t.a({href, target: "_blank", rel: "noopener noreferrer"}, label); -} \ No newline at end of file +} diff --git a/src/open/ClientView.js b/src/open/ClientView.js index 5382625..e0ab4d8 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -106,4 +106,4 @@ class InstallClientView extends TemplateView { return t.div({className: "InstallClientView"}, children); } -} \ No newline at end of file +} diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index 8685160..b2a98ce 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -146,4 +146,4 @@ if (this._preferredClient.getLinkSupport(this.preferences.platform, this._link)) } else { this.acceptInstructions = this._preferredClient.getLinkInstructions(this.preferences.platform, this._link); } - */ \ No newline at end of file + */ diff --git a/src/utils/ViewModel.js b/src/utils/ViewModel.js index 6c4b484..a74f93d 100644 --- a/src/utils/ViewModel.js +++ b/src/utils/ViewModel.js @@ -75,4 +75,4 @@ export class ViewModel extends EventEmitter { preferences: this.preferences, }, options); } -} \ No newline at end of file +} From 604dda73804ab626b29476336d98d13c00159a9a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 12:35:21 +0100 Subject: [PATCH 038/101] create apple-app-site-association during build --- package.json | 4 +++ scripts/build.js | 60 +++++++++++++++++++++++++------------ scripts/serve-local.js | 12 ++++---- src/open/clients/Element.js | 2 ++ 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index f375573..28234a8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,9 @@ { "version": "0.0.1", + "type": "module", + "engines": { + "node": ">= 14.0.0" + }, "scripts": { "start": "node scripts/serve-local.js", "build": "node scripts/build.js" diff --git a/scripts/build.js b/scripts/build.js index a12e368..4cd2f74 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -15,32 +15,34 @@ See the License for the specific language governing permissions and limitations under the License. */ -const cheerio = require("cheerio"); -const fsRoot = require("fs"); -const fs = fsRoot.promises; -const path = require("path"); -const xxhash = require('xxhashjs'); -const { rollup } = require('rollup'); -const postcss = require("postcss"); -const postcssImport = require("postcss-import"); +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 -const babel = require('@rollup/plugin-babel'); +import babel from '@rollup/plugin-babel'; // needed to find the polyfill modules in the main-legacy.js bundle -const { nodeResolve } = require('@rollup/plugin-node-resolve'); +import { nodeResolve } from '@rollup/plugin-node-resolve'; // needed because some of the polyfills are written as commonjs modules -const commonjs = require('@rollup/plugin-commonjs'); +import commonjs from '@rollup/plugin-commonjs'; // multi-entry plugin so we can add polyfill file to main -const multi = require('@rollup/plugin-multi-entry'); -const { terser } = require("rollup-plugin-terser"); -const replace = require("@rollup/plugin-replace"); +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 -const postcssUrl = require("postcss-url"); +import postcssUrl from "postcss-url"; +import cssvariables from "postcss-css-variables"; +import autoprefixer from "autoprefixer"; +import flexbugsFixes from "postcss-flexbugs-fixes"; -const cssvariables = require("postcss-css-variables"); -const autoprefixer = require("autoprefixer"); -const flexbugsFixes = require("postcss-flexbugs-fixes"); +import {createClients} from "../src/open/clients/index.js"; -const projectDir = path.join(__dirname, "../"); +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +const projectDir = path.join(dirname(fileURLToPath(import.meta.url)), "../"); async function build() { // get version number @@ -56,6 +58,8 @@ async function build() { 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", assets)); + await assets.writeUnhashed("apple-app-site-association", buildAppleAssociatedAppsFile(createClients())); + const globalHash = assets.hashForAll(); await buildHtml(assets); @@ -132,6 +136,24 @@ async function buildJsLegacy(mainFile, assets, extraFiles = []) { return code; } +function buildAppleAssociatedAppsFile(clients) { + const appIds = clients.map(c => c.appleAssociatedAppId).filter(id => !!id); + return JSON.stringify({ + "applinks": { + "apps": [], + "details": appIds.map(id => { + return { + "appID": id, + "paths": ["*"] + }; + }), + }, + "webcredentials": { + "apps": appIds + } + }); +} + async function buildCss(entryPath, assets) { entryPath = path.join(projectDir, entryPath); const assetUrlMapper = ({absolutePath}) => { diff --git a/scripts/serve-local.js b/scripts/serve-local.js index 671a842..02581f9 100644 --- a/scripts/serve-local.js +++ b/scripts/serve-local.js @@ -14,14 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -const finalhandler = require('finalhandler') -const http = require('http') -const serveStatic = require('serve-static') -const path = require('path'); +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( - path.resolve(__dirname, "../"), + projectDir, { etag: false, setHeaders: res => { diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 2f426a6..6faf89c 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -32,6 +32,8 @@ export class Element { ]; } + get appleAssociatedAppId() { return "7J4U792NQT.im.vector.app"; } + get description() { return 'Fully-featured Matrix client, used by millions.'; } get homepage() { return "https://element.io"; } From a342293b39b900d8d91303510e28f52e2926fba4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 12:35:53 +0100 Subject: [PATCH 039/101] add code to get privacy policy from homeserver --- src/preview/HomeServer.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/preview/HomeServer.js b/src/preview/HomeServer.js index 872d8af..5080e3b 100644 --- a/src/preview/HomeServer.js +++ b/src/preview/HomeServer.js @@ -75,6 +75,24 @@ export class HomeServer { } } + 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 stages = body.flows?.stages; + if (Array.isArray(stages) && stages.includes("m.login.terms")) { + 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) { From 8eb6ea6d6c2c0761ec2191a9cf27a37608789fce Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 13:22:42 +0100 Subject: [PATCH 040/101] only open links with ios app so you can still create links --- scripts/build.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 4cd2f74..8a04234 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -52,13 +52,14 @@ async function 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", assets)); - await assets.writeUnhashed("apple-app-site-association", buildAppleAssociatedAppsFile(createClients())); + await assets.writeUnhashed(".well-known/apple-app-site-association", buildAppleAssociatedAppsFile(createClients())); const globalHash = assets.hashForAll(); @@ -141,12 +142,14 @@ function buildAppleAssociatedAppsFile(clients) { return JSON.stringify({ "applinks": { "apps": [], - "details": appIds.map(id => { - return { - "appID": id, - "paths": ["*"] - }; - }), + "details": { + appIDs: appIds, + components: [ + { + "#": "/*", // only open urls with a fragment, so you can still create links + } + ] + }, }, "webcredentials": { "apps": appIds From 5704ade5dc8229171fa12acde5ec6655b12ce848 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 13:27:48 +0100 Subject: [PATCH 041/101] more build script cleanup --- scripts/build.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 8a04234..71f897b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -60,10 +60,8 @@ async function build() { 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", assets)); await assets.writeUnhashed(".well-known/apple-app-site-association", buildAppleAssociatedAppsFile(createClients())); - + await assets.writeUnhashed("index.html", await buildHtml(assets)); const globalHash = assets.hashForAll(); - - await buildHtml(assets); console.log(`built matrix.to ${version} (${globalHash}) successfully with ${assets.size} files`); } @@ -77,10 +75,7 @@ async function buildHtml(assets) { `` ]; doc("script#main").replaceWith(mainScripts.join("")); - const html = doc.html(); - // include in the global hash, even not hashed itself - assets.addToHashForAll("index.html", html); - await assets.writeUnhashed("index.html", html); + return doc.html(); } function createReplaceUrlPlugin(assets) { From 822431ac14a6f13b65ad5d1eb31659353203bfc6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 13:53:47 +0100 Subject: [PATCH 042/101] guess platform, might need tweaking still --- src/Platform.js | 32 +++++++++++++++++++++++++++----- src/main.js | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Platform.js b/src/Platform.js index 328826c..21de264 100644 --- a/src/Platform.js +++ b/src/Platform.js @@ -26,10 +26,32 @@ export const Platform = createEnum( "Linux" ); -export function guessApplicablePlatforms(userAgent) { - // use https://github.com/faisalman/ua-parser-js to guess, and pass as RootVM options - return [Platform.DesktopWeb, Platform.Linux]; - // return [Platform.MobileWeb, Platform.Android]; +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) { @@ -39,4 +61,4 @@ export function isWebPlatform(p) { export function isDesktopPlatform(p) { return p === Platform.Linux || p === Platform.Windows || p === Platform.macOS; -} \ No newline at end of file +} diff --git a/src/main.js b/src/main.js index 3ffd2f1..16c324a 100644 --- a/src/main.js +++ b/src/main.js @@ -24,7 +24,7 @@ export async function main(container) { const vm = new RootViewModel({ request: xhrRequest, openLink: url => location.href = url, - platforms: guessApplicablePlatforms(navigator.userAgent), + platforms: guessApplicablePlatforms(navigator.userAgent, navigator.platform), preferences: new Preferences(window.localStorage), origin: location.origin, }); From db2c2b0b25e1ad16c6635641d638c23b16c5399f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:11:46 +0100 Subject: [PATCH 043/101] first round of styling for preview --- css/client.css | 48 +++++++++++++ css/create.css | 5 ++ css/main.css | 70 ++----------------- css/preview.css | 117 ++++++++++++++++++++++++++++++++ css/spinner.css | 27 ++++++++ images/chat-icon.svg | 4 ++ images/member-icon.svg | 7 ++ src/open/OpenLinkView.js | 16 ++--- src/preview/PreviewView.js | 37 +++++++--- src/preview/PreviewViewModel.js | 23 +++++-- 10 files changed, 267 insertions(+), 87 deletions(-) create mode 100644 css/client.css create mode 100644 css/create.css create mode 100644 css/preview.css create mode 100644 css/spinner.css create mode 100644 images/chat-icon.svg create mode 100644 images/member-icon.svg diff --git a/css/client.css b/css/client.css new file mode 100644 index 0000000..0ba3b9d --- /dev/null +++ b/css/client.css @@ -0,0 +1,48 @@ +.ClientListView .list { + padding: 16px 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 .icon { + border-radius: 8px; + background-repeat: no-repeat; + background-size: cover; + width: 60px; + height: 60px; +} + +.ClientView .icon.element-io { + background-image: url('../images/client-icons/element.svg'); +} + +.ClientView .icon.weechat { + background-image: url('../images/client-icons/weechat.svg'); +} + +.ClientView .actions a.badge { + display: inline-block; + height: 40px; + margin: 8px 16px 8px 0; +} + +.ClientView .actions img { + height: 100%; +} diff --git a/css/create.css b/css/create.css new file mode 100644 index 0000000..4739e14 --- /dev/null +++ b/css/create.css @@ -0,0 +1,5 @@ +.CreateLinkView h2 { + padding: 0 40px; + word-break: break-all; + text-align: center; +} diff --git a/css/main.css b/css/main.css index f78ad1d..7bcf364 100644 --- a/css/main.css +++ b/css/main.css @@ -14,6 +14,11 @@ 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'); + :root { --app-background: #f4f4f4; --background: #ffffff; @@ -24,6 +29,8 @@ limitations under the License. --error: #d6001c; --link: #0098d4; --borders: #f4f4f4; + --lightgrey: #E6E6E6; + --spinner-stroke-size: 2px; } html { @@ -70,14 +77,6 @@ textarea { padding: 2rem; } -.PreviewView .preview { - text-align: center; -} - -.PreviewView .avatar { - border-radius: 100%; -} - .hidden { display: none !important; } @@ -134,45 +133,6 @@ button.text:hover { cursor: pointer; } -.ClientListView .list { - padding: 16px 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 .icon { - border-radius: 8px; - background-repeat: no-repeat; - background-size: cover; - width: 60px; - height: 60px; -} - -.ClientView .icon.element-io { - background-image: url('../images/client-icons/element.svg'); -} - -.ClientView .icon.weechat { - background-image: url('../images/client-icons/weechat.svg'); -} - .primary, .secondary { text-decoration: none; font-weight: bold; @@ -213,19 +173,3 @@ input[type='text'].large { width: 100%; box-sizing: border-box; } - -.ClientView .actions a.badge { - display: inline-block; - height: 40px; - margin: 8px 16px 8px 0; -} - -.ClientView .actions img { - height: 100%; -} - -.CreateLinkView h2 { - padding: 0 40px; - word-break: break-all; - text-align: center; -} \ No newline at end of file diff --git a/css/preview.css b/css/preview.css new file mode 100644 index 0000000..dced91c --- /dev/null +++ b/css/preview.css @@ -0,0 +1,117 @@ +.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 .spinner { + width: 32px; + height: 32px; +} + +.PreviewView .avatar.loading { + border: 1px solid #eee; + display: flex; + align-items: center; + justify-content: center; +} + +.PreviewView .identifier { + color: var(--grey); + font-size: 12px; + margin: 8px 0; +} + +.PreviewView .identifier.placeholder { + height: 1em; + margin: 16px 30%; +} + +.PreviewView .memberCount { + display: flex; + justify-content: center; + margin: 8px 0; +} + +.PreviewView .memberCount p:not(.placeholder) { + background-image: url(../images/member-icon.svg); + background-repeat: no-repeat; + background-position: 2px center; + padding: 0 4px 0 24px; +} + +.PreviewView .memberCount.loading { + margin: 16px 0; +} + +.PreviewView .memberCount p { + background-color: var(--lightgrey); + padding: 0 4px; + border-radius: 8px; + font-size: 12px; + margin: 0; +} + +.PreviewView .memberCount p.placeholder { + height: 1.5em; + width: 80px; +} + +.PreviewView .topic { + font-size: 12px; + color: var(--grey); + margin: 32px; +} + +.PreviewView .topic.loading { + display: block; +} + +.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 ease-in-out infinite; + background-size: 200%; +} + +@keyframes flash { + 0% { background-position-x: 0; } + 50% { background-position-x: -80%; } + 51% { background-position-x: 40%; } + 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/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/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/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index 52efe87..c67e2ff 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -22,14 +22,12 @@ export class OpenLinkView extends TemplateView { render(t, vm) { return t.div({className: "OpenLinkView card"}, [ t.view(new PreviewView(vm.previewViewModel)), - t.div({className: {hidden: vm => vm.previewLoading}}, [ - t.p({className: {hidden: vm => vm.clientsViewModel}}, t.button({ - className: "primary fullwidth", - onClick: () => vm.showClients() - }, vm => vm.showClientsLabel)), - t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), - t.p(["Preview provided by ", vm => vm.previewDomain]), - ]) + t.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({ + className: "primary fullwidth", + onClick: () => vm.showClients() + }, vm => vm.showClientsLabel)), + t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), + t.p({className: {hidden: vm => !vm.previewDomain}}, ["Preview provided by ", vm => vm.previewDomain]), ]); } -} \ No newline at end of file +} diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index a80216c..825ab62 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -19,18 +19,35 @@ import {ClientListView} from "../open/ClientListView.js"; import {ClientView} from "../open/ClientView.js"; export class PreviewView extends TemplateView { + render(t, vm) { + return t.mapView(vm => vm.loading, loading => loading ? new LoadingPreviewView(vm) : new LoadedPreviewView(vm)); + } +} + +class LoadingPreviewView extends TemplateView { + render(t, vm) { + return t.div({className: "PreviewView"}, [ + t.div({className: "avatarContainer"}, t.div({className: "avatar loading"}, t.div({className: "spinner"}))), + t.h1(vm => vm.identifier), + 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) { return t.div({className: "PreviewView"}, [ - t.h1({className: {hidden: vm => !vm.loading}}, "Loading preview…"), - t.div({className: {hidden: vm => vm.loading}}, [ - t.div({className: "preview"}, [ - t.p(t.img({className: "avatar", src: vm => vm.avatarUrl})), - t.h1(vm => vm.name), - t.p({className: "identifier"}, vm => vm.identifier), - t.p({className: {memberCount: true, hidden: vm => !vm.memberCount}}, [vm => vm.memberCount, " members"]), - t.p({className: {topic: true, hidden: vm => !vm.topic}}, [vm => vm.topic]), - ]), - ]) + t.div({className: "avatarContainer"}, t.img({className: "avatar", src: vm => vm.avatarUrl})), + 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 index 1d9f0f9..3f90daa 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -29,7 +29,7 @@ export class PreviewViewModel extends ViewModel { this.loading = false; this.name = null; this.avatarUrl = null; - this.identifier = null; + this.identifier = this._link.identifier; this.memberCount = null; this.topic = null; this.previewDomain = null; @@ -38,6 +38,7 @@ export class PreviewViewModel extends ViewModel { async load() { this.loading = true; this.emitChange(); + // await new Promise(r => setTimeout(r, 5000)); for (const server of this._consentedServers) { try { const homeserver = await resolveServer(this.request, server); @@ -51,15 +52,21 @@ export class PreviewViewModel extends ViewModel { } // assume we're done if nothing threw this.previewDomain = server; - break; + this.loading = false; + this.emitChange(); + return; } catch (err) { continue; } } - this.loading = false; - this.emitChange(); + this._setNoPreview(this._link); + this.loading = false; + 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; @@ -87,4 +94,10 @@ export class PreviewViewModel extends ViewModel { this.topic = publicRoom?.topic; this.identifier = publicRoom?.canonical_alias || link.identifier; } -} \ No newline at end of file + + _setNoPreview(link) { + this.name = link.identifier; + this.identifier = null; + this.avatarUrl = "images/chat-icon.svg"; + } +} From 72722dd9a81632e03a68df166e9bb0456649c897 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:27:36 +0100 Subject: [PATCH 044/101] more placeholder tuning --- css/preview.css | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/css/preview.css b/css/preview.css index dced91c..db2c508 100644 --- a/css/preview.css +++ b/css/preview.css @@ -50,11 +50,7 @@ margin: 8px 0; } -.PreviewView .memberCount p:not(.placeholder) { - background-image: url(../images/member-icon.svg); - background-repeat: no-repeat; - background-position: 2px center; - padding: 0 4px 0 24px; +.PreviewView .memberCount p { } .PreviewView .memberCount.loading { @@ -62,8 +58,11 @@ } .PreviewView .memberCount p { + background-image: url(../images/member-icon.svg); + background-repeat: no-repeat; + background-position: 2px center; + padding: 0 4px 0 24px; background-color: var(--lightgrey); - padding: 0 4px; border-radius: 8px; font-size: 12px; margin: 0; @@ -71,7 +70,9 @@ .PreviewView .memberCount p.placeholder { height: 1.5em; - width: 80px; + width: 100px; + background-image: none; + padding: 0; } .PreviewView .topic { @@ -82,6 +83,8 @@ .PreviewView .topic.loading { display: block; + margin: 24px 32px; + padding: 4px 0; } .PreviewView .topic.loading .placeholder { From 4ffefa0473f9304a947538034713d51f765fd48e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:30:02 +0100 Subject: [PATCH 045/101] margins closer to design --- css/preview.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/preview.css b/css/preview.css index db2c508..f4ec789 100644 --- a/css/preview.css +++ b/css/preview.css @@ -78,12 +78,12 @@ .PreviewView .topic { font-size: 12px; color: var(--grey); - margin: 32px; + margin: 32px 0; } .PreviewView .topic.loading { display: block; - margin: 24px 32px; + margin: 24px 12px; padding: 4px 0; } From 7f460caf6bd2099b1136347e630984fad6e0acac Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:31:24 +0100 Subject: [PATCH 046/101] don't show identifier if already shown in name --- src/preview/PreviewViewModel.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 3f90daa..3be8b11 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -93,6 +93,9 @@ export class PreviewViewModel extends ViewModel { 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) { From bc8540c3f53852c8e04925ff137df2ede25fee62 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:42:46 +0100 Subject: [PATCH 047/101] more preview polish --- css/preview.css | 15 +++++++-------- src/preview/PreviewView.js | 2 +- src/preview/PreviewViewModel.js | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/css/preview.css b/css/preview.css index f4ec789..b2f7b3f 100644 --- a/css/preview.css +++ b/css/preview.css @@ -50,29 +50,28 @@ margin: 8px 0; } -.PreviewView .memberCount p { -} .PreviewView .memberCount.loading { margin: 16px 0; } .PreviewView .memberCount p { + font-size: 12px; + margin: 0; +} + +.PreviewView .memberCount p:not(.placeholder) { + padding: 0 4px 0 24px; + border-radius: 8px; background-image: url(../images/member-icon.svg); background-repeat: no-repeat; background-position: 2px center; - padding: 0 4px 0 24px; background-color: var(--lightgrey); - border-radius: 8px; - font-size: 12px; - margin: 0; } .PreviewView .memberCount p.placeholder { height: 1.5em; width: 100px; - background-image: none; - padding: 0; } .PreviewView .topic { diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index 825ab62..ecc95f1 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -28,7 +28,7 @@ class LoadingPreviewView extends TemplateView { render(t, vm) { return t.div({className: "PreviewView"}, [ t.div({className: "avatarContainer"}, t.div({className: "avatar loading"}, t.div({className: "spinner"}))), - t.h1(vm => vm.identifier), + 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}}, [ diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 3be8b11..51de645 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -27,9 +27,9 @@ export class PreviewViewModel extends ViewModel { this._link = link; this._consentedServers = consentedServers; this.loading = false; - this.name = null; + this.name = this._link.identifier; this.avatarUrl = null; - this.identifier = this._link.identifier; + this.identifier = null; this.memberCount = null; this.topic = null; this.previewDomain = null; From 670afcc8c3bfe7007a9e04a0f24da00192fb21dd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 3 Dec 2020 17:43:00 +0100 Subject: [PATCH 048/101] add license and project name --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 28234a8..f2cfafb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { + "name": "matrix.to", "version": "0.0.1", "type": "module", + "license": "Apache-2.0", "engines": { "node": ">= 14.0.0" }, From 285ef27b0b3e06f76336d6ba1949ea0d33c1d8aa Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 10:33:31 +0100 Subject: [PATCH 049/101] add support for loading privacy policies so we can link to them in a new tab from server selection --- css/main.css | 14 +++++++ src/RootView.js | 2 + src/RootViewModel.js | 14 +++++-- src/policy/LoadServerPolicyView.js | 26 +++++++++++++ src/policy/LoadServerPolicyViewModel.js | 52 +++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/policy/LoadServerPolicyView.js create mode 100644 src/policy/LoadServerPolicyViewModel.js diff --git a/css/main.css b/css/main.css index 7bcf364..fc89f50 100644 --- a/css/main.css +++ b/css/main.css @@ -173,3 +173,17 @@ input[type='text'].large { 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/src/RootView.js b/src/RootView.js index 303deb9..183724d 100644 --- a/src/RootView.js +++ b/src/RootView.js @@ -17,12 +17,14 @@ 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."]), diff --git a/src/RootViewModel.js b/src/RootViewModel.js index 1387702..fba00ab 100644 --- a/src/RootViewModel.js +++ b/src/RootViewModel.js @@ -19,6 +19,7 @@ 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 { @@ -27,6 +28,7 @@ export class RootViewModel extends ViewModel { this.link = null; this.openLinkViewModel = null; this.createLinkViewModel = null; + this.loadServerPolicyViewModel = null; } _updateChildVMs(oldLink) { @@ -48,9 +50,15 @@ export class RootViewModel extends ViewModel { } updateHash(hash) { - const oldLink = this.link; - this.link = Link.parse(hash); - this._updateChildVMs(oldLink); + 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() { 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(); + } +} From c68e00f7a2d9f52721823eff10fb466e49048e12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 12:34:31 +0100 Subject: [PATCH 050/101] implement homeserver consent stage --- css/main.css | 38 ++++++++++ src/Preferences.js | 15 +++- src/RootViewModel.js | 2 - src/open/OpenLinkView.js | 26 +++++-- src/open/OpenLinkViewModel.js | 38 +++++++--- src/open/ServerConsentView.js | 118 +++++++++++++++++++++++++++++ src/open/ServerConsentViewModel.js | 54 +++++++++++++ 7 files changed, 268 insertions(+), 23 deletions(-) create mode 100644 src/open/ServerConsentView.js create mode 100644 src/open/ServerConsentViewModel.js diff --git a/css/main.css b/css/main.css index fc89f50..2e9220f 100644 --- a/css/main.css +++ b/css/main.css @@ -61,6 +61,11 @@ textarea { font-style: normal; } +button, input { + font-size: inherit; + font-weight: inherit; +} + .RootView { margin: 0 auto; max-width: 480px; @@ -174,6 +179,39 @@ input[type='text'].large { box-sizing: border-box; } +.ServerConsentView .actions { + margin-top: 24px; + display: flex; + align-items: center; +} + +.ServerConsentView input[type=submit] { + flex: 1; + margin-left: 12px; +} + +.ServerOptions div { + margin: 8px 0; +} + +.ServerOptions label { + display: flex; + align-items: center; +} + +.ServerOptions label > span, +.ServerOptions label > .line { + margin-left: 8px; +} + +.ServerOptions label > .line { + flex: 1; + border: none; + border-bottom: 1px solid var(--grey); + padding: 4px 0; +} + + .LoadServerPolicyView { display: flex; } diff --git a/src/Preferences.js b/src/Preferences.js index d75edc2..8293371 100644 --- a/src/Preferences.js +++ b/src/Preferences.js @@ -22,7 +22,7 @@ export class Preferences { this.clientId = null; // used to differentiate web from native if a client supports both this.platform = null; - this.homeservers = []; + this.homeservers = null; const prefsStr = localStorage.getItem("preferred_client"); if (prefsStr) { @@ -30,6 +30,10 @@ export class Preferences { this.clientId = id; this.platform = Platform[platform]; } + const serversStr = localStorage.getItem("consented_servers"); + if (serversStr) { + this.homeservers = JSON.parse(serversStr); + } } setClient(id, platform) { @@ -40,16 +44,19 @@ export class Preferences { } setHomeservers(homeservers) { - + this.homeservers = homeservers; + this._localStorage.setItem("consented_servers", JSON.stringify(homeservers)); } 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; + return !!this.clientId || !!this.platform || !!this.homeservers; } -} \ No newline at end of file +} diff --git a/src/RootViewModel.js b/src/RootViewModel.js index fba00ab..19deca7 100644 --- a/src/RootViewModel.js +++ b/src/RootViewModel.js @@ -37,10 +37,8 @@ export class RootViewModel extends ViewModel { if (!oldLink || !oldLink.equals(this.link)) { this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({ link: this.link, - consentedServers: this.link.servers, clients: createClients() })); - this.openLinkViewModel.load(); } } else { this.openLinkViewModel = null; diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index c67e2ff..9f2cca4 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -17,17 +17,29 @@ 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.view(new PreviewView(vm.previewViewModel)), - t.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({ - className: "primary fullwidth", - onClick: () => vm.showClients() - }, vm => vm.showClientsLabel)), - t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), - t.p({className: {hidden: vm => !vm.previewDomain}}, ["Preview provided by ", vm => vm.previewDomain]), + 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.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({ + className: "primary fullwidth", + onClick: () => vm.showClients() + }, vm => vm.showClientsLabel)), + t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), + t.p({className: {hidden: vm => !vm.previewDomain}}, ["Preview provided by ", vm => vm.previewDomain]), + ]); + } +} diff --git a/src/open/OpenLinkViewModel.js b/src/open/OpenLinkViewModel.js index 99707bb..a6a6320 100644 --- a/src/open/OpenLinkViewModel.js +++ b/src/open/OpenLinkViewModel.js @@ -18,31 +18,49 @@ 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"; export class OpenLinkViewModel extends ViewModel { constructor(options) { super(options); - const {clients, link, consentedServers} = options; + const {clients, link} = options; this._link = link; this._clients = clients; - this.previewViewModel = new PreviewViewModel(this.childOptions({link, consentedServers})); + this.serverConsentViewModel = null; + this.previewViewModel = null; + this.clientsViewModel = null; this.previewLoading = false; - const preferredClient = this.preferences.clientId ? clients.find(c => c.id === this.preferences.clientId) : null; - this.clientsViewModel = preferredClient ? new ClientListViewModel(this.childOptions({ - clients, - link, - client: preferredClient, - })) : null; + if (this.preferences.homeservers === null) { + this.serverConsentViewModel = new ServerConsentViewModel(this.childOptions({ + servers: this._link.servers, + done: () => { + this.serverConsentViewModel = null; + this._showLink(); + } + })); + } else { + this._showLink(); + } } - async load() { + async _showLink() { + const preferredClient = this.preferences.clientId ? this._clients.find(c => c.id === this.preferences.clientId) : null; + this.clientsViewModel = preferredClient ? new ClientListViewModel(this.childOptions({ + clients: this._clients, + link: this._link, + client: preferredClient, + })) : null; + 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.previewDomain; diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js new file mode 100644 index 0000000..cb87d7d --- /dev/null +++ b/src/open/ServerConsentView.js @@ -0,0 +1,118 @@ +/* +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() + }, "continue without a preview"); + return t.div({className: "ServerConsentView"}, [ + t.p([ + "View this link using ", + t.strong(vm => vm.selectedServer || "…"), + 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}}, [ + " to preview content, or your can ", + useAnotherServer, + ]), + " or ", + continueWithoutPreview, + "." + ]), + t.form({action: "#", 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: "persist"}), "Ask every time"]), + t.input({type: "submit", value: "Continue", className: "primary fullwidth"}) + ]) + ]) + ]); + } + + _onSubmit(evt) { + evt.preventDefault(); + this.value.continueWithSelection(); + } +} + +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}), 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", + pattern: "((?:[0-9a-zA-Z][0-9a-zA-Z-]{1,61}\\.)*)(xn--[a-z0-9]+|[a-z]+)", + 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") { + this.value.selectServer(value); + } + } + + _onChangeServerRadio(radio) { + let {value, form} = radio; + const {otherServer} = form.elements; + if (value === "other") { + otherServer.required = true; + value = otherServer.value; + } else { + otherServer.required = false; + } + this.value.selectServer(value); + } +} diff --git a/src/open/ServerConsentViewModel.js b/src/open/ServerConsentViewModel.js new file mode 100644 index 0000000..d11b1fc --- /dev/null +++ b/src/open/ServerConsentViewModel.js @@ -0,0 +1,54 @@ +/* +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"; + +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(); + } + + continueWithSelection() { + // keep previously consented servers + const homeservers = this.preferences.homeservers || []; + homeservers.unshift(this.selectedServer); + this.preferences.setHomeservers(homeservers); + this.done(); + } + + continueWithoutConsent() { + this.preferences.setHomeservers([]); + this.done(); + } +} From 9760b78f16db6d2de82d02193d4b5ed9e3a406ce Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 12:34:57 +0100 Subject: [PATCH 051/101] change build dir to "build" as in previous project --- .gitignore | 2 +- scripts/build.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2c085d1..dd87e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -target +build diff --git a/scripts/build.js b/scripts/build.js index 71f897b..633c841 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -48,7 +48,7 @@ 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, "target/"); + const targetDir = path.join(projectDir, "build/"); await removeDirIfExists(targetDir); await fs.mkdir(targetDir); await fs.mkdir(path.join(targetDir, "images")); @@ -58,7 +58,7 @@ async function build() { 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", assets)); + 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(); @@ -152,11 +152,11 @@ function buildAppleAssociatedAppsFile(clients) { }); } -async function buildCss(entryPath, assets) { +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(projectDir, "target", relPath)); + return assets.resolve(path.join(targetDir, relPath)); }; const preCss = await fs.readFile(entryPath, "utf8"); From 85ec4ab962050d4aedfca514d7af3323c043a884 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 12:35:13 +0100 Subject: [PATCH 052/101] adjust author of Element --- src/open/clients/Element.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 6faf89c..a489927 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -37,7 +37,7 @@ export class Element { get description() { return 'Fully-featured Matrix client, used by millions.'; } get homepage() { return "https://element.io"; } - get author() { return "https://element.io"; } + get author() { return "Element"; } getMaturity(platform) { return Maturity.Stable; } From 85b7d570e6d282409884ba4069386d758fb6608d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 12:35:21 +0100 Subject: [PATCH 053/101] fix privacy lookup stage filtering --- src/preview/HomeServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preview/HomeServer.js b/src/preview/HomeServer.js index 5080e3b..7736e8c 100644 --- a/src/preview/HomeServer.js +++ b/src/preview/HomeServer.js @@ -81,8 +81,8 @@ export class HomeServer { 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 stages = body.flows?.stages; - if (Array.isArray(stages) && stages.includes("m.login.terms")) { + 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"); From 4d57b3c5dabd0bad3c33d9f6937211c74f660b50 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 13:09:37 +0100 Subject: [PATCH 054/101] improve hs validation --- src/open/ServerConsentView.js | 10 +++++++--- src/open/ServerConsentViewModel.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js index cb87d7d..a44be81 100644 --- a/src/open/ServerConsentView.js +++ b/src/open/ServerConsentView.js @@ -75,7 +75,6 @@ class ServerOptions extends TemplateView { className: "line", placeholder: "Other", name: "otherServer", - pattern: "((?:[0-9a-zA-Z][0-9a-zA-Z-]{1,61}\\.)*)(xn--[a-z0-9]+|[a-z]+)", onClick: evt => this._onClickOther(evt), }) ]))); @@ -98,9 +97,13 @@ class ServerOptions extends TemplateView { let {name, value} = evt.target; if (name === "selectedServer") { this._onChangeServerRadio(evt.target); - } else if (name === "otherServer") { - this.value.selectServer(value); + const textField = evt.target; + if(!this.value.selectOtherServer(value)) { + textField.setCustomValidity("Please enter a valid domain name"); + } else { + textField.setCustomValidity(""); + } } } @@ -112,6 +115,7 @@ class ServerOptions extends TemplateView { 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 index d11b1fc..d7a117a 100644 --- a/src/open/ServerConsentViewModel.js +++ b/src/open/ServerConsentViewModel.js @@ -39,6 +39,22 @@ export class ServerConsentViewModel extends ViewModel { 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(urlStr); + return true; + } + } catch (err) {} + this.selectServer(null); + return false; + } + continueWithSelection() { // keep previously consented servers const homeservers = this.preferences.homeservers || []; From aa62f1fedc41db5cb555c4468221cf9b5d97d198 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 13:59:52 +0100 Subject: [PATCH 055/101] more tweaking to server consent flow and changing servers on preview --- css/main.css | 5 +++++ src/Link.js | 11 +---------- src/Preferences.js | 12 +++++++++--- src/RootViewModel.js | 5 ++++- src/open/OpenLinkView.js | 6 +++++- src/open/OpenLinkViewModel.js | 31 ++++++++++++++++++++++-------- src/open/ServerConsentView.js | 5 +++-- src/open/ServerConsentViewModel.js | 7 ++++--- src/preview/PreviewViewModel.js | 11 ++++++++--- src/utils/ViewModel.js | 2 +- src/utils/unique.js | 25 ++++++++++++++++++++++++ 11 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 src/utils/unique.js diff --git a/css/main.css b/css/main.css index 2e9220f..74ef817 100644 --- a/css/main.css +++ b/css/main.css @@ -179,6 +179,11 @@ input[type='text'].large { box-sizing: border-box; } +.OpenLinkView .previewSource { + color: var(--grey); + font-size: 12px; +} + .ServerConsentView .actions { margin-top: 24px; display: flex; diff --git a/src/Link.js b/src/Link.js index 6b6c401..94bf266 100644 --- a/src/Link.js +++ b/src/Link.js @@ -15,6 +15,7 @@ limitations under the License. */ import {createEnum} from "./utils/enum.js"; +import {orderedUnique} from "./utils/unique.js"; const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/; const ROOMID_PATTERN = /^!([^:]*):(.+)$/; @@ -56,16 +57,6 @@ export const LinkKind = createEnum( "Event" ) -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; -} - export class Link { static parse(fragment) { if (!fragment) { diff --git a/src/Preferences.js b/src/Preferences.js index 8293371..53f5511 100644 --- a/src/Preferences.js +++ b/src/Preferences.js @@ -15,9 +15,11 @@ limitations under the License. */ import {Platform} from "./Platform.js"; +import {EventEmitter} from "./utils/ViewModel.js"; -export class Preferences { +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 @@ -41,11 +43,15 @@ export class Preferences { platform = Platform[platform]; this.platform = platform; this._localStorage.setItem("preferred_client", JSON.stringify({id, platform})); + this.emit("canClear") } - setHomeservers(homeservers) { + setHomeservers(homeservers, persist) { this.homeservers = homeservers; - this._localStorage.setItem("consented_servers", JSON.stringify(homeservers)); + if (persist) { + this._localStorage.setItem("consented_servers", JSON.stringify(homeservers)); + this.emit("canClear"); + } } clear() { diff --git a/src/RootViewModel.js b/src/RootViewModel.js index 19deca7..a77b82b 100644 --- a/src/RootViewModel.js +++ b/src/RootViewModel.js @@ -29,6 +29,9 @@ export class RootViewModel extends ViewModel { this.openLinkViewModel = null; this.createLinkViewModel = null; this.loadServerPolicyViewModel = null; + this.preferences.on("canClear", () => { + this.emitChange(); + }); } _updateChildVMs(oldLink) { @@ -37,7 +40,7 @@ export class RootViewModel extends ViewModel { if (!oldLink || !oldLink.equals(this.link)) { this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({ link: this.link, - clients: createClients() + clients: createClients(), })); } } else { diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index 9f2cca4..d325e43 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -39,7 +39,11 @@ class ShowLinkView extends TemplateView { onClick: () => vm.showClients() }, vm => vm.showClientsLabel)), t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), - t.p({className: {hidden: vm => !vm.previewDomain}}, ["Preview provided by ", vm => vm.previewDomain]), + t.p({className: {previewSource: true, hidden: vm => !vm.previewDomain}}, [ + vm => vm.previewFailed ? `${vm.previewDomain} did not return 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 index a6a6320..f49891c 100644 --- a/src/open/OpenLinkViewModel.js +++ b/src/open/OpenLinkViewModel.js @@ -32,18 +32,22 @@ export class OpenLinkViewModel extends ViewModel { this.clientsViewModel = null; this.previewLoading = false; if (this.preferences.homeservers === null) { - this.serverConsentViewModel = new ServerConsentViewModel(this.childOptions({ - servers: this._link.servers, - done: () => { - this.serverConsentViewModel = null; - this._showLink(); - } - })); + this._showServerConsent(); } else { this._showLink(); } } + _showServerConsent() { + this.serverConsentViewModel = new ServerConsentViewModel(this.childOptions({ + servers: this._link.servers, + done: () => { + this.serverConsentViewModel = null; + this._showLink(); + } + })); + } + async _showLink() { const preferredClient = this.preferences.clientId ? this._clients.find(c => c.id === this.preferences.clientId) : null; this.clientsViewModel = preferredClient ? new ClientListViewModel(this.childOptions({ @@ -63,13 +67,24 @@ export class OpenLinkViewModel extends ViewModel { } get previewDomain() { - return this.previewViewModel.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(); + } + showClients() { if (!this.clientsViewModel) { this.clientsViewModel = new ClientListViewModel(this.childOptions({ diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js index a44be81..d0461bb 100644 --- a/src/open/ServerConsentView.js +++ b/src/open/ServerConsentView.js @@ -50,7 +50,7 @@ export class ServerConsentView extends TemplateView { t.form({action: "#", 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: "persist"}), "Ask every time"]), + t.label([t.input({type: "checkbox", name: "askEveryTime"}), "Ask every time"]), t.input({type: "submit", value: "Continue", className: "primary fullwidth"}) ]) ]) @@ -59,7 +59,8 @@ export class ServerConsentView extends TemplateView { _onSubmit(evt) { evt.preventDefault(); - this.value.continueWithSelection(); + const {askEveryTime} = evt.target.elements; + this.value.continueWithSelection(askEveryTime.checked); } } diff --git a/src/open/ServerConsentViewModel.js b/src/open/ServerConsentViewModel.js index d7a117a..7278f61 100644 --- a/src/open/ServerConsentViewModel.js +++ b/src/open/ServerConsentViewModel.js @@ -19,6 +19,7 @@ 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) { @@ -47,7 +48,7 @@ export class ServerConsentViewModel extends ViewModel { 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(urlStr); + this.selectServer(domainOrUrl); return true; } } catch (err) {} @@ -55,11 +56,11 @@ export class ServerConsentViewModel extends ViewModel { return false; } - continueWithSelection() { + continueWithSelection(askEveryTime) { // keep previously consented servers const homeservers = this.preferences.homeservers || []; homeservers.unshift(this.selectedServer); - this.preferences.setHomeservers(homeservers); + this.preferences.setHomeservers(orderedUnique(homeservers), !askEveryTime); this.done(); } diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 51de645..03c72f4 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -32,13 +32,13 @@ export class PreviewViewModel extends ViewModel { this.identifier = null; this.memberCount = null; this.topic = null; - this.previewDomain = null; + this.domain = null; + this.failed = false; } async load() { this.loading = true; this.emitChange(); - // await new Promise(r => setTimeout(r, 5000)); for (const server of this._consentedServers) { try { const homeserver = await resolveServer(this.request, server); @@ -51,7 +51,7 @@ export class PreviewViewModel extends ViewModel { break; } // assume we're done if nothing threw - this.previewDomain = server; + this.domain = server; this.loading = false; this.emitChange(); return; @@ -59,8 +59,13 @@ export class PreviewViewModel extends ViewModel { continue; } } + this._setNoPreview(this._link); this.loading = false; + if (this._consentedServers.length) { + this.domain = this._consentedServers[this._consentedServers.length - 1]; + this.failed = true; + } this.emitChange(); } diff --git a/src/utils/ViewModel.js b/src/utils/ViewModel.js index a74f93d..b9afbc1 100644 --- a/src/utils/ViewModel.js +++ b/src/utils/ViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -class EventEmitter { +export class EventEmitter { constructor() { this._handlersByName = {}; } 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 From e69b92418fcb12414ff5ca9c6ed69b606382bd45 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 14:00:50 +0100 Subject: [PATCH 056/101] language --- src/open/OpenLinkView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index d325e43..5c82f57 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -40,7 +40,7 @@ class ShowLinkView extends TemplateView { }, vm => vm.showClientsLabel)), t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), t.p({className: {previewSource: true, hidden: vm => !vm.previewDomain}}, [ - vm => vm.previewFailed ? `${vm.previewDomain} did not return a preview.` : `Preview provided by ${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"), ]), From 2ecdf323563c9daa897b709593d291a0fd1a8b6c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 15:31:03 +0100 Subject: [PATCH 057/101] polish create page --- css/create.css | 8 +++++ css/main.css | 21 +++++++++++-- src/Link.js | 9 ++++++ src/create/CreateLinkView.js | 49 +++++++++++++++++++++++++++---- src/create/CreateLinkViewModel.js | 6 +++- 5 files changed, 84 insertions(+), 9 deletions(-) diff --git a/css/create.css b/css/create.css index 4739e14..c11e273 100644 --- a/css/create.css +++ b/css/create.css @@ -3,3 +3,11 @@ 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 index 74ef817..6fdb456 100644 --- a/css/main.css +++ b/css/main.css @@ -47,7 +47,6 @@ body { height: 100%; width: 100%; font-size: 14px; - line-height: 24px; color: var(--font); padding: 120px 0 0 0; margin: 0; @@ -142,9 +141,8 @@ button.text:hover { text-decoration: none; font-weight: bold; text-align: center; - padding: 8px; + padding: 12px 8px; margin: 8px 0; - line-height: 24px; } .secondary { @@ -158,6 +156,23 @@ button.text:hover { border-radius: 32px; } +.primary.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; diff --git a/src/Link.js b/src/Link.js index 94bf266..1ccc4d0 100644 --- a/src/Link.js +++ b/src/Link.js @@ -58,6 +58,15 @@ export const LinkKind = createEnum( ) 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; diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js index b7bc007..473c781 100644 --- a/src/create/CreateLinkView.js +++ b/src/create/CreateLinkView.js @@ -17,23 +17,53 @@ limitations under the License. import {TemplateView} from "../utils/TemplateView.js"; import {PreviewView} from "../preview/PreviewView.js"; +function selectNode(node) { + let selection = window.getSelection(); + selection.removeAllRanges(); + let range = document.createRange(); + range.selectNode(copyNode); + selection.addRange(range); +} + +function copyButton(t, copyNode, label, classNames) { + return t.button({className: `${classNames} icon copy`, onClick: evt => { + const button = evt.target; + selectNode(copyNode); + if (document.execCommand("copy")) { + 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); +} + 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( {className: {hidden: vm => vm.previewViewModel}}, "Create shareable links to Matrix rooms, users or messages without being tied to any app" ), t.mapView(vm => vm.previewViewModel, childVM => childVM ? new PreviewView(childVM) : null), - t.h2({className: {hidden: vm => !vm.linkUrl}}, t.a({href: vm => vm.linkUrl}, vm => vm.linkUrl)), - t.form({action: "#", onSubmit: evt => this._onSubmit(evt)}, [ + t.div({className: {hidden: vm => !vm.linkUrl}}, [ + t.h2(link), + t.div(copyButton(t, link, "Copy link", "fullwidth primary")), + ]), + t.form({action: "#", onSubmit: evt => this._onSubmit(evt), className: {hidden: vm => vm.linkUrl}}, [ t.div(t.input({ className: "fullwidth large", type: "text", name: "identifier", - placeholder: "#room:example.com, @user:example.com" + placeholder: "#room:example.com, @user:example.com", + onChange: evt => this._onIdentifierChange(evt) })), - t.div(t.input({className: "primary fullwidth", type: "submit", value: "Create link"})) + t.div(t.input({className: "primary fullwidth icon link", type: "submit", value: "Create link"})) ]), ]); } @@ -44,4 +74,13 @@ export class CreateLinkView extends TemplateView { const identifier = form.elements.identifier.value; this.value.createLink(identifier); } -} \ No newline at end of file + + _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 index 40f39d2..306cc47 100644 --- a/src/create/CreateLinkViewModel.js +++ b/src/create/CreateLinkViewModel.js @@ -24,6 +24,10 @@ export class CreateLinkViewModel extends ViewModel { this.previewViewModel = null; } + validateIdentifier(identifier) { + return Link.validateIdentifier(identifier); + } + async createLink(identifier) { this._link = Link.parse(identifier); if (this._link) { @@ -45,4 +49,4 @@ export class CreateLinkViewModel extends ViewModel { return `${this.origin}/#${this._link.toFragment()}`; } } -} \ No newline at end of file +} From 081451252dca3680fa9de4af2dbc70c79bb6b0c1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 15:31:11 +0100 Subject: [PATCH 058/101] language --- index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index bcf855d..c4caf02 100644 --- a/index.html +++ b/index.html @@ -2,8 +2,8 @@ - matrix.to - you're invited to chat on matrix - + You're invited to talk on Matrix + @@ -13,4 +13,4 @@ main(document.body); - \ No newline at end of file + From 77fad9406b97ebe3dc725d2cd659ffa3977fe835 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 15:31:25 +0100 Subject: [PATCH 059/101] icon for create --- images/copy.svg | 4 ++++ images/link.svg | 3 +++ images/tick.svg | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 images/copy.svg create mode 100644 images/link.svg create mode 100644 images/tick.svg diff --git a/images/copy.svg b/images/copy.svg new file mode 100644 index 0000000..7906974 --- /dev/null +++ b/images/copy.svg @@ -0,0 +1,4 @@ + + + + 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/tick.svg b/images/tick.svg new file mode 100644 index 0000000..b459490 --- /dev/null +++ b/images/tick.svg @@ -0,0 +1,3 @@ + + + From 7bb2ba91c339f16c6a1138f82cdd6d14894f5b7e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 15:59:50 +0100 Subject: [PATCH 060/101] we should not have a mapView as root, add a div because if we do a mapView of this view from the parent, it won't work as the parent is swapped from underneath that binding and it can't replace it --- src/preview/PreviewView.js | 8 +++++--- src/utils/TemplateView.js | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index ecc95f1..2cee062 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -20,13 +20,15 @@ import {ClientView} from "../open/ClientView.js"; export class PreviewView extends TemplateView { render(t, vm) { - return t.mapView(vm => vm.loading, loading => loading ? new LoadingPreviewView(vm) : new LoadedPreviewView(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({className: "PreviewView"}, [ + 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"}), @@ -42,7 +44,7 @@ class LoadingPreviewView extends TemplateView { class LoadedPreviewView extends TemplateView { render(t, vm) { - return t.div({className: "PreviewView"}, [ + return t.div([ t.div({className: "avatarContainer"}, t.img({className: "avatar", src: vm => vm.avatarUrl})), t.h1(vm => vm.name), t.p({className: {identifier: true, hidden: vm => !vm.identifier}}, vm => vm.identifier), diff --git a/src/utils/TemplateView.js b/src/utils/TemplateView.js index 1617d6a..c414c37 100644 --- a/src/utils/TemplateView.js +++ b/src/utils/TemplateView.js @@ -238,6 +238,8 @@ class TemplateBuilder { const newNode = renderNode(node); if (node.parentNode) { node.parentNode.replaceChild(newNode, node); + } else { + console.warn("Could not update parent of node binding"); } node = newNode; } @@ -342,4 +344,4 @@ for (const [ns, tags] of Object.entries(TAG_NAMES)) { return this.elNS(ns, tag, attributes, children); }; } -} \ No newline at end of file +} From 19ce0c8b69c0ee1a3ff8fe1be6c07d358eb2ba28 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 16:01:26 +0100 Subject: [PATCH 061/101] allow to create more links --- src/create/CreateLinkView.js | 14 ++++++++++++-- src/create/CreateLinkViewModel.js | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js index 473c781..c4ae494 100644 --- a/src/create/CreateLinkView.js +++ b/src/create/CreateLinkView.js @@ -54,12 +54,14 @@ export class CreateLinkView extends TemplateView { t.div({className: {hidden: vm => !vm.linkUrl}}, [ t.h2(link), t.div(copyButton(t, link, "Copy link", "fullwidth primary")), + t.div(t.button({className: "secondary fullwidth", onClick: () => this._clear()}, "Or create another link")), ]), t.form({action: "#", onSubmit: evt => this._onSubmit(evt), className: {hidden: vm => vm.linkUrl}}, [ 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) })), @@ -71,8 +73,10 @@ export class CreateLinkView extends TemplateView { _onSubmit(evt) { evt.preventDefault(); const form = evt.target; - const identifier = form.elements.identifier.value; - this.value.createLink(identifier); + const {identifier} = form.elements; + this.value.createLink(identifier.value); + identifier.value = ""; + } _onIdentifierChange(evt) { @@ -83,4 +87,10 @@ export class CreateLinkView extends TemplateView { inputField.setCustomValidity(""); } } + + _clear() { + this.value.clear(); + const textField = this.root().querySelector("input[name=identifier]"); + textField.focus(); + } } diff --git a/src/create/CreateLinkViewModel.js b/src/create/CreateLinkViewModel.js index 306cc47..6e5fcc5 100644 --- a/src/create/CreateLinkViewModel.js +++ b/src/create/CreateLinkViewModel.js @@ -21,6 +21,7 @@ import {Link} from "../Link.js"; export class CreateLinkViewModel extends ViewModel { constructor(options) { super(options); + this._link = null; this.previewViewModel = null; } @@ -49,4 +50,10 @@ export class CreateLinkViewModel extends ViewModel { return `${this.origin}/#${this._link.toFragment()}`; } } + + clear() { + this._link = null; + this.previewViewModel = null; + this.emitChange(); + } } From c8bd5291735d9619e488438dcac0848be7ae3a4d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 16:01:43 +0100 Subject: [PATCH 062/101] extract open css file --- css/main.css | 55 +++++----------------------------------------------- css/open.css | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 50 deletions(-) create mode 100644 css/open.css diff --git a/css/main.css b/css/main.css index 6fdb456..4f149ef 100644 --- a/css/main.css +++ b/css/main.css @@ -18,6 +18,7 @@ limitations under the License. @import url('client.css'); @import url('preview.css'); @import url('create.css'); +@import url('open.css'); :root { --app-background: #f4f4f4; @@ -156,22 +157,14 @@ button.text:hover { border-radius: 32px; } -.primary.icon { +.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'); -} +.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; @@ -194,44 +187,6 @@ input[type='text'].large { box-sizing: border-box; } -.OpenLinkView .previewSource { - color: var(--grey); - font-size: 12px; -} - -.ServerConsentView .actions { - margin-top: 24px; - display: flex; - align-items: center; -} - -.ServerConsentView input[type=submit] { - flex: 1; - margin-left: 12px; -} - -.ServerOptions div { - margin: 8px 0; -} - -.ServerOptions label { - display: flex; - align-items: center; -} - -.ServerOptions label > span, -.ServerOptions label > .line { - margin-left: 8px; -} - -.ServerOptions label > .line { - flex: 1; - border: none; - border-bottom: 1px solid var(--grey); - padding: 4px 0; -} - - .LoadServerPolicyView { display: flex; } diff --git a/css/open.css b/css/open.css new file mode 100644 index 0000000..d02c0e0 --- /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 .previewSource { + color: var(--grey); + font-size: 12px; +} + +.ServerConsentView .actions { + margin-top: 24px; + display: flex; + align-items: center; +} + +.ServerConsentView input[type=submit] { + flex: 1; + margin-left: 12px; +} + +.ServerOptions div { + margin: 8px 0; +} + +.ServerOptions label { + display: flex; + align-items: center; +} + +.ServerOptions label > span, +.ServerOptions label > .line { + margin-left: 8px; +} + +.ServerOptions label > .line { + flex: 1; + border: none; + border-bottom: 1px solid var(--grey); + padding: 4px 0; +} From f52867f2e8e2c327034ba96934da08d7a6ae0065 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 16:01:51 +0100 Subject: [PATCH 063/101] c/p error --- src/create/CreateLinkView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js index c4ae494..3dcebcd 100644 --- a/src/create/CreateLinkView.js +++ b/src/create/CreateLinkView.js @@ -21,7 +21,7 @@ function selectNode(node) { let selection = window.getSelection(); selection.removeAllRanges(); let range = document.createRange(); - range.selectNode(copyNode); + range.selectNode(node); selection.addRange(range); } From a705621dd5f27f5bd918c8999cfb7c4ce520146f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 16:08:42 +0100 Subject: [PATCH 064/101] put client icons as img tag --- css/client.css | 12 +++--------- src/open/ClientView.js | 2 +- src/open/ClientViewModel.js | 4 ++++ src/open/clients/Element.js | 7 +++++-- src/open/clients/Weechat.js | 3 ++- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/css/client.css b/css/client.css index 0ba3b9d..6aa01ef 100644 --- a/css/client.css +++ b/css/client.css @@ -21,20 +21,14 @@ margin-top: 0; } -.ClientView .icon { +.ClientView .clientIcon { border-radius: 8px; background-repeat: no-repeat; background-size: cover; width: 60px; height: 60px; -} - -.ClientView .icon.element-io { - background-image: url('../images/client-icons/element.svg'); -} - -.ClientView .icon.weechat { - background-image: url('../images/client-icons/weechat.svg'); + overflow: hidden; + display: block; } .ClientView .actions a.badge { diff --git a/src/open/ClientView.js b/src/open/ClientView.js index e0ab4d8..ba959f1 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -34,7 +34,7 @@ export class ClientView extends TemplateView { t.p(vm.description), t.p(formatPlatforms(vm.availableOnPlatformNames)), ]), - t.div({className: `icon ${vm.clientId}`}) + t.img({className: "clientIcon", src: vm.iconUrl}) ]), t.mapView(vm => vm.stage, stage => { switch (stage) { diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index b2a98ce..80a3717 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -90,6 +90,10 @@ export class ClientViewModel extends ViewModel { return this._client.id; } + get iconUrl() { + return this._client.icon; + } + get stage() { return this._showOpen ? "open" : "install"; } diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index a489927..9fa3f58 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -21,8 +21,7 @@ import {Maturity, Platform, LinkKind, * Information on how to deep link to a given matrix client. */ export class Element { - /* should only contain alphanumerical and -_, no dots (needs to be usable as css class) */ - get id() { return "element-io"; } + get id() { return "element.io"; } get platforms() { return [ @@ -32,6 +31,10 @@ export class Element { ]; } + get icon() { + return "images/client-icons/element.svg"; + } + get appleAssociatedAppId() { return "7J4U792NQT.im.vector.app"; } get description() { return 'Fully-featured Matrix client, used by millions.'; } diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js index 301ebe1..15229b2 100644 --- a/src/open/clients/Weechat.js +++ b/src/open/clients/Weechat.js @@ -23,6 +23,7 @@ export class Weechat { /* should only contain alphanumerical and -_, no dots (needs to be usable as css class) */ get id() { return "weechat"; } getName(platform) { 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]; } @@ -39,4 +40,4 @@ export class Weechat { } getInstallLinks(platform) {} -} \ No newline at end of file +} From 920a95296cf84567fe631d779bac42c3b803ce2f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 16:38:10 +0100 Subject: [PATCH 065/101] basic handling of event and group preview --- src/Link.js | 40 +++++++------------------ src/preview/HomeServer.js | 4 --- src/preview/PreviewViewModel.js | 53 ++++++++++++++++++--------------- 3 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/Link.js b/src/Link.js index 1ccc4d0..7d7b148 100644 --- a/src/Link.js +++ b/src/Link.js @@ -19,9 +19,8 @@ import {orderedUnique} from "./utils/unique.js"; const ROOMALIAS_PATTERN = /^#([^:]*):(.+)$/; const ROOMID_PATTERN = /^!([^:]*):(.+)$/; -const EVENT_WITH_ROOMID_PATTERN = /^[!]([^:]*):(.+)\/\$([^:]+):(.+)$/; -const EVENT_WITH_ROOMALIAS_PATTERN = /^[#]([^:]*):(.+)\/\$([^:]+):(.+)$/; const USERID_PATTERN = /^@([^:]+):(.+)$/; +const EVENTID_PATTERN = /^$([^:]+):(.+)$/; const GROUPID_PATTERN = /^\+([^:]+):(.+)$/; export const IdentifierKind = createEnum( @@ -71,7 +70,7 @@ export class Link { if (!fragment) { return null; } - let [identifier, queryParams] = fragment.split("?"); + let [linkStr, queryParams] = fragment.split("?"); let viaServers = []; if (queryParams) { @@ -81,29 +80,13 @@ export class Link { .map(([,value]) => value); } - if (identifier.startsWith("#/")) { - identifier = identifier.substr(2); + if (linkStr.startsWith("#/")) { + linkStr = linkStr.substr(2); } - let kind; + const [identifier, eventId] = linkStr.split("/"); + let matches; - // longest first, so they dont get caught by ROOMALIAS_PATTERN and ROOMID_PATTERN - matches = EVENT_WITH_ROOMID_PATTERN.exec(identifier); - if (matches) { - const roomServer = matches[2]; - const messageServer = matches[4]; - const roomLocalPart = matches[1]; - const messageLocalPart = matches[3]; - return new Link(viaServers, IdentifierKind.RoomId, roomLocalPart, roomServer, messageLocalPart, messageServer); - } - matches = EVENT_WITH_ROOMALIAS_PATTERN.exec(identifier); - if (matches) { - const roomServer = matches[2]; - const messageServer = matches[4]; - const roomLocalPart = matches[1]; - const messageLocalPart = matches[3]; - return new Link(viaServers, IdentifierKind.RoomAlias, roomLocalPart, roomServer, messageLocalPart, messageServer); - } matches = USERID_PATTERN.exec(identifier); if (matches) { const server = matches[2]; @@ -114,13 +97,13 @@ export class Link { if (matches) { const server = matches[2]; const localPart = matches[1]; - return new Link(viaServers, IdentifierKind.RoomAlias, localPart, server); + return new Link(viaServers, IdentifierKind.RoomAlias, localPart, server, eventId); } matches = ROOMID_PATTERN.exec(identifier); if (matches) { const server = matches[2]; const localPart = matches[1]; - return new Link(viaServers, IdentifierKind.RoomId, localPart, server); + return new Link(viaServers, IdentifierKind.RoomId, localPart, server, eventId); } matches = GROUPID_PATTERN.exec(identifier); if (matches) { @@ -131,16 +114,13 @@ export class Link { return null; } - constructor(viaServers, identifierKind, localPart, server, messageLocalPart = null, messageServer = null) { + constructor(viaServers, identifierKind, localPart, server, eventId) { const servers = [server]; - if (messageServer) { - servers.push(messageServer); - } servers.push(...viaServers); this.servers = orderedUnique(servers); this.identifierKind = identifierKind; this.identifier = `${asPrefix(identifierKind)}${localPart}:${server}`; - this.eventId = messageLocalPart ? `$${messageLocalPart}:${messageServer}` : null; + this.eventId = eventId; } get kind() { diff --git a/src/preview/HomeServer.js b/src/preview/HomeServer.js index 7736e8c..d0c19dd 100644 --- a/src/preview/HomeServer.js +++ b/src/preview/HomeServer.js @@ -47,10 +47,6 @@ export class HomeServer { return body; } - getGroupProfile(groupId) { - //`/_matrix/client/r0/groups/${groupId}/profile` - } - 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") { diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 03c72f4..d025782 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -37,32 +37,37 @@ export class PreviewViewModel extends ViewModel { } async load() { - 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: - 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; - } - } + 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._setNoPreview(this._link); this.loading = false; - if (this._consentedServers.length) { + this._setNoPreview(this._link); + if (this._consentedServers.length && supportsPreview) { this.domain = this._consentedServers[this._consentedServers.length - 1]; this.failed = true; } From f538be6748668983147e0a0705691e08847fa762 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 17:16:44 +0100 Subject: [PATCH 066/101] render back button in client list as left chevron --- css/client.css | 20 ++++++++++++++++++++ images/chevron-left.svg | 10 ++++++++++ src/open/ClientListView.js | 5 +++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 images/chevron-left.svg diff --git a/css/client.css b/css/client.css index 6aa01ef..ec2f251 100644 --- a/css/client.css +++ b/css/client.css @@ -2,6 +2,25 @@ padding: 16px 0; } +.ClientListView > h2 { + display: flex; + align-items: center; +} + +.ClientListView > h2 .back { + background: url('../images/chevron-left.svg'); + background-color: none; + background-size: 40%; + background-repeat: no-repeat; + background-position: center; + border: none; + width: 24px; + height: 24px; + padding: 16px; + cursor: pointer; + margin-right: 8px; +} + .ClientView { border: 1px solid #E6E6E6; border-radius: 8px; @@ -29,6 +48,7 @@ height: 60px; overflow: hidden; display: block; + margin-left: 8px; } .ClientView .actions a.badge { diff --git a/images/chevron-left.svg b/images/chevron-left.svg new file mode 100644 index 0000000..27f4dce --- /dev/null +++ b/images/chevron-left.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/open/ClientListView.js b/src/open/ClientListView.js index 25fce93..7d91f4a 100644 --- a/src/open/ClientListView.js +++ b/src/open/ClientListView.js @@ -62,10 +62,11 @@ class AllClientsView extends TemplateView { class ContinueWithClientView extends TemplateView { render(t, vm) { + const backTitle = "Back to all clients"; return t.div({className: "ClientListView"}, [ t.h2([ - `Continue with ${vm.clientViewModel.name} `, - t.button({onClick: () => vm.showAll()}, "Back") + t.button({className: "back", ["aria-label"]: backTitle, title: backTitle, onClick: () => vm.showAll()}), + t.span(`Continue with ${vm.clientViewModel.name}`) ]), t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel))) ]); From 7333fba6a1ce041f24b2f170b2fa1d2af0b24e31 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 17:17:04 +0100 Subject: [PATCH 067/101] fix member count padding --- css/preview.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/preview.css b/css/preview.css index b2f7b3f..2921850 100644 --- a/css/preview.css +++ b/css/preview.css @@ -61,7 +61,7 @@ } .PreviewView .memberCount p:not(.placeholder) { - padding: 0 4px 0 24px; + padding: 4px 4px 4px 24px; border-radius: 8px; background-image: url(../images/member-icon.svg); background-repeat: no-repeat; From f42820e4ba70118c32c27e03c4dbf9cdc50d53ad Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 18:28:40 +0100 Subject: [PATCH 068/101] add copy invite button to text clients --- src/create/CreateLinkView.js | 28 ++-------------------- src/open/ClientView.js | 13 +++++------ src/open/ClientViewModel.js | 19 ++++----------- src/open/clients/Element.js | 1 + src/open/clients/Weechat.js | 7 ++++++ src/utils/copy.js | 45 ++++++++++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 src/utils/copy.js diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js index 3dcebcd..0dd5bc6 100644 --- a/src/create/CreateLinkView.js +++ b/src/create/CreateLinkView.js @@ -16,31 +16,7 @@ limitations under the License. import {TemplateView} from "../utils/TemplateView.js"; import {PreviewView} from "../preview/PreviewView.js"; - -function selectNode(node) { - let selection = window.getSelection(); - selection.removeAllRanges(); - let range = document.createRange(); - range.selectNode(node); - selection.addRange(range); -} - -function copyButton(t, copyNode, label, classNames) { - return t.button({className: `${classNames} icon copy`, onClick: evt => { - const button = evt.target; - selectNode(copyNode); - if (document.execCommand("copy")) { - 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); -} +import {copyButton} from "../utils/copy.js"; export class CreateLinkView extends TemplateView { render(t, vm) { @@ -53,7 +29,7 @@ export class CreateLinkView extends TemplateView { t.mapView(vm => vm.previewViewModel, childVM => childVM ? new PreviewView(childVM) : null), t.div({className: {hidden: vm => !vm.linkUrl}}, [ t.h2(link), - t.div(copyButton(t, link, "Copy link", "fullwidth primary")), + t.div(copyButton(t, () => vm.linkUrl, "Copy link", "fullwidth primary")), t.div(t.button({className: "secondary fullwidth", onClick: () => this._clear()}, "Or create another link")), ]), t.form({action: "#", onSubmit: evt => this._onSubmit(evt), className: {hidden: vm => vm.linkUrl}}, [ diff --git a/src/open/ClientView.js b/src/open/ClientView.js index ba959f1..696a011 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -15,6 +15,8 @@ limitations under the License. */ import {TemplateView} from "../utils/TemplateView.js"; +import {copyButton} from "../utils/copy.js"; +import {text} from "../utils/html.js"; function formatPlatforms(platforms) { return platforms.reduce((str, p, i, all) => { @@ -60,17 +62,14 @@ class OpenClientView extends TemplateView { } class InstallClientView extends TemplateView { - - copyToClipboard() { - - } - render(t, vm) { const children = []; if (vm.textInstructions) { - const copy = t.button({className: "primary", onClick: evt => this.copyToClipboard(evt)}, "Copy"); - children.push(t.p([vm.textInstructions, copy])); + children.push(t.p({}, vm.textInstructions)); + if (vm.copyString) { + children.push(t.p(copyButton(t, () => vm.copyString, "Copy invite", "fullwidth primary"))); + } } const actions = t.div({className: "actions"}, vm.actions.map(a => { diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index 80a3717..b47ce69 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -16,6 +16,7 @@ limitations under the License. import {isWebPlatform, isDesktopPlatform, Platform} from "../Platform.js"; import {ViewModel} from "../utils/ViewModel.js"; +import {IdentifierKind} from "../Link.js"; export class ClientViewModel extends ViewModel { constructor(options) { @@ -102,6 +103,10 @@ export class ClientViewModel extends ViewModel { return this._client.getLinkInstructions(this._proposedPlatform, this._link); } + get copyString() { + return this._client.getCopyString(this._proposedPlatform, this._link); + } + get showDeepLinkInInstall() { return this._clientCanIntercept && this.deepLink; } @@ -137,17 +142,3 @@ export class ClientViewModel extends ViewModel { } } } - -/* -if (this._preferredClient.getLinkSupport(this.preferences.platform, this._link)) { - const deepLink = this._preferredClient.getDeepLink(this.preferences.platform, this._link); - this.openLink(deepLink); - const protocol = new URL(deepLink).protocol; - const isWebProtocol = protocol === "http:" || protocol === "https:"; - if (!isWebProtocol) { - this.missingClientViewModel = new ClientViewModel(this.childOptions({client: this._preferredClient, link: this._link})); - } - } else { - this.acceptInstructions = this._preferredClient.getLinkInstructions(this.preferences.platform, this._link); - } - */ diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 9fa3f58..8811361 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -70,6 +70,7 @@ export class Element { } getLinkInstructions(platform, link) {} + getCopyString(platform, link) {} getName(platform) { if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb) { diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js index 15229b2..8e66559 100644 --- a/src/open/clients/Weechat.js +++ b/src/open/clients/Weechat.js @@ -39,5 +39,12 @@ export class Weechat { } } + 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/utils/copy.js b/src/utils/copy.js new file mode 100644 index 0000000..1a0773c --- /dev/null +++ b/src/utils/copy.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. +*/ + +function selectNode(node) { + const selection = window.getSelection(); + selection.removeAllRanges(); + const range = document.createRange(); + range.selectNode(node); + selection.addRange(range); +} + +export function copyButton(t, getCopyText, label, classNames) { + return t.button({className: `${classNames} icon copy`, onClick: evt => { + const button = evt.target; + const text = getCopyText(); + const span = document.createElement("span"); + span.innerText = text; + button.parentElement.appendChild(span); + selectNode(span); + if (document.execCommand("copy")) { + button.innerText = "Copied!"; + button.classList.remove("copy"); + button.classList.add("tick"); + setTimeout(() => { + button.classList.remove("tick"); + button.classList.add("copy"); + button.innerText = label; + }, 2000); + } + span.parentElement.removeChild(span); + }}, label); +} From 7b5796ce0340a616d2183de2ead0dcab5e8e3a0e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 18:50:07 +0100 Subject: [PATCH 069/101] always show default avatar if none available --- src/preview/PreviewViewModel.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index d025782..5c340eb 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -20,6 +20,8 @@ import {resolveServer} from "./HomeServer.js"; import {ClientListViewModel} from "../open/ClientListViewModel.js"; import {ClientViewModel} from "../open/ClientViewModel.js"; +const DEFAULT_AVATAR = "images/chat-icon.svg"; + export class PreviewViewModel extends ViewModel { constructor(options) { super(options); @@ -82,7 +84,7 @@ export class PreviewViewModel extends ViewModel { this.name = profile.displayname || userId; this.avatarUrl = profile.avatar_url ? homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") : - null; + DEFAULT_AVATAR; this.identifier = userId; } @@ -99,7 +101,7 @@ export class PreviewViewModel extends ViewModel { this.name = publicRoom?.name || publicRoom?.canonical_alias || link.identifier; this.avatarUrl = publicRoom?.avatar_url ? homeserver.mxcUrlThumbnail(publicRoom.avatar_url, 64, 64, "crop") : - null; + DEFAULT_AVATAR; this.memberCount = publicRoom?.num_joined_members; this.topic = publicRoom?.topic; this.identifier = publicRoom?.canonical_alias || link.identifier; @@ -111,6 +113,6 @@ export class PreviewViewModel extends ViewModel { _setNoPreview(link) { this.name = link.identifier; this.identifier = null; - this.avatarUrl = "images/chat-icon.svg"; + this.avatarUrl = DEFAULT_AVATAR; } } From 0c68a701305e5fbc81cf107c6df4032c896bde86 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 18:50:27 +0100 Subject: [PATCH 070/101] buttons have hands --- css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/main.css b/css/main.css index 4f149ef..1502763 100644 --- a/css/main.css +++ b/css/main.css @@ -61,6 +61,10 @@ textarea { font-style: normal; } +button { + cursor: pointer; +} + button, input { font-size: inherit; font-weight: inherit; From e5743471f44cda00330f25677433e867c5dd2000 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 19:34:44 +0100 Subject: [PATCH 071/101] add Nheko and Fractal --- images/client-icons/fractal.png | Bin 0 -> 8796 bytes images/client-icons/nheko.svg | 155 ++++++++++++++++++++++++++++++++ images/flathub-badge.svg | 62 +++++++++++++ src/open/clients/Fractal.js | 51 +++++++++++ src/open/clients/Nheko.js | 53 +++++++++++ src/open/types.js | 19 +++- 6 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 images/client-icons/fractal.png create mode 100644 images/client-icons/nheko.svg create mode 100644 images/flathub-badge.svg create mode 100644 src/open/clients/Fractal.js create mode 100644 src/open/clients/Nheko.js diff --git a/images/client-icons/fractal.png b/images/client-icons/fractal.png new file mode 100644 index 0000000000000000000000000000000000000000..e60c89c98878256859f41e0667183cccf6de2d68 GIT binary patch literal 8796 zcmd^Fg3>&}Q^f&iJ*%ZTq##VN7b{i?5P+BY#riZQA=i5ssNS^OL*g=$E5SN(nfnSu^ z6`_};o~46QwT6vp0kNASAnYvpeDM$0!Maj?6-VjX##3)1tM)Rv;I6T^`cZmqc0^jlasL(U6@^HoHI&{WQS2Y=_BLQKNK&yuTI zxL~8Wnfli(^9Z+vlK58f@58vrWOW%Y8sQ8UVqSFX0()xpi@v`jP%{0)F_r!pkj~63 zCF@aB-B%)~&nC4TCeJSa*40q^3bUk4`}+A5GOZjmt(QDg!{mx}icRGCY-tUDJ)x(k zj-RgoY=nDVd!%5Ob_YyM|EKxGDVL{kLE$rLoJHrvYQhPv<3N!~h;iKEyXf3%caJ|& zb?uTHfwoRxgbRZ8wckrl5M=MOoQ!3$iYQ*NzYW0)E+iZ~?=z<2Ke^n`OxqO(9+?Zp zsfAEgWrIBrmjsqWb@(s*tsy1Ey{kX$Nju22R&^ANIsE+DrNFT@&je&YP~@*ZH0qt( z`=fYwS2X0cxPJ>(X_M>>6)mL8!0eM`vue~(+K%VelkH2|v20VQbj}GRr9+GSennT8oH>aEc4ISW6XTA%rw z2reC3h~>z5gkt)lsF&mG+e7yjYZfHt?wWLXx~ zvWzVk``~8aO7RXD>?_>Upp20I?p!uoEe|y@f9JG+e7ekFW z8x+?DWwPFzVl0lTt0lgW$#YmwhU^JRmM~fx1Vzok2uQ@ zmuw<^IoDyPF+KkN4iJT5ML5dz1uRO&pqAfm(Hz2CytQBJg54MEA&W_F8y5oYkkkG1_*2-2K8fkvI z)x5=zPoaP)v<;4x06ysOH;XV|4ZsqGG||};c%wEX=SCo0Vj5K+bx|^&7(h?W?8x2{ z<;2wP-MRjqQ8OzHWvSUxwLM)y@-1d_*93)lb3u$sTav0NMhz~ReU(J&-^p*~!Tsyc zl`8_4CdpBmL4S$Mx6>v_PY%$ON8jX`lI^Wr*AsIF7;yj8jojX4&NsGS!cl|>>%Z3L z*`5GCN($w+S%cVMuB-w2dLqur8((F^E786NA(qmLhL!9zb^aelN9qc5S9uQMfn zre(`G53mz-}$s9aOE`zuc=5B(VfwkGLGY9wzNQH$|E8;$NYjeZH zlE^#4lL>)seN@T%#k-3eF*Y;wSp)pES1bkI!dJ>O1H_{QaC?zr9xQGY&ek>He=+riCma9ZC@`SO zJ-h3USG8E_!gceNJ*ivXe&27wMArdY*MI)Cnsi|W*BNFlZXl?>mEr>3%)q;%+d1AK zf$XJThG0;O+c3?nL4Zw{8ZkS2f_|I){84{R6>)iQoMQi}^9#oGmie%69?UQalN z-x!fas5e$%O`F-!Z-V%~CyD|c66+OdZ~q}cR$4ghO7yY(0@!?v z{kNk_lXzv>rqq!q$NiFFxHTYe|60Lk5=-4Fqb~np#O?^rapTUFkRZ(A@c~8C+137U z2rfrg%{$RgF5AuG+buotsxFF3`LEG+lQYWO>AcFRkYOv4pXF*Yl3y34K*Ji zYcigGSmqhM4m&PR#>q0_W4T6GFs(nJz&a5v6X47Bg~y%( zQ0ji2*)FW*AdPKlm}_@#ppXfo8x8cw+1Npb^o;D?i>Yw*zvth%+BL0ZYm-0pX8QoB zcpN{Rudx3c5}1Rz4^aQx6S(n*{hn{2#$QfTc_UE(z%bHjQU8!4XxQs_9Z%VE0`|NG z{gR{xRr-E`?3tL3!Yltf-OzRTrACMNP9J-+)UJAaDSX(r7D(}lO(HADR3+9I+~1_R zsA9kNJN!WRUw=62FNxU307aa91*f~7=hMCLWHHbTFwHfMHTvsij!~#GIocIov8;6R zy>LN+DrV8|l2R?|8A>7o(WrYF>dwZFGW;)9Fb_`y#=UUTgfdN=EuFB|mO*UJu}1rV zG;HAA@_cs6BOv?*wVw2ks`{Gk`L+$GQgK*xcBgme*x6 zrEclzqq;~CZ8F8yXkLZHZzLt^q(f;MVVD8s{TIr~)?RRinx}Nzz68 zH?IoV-k{swyGz|vYXh(`q1M1(z}d*cko`ZHe4dFVtRpOJCFE-CV4M1E_4xCgn_d@b zV*JHdvyRyAh(1&K^-de!Qi8({D7;ob^~v~hRB+qto3zX5w!s18BU^| zFbr`T2b6Y<2tZQmsQ)$sH zm1-q6052Z%5=oS+ZkK-Lc*fe&EE*#nvMVoro)B}BsWc6t{3ovS4b#b+v@n!N7n;40 z0OLAGx(9OraAC-HJ+xHdw3#s-hl^)|^pKZpYMB7-4^7N3+?IQu;nOmUu8eR(_X4`c zS~~U1r9M=pY|!BSTJoBo7ywX}@l2@kFTGr?F1=Y2{__DGB3T^9%*9|hVj!2lPcGN_ za0-3t!avT`kYe@44hUdmq^|Nus_5V%4)PNRy^xp9I z(G@&ne?&TIGJ`1K3to5edaks3`s0fcQ=P%INVD+o1<0wlgSsbg26V8wdESO7S(r^u&DS7H5|AFqf3-thzz#g%yqLMIBhKIF{3b~T2b@w zz7NB%S8yTC1QC`msHyh!pAwlxF>?!*gYuz~(_WJ1ZGSJkw3>PF=4IlD$K5GP+{lSQ z23xJ(n*WxGys0QS9dVDF!RMIx{0#c0e3q8K<`(HTkT#cNM^sCPK0UdH2nUDXzT%LcK{Yuw{<}e7?`0*Mp!vJXuCKokbB^@cBDI^# zL`WfBuVy0;L2Dba>NDS^gQ+I0%_K4K*BpK@A+;idpY0@mKo<%3|cRZ9L6l(r=W+1(j#xbwh>NCiiV?Z zFdJKFYj_cVCPK%il2HslRQJSn>MGAkPLA7a7qT)q$sX|=gDS_RkM;ck(>vXNC|C<9 zu_xWS)1MqU7j$I(_uUBY*YWWxoO7AIX`^y5ws(fmbw;$CZylY~alsYjzD1gXA4bF*BHtSrLU&BWfyFrXd~2iXm3do?GX3;`ZmUZKp+2A91u0Z zgD6Q6=4(t_g!qLH^Tn^u*-G~)7pNSIYn>s6sQP}@w78E5MuaUXmTy$UB zAo_w6m}d8qwCn_rSjq8{c<88Og|NxpmHEK@cV>^ke9)wNOMD7p)8Mc&0>*IK;gvZ) zLlzO_l$7hTu3?O>nx7pgB4lfIQMX2?opZObL|=!=Jj$mbf8TpFih$WCRe#=`_dIwo!Er zTxRib-h}SFCx4d|mLZ9l5?Sx&2{gJh(f`0>+9V_6)UD!V2!PyB1?T%xttIKjsLq#P zd=E(TSHtdfNGFK!!Ogs1Odsrkpns`cdTl*&jAFZY(W&72?zT<*gXCbbaju7V&eJpO z?H@xbOVX5rJU1_y$en5`Vj2$fdnJ5)5P7qlVR5RFMM{z!ho1zMQnyWKaM068ZLGs< z8yEy?eYB)7%fl z*=wrl3EHlNI%B}*!#(7^Ht?U_gA=xRrdmflF476-FhPmf zE8mo%@yN|&nn6db;nv3Usx_#927lA|w;h1ni;znhbaGY#Rn&+GG&YRGaqeiu3 zol8|!OTeaRe7&1>jLn0B++FB}#GN<01i z^+&(H%Qw%fE#-VHagodL_LN}^ueyQ!m!dBs^p<6!InEi#!LP6rct`eef+}ukcWPH- zx-{pwKh>zPo))pz=>L^6)M8vMv?iu`j*dNeZRDWdeIgH_=I_e~7 z`N`RWs(N2Sf<>smm{}CK(a{PYc^3sG3#rlf#95&KPWfwvZtQsT?iRC>8Aqu%0yTPR z_Wt39$YfG9xP>||{QEM_faE(%Rt$l|`V+XohXntVB^~1Gp-LVB22AKf)6&ZD)z^O$ zO?j3Gze|f(lCR60VRcAG-PRoG4RqYWFoTqf9i0_A;8XaAyK{260D7n7b56P=5%f+e zqRb{pbqO1Vpr%H$6rfc;S=Yu3n(0`VXW9unj*-sM&@-?2A9Ap`Uk^7g!D@kVwn`1^ zL_2v49-ETbkoic zy@OdjIpAfFlx*Z!5bf`%wd@K*ySJ+it6gZc^rQ{-qD5x7$?O&!%m%mK(%TrzDq9A7 ziQEh@K5@I$F+|g-3TL|Ug7;v1?cfL76E`%2q}s94(Rn1zN2pmK2caw2|7am-K#os!w_wGxV=U)8{y0;l`A!c!ez3IPw`C7DGO+$fr@u${2Kc(t$IoS>f`qvVGzu zu89LnmB3}-ScxTK0bOj8c(-Tqd zTZByZ!uzw-S;m|CoLo3ccQl$sd*O@9{h}P*FXh(F8^@Jc%uR4cntT;TbN_pR^V;uB zTi@N3@Ao4dTRGRa=AcQYb0Ly_yO=DY!R1z@lC%< z%Jc*Sf=<$%24W$%?@VVQS3PaZsR68w<{bma1*sW`{;y!Z{Ek(jGHoE$?3|n0uIoyh z=iI^*oZ7;+7K3(-iGLV>K9Ip`&*p-!0L^GPNqV_-msknN zst0=sef!T7PAZ~bR)cj<5(=bR1EkKXM+2>2<2ij!baRUU5+nMBJDc8NYfS5IVDzn$ zB)+Dl?1MmF3YCUVQq-m1cSX+%n#`0QoYV*XUTg`p#uEB_lt<#~Vm5_y6i2j8#TFQCuHnpMz<7HC6ZIJ&zG7*1_&W8@QH5 zl{7r6wbALG2tJMO z`{Vb7(a8L7bG`O@vh|cJKYu2Td=1gr!o;r=`X|*wgaixSZW$EZInL0h?Jiw*8VyiN ze+ZJhDfh>k@tM*At*%4{z@CJLC^Oxdye(T}9UUCV*q`_%MoS&_S?+6yNficucq;Ri zh-?r~=dTFVT>y2yC%jTV(MRNT-t%ac+sG5P7 z`9aG{97f;|4DC*a^%XsobCG=h*;pR+r^+i zoW#6XqS$3I-nTSn)j3%Avm%U_!(D#jn(;flT?U-a8(H%CaC z+ucV3@WUch{Q-48^+$#LE(OR+!b)xZ!(%S42zIcGz<;&hSaTOC7bssUFOS}T9{VD3 z-r?1yg)zGQ)=OM+216Uv4iflZ2HBf%!4W1kV??pT9B&KV5ey=9P|8#v(L`EzcZT#V z3^;>%bd27aLcA7k!cr=%l+-19w2tDut{PjEn@zxjm; z&^=cjA9}-;BbTH4W%Sgd-%DDE%v-#fG_dK=&H8ERTUENCQW3#VGG% z{h6&^yX*mubn*d?LD6NLRIy%1MfQeyAqS;8Pgf2-um1vkoeEy+&XTzM+(Y`$&u3E; zVjMqyaS2UgP?gNYA@ZIqy#6v64iM)1>Y$+d6#+udk{O1$&Az{M!Ud^Xe%E&abKGc; z#g+pGBDlcvyZ@B~;h1quei|ZxoQaxtpmTG_3hHsx3mh>ReLM?rs`{wK3G7M*vzwO&w&QpMcq# zN3FsnJj94SP?LMK4Bjab;+I?xq+tT+qKe`+Nh>~V#mCVrA(7Vn+dUQw;uH1S%IqJ- zXNgZxlRRF$-Z&*XTGW`*(g4Os7NP@{&IN4KD1cbt1H5MMnGoC2FEgpNT6yd&GN0iC z$-@Y~YZ;*Uo8hGI@XG#aZ!q8?g+1V5J|n<$!~uR#SnsTk&&%YH04Ec4OfHD}U<@_- z^~`|5{>g7Wx2Lb6ac}znJx+Bj3osL6qHBRPPf8zcnovh(+_}n&>OblZZ3*w7`jXlR zZY>{GQ&w{#k6GqmgS5ay+}Joo+wplQ#pHVr49|oA)hE=P9KVZ6EgS4n7ah5w>Gr{m z_wlCY)dyr9UzXpIRNQ0D*8>+hl2W5j0(~I1Hs7>;5ZU?$oCHBrQc#z#k+q2YKPw#> A`v3p{ literal 0 HcmV?d00001 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/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/src/open/clients/Fractal.js b/src/open/clients/Fractal.js new file mode 100644 index 0000000..be51744 --- /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"; } + getName(platform) { 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..064f6bd --- /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} from "../types.js"; + +/** + * Information on how to deep link to a given matrix client. + */ +export class Nheko { + get id() { return "nheko"; } + getName(platform) { 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 /invite ${link.identifier}`; + case LinkKind.Room: return `Type /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/types.js b/src/open/types.js index 443d769..52472d6 100644 --- a/src/open/types.js +++ b/src/open/types.js @@ -74,6 +74,23 @@ export class FDroidLink { } } +export class FlathubLink { + constructor(appId) { + this._appId = appId; + } + + createInstallURL(link) { + return `https://flathub.org/apps/details/${encodeURIComponent(this._appId)}`; + } + + get channelId() { + return "flathub"; + } + + get description() { + return "Get it on Flathub"; + } +} export class WebsiteLink { constructor(url) { @@ -91,4 +108,4 @@ export class WebsiteLink { get description() { return `Download from ${new URL(this._url).hostname}`; } -} \ No newline at end of file +} From 166f5ded77dd578cfe349243564ab5f25a593998 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 19:35:01 +0100 Subject: [PATCH 072/101] improve text instructions and copy button --- css/client.css | 21 +++++++++++++++++++++ src/open/ClientListViewModel.js | 3 ++- src/open/ClientView.js | 19 ++++++++++++++----- src/open/ClientViewModel.js | 16 +++++++--------- src/open/clients/Weechat.js | 1 - src/open/clients/index.js | 6 +++++- src/utils/copy.js | 18 +++++++++++------- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/css/client.css b/css/client.css index ec2f251..f045851 100644 --- a/css/client.css +++ b/css/client.css @@ -60,3 +60,24 @@ .ClientView .actions img { height: 100%; } + +.InstallClientView .instructions button { + background-repeat: no-repeat; + background-position: center; + background-color: var(--link); + padding: 4px; + border-radius: 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.svg'); +} diff --git a/src/open/ClientListViewModel.js b/src/open/ClientListViewModel.js index 2d34166..96d2737 100644 --- a/src/open/ClientListViewModel.js +++ b/src/open/ClientListViewModel.js @@ -55,7 +55,8 @@ export class ClientListViewModel extends ViewModel { _filterClients() { this.clientList = this._clients.filter(client => { - const isStable = this.platforms.map(p => client.getMaturity(p)).includes(Maturity.Stable); + 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; diff --git a/src/open/ClientView.js b/src/open/ClientView.js index 696a011..dee2ec4 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -15,7 +15,7 @@ limitations under the License. */ import {TemplateView} from "../utils/TemplateView.js"; -import {copyButton} from "../utils/copy.js"; +import {copy} from "../utils/copy.js"; import {text} from "../utils/html.js"; function formatPlatforms(platforms) { @@ -66,10 +66,18 @@ class InstallClientView extends TemplateView { const children = []; if (vm.textInstructions) { - children.push(t.p({}, vm.textInstructions)); - if (vm.copyString) { - children.push(t.p(copyButton(t, () => vm.copyString, "Copy invite", "fullwidth primary"))); - } + const copyButton = t.button({ + className: "copy", + onClick: evt => { + if (copy(vm.copyString, copyButton.parentElement)) { + copyButton.className = "tick"; + setTimeout(() => { + copyButton.className = "copy"; + }, 2000); + } + } + }); + children.push(t.p({className: "instructions"}, [vm.textInstructions, copyButton])); } const actions = t.div({className: "actions"}, vm.actions.map(a => { @@ -78,6 +86,7 @@ class InstallClientView extends TemplateView { 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, diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index b47ce69..d75b45c 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -67,15 +67,13 @@ export class ClientViewModel extends ViewModel { }); } } - if (actions.length === 0) { - actions.push({ - label: `Visit app homepage`, - url: client.homepage, - primary: true, - kind: "homepage", - activated: () => {}, - }); - } + actions.push({ + label: `Visit app homepage`, + url: client.homepage, + primary: true, + kind: "homepage", + activated: () => {}, + }); return actions; } diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js index 8e66559..2a018b7 100644 --- a/src/open/clients/Weechat.js +++ b/src/open/clients/Weechat.js @@ -20,7 +20,6 @@ import {Maturity, Platform, LinkKind, WebsiteLink} from "../types.js"; * Information on how to deep link to a given matrix client. */ export class Weechat { - /* should only contain alphanumerical and -_, no dots (needs to be usable as css class) */ get id() { return "weechat"; } getName(platform) { return "Weechat"; } get icon() { return "images/client-icons/weechat.svg"; } diff --git a/src/open/clients/index.js b/src/open/clients/index.js index a939543..657982e 100644 --- a/src/open/clients/index.js +++ b/src/open/clients/index.js @@ -16,10 +16,14 @@ limitations under the License. import {Element} from "./Element.js"; import {Weechat} from "./Weechat.js"; +import {Nheko} from "./Nheko.js"; +import {Fractal} from "./Fractal.js"; export function createClients() { return [ new Element(), new Weechat(), + new Nheko(), + new Fractal(), ]; -} \ No newline at end of file +} diff --git a/src/utils/copy.js b/src/utils/copy.js index 1a0773c..c417bfd 100644 --- a/src/utils/copy.js +++ b/src/utils/copy.js @@ -22,15 +22,20 @@ function 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; - const text = getCopyText(); - const span = document.createElement("span"); - span.innerText = text; - button.parentElement.appendChild(span); - selectNode(span); - if (document.execCommand("copy")) { + if (copy(getCopyText(), button)) { button.innerText = "Copied!"; button.classList.remove("copy"); button.classList.add("tick"); @@ -40,6 +45,5 @@ export function copyButton(t, getCopyText, label, classNames) { button.innerText = label; }, 2000); } - span.parentElement.removeChild(span); }}, label); } From b5daf8fe0aa540f4e879182d5114a6111c1f1abf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 13:34:22 +0100 Subject: [PATCH 073/101] remove chevron, no homepage button for element and add change client --- css/client.css | 19 ---------- images/chevron-left.svg | 10 ----- src/open/ClientListView.js | 1 - src/open/ClientListViewModel.js | 1 + src/open/ClientView.js | 9 ++++- src/open/ClientViewModel.js | 66 ++++++++++++++++++++++++++------- src/open/clients/Element.js | 2 +- src/utils/html.js | 2 +- 8 files changed, 62 insertions(+), 48 deletions(-) delete mode 100644 images/chevron-left.svg diff --git a/css/client.css b/css/client.css index f045851..1e05d19 100644 --- a/css/client.css +++ b/css/client.css @@ -2,25 +2,6 @@ padding: 16px 0; } -.ClientListView > h2 { - display: flex; - align-items: center; -} - -.ClientListView > h2 .back { - background: url('../images/chevron-left.svg'); - background-color: none; - background-size: 40%; - background-repeat: no-repeat; - background-position: center; - border: none; - width: 24px; - height: 24px; - padding: 16px; - cursor: pointer; - margin-right: 8px; -} - .ClientView { border: 1px solid #E6E6E6; border-radius: 8px; diff --git a/images/chevron-left.svg b/images/chevron-left.svg deleted file mode 100644 index 27f4dce..0000000 --- a/images/chevron-left.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/open/ClientListView.js b/src/open/ClientListView.js index 7d91f4a..5c69ef8 100644 --- a/src/open/ClientListView.js +++ b/src/open/ClientListView.js @@ -65,7 +65,6 @@ class ContinueWithClientView extends TemplateView { const backTitle = "Back to all clients"; return t.div({className: "ClientListView"}, [ t.h2([ - t.button({className: "back", ["aria-label"]: backTitle, title: backTitle, onClick: () => vm.showAll()}), t.span(`Continue with ${vm.clientViewModel.name}`) ]), t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel))) diff --git a/src/open/ClientListViewModel.js b/src/open/ClientListViewModel.js index 96d2737..df0c166 100644 --- a/src/open/ClientListViewModel.js +++ b/src/open/ClientListViewModel.js @@ -75,6 +75,7 @@ export class ClientListViewModel extends ViewModel { _pickClient(client) { this.clientViewModel = this.clientList.find(vm => vm.clientId === client.id); + this.clientViewModel.pick(this); this.emitChange(); } diff --git a/src/open/ClientView.js b/src/open/ClientView.js index dee2ec4..b3c8c45 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -56,7 +56,12 @@ class OpenClientView extends TemplateView { href: vm.deepLink, rel: "noopener noreferrer", onClick: () => vm.deepLinkActivated(), - }, "Continue") + }, "Continue"), + t.p({className: {previewSource: true, hidden: vm => !vm.showBack}}, [ + `Continue with ${vm.name}.`, + " ", + t.button({className: "text", onClick: () => vm.back()}, "Change"), + ]), ]); } } @@ -77,7 +82,7 @@ class InstallClientView extends TemplateView { } } }); - children.push(t.p({className: "instructions"}, [vm.textInstructions, copyButton])); + children.push(t.p({className: "instructions"}, [t.code(vm.textInstructions), copyButton])); } const actions = t.div({className: "actions"}, vm.actions.map(a => { diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index d75b45c..d8c58d5 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -18,6 +18,14 @@ 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); @@ -25,19 +33,17 @@ export class ClientViewModel extends ViewModel { this._client = client; this._link = link; this._pickClient = pickClient; + // to provide "choose other client" button after calling pick() + this._clientListViewModel = null; - const supportedPlatforms = client.platforms; - const matchingPlatforms = this.platforms.filter(p => { - return supportedPlatforms.includes(p); - }); + 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.name = this._client.getName(this._proposedPlatform); - this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link); this._clientCanIntercept = !!(nativePlatform && client.canInterceptMatrixToLinks(nativePlatform)); this._showOpen = this.deepLink && !this._clientCanIntercept; } @@ -67,13 +73,15 @@ export class ClientViewModel extends ViewModel { }); } } - actions.push({ - label: `Visit app homepage`, - url: client.homepage, - primary: true, - kind: "homepage", - activated: () => {}, - }); + if (client.homepage) { + actions.push({ + label: `Visit app homepage`, + url: client.homepage, + primary: true, + kind: "homepage", + activated: () => {}, + }); + } return actions; } @@ -89,6 +97,10 @@ export class ClientViewModel extends ViewModel { return this._client.id; } + get name() { + return this._client.getName(this._platform); + } + get iconUrl() { return this._client.icon; } @@ -130,6 +142,14 @@ export class ClientViewModel extends ViewModel { } return textPlatforms; } + + get deepLink() { + return this._client.getDeepLink(this._platform, this._link); + } + + get _platform() { + return this.showBack ? this._proposedPlatform : this._nativePlatform; + } deepLinkActivated() { this._pickClient(this._client); @@ -139,4 +159,22 @@ export class ClientViewModel extends ViewModel { this.emitChange(); } } + + pick(clientListViewModel) { + this._clientListViewModel = clientListViewModel; + this.emitChange(); + } + + get showBack() { + return !!this._clientListViewModel; + } + + back() { + if (this._clientListViewModel) { + const vm = this._clientListViewModel; + this._clientListViewModel = null; + this.emitChange(); + vm.showAll(); + } + } } diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 8811361..a467668 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -39,7 +39,7 @@ export class Element { get description() { return 'Fully-featured Matrix client, used by millions.'; } - get homepage() { return "https://element.io"; } + get homepage() { return ; } // prevents a visit app homepage button from appearing get author() { return "Element"; } getMaturity(platform) { return Maturity.Stable; } diff --git a/src/utils/html.js b/src/utils/html.js index c391a00..16607ed 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -95,7 +95,7 @@ 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"], + "pre", "button", "time", "input", "textarea", "label", "form", "progress", "output", "code"], [SVG_NS]: ["svg", "circle"] }; From bc0fb89846097f766d21facf14b8aaf28954b659 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 13:49:15 +0100 Subject: [PATCH 074/101] remove title here --- src/open/ClientListView.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/open/ClientListView.js b/src/open/ClientListView.js index 5c69ef8..4dab154 100644 --- a/src/open/ClientListView.js +++ b/src/open/ClientListView.js @@ -62,11 +62,7 @@ class AllClientsView extends TemplateView { class ContinueWithClientView extends TemplateView { render(t, vm) { - const backTitle = "Back to all clients"; return t.div({className: "ClientListView"}, [ - t.h2([ - t.span(`Continue with ${vm.clientViewModel.name}`) - ]), t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel))) ]); } From bcd1cbc1b100ec5d7b6d64cf692bcd0a5317cfb3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:05:57 +0100 Subject: [PATCH 075/101] allow specifying client in url --- src/Link.js | 24 +++++++++++++++--------- src/open/OpenLinkView.js | 6 +----- src/open/OpenLinkViewModel.js | 17 ++++------------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Link.js b/src/Link.js index 7d7b148..44d769e 100644 --- a/src/Link.js +++ b/src/Link.js @@ -70,14 +70,19 @@ export class Link { if (!fragment) { return null; } - let [linkStr, queryParams] = fragment.split("?"); + let [linkStr, queryParamsStr] = fragment.split("?"); let viaServers = []; - if (queryParams) { - viaServers = queryParams.split("&") - .map(pair => pair.split("=")) + 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("#/")) { @@ -91,36 +96,37 @@ export class Link { if (matches) { const server = matches[2]; const localPart = matches[1]; - return new Link(viaServers, IdentifierKind.UserId, localPart, server); + 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(viaServers, IdentifierKind.RoomAlias, localPart, server, eventId); + 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(viaServers, IdentifierKind.RoomId, localPart, server, eventId); + 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(viaServers, IdentifierKind.GroupId, localPart, server); + return new Link(clientId, viaServers, IdentifierKind.GroupId, localPart, server); } return null; } - constructor(viaServers, identifierKind, localPart, server, eventId) { + 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() { diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index 5c82f57..badcd29 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -34,11 +34,7 @@ class ShowLinkView extends TemplateView { render(t, vm) { return t.div([ t.view(new PreviewView(vm.previewViewModel)), - t.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({ - className: "primary fullwidth", - onClick: () => vm.showClients() - }, vm => vm.showClientsLabel)), - t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), + t.view(new ClientListView(vm.clientsViewModel)), t.p({className: {previewSource: true, hidden: vm => !vm.previewDomain}}, [ vm => vm.previewFailed ? `${vm.previewDomain} has not returned a preview.` : `Preview provided by ${vm.previewDomain}.`, " ", diff --git a/src/open/OpenLinkViewModel.js b/src/open/OpenLinkViewModel.js index f49891c..bd72ff8 100644 --- a/src/open/OpenLinkViewModel.js +++ b/src/open/OpenLinkViewModel.js @@ -49,12 +49,13 @@ export class OpenLinkViewModel extends ViewModel { } async _showLink() { - const preferredClient = this.preferences.clientId ? this._clients.find(c => c.id === this.preferences.clientId) : null; - this.clientsViewModel = preferredClient ? new ClientListViewModel(this.childOptions({ + 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, - })) : null; + })); this.previewViewModel = new PreviewViewModel(this.childOptions({ link: this._link, consentedServers: this.preferences.homeservers @@ -84,14 +85,4 @@ export class OpenLinkViewModel extends ViewModel { this._showServerConsent(); this.emitChange(); } - - showClients() { - if (!this.clientsViewModel) { - this.clientsViewModel = new ClientListViewModel(this.childOptions({ - clients: this._clients, - link: this._link - })); - this.emitChange(); - } - } } From 614388e663ec93b1e4de6b7df96bdfb8d7e1ee4b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:06:16 +0100 Subject: [PATCH 076/101] don't differentiate name between platforms --- src/open/ClientViewModel.js | 11 ++++------- src/open/clients/Element.js | 10 +--------- src/open/clients/Fractal.js | 2 +- src/open/clients/Nheko.js | 2 +- src/open/clients/Weechat.js | 2 +- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index d8c58d5..7c26324 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -66,7 +66,7 @@ export class ClientViewModel extends ViewModel { const webDeepLink = client.getDeepLink(webPlatform, link); if (webDeepLink) { actions.push({ - label: `Or open in ${client.getName(webPlatform)}`, + label: `Or open in web app`, url: webDeepLink, kind: "open-in-web", activated: () => this.preferences.setClient(client.id, webPlatform), @@ -98,7 +98,7 @@ export class ClientViewModel extends ViewModel { } get name() { - return this._client.getName(this._platform); + return this._client.name; } get iconUrl() { @@ -144,11 +144,8 @@ export class ClientViewModel extends ViewModel { } get deepLink() { - return this._client.getDeepLink(this._platform, this._link); - } - - get _platform() { - return this.showBack ? this._proposedPlatform : this._nativePlatform; + const platform = this.showBack ? this._proposedPlatform : this._nativePlatform; + return this._client.getDeepLink(platform, this._link); } deepLinkActivated() { diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index a467668..e793b4d 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -37,6 +37,7 @@ export class Element { get appleAssociatedAppId() { return "7J4U792NQT.im.vector.app"; } + get name() {return "Element"; } get description() { return 'Fully-featured Matrix client, used by millions.'; } get homepage() { return ; } // prevents a visit app homepage button from appearing @@ -71,15 +72,6 @@ export class Element { getLinkInstructions(platform, link) {} getCopyString(platform, link) {} - - getName(platform) { - if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb) { - return "Element Web"; - } else { - return "Element"; - } - } - getInstallLinks(platform) { switch (platform) { case Platform.iOS: return [new AppleStoreLink('vector', 'id1083446067')]; diff --git a/src/open/clients/Fractal.js b/src/open/clients/Fractal.js index be51744..47310af 100644 --- a/src/open/clients/Fractal.js +++ b/src/open/clients/Fractal.js @@ -21,7 +21,7 @@ import {Maturity, Platform, LinkKind, FlathubLink} from "../types.js"; */ export class Fractal { get id() { return "fractal"; } - getName(platform) { 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"; } diff --git a/src/open/clients/Nheko.js b/src/open/clients/Nheko.js index 064f6bd..2f860ee 100644 --- a/src/open/clients/Nheko.js +++ b/src/open/clients/Nheko.js @@ -21,7 +21,7 @@ import {Maturity, Platform, LinkKind, FlathubLink} from "../types.js"; */ export class Nheko { get id() { return "nheko"; } - getName(platform) { 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"; } diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js index 2a018b7..9d37efc 100644 --- a/src/open/clients/Weechat.js +++ b/src/open/clients/Weechat.js @@ -21,7 +21,7 @@ import {Maturity, Platform, LinkKind, WebsiteLink} from "../types.js"; */ export class Weechat { get id() { return "weechat"; } - getName(platform) { 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"; } From 1896fde3a05b9c8821764b4694b7f8e33bdcd992 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:06:30 +0100 Subject: [PATCH 077/101] also add change client link in install client screen --- src/open/ClientView.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/open/ClientView.js b/src/open/ClientView.js index b3c8c45..8859f4b 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -117,6 +117,12 @@ class InstallClientView extends TemplateView { children.push(t.p([`If you already have ${vm.name} installed, you can `, deepLink, "."])) } + children.push(t.p({className: {previewSource: true, hidden: vm => !vm.showBack}}, [ + `Continue with ${vm.name}.`, + " ", + t.button({className: "text", onClick: () => vm.back()}, "Change"), + ])); + return t.div({className: "InstallClientView"}, children); } } From e13c943d3f0ac802974a87c6ff7e2dcf6dcc9d9c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:18:19 +0100 Subject: [PATCH 078/101] show back button also in install client screen --- src/open/ClientView.js | 20 ++++++++++---------- src/open/ClientViewModel.js | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/open/ClientView.js b/src/open/ClientView.js index 8859f4b..7bc9868 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -57,11 +57,7 @@ class OpenClientView extends TemplateView { rel: "noopener noreferrer", onClick: () => vm.deepLinkActivated(), }, "Continue"), - t.p({className: {previewSource: true, hidden: vm => !vm.showBack}}, [ - `Continue with ${vm.name}.`, - " ", - t.button({className: "text", onClick: () => vm.back()}, "Change"), - ]), + showBack(t, vm), ]); } } @@ -117,12 +113,16 @@ class InstallClientView extends TemplateView { children.push(t.p([`If you already have ${vm.name} installed, you can `, deepLink, "."])) } - children.push(t.p({className: {previewSource: true, hidden: vm => !vm.showBack}}, [ - `Continue with ${vm.name}.`, - " ", - t.button({className: "text", onClick: () => vm.back()}, "Change"), - ])); + children.push(showBack(t, vm)); return t.div({className: "InstallClientView"}, children); } } + +function showBack(t, vm) { + return t.p({className: {previewSource: 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 index 7c26324..dd2fb43 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -163,6 +163,7 @@ export class ClientViewModel extends ViewModel { } get showBack() { + // if we're not only showing this client, don't show back (see pick()) return !!this._clientListViewModel; } From 09c3f09d8b6b20587beccd8b59c1f55abcf7c3e6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:21:02 +0100 Subject: [PATCH 079/101] load link when creating rather than previewing it --- src/create/CreateLinkView.js | 20 ++------------------ src/create/CreateLinkViewModel.js | 23 +---------------------- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/src/create/CreateLinkView.js b/src/create/CreateLinkView.js index 0dd5bc6..784cb66 100644 --- a/src/create/CreateLinkView.js +++ b/src/create/CreateLinkView.js @@ -22,17 +22,8 @@ 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( - {className: {hidden: vm => vm.previewViewModel}}, - "Create shareable links to Matrix rooms, users or messages without being tied to any app" - ), - t.mapView(vm => vm.previewViewModel, childVM => childVM ? new PreviewView(childVM) : null), - t.div({className: {hidden: vm => !vm.linkUrl}}, [ - t.h2(link), - t.div(copyButton(t, () => vm.linkUrl, "Copy link", "fullwidth primary")), - t.div(t.button({className: "secondary fullwidth", onClick: () => this._clear()}, "Or create another link")), - ]), - t.form({action: "#", onSubmit: evt => this._onSubmit(evt), className: {hidden: vm => vm.linkUrl}}, [ + 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", @@ -52,7 +43,6 @@ export class CreateLinkView extends TemplateView { const {identifier} = form.elements; this.value.createLink(identifier.value); identifier.value = ""; - } _onIdentifierChange(evt) { @@ -63,10 +53,4 @@ export class CreateLinkView extends TemplateView { inputField.setCustomValidity(""); } } - - _clear() { - this.value.clear(); - const textField = this.root().querySelector("input[name=identifier]"); - textField.focus(); - } } diff --git a/src/create/CreateLinkViewModel.js b/src/create/CreateLinkViewModel.js index 6e5fcc5..21f4c81 100644 --- a/src/create/CreateLinkViewModel.js +++ b/src/create/CreateLinkViewModel.js @@ -32,28 +32,7 @@ export class CreateLinkViewModel extends ViewModel { async createLink(identifier) { this._link = Link.parse(identifier); if (this._link) { - // TODO: abort previous load - this.previewViewModel = new PreviewViewModel(this.childOptions({ - link: this._link, - consentedServers: this._link.servers, - })); - this.emitChange(); - await this.previewViewModel.load(); - } else { - this.previewViewModel = null; + this.openLink("#" + this._link.toFragment()); } - this.emitChange(); - } - - get linkUrl() { - if (this._link) { - return `${this.origin}/#${this._link.toFragment()}`; - } - } - - clear() { - this._link = null; - this.previewViewModel = null; - this.emitChange(); } } From 732b8a48ff7e9963e781ab8756623b166f2e6dbe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:24:22 +0100 Subject: [PATCH 080/101] add platforms icon --- css/client.css | 7 +++++++ images/platform-icon.svg | 1 + src/open/ClientView.js | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 images/platform-icon.svg diff --git a/css/client.css b/css/client.css index 1e05d19..ebe9f20 100644 --- a/css/client.css +++ b/css/client.css @@ -32,6 +32,13 @@ 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; 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/src/open/ClientView.js b/src/open/ClientView.js index 7bc9868..17721c7 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -34,7 +34,7 @@ export class ClientView extends TemplateView { t.div({className: "description"}, [ t.h3(vm.name), t.p(vm.description), - t.p(formatPlatforms(vm.availableOnPlatformNames)), + t.p({className: "platforms"}, formatPlatforms(vm.availableOnPlatformNames)), ]), t.img({className: "clientIcon", src: vm.iconUrl}) ]), From 42994c447467f9cc79b2c55df99bf3c9e3f3ca70 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 14:35:08 +0100 Subject: [PATCH 081/101] allow markup in instructions --- src/open/ClientView.js | 17 ++++++++++++++--- src/open/ClientViewModel.js | 6 +++++- src/open/clients/Nheko.js | 6 +++--- src/open/types.js | 6 ++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/open/ClientView.js b/src/open/ClientView.js index 17721c7..3cf76e5 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -16,7 +16,7 @@ limitations under the License. import {TemplateView} from "../utils/TemplateView.js"; import {copy} from "../utils/copy.js"; -import {text} from "../utils/html.js"; +import {text, tag} from "../utils/html.js"; function formatPlatforms(platforms) { return platforms.reduce((str, p, i, all) => { @@ -26,6 +26,16 @@ function formatPlatforms(platforms) { }, ""); } +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) { @@ -66,7 +76,8 @@ class InstallClientView extends TemplateView { render(t, vm) { const children = []; - if (vm.textInstructions) { + const textInstructions = vm.textInstructions; + if (textInstructions) { const copyButton = t.button({ className: "copy", onClick: evt => { @@ -78,7 +89,7 @@ class InstallClientView extends TemplateView { } } }); - children.push(t.p({className: "instructions"}, [t.code(vm.textInstructions), copyButton])); + children.push(t.p({className: "instructions"}, renderInstructions(textInstructions).concat(copyButton))); } const actions = t.div({className: "actions"}, vm.actions.map(a => { diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index dd2fb43..9e4d6f1 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -110,7 +110,11 @@ export class ClientViewModel extends ViewModel { } get textInstructions() { - return this._client.getLinkInstructions(this._proposedPlatform, this._link); + let instructions = this._client.getLinkInstructions(this._proposedPlatform, this._link); + if (!Array.isArray(instructions)) { + instructions = [instructions]; + } + return instructions; } get copyString() { diff --git a/src/open/clients/Nheko.js b/src/open/clients/Nheko.js index 2f860ee..0aa35ad 100644 --- a/src/open/clients/Nheko.js +++ b/src/open/clients/Nheko.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Maturity, Platform, LinkKind, FlathubLink} from "../types.js"; +import {Maturity, Platform, LinkKind, FlathubLink, style} from "../types.js"; /** * Information on how to deep link to a given matrix client. @@ -33,8 +33,8 @@ export class Nheko { getLinkInstructions(platform, link) { switch (link.kind) { - case LinkKind.User: return `Type /invite ${link.identifier}`; - case LinkKind.Room: return `Type /join ${link.identifier}`; + case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)]; + case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)]; } } diff --git a/src/open/types.js b/src/open/types.js index 52472d6..26c3e48 100644 --- a/src/open/types.js +++ b/src/open/types.js @@ -109,3 +109,9 @@ export class WebsiteLink { return `Download from ${new URL(this._url).hostname}`; } } + +export const style = { + code(text) { + return {type: "code", text}; + } +} From ef073de306f1cb8ff982e4ed25dfaadef8f39f00 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 16:01:29 +0100 Subject: [PATCH 082/101] change download buttons labeling --- css/main.css | 2 ++ src/open/ClientViewModel.js | 13 ++----------- src/open/types.js | 12 ++++++------ 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/css/main.css b/css/main.css index 1502763..23d43d4 100644 --- a/css/main.css +++ b/css/main.css @@ -153,6 +153,8 @@ button.text:hover { .secondary { background: var(--background); color: var(--link); + border: 1px solid var(--link); + border-radius: 32px; } .primary { diff --git a/src/open/ClientViewModel.js b/src/open/ClientViewModel.js index 9e4d6f1..c6d54b5 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -53,7 +53,7 @@ export class ClientViewModel extends ViewModel { if (nativePlatform) { const nativeActions = (client.getInstallLinks(nativePlatform) || []).map(installLink => { return { - label: installLink.description, + label: installLink.getDescription(nativePlatform), url: installLink.createInstallURL(link), kind: installLink.channelId, primary: true, @@ -66,22 +66,13 @@ export class ClientViewModel extends ViewModel { const webDeepLink = client.getDeepLink(webPlatform, link); if (webDeepLink) { actions.push({ - label: `Or open in web app`, + label: `Continue in your browser`, url: webDeepLink, kind: "open-in-web", activated: () => this.preferences.setClient(client.id, webPlatform), }); } } - if (client.homepage) { - actions.push({ - label: `Visit app homepage`, - url: client.homepage, - primary: true, - kind: "homepage", - activated: () => {}, - }); - } return actions; } diff --git a/src/open/types.js b/src/open/types.js index 26c3e48..eb99b2b 100644 --- a/src/open/types.js +++ b/src/open/types.js @@ -33,7 +33,7 @@ export class AppleStoreLink { return "apple-app-store"; } - get description() { + getDescription() { return "Download on the App Store"; } } @@ -51,7 +51,7 @@ export class PlayStoreLink { return "play-store"; } - get description() { + getDescription() { return "Get it on Google Play"; } } @@ -69,7 +69,7 @@ export class FDroidLink { return "fdroid"; } - get description() { + getDescription() { return "Get it on F-Droid"; } } @@ -87,7 +87,7 @@ export class FlathubLink { return "flathub"; } - get description() { + getDescription() { return "Get it on Flathub"; } } @@ -105,8 +105,8 @@ export class WebsiteLink { return "website"; } - get description() { - return `Download from ${new URL(this._url).hostname}`; + getDescription(platform) { + return `Download for ${platform}`; } } From ca601d0e732185398d8725fba891ed9cd9732ceb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 16:02:06 +0100 Subject: [PATCH 083/101] polish --- css/client.css | 15 ++++++++++----- css/main.css | 13 ++++++++++--- css/open.css | 10 ++++++++-- css/preview.css | 6 +++--- images/background.svg | 2 +- images/copy.svg | 4 ++-- images/tick-dark.svg | 3 +++ src/open/ClientView.js | 13 +++++++++---- src/open/ClientViewModel.js | 6 +++++- src/open/OpenLinkView.js | 6 +++--- src/open/clients/Element.js | 2 +- 11 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 images/tick-dark.svg diff --git a/css/client.css b/css/client.css index ebe9f20..fbacecd 100644 --- a/css/client.css +++ b/css/client.css @@ -1,5 +1,6 @@ -.ClientListView .list { - padding: 16px 0; +.ClientListView h2 { + text-align: center; + margin: 18px 0; } .ClientView { @@ -49,12 +50,15 @@ height: 100%; } +.ClientView .back { + margin-top: 22px; +} + .InstallClientView .instructions button { background-repeat: no-repeat; background-position: center; - background-color: var(--link); + background-color: transparent; padding: 4px; - border-radius: 4px; border: none; width: 24px; height: 24px; @@ -67,5 +71,6 @@ } .InstallClientView .instructions button.tick { - background-image: url('../images/tick.svg'); + background-image: url('../images/tick-dark.svg'); } + diff --git a/css/main.css b/css/main.css index 23d43d4..a16404a 100644 --- a/css/main.css +++ b/css/main.css @@ -42,6 +42,7 @@ html { 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; @@ -53,6 +54,14 @@ body { margin: 0; } +p { + line-height: 150%; +} + +a { + text-decoration: none; +} + body, button, input, @@ -118,14 +127,13 @@ button, input { } .footer .links { - font-size: 0.8em; + font-size: 12px; list-style: none; padding: 0; } a, button.text { color: var(--link); - text-decoration: underline; } button.text { @@ -184,7 +192,6 @@ input[type='text'].large { border: 1px solid var(--foreground); border-radius: 16px; font-size: 14px; - line-height: 24px; } .fullwidth { diff --git a/css/open.css b/css/open.css index d02c0e0..de0c018 100644 --- a/css/open.css +++ b/css/open.css @@ -14,11 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -.OpenLinkView .previewSource { +.OpenLinkView .caption { color: var(--grey); font-size: 12px; } +.ServerConsentView .actions label { + display: flex; + align-items: end; + gap: 5px; +} + .ServerConsentView .actions { margin-top: 24px; display: flex; @@ -27,7 +33,7 @@ limitations under the License. .ServerConsentView input[type=submit] { flex: 1; - margin-left: 12px; + margin-left: 32px; } .ServerOptions div { diff --git a/css/preview.css b/css/preview.css index 2921850..1de5220 100644 --- a/css/preview.css +++ b/css/preview.css @@ -61,8 +61,8 @@ } .PreviewView .memberCount p:not(.placeholder) { - padding: 4px 4px 4px 24px; - border-radius: 8px; + padding: 4px 8px 4px 24px; + border-radius: 14px; background-image: url(../images/member-icon.svg); background-repeat: no-repeat; background-position: 2px center; @@ -107,7 +107,7 @@ var(--flash-fg) calc(10% + 25px), var(--flash-bg) calc(10% + 50px) ); - animation: flash 2s ease-in-out infinite; + animation: flash 2s linear infinite; background-size: 200%; } diff --git a/images/background.svg b/images/background.svg index bdca144..37c31f0 100644 --- a/images/background.svg +++ b/images/background.svg @@ -1,6 +1,6 @@ - + diff --git a/images/copy.svg b/images/copy.svg index 7906974..1d60511 100644 --- a/images/copy.svg +++ b/images/copy.svg @@ -1,4 +1,4 @@ - - + + 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/src/open/ClientView.js b/src/open/ClientView.js index 3cf76e5..96c585f 100644 --- a/src/open/ClientView.js +++ b/src/open/ClientView.js @@ -43,7 +43,11 @@ export class ClientView extends TemplateView { t.div({className: "header"}, [ t.div({className: "description"}, [ t.h3(vm.name), - t.p(vm.description), + 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}) @@ -80,6 +84,8 @@ class InstallClientView extends TemplateView { 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"; @@ -131,9 +137,8 @@ class InstallClientView extends TemplateView { } function showBack(t, vm) { - return t.p({className: {previewSource: true, hidden: vm => !vm.showBack}}, [ - `Continue with ${vm.name}.`, - " ", + 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 index c6d54b5..eb21f15 100644 --- a/src/open/ClientViewModel.js +++ b/src/open/ClientViewModel.js @@ -76,6 +76,10 @@ export class ClientViewModel extends ViewModel { return actions; } + get homepage() { + return this._client.homepage; + } + get identifier() { return this._link.identifier; } @@ -102,7 +106,7 @@ export class ClientViewModel extends ViewModel { get textInstructions() { let instructions = this._client.getLinkInstructions(this._proposedPlatform, this._link); - if (!Array.isArray(instructions)) { + if (instructions && !Array.isArray(instructions)) { instructions = [instructions]; } return instructions; diff --git a/src/open/OpenLinkView.js b/src/open/OpenLinkView.js index badcd29..8a2dbd4 100644 --- a/src/open/OpenLinkView.js +++ b/src/open/OpenLinkView.js @@ -35,9 +35,9 @@ class ShowLinkView extends TemplateView { return t.div([ t.view(new PreviewView(vm.previewViewModel)), t.view(new ClientListView(vm.clientsViewModel)), - t.p({className: {previewSource: true, hidden: vm => !vm.previewDomain}}, [ - vm => vm.previewFailed ? `${vm.previewDomain} has not returned a preview.` : `Preview provided by ${vm.previewDomain}.`, - " ", + 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/clients/Element.js b/src/open/clients/Element.js index e793b4d..7469e5d 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -40,7 +40,7 @@ export class Element { get name() {return "Element"; } get description() { return 'Fully-featured Matrix client, used by millions.'; } - get homepage() { return ; } // prevents a visit app homepage button from appearing + get homepage() { return "https://element.io"; } get author() { return "Element"; } getMaturity(platform) { return Maturity.Stable; } From 42190df0405b5ed0202530ed25face02d079e52d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 16:19:59 +0100 Subject: [PATCH 084/101] improve privacy dialog consent --- src/open/ServerConsentView.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js index d0461bb..2acc1e7 100644 --- a/src/open/ServerConsentView.js +++ b/src/open/ServerConsentView.js @@ -29,18 +29,19 @@ export class ServerConsentView extends TemplateView { }, "continue without a preview"); return t.div({className: "ServerConsentView"}, [ t.p([ - "View this link using ", + "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}}, [ - " to preview content, or your can ", + ", ", useAnotherServer, ]), " or ", From e9224c4d6651815c20c6eb50a760e9dd6388bda7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 16:20:17 +0100 Subject: [PATCH 085/101] server consent improvements --- css/main.css | 2 +- src/open/ServerConsentView.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/css/main.css b/css/main.css index a16404a..948fa38 100644 --- a/css/main.css +++ b/css/main.css @@ -70,7 +70,7 @@ textarea { font-style: normal; } -button { +button, input[type=submit] { cursor: pointer; } diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js index 2acc1e7..00a873c 100644 --- a/src/open/ServerConsentView.js +++ b/src/open/ServerConsentView.js @@ -68,7 +68,12 @@ export class ServerConsentView extends TemplateView { 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}), t.span(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"}), From 4e253697de673dbb67f5a0fb19d3e734e8c10242 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 16:20:31 +0100 Subject: [PATCH 086/101] stylize weechat invite instructions --- src/open/clients/Weechat.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/open/clients/Weechat.js b/src/open/clients/Weechat.js index 9d37efc..49ceb7a 100644 --- a/src/open/clients/Weechat.js +++ b/src/open/clients/Weechat.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Maturity, Platform, LinkKind, WebsiteLink} from "../types.js"; +import {Maturity, Platform, LinkKind, WebsiteLink, style} from "../types.js"; /** * Information on how to deep link to a given matrix client. @@ -33,8 +33,8 @@ export class Weechat { getLinkInstructions(platform, link) { switch (link.kind) { - case LinkKind.User: return `Type /invite ${link.identifier}`; - case LinkKind.Room: return `Type /join ${link.identifier}`; + case LinkKind.User: return [`Type `, style.code(`/invite ${link.identifier}`)]; + case LinkKind.Room: return [`Type `, style.code(`/join ${link.identifier}`)]; } } From e5fb5709f7a00f03636bbc10569dca9abd8c3af3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:22:52 +0100 Subject: [PATCH 087/101] cleanup --- src/open/clients/Element.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/open/clients/Element.js b/src/open/clients/Element.js index 7469e5d..2c28af8 100644 --- a/src/open/clients/Element.js +++ b/src/open/clients/Element.js @@ -31,18 +31,12 @@ export class Element { ]; } - get icon() { - return "images/client-icons/element.svg"; - } - + 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) { From 639a18973070909354903d3af59ec2a1f15e0748 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:35:49 +0100 Subject: [PATCH 088/101] vertically align checkboxes and radiobuttons --- css/client.css | 6 ++++++ css/main.css | 4 ++++ css/open.css | 8 +------- src/open/ClientListView.js | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/css/client.css b/css/client.css index fbacecd..f4f63ff 100644 --- a/css/client.css +++ b/css/client.css @@ -3,6 +3,12 @@ margin: 18px 0; } +.ClientListView .filterOption { + display: flex; + align-items: center; + margin: 8px 0; +} + .ClientView { border: 1px solid #E6E6E6; border-radius: 8px; diff --git a/css/main.css b/css/main.css index 948fa38..a9f7a77 100644 --- a/css/main.css +++ b/css/main.css @@ -79,6 +79,10 @@ button, input { font-weight: inherit; } +input[type="checkbox"], input[type="radio"] { + margin: 0 8px 0 0; +} + .RootView { margin: 0 auto; max-width: 480px; diff --git a/css/open.css b/css/open.css index de0c018..560f7a3 100644 --- a/css/open.css +++ b/css/open.css @@ -21,8 +21,7 @@ limitations under the License. .ServerConsentView .actions label { display: flex; - align-items: end; - gap: 5px; + align-items: center; } .ServerConsentView .actions { @@ -45,11 +44,6 @@ limitations under the License. align-items: center; } -.ServerOptions label > span, -.ServerOptions label > .line { - margin-left: 8px; -} - .ServerOptions label > .line { flex: 1; border: none; diff --git a/src/open/ClientListView.js b/src/open/ClientListView.js index 4dab154..3fc43ab 100644 --- a/src/open/ClientListView.js +++ b/src/open/ClientListView.js @@ -48,7 +48,7 @@ class AllClientsView extends TemplateView { }), "Show apps not available on my platform" ])), - t.div(t.label([ + t.div(t.label({className: "filterOption"}, [ t.input({ type: "checkbox", checked: vm.showExperimental, From 34410059b1d7b9bfc3de2fb1af8ab5ee736e7a0f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:36:03 +0100 Subject: [PATCH 089/101] dot --- src/open/clients/Fractal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/open/clients/Fractal.js b/src/open/clients/Fractal.js index 47310af..4c860ee 100644 --- a/src/open/clients/Fractal.js +++ b/src/open/clients/Fractal.js @@ -26,7 +26,7 @@ export class Fractal { 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'; } + get description() { return 'Fractal is a Matrix Client written in Rust.'; } getMaturity(platform) { return Maturity.Beta; } getDeepLink(platform, link) {} canInterceptMatrixToLinks(platform) { return false; } From 9712ecf9746bbf86e790f738828a84856c5269b1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:47:08 +0100 Subject: [PATCH 090/101] don't show border radios on default avatar icon --- css/preview.css | 9 +++++++++ src/preview/PreviewView.js | 9 ++++++++- src/preview/PreviewViewModel.js | 8 +++----- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/css/preview.css b/css/preview.css index 1de5220..c72e96e 100644 --- a/css/preview.css +++ b/css/preview.css @@ -21,6 +21,15 @@ 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; diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index 2cee062..4d16075 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -44,8 +44,15 @@ class LoadingPreviewView extends TemplateView { 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"}, t.img({className: "avatar", src: vm => vm.avatarUrl})), + 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"])), diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 5c340eb..50843e6 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -20,8 +20,6 @@ import {resolveServer} from "./HomeServer.js"; import {ClientListViewModel} from "../open/ClientListViewModel.js"; import {ClientViewModel} from "../open/ClientViewModel.js"; -const DEFAULT_AVATAR = "images/chat-icon.svg"; - export class PreviewViewModel extends ViewModel { constructor(options) { super(options); @@ -84,7 +82,7 @@ export class PreviewViewModel extends ViewModel { this.name = profile.displayname || userId; this.avatarUrl = profile.avatar_url ? homeserver.mxcUrlThumbnail(profile.avatar_url, 64, 64, "crop") : - DEFAULT_AVATAR; + null; this.identifier = userId; } @@ -101,7 +99,7 @@ export class PreviewViewModel extends ViewModel { this.name = publicRoom?.name || publicRoom?.canonical_alias || link.identifier; this.avatarUrl = publicRoom?.avatar_url ? homeserver.mxcUrlThumbnail(publicRoom.avatar_url, 64, 64, "crop") : - DEFAULT_AVATAR; + null; this.memberCount = publicRoom?.num_joined_members; this.topic = publicRoom?.topic; this.identifier = publicRoom?.canonical_alias || link.identifier; @@ -113,6 +111,6 @@ export class PreviewViewModel extends ViewModel { _setNoPreview(link) { this.name = link.identifier; this.identifier = null; - this.avatarUrl = DEFAULT_AVATAR; + this.avatarUrl = null; } } From 9395d7f3f5087932305ac728e7fe7d3f248b3594 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:51:17 +0100 Subject: [PATCH 091/101] fix ask every time not being respected for no preview option --- src/open/ServerConsentView.js | 15 ++++++++++----- src/open/ServerConsentViewModel.js | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/open/ServerConsentView.js b/src/open/ServerConsentView.js index 00a873c..e35c257 100644 --- a/src/open/ServerConsentView.js +++ b/src/open/ServerConsentView.js @@ -19,13 +19,13 @@ import {ClientListView} from "./ClientListView.js"; import {PreviewView} from "../preview/PreviewView.js"; export class ServerConsentView extends TemplateView { - render(t, vm) { + render(t, vm) { const useAnotherServer = t.button({ className: "text", onClick: () => vm.setShowServers()}, "use another server"); const continueWithoutPreview = t.button({ className: "text", - onClick: () => vm.continueWithoutConsent() + onClick: () => vm.continueWithoutConsent(this._askEveryTimeChecked) }, "continue without a preview"); return t.div({className: "ServerConsentView"}, [ t.p([ @@ -48,7 +48,7 @@ export class ServerConsentView extends TemplateView { continueWithoutPreview, "." ]), - t.form({action: "#", onSubmit: evt => this._onSubmit(evt)}, [ + 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"]), @@ -60,8 +60,13 @@ export class ServerConsentView extends TemplateView { _onSubmit(evt) { evt.preventDefault(); - const {askEveryTime} = evt.target.elements; - this.value.continueWithSelection(askEveryTime.checked); + this.value.continueWithSelection(this._askEveryTimeChecked); + } + + get _askEveryTimeChecked() { + const form = document.getElementById("serverConsentForm"); + const {askEveryTime} = form.elements; + return askEveryTime.checked; } } diff --git a/src/open/ServerConsentViewModel.js b/src/open/ServerConsentViewModel.js index 7278f61..2cb6e07 100644 --- a/src/open/ServerConsentViewModel.js +++ b/src/open/ServerConsentViewModel.js @@ -64,8 +64,8 @@ export class ServerConsentViewModel extends ViewModel { this.done(); } - continueWithoutConsent() { - this.preferences.setHomeservers([]); + continueWithoutConsent(askEveryTime) { + this.preferences.setHomeservers([], !askEveryTime); this.done(); } } From 63b9a4c8b4d690448429de266883d5c0f5f9ac59 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 17:57:10 +0100 Subject: [PATCH 092/101] fix margin changes between preview placeholders and actual text --- css/preview.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/css/preview.css b/css/preview.css index c72e96e..9a0184e 100644 --- a/css/preview.css +++ b/css/preview.css @@ -40,6 +40,7 @@ display: flex; align-items: center; justify-content: center; + box-sizing: border-box; } .PreviewView .identifier { @@ -50,7 +51,7 @@ .PreviewView .identifier.placeholder { height: 1em; - margin: 16px 30%; + margin: 1em 30%; } .PreviewView .memberCount { From cc643257a97861dbec5085ba009be8e3d1dc1a93 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 18:00:32 +0100 Subject: [PATCH 093/101] polish flash animation in placeholder --- css/preview.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/css/preview.css b/css/preview.css index 9a0184e..8816178 100644 --- a/css/preview.css +++ b/css/preview.css @@ -60,7 +60,6 @@ margin: 8px 0; } - .PreviewView .memberCount.loading { margin: 16px 0; } @@ -124,6 +123,6 @@ @keyframes flash { 0% { background-position-x: 0; } 50% { background-position-x: -80%; } - 51% { background-position-x: 40%; } + 51% { background-position-x: 80%; } 100% { background-position-x: 0%; } } From 7782a9d197267d6571946ff62b3e67ebf8f5cb6f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 18:24:10 +0100 Subject: [PATCH 094/101] also include preference homeservers in server dialog --- src/open/OpenLinkViewModel.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/open/OpenLinkViewModel.js b/src/open/OpenLinkViewModel.js index bd72ff8..ebd6210 100644 --- a/src/open/OpenLinkViewModel.js +++ b/src/open/OpenLinkViewModel.js @@ -20,6 +20,7 @@ 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) { @@ -39,8 +40,14 @@ export class OpenLinkViewModel extends ViewModel { } _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: this._link.servers, + servers, done: () => { this.serverConsentViewModel = null; this._showLink(); From 4aff6a96ea9f28ea8eb6fba0d3af2e06730324e0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 18:45:04 +0100 Subject: [PATCH 095/101] add quaternion again --- images/client-icons/quaternion.svg | 464 +++++++++++++++++++++++++++++ src/open/clients/Quaternion.js | 50 ++++ src/open/clients/index.js | 3 +- 3 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 images/client-icons/quaternion.svg create mode 100644 src/open/clients/Quaternion.js 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/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/index.js b/src/open/clients/index.js index 657982e..6a36b10 100644 --- a/src/open/clients/index.js +++ b/src/open/clients/index.js @@ -18,12 +18,13 @@ 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"; export function createClients() { return [ new Element(), new Weechat(), new Nheko(), - new Fractal(), + new Quaternion(), ]; } From adffdb33bb8a008dbdffdf95aa00b59c7f60146b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 18:54:57 +0100 Subject: [PATCH 096/101] add tensor client again --- images/client-icons/tensor.png | Bin 0 -> 6629 bytes src/open/clients/Tensor.js | 50 +++++++++++++++++++++++++++++++++ src/open/clients/index.js | 2 ++ 3 files changed, 52 insertions(+) create mode 100644 images/client-icons/tensor.png create mode 100644 src/open/clients/Tensor.js diff --git a/images/client-icons/tensor.png b/images/client-icons/tensor.png new file mode 100644 index 0000000000000000000000000000000000000000..f4437c242aa7aa77e0365d7b88e29595fa07399f GIT binary patch literal 6629 zcmY*e30PCtwhkhw3^EBp5GaV+f*}YbQOi&f1d2$tCdd(j7s@peggAhQK|ux?Leyd* zVvtdzC59Lqub>BHO1LF}43|-vf`AMK0x}BncCg;}-j^@oTYFgRUuzF*|BIBLj=88R z=_#R5C{;ICrxPfYoQ3SWSrN2Y{;+l!{899GcX2|k$v$~ykM4k$Ei_l}ODL4Gmh3Bs zx|gksLTyiUb2@Yq?*6OiZOG|`48`ftDG%>ViW_@;Gv+q?&hl*&d*25hgZ{A_;v;^I zNM6K$Yk6s(+vM=>x}WRi>ye71%J0AVo*z)^x+g_T;rX`lUrv}V__#hen!0Ua;PqM* zcWqgxiB*#s(ZU-){dvNv|0I6YXl>CQDO)pITrKpiU;EFnxqrW1=W8A_xZ)uHASjceCoU(rRwKf%9yXS^~>PSs|&PfI~p)7KjYW_!~Raw(D*Tl+G z7&S)#w&=XM{7dWDz8$s;CEbu=>y->l54c7Ls!h5gEnG&k{I|HL5zczqM*iUNrM;#r7;r3-$6r>&@q`j9@YCqRfcHD#*kb*cij@d+#{H5Pi=P% zHYF!;zJ)j@hUx(U|CUZ4MRfJIc;&z!rv5*yXL&?lPw{qg8l$mymVHW)j2@`lp~jh_ z9-uR7#DB8GylkMIuyokDWOx29tf3~BE~S}~kqC|A3xbseLH2-<#gr2O-5r~uxp_f$ zzmVlsyny_!vmU3%kA$5cuC%`7=mAkoOBtu`!&@0z;Wdj@q(8kew!Z>kDmGc&Vt!Xr zv#zXjfAkwxWkH}G)|&SF=sF1VJQis;ve#h3qvB!lBWFZcO}RgC^o)_M&pBCCo?V%> zO3p0p)V1-!3ag2ycEl5~@&t@XbbBC@+WJyO9Plk(?El4knCki%UZX-ER}ee8yumDIp+mu<6b)GQwO)*47Jn7}J;mhU_a1cqah%TtwTsR`fa^s!=yu$q zfI?dYZZ#nn4S+#t@Un^>b`CySw%J69;$7GoB(=%6RXA}X%cK^XT+vaESUhlVKu>e4zIxn=i zr#E7}jqW7YA(#6H4ABf~56=tsHaM!hqhiR5%iVLO)y`Fcd-2N^oD(HC30$?xK085T z`&)5^f81i;h4Q(fHuFnK9+E&6UQs|mpiHYach?!|`+%^74qgg6pfFknvW$9v`L;p* z{m`osLrMWV0}4-pRtXy?jmM`kYUuEmco3Zs=ywCM)nJl0QYR8s0YE|z z@t<{XSO5xky?f}QuKsp?^Q3i)M3pFV4ae%syrJpgKr{~_@an%zs|B(a53%DN&2^m@ zlu(y;%p(($3>>~OVSRF#irRd5K!6S+QDNysLGJ|Ts^|nP2X07ZNL2(f2X~>cq9Y=a z%DO)H7E8R<*SVTWc325c+d`(-&9E2f#a)|Ct(6$d^lSs?n2AF~QDmt*WuM{2))R5) zC^AW^S0lPBi<4f`rl?nZY5e86wr^K^%GPS$H3mg{nB60EW1fU(3T<5Ofda^!XS+C;J%6l*T`Rq^S*OOlN3ENy$ZMx~vZbgfY2TvV&g;6dp z8&sP*QYGsTe_T(G7$Bnz79He*r{@h_QYFkkkk~T8Z4M(q9UmjUl zdZwFYlfn-R)V|-2{8JFldmWK)v}a%6Pv29nUe90{nBFfWtS*(}4gGeKGub>ePY$vx z2t41~4c~kqkGvab~57|$Vsf~Qw>rA|37$atewzHk&}#`t>AW#kidx%14AOo#q*3 zG)P!PB&W2JPQVlp)qK(zM*;$t(#-~qn~TsEVE^(Y#4QERf?q0t6$17&f1iU4Eynu` zTJwN)w0|b7qs_jK2J_NY(b8x1j^EZT9{g$%ez|V3Md6FZn^Eg(?$h?+^%#r`X}za8 z*lVjv+6941+?#5Gl?rEMqG3jfHhT=2mJr-8mC?8Osavz!Z-Bnabtqbto^k~nz2p|t zbCbS7n!M@ems8Z%88w-HV}asfBslN+xCUkoSh(j%hqIECdYg+j0hg385^X&6JuZ)E z0)AuWpBhmwwnb4j7-Q74!OW3BWK?{?6w`p31VOqr) z-Z42v6>%DKGIJj9PxW${YmjN`Q%n!I@}luMzq#89+rDI6tj@io<}c4FNr~_|xukhN z4~Gh~x36LRGgToY=HEV!Lx+rfsvFz~8iJlug#ltoJ2*t?XqChZ!Q^uT!-NvY+ zNkO{$3WKBA_haW3|v;8Wysby1#Tkw;$Gk%v_)AjTf z3U;aT5a~|`$=#Ctkdm9zMGI&A!dugI^c4zsDewxUKbeu`1>xHY9-@l}hKbb;;x_iK zU3jw}I3eH`KTL(GF75=M*tHzyxCdz3mQO^JiLxI(HR(X*kTst4oZ6PibQDAn5^+$u zv?VfGO{@4H!B5nAMFdPA{M`|d>C2mD5}Ct?qf2PS6eiCDw3ye%;28dbB9WK?5eKz@ zv{lh6Hk+O)qY@b%byl=-eAC^-`;GeL$e1?f`k8L~SgGwT_ zKSa2734Da2p=nwF%pmcK?PZ2nG6C~FO>QZQ+=AymgSj8pda8@0>8YgSh+`SGE6&_A z__vo-ZDEkGk&WBV>)I4GN*NN0l^}e-o=9XponB4_q9wG)XhM{=DGEag6q>(3jQEx` zR=JDW8QRza+>MUW26uhvkLhQ$)@RBcrikD?5KN%6!7@KRs>LU41KGPH&UFDwdK0t46>iqu=X%ah8tn}T8YFlw zCn*<6$6jO>_7He}l&QvtQMc|zQL!fE0PEzWx3$ES**fV#r@~qF*PWfh6EBip+^Z-k zoeKEb6OJYAvF}60RD%&w=1KG@f&_= z0e2r#(}yfGZ-zjvO(x_&n)QV4pNGSqQ5zZ-d#V#llKQfe8HXGyrL6XPwd845hm`%? zMj0c91D(e|4)4<9jY?_V*#v%s!l(B$1vq1TYE=-))|9-|oNlN;TZp^qRl@SR?fK+e zNNZYCL!Vv{xP@0hVMxN<>xXfl*tj6YO`j~KN|SCG&hAj~j*o)Wf-oiCH5d+aF9@4y zuw3jBMG|-u1nv_!5v?B|=~ uBl%e1wG7tLz{K9gjGKew_wipsn5+LskPOAOe zp){I$74aSUdc2~hgG-|6sHfPN*1DGwiPxFSdwzZXXlOI9E|lyY1qHfTllR2TKOGC* z>R^YxLg4O&6PssGtZww_6O7$tL_|S4(lNQkck<990`J^(k%i$gmSrUQoKZB9z^W@JVkhio2txvQ&^Btu$l~Ei88B2yhP(SIjeco`& zVOWQ}>|c0q)9DTmju|p`Gf8i{Xq=s)%3I+dH-sVs3R5>hVV+`Ra#jfWM00wAR}X<3 z%-{E^viviPbc%m`CH~1L^;|7p76I#LhiQP_N~wX>LG#1AM%Wn&SOYt3v)WJH9-QFd zk3Fp|$xH!@G~d8^xYzKQKIr4C9UJrDd`oT_%-@JS=oO?&Kn}ftybmFp7~Rc;@5=Gy z8z@UP?#mwLSn^CLMZzPRD-mj)Q(DCp&5_%A zYhBmMR##uck5A&23*#>hLV;Rfr5|i9vq(h@88M%I{f!am2`^Pr~Q(g8f3N36wuT z>>DPEvGpuK>5@swe13TjbHVR70wxO9;5JETjP%Ps$06$s%)3Gy6Z~^NLH z8j-OKtfwUf-hjP#5bStW4q4LMlu}(u;C7yb<&~f7DoD-*`;Zh6AdSB|nOg(is73zC zXlaoOa4wmYaTq+mFO}%3rDWbRCfA6!`#6bve;Wlv_1FdE*=opOy-^J-vjJ>Hz`-x1 z4rFjgwBPg!0Qm)1(GI^mTr&-8!;7Xhhb6-8!tn`~*HOX#6B1*oBuyk5g@uor0Rxua zhA;dwsGTdDW&+jFck1)*N}DEKWpwzM4&q-Bh{pU*z(|ByiC*;pU(5c(NMy9b`GK&L z8I5sL56JtE@O!0~<)eC1+;2@#AfL4{Kh%vJHJ}q<6Q}gzQA$c`^J~ zf7J^sm#5W^gzQ+*)lWe;Oa4tyGBQrdDPK|ZG&nm2lqiEmHZ7RDR1mlo^U>bWCCJ1F zHEj_}u5p*Jg@=*n9zm+d2Vc3ZT^tKdnjMx^WDd-ohfc8s)P{thSTtBw6KDsI@-Qfw zU5L}NK*3DbdQ&K6u0vhud3LqpXaiLQieiHO8HH`NwaMv^ysy!Q9cB(8;IS?_`GWe7 zQx;)L;O$4+C2J@-5+w5AZ1X|Mn6e|FwN}Zbg+YX(9jAQe4IL&5sDSht;BrA_)WXGqd2G(10TVyJ}) zqLhwO+qQxdLD_+$O`rV@Z>iZ=>TLb^+2!nk1I-sZTQ%hX_eyM!*S76)YpS$g{xg^F zg6s{3LZTpl851i?Q2j!Uh*A#@N#@?sd}l-@kLcm)zC&h=4U+#r0r4N6?xtU8W}Y)r zm6nvU=D`UsQG~C$lhL-oH)3_DMvR53LZKqr^2iL%#Aep#kZ3fyiHIz~5{XHf{W+xD|FvJV z+i>?X9_Uw{-mnhnVlwF^d!M1=SJ#>Hg3BKTaeuqc*$<^gLc9NVT>+2rwZmBd*L8Cv zs4f&b^wsqYIm~@K%%!iUJzU%`c#8iw>bq;JO%V@snd_C7>+6Ee&nQmRRcokF==>`p z%L%MQYpazSQ`kw-!@PnO+FDI~MBshATdD*Kpd})yQi<6i!BL6!FP?N7V^Ev&hEB<< zG4IUL8MtSeKL?VL8xl83)hPbMKL7=G@*P;A52&!7j`LsdJNi+ux#LOamlmPN}97j_o?T%WP9HZ9z{s|;bh3j~POm~*dk`@0r z>dQ1)mnr$N1}KMUV&?^$nem;cT0!CPs038c23qv>QvkY7U@{Wi61%1Tzy)HOS0boI z{5^gXVm7v}7}OC8o*@H)$mf|Sh)j?=#E&)^Z&=S8P=3H*|f*R?mG@FuDY2ICV kDQ`_flE5N4&#ry5@_wLe)h8GHmj>nLe9WoX@l4$R08nZvXaE2J literal 0 HcmV?d00001 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/index.js b/src/open/clients/index.js index 6a36b10..91ce46b 100644 --- a/src/open/clients/index.js +++ b/src/open/clients/index.js @@ -19,6 +19,7 @@ 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 [ @@ -26,5 +27,6 @@ export function createClients() { new Weechat(), new Nheko(), new Quaternion(), + new Tensor(), ]; } From 1f43db2a5ba1a48174adcb3bc4199cf107924cc1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 7 Dec 2020 19:06:26 +0100 Subject: [PATCH 097/101] remove most bits --- jest.config.js | 4 - public/index.html | 16 - src/App.scss | 42 - src/App.tsx | 76 - src/_color-scheme.scss | 25 - src/_error.scss | 20 - src/_mixins.scss | 11 - src/clients/Element.ts | 106 - src/clients/Fractal.tsx | 75 - src/clients/Nheko.tsx | 92 - src/clients/Weechat.tsx | 92 - src/clients/index.ts | 45 - src/clients/types.ts | 168 - src/components/Avatar.scss | 29 - src/components/Avatar.stories.tsx | 44 - src/components/Avatar.tsx | 121 - src/components/Button.scss | 65 - src/components/Button.stories.tsx | 33 - src/components/Button.tsx | 89 - src/components/ClientList.scss | 22 - src/components/ClientList.tsx | 98 - src/components/ClientSelection.scss | 27 - src/components/ClientSelection.tsx | 88 - src/components/ClientTile.scss | 85 - src/components/ClientTile.tsx | 125 - src/components/CreateLinkTile.scss | 92 - src/components/CreateLinkTile.stories.tsx | 32 - src/components/CreateLinkTile.tsx | 158 - src/components/DefaultPreview.scss | 22 - src/components/DefaultPreview.tsx | 42 - src/components/Details.scss | 46 - src/components/Details.tsx | 35 - src/components/EventPreview.tsx | 35 - src/components/FakeProgress.scss | 31 - src/components/FakeProgress.tsx | 27 - src/components/Footer.scss | 34 - src/components/Footer.tsx | 65 - src/components/GroupPreview.scss | 26 - src/components/GroupPreview.tsx | 45 - src/components/HomeserverOptions.scss | 57 - src/components/HomeserverOptions.stories.tsx | 42 - src/components/HomeserverOptions.tsx | 130 - src/components/Input.scss | 50 - src/components/Input.stories.tsx | 45 - src/components/Input.tsx | 50 - src/components/InviteTile.scss | 39 - src/components/InviteTile.stories.tsx | 123 - src/components/InviteTile.tsx | 120 - src/components/InvitingClientTile.stories.tsx | 23 - src/components/InvitingClientTile.tsx | 58 - src/components/LinkButton.tsx | 34 - src/components/LinkPreview.tsx | 193 - src/components/MatrixTile.scss | 24 - src/components/MatrixTile.stories.tsx | 23 - src/components/MatrixTile.tsx | 53 - src/components/RoomPreview.scss | 30 - src/components/RoomPreview.tsx | 60 - src/components/StyledCheckbox.scss | 59 - src/components/StyledCheckbox.tsx | 41 - src/components/TextButton.scss | 31 - src/components/TextButton.stories.tsx | 34 - src/components/TextButton.tsx | 30 - src/components/Tile.scss | 35 - src/components/Tile.stories.tsx | 38 - src/components/Tile.tsx | 35 - src/components/UserPreview.scss | 85 - src/components/UserPreview.tsx | 100 - src/contexts/ClientContext.ts | 108 - src/contexts/GlobalContext.tsx | 49 - src/contexts/HSContext.ts | 120 - src/imgs/app-store-us-alt.svg | 7 - src/imgs/background.svg | 226 - src/imgs/chat-icon.svg | 4 - src/imgs/chevron-down.svg | 3 - src/imgs/copy.svg | 4 - src/imgs/element.svg | 6 - src/imgs/fdroid-badge.png | Bin 27159 -> 0 bytes src/imgs/fractal.png | Bin 8796 -> 0 bytes src/imgs/google-play-us.svg | 41 - src/imgs/link.svg | 3 - src/imgs/matrix-logo.svg | 10 - src/imgs/nheko.svg | 155 - src/imgs/refresh.svg | 3 - src/imgs/telecom-mast.svg | 3 - src/imgs/tick.svg | 3 - src/imgs/weechat.svg | 170 - src/index.scss | 75 - src/index.tsx | 12 - src/layouts/SingleColumn.scss | 29 - src/layouts/SingleColumn.tsx | 29 - src/matrix-cypher/index.ts | 19 - src/matrix-cypher/matrix-cypher.ts | 211 - src/matrix-cypher/schemas/EventSchema.ts | 30 - src/matrix-cypher/schemas/GroupSchema.ts | 27 - .../schemas/PublicRoomsSchema.ts | 41 - src/matrix-cypher/schemas/RoomAliasSchema.ts | 25 - src/matrix-cypher/schemas/UserSchema.ts | 25 - src/matrix-cypher/schemas/VersionSchema.ts | 21 - src/matrix-cypher/schemas/WellKnownSchema.ts | 29 - src/matrix-cypher/schemas/index.ts | 24 - src/matrix-cypher/utils/fetch.ts | 36 - src/matrix-cypher/utils/index.ts | 18 - src/matrix-cypher/utils/promises.ts | 60 - src/pages/LinkRouter.tsx | 72 - src/parser/parser.test.ts | 64 - src/parser/parser.ts | 166 - src/parser/types.ts | 55 - src/react-app-env.d.ts | 1 - src/utils/cypher-wrapper.ts | 209 - src/utils/getHS.ts | 88 - src/utils/localStorage.ts | 59 - tsconfig.json | 26 - yarn.lock | 14705 ---------------- 113 files changed, 21001 deletions(-) delete mode 100644 jest.config.js delete mode 100644 public/index.html delete mode 100644 src/App.scss delete mode 100644 src/App.tsx delete mode 100644 src/_color-scheme.scss delete mode 100644 src/_error.scss delete mode 100644 src/_mixins.scss delete mode 100644 src/clients/Element.ts delete mode 100644 src/clients/Fractal.tsx delete mode 100644 src/clients/Nheko.tsx delete mode 100644 src/clients/Weechat.tsx delete mode 100644 src/clients/index.ts delete mode 100644 src/clients/types.ts delete mode 100644 src/components/Avatar.scss delete mode 100644 src/components/Avatar.stories.tsx delete mode 100644 src/components/Avatar.tsx delete mode 100644 src/components/Button.scss delete mode 100644 src/components/Button.stories.tsx delete mode 100644 src/components/Button.tsx delete mode 100644 src/components/ClientList.scss delete mode 100644 src/components/ClientList.tsx delete mode 100644 src/components/ClientSelection.scss delete mode 100644 src/components/ClientSelection.tsx delete mode 100644 src/components/ClientTile.scss delete mode 100644 src/components/ClientTile.tsx delete mode 100644 src/components/CreateLinkTile.scss delete mode 100644 src/components/CreateLinkTile.stories.tsx delete mode 100644 src/components/CreateLinkTile.tsx delete mode 100644 src/components/DefaultPreview.scss delete mode 100644 src/components/DefaultPreview.tsx delete mode 100644 src/components/Details.scss delete mode 100644 src/components/Details.tsx delete mode 100644 src/components/EventPreview.tsx delete mode 100644 src/components/FakeProgress.scss delete mode 100644 src/components/FakeProgress.tsx delete mode 100644 src/components/Footer.scss delete mode 100644 src/components/Footer.tsx delete mode 100644 src/components/GroupPreview.scss delete mode 100644 src/components/GroupPreview.tsx delete mode 100644 src/components/HomeserverOptions.scss delete mode 100644 src/components/HomeserverOptions.stories.tsx delete mode 100644 src/components/HomeserverOptions.tsx delete mode 100644 src/components/Input.scss delete mode 100644 src/components/Input.stories.tsx delete mode 100644 src/components/Input.tsx delete mode 100644 src/components/InviteTile.scss delete mode 100644 src/components/InviteTile.stories.tsx delete mode 100644 src/components/InviteTile.tsx delete mode 100644 src/components/InvitingClientTile.stories.tsx delete mode 100644 src/components/InvitingClientTile.tsx delete mode 100644 src/components/LinkButton.tsx delete mode 100644 src/components/LinkPreview.tsx delete mode 100644 src/components/MatrixTile.scss delete mode 100644 src/components/MatrixTile.stories.tsx delete mode 100644 src/components/MatrixTile.tsx delete mode 100644 src/components/RoomPreview.scss delete mode 100644 src/components/RoomPreview.tsx delete mode 100644 src/components/StyledCheckbox.scss delete mode 100644 src/components/StyledCheckbox.tsx delete mode 100644 src/components/TextButton.scss delete mode 100644 src/components/TextButton.stories.tsx delete mode 100644 src/components/TextButton.tsx delete mode 100644 src/components/Tile.scss delete mode 100644 src/components/Tile.stories.tsx delete mode 100644 src/components/Tile.tsx delete mode 100644 src/components/UserPreview.scss delete mode 100644 src/components/UserPreview.tsx delete mode 100644 src/contexts/ClientContext.ts delete mode 100644 src/contexts/GlobalContext.tsx delete mode 100644 src/contexts/HSContext.ts delete mode 100644 src/imgs/app-store-us-alt.svg delete mode 100644 src/imgs/background.svg delete mode 100644 src/imgs/chat-icon.svg delete mode 100644 src/imgs/chevron-down.svg delete mode 100644 src/imgs/copy.svg delete mode 100644 src/imgs/element.svg delete mode 100644 src/imgs/fdroid-badge.png delete mode 100644 src/imgs/fractal.png delete mode 100644 src/imgs/google-play-us.svg delete mode 100644 src/imgs/link.svg delete mode 100644 src/imgs/matrix-logo.svg delete mode 100644 src/imgs/nheko.svg delete mode 100644 src/imgs/refresh.svg delete mode 100644 src/imgs/telecom-mast.svg delete mode 100644 src/imgs/tick.svg delete mode 100644 src/imgs/weechat.svg delete mode 100644 src/index.scss delete mode 100644 src/index.tsx delete mode 100644 src/layouts/SingleColumn.scss delete mode 100644 src/layouts/SingleColumn.tsx delete mode 100644 src/matrix-cypher/index.ts delete mode 100644 src/matrix-cypher/matrix-cypher.ts delete mode 100644 src/matrix-cypher/schemas/EventSchema.ts delete mode 100644 src/matrix-cypher/schemas/GroupSchema.ts delete mode 100644 src/matrix-cypher/schemas/PublicRoomsSchema.ts delete mode 100644 src/matrix-cypher/schemas/RoomAliasSchema.ts delete mode 100644 src/matrix-cypher/schemas/UserSchema.ts delete mode 100644 src/matrix-cypher/schemas/VersionSchema.ts delete mode 100644 src/matrix-cypher/schemas/WellKnownSchema.ts delete mode 100644 src/matrix-cypher/schemas/index.ts delete mode 100644 src/matrix-cypher/utils/fetch.ts delete mode 100644 src/matrix-cypher/utils/index.ts delete mode 100644 src/matrix-cypher/utils/promises.ts delete mode 100644 src/pages/LinkRouter.tsx delete mode 100644 src/parser/parser.test.ts delete mode 100644 src/parser/parser.ts delete mode 100644 src/parser/types.ts delete mode 100644 src/react-app-env.d.ts delete mode 100644 src/utils/cypher-wrapper.ts delete mode 100644 src/utils/getHS.ts delete mode 100644 src/utils/localStorage.ts delete mode 100644 tsconfig.json delete mode 100644 yarn.lock diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 91a2d2c..0000000 --- a/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 823d8d4..0000000 --- a/public/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Matrix.to - - - -
- - diff --git a/src/App.scss b/src/App.scss deleted file mode 100644 index e568c3e..0000000 --- a/src/App.scss +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import './color-scheme'; - -#root { - background-color: $app-background; - background-image: url('./imgs/background.svg'); - background-repeat: no-repeat; - background-size: cover; -} - -@mixin spacer { - width: 100%; - flex-grow: 0; - flex-shrink: 0; -} - -.topSpacer { - @include spacer; - - height: 10vh; -} - -.bottomSpacer { - @include spacer; - - height: 10vh; -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index fe11e17..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useState, useEffect } from 'react'; - -import SingleColumn from './layouts/SingleColumn'; -import CreateLinkTile from './components/CreateLinkTile'; -import MatrixTile from './components/MatrixTile'; -import Tile from './components/Tile'; -import LinkRouter from './pages/LinkRouter'; - -import './App.scss'; - -import GlobalContext from './contexts/GlobalContext'; - -/* eslint-disable no-restricted-globals */ - -const App: React.FC = () => { - let page = ( - <> - - - ); - - const [hash, setHash] = useState(location.hash); - - console.log(`Link for ${hash}`); - - useEffect(() => { - // Some hacky uri decoding - if (location.href.split('/').length > 4) { - location.href = decodeURIComponent(location.href); - } - - window.onhashchange = () => setHash(location.hash); - }, []); - - if (hash) { - if (hash.startsWith('#/')) { - page = ; - } else { - page = ( - - Links should be in the format {location.host}/#/{'<'} - matrix-resource-identifier{'>'} - - ); - } - } - - return ( - - -
- {page} - -
- - - ); -}; - -export default App; diff --git a/src/_color-scheme.scss b/src/_color-scheme.scss deleted file mode 100644 index 1c32596..0000000 --- a/src/_color-scheme.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -$app-background: #f4f4f4; -$background: #ffffff; -$foreground: #000000; -$font: #333333; -$grey: #666666; -$accent: #0098d4; -$error: #d6001c; -$link: #0098d4; -$borders: #f4f4f4; diff --git a/src/_error.scss b/src/_error.scss deleted file mode 100644 index 2845abb..0000000 --- a/src/_error.scss +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@mixin error { - color: $error; - border-color: $error; -} diff --git a/src/_mixins.scss b/src/_mixins.scss deleted file mode 100644 index 5cff438..0000000 --- a/src/_mixins.scss +++ /dev/null @@ -1,11 +0,0 @@ -@mixin unreal-focus { - outline-width: 2px; - outline-style: solid; - outline-color: Highlight; - - /* WebKit gets its native focus styles. */ - @media (-webkit-min-device-pixel-ratio: 0) { - outline-color: -webkit-focus-ring-color; - outline-style: auto; - } -} diff --git a/src/clients/Element.ts b/src/clients/Element.ts deleted file mode 100644 index 37e3db0..0000000 --- a/src/clients/Element.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { - LinkedClient, - Maturity, - ClientKind, - ClientId, - Platform, - AppleStoreLink, - PlayStoreLink, - FDroidLink, -} from './types'; -import { LinkKind } from '../parser/types'; -import logo from '../imgs/element.svg'; - -export const Element: LinkedClient = { - kind: ClientKind.LINKED_CLIENT, - name: 'Element', - author: 'Element', - logo: logo, - homepage: 'https://element.io', - maturity: Maturity.STABLE, - description: 'Fully-featured Matrix client', - platforms: [Platform.Desktop, Platform.Android, Platform.iOS], - experimental: false, - clientId: ClientId.Element, - toUrl: (link) => { - const params = link.arguments.originalParams.toString(); - const prefixedParams = params ? `?${params}` : ''; - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return new URL( - `https://app.element.io/#/room/${link.identifier}${prefixedParams}` - ); - case LinkKind.UserId: - return new URL( - `https://app.element.io/#/user/${link.identifier}${prefixedParams}` - ); - case LinkKind.Permalink: - return new URL( - `https://app.element.io/#/room/${link.identifier}${prefixedParams}` - ); - case LinkKind.GroupId: - return new URL( - `https://app.element.io/#/group/${link.identifier}${prefixedParams}` - ); - } - }, - linkSupport: () => true, - installLinks: [ - new AppleStoreLink('vector', 'id1083446067'), - new PlayStoreLink('im.vector.app'), - new FDroidLink('im.vector.app'), - ], -}; - -export const ElementDevelop: LinkedClient = { - kind: ClientKind.LINKED_CLIENT, - name: 'Element Develop', - author: 'Element', - logo: logo, - homepage: 'https://element.io', - maturity: Maturity.STABLE, - description: 'Fully-featured Matrix client for the Web', - platforms: [Platform.Desktop], - experimental: true, - clientId: ClientId.ElementDevelop, - toUrl: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return new URL( - `https://develop.element.io/#/room/${link.identifier}` - ); - case LinkKind.UserId: - return new URL( - `https://develop.element.io/#/user/${link.identifier}` - ); - case LinkKind.Permalink: - return new URL( - `https://develop.element.io/#/room/${link.identifier}` - ); - case LinkKind.GroupId: - return new URL( - `https://develop.element.io/#/group/${link.identifier}` - ); - } - }, - linkSupport: () => true, - installLinks: [], -}; diff --git a/src/clients/Fractal.tsx b/src/clients/Fractal.tsx deleted file mode 100644 index f9ea9a2..0000000 --- a/src/clients/Fractal.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import { TextClient, Maturity, ClientKind, ClientId, Platform } from './types'; - -import { LinkKind } from '../parser/types'; - -import logo from '../imgs/fractal.png'; - -const Fractal: TextClient = { - kind: ClientKind.TEXT_CLIENT, - name: 'Fractal', - logo: logo, - author: 'Daniel Garcia Moreno', - homepage: 'https://github.com/poljar/weechat-matrix', - maturity: Maturity.BETA, - experimental: false, - platforms: [Platform.Desktop], - clientId: ClientId.Fractal, - toInviteString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - case LinkKind.UserId: - return ( - - Click the '+' button in the top right and paste the - identifier - - ); - default: - return Fractal doesn't support this kind of link; - } - }, - copyString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - case LinkKind.UserId: - return `${link.identifier}`; - default: - return ''; - } - }, - linkSupport: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - case LinkKind.UserId: - return true; - default: - return false; - } - }, - - description: 'Fractal is a Matrix Client written in Rust', - installLinks: [], -}; - -export default Fractal; diff --git a/src/clients/Nheko.tsx b/src/clients/Nheko.tsx deleted file mode 100644 index f9201d3..0000000 --- a/src/clients/Nheko.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import { TextClient, Maturity, ClientKind, ClientId, Platform } from './types'; - -import { LinkKind } from '../parser/types'; - -import logo from '../imgs/nheko.svg'; - -const Nheko: TextClient = { - kind: ClientKind.TEXT_CLIENT, - name: 'Nheko', - logo: logo, - author: 'mujx, red_sky, deepbluev7, Konstantinos Sideris', - homepage: 'https://github.com/Nheko-Reborn/nheko', - maturity: Maturity.BETA, - experimental: false, - platforms: [Platform.Desktop], - clientId: ClientId.Nheko, - toInviteString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return ( - - Type{' '} - - /join{' '} - - {link.identifier} - - - - ); - case LinkKind.UserId: - return ( - - Type{' '} - - /invite{' '} - - {link.identifier} - - - - ); - default: - return Nheko doesn't support this kind of link; - } - }, - copyString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return `/join ${link.identifier}`; - case LinkKind.UserId: - return `/invite ${link.identifier}`; - default: - return ''; - } - }, - linkSupport: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - case LinkKind.UserId: - return true; - default: - return false; - } - }, - description: - 'A native desktop app for Matrix that feels more like a mainstream chat app.', - installLinks: [], -}; - -export default Nheko; diff --git a/src/clients/Weechat.tsx b/src/clients/Weechat.tsx deleted file mode 100644 index 805a4b2..0000000 --- a/src/clients/Weechat.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import { TextClient, Maturity, ClientKind, ClientId, Platform } from './types'; - -import { LinkKind } from '../parser/types'; - -import logo from '../imgs/weechat.svg'; - -const Weechat: TextClient = { - kind: ClientKind.TEXT_CLIENT, - name: 'Weechat', - logo: logo, - author: 'Poljar', - homepage: 'https://github.com/poljar/weechat-matrix', - maturity: Maturity.LATE_BETA, - experimental: false, - platforms: [Platform.Desktop], - clientId: ClientId.WeeChat, - toInviteString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return ( - - Type{' '} - - /join{' '} - - {link.identifier} - - - - ); - case LinkKind.UserId: - return ( - - Type{' '} - - /invite{' '} - - {link.identifier} - - - - ); - default: - return Weechat doesn't support this kind of link; - } - }, - copyString: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - return `/join ${link.identifier}`; - case LinkKind.UserId: - return `/invite ${link.identifier}`; - default: - return ''; - } - }, - linkSupport: (link) => { - switch (link.kind) { - case LinkKind.Alias: - case LinkKind.RoomId: - case LinkKind.UserId: - return true; - default: - return false; - } - }, - - description: 'Command-line Matrix interface using Weechat', - installLinks: [], -}; - -export default Weechat; diff --git a/src/clients/index.ts b/src/clients/index.ts deleted file mode 100644 index a00cd71..0000000 --- a/src/clients/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { Client } from './types'; - -import { Element, ElementDevelop } from './Element'; -import Weechat from './Weechat'; -import Nheko from './Nheko'; -import Fractal from './Fractal'; - -/* - * All the supported clients of matrix.to - */ -const clients: Client[] = [Element, Weechat, Nheko, Fractal, ElementDevelop]; - -/* - * A map from sharer string to client. - * Configured by hand so we can change the mappings - * easily later. - */ -export const clientMap: { [key: string]: Client } = { - [Element.clientId]: Element, - [Weechat.clientId]: Weechat, - [ElementDevelop.clientId]: ElementDevelop, - [Nheko.clientId]: Nheko, - [Fractal.clientId]: Fractal, -}; - -/* - * All the supported clients of matrix.to - */ -export default clients; diff --git a/src/clients/types.ts b/src/clients/types.ts deleted file mode 100644 index 9c4c266..0000000 --- a/src/clients/types.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { SafeLink } from '../parser/types'; - -/* - * A collection of descriptive tags that can be added to - * a clients description. - */ -export enum Platform { - iOS = 'iOS', - Android = 'ANDROID', - Desktop = 'DESKTOP', -} - -/* - * A collection of states used for describing a clients maturity. - */ -export enum Maturity { - ALPHA = 'ALPHA', - LATE_ALPHA = 'LATE ALPHA', - BETA = 'BETA', - LATE_BETA = 'LATE_BETA', - STABLE = 'STABLE', -} - -/* - * Used for constructing the discriminated union of all client types. - */ -export enum ClientKind { - LINKED_CLIENT = 'LINKED_CLIENT', - TEXT_CLIENT = 'TEXT_CLIENT', -} - -export enum ClientId { - Element = 'element.io', - ElementDevelop = 'develop.element.io', - WeeChat = 'weechat', - Nheko = 'nheko', - Fractal = 'fractal', -} - -/** - * Define a native distribution channel for a client. - * E.g App store for apple, PlayStore or F-Droid for Android - */ -export interface InstallLink { - createInstallURL(deepLink: SafeLink) : string; - // in AppleStoreLink, we can set the cookie here for deeplinking - // onInstallChosen(deepLink: SafeLink); - platform: Platform; - channelId: string; - description: string; -} - -export class AppleStoreLink implements InstallLink { - constructor(private org: string, private appId: string) {} - - createInstallURL(deepLink: SafeLink) : string { - return `https://apps.apple.com/app/${encodeURIComponent(this.org)}/${encodeURIComponent(this.appId)}`; - } - - get platform() : Platform { - return Platform.iOS; - } - - get channelId(): string { - return "apple-app-store"; - } - - get description() { - return "Download on the App Store"; - } -} - -export class PlayStoreLink implements InstallLink { - constructor(private appId: string) {} - - createInstallURL(deepLink: SafeLink) : string { - return `https://play.google.com/store/apps/details?id=${encodeURIComponent(this.appId)}&referrer=${encodeURIComponent(deepLink.originalLink)}`; - } - - get platform() : Platform { - return Platform.Android; - } - - get channelId(): string { - return "play-store"; - } - - get description() { - return "Get it on Google Play"; - } -} - -export class FDroidLink implements InstallLink { - constructor(private appId: string) {} - - createInstallURL(deepLink: SafeLink) : string { - return `https://f-droid.org/packages/${encodeURIComponent(this.appId)}`; - } - - get platform() : Platform { - return Platform.Android; - } - - get channelId(): string { - return "fdroid"; - } - - get description() { - return "Get it on F-Droid"; - } -} - -/* - * The descriptive details of a client - */ -export interface ClientDescription { - name: string; - author: string; - homepage: string; - logo: string; - description: string; - platforms: Platform[]; - maturity: Maturity; - clientId: ClientId; - experimental: boolean; - linkSupport: (link: SafeLink) => boolean; - installLinks: InstallLink[]; -} - -/* - * A client which can be opened using a link with the matrix resource. - */ -export interface LinkedClient extends ClientDescription { - kind: ClientKind.LINKED_CLIENT; - toUrl(parsedLink: SafeLink): URL; -} - -/* - * A client which provides isntructions for how to access the descired - * resource. - */ -export interface TextClient extends ClientDescription { - kind: ClientKind.TEXT_CLIENT; - toInviteString(parsedLink: SafeLink): JSX.Element; - copyString(parsedLink: SafeLink): string; -} - -/* - * A description for a client as well as a method for converting matrix.to - * links to the client's specific representation. - */ -export type Client = LinkedClient | TextClient; diff --git a/src/components/Avatar.scss b/src/components/Avatar.scss deleted file mode 100644 index 6767f2a..0000000 --- a/src/components/Avatar.scss +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.avatar { - border-radius: 100%; - border: 1px solid $borders; - height: 60px; - width: 60px; -} - -.avatarNoCrop { - border-radius: 0; - border: 0; -} diff --git a/src/components/Avatar.stories.tsx b/src/components/Avatar.stories.tsx deleted file mode 100644 index 1032c18..0000000 --- a/src/components/Avatar.stories.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// disable camelcase check because our object keys come -// from the matrix spec -/* eslint-disable @typescript-eslint/camelcase */ - -import React from 'react'; - -import { UserAvatar } from './Avatar'; - -export default { - title: 'Avatar', - parameters: { - design: { - type: 'figma', - url: - 'https://www.figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853', - }, - }, -}; - -export const Default: React.FC<{}> = () => ( - -); diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx deleted file mode 100644 index adf99ac..0000000 --- a/src/components/Avatar.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useEffect, useState } from 'react'; -import classNames from 'classnames'; - -import { Group, Room, User } from '../matrix-cypher'; -import useHSs from '../utils/getHS'; -import { getThumbnailURI } from '../utils/cypher-wrapper'; -import logo from '../imgs/chat-icon.svg'; - -import './Avatar.scss'; - -const AVATAR_SIZE = 96; - -interface IProps { - className?: string; - avatarUrl: string; - label: string; -} - -const Avatar: React.FC = ({ className, avatarUrl, label }: IProps) => { - const [src, setSrc] = useState(avatarUrl); - useEffect(() => { - setSrc(avatarUrl ? avatarUrl : logo); - }, [avatarUrl]); - - const _className = classNames('avatar', className, { - avatarNoCrop: src === logo, - }); - return ( - setSrc(logo)} - alt={label} - className={_className} - /> - ); -}; - -interface IPropsUserAvatar { - user: User; - userId: string; -} - -export const UserAvatar: React.FC = ({ - user, - userId, -}: IPropsUserAvatar) => { - const [hs] = useHSs({ identifier: userId }); - return ( - - ); -}; - -interface IPropsRoomAvatar { - room: Room; -} - -export const RoomAvatar: React.FC = ({ - room, -}: IPropsRoomAvatar) => { - const [hs] = useHSs({ identifier: room.room_id }); - return ( - - ); -}; - -interface IPropsGroupAvatar { - group: Group; - groupId: string; -} - -export const GroupAvatar: React.FC = ({ - group, - groupId, -}: IPropsGroupAvatar) => { - const [hs] = useHSs({ identifier: groupId }); - return ( - - ); -}; - -export default Avatar; diff --git a/src/components/Button.scss b/src/components/Button.scss deleted file mode 100644 index 25997ab..0000000 --- a/src/components/Button.scss +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.button { - width: 100%; - - height: 48px; - - border-radius: 24px; - border: 0; - - background-color: $accent; - color: $background; - - font-size: 15px; - font-weight: 500; - - display: inline-flex; - justify-content: center; - align-items: center; - - &:hover { - cursor: pointer; - } - - position: relative; - - .buttonIcon { - position: absolute; - height: 24px; - width: 24px; - - left: 18px; - top: 12px; - } -} - -.buttonSecondary { - background-color: $background; - color: $foreground; - border: 1px solid $foreground; -} - -.errorButton:hover { - cursor: not-allowed; -} - -.buttonHighlight { - background-color: $accent; -} diff --git a/src/components/Button.stories.tsx b/src/components/Button.stories.tsx deleted file mode 100644 index 25cfee8..0000000 --- a/src/components/Button.stories.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { action } from '@storybook/addon-actions'; -import { text } from '@storybook/addon-knobs'; - -import Button from './Button'; - -export default { title: 'Button' }; - -export const WithText: React.FC = () => ( - -); - -export const Secondary: React.FC = () => ( - -); diff --git a/src/components/Button.tsx b/src/components/Button.tsx deleted file mode 100644 index fd031f2..0000000 --- a/src/components/Button.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import classnames from 'classnames'; - -import './Button.scss'; - -interface IProps extends React.ButtonHTMLAttributes { - // Briefly display these instead of the children onClick - flashChildren?: React.ReactNode; - secondary?: boolean; - icon?: string; - flashIcon?: string; -} - -/** - * Like a normal button except it will flash content when clicked. - */ -const Button: React.FC< - IProps & React.RefAttributes -> = React.forwardRef( - ( - { - onClick, - children, - flashChildren, - className, - secondary, - icon, - flashIcon, - ...props - }: IProps, - ref: React.Ref - ) => { - const [wasClicked, setWasClicked] = React.useState(false); - - const wrappedOnClick: React.MouseEventHandler = (e) => { - if (onClick) { - onClick(e); - } - - setWasClicked(true); - window.setTimeout(() => { - setWasClicked(false); - }, 1000); - }; - - const content = wasClicked && flashChildren ? flashChildren : children; - - const classNames = classnames('button', className, { - buttonHighlight: wasClicked, - buttonSecondary: secondary, - }); - - const iconSrc = wasClicked && flashIcon ? flashIcon : icon; - - const buttonIcon = icon ? ( - - ) : null; - - return ( - - ); - } -); - -export default Button; diff --git a/src/components/ClientList.scss b/src/components/ClientList.scss deleted file mode 100644 index 24c8823..0000000 --- a/src/components/ClientList.scss +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.clientList { - display: grid; - row-gap: 20px; - list-style-type: none; - padding-inline-start: 0; -} diff --git a/src/components/ClientList.tsx b/src/components/ClientList.tsx deleted file mode 100644 index 64dbebc..0000000 --- a/src/components/ClientList.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useContext } from 'react'; -import { UAContext } from '@quentin-sommer/react-useragent'; - -import { SafeLink } from '../parser/types'; -import { ActionType, ClientContext } from '../contexts/ClientContext'; -import Clients from '../clients'; -import { Client, Platform } from '../clients/types'; -import ClientTile from './ClientTile'; - -import './ClientList.scss'; - -interface IProps { - link: SafeLink; - rememberSelection: boolean; -} - -const ClientList: React.FC = ({ link, rememberSelection }: IProps) => { - const [ - { showOnlyDeviceClients, showExperimentalClients }, - clientDispatcher, - ] = useContext(ClientContext); - const { uaResults } = useContext(UAContext); - - /* - * Function to decide whether a client is shown - */ - const showClient = (client: Client): boolean => { - let showClient = false; - - if (!showOnlyDeviceClients || uaResults === {}) { - showClient = true; - } - - for (const platform of client.platforms) { - switch (platform) { - case Platform.Desktop: - showClient = showClient || !(uaResults as any).mobile; - break; - case Platform.iOS: - showClient = showClient || (uaResults as any).ios; - break; - case Platform.Android: - showClient = showClient || (uaResults as any).android; - break; - } - } - - if (!showExperimentalClients && client.experimental) { - showClient = false; - } - - if (!client.linkSupport(link)) { - showClient = false; - } - - return showClient; - }; - - const clientLi = (client: Client): JSX.Element => ( -
  • - rememberSelection - ? clientDispatcher({ - action: ActionType.SetClient, - clientId: client.clientId, - }) - : undefined - } - > - -
  • - ); - - return ( -
      - {Clients.filter(showClient).map(clientLi)} -
    - ); -}; - -export default ClientList; diff --git a/src/components/ClientSelection.scss b/src/components/ClientSelection.scss deleted file mode 100644 index f4977b7..0000000 --- a/src/components/ClientSelection.scss +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.advanced { - display: grid; - row-gap: 20px; - - .advancedOptions { - display: flex; - flex-direction: column; - - align-items: flex-start; - } -} diff --git a/src/components/ClientSelection.tsx b/src/components/ClientSelection.tsx deleted file mode 100644 index 6e5ed8d..0000000 --- a/src/components/ClientSelection.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useContext, useState } from 'react'; - -import './ClientSelection.scss'; -import { ActionType, ClientContext } from '../contexts/ClientContext'; -import ClientList from './ClientList'; -import { SafeLink } from '../parser/types'; -import Button from './Button'; -import StyledCheckbox from './StyledCheckbox'; - -interface IProps { - link: SafeLink; -} - -const ClientSelection: React.FC = ({ link }: IProps) => { - const [clientState, clientStateDispatch] = useContext(ClientContext); - const [rememberSelection, setRememberSelection] = useState(false); - const options = ( -
    - { - setRememberSelection(!rememberSelection); - }} - checked={rememberSelection} - > - Remember for future invites in this browser - - { - clientStateDispatch({ - action: ActionType.ToggleShowOnlyDeviceClients, - }); - }} - checked={clientState.showOnlyDeviceClients} - > - Show only clients suggested for this device - - { - clientStateDispatch({ - action: ActionType.ToggleShowExperimentalClients, - }); - }} - checked={clientState.showExperimentalClients} - > - Show experimental clients - -
    - ); - - const clearSelection = - clientState.clientId !== null ? ( - - ) : null; - - return ( -
    - {options} - - {clearSelection} -
    - ); -}; - -export default ClientSelection; diff --git a/src/components/ClientTile.scss b/src/components/ClientTile.scss deleted file mode 100644 index a697aea..0000000 --- a/src/components/ClientTile.scss +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.clientTile { - display: flex; - flex-direction: row; - align-items: flex-start; - - min-height: 150px; - width: 100%; - - color: $foreground; - - > img { - flex-shrink: 0; - height: 116px; - width: 116px; - margin-right: 14px; - border-radius: 16px; - } - - > div { - display: flex; - flex-direction: column; - justify-content: space-between; - h1 { - text-align: left; - font-size: 14px; - line-height: 24px; - } - - p { - margin-right: 8px; - text-align: left; - } - - .button { - height: 40px; - min-width: 130px; - max-width: 165px; - margin-top: 16px; - } - } - - border-radius: 8px; - - padding: 15px; - - // For the chevron - position: relative; - - &:hover { - background-color: $app-background; - } - - .installLink { - display: inline-block; - height: 40px; - margin: 8px 16px 8px 0; - img { - height: 100%; - } - } -} - -.clientTileLink { - position: relative; - - width: 100%; -} diff --git a/src/components/ClientTile.tsx b/src/components/ClientTile.tsx deleted file mode 100644 index 9464e61..0000000 --- a/src/components/ClientTile.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useContext } from 'react'; -import classNames from 'classnames'; -import { UAContext } from '@quentin-sommer/react-useragent'; - -import { Client, ClientKind, Platform } from '../clients/types'; -import { SafeLink } from '../parser/types'; -import Tile from './Tile'; -import Button from './Button'; - -import appStoreBadge from '../imgs/app-store-us-alt.svg'; -import playStoreBadge from '../imgs/google-play-us.svg'; -import fdroidBadge from '../imgs/fdroid-badge.png'; - -import './ClientTile.scss'; - -interface IProps { - client: Client; - link: SafeLink; -} - -interface IInstallBadgeImages { - [index: string]: string; -} - -const installBadgeImages : IInstallBadgeImages = { - "fdroid": fdroidBadge, - "apple-app-store": appStoreBadge, - "play-store": playStoreBadge -}; - -const ClientTile: React.FC = ({ client, link }: IProps) => { - const inviteLine = - client.kind === ClientKind.TEXT_CLIENT ? ( -

    {client.toInviteString(link)}

    - ) : null; - - const { uaResults } = useContext(UAContext); - - const className = classNames('clientTile', { - clientTileLink: client.kind === ClientKind.LINKED_CLIENT, - }); - - let inviteButton: JSX.Element = <>; - const matchingInstallLinks = client.installLinks.filter((installLink) => { - if ((uaResults as any).ios) { - return installLink.platform === Platform.iOS; - } else if ((uaResults as any).android) { - return installLink.platform === Platform.Android; - } else { - return false; - } - }); - const hasNativeClient = matchingInstallLinks.length > 0; - let installButtons = undefined; - if (matchingInstallLinks.length) { - installButtons =

    {matchingInstallLinks.map((installLink) => { - return - {installLink.description} - ; - })}

    ; - } - - if (client.kind === ClientKind.LINKED_CLIENT) { - inviteButton = ; - } else { - const copyString = client.copyString(link); - if (copyString !== '') { - inviteButton = ( - - ); - } - } - - let clientTile = ( - - {client.name -
    -

    {client.name}

    -

    {client.description}

    - {installButtons} - {inviteLine} - {inviteButton} -
    -
    - ); - - if (client.kind === ClientKind.LINKED_CLIENT) { - if (!hasNativeClient) { - clientTile = ( - {clientTile} - ); - } - } - - return clientTile; -}; - -export default ClientTile; diff --git a/src/components/CreateLinkTile.scss b/src/components/CreateLinkTile.scss deleted file mode 100644 index e302aff..0000000 --- a/src/components/CreateLinkTile.scss +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.createLinkTile { - row-gap: 24px; - - * { - width: 100%; - } - - > form { - display: grid; - row-gap: 24px; - align-self: center; - } - - > a { - color: $foreground; - font-weight: bold; - font-size: 24px; - line-height: 32px; - text-align: left; - text-decoration: underline; - } - - .createLinkReset { - height: 40px; - width: 40px; - - border-radius: 100%; - border: 1px solid lighten($grey, 50%); - - background: $background; - - padding: 6px; - - position: relative; - - > div { - // This is a terrible case of faking it till - // we make it. It will break. I'm so sorry - position: absolute; - display: none; - - width: max-content; - top: -35px; - left: -17px; - - border-radius: 30px; - padding: 5px 15px; - - background: $background; - - word-wrap: none; - } - - img { - height: 100%; - width: 100%; - border: 0; - - filter: invert(12%); - } - - &:hover { - border: 0; - - background: $foreground; - - cursor: pointer; - - > div { - display: block; - } - } - } -} diff --git a/src/components/CreateLinkTile.stories.tsx b/src/components/CreateLinkTile.stories.tsx deleted file mode 100644 index 48d7e5c..0000000 --- a/src/components/CreateLinkTile.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import CreateLinkTile from './CreateLinkTile'; - -export default { - title: 'CreateLinkTile', - parameters: { - design: { - type: 'figma', - url: - 'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1', - }, - }, -}; - -export const Default: React.FC = () => ; diff --git a/src/components/CreateLinkTile.tsx b/src/components/CreateLinkTile.tsx deleted file mode 100644 index 35950fd..0000000 --- a/src/components/CreateLinkTile.tsx +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useEffect, useRef } from 'react'; -import { Formik, Form } from 'formik'; - -import Tile from './Tile'; -import Button from './Button'; -import Input from './Input'; -import { parseHash } from '../parser/parser'; -import { LinkKind } from '../parser/types'; -import linkIcon from '../imgs/link.svg'; -import copyIcon from '../imgs/copy.svg'; -import tickIcon from '../imgs/tick.svg'; -import refreshIcon from '../imgs/refresh.svg'; -import './CreateLinkTile.scss'; - -interface ILinkNotCreatedTileProps { - setLink: React.Dispatch>; -} - -interface FormValues { - identifier: string; -} - -// Hacky use of types here -function validate(values: FormValues): Partial { - const errors: Partial = {}; - - if (values.identifier === '') { - errors.identifier = ''; - return errors; - } - - const parse = parseHash(values.identifier); - - if (parse.kind === LinkKind.ParseFailed) { - errors.identifier = - "That identifier doesn't look right. Double check the details."; - } - - return errors; -} - -const LinkNotCreatedTile: React.FC = ( - props: ILinkNotCreatedTileProps -) => { - return ( - -

    - Create shareable links to Matrix rooms, users or messages - without being tied to any app -

    - { - props.setLink( - document.location.protocol + - '//' + - document.location.host + - '/#/' + - values.identifier - ); - }} - > - {(formik): JSX.Element => ( -
    - - -
    - )} -
    -
    - ); -}; - -interface ILinkCreatedTileProps { - link: string; - setLink: React.Dispatch>; -} - -const LinkCreatedTile: React.FC = (props) => { - const buttonRef = useRef(null); - - // Focus button on render - useEffect((): void => { - if (buttonRef && buttonRef.current) { - buttonRef.current.focus(); - } - }); - - return ( - - - - {props.link} - - - - ); -}; - -const CreateLinkTile: React.FC = () => { - const [link, setLink] = React.useState(''); - if (!link) { - return ; - } else { - return ; - } -}; - -export default CreateLinkTile; diff --git a/src/components/DefaultPreview.scss b/src/components/DefaultPreview.scss deleted file mode 100644 index 21b77bb..0000000 --- a/src/components/DefaultPreview.scss +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.defaultPreview { - .avatar { - border-radius: 0; - border: 0; - } -} diff --git a/src/components/DefaultPreview.tsx b/src/components/DefaultPreview.tsx deleted file mode 100644 index 00f4b91..0000000 --- a/src/components/DefaultPreview.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import { SafeLink } from '../parser/types'; -import Avatar from './Avatar'; - -import './DefaultPreview.scss'; - -import genericRoomPreview from '../imgs/chat-icon.svg'; - -interface IProps { - link: SafeLink; -} - -const DefaultPreview: React.FC = ({ link }: IProps) => { - return ( -
    - -

    {link.identifier}

    -
    - ); -}; - -export default DefaultPreview; diff --git a/src/components/Details.scss b/src/components/Details.scss deleted file mode 100644 index 8fc8e6f..0000000 --- a/src/components/Details.scss +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../mixins'; - -.details { - display: flex; - - > :first-child { - min-width: 100%; - } - - > input[type='checkbox'] { - // Remove the OS's representation - display: none; - - &.focus-visible { - & + img { - @include unreal-focus; - } - } - - &:checked { - & + img { - transform: rotate(180deg); - } - } - } - - &:hover { - cursor: pointer; - } -} diff --git a/src/components/Details.tsx b/src/components/Details.tsx deleted file mode 100644 index afa91d8..0000000 --- a/src/components/Details.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import chevron from '../imgs/chevron-down.svg'; - -import './Details.scss'; - -interface IProps extends React.InputHTMLAttributes { - children?: React.ReactNode; -} - -const Details: React.FC = ({ children, ...props }: IProps) => ( - -); - -export default Details; diff --git a/src/components/EventPreview.tsx b/src/components/EventPreview.tsx deleted file mode 100644 index 00ac979..0000000 --- a/src/components/EventPreview.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { Room, Event } from '../matrix-cypher'; - -import RoomPreview from './RoomPreview'; - -interface IProps { - room: Room; - event: Event; -} - -const EventPreview: React.FC = ({ room, event }: IProps) => ( - <> - -

    "{event.content}"

    -

    {event.sender}

    - -); - -export default EventPreview; diff --git a/src/components/FakeProgress.scss b/src/components/FakeProgress.scss deleted file mode 100644 index 32cd224..0000000 --- a/src/components/FakeProgress.scss +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.fakeProgress { - width: 100%; - height: 4px; - background-color: lighten($grey, 50%); - border-radius: 4px; - - > div { - width: 60%; - height: 100%; - background-color: $foreground; - border-radius: 4px; - } -} diff --git a/src/components/FakeProgress.tsx b/src/components/FakeProgress.tsx deleted file mode 100644 index 587a7e3..0000000 --- a/src/components/FakeProgress.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import './FakeProgress.scss'; - -const FakeProgress = () => ( -
    -
    -
    -); - -export default FakeProgress; diff --git a/src/components/Footer.scss b/src/components/Footer.scss deleted file mode 100644 index ba0f90b..0000000 --- a/src/components/Footer.scss +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.footer { - display: grid; - grid-auto-flow: column; - justify-content: center; - column-gap: 5px; - font-size: 11px; - - * { - color: $font; - } - - .textButton { - margin: 0; - padding: 0; - } -} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx deleted file mode 100644 index ca8dba5..0000000 --- a/src/components/Footer.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useContext } from 'react'; - -import HSContext, { - HSOptions, - ActionType as HSACtionType, -} from '../contexts/HSContext'; -import ClientContext, { - ActionType as ClientActionType, -} from '../contexts/ClientContext'; -import TextButton from './TextButton'; - -import './Footer.scss'; - -const Footer: React.FC = () => { - const [hsState, hsDispatch] = useContext(HSContext); - const [clientState, clientDispatch] = useContext(ClientContext); - - const clear = - hsState.option !== HSOptions.Unset || clientState.clientId !== null ? ( - <> - {' · '} - { - hsDispatch({ - action: HSACtionType.Clear, - }); - clientDispatch({ - action: ClientActionType.ClearClient, - }); - }} - > - Clear preferences - - - ) : null; - - return ( -
    - GitHub project - {' · '} - - Add your app - - {clear} -
    - ); -}; - -export default Footer; diff --git a/src/components/GroupPreview.scss b/src/components/GroupPreview.scss deleted file mode 100644 index aae3aba..0000000 --- a/src/components/GroupPreview.scss +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.groupPreview { - > .avatar { - margin-bottom: 8px; - } - - > h1 { - font-size: 24px; - margin-bottom: 4px; - } -} diff --git a/src/components/GroupPreview.tsx b/src/components/GroupPreview.tsx deleted file mode 100644 index 49a2c22..0000000 --- a/src/components/GroupPreview.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { Group } from '../matrix-cypher'; - -import { GroupAvatar } from './Avatar'; - -import './GroupPreview.scss'; - -interface IProps { - group: Group; - groupId: string; -} - -const GroupPreview: React.FC = ({ group, groupId }: IProps) => { - const description = group.long_description - ? group.long_description - : group.short_description - ? group.short_description - : null; - - return ( -
    - -

    {group.name}

    - {description ?

    {description}

    : null} -
    - ); -}; - -export default GroupPreview; diff --git a/src/components/HomeserverOptions.scss b/src/components/HomeserverOptions.scss deleted file mode 100644 index f0736c0..0000000 --- a/src/components/HomeserverOptions.scss +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.homeserverOptions { - display: grid; - row-gap: 20px; - - background: $app-background; - text-align: left; - - > * { - width: 100%; - } - - .homeserverOptionsDescription { - width: 100%; - - display: flex; - flex-direction: row; - justify-content: space-between; - - > p { - flex-grow: 1; - } - - > img { - flex-shrink: 0; - flex-grow: 0; - background-color: $background; - height: 62px; - width: 62px; - padding: 11px; - border-radius: 100%; - margin-left: 14px; - } - } - - form { - display: grid; - row-gap: 25px; - } -} diff --git a/src/components/HomeserverOptions.stories.tsx b/src/components/HomeserverOptions.stories.tsx deleted file mode 100644 index b6e112e..0000000 --- a/src/components/HomeserverOptions.stories.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import HomeserverOptions from './HomeserverOptions'; -import { LinkKind } from '../parser/types'; - -export default { - title: 'HomeserverOptions', - parameters: { - design: { - type: 'figma', - url: - 'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=143%3A5853', - }, - }, -}; - -export const Default: React.FC = () => ( - -); diff --git a/src/components/HomeserverOptions.tsx b/src/components/HomeserverOptions.tsx deleted file mode 100644 index 750b1c0..0000000 --- a/src/components/HomeserverOptions.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useContext, useState } from 'react'; -import { Formik, Form } from 'formik'; -import { string } from 'zod'; - -import Tile from './Tile'; -import HSContext, { TempHSContext, ActionType } from '../contexts/HSContext'; -import icon from '../imgs/telecom-mast.svg'; -import Button from './Button'; -import Input from './Input'; -import StyledCheckbox from './StyledCheckbox'; -import { SafeLink } from '../parser/types'; - -import './HomeserverOptions.scss'; - -interface IProps { - link: SafeLink; -} - -interface FormValues { - HSUrl: string; -} - -function validateURL(values: FormValues): Partial { - const errors: Partial = {}; - try { - string().url().parse(values.HSUrl); - } catch { - errors.HSUrl = - 'This must be a valid homeserver URL, starting with https://'; - } - return errors; -} - -const HomeserverOptions: React.FC = ({ link }: IProps) => { - const HSStateDispatcher = useContext(HSContext)[1]; - const TempHSStateDispatcher = useContext(TempHSContext)[1]; - - const [rememberSelection, setRemeberSelection] = useState(false); - - // Select which disaptcher to use based on whether we're writing - // the choice to localstorage - const dispatcher = rememberSelection - ? HSStateDispatcher - : TempHSStateDispatcher; - - const hsInput = ( - - dispatcher({ action: ActionType.SetHS, HSURL: HSUrl }) - } - > - {({ values, errors }): JSX.Element => ( -
    - - {values.HSUrl && !errors.HSUrl ? ( - - ) : null} -
    - )} -
    - ); - - return ( - -
    -
    -

    - About  - - {link.identifier} - -

    -

    - A homeserver will show you metadata about the link, like - a description. Homeservers will be able to relate your - IP to things you've opened invites for in matrix.to. -

    -
    - Icon making it clear that connections may be made with external services -
    - setRemeberSelection(e.target.checked)} - > - Remember my choice - - - {hsInput} -
    - ); -}; - -export default HomeserverOptions; diff --git a/src/components/Input.scss b/src/components/Input.scss deleted file mode 100644 index 8163678..0000000 --- a/src/components/Input.scss +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; -@import '../error'; - -.input { - width: 100%; - padding: 12px; - - background: $background; - - border: 1px solid $foreground; - border-radius: 16px; - - font: lighten($grey, 60%); - font-size: 14px; - line-height: 24px; - - &.error { - @include error; - } - - &:focus { - border: 1px solid $font; - font: $font; - } -} - -.inputError { - @include error; - text-align: center; -} - -.inputMuted { - border-color: lighten($grey, 60%); -} diff --git a/src/components/Input.stories.tsx b/src/components/Input.stories.tsx deleted file mode 100644 index f45e5ab..0000000 --- a/src/components/Input.stories.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { withDesign } from 'storybook-addon-designs'; -import { Formik, Form } from 'formik'; - -import Input from './Input'; - -export default { - title: 'Input', - parameters: { - design: { - type: 'figma', - url: - 'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A1', - }, - }, - decorators: [withDesign], -}; - -export const Default: React.FC = () => ( - {}}> -
    - -
    -
    -); diff --git a/src/components/Input.tsx b/src/components/Input.tsx deleted file mode 100644 index 21ce342..0000000 --- a/src/components/Input.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import classnames from 'classnames'; -import { useField } from 'formik'; - -import './Input.scss'; - -interface IProps extends React.InputHTMLAttributes { - name: string; - type: string; - muted?: boolean; -} - -const Input: React.FC = ({ className, muted, ...props }) => { - const [field, meta] = useField(props); - - const errorBool = meta.touched && meta.value !== '' && meta.error; - const error = errorBool ? ( -
    {meta.error}
    - ) : null; - - const classNames = classnames('input', className, { - error: errorBool, - inputMuted: !!muted, - }); - - return ( - <> - - {error} - - ); -}; - -export default Input; diff --git a/src/components/InviteTile.scss b/src/components/InviteTile.scss deleted file mode 100644 index f9a8cdd..0000000 --- a/src/components/InviteTile.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.inviteTile { - display: grid; - row-gap: 24px; - - .inviteTileClientSelection { - margin: 0 auto; - display: grid; - - justify-content: space-between; - row-gap: 20px; - - h2 + p { - color: $foreground; - } - } - - hr { - width: 100%; - margin: 0; - } -} diff --git a/src/components/InviteTile.stories.tsx b/src/components/InviteTile.stories.tsx deleted file mode 100644 index b92870c..0000000 --- a/src/components/InviteTile.stories.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// disable camelcase check because our object keys come -// from the matrix spec -/* eslint-disable @typescript-eslint/camelcase */ - -import React from 'react'; - -import InviteTile from './InviteTile'; -import UserPreview, { InviterPreview } from './UserPreview'; -import RoomPreview, { RoomPreviewWithTopic } from './RoomPreview'; -import Clients from '../clients'; -import { LinkKind, SafeLink } from '../parser/types'; - -export default { - title: 'InviteTile', - parameters: { - design: { - type: 'figma', - url: - 'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=59%3A334', - }, - }, -}; - -const userLink: SafeLink = { - kind: LinkKind.UserId, - identifier: '@jorik:matrix.org', - arguments: { - vias: [], - originalParams: new URLSearchParams(), - }, - originalLink: 'asdfsadf', -}; - -const roomLink: SafeLink = { - kind: LinkKind.Alias, - identifier: '#element-dev:matrix.org', - arguments: { - vias: [], - originalParams: new URLSearchParams(), - }, - originalLink: 'asdfsadf', -}; - -export const withLink: React.FC<{}> = () => ( - - This is an invite with a link - -); - -export const withInstruction: React.FC<{}> = () => ( - - This is an invite with an instruction - -); - -export const withUserPreview: React.FC<{}> = () => ( - - - -); - -export const withRoomPreviewAndRoomTopic: React.FC<{}> = () => ( - - - -); - -export const withRoomPreviewAndInviter: React.FC<{}> = () => ( - - - - -); diff --git a/src/components/InviteTile.tsx b/src/components/InviteTile.tsx deleted file mode 100644 index 76e8ef4..0000000 --- a/src/components/InviteTile.tsx +++ /dev/null @@ -1,120 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useState } from 'react'; - -import './InviteTile.scss'; - -import Tile from './Tile'; -import LinkButton from './LinkButton'; -import Button from './Button'; -import ClientSelection from './ClientSelection'; -import { Client, ClientKind } from '../clients/types'; -import { SafeLink } from '../parser/types'; -import TextButton from './TextButton'; - -interface IProps { - children?: React.ReactNode; - client: Client | null; - link: SafeLink; -} - -const InviteTile: React.FC = ({ children, client, link }: IProps) => { - const [showAdvanced, setShowAdvanced] = useState(false); - let invite: React.ReactNode; - let advanced: React.ReactNode; - - if (client === null) { - invite = showAdvanced ? null : ( - - ); - } else { - let inviteUseString: string; - - switch (client.kind) { - case ClientKind.LINKED_CLIENT: - invite = ( - - Accept invite - - ); - inviteUseString = `Accepting will open this link in ${client.name}.`; - break; - case ClientKind.TEXT_CLIENT: - // TODO: copy to clipboard - invite =

    {client.toInviteString(link)}

    ; - navigator.clipboard?.writeText(client.copyString(link)); - inviteUseString = `These are instructions for ${client.name}.`; - break; - } - - const advancedButton = ( -

    - {inviteUseString} - setShowAdvanced(!showAdvanced)} - > - Change client - -

    - ); - - invite = ( - <> - {invite} - {advancedButton} - - ); - } - - if (showAdvanced) { - if (client === null) { - advanced = ( - <> -
    -

    Almost done!

    -

    Great, pick a client below to confirm and continue

    - - - ); - } else { - advanced = ( - <> -
    -

    Change app

    - - - ); - } - } - - advanced = advanced ? ( -
    {advanced}
    - ) : null; - return ( - <> - - {children} - {invite} - {advanced} - - - ); -}; - -export default InviteTile; diff --git a/src/components/InvitingClientTile.stories.tsx b/src/components/InvitingClientTile.stories.tsx deleted file mode 100644 index 6cac6ee..0000000 --- a/src/components/InvitingClientTile.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import ClientTile from './InvitingClientTile'; - -export default { title: 'ClientTile' }; - -export const Element = ; diff --git a/src/components/InvitingClientTile.tsx b/src/components/InvitingClientTile.tsx deleted file mode 100644 index 2d22a6c..0000000 --- a/src/components/InvitingClientTile.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import Tile from './Tile'; - -import { clientMap } from '../clients'; -import './MatrixTile.scss'; - -interface IProps { - clientName: string; -} - -const InvitingClientTile: React.FC = ({ clientName }: IProps) => { - const client = clientMap[clientName]; - - if (!client) { - return ( - - {/* TODO: add gh link */} -

    - The client that created this link "{clientName}" is not a - recognised client. If this is a mistake and you'd like a - nice advertisement for it here please{' '} - - open a pr - - . -

    -
    - ); - } - - return ( - - {client.name} -

    - Invite created with {client.name} -

    -
    {client.description}
    -
    - ); -}; - -export default InvitingClientTile; diff --git a/src/components/LinkButton.tsx b/src/components/LinkButton.tsx deleted file mode 100644 index 375f926..0000000 --- a/src/components/LinkButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import classnames from 'classnames'; - -import './Button.scss'; - -interface IProps extends React.LinkHTMLAttributes {} - -const LinkButton: React.FC = ({ - className, - children, - ...props -}: IProps) => ( - - {children} - -); - -export default LinkButton; diff --git a/src/components/LinkPreview.tsx b/src/components/LinkPreview.tsx deleted file mode 100644 index e5355ef..0000000 --- a/src/components/LinkPreview.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useState, useEffect, useContext } from 'react'; -import { getEvent, client } from '../matrix-cypher'; - -import { RoomPreviewWithTopic } from './RoomPreview'; -import InviteTile from './InviteTile'; -import { SafeLink, LinkKind } from '../parser/types'; -import UserPreview, { WrappedInviterPreview } from './UserPreview'; -import EventPreview from './EventPreview'; -import GroupPreview from './GroupPreview'; -import HomeserverOptions from './HomeserverOptions'; -import DefaultPreview from './DefaultPreview'; -import Details from './Details'; -import { clientMap } from '../clients'; -import { - getRoomFromId, - getRoomFromAlias, - getRoomFromPermalink, - getUser, - getGroup, -} from '../utils/cypher-wrapper'; -import { ClientContext } from '../contexts/ClientContext'; -import useHSs from '../utils/getHS'; - -interface IProps { - link: SafeLink; -} - -const invite = async ({ - clientAddress, - link, -}: { - clientAddress: string; - link: SafeLink; -}): Promise => { - // TODO: replace with client fetch - switch (link.kind) { - case LinkKind.Alias: - return ( - - ); - - case LinkKind.RoomId: - return ( - - ); - - case LinkKind.UserId: - return ( - - ); - - case LinkKind.Permalink: - return ( - - ); - - case LinkKind.GroupId: - return ( - - ); - - default: - // Todo Implement events - return <>; - } -}; - -interface PreviewProps extends IProps { - client: string; -} - -const Preview: React.FC = ({ link, client }: PreviewProps) => { - const [content, setContent] = useState(); - - // TODO: support multiple clients with vias - useEffect(() => { - (async (): Promise => - setContent( - await invite({ - clientAddress: client, - link, - }) - ))(); - }, [link, client]); - - return content; -}; - -const LinkPreview: React.FC = ({ link }: IProps) => { - let content: JSX.Element; - const [showHSOptions, setShowHSOPtions] = useState(false); - - const hses = useHSs({ link }); - - if (!hses.length) { - content = ( - <> - -
    setShowHSOPtions(!showHSOptions)} - > - - About  - - {link.identifier} - - -
    - - ); - if (showHSOptions) { - content = ( - <> - {content} - - - ); - } - } else { - content = ; - } - - const [{ clientId }] = useContext(ClientContext); - - // Select which client to link to - const displayClientId = clientId - ? clientId - : link.arguments.client - ? link.arguments.client - : null; - - const client = displayClientId ? clientMap[displayClientId] : null; - - const sharer = link.arguments.sharer ? ( - - ) : ( -

    You're invited to join

    - ); - - return ( - - {sharer} - {content} - - ); -}; - -export default LinkPreview; diff --git a/src/components/MatrixTile.scss b/src/components/MatrixTile.scss deleted file mode 100644 index 6c8f17b..0000000 --- a/src/components/MatrixTile.scss +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.matrixTile { - background: none; - box-shadow: none; - row-gap: 16px; - padding: 0 40px; - justify-items: left; - text-align: left; -} diff --git a/src/components/MatrixTile.stories.tsx b/src/components/MatrixTile.stories.tsx deleted file mode 100644 index 9bc3233..0000000 --- a/src/components/MatrixTile.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import MatrixTile from './MatrixTile'; - -export default { title: 'MatrixTile' }; - -export const Default: React.FC = () => ; diff --git a/src/components/MatrixTile.tsx b/src/components/MatrixTile.tsx deleted file mode 100644 index 9808a09..0000000 --- a/src/components/MatrixTile.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import Tile from './Tile'; -import Footer from './Footer'; - -import logo from '../imgs/matrix-logo.svg'; - -import './MatrixTile.scss'; - -interface IProps { - isLink?: boolean; -} - -const MatrixTile: React.FC = ({ isLink }: IProps) => { - const copy = isLink ? ( -
    - This invite uses Matrix, an open - network for secure, decentralized communication. -
    - ) : ( -
    - Matrix.to is a stateless URL redirecting service for the{' '} - Matrix ecosystem. -
    - ); - - return ( -
    - - matrix-logo - {copy} -
    - -
    - ); -}; - -export default MatrixTile; diff --git a/src/components/RoomPreview.scss b/src/components/RoomPreview.scss deleted file mode 100644 index b7e7b36..0000000 --- a/src/components/RoomPreview.scss +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.roomPreview { - > .avatar { - margin-bottom: 8px; - } - - > h1 { - font-size: 24px; - margin-bottom: 4px; - } -} - -.roomTopic { - padding-top: 8px; -} diff --git a/src/components/RoomPreview.tsx b/src/components/RoomPreview.tsx deleted file mode 100644 index 620874a..0000000 --- a/src/components/RoomPreview.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { Room } from '../matrix-cypher'; - -import { RoomAvatar } from './Avatar'; - -import './RoomPreview.scss'; - -interface IProps { - room: Room; -} - -const RoomPreview: React.FC = ({ room }: IProps) => { - const roomAlias = room.canonical_alias - ? room.canonical_alias - : room.aliases - ? room.aliases[0] - : room.room_id; - const members = - room.num_joined_members > 0 ? ( -

    {room.num_joined_members.toLocaleString()} members

    - ) : null; - return ( -
    - -

    - {room.name ? room.name : roomAlias} -

    - {members} -

    {roomAlias}

    -
    - ); -}; - -export const RoomPreviewWithTopic: React.FC = ({ room }: IProps) => { - const topic = room.topic ?

    {room.topic}

    : null; - return ( - <> - - {topic} - - ); -}; - -export default RoomPreview; diff --git a/src/components/StyledCheckbox.scss b/src/components/StyledCheckbox.scss deleted file mode 100644 index 011e53f..0000000 --- a/src/components/StyledCheckbox.scss +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; -@import '../mixins'; - -.styledCheckbox { - display: flex; - - align-items: center; - - input[type='checkbox'] { - display: none; - - &:checked + div { - background: $foreground; - img { - display: block; - } - } - - &.focus-visible { - & + div { - @include unreal-focus; - } - } - } - - .styledCheckboxWrapper { - display: flex; - - margin-right: 5px; - border: 2px solid $foreground; - box-sizing: border-box; - border-radius: 4px; - height: 16px; - width: 16px; - - img { - height: 100%; - width: 100%; - - display: none; - } - } -} diff --git a/src/components/StyledCheckbox.tsx b/src/components/StyledCheckbox.tsx deleted file mode 100644 index 9450f4e..0000000 --- a/src/components/StyledCheckbox.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* - * Stolen from the matrix-react-sdk - */ - -import React from 'react'; - -import tick from '../imgs/tick.svg'; - -import './StyledCheckbox.scss'; - -interface IProps extends React.InputHTMLAttributes {} - -const StyledCheckbox: React.FC = ({ - children, - className, - ...otherProps -}: IProps) => ( - -); - -export default StyledCheckbox; diff --git a/src/components/TextButton.scss b/src/components/TextButton.scss deleted file mode 100644 index cdaed54..0000000 --- a/src/components/TextButton.scss +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@import '../color-scheme'; - -.textButton { - background: none; - border: none; - color: $link; - font-style: normal; - font-weight: normal; - font-size: 14px; - line-height: 24px; - - &:hover { - cursor: pointer; - } -} diff --git a/src/components/TextButton.stories.tsx b/src/components/TextButton.stories.tsx deleted file mode 100644 index 2c95dc2..0000000 --- a/src/components/TextButton.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; - -import TextButton from './TextButton'; - -export default { - title: 'TextButton', - parameters: { - design: { - type: 'figma', - url: - 'https://figma.com/file/WSXjCGc1k6FVI093qhlzOP/04-Recieving-share-link?node-id=149%3A10756', - }, - }, -}; - -export const Default: React.FC = () => ( - This is a button? -); diff --git a/src/components/TextButton.tsx b/src/components/TextButton.tsx deleted file mode 100644 index 6c9cfc5..0000000 --- a/src/components/TextButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import classnames from 'classnames'; - -import './TextButton.scss'; - -const TextButton: React.FC> = ({ - className, - ...props -}) => { - return ( -