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