Add suggestions for invalid URLs

This commit is contained in:
Danila Fedorin 2021-08-30 14:14:31 -07:00
parent 0844279c58
commit b57fbd8b6d
5 changed files with 71 additions and 5 deletions

View File

@ -15,15 +15,35 @@ limitations under the License.
*/ */
import {TemplateView} from "./utils/TemplateView.js"; import {TemplateView} from "./utils/TemplateView.js";
import {LinkKind} from "./Link.js";
export class InvalidUrlView extends TemplateView { export class InvalidUrlView extends TemplateView {
render(t) { render(t, vm) {
return t.div({ className: "DisclaimerView card" }, [ return t.div({ className: "DisclaimerView card" }, [
t.h1("Invalid URL"), t.h1("Invalid URL"),
t.p([ t.p([
'The link you have entered is not valid. If you like, you can ', 'The link you have entered is not valid. If you like, you can ',
t.a({ href: "#/" }, 'return to the home page.') t.a({ href: "#/" }, 'return to the home page.')
]), ]),
vm.validFixes.length ? this._renderValidFixes(t, vm.validFixes) : [],
]);
}
_describeLinkKind(kind) {
switch (kind) {
case LinkKind.Room: return "The room ";
case LinkKind.User: return "The user ";
case LinkKind.Group: return "The group ";
case LinkKind.Event: return "An event in room ";
}
}
_renderValidFixes(t, validFixes) {
return t.p([
'Did you mean any of the following?',
t.ul(validFixes.map(fix =>
t.li([this._describeLinkKind(fix.link.kind), t.a({ href: fix.url }, fix.link.identifier)])
))
]); ]);
} }
} }

View File

@ -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.
*/
import {ViewModel} from "./utils/ViewModel.js";
import {tryFixUrl} from "./Link.js";
export class InvalidUrlViewModel extends ViewModel {
constructor(options) {
super(options);
this.validFixes = tryFixUrl(options.fragment);
}
}

View File

@ -71,6 +71,24 @@ export const LinkKind = createEnum(
"Event" "Event"
) )
export function tryFixUrl(fragment) {
const attempts = [];
if (fragment.startsWith('#') && !fragment.startsWith('#/')) {
if (!fragment.match(/^#[@$!]/)) {
attempts.push('#/' + fragment); // #room => #/#room
}
attempts.push('#/' + fragment.substring(1)); // #@room => #/@room
}
const validAttempts = [];
for (const attempt of attempts) {
const link = Link.parse(attempt);
if (link) {
validAttempts.push({ url: attempt, link });
}
}
return validAttempts;
}
export class Link { export class Link {
static validateIdentifier(identifier) { static validateIdentifier(identifier) {
return !!( return !!(

View File

@ -24,7 +24,7 @@ import {InvalidUrlView} from "./InvalidUrlView.js";
export class RootView extends TemplateView { export class RootView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.div({className: "RootView"}, [ return t.div({className: "RootView"}, [
t.mapView(vm => vm.invalidUrl, invalid => invalid ? new InvalidUrlView() : null), t.mapView(vm => vm.invalidUrlViewModel, invalidVM => invalidVM ? new InvalidUrlView(invalidVM) : null),
t.mapView(vm => vm.showDisclaimer, disclaimer => disclaimer ? new DisclaimerView() : null), t.mapView(vm => vm.showDisclaimer, disclaimer => disclaimer ? new DisclaimerView() : null),
t.mapView(vm => vm.openLinkViewModel, vm => vm ? new OpenLinkView(vm) : null), 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.createLinkViewModel, vm => vm ? new CreateLinkView(vm) : null),

View File

@ -20,6 +20,7 @@ import {OpenLinkViewModel} from "./open/OpenLinkViewModel.js";
import {createClients} from "./open/clients/index.js"; import {createClients} from "./open/clients/index.js";
import {CreateLinkViewModel} from "./create/CreateLinkViewModel.js"; import {CreateLinkViewModel} from "./create/CreateLinkViewModel.js";
import {LoadServerPolicyViewModel} from "./policy/LoadServerPolicyViewModel.js"; import {LoadServerPolicyViewModel} from "./policy/LoadServerPolicyViewModel.js";
import {InvalidUrlViewModel} from "./InvalidUrlViewModel.js";
import {Platform} from "./Platform.js"; import {Platform} from "./Platform.js";
export class RootViewModel extends ViewModel { export class RootViewModel extends ViewModel {
@ -29,8 +30,8 @@ export class RootViewModel extends ViewModel {
this.openLinkViewModel = null; this.openLinkViewModel = null;
this.createLinkViewModel = null; this.createLinkViewModel = null;
this.loadServerPolicyViewModel = null; this.loadServerPolicyViewModel = null;
this.invalidUrlViewModel = null;
this.showDisclaimer = false; this.showDisclaimer = false;
this.invalidUrl = false;
this.preferences.on("canClear", () => { this.preferences.on("canClear", () => {
this.emitChange(); this.emitChange();
}); });
@ -59,7 +60,7 @@ export class RootViewModel extends ViewModel {
// clear them to avoid having to manually reset (n-1)/n view models in every case. // clear them to avoid having to manually reset (n-1)/n view models in every case.
// That just doesn't scale well when we add new views. // That just doesn't scale well when we add new views.
const oldLink = this.link; const oldLink = this.link;
this.invalidUrl = false; this.invalidUrlViewModel = null;
this.showDisclaimer = false; this.showDisclaimer = false;
this.loadServerPolicyViewModel = null; this.loadServerPolicyViewModel = null;
this.createLinkViewModel = null; this.createLinkViewModel = null;
@ -79,7 +80,9 @@ export class RootViewModel extends ViewModel {
this._updateChildVMs(newLink, oldLink); this._updateChildVMs(newLink, oldLink);
} else { } else {
this._updateChildVMs(null, oldLink); this._updateChildVMs(null, oldLink);
this.invalidUrl = true; this.invalidUrlViewModel = new InvalidUrlViewModel(this.childOptions({
fragment: hash
}));
} }
this.emitChange(); this.emitChange();
} }