mirror of
https://codeberg.org/keyoxide/keyoxide-web.git
synced 2024-12-22 23:09:29 -07:00
Begin support for full SSR
This commit is contained in:
parent
c747d39c7d
commit
d3c8f5204d
4 changed files with 285 additions and 100 deletions
|
@ -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');
|
||||
|
|
|
@ -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: <a href="${data.key.uri}">${data.key.uri}</a>`;
|
||||
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,29 @@ 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')
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue