Versie 0.3.3
This commit is contained in:
1 parent
1cf6eeed29
commit
6e741a51fe
57 files changed
+1362
-2135
No files matched your search
+55
-93
@@ -1,4 +1,4 @@
|
||||
// Generated, do not edit. sha256:5dea810da6d84ed187a1154955f4194498f812771e3ddaa6b6dc7c547a305b8d
|
||||
// Generated, do not edit. sha256:867f87b26d0b27fe51f4a0cb77d5196bad8cd8b58a6856168c9cad25f5776d99
|
||||
import { t, localeTag } from '../i18n/index.js';
|
||||
import { CONFIG } from '../../shared/config.js';
|
||||
import { esc } from '../ui/escape.js';
|
||||
@@ -8,7 +8,8 @@ import { refreshAttention } from '../ui/attention.js';
|
||||
import { npubFromHex, shortKey, secretFromText, publicKeyOf, nsecFromSecret, newSecret } from '../nostr/keys.js';
|
||||
import { keySigner, extensionSigner, nip98Header } from '../nostr/nip98.js';
|
||||
import { qrSvg, invoiceQrText } from '../ui/qr.js';
|
||||
import { CHECK, CHEVRON } from '../ui/icons.js';
|
||||
import { CHECK, CHEVRON, IMAGE } from '../ui/icons.js';
|
||||
import { setPageHint } from '../ui/page-hint.js';
|
||||
import { splashMarkHtml } from '../ui/splash.js';
|
||||
import { expanders } from '../ui/expand.js';
|
||||
import { supportInfo, supportMessages, sendSupport, supportAttachment } from '../data/support.js';
|
||||
@@ -21,24 +22,21 @@ function buyerOf(s) {
|
||||
return { text: shortKey(npub ?? s.buyer, 10), title: npub ?? s.buyer ?? '' };
|
||||
}
|
||||
|
||||
function lines(s) {
|
||||
function standHtml(s) {
|
||||
const out = [];
|
||||
const line = (text, title = '') => out.push({ text, title });
|
||||
|
||||
if (s.why === 'develop' || s.why === 'forced-develop') line(t('license.cardDevelop'));
|
||||
line(s.level === 'full' ? t('license.cardFull') : t('license.cardFree'));
|
||||
if (s.installed) {
|
||||
const buyer = buyerOf(s);
|
||||
line(t('license.cardFacts', {
|
||||
buyer: buyer.text, issued: s.issued ?? '',
|
||||
from: s.installed.majorFrom,
|
||||
const range = t('license.cardFacts', {
|
||||
issued: s.issued ?? '', from: s.installed.majorFrom,
|
||||
to: s.installed.majorTo === 'unlimited' ? t('license.unlimited') : s.installed.majorTo,
|
||||
}), buyer.title);
|
||||
if (!s.installedCovers) line(t('license.cardOutOfRange', { version: s.version }));
|
||||
});
|
||||
const [before, after] = t('license.cardFor', { npub: '§' }).split('§');
|
||||
out.push(`<p class="t-body-sm">${esc(before)}<span class="badge badge-accent" title="${esc(`${buyer.title}\n${range}`)}">${esc(buyer.text)}</span>${esc(after)}</p>`);
|
||||
} else {
|
||||
out.push(`<p class="t-body-sm">${esc(t(s.level === 'full' ? 'license.cardFull' : 'license.cardFree'))}</p>`);
|
||||
}
|
||||
if (BROKEN.has(s.why)) line(`${t('license.cardProblem')} ${t(`license.why_${s.why}`)}.`);
|
||||
if (s.level !== 'full') line(t('license.cardBuy'));
|
||||
return out;
|
||||
if (BROKEN.has(s.why)) out.push(`<p class="t-body-sm">${esc(`${t('license.cardProblem')} ${t(`license.why_${s.why}`)}.`)}</p>`);
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
async function installAndReload(text, message) {
|
||||
@@ -57,17 +55,16 @@ async function installAndReload(text, message) {
|
||||
|
||||
const INFO = ['Nostr', 'Why', 'SignIn', 'Zap', 'Privacy'];
|
||||
const link = (url, html) => `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${html}</a>`;
|
||||
const payHtml = (expand) => `<div class="lic-head"><div class="t-label">${say('license.payTitle')}</div></div>
|
||||
<p class="t-body-sm">${say('license.payIntro')}</p>`
|
||||
+ ['easy', 'other'].map(group => `<div class="lic-apps"><div class="t-caption">${say(`license.payGroup_${group}`)}</div>`
|
||||
+ CONFIG.LICENSE.PAY_APPS.filter(a => a.group === group)
|
||||
.map(a => `<p class="t-body-sm">${link(a.url, `<strong>${esc(a.name)}</strong>`)} ${say(`license.payApp_${a.id}`)}</p>`).join('')
|
||||
+ '</div>').join('')
|
||||
+ `<p class="t-caption">${say('license.payNote')}</p>
|
||||
<details class="lic-more" ${expand.attr('nostr')}><summary class="t-body-sm">${say('license.nostrMore')}</summary>`
|
||||
+ INFO.map(k => `<p class="t-body-sm"><strong>${say(`license.info${k}Head`)}</strong> ${say(`license.info${k}`)}</p>`).join('')
|
||||
+ CONFIG.LICENSE.INFO_LINKS.map(l => `<p class="t-body-sm">${link(l.url, say(`license.link_${l.id}`))}</p>`).join('')
|
||||
+ '</details>';
|
||||
const legendRows = () => [
|
||||
say('license.payIntro'),
|
||||
...['easy', 'other'].flatMap(group => [{ heading: say(`license.payGroup_${group}`) },
|
||||
...CONFIG.LICENSE.PAY_APPS.filter(a => a.group === group)
|
||||
.map(a => `${link(a.url, `<strong>${esc(a.name)}</strong>`)} ${say(`license.payApp_${a.id}`)}`)]),
|
||||
say('license.payNote'),
|
||||
{ heading: say('license.nostrMore') },
|
||||
...INFO.map(k => `<strong>${say(`license.info${k}Head`)}</strong> ${say(`license.info${k}`)}`),
|
||||
...CONFIG.LICENSE.INFO_LINKS.map(l => link(l.url, say(`license.link_${l.id}`))),
|
||||
];
|
||||
|
||||
let signer = null;
|
||||
let newKey = null;
|
||||
@@ -91,12 +88,12 @@ async function drawSignIn(box, s) {
|
||||
const who = t(signer.kind === 'extension' ? 'license.signedInExtension' : 'license.signedIn', { npub: '§' });
|
||||
const [before, after] = who.split('§');
|
||||
const other = s.installed && s.buyer && s.buyer !== signer.pubkey;
|
||||
box.innerHTML = `${head}
|
||||
<p class="t-body-sm">${esc(before)}${npubText(signer.pubkey)}${esc(after)}</p>
|
||||
box.innerHTML = `
|
||||
<div class="flex-between gap-sm"><span class="t-body-sm">${esc(before)}${npubText(signer.pubkey)}${esc(after)}</span>
|
||||
<button class="btn btn-ghost btn-sm" type="button" data-signout>${say('license.signOut')}</button></div>
|
||||
${other ? `<p class="t-body-sm">${say('license.otherBuyer')}</p>` : ''}
|
||||
<div data-licenses class="mt-sm"></div>
|
||||
<p class="t-body-sm" data-signin-msg></p>
|
||||
<div class="flex gap-sm"><button class="btn btn-secondary btn-sm" type="button" data-signout>${say('license.signOut')}</button></div>`;
|
||||
<p class="t-body-sm" data-signin-msg></p>`;
|
||||
box.querySelector('[data-signout]').onclick = () => { signer = null; drawSignIn(box, s); };
|
||||
await drawLicenses(box.querySelector('[data-licenses]'), box.querySelector('[data-signin-msg]'), shop);
|
||||
return;
|
||||
@@ -147,7 +144,7 @@ async function drawSignIn(box, s) {
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-key>${say('license.signInKey')}</button>
|
||||
<button class="btn btn-ghost btn-sm" type="button" data-new>${say('license.signInNew')}</button>
|
||||
</div>
|
||||
${hasExtension ? '' : `<p class="t-caption mt-sm">${say('license.signInExtensionNone')}</p>`}
|
||||
${hasExtension ? '' : `<p class="t-caption t-muted lic-status">${say('license.signInExtensionNone')}</p>`}
|
||||
<form class="mt-md" data-key-form hidden autocomplete="off">
|
||||
<label class="form-label" for="lic-nsec">${say('license.signInKeyLabel')}</label>
|
||||
<input class="input t-mono mt-sm" id="lic-nsec" type="password" autocomplete="off" spellcheck="false">
|
||||
@@ -189,6 +186,8 @@ const AWAY = new Set(['marketService.unreachable', 'marketService.timeout', 'mar
|
||||
async function drawBuy() {
|
||||
const box = buyBox;
|
||||
if (!box) return;
|
||||
box.hidden = !signer && !purchase;
|
||||
if (box.hidden) return;
|
||||
const head = `<div class="lic-head"><div class="t-label">${say('license.buyTitle')}</div></div>`;
|
||||
|
||||
if (purchase) {
|
||||
@@ -223,7 +222,6 @@ async function drawBuy() {
|
||||
: sale ? say('license.buyClosed') : esc(problem ?? '');
|
||||
box.innerHTML = `${head}
|
||||
<p class="t-body-sm${problem ? ' t-neg' : ''}">${price}</p>
|
||||
${sale?.open && !signer ? `<p class="t-body-sm">${say('license.buySignInFirst')}</p>` : ''}
|
||||
<div class="flex gap-sm"><button class="btn btn-primary btn-sm" type="button" data-buy-go${sale?.open && signer ? '' : ' disabled'}>${say('license.buyButton')}</button></div>
|
||||
<p class="t-body-sm mt-sm" data-buy-msg></p>`;
|
||||
box.querySelector('[data-buy-go]').onclick = async (e) => {
|
||||
@@ -305,10 +303,10 @@ async function drawSupport() {
|
||||
<textarea class="input mt-sm" id="lic-support" rows="3" maxlength="${info.maxText}" placeholder="${esc(t('license.supportPlaceholder'))}">${esc(supportDraft)}</textarea>
|
||||
<div data-support-file></div>
|
||||
<div class="flex gap-sm mt-sm">
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-support-pick>${say('license.supportAttach')}</button>
|
||||
<input type="file" accept="${esc(info.attachmentTypes.join(','))}" data-support-input hidden>
|
||||
<button class="btn btn-primary btn-sm" type="button" data-support-send>${say('license.supportSend')}</button>
|
||||
<span class="t-caption">${say('license.supportAttachHint', { mb })}</span>
|
||||
<input type="file" accept="${esc(info.attachmentTypes.join(','))}" data-support-input hidden>
|
||||
<button class="btn btn-ghost btn-sm lic-attach" type="button" data-support-pick
|
||||
title="${esc(`${t('license.supportAttach')}: ${t('license.supportAttachHint', { mb })}`)}" aria-label="${esc(t('license.supportAttach'))}">${IMAGE}</button>
|
||||
</div>
|
||||
<p class="t-body-sm mt-sm" data-support-msg></p>
|
||||
${nostr}`;
|
||||
@@ -474,10 +472,12 @@ function download(name, text) {
|
||||
const mark = (v) => {
|
||||
if (v === true) return `<span class="lic-feat-yes" title="${esc(t('license.featIncluded'))}">${CHECK}</span>`;
|
||||
if (v === false) return `<span class="lic-feat-no" title="${esc(t('license.featExcluded'))}">-</span>`;
|
||||
if (v === 'partial') return `<span class="lic-feat-part" title="${esc(t('license.featPartialTitle'))}">${esc(t('license.featPartial'))}</span>`;
|
||||
if (v === 'partial') return `<span class="lic-feat-part" title="${esc(t('license.featPartialTitle'))}">${CHECK}</span>`;
|
||||
return `<span class="lic-feat-open" title="${esc(t('license.featUndecided'))}">?</span>`;
|
||||
};
|
||||
|
||||
const MARKS = '.lic-feat-yes, .lic-feat-no, .lic-feat-part, .lic-feat-open';
|
||||
|
||||
const inFree = f => (f.has ? CONFIG.LICENSE.FREE.HAS[f.has] : f.free);
|
||||
|
||||
function groupFree(items) {
|
||||
@@ -502,23 +502,21 @@ function featuresHtml(expand) {
|
||||
<ul class="lic-feat-sub">${sub}</ul>
|
||||
</details>`;
|
||||
}).join('');
|
||||
const open = CONFIG.LICENSE.FEATURES.some(f => inFree(f) === null);
|
||||
return `
|
||||
<div class="lic-hero">${splashMarkHtml()}<div class="t-label">${esc(t('license.featTitle'))}</div></div>
|
||||
<div class="lic-feat">
|
||||
<div class="lic-feat-row lic-feat-cols"><span></span><span>${esc(t('license.featFull'))}</span><span>${esc(t('license.featFree'))}</span></div>
|
||||
${groups}
|
||||
</div>
|
||||
${open ? `<p class="t-caption">${esc(t('license.featFreeOpen'))}</p>` : ''}`;
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function placeShot(shot, row) {
|
||||
function placeShot(shot, anchor) {
|
||||
const gap = 8, pad = 8;
|
||||
const r = row.getBoundingClientRect();
|
||||
const r = anchor.getBoundingClientRect();
|
||||
const w = shot.offsetWidth, h = shot.offsetHeight;
|
||||
let top = r.top - gap - h;
|
||||
if (top < pad) top = r.bottom + gap;
|
||||
const left = Math.min(Math.max(pad, r.left + 24), window.innerWidth - pad - w);
|
||||
const left = Math.min(Math.max(pad, r.left + r.width / 2 - w / 2), window.innerWidth - pad - w);
|
||||
shot.style.left = `${left}px`;
|
||||
shot.style.top = `${Math.min(Math.max(pad, top), window.innerHeight - pad - h)}px`;
|
||||
}
|
||||
@@ -531,12 +529,12 @@ function bindFeatures(root) {
|
||||
document.body.appendChild(shot);
|
||||
let current = null;
|
||||
let on = null;
|
||||
const show = (row) => {
|
||||
if (row === on && !shot.hidden) return;
|
||||
on = row;
|
||||
const show = (row, anchor = row) => {
|
||||
if (anchor === on && !shot.hidden) return;
|
||||
on = anchor;
|
||||
root.querySelectorAll('[data-feature]').forEach(r => r.classList.toggle('is-on', r === row));
|
||||
shot.hidden = false;
|
||||
placeShot(shot, row);
|
||||
placeShot(shot, anchor);
|
||||
const id = row.dataset.feature;
|
||||
if (id === current) return;
|
||||
current = id;
|
||||
@@ -555,8 +553,9 @@ function bindFeatures(root) {
|
||||
};
|
||||
const over = (e) => {
|
||||
if (!root.isConnected) { stop(); return; }
|
||||
const row = e.target.closest?.('[data-feature]');
|
||||
if (row && root.contains(row)) show(row); else hide();
|
||||
const mark = e.target.closest?.(MARKS);
|
||||
const row = mark?.closest('[data-feature]');
|
||||
if (row && root.contains(row)) show(row, mark); else hide();
|
||||
};
|
||||
const stop = () => {
|
||||
shot.remove();
|
||||
@@ -574,35 +573,27 @@ function bindFeatures(root) {
|
||||
|
||||
export async function render(el) {
|
||||
const s = licenseSnapshot() ?? await loadLicense();
|
||||
const expand = await expanders('license', {
|
||||
nostr: false, ...Object.fromEntries(CONFIG.LICENSE.FEATURE_GROUPS.map(g => [groupKey(g), false])),
|
||||
});
|
||||
const expand = await expanders('license', Object.fromEntries(CONFIG.LICENSE.FEATURE_GROUPS.map(g => [groupKey(g), false])));
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="lic-layout">
|
||||
<div class="lic-main">
|
||||
<div class="card lic-card" data-features>${featuresHtml(expand)}</div>
|
||||
</div>
|
||||
<div class="lic-side">
|
||||
<div class="card lic-card">
|
||||
<div class="lic-head">
|
||||
<div class="t-label">${esc(t('license.cardTitle'))}</div>
|
||||
<span class="badge badge-neutral">${esc(t('license.cardVersion', { version: s.version }))}</span>
|
||||
<span class="badge badge-accent">${esc(t('license.cardVersion', { version: s.version }))}</span>
|
||||
</div>
|
||||
${lines(s).map(r => `<p class="t-body-sm"${r.title ? ` title="${esc(r.title)}"` : ''}>${esc(r.text)}</p>`).join('')}
|
||||
<div class="lic-drop" id="lic-drop" tabindex="0" role="button">
|
||||
<span class="t-body-sm">${esc(t('license.dropHere'))}</span>
|
||||
<span class="t-caption">${esc(t('license.dropOr'))}</span>
|
||||
<input type="file" id="lic-file" accept=".json,application/json" hidden>
|
||||
</div>
|
||||
<span id="lic-msg" class="t-body-sm"></span>
|
||||
${standHtml(s)}
|
||||
<section class="lic-section" data-signin></section>
|
||||
<section class="lic-section" data-buy></section>
|
||||
<section class="lic-section" data-buy hidden></section>
|
||||
</div>
|
||||
<div class="card lic-card">${payHtml(expand)}</div>
|
||||
</div>
|
||||
<div class="lic-main">
|
||||
<div class="card lic-card" data-features>${featuresHtml(expand)}</div>
|
||||
<div class="card lic-card lic-chat-card" data-support hidden></div>
|
||||
</div>
|
||||
</div>`;
|
||||
setPageHint(el, { title: t('license.payTitle'), rows: legendRows() });
|
||||
expand.bind(el);
|
||||
bindFeatures(el.querySelector('[data-features]'));
|
||||
buyBox = el.querySelector('[data-buy]');
|
||||
@@ -610,33 +601,4 @@ export async function render(el) {
|
||||
drawSignIn(el.querySelector('[data-signin]'), s);
|
||||
|
||||
markLicenseSeen().then(() => refreshAttention()).catch(() => { });
|
||||
|
||||
const message = el.querySelector('#lic-msg');
|
||||
const install = (text) => installAndReload(text, message);
|
||||
|
||||
const MAX = 64 * 1024;
|
||||
const zone = el.querySelector('#lic-drop');
|
||||
const picker = el.querySelector('#lic-file');
|
||||
|
||||
async function fromFile(file) {
|
||||
if (!file) return;
|
||||
if (file.size > MAX) {
|
||||
message.className = 't-body-sm t-neg';
|
||||
message.textContent = t('license.dropTooBig');
|
||||
return;
|
||||
}
|
||||
await install(await file.text());
|
||||
}
|
||||
|
||||
zone.onclick = () => picker.click();
|
||||
zone.onkeydown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); picker.click(); } };
|
||||
picker.onchange = () => fromFile(picker.files?.[0]);
|
||||
|
||||
zone.ondragover = (e) => { e.preventDefault(); zone.classList.add('is-over'); };
|
||||
zone.ondragleave = () => zone.classList.remove('is-over');
|
||||
zone.ondrop = (e) => {
|
||||
e.preventDefault();
|
||||
zone.classList.remove('is-over');
|
||||
fromFile(e.dataTransfer?.files?.[0]);
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user