From d3c8f5204dd558f6700ac557b78199bbdd35bf4d Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 4 May 2021 13:57:33 +0200 Subject: [PATCH] Begin support for full SSR --- static/kx-claim.js | 62 ++----------- static/kx-key.js | 54 ++--------- static/styles.css | 225 +++++++++++++++++++++++++++++++++++++++++++++ views/profile.pug | 44 ++++++++- 4 files changed, 285 insertions(+), 100 deletions(-) diff --git a/static/kx-claim.js b/static/kx-claim.js index d1ce4fc..50c6520 100644 --- a/static/kx-claim.js +++ b/static/kx-claim.js @@ -7,50 +7,6 @@ class Claim extends HTMLElement { constructor() { // Call super super(); - - // Shadow root - this.attachShadow({mode: 'open'}); - - // Details element - const details = document.createElement('details'); - details.setAttribute('class', 'kx-item'); - - // Summary element - const summary = details.appendChild(document.createElement('summary')); - - // Info - const info = summary.appendChild(document.createElement('div')); - info.setAttribute('class', 'info'); - - // Info > Service provider - const serviceProvider = info.appendChild(document.createElement('p')); - serviceProvider.setAttribute('class', 'subtitle'); - - // Info > Profile - const profile = info.appendChild(document.createElement('p')); - profile.setAttribute('class', 'title'); - - // Icons - const icons = summary.appendChild(document.createElement('div')); - icons.setAttribute('class', 'icons'); - - const icons__verificationStatus = icons.appendChild(document.createElement('div')); - icons__verificationStatus.setAttribute('class', 'verificationStatus'); - - const icons__verificationStatus__inProgress = icons__verificationStatus.appendChild(document.createElement('div')); - icons__verificationStatus__inProgress.setAttribute('class', 'inProgress'); - - // Details content - const content = details.appendChild(document.createElement('div')); - content.setAttribute('class', 'content'); - - // Load CSS stylesheet - const linkCSS = document.createElement('link'); - linkCSS.setAttribute('rel', 'stylesheet'); - linkCSS.setAttribute('href', '/static/kx-styles.css'); - - // Attach the elements to the shadow DOM - this.shadowRoot.append(linkCSS, details); } attributeChangedCallback(name, oldValue, newValue) { @@ -69,38 +25,38 @@ class Claim extends HTMLElement { } updateContent(value) { - const shadow = this.shadowRoot; + const root = this; const claim = new doip.Claim(JSON.parse(value)); switch (claim.matches[0].serviceprovider.name) { case 'dns': case 'xmpp': case 'irc': - shadow.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name.toUpperCase(); + root.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name.toUpperCase(); break; default: - shadow.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name; + root.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name; break; } - shadow.querySelector('.info .title').innerText = claim.matches[0].profile.display; + root.querySelector('.info .title').innerText = claim.matches[0].profile.display; try { if (claim.status === 'verified') { - shadow.querySelector('.icons .verificationStatus').setAttribute('data-value', claim.verification.result ? 'success' : 'failed'); + root.querySelector('.icons .verificationStatus').setAttribute('data-value', claim.verification.result ? 'success' : 'failed'); } else { - shadow.querySelector('.icons .verificationStatus').setAttribute('data-value', 'running'); + root.querySelector('.icons .verificationStatus').setAttribute('data-value', 'running'); } } catch (error) { - shadow.querySelector('.icons .verificationStatus').setAttribute('data-value', 'failed'); + root.querySelector('.icons .verificationStatus').setAttribute('data-value', 'failed'); } - const elContent = shadow.querySelector('.content'); + const elContent = root.querySelector('.content'); elContent.innerHTML = ``; // Handle failed ambiguous claim if (claim.status === 'verified' && !claim.verification.result && claim.isAmbiguous()) { - shadow.querySelector('.info .subtitle').innerText = '---'; + root.querySelector('.info .subtitle').innerText = '---'; const subsection_alert = elContent.appendChild(document.createElement('div')); subsection_alert.setAttribute('class', 'subsection'); diff --git a/static/kx-key.js b/static/kx-key.js index 78c821e..a4b8cb5 100644 --- a/static/kx-key.js +++ b/static/kx-key.js @@ -7,40 +7,6 @@ class Key extends HTMLElement { constructor() { // Call super super(); - - // Shadow root - this.attachShadow({mode: 'open'}); - - // Details element - const details = document.createElement('details'); - details.setAttribute('class', 'kx-item'); - - // Summary element - const summary = details.appendChild(document.createElement('summary')); - - // Info - const info = summary.appendChild(document.createElement('div')); - info.setAttribute('class', 'info'); - - // Info > Protocol - const serviceProvider = info.appendChild(document.createElement('p')); - serviceProvider.setAttribute('class', 'subtitle'); - - // Info > Fingerprint - const profile = info.appendChild(document.createElement('p')); - profile.setAttribute('class', 'title'); - - // Details content - const content = details.appendChild(document.createElement('div')); - content.setAttribute('class', 'content'); - - // Load CSS stylesheet - const linkCSS = document.createElement('link'); - linkCSS.setAttribute('rel', 'stylesheet'); - linkCSS.setAttribute('href', '/static/kx-styles.css'); - - // Attach the elements to the shadow DOM - this.shadowRoot.append(linkCSS, details); } attributeChangedCallback(name, oldValue, newValue) { @@ -48,23 +14,23 @@ class Key extends HTMLElement { } updateContent(value) { - const shadow = this.shadowRoot; + const root = this; const data = JSON.parse(value); - shadow.querySelector('.info .subtitle').innerText = data.key.fetchMethod; - shadow.querySelector('.info .title').innerText = data.fingerprint; + root.querySelector('.info .subtitle').innerText = data.key.fetchMethod; + root.querySelector('.info .title').innerText = data.fingerprint; - const elContent = shadow.querySelector('.content'); + const elContent = root.querySelector('.content'); elContent.innerHTML = ``; // Link to key - const subsection1 = elContent.appendChild(document.createElement('div')); - subsection1.setAttribute('class', 'subsection'); - const subsection1_icon = subsection1.appendChild(document.createElement('img')); - subsection1_icon.setAttribute('src', '/static/img/link.png'); - const subsection1_text = subsection1.appendChild(document.createElement('div')); + const subsection_links = elContent.appendChild(document.createElement('div')); + subsection_links.setAttribute('class', 'subsection'); + const subsection_links_icon = subsection_links.appendChild(document.createElement('img')); + subsection_links_icon.setAttribute('src', '/static/img/link.png'); + const subsection_links_text = subsection_links.appendChild(document.createElement('div')); - const profile_link = subsection1_text.appendChild(document.createElement('p')); + const profile_link = subsection_links_text.appendChild(document.createElement('p')); profile_link.innerHTML = `Key link: ${data.key.uri}`; elContent.appendChild(document.createElement('hr')); diff --git a/static/styles.css b/static/styles.css index 93d25d3..66ca5c5 100644 --- a/static/styles.css +++ b/static/styles.css @@ -472,3 +472,228 @@ dialog p { dialog p:first-of-type { margin-top: 0; } + +/* KX-ITEM */ +.kx-item details { + width: 100%; + border-radius: 8px; +} +.kx-item details p { + margin: 0; + word-break: break-word; + font-size: 1rem; +} +.kx-item details a { + color: var(--blue-700); +} +.kx-item details hr { + border: none; + border-top: 2px solid var(--purple-100); +} +.kx-item details .content { + padding: 12px; + border: solid 3px var(--purple-100); + border-top: 0px; + border-radius: 0px 0px 8px 8px; +} +.kx-item details summary { + display: flex; + align-items: center; + padding: 8px 12px; + background-color: var(--purple-100); + border: solid 3px var(--purple-100); + border-radius: 8px; + list-style: none; + cursor: pointer; +} +.kx-item details summary::-webkit-details-marker { + display: none; +} +.kx-item details summary:hover, summary:focus { + border-color: var(--purple-400); +} +details[open] summary { + border-radius: 8px 8px 0px 0px; +} +.kx-item details summary .info { + flex: 1; +} +.kx-item details summary .info .title { + font-size: 1.1em; +} +.kx-item details summary .claim__description p { + font-size: 1.4rem; + line-height: 2rem; +} +.kx-item details summary .claim__links p, p.subtle-links { + display: flex; + align-items: center; + flex-wrap: wrap; + font-size: 1rem; + color: var(--grey-700); +} +.kx-item details summary .claim__links a, summary .claim__links span, p.subtle-links a { + font-size: 1rem; + margin: 0 10px 0 0; + color: var(--grey-700); +} +.kx-item details summary .subtitle { + color: var(--purple-700); +} +.kx-item details summary .verificationStatus { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 100%; + color: #fff; + font-size: 2rem; + user-select: none; +} +.kx-item details summary .verificationStatus::after { + position: absolute; + display: flex; + top: 0; + left: 0; + right: 0; + bottom: 0; + align-items: center; + justify-content: center; +} +.kx-item details summary .verificationStatus .inProgress { + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; +} +.kx-item details summary .verificationStatus[data-value="success"] { + content: "v"; + background-color: var(--green-600); +} +.kx-item details summary .verificationStatus[data-value="success"]::after { + content: "✔"; +} +.kx-item details summary .verificationStatus[data-value="failed"] { + background-color: var(--red-400); +} +.kx-item details summary .verificationStatus[data-value="failed"]::after { + content: "✕"; +} +.kx-item details summary .verificationStatus[data-value="running"] .inProgress { + opacity: 1; +} + +.kx-item details .subsection { + display: flex; + align-items: center; + gap: 16px; +} +.kx-item details .subsection > img { + width: 24px; + height: 24px; + opacity: 0.4; +} + +.kx-item details .inProgress { + font-size: 10px; + margin: 50px auto; + text-indent: -9999em; + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--purple-400); + background: -moz-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%); + background: -webkit-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%); + background: -o-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%); + background: -ms-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%); + background: linear-gradient(to right, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%); + position: relative; + -webkit-animation: load3 1.4s infinite linear; + animation: load3 1.4s infinite linear; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); +} +.kx-item details .inProgress:before { + width: 50%; + height: 50%; + background: var(--purple-400); + border-radius: 100% 0 0 0; + position: absolute; + top: 0; + left: 0; + content: ''; +} +.kx-item details .inProgress:after { + background: var(--purple-100); + width: 65%; + height: 65%; + border-radius: 50%; + content: ''; + margin: auto; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; +} +.kx-item details button { + padding: 0.4rem 0.8rem; + margin-right: 8px; + text-decoration: none; + text-transform: uppercase; + background-color: #fff; + border: solid 2px var(--purple-400); + border-radius: 4px; + cursor: pointer; +} +.kx-item details button:hover { + background-color: var(--purple-500); + border-color: var(--purple-500); + color: #fff; +} + +@media screen and (max-width: 640px) { + .kx-item details summary .claim__description p { + font-size: 1.2rem; + } + .kx-item details summary .claim__links a, p.subtle-links a { + font-size: 0.9rem; + } +} +@media screen and (max-width: 480px) { + summary .claim__description p { + font-size: 1rem; + } + .kx-item details summary .verificationStatus { + width: 36px; + height: 36px; + font-size: 1.6rem; + } + .kx-item details .inProgress { + width: 36px; + height: 36px; + } +} + +@-webkit-keyframes load3 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load3 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/views/profile.pug b/views/profile.pug index f6624e0..ba84ac9 100644 --- a/views/profile.pug +++ b/views/profile.pug @@ -6,8 +6,30 @@ mixin generateUser(user, isPrimary) if isPrimary small.primary primary each claim in user.claims - kx-claim(data-claim=claim) - + kx-claim.kx-item(data-claim=claim) + details + summary + .info + p.subtitle= claim.matches[0].serviceprovider.name + p.title= claim.matches[0].profile.display + .icons + .verificationStatus(data-value='running') + .inProgress + .content + .subsection + img(src='/static/img/link.png') + div + if (claim.matches[0].profile.uri) + p Profile link: + a(rel='me' href=claim.matches[0].profile.uri)= claim.matches[0].profile.uri + else + p Profile link: not accessible from browser + if (claim.matches[0].proof.uri) + p Proof link: + a(rel='me' href=claim.matches[0].proof.uri)= claim.matches[0].proof.uri + else + p Proof link: not accessible from browser + block js script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8') script(type='application/javascript' src='/static/dialog-polyfill.js' charset='utf-8') @@ -84,7 +106,23 @@ block content #profileProofs.card.card--transparent h2 Key - kx-key(data-keydata=data.keyData) + kx-key.kx-item(data-keydata=data.keyData) + details + summary + .info + p.subtitle= data.keyData.key.fetchMethod + p.title= data.keyData.fingerprint + .content + .subsection + img(src='/static/img/link.png') + div + p Key link: + a(href=data.keyData.key.uri)= data.keyData.key.uri + hr + .subsection + img(src='/static/img/qrcode.png') + div + button(onClick=`showQR('${data.keyData.fingerprint}', 'fingerprint')`) Show OpenPGP fingerprint QR +generateUser(data.keyData.users[data.keyData.primaryUserIndex], true) each user, index in data.keyData.users