implement homeserver consent stage
This commit is contained in:
parent
285ef27b0b
commit
c68e00f7a2
7 changed files with 268 additions and 23 deletions
38
css/main.css
38
css/main.css
|
@ -61,6 +61,11 @@ textarea {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button, input {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.RootView {
|
.RootView {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
|
@ -174,6 +179,39 @@ input[type='text'].large {
|
||||||
box-sizing: border-box;
|
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 {
|
.LoadServerPolicyView {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class Preferences {
|
||||||
this.clientId = null;
|
this.clientId = null;
|
||||||
// used to differentiate web from native if a client supports both
|
// used to differentiate web from native if a client supports both
|
||||||
this.platform = null;
|
this.platform = null;
|
||||||
this.homeservers = [];
|
this.homeservers = null;
|
||||||
|
|
||||||
const prefsStr = localStorage.getItem("preferred_client");
|
const prefsStr = localStorage.getItem("preferred_client");
|
||||||
if (prefsStr) {
|
if (prefsStr) {
|
||||||
|
@ -30,6 +30,10 @@ export class Preferences {
|
||||||
this.clientId = id;
|
this.clientId = id;
|
||||||
this.platform = Platform[platform];
|
this.platform = Platform[platform];
|
||||||
}
|
}
|
||||||
|
const serversStr = localStorage.getItem("consented_servers");
|
||||||
|
if (serversStr) {
|
||||||
|
this.homeservers = JSON.parse(serversStr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setClient(id, platform) {
|
setClient(id, platform) {
|
||||||
|
@ -40,16 +44,19 @@ export class Preferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
setHomeservers(homeservers) {
|
setHomeservers(homeservers) {
|
||||||
|
this.homeservers = homeservers;
|
||||||
|
this._localStorage.setItem("consented_servers", JSON.stringify(homeservers));
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._localStorage.removeItem("preferred_client");
|
this._localStorage.removeItem("preferred_client");
|
||||||
|
this._localStorage.removeItem("consented_servers");
|
||||||
this.clientId = null;
|
this.clientId = null;
|
||||||
this.platform = null;
|
this.platform = null;
|
||||||
|
this.homeservers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canClear() {
|
get canClear() {
|
||||||
return !!this.clientId || !!this.platform;
|
return !!this.clientId || !!this.platform || !!this.homeservers;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,10 +37,8 @@ export class RootViewModel extends ViewModel {
|
||||||
if (!oldLink || !oldLink.equals(this.link)) {
|
if (!oldLink || !oldLink.equals(this.link)) {
|
||||||
this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
|
this.openLinkViewModel = new OpenLinkViewModel(this.childOptions({
|
||||||
link: this.link,
|
link: this.link,
|
||||||
consentedServers: this.link.servers,
|
|
||||||
clients: createClients()
|
clients: createClients()
|
||||||
}));
|
}));
|
||||||
this.openLinkViewModel.load();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.openLinkViewModel = null;
|
this.openLinkViewModel = null;
|
||||||
|
|
|
@ -17,10 +17,22 @@ limitations under the License.
|
||||||
import {TemplateView} from "../utils/TemplateView.js";
|
import {TemplateView} from "../utils/TemplateView.js";
|
||||||
import {ClientListView} from "./ClientListView.js";
|
import {ClientListView} from "./ClientListView.js";
|
||||||
import {PreviewView} from "../preview/PreviewView.js";
|
import {PreviewView} from "../preview/PreviewView.js";
|
||||||
|
import {ServerConsentView} from "./ServerConsentView.js";
|
||||||
|
|
||||||
export class OpenLinkView extends TemplateView {
|
export class OpenLinkView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.div({className: "OpenLinkView card"}, [
|
return t.div({className: "OpenLinkView card"}, [
|
||||||
|
t.mapView(vm => vm.previewViewModel, previewVM => previewVM ?
|
||||||
|
new ShowLinkView(vm) :
|
||||||
|
new ServerConsentView(vm.serverConsentViewModel)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowLinkView extends TemplateView {
|
||||||
|
render(t, vm) {
|
||||||
|
return t.div([
|
||||||
t.view(new PreviewView(vm.previewViewModel)),
|
t.view(new PreviewView(vm.previewViewModel)),
|
||||||
t.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({
|
t.p({className: {accept: true, hidden: vm => vm.clientsViewModel}}, t.button({
|
||||||
className: "primary fullwidth",
|
className: "primary fullwidth",
|
||||||
|
|
|
@ -18,25 +18,43 @@ import {ViewModel} from "../utils/ViewModel.js";
|
||||||
import {ClientListViewModel} from "./ClientListViewModel.js";
|
import {ClientListViewModel} from "./ClientListViewModel.js";
|
||||||
import {ClientViewModel} from "./ClientViewModel.js";
|
import {ClientViewModel} from "./ClientViewModel.js";
|
||||||
import {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
import {PreviewViewModel} from "../preview/PreviewViewModel.js";
|
||||||
|
import {ServerConsentViewModel} from "./ServerConsentViewModel.js";
|
||||||
import {getLabelForLinkKind} from "../Link.js";
|
import {getLabelForLinkKind} from "../Link.js";
|
||||||
|
|
||||||
export class OpenLinkViewModel extends ViewModel {
|
export class OpenLinkViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {clients, link, consentedServers} = options;
|
const {clients, link} = options;
|
||||||
this._link = link;
|
this._link = link;
|
||||||
this._clients = clients;
|
this._clients = clients;
|
||||||
this.previewViewModel = new PreviewViewModel(this.childOptions({link, consentedServers}));
|
this.serverConsentViewModel = null;
|
||||||
|
this.previewViewModel = null;
|
||||||
|
this.clientsViewModel = null;
|
||||||
this.previewLoading = false;
|
this.previewLoading = false;
|
||||||
const preferredClient = this.preferences.clientId ? clients.find(c => c.id === this.preferences.clientId) : null;
|
if (this.preferences.homeservers === null) {
|
||||||
this.clientsViewModel = preferredClient ? new ClientListViewModel(this.childOptions({
|
this.serverConsentViewModel = new ServerConsentViewModel(this.childOptions({
|
||||||
clients,
|
servers: this._link.servers,
|
||||||
link,
|
done: () => {
|
||||||
client: preferredClient,
|
this.serverConsentViewModel = null;
|
||||||
})) : 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.previewLoading = true;
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
await this.previewViewModel.load();
|
await this.previewViewModel.load();
|
||||||
|
|
118
src/open/ServerConsentView.js
Normal file
118
src/open/ServerConsentView.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
54
src/open/ServerConsentViewModel.js
Normal file
54
src/open/ServerConsentViewModel.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue