mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Serve client at site root; redirect legacy /nostr/
The /nostr/ path was a leftover from when the site also hosted a Tor client. The app is Nostr-only now, so move the client to the site root and shorten poster URLs to https://voxvera.org/#naddr1... (drops both /nostr/ and the ?addr= query key, for a cleaner/smaller tear-off QR). - Move site/nostr/{nostr-client.js,css,locales.js,vendor} to site/. - site/index.html is now the client; site/nostr/index.html is a redirect stub that forwards /nostr/ to / while preserving the query string and #naddr fragment, so older poster URLs and printed QR codes still resolve. - Carry the naddr in the URL fragment (#) instead of ?addr=; the viewer reads query, fragment, or the full URL, so old links keep working. - Update README, AGENTS.md, and the spec to the new paths. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
828ac3a624
commit
77bfef1bbe
13 files changed
+222
-211
No files matched your search
@@ -4,9 +4,10 @@
|
||||
|
||||
## Current Architecture
|
||||
|
||||
- Active frontend: `site/nostr/`
|
||||
- Active frontend: `site/` (`index.html`, `nostr-client.js`, `nostr-client.css`, `locales.js`)
|
||||
- Static deployment root: `site`
|
||||
- Browser dependencies: vendored under `site/nostr/vendor/`
|
||||
- Browser dependencies: vendored under `site/vendor/`
|
||||
- Legacy path: `site/nostr/index.html` is a redirect stub forwarding old `/nostr/` poster URLs and QR codes to the root
|
||||
- Python helper package: `voxvera/nostr/`
|
||||
- Design plan: `docs/nostr-static-client-spec.md`
|
||||
|
||||
@@ -21,8 +22,8 @@
|
||||
|
||||
## Localization
|
||||
|
||||
- Use `site/nostr/locales.js` for flyer content defaults.
|
||||
- Use `NOSTR_UI` in `site/nostr/nostr-client.js` for editor/viewer controls and Nostr-specific labels.
|
||||
- Use `site/locales.js` for flyer content defaults.
|
||||
- Use `NOSTR_UI` in `site/nostr-client.js` for editor/viewer controls and Nostr-specific labels.
|
||||
- Add translations for every supported language when adding user-facing strings.
|
||||
- Check right-to-left languages when touching layout.
|
||||
|
||||
@@ -31,8 +32,8 @@
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node --check site/nostr/nostr-client.js
|
||||
node --check site/nostr-client.js
|
||||
pytest -q
|
||||
```
|
||||
|
||||
For visual or print changes, also run a local static server and inspect `/nostr/` in a browser.
|
||||
For visual or print changes, also run a local static server and inspect the site root (`/`) in a browser.
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
VoxVera is now a static Nostr-powered flyer client.
|
||||
|
||||
The active app lives at [`site/nostr/`](site/nostr/). It lets a user author flyer content, publish it as a public Nostr event, and render the flyer later from an event ID or Nostr address. The frontend is intentionally self-contained: relays store the content, and any static host can serve the client.
|
||||
The active app lives at the site root ([`site/`](site/)). It lets a user author flyer content, publish it as a public Nostr event, and render the flyer later from an event ID or Nostr address. The frontend is intentionally self-contained: relays store the content, and any static host can serve the client.
|
||||
|
||||
The previous Tor/OnionShare CLI version is preserved on the `legacy-tor` branch.
|
||||
|
||||
## What Is In `main`
|
||||
|
||||
- `site/nostr/`: static editor/viewer client
|
||||
- `site/nostr/vendor/`: vendored browser dependencies for Nostr and QR generation
|
||||
- `site/index.html`, `site/nostr-client.js`, `site/nostr-client.css`, `site/locales.js`: static editor/viewer client
|
||||
- `site/vendor/`: vendored browser dependencies for Nostr and QR generation
|
||||
- `site/nostr/index.html`: redirect stub so legacy `/nostr/` poster URLs and QR codes still resolve
|
||||
- `site/CNAME`: custom-domain target
|
||||
- `voxvera/nostr/`: small Python schema/validation helpers
|
||||
- `docs/nostr-static-client-spec.md`: design notes and development plan
|
||||
@@ -25,7 +26,7 @@ python3 -m http.server 8768 --directory site
|
||||
Then open:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8768/nostr/
|
||||
http://127.0.0.1:8768/
|
||||
```
|
||||
|
||||
No build command is required for the frontend.
|
||||
@@ -39,12 +40,12 @@ Point Vercel at this repository on `main`.
|
||||
- Build command: leave empty
|
||||
- Output directory: `site`
|
||||
|
||||
The root `site/index.html` redirects to `/nostr/`, so the domain can serve the current client without preserving the legacy Tor page.
|
||||
The client is served from the domain root (`site/index.html`). `site/nostr/index.html` is a redirect stub that forwards the legacy `/nostr/` path to the root while preserving the query/fragment, so older poster URLs and printed QR codes still resolve.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
node --check site/nostr/nostr-client.js
|
||||
node --check site/nostr-client.js
|
||||
pytest -q
|
||||
```
|
||||
|
||||
|
||||
@@ -22,16 +22,16 @@ Nostr relays store the flyer source content. Static hosts only serve the client.
|
||||
|
||||
```text
|
||||
site/
|
||||
index.html
|
||||
index.html (the client)
|
||||
nostr-client.css
|
||||
nostr-client.js
|
||||
locales.js
|
||||
CNAME
|
||||
vendor/
|
||||
nostr-tools/
|
||||
qrcode-generator/
|
||||
nostr/
|
||||
index.html
|
||||
nostr-client.css
|
||||
nostr-client.js
|
||||
locales.js
|
||||
vendor/
|
||||
nostr-tools/
|
||||
qrcode-generator/
|
||||
index.html (redirect stub for legacy /nostr/ URLs)
|
||||
voxvera/
|
||||
nostr/
|
||||
schema.py
|
||||
@@ -49,7 +49,7 @@ No build command is required. Vercel should use:
|
||||
- output directory: `site`
|
||||
- build command: empty
|
||||
|
||||
`site/index.html` redirects root traffic to `/nostr/`.
|
||||
The client is served from the root (`site/index.html`). `site/nostr/index.html` is a redirect stub that forwards the legacy `/nostr/` path to the root, preserving the query string and `#naddr` fragment, so poster URLs and QR codes printed before the move still resolve.
|
||||
|
||||
## Event Model
|
||||
|
||||
@@ -84,7 +84,7 @@ Payload:
|
||||
"content": "Flyer body text",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://creator.example/action",
|
||||
"tear_off_link": "https://voxvera.org/nostr/?addr=naddr1...",
|
||||
"tear_off_link": "https://voxvera.org/nostr/#naddr1...",
|
||||
"footer_message": "0110010",
|
||||
"qr_target": "flyer_url"
|
||||
}
|
||||
@@ -102,6 +102,8 @@ When publishing, the client computes the replaceable-event `naddr` and writes th
|
||||
|
||||
The `naddr` is encoded with a **single relay hint** (the primary configured relay) instead of the full relay list. This keeps the poster URL short and its tear-off QR code easy to scan — a typical poster URL drops from ~233 characters (full list) to ~171 — while still pointing a fresh viewer at a relay that has the event. Viewers also fall back to `DEFAULT_RELAYS`, which is where flyers are published by default. This stays a standard NIP-19 `naddr`, so any Nostr client can resolve it, and older relay-bearing `naddr` URLs continue to decode and resolve unchanged.
|
||||
|
||||
The naddr is carried in the URL **fragment** (`#naddr1...`) rather than a `?addr=` query parameter. The fragment is shorter (less QR clutter), needs no URL-encoding, and never reaches the static host. `getUrlEventReference` reads the naddr from query params, the fragment, or anywhere in the full URL, so older `?addr=` poster URLs and printed QR codes still resolve.
|
||||
|
||||
Supported values:
|
||||
|
||||
- `flyer_url`: tear-off QR points to the hosted client URL for the poster
|
||||
@@ -122,9 +124,9 @@ The client and schema helper must:
|
||||
|
||||
## Localization
|
||||
|
||||
Flyer defaults come from `site/nostr/locales.js`.
|
||||
Flyer defaults come from `site/locales.js`.
|
||||
|
||||
Nostr editor/viewer controls and Nostr-specific flyer labels come from `NOSTR_UI` in `site/nostr/nostr-client.js`.
|
||||
Nostr editor/viewer controls and Nostr-specific flyer labels come from `NOSTR_UI` in `site/nostr-client.js`.
|
||||
|
||||
Language changes should:
|
||||
|
||||
@@ -161,7 +163,7 @@ browser.
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
node --check site/nostr/nostr-client.js
|
||||
node --check site/nostr-client.js
|
||||
pytest -q
|
||||
```
|
||||
|
||||
|
||||
+173
-12
@@ -1,15 +1,176 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="0; url=/nostr/">
|
||||
<title>VoxVera</title>
|
||||
<script>
|
||||
window.location.replace("/nostr/" + window.location.search + window.location.hash);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="/nostr/">Open VoxVera</a></p>
|
||||
</body>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VoxVera Nostr Client</title>
|
||||
<link rel="stylesheet" href="nostr-client.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<h1 id="client-title">VoxVera Nostr Client</h1>
|
||||
<p id="client-description">Static editor and viewer for decentralized flyer source events.</p>
|
||||
</div>
|
||||
<div class="topbar-controls">
|
||||
<nav class="mode-tabs" aria-label="Client mode">
|
||||
<button id="editor-tab" class="tab active" type="button" data-mode="editor">Editor</button>
|
||||
<button id="viewer-tab" class="tab" type="button" data-mode="viewer">Viewer</button>
|
||||
</nav>
|
||||
<select id="topbar-lang" aria-label="Language"></select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="viewer-floating" aria-label="Viewer controls">
|
||||
<button type="button" id="open-viewer-controls">Load Event</button>
|
||||
<button type="button" id="viewer-print">Print</button>
|
||||
<button type="button" id="viewer-editor">Editor</button>
|
||||
<select id="viewer-lang" aria-label="Language"></select>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<section id="editor-panel" class="panel active" aria-labelledby="editor-heading">
|
||||
<div class="workspace">
|
||||
<form id="editor-form" class="editor-form">
|
||||
<div class="section-title">
|
||||
<h2 id="editor-heading">Editor</h2>
|
||||
<span id="signer-state" class="state-pill">Checking signer</span>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span id="label-relays">Relays</span>
|
||||
<textarea id="editor-relays" rows="3" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<div class="identity-row">
|
||||
<span id="identity-state" class="state-pill">Checking identity</span>
|
||||
<button type="button" id="generate-anon-identity" class="secondary-button">Generate anonymous npub</button>
|
||||
</div>
|
||||
|
||||
<div class="field-grid">
|
||||
<label>
|
||||
<span id="label-flyer-name">Flyer Name</span>
|
||||
<input id="field-folder-name" name="folder_name" maxlength="64" value="voxvera">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-language">Language</span>
|
||||
<select id="field-lang" name="lang">
|
||||
<option value="en">en</option>
|
||||
<option value="es">es</option>
|
||||
<option value="de">de</option>
|
||||
<option value="fr">fr</option>
|
||||
<option value="ru">ru</option>
|
||||
<option value="he">he</option>
|
||||
<option value="ar">ar</option>
|
||||
<option value="ja">ja</option>
|
||||
<option value="hi">hi</option>
|
||||
<option value="sw">sw</option>
|
||||
<option value="pt">pt</option>
|
||||
<option value="tr">tr</option>
|
||||
<option value="zh">zh</option>
|
||||
<option value="fa">fa</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span id="label-page-title">Page title</span>
|
||||
<input id="field-name" name="name" maxlength="120" value="Vox Vera Printable Flyers">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-title">Title</span>
|
||||
<input id="field-title" name="title" maxlength="80" value="TOP SECRET">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-subtitle">Subtitle</span>
|
||||
<input id="field-subtitle" name="subtitle" maxlength="120" value="DO ~~NOT~~ DISTRIBUTE">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-headline">Headline</span>
|
||||
<input id="field-headline" name="headline" maxlength="160" value="OPERATION VOX VERA">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-content">Content</span>
|
||||
<textarea id="field-content" name="content" rows="9" maxlength="10000"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-url-message">URL message</span>
|
||||
<input id="field-url-message" name="url_message" maxlength="240" value="Follow this link to learn more.">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-poster-url">Content URL</span>
|
||||
<input id="field-url" name="url" maxlength="2048" value="https://voxvera.org/">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-footer-message">Footer message</span>
|
||||
<input id="field-footer-message" name="footer_message" maxlength="240" value="0110010 0101011 0110010 0111101 0110100">
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button type="button" id="preview-editor">Preview</button>
|
||||
<button type="button" id="export-event">Export event JSON</button>
|
||||
<button type="submit" id="publish-event">Sign and publish</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<aside class="result-panel">
|
||||
<h2 id="published-event-heading">Published Event</h2>
|
||||
<dl class="result-list">
|
||||
<dt id="result-event-id-label">Event ID</dt>
|
||||
<dd class="copy-value"><code id="event-id-output">Not published</code><button type="button" class="copy-button" data-copy-target="event-id-output">Copy</button></dd>
|
||||
<dt id="result-author-npub-label">Author npub</dt>
|
||||
<dd class="copy-value"><code id="author-npub-output">Not generated</code><button type="button" class="copy-button" data-copy-target="author-npub-output">Copy</button></dd>
|
||||
<dt id="result-naddr-label">Nostr address</dt>
|
||||
<dd class="copy-value"><code id="naddr-output">Not generated</code><button type="button" class="copy-button" data-copy-target="naddr-output">Copy</button></dd>
|
||||
<dt id="result-poster-url-label">Poster URL</dt>
|
||||
<dd class="copy-value"><code id="poster-url-output">Not generated</code><button type="button" class="copy-button" data-copy-target="poster-url-output">Copy</button></dd>
|
||||
<dt id="result-status-label">Status</dt>
|
||||
<dd id="publish-status">Idle</dd>
|
||||
</dl>
|
||||
<textarea id="event-json-output" rows="14" spellcheck="false" readonly></textarea>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="viewer-panel" class="panel viewer-drawer" aria-labelledby="viewer-heading">
|
||||
<div class="workspace">
|
||||
<form id="viewer-form" class="editor-form">
|
||||
<div class="section-title">
|
||||
<h2 id="viewer-heading">Viewer</h2>
|
||||
<button type="button" id="close-viewer-controls" class="secondary-button">Close</button>
|
||||
</div>
|
||||
<label>
|
||||
<span id="label-event-id">Event ID</span>
|
||||
<input id="viewer-event-id" spellcheck="false" placeholder="naddr, event id, note1, nevent1, or nostr: URL">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-viewer-relays">Relays</span>
|
||||
<textarea id="viewer-relays" rows="3" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button id="viewer-fetch-render" type="submit">Fetch and render</button>
|
||||
<button type="button" id="copy-viewer-config">Copy config JSON</button>
|
||||
</div>
|
||||
<p id="viewer-status" class="status-line">Idle</p>
|
||||
</form>
|
||||
|
||||
<aside class="result-panel">
|
||||
<h2 id="normalized-config-heading">Normalized Config</h2>
|
||||
<textarea id="viewer-config-output" rows="16" spellcheck="false" readonly></textarea>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="preview-band" aria-label="Flyer preview">
|
||||
<div class="preview-toolbar">
|
||||
<h2 id="preview-heading">Preview</h2>
|
||||
<button type="button" id="print-preview">Print preview</button>
|
||||
</div>
|
||||
<div id="flyer-preview" class="flyer-preview"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="locales.js"></script>
|
||||
<script src="vendor/qrcode-generator/qrcode.js"></script>
|
||||
<script src="vendor/nostr-tools/nostr.bundle.js"></script>
|
||||
<script src="nostr-client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File renamed without changes.
File renamed without changes.
@@ -1406,7 +1406,11 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
}
|
||||
|
||||
function posterUrlForNaddr(naddr) {
|
||||
return `${clientBaseUrl()}?addr=${encodeURIComponent(naddr)}`;
|
||||
// The naddr is carried in the URL fragment (#) rather than a ?addr= query
|
||||
// param: it is shorter (less QR clutter), needs no URL-encoding (naddr is
|
||||
// bech32, URL-safe), and never reaches the static host. The viewer reads
|
||||
// both forms, so older ?addr= URLs and QR codes still resolve.
|
||||
return `${clientBaseUrl()}#${naddr}`;
|
||||
}
|
||||
|
||||
function withPosterUrl(payload, identity, relays) {
|
||||
+15
-173
@@ -1,176 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VoxVera Nostr Client</title>
|
||||
<link rel="stylesheet" href="nostr-client.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<h1 id="client-title">VoxVera Nostr Client</h1>
|
||||
<p id="client-description">Static editor and viewer for decentralized flyer source events.</p>
|
||||
</div>
|
||||
<div class="topbar-controls">
|
||||
<nav class="mode-tabs" aria-label="Client mode">
|
||||
<button id="editor-tab" class="tab active" type="button" data-mode="editor">Editor</button>
|
||||
<button id="viewer-tab" class="tab" type="button" data-mode="viewer">Viewer</button>
|
||||
</nav>
|
||||
<select id="topbar-lang" aria-label="Language"></select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="viewer-floating" aria-label="Viewer controls">
|
||||
<button type="button" id="open-viewer-controls">Load Event</button>
|
||||
<button type="button" id="viewer-print">Print</button>
|
||||
<button type="button" id="viewer-editor">Editor</button>
|
||||
<select id="viewer-lang" aria-label="Language"></select>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<section id="editor-panel" class="panel active" aria-labelledby="editor-heading">
|
||||
<div class="workspace">
|
||||
<form id="editor-form" class="editor-form">
|
||||
<div class="section-title">
|
||||
<h2 id="editor-heading">Editor</h2>
|
||||
<span id="signer-state" class="state-pill">Checking signer</span>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span id="label-relays">Relays</span>
|
||||
<textarea id="editor-relays" rows="3" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<div class="identity-row">
|
||||
<span id="identity-state" class="state-pill">Checking identity</span>
|
||||
<button type="button" id="generate-anon-identity" class="secondary-button">Generate anonymous npub</button>
|
||||
</div>
|
||||
|
||||
<div class="field-grid">
|
||||
<label>
|
||||
<span id="label-flyer-name">Flyer Name</span>
|
||||
<input id="field-folder-name" name="folder_name" maxlength="64" value="voxvera">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-language">Language</span>
|
||||
<select id="field-lang" name="lang">
|
||||
<option value="en">en</option>
|
||||
<option value="es">es</option>
|
||||
<option value="de">de</option>
|
||||
<option value="fr">fr</option>
|
||||
<option value="ru">ru</option>
|
||||
<option value="he">he</option>
|
||||
<option value="ar">ar</option>
|
||||
<option value="ja">ja</option>
|
||||
<option value="hi">hi</option>
|
||||
<option value="sw">sw</option>
|
||||
<option value="pt">pt</option>
|
||||
<option value="tr">tr</option>
|
||||
<option value="zh">zh</option>
|
||||
<option value="fa">fa</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span id="label-page-title">Page title</span>
|
||||
<input id="field-name" name="name" maxlength="120" value="Vox Vera Printable Flyers">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-title">Title</span>
|
||||
<input id="field-title" name="title" maxlength="80" value="TOP SECRET">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-subtitle">Subtitle</span>
|
||||
<input id="field-subtitle" name="subtitle" maxlength="120" value="DO ~~NOT~~ DISTRIBUTE">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-headline">Headline</span>
|
||||
<input id="field-headline" name="headline" maxlength="160" value="OPERATION VOX VERA">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-content">Content</span>
|
||||
<textarea id="field-content" name="content" rows="9" maxlength="10000"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-url-message">URL message</span>
|
||||
<input id="field-url-message" name="url_message" maxlength="240" value="Follow this link to learn more.">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-poster-url">Content URL</span>
|
||||
<input id="field-url" name="url" maxlength="2048" value="https://voxvera.org/">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-footer-message">Footer message</span>
|
||||
<input id="field-footer-message" name="footer_message" maxlength="240" value="0110010 0101011 0110010 0111101 0110100">
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button type="button" id="preview-editor">Preview</button>
|
||||
<button type="button" id="export-event">Export event JSON</button>
|
||||
<button type="submit" id="publish-event">Sign and publish</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<aside class="result-panel">
|
||||
<h2 id="published-event-heading">Published Event</h2>
|
||||
<dl class="result-list">
|
||||
<dt id="result-event-id-label">Event ID</dt>
|
||||
<dd class="copy-value"><code id="event-id-output">Not published</code><button type="button" class="copy-button" data-copy-target="event-id-output">Copy</button></dd>
|
||||
<dt id="result-author-npub-label">Author npub</dt>
|
||||
<dd class="copy-value"><code id="author-npub-output">Not generated</code><button type="button" class="copy-button" data-copy-target="author-npub-output">Copy</button></dd>
|
||||
<dt id="result-naddr-label">Nostr address</dt>
|
||||
<dd class="copy-value"><code id="naddr-output">Not generated</code><button type="button" class="copy-button" data-copy-target="naddr-output">Copy</button></dd>
|
||||
<dt id="result-poster-url-label">Poster URL</dt>
|
||||
<dd class="copy-value"><code id="poster-url-output">Not generated</code><button type="button" class="copy-button" data-copy-target="poster-url-output">Copy</button></dd>
|
||||
<dt id="result-status-label">Status</dt>
|
||||
<dd id="publish-status">Idle</dd>
|
||||
</dl>
|
||||
<textarea id="event-json-output" rows="14" spellcheck="false" readonly></textarea>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="viewer-panel" class="panel viewer-drawer" aria-labelledby="viewer-heading">
|
||||
<div class="workspace">
|
||||
<form id="viewer-form" class="editor-form">
|
||||
<div class="section-title">
|
||||
<h2 id="viewer-heading">Viewer</h2>
|
||||
<button type="button" id="close-viewer-controls" class="secondary-button">Close</button>
|
||||
</div>
|
||||
<label>
|
||||
<span id="label-event-id">Event ID</span>
|
||||
<input id="viewer-event-id" spellcheck="false" placeholder="naddr, event id, note1, nevent1, or nostr: URL">
|
||||
</label>
|
||||
<label>
|
||||
<span id="label-viewer-relays">Relays</span>
|
||||
<textarea id="viewer-relays" rows="3" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button id="viewer-fetch-render" type="submit">Fetch and render</button>
|
||||
<button type="button" id="copy-viewer-config">Copy config JSON</button>
|
||||
</div>
|
||||
<p id="viewer-status" class="status-line">Idle</p>
|
||||
</form>
|
||||
|
||||
<aside class="result-panel">
|
||||
<h2 id="normalized-config-heading">Normalized Config</h2>
|
||||
<textarea id="viewer-config-output" rows="16" spellcheck="false" readonly></textarea>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="preview-band" aria-label="Flyer preview">
|
||||
<div class="preview-toolbar">
|
||||
<h2 id="preview-heading">Preview</h2>
|
||||
<button type="button" id="print-preview">Print preview</button>
|
||||
</div>
|
||||
<div id="flyer-preview" class="flyer-preview"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="locales.js"></script>
|
||||
<script src="vendor/qrcode-generator/qrcode.js"></script>
|
||||
<script src="vendor/nostr-tools/nostr.bundle.js"></script>
|
||||
<script src="nostr-client.js"></script>
|
||||
</body>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="0; url=/">
|
||||
<title>VoxVera</title>
|
||||
<script>
|
||||
// The client now lives at the site root. /nostr/ is kept only so older
|
||||
// poster URLs and printed QR codes (https://.../nostr/#naddr1... or
|
||||
// ?addr=...) still resolve. Forward to root, preserving query + fragment.
|
||||
window.location.replace("/" + window.location.search + window.location.hash);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="/">Open VoxVera</a></p>
|
||||
</body>
|
||||
</html>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user