From 2ecdf323563c9daa897b709593d291a0fd1a8b6c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 4 Dec 2020 15:31:03 +0100 Subject: [PATCH] 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 +}