mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Improve flyer tear-offs, shorten poster URL, adaptive tracking
- Tear-off tabs: single hyperlinked statement (no printed URL), with "Share and reprint." on its own line; localize "Click to open this poster." across all 14 languages. - Show the full event ID, bottom-anchored on each tab, split into two 32-char lines at max readable monospace size. - Shorten the poster URL by encoding the naddr with a single relay hint instead of the full relay list (~233 -> ~171 chars) for an easier-to- scan QR; still a standard naddr, older URLs resolve unchanged. - Add per-script letter-spacing policy (ls-full/ls-cjk/ls-none) to fit more text per line where safe, and turn tracking off for Arabic, Persian, and Hindi (fixes broken cursive joins / combining marks). - Move the "field too long" warning inline under the offending field instead of a single bottom status line. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
9baf5fb195
commit
aedb381dfb
4 files changed
+188
-73
No files matched your search
@@ -100,6 +100,8 @@ Default behavior: `qr_target: "flyer_url"`.
|
||||
|
||||
When publishing, the client computes the replaceable-event `naddr` and writes the generated poster URL to `tear_off_link`. The tear-off tabs and tear-off QR code point at `tear_off_link`, so people can re-open and reprint the flyer. The main flyer QR code points at `url`, so viewers can visit the creator's intended destination.
|
||||
|
||||
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.
|
||||
|
||||
Supported values:
|
||||
|
||||
- `flyer_url`: tear-off QR points to the hosted client URL for the poster
|
||||
@@ -135,6 +137,27 @@ Language changes should:
|
||||
- update printed side-tab labels
|
||||
- respect RTL direction metadata
|
||||
|
||||
### Letter-spacing by script
|
||||
|
||||
Flyer text uses tighter letter-spacing than the original design to fit more
|
||||
characters per line, but `letter-spacing` is not script-neutral, so it is
|
||||
applied per script via a policy class on the flyer `.container`
|
||||
(`LETTER_SPACING_POLICY` in `nostr-client.js`; `ls-full` / `ls-cjk` / `ls-none`
|
||||
rules and `--ls-*` variables in `nostr-client.css`):
|
||||
|
||||
- `full` — Latin, Cyrillic, Hebrew: reduced positive tracking on the display
|
||||
fields plus a slight negative on body/URL to reclaim line width.
|
||||
- `cjk` — Japanese, Chinese: tracking is clamped to `0` and never negative, so
|
||||
full-width glyphs do not overlap.
|
||||
- `none` — Arabic, Persian, Hindi: letter-spacing is turned off entirely. These
|
||||
are cursive or complex scripts where any tracking breaks the letter joins or
|
||||
detaches combining marks. (This also corrected the prior behavior, which
|
||||
applied the design's display tracking to every language unconditionally.)
|
||||
|
||||
When adding a language, classify its script in `LETTER_SPACING_POLICY` (default
|
||||
is `full`) and verify the headline, subtitle, and body render correctly in the
|
||||
browser.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
|
||||
@@ -104,8 +104,6 @@
|
||||
<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>
|
||||
<p id="field-fit-status" class="status-line field-fit-status" aria-live="polite"></p>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" id="preview-editor">Preview</button>
|
||||
<button type="button" id="export-event">Export event JSON</button>
|
||||
|
||||
@@ -258,8 +258,8 @@ code {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.field-fit-status {
|
||||
margin: 0 0 10px;
|
||||
.field-error {
|
||||
margin: 0;
|
||||
color: var(--warn);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
@@ -382,6 +382,41 @@ a {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
page-break-inside: avoid;
|
||||
/* Per-script letter-spacing, set by the ls-* policy class (see
|
||||
LETTER_SPACING_POLICY in nostr-client.js). Defaults below match the
|
||||
historical design and act as a safe fallback if no policy class is set. */
|
||||
--ls-display: 2px;
|
||||
--ls-subtitle: 6px;
|
||||
--ls-body: 0;
|
||||
--ls-url: 0;
|
||||
--ls-footer: 0;
|
||||
}
|
||||
|
||||
/* Latin, Cyrillic, Hebrew: tighten tracking to reclaim line space. */
|
||||
.container.ls-full {
|
||||
--ls-display: 0.5px;
|
||||
--ls-subtitle: 2px;
|
||||
--ls-body: -0.3px;
|
||||
--ls-url: -0.3px;
|
||||
--ls-footer: 0;
|
||||
}
|
||||
|
||||
/* CJK: tracking is safe but must never go negative (glyphs would overlap). */
|
||||
.container.ls-cjk {
|
||||
--ls-display: 0;
|
||||
--ls-subtitle: 0;
|
||||
--ls-body: 0;
|
||||
--ls-url: 0;
|
||||
--ls-footer: 0;
|
||||
}
|
||||
|
||||
/* Arabic, Persian, Hindi: letter-spacing breaks shaping — turn it off. */
|
||||
.container.ls-none {
|
||||
--ls-display: normal;
|
||||
--ls-subtitle: normal;
|
||||
--ls-body: normal;
|
||||
--ls-url: normal;
|
||||
--ls-footer: normal;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
@@ -410,6 +445,26 @@ a {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tear-off-event-id {
|
||||
margin-top: auto;
|
||||
padding-top: 4px;
|
||||
font-size: 8px;
|
||||
line-height: 1.2;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.tear-off-event-id-value {
|
||||
display: block;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.2px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
@@ -463,7 +518,7 @@ a {
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 2px;
|
||||
letter-spacing: var(--ls-display);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -473,7 +528,7 @@ a {
|
||||
.distribute {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 6px;
|
||||
letter-spacing: var(--ls-subtitle);
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
@@ -487,6 +542,7 @@ a {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: var(--ls-body);
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -516,6 +572,7 @@ a {
|
||||
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
letter-spacing: var(--ls-url);
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
@@ -551,6 +608,7 @@ hr {
|
||||
.container .footer .binary {
|
||||
margin-bottom: 8px;
|
||||
margin-top: 10px;
|
||||
letter-spacing: var(--ls-footer);
|
||||
}
|
||||
|
||||
.container .footer .credit {
|
||||
|
||||
+103
-67
@@ -12,6 +12,23 @@
|
||||
const FALLBACK_LANG = "en";
|
||||
const LANGUAGE_SELECT_IDS = ["topbar-lang", "field-lang", "viewer-lang"];
|
||||
const EVENT_KIND = 30078;
|
||||
// Per-script letter-spacing policy for flyer text. `letter-spacing` is not
|
||||
// script-neutral, so the flyer tightens tracking only where it is safe:
|
||||
// "full" (Latin, Cyrillic, Hebrew): reduced positive tracking + slight
|
||||
// negative on body/url to reclaim space per line.
|
||||
// "cjk" (Japanese, Chinese): tracking clamped to 0 — full-width glyphs
|
||||
// must not overlap, so never negative.
|
||||
// "none" (Arabic, Persian, Hindi): letter-spacing OFF. These are cursive
|
||||
// or complex scripts where any tracking breaks the letter joins or
|
||||
// detaches combining marks; neutralizing it also fixes rendering.
|
||||
// The values themselves live in nostr-client.css, keyed by the ls-* class.
|
||||
const LETTER_SPACING_POLICY = {
|
||||
ar: "none", fa: "none", hi: "none",
|
||||
ja: "cjk", zh: "cjk"
|
||||
};
|
||||
function trackingPolicy(lang) {
|
||||
return LETTER_SPACING_POLICY[supportedLang(lang)] || "full";
|
||||
}
|
||||
const FIELD_LIMITS = {
|
||||
folder_name: 64,
|
||||
name: 120,
|
||||
@@ -119,15 +136,6 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
"field-url": ".qr-code-url a",
|
||||
"field-footer-message": ".footer .binary"
|
||||
};
|
||||
const DISPLAY_FIT_LABEL_KEYS = {
|
||||
"field-title": "labels.title",
|
||||
"field-subtitle": "labels.subtitle",
|
||||
"field-headline": "labels.headline",
|
||||
"field-content": "labels.content",
|
||||
"field-url-message": "labels.url_message",
|
||||
"field-url": "labels.poster_url",
|
||||
"field-footer-message": "labels.footer_message"
|
||||
};
|
||||
const NOSTR_UI = {
|
||||
ar: {
|
||||
back: "رجوع",
|
||||
@@ -141,7 +149,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "اللغة",
|
||||
load_event: "تحميل الحدث",
|
||||
normalized_config: "الإعداد الموحد",
|
||||
open_poster: "افتح هذا الملصق:",
|
||||
open_poster: "انقر لفتح هذا الملصق.",
|
||||
page_title: "عنوان الصفحة",
|
||||
preview: "معاينة",
|
||||
print: "اطبع المنشور",
|
||||
@@ -193,7 +201,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Sprache",
|
||||
load_event: "Event laden",
|
||||
normalized_config: "Normalisierte Konfiguration",
|
||||
open_poster: "Dieses Plakat öffnen:",
|
||||
open_poster: "Klicken Sie, um dieses Plakat zu öffnen.",
|
||||
page_title: "Seitentitel",
|
||||
preview: "Vorschau",
|
||||
print: "Flyer drucken",
|
||||
@@ -245,7 +253,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Language",
|
||||
load_event: "Load Event",
|
||||
normalized_config: "Normalized Config",
|
||||
open_poster: "Open this poster:",
|
||||
open_poster: "Click to open this poster.",
|
||||
page_title: "Page title",
|
||||
preview: "Preview",
|
||||
print: "Print this page",
|
||||
@@ -298,7 +306,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Idioma",
|
||||
load_event: "Cargar evento",
|
||||
normalized_config: "Configuración normalizada",
|
||||
open_poster: "Abra este cartel:",
|
||||
open_poster: "Haga clic para abrir este cartel.",
|
||||
page_title: "Título de la página",
|
||||
preview: "Vista previa",
|
||||
print: "Imprimir volante",
|
||||
@@ -350,7 +358,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "زبان",
|
||||
load_event: "بارگذاری رویداد",
|
||||
normalized_config: "پیکربندی عادیشده",
|
||||
open_poster: "این پوستر را باز کنید:",
|
||||
open_poster: "برای باز کردن این پوستر کلیک کنید.",
|
||||
page_title: "عنوان صفحه",
|
||||
preview: "پیشنمایش",
|
||||
print: "چاپ اعلامیه",
|
||||
@@ -402,7 +410,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Langue",
|
||||
load_event: "Charger l'evenement",
|
||||
normalized_config: "Configuration normalisee",
|
||||
open_poster: "Ouvrez cette affiche :",
|
||||
open_poster: "Cliquez pour ouvrir cette affiche.",
|
||||
page_title: "Titre de page",
|
||||
preview: "Apercu",
|
||||
print: "Imprimer le flyer",
|
||||
@@ -454,7 +462,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "שפה",
|
||||
load_event: "טען אירוע",
|
||||
normalized_config: "תצורה מנורמלת",
|
||||
open_poster: "פתח את הכרזה הזו:",
|
||||
open_poster: "לחץ כדי לפתוח כרזה זו.",
|
||||
page_title: "כותרת עמוד",
|
||||
preview: "תצוגה מקדימה",
|
||||
print: "הדפס פלייר",
|
||||
@@ -506,7 +514,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "भाषा",
|
||||
load_event: "इवेंट लोड करें",
|
||||
normalized_config: "सामान्यीकृत कॉन्फिग",
|
||||
open_poster: "यह पोस्टर खोलें:",
|
||||
open_poster: "इस पोस्टर को खोलने के लिए क्लिक करें।",
|
||||
page_title: "पृष्ठ शीर्षक",
|
||||
preview: "पूर्वावलोकन",
|
||||
print: "फ्लायर प्रिंट करें",
|
||||
@@ -558,7 +566,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "言語",
|
||||
load_event: "イベントを読み込む",
|
||||
normalized_config: "正規化設定",
|
||||
open_poster: "このポスターを開く:",
|
||||
open_poster: "クリックしてこのポスターを開く。",
|
||||
page_title: "ページタイトル",
|
||||
preview: "プレビュー",
|
||||
print: "フライヤーを印刷",
|
||||
@@ -610,7 +618,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Idioma",
|
||||
load_event: "Carregar evento",
|
||||
normalized_config: "Configuracao normalizada",
|
||||
open_poster: "Abra este cartaz:",
|
||||
open_poster: "Clique para abrir este cartaz.",
|
||||
page_title: "Titulo da pagina",
|
||||
preview: "Previa",
|
||||
print: "Imprimir Panfleto",
|
||||
@@ -662,7 +670,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Язык",
|
||||
load_event: "Загрузить событие",
|
||||
normalized_config: "Нормализованная конфигурация",
|
||||
open_poster: "Откройте этот плакат:",
|
||||
open_poster: "Нажмите, чтобы открыть этот плакат.",
|
||||
page_title: "Заголовок страницы",
|
||||
preview: "Предпросмотр",
|
||||
print: "Печать листовки",
|
||||
@@ -714,7 +722,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Lugha",
|
||||
load_event: "Pakia tukio",
|
||||
normalized_config: "Usanidi uliorekebishwa",
|
||||
open_poster: "Fungua bango hili:",
|
||||
open_poster: "Bofya kufungua bango hili.",
|
||||
page_title: "Kichwa cha ukurasa",
|
||||
preview: "Hakiki",
|
||||
print: "Chapa Kipeperushi",
|
||||
@@ -766,7 +774,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "Dil",
|
||||
load_event: "Olay yükle",
|
||||
normalized_config: "Normalleştirilmiş yapılandırma",
|
||||
open_poster: "Bu posteri açın:",
|
||||
open_poster: "Bu posteri açmak için tıklayın.",
|
||||
page_title: "Sayfa başlığı",
|
||||
preview: "Önizleme",
|
||||
print: "İlanı Yazdır",
|
||||
@@ -818,7 +826,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
language: "语言",
|
||||
load_event: "加载事件",
|
||||
normalized_config: "标准化配置",
|
||||
open_poster: "打开此海报:",
|
||||
open_poster: "点击打开此海报。",
|
||||
page_title: "页面标题",
|
||||
preview: "预览",
|
||||
print: "打印传单",
|
||||
@@ -861,6 +869,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
};
|
||||
let flyerSource = "default";
|
||||
let loadedEventConfig = null;
|
||||
let loadedEventId = "";
|
||||
const lastAcceptedFieldValues = {};
|
||||
|
||||
const el = (id) => document.getElementById(id);
|
||||
@@ -918,18 +927,46 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
return Boolean(node && node.dataset.emptyKey);
|
||||
}
|
||||
|
||||
function fieldLabel(fieldId, lang = currentUiLang()) {
|
||||
const localePath = DISPLAY_FIT_LABEL_KEYS[fieldId];
|
||||
if (localePath) return translate(localePath, lang) || fieldId;
|
||||
// The "too long" warning is shown inline under the offending field (so the
|
||||
// issue is visible without scrolling to a single shared status line). The
|
||||
// message element is created lazily inside the field's <label>.
|
||||
function fieldErrorElement(fieldId) {
|
||||
const field = el(fieldId);
|
||||
const label = field ? field.closest("label") : null;
|
||||
const labelText = label ? label.querySelector("span") : null;
|
||||
return labelText ? labelText.textContent.trim() : fieldId;
|
||||
if (!label) return null;
|
||||
let node = label.querySelector(".field-error");
|
||||
if (!node) {
|
||||
node = document.createElement("p");
|
||||
node.className = "field-error";
|
||||
node.setAttribute("role", "alert");
|
||||
node.hidden = true;
|
||||
label.appendChild(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function setFitStatus(message) {
|
||||
const status = el("field-fit-status");
|
||||
if (status) status.textContent = message || "";
|
||||
function setFieldError(fieldId, message) {
|
||||
const node = fieldErrorElement(fieldId);
|
||||
if (node) {
|
||||
node.textContent = message || "";
|
||||
node.hidden = !message;
|
||||
}
|
||||
const field = el(fieldId);
|
||||
if (field) {
|
||||
if (message) field.setAttribute("aria-invalid", "true");
|
||||
else field.removeAttribute("aria-invalid");
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllFieldErrors() {
|
||||
document.querySelectorAll("#editor-form .field-error").forEach((node) => {
|
||||
node.textContent = "";
|
||||
node.hidden = true;
|
||||
});
|
||||
TEXT_FIELD_IDS.forEach((fieldId) => {
|
||||
const field = el(fieldId);
|
||||
if (field) field.removeAttribute("aria-invalid");
|
||||
});
|
||||
}
|
||||
|
||||
function syncAcceptedFieldValues() {
|
||||
@@ -959,6 +996,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
el("field-footer-message").value = normalized.footer_message;
|
||||
syncUiLanguage(normalized.lang);
|
||||
syncAcceptedFieldValues();
|
||||
clearAllFieldErrors();
|
||||
}
|
||||
|
||||
function printableTitle() {
|
||||
@@ -1107,6 +1145,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
function applyFlyerDefaults(lang) {
|
||||
const defaults = localeDefaults(lang);
|
||||
flyerSource = "default";
|
||||
loadedEventId = "";
|
||||
el("field-lang").value = defaults.lang;
|
||||
el("field-folder-name").value = defaults.folder_name;
|
||||
el("field-name").value = defaults.name;
|
||||
@@ -1119,7 +1158,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
el("field-footer-message").value = defaults.footer_message;
|
||||
syncUiLanguage(defaults.lang);
|
||||
syncAcceptedFieldValues();
|
||||
setFitStatus("");
|
||||
clearAllFieldErrors();
|
||||
}
|
||||
|
||||
function applyLanguageChange(lang) {
|
||||
@@ -1132,7 +1171,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
|
||||
el("field-lang").value = selected;
|
||||
syncUiLanguage(selected);
|
||||
renderPreview(normalizePayload(buildPayloadFromForm()));
|
||||
renderPreview({ ...normalizePayload(buildPayloadFromForm()), event_id: loadedEventId });
|
||||
}
|
||||
|
||||
function refreshSignerState() {
|
||||
@@ -1346,12 +1385,19 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
return `voxvera:${slugify(payload.folder_name)}`;
|
||||
}
|
||||
|
||||
// The naddr carries a single relay hint (the primary configured relay) rather
|
||||
// than the full relay list. This keeps the poster URL short and its tear-off
|
||||
// QR easy to scan (~171 chars vs ~233 for the old full-list URL) while still
|
||||
// pointing a fresh viewer at a relay that has the event. Viewers also fall
|
||||
// back to DEFAULT_RELAYS. Older relay-bearing naddr URLs still decode and
|
||||
// resolve unchanged.
|
||||
function buildNaddr(identity, payload, relays) {
|
||||
const relayHints = Array.isArray(relays) ? relays.slice(0, 1) : [];
|
||||
return nostrTools().nip19.naddrEncode({
|
||||
identifier: flyerIdentifier(payload),
|
||||
pubkey: identity.pubkey,
|
||||
kind: EVENT_KIND,
|
||||
relays
|
||||
relays: relayHints
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1622,48 +1668,35 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
}
|
||||
}
|
||||
|
||||
function truncateMiddle(value, prefixLength, suffixLength) {
|
||||
const text = String(value || "");
|
||||
if (text.length <= prefixLength + suffixLength + 3) return text;
|
||||
return `${text.slice(0, prefixLength)}...${text.slice(-suffixLength)}`;
|
||||
}
|
||||
|
||||
function displayTearOffUrl(value) {
|
||||
const text = String(value || "").trim();
|
||||
if (text.length <= 48) return text;
|
||||
try {
|
||||
const parsed = new URL(text);
|
||||
const host = parsed.host.replace(/^www\./, "");
|
||||
const path = parsed.pathname || "/";
|
||||
const lookupKeys = ["addr", "naddr", "event", "id", "note", "nevent"];
|
||||
const lookupKey = lookupKeys.find((key) => parsed.searchParams.get(key));
|
||||
if (lookupKey) {
|
||||
return `${host}${path}?${lookupKey}=${truncateMiddle(parsed.searchParams.get(lookupKey), 12, 8)}`;
|
||||
}
|
||||
return truncateMiddle(`${host}${path}${parsed.search}${parsed.hash}`, 32, 10);
|
||||
} catch (_) {
|
||||
return truncateMiddle(text, 32, 10);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPreview(config) {
|
||||
const flyerLang = supportedLang(config.lang || FALLBACK_LANG);
|
||||
const flyerLocale = localeData(flyerLang);
|
||||
const tearOff = config.tear_off_link || config.url || "";
|
||||
const tearOffDisplay = displayTearOffUrl(tearOff);
|
||||
const contentQr = config.url || tearOff;
|
||||
const tearOffQrSvg = makeQrSvg(tearOff);
|
||||
const contentQrSvg = makeQrSvg(contentQr);
|
||||
const sheetClass = tearOff ? "container" : "container no-tear-offs";
|
||||
const sheetClass = `${tearOff ? "container" : "container no-tear-offs"} ls-${trackingPolicy(flyerLang)}`;
|
||||
const openPosterLabel = nostrLabel("open_poster", flyerLang);
|
||||
const shareReprintLabel = nostrLabel("share_reprint", flyerLang);
|
||||
const builtWithLabel = nostrLabel("built_with", flyerLang);
|
||||
const eventIdLabel = nostrLabel("event_id", flyerLang);
|
||||
const eventId = String(config.event_id || "").trim();
|
||||
const tearOffStatement = [openPosterLabel, shareReprintLabel]
|
||||
.map((part) => String(part || "").trim())
|
||||
.filter(Boolean)
|
||||
.map((part) => escapeHtml(part))
|
||||
.join("<br>");
|
||||
const eventIdValueHtml = eventId.length > 32
|
||||
? `${escapeHtml(eventId.slice(0, 32))}<br>${escapeHtml(eventId.slice(32))}`
|
||||
: escapeHtml(eventId);
|
||||
const eventIdHtml = eventId
|
||||
? `<div class="tear-off-event-id">${escapeHtml(eventIdLabel)}:<br><span class="tear-off-event-id-value">${eventIdValueHtml}</span></div>`
|
||||
: "";
|
||||
const tearOffHtml = Array.from({ length: 10 }).map(() => `
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
${escapeHtml(openPosterLabel)}<br>
|
||||
<a href="${escapeHtml(tearOff)}" title="${escapeHtml(tearOff)}">${escapeHtml(tearOffDisplay)}</a><br>
|
||||
${escapeHtml(shareReprintLabel)}
|
||||
<a href="${escapeHtml(tearOff)}" title="${escapeHtml(tearOff)}">${tearOffStatement}</a>
|
||||
${eventIdHtml}
|
||||
</div>
|
||||
<div class="qr-code" aria-label="QR code for ${escapeHtml(tearOff)}">${tearOffQrSvg}</div>
|
||||
</div>
|
||||
@@ -1747,15 +1780,16 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
const field = el(fieldId);
|
||||
if (!field) return;
|
||||
flyerSource = "custom";
|
||||
loadedEventId = "";
|
||||
renderFormPreviewForEditing();
|
||||
if (!fieldDisplayFits(fieldId)) {
|
||||
field.value = lastAcceptedFieldValues[fieldId] || "";
|
||||
renderFormPreviewForEditing();
|
||||
setFitStatus(`${nostrLabel("field_too_long", currentUiLang())} ${fieldLabel(fieldId)}`);
|
||||
setFieldError(fieldId, nostrLabel("field_too_long", currentUiLang()).replace(/[::]\s*$/, ""));
|
||||
return;
|
||||
}
|
||||
lastAcceptedFieldValues[fieldId] = field.value;
|
||||
setFitStatus("");
|
||||
setFieldError(fieldId, "");
|
||||
}
|
||||
|
||||
function formToPreview() {
|
||||
@@ -1840,9 +1874,10 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
const config = normalizePayload(payload);
|
||||
flyerSource = "event";
|
||||
loadedEventConfig = config;
|
||||
loadedEventId = (nostrEvent && nostrEvent.id) || "";
|
||||
fillEditorFromConfig(config);
|
||||
el("viewer-config-output").value = JSON.stringify(config, null, 2);
|
||||
renderPreview(config);
|
||||
renderPreview({ ...config, event_id: loadedEventId });
|
||||
statusElement.textContent = `${nostrLabel("fetched", config.lang)} ${parsed.type === "address" ? parsed.address.identifier : parsed.id}`;
|
||||
closeViewerDrawer();
|
||||
}
|
||||
@@ -1941,11 +1976,12 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
||||
if (!relays.length) throw new Error(nostrLabel("relay_required", currentUiLang()));
|
||||
el("publish-status").textContent = nostrLabel("signing_publishing", currentUiLang());
|
||||
const result = await signAndPublish(payload, relays);
|
||||
loadedEventId = result.event.id || "";
|
||||
setRealOutput("event-id-output", result.event.id);
|
||||
el("event-json-output").value = JSON.stringify(result.event, null, 2);
|
||||
el("viewer-event-id").value = result.naddr;
|
||||
el("publish-status").innerHTML = renderRelayResults(result.results);
|
||||
renderPreview(normalizePayload(result.payload));
|
||||
renderPreview({ ...normalizePayload(result.payload), event_id: loadedEventId });
|
||||
} catch (error) {
|
||||
el("publish-status").textContent = error.message;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user