Begin support for full SSR

This commit is contained in:
Yarmo Mackenbach 2021-05-04 13:57:33 +02:00
parent c747d39c7d
commit d3c8f5204d
No known key found for this signature in database
GPG key ID: 37367F4AF4087AD1
4 changed files with 285 additions and 100 deletions

View file

@ -7,50 +7,6 @@ class Claim extends HTMLElement {
constructor() { constructor() {
// Call super // Call super
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) { attributeChangedCallback(name, oldValue, newValue) {
@ -69,38 +25,38 @@ class Claim extends HTMLElement {
} }
updateContent(value) { updateContent(value) {
const shadow = this.shadowRoot; const root = this;
const claim = new doip.Claim(JSON.parse(value)); const claim = new doip.Claim(JSON.parse(value));
switch (claim.matches[0].serviceprovider.name) { switch (claim.matches[0].serviceprovider.name) {
case 'dns': case 'dns':
case 'xmpp': case 'xmpp':
case 'irc': 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; break;
default: default:
shadow.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name; root.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name;
break; break;
} }
shadow.querySelector('.info .title').innerText = claim.matches[0].profile.display; root.querySelector('.info .title').innerText = claim.matches[0].profile.display;
try { try {
if (claim.status === 'verified') { 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 { } else {
shadow.querySelector('.icons .verificationStatus').setAttribute('data-value', 'running'); root.querySelector('.icons .verificationStatus').setAttribute('data-value', 'running');
} }
} catch (error) { } 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 = ``; elContent.innerHTML = ``;
// Handle failed ambiguous claim // Handle failed ambiguous claim
if (claim.status === 'verified' && !claim.verification.result && claim.isAmbiguous()) { 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')); const subsection_alert = elContent.appendChild(document.createElement('div'));
subsection_alert.setAttribute('class', 'subsection'); subsection_alert.setAttribute('class', 'subsection');

View file

@ -7,40 +7,6 @@ class Key extends HTMLElement {
constructor() { constructor() {
// Call super // Call super
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) { attributeChangedCallback(name, oldValue, newValue) {
@ -48,23 +14,23 @@ class Key extends HTMLElement {
} }
updateContent(value) { updateContent(value) {
const shadow = this.shadowRoot; const root = this;
const data = JSON.parse(value); const data = JSON.parse(value);
shadow.querySelector('.info .subtitle').innerText = data.key.fetchMethod; root.querySelector('.info .subtitle').innerText = data.key.fetchMethod;
shadow.querySelector('.info .title').innerText = data.fingerprint; root.querySelector('.info .title').innerText = data.fingerprint;
const elContent = shadow.querySelector('.content'); const elContent = root.querySelector('.content');
elContent.innerHTML = ``; elContent.innerHTML = ``;
// Link to key // Link to key
const subsection1 = elContent.appendChild(document.createElement('div')); const subsection_links = elContent.appendChild(document.createElement('div'));
subsection1.setAttribute('class', 'subsection'); subsection_links.setAttribute('class', 'subsection');
const subsection1_icon = subsection1.appendChild(document.createElement('img')); const subsection_links_icon = subsection_links.appendChild(document.createElement('img'));
subsection1_icon.setAttribute('src', '/static/img/link.png'); subsection_links_icon.setAttribute('src', '/static/img/link.png');
const subsection1_text = subsection1.appendChild(document.createElement('div')); 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: <a href="${data.key.uri}">${data.key.uri}</a>`; profile_link.innerHTML = `Key link: <a href="${data.key.uri}">${data.key.uri}</a>`;
elContent.appendChild(document.createElement('hr')); elContent.appendChild(document.createElement('hr'));

View file

@ -472,3 +472,228 @@ dialog p {
dialog p:first-of-type { dialog p:first-of-type {
margin-top: 0; 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);
}
}

View file

@ -6,7 +6,29 @@ mixin generateUser(user, isPrimary)
if isPrimary if isPrimary
small.primary primary small.primary primary
each claim in user.claims 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 block js
script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8') script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8')
@ -84,7 +106,23 @@ block content
#profileProofs.card.card--transparent #profileProofs.card.card--transparent
h2 Key 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) +generateUser(data.keyData.users[data.keyData.primaryUserIndex], true)
each user, index in data.keyData.users each user, index in data.keyData.users