From d273843bb12d45c7c47d155818aa9ea4bd493985 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Sat, 6 Jun 2026 14:26:40 -0400
Subject: [PATCH] Improve flyer editing and add demos
---
demo-flyers/data-center-protest.md | 18 +++
demo-flyers/presbyterian-gospel-tract.md | 18 +++
demo-flyers/yard-sale.md | 18 +++
site/nostr/index.html | 1 +
site/nostr/nostr-client.css | 7 +
site/nostr/nostr-client.js | 194 +++++++++++++++++++----
6 files changed, 227 insertions(+), 29 deletions(-)
create mode 100644 demo-flyers/data-center-protest.md
create mode 100644 demo-flyers/presbyterian-gospel-tract.md
create mode 100644 demo-flyers/yard-sale.md
diff --git a/demo-flyers/data-center-protest.md b/demo-flyers/data-center-protest.md
new file mode 100644
index 0000000..dd83093
--- /dev/null
+++ b/demo-flyers/data-center-protest.md
@@ -0,0 +1,18 @@
+# Data Center Construction Protest Demo Flyer
+
+Use these values in the VoxVera editor for a civic protest or public-comment demo.
+
+| Field | Value |
+| --- | --- |
+| Relays | `wss://relay.damus.io`, `wss://nos.lol`, `wss://relay.primal.net` |
+| Flyer Name | `data-center-protest` |
+| Language | `en` |
+| Page title | `Data Center Protest Flyer` |
+| Title | `PUBLIC HEARING` |
+| Subtitle | `NO BLANK CHECK FOR BIG TECH` |
+| Headline | `STOP THE DATA CENTER LAND GRAB` |
+| Content | `A proposed data center project could strain local power, water, roads, and public budgets while leaving residents with noise, traffic, and long-term infrastructure costs. Attend the public hearing, ask for full environmental review, and demand binding limits before any approval.` |
+| URL message | `Scan for meeting time, talking points, and public-comment instructions.` |
+| Content URL | `https://example.com/data-center-hearing` |
+| Footer message | `BRING NEIGHBORS - SPEAK DURING PUBLIC COMMENT` |
+
diff --git a/demo-flyers/presbyterian-gospel-tract.md b/demo-flyers/presbyterian-gospel-tract.md
new file mode 100644
index 0000000..e0c97e1
--- /dev/null
+++ b/demo-flyers/presbyterian-gospel-tract.md
@@ -0,0 +1,18 @@
+# Presbyterian Gospel Tract Demo Flyer
+
+Use these values in the VoxVera editor for a Reformed Presbyterian gospel tract demo.
+
+| Field | Value |
+| --- | --- |
+| Relays | `wss://relay.damus.io`, `wss://nos.lol`, `wss://relay.primal.net` |
+| Flyer Name | `presbyterian-gospel-tract` |
+| Language | `en` |
+| Page title | `Presbyterian Gospel Tract` |
+| Title | `CHRIST SAVES SINNERS` |
+| Subtitle | `REPENT AND BELIEVE THE GOSPEL` |
+| Headline | `THE GOOD NEWS OF JESUS CHRIST` |
+| Content | `God is holy, and all men have sinned against Him. Yet God sent His eternal Son, Jesus Christ, to take on true humanity, obey perfectly, die for sinners, and rise again. Salvation is by grace alone, through faith alone, in Christ alone. Turn from sin and trust the Lord Jesus Christ.` |
+| URL message | `Scan to read Scripture, a short catechism, and a local church invitation.` |
+| Content URL | `https://example.com/gospel` |
+| Footer message | `FOR BY GRACE ARE YE SAVED THROUGH FAITH - EPHESIANS 2:8` |
+
diff --git a/demo-flyers/yard-sale.md b/demo-flyers/yard-sale.md
new file mode 100644
index 0000000..b619148
--- /dev/null
+++ b/demo-flyers/yard-sale.md
@@ -0,0 +1,18 @@
+# Yard Sale Demo Flyer
+
+Use these values in the VoxVera editor as a practical neighborhood-sale demo.
+
+| Field | Value |
+| --- | --- |
+| Relays | `wss://relay.damus.io`, `wss://nos.lol`, `wss://relay.primal.net` |
+| Flyer Name | `yard-sale-saturday` |
+| Language | `en` |
+| Page title | `Saturday Yard Sale` |
+| Title | `YARD SALE` |
+| Subtitle | `SATURDAY 8 AM TO 2 PM` |
+| Headline | `TOOLS, BOOKS, TOYS, AND HOME GOODS` |
+| Content | `Stop by this Saturday for a family yard sale with useful household items, kids clothes, books, tools, small furniture, toys, and kitchen goods. Prices are marked to move. Cash is preferred, and early visitors are welcome after 8 AM.` |
+| URL message | `Scan for address, photos, and any weather updates.` |
+| Content URL | `https://example.com/yard-sale` |
+| Footer message | `SATURDAY ONLY - PLEASE PARK ALONG THE STREET` |
+
diff --git a/site/nostr/index.html b/site/nostr/index.html
index bdff6e8..86e896d 100644
--- a/site/nostr/index.html
+++ b/site/nostr/index.html
@@ -104,6 +104,7 @@
Footer message
+
diff --git a/site/nostr/nostr-client.css b/site/nostr/nostr-client.css
index 0d9900e..cb04a77 100644
--- a/site/nostr/nostr-client.css
+++ b/site/nostr/nostr-client.css
@@ -258,6 +258,13 @@ code {
color: var(--muted);
}
+.field-fit-status {
+ margin: 0 0 10px;
+ color: var(--warn);
+ font-size: 12px;
+ font-weight: 700;
+}
+
.preview-band {
margin-top: 26px;
border-top: 2px solid #000;
diff --git a/site/nostr/nostr-client.js b/site/nostr/nostr-client.js
index de2038a..afaee1e 100644
--- a/site/nostr/nostr-client.js
+++ b/site/nostr/nostr-client.js
@@ -99,6 +99,35 @@ Join us in a revolution that values truth and transparency. Together, we can bui
"naddr-output": "not_generated",
"poster-url-output": "not_generated"
};
+ const TEXT_FIELD_IDS = [
+ "field-folder-name",
+ "field-name",
+ "field-title",
+ "field-subtitle",
+ "field-headline",
+ "field-content",
+ "field-url-message",
+ "field-url",
+ "field-footer-message"
+ ];
+ const DISPLAY_FIT_TARGETS = {
+ "field-title": ".content h1:nth-of-type(1)",
+ "field-subtitle": ".distribute",
+ "field-headline": ".content h1:nth-of-type(2)",
+ "field-content": ".message",
+ "field-url-message": ".url-message",
+ "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: "رجوع",
@@ -254,7 +283,8 @@ Join us in a revolution that values truth and transparency. Together, we can bui
relay_required: "Enter at least one wss:// relay.",
failed: "failed",
timeout: "timeout",
- connection_error: "connection error"
+ connection_error: "connection error",
+ field_too_long: "That field is too long for the printable flyer:"
},
es: {
back: "Volver",
@@ -830,6 +860,8 @@ Join us in a revolution that values truth and transparency. Together, we can bui
}
};
let flyerSource = "default";
+ let loadedEventConfig = null;
+ const lastAcceptedFieldValues = {};
const el = (id) => document.getElementById(id);
@@ -886,6 +918,69 @@ 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;
+ const field = el(fieldId);
+ const label = field ? field.closest("label") : null;
+ const labelText = label ? label.querySelector("span") : null;
+ return labelText ? labelText.textContent.trim() : fieldId;
+ }
+
+ function setFitStatus(message) {
+ const status = el("field-fit-status");
+ if (status) status.textContent = message || "";
+ }
+
+ function syncAcceptedFieldValues() {
+ TEXT_FIELD_IDS.forEach((fieldId) => {
+ const field = el(fieldId);
+ if (field) lastAcceptedFieldValues[fieldId] = field.value;
+ });
+ }
+
+ function fillEditorFromConfig(config) {
+ const normalized = config && config.type === "voxvera_flyer" ? normalizePayload(config) : {
+ ...CONFIG_DEFAULTS,
+ ...config,
+ folder_name: slugify(config && config.folder_name),
+ lang: supportedLang((config && config.lang) || FALLBACK_LANG),
+ content: htmlBreaksToNewlines((config && config.content) || CONFIG_DEFAULTS.content)
+ };
+ el("field-lang").value = normalized.lang;
+ el("field-folder-name").value = normalized.folder_name;
+ el("field-name").value = normalized.name;
+ el("field-title").value = normalized.title;
+ el("field-subtitle").value = normalized.subtitle;
+ el("field-headline").value = normalized.headline;
+ el("field-content").value = normalized.content;
+ el("field-url-message").value = normalized.url_message;
+ el("field-url").value = normalized.url;
+ el("field-footer-message").value = normalized.footer_message;
+ syncUiLanguage(normalized.lang);
+ syncAcceptedFieldValues();
+ }
+
+ function printableTitle() {
+ const source = (el("field-name") && el("field-name").value) || CONFIG_DEFAULTS.name;
+ return String(source || CONFIG_DEFAULTS.name)
+ .trim()
+ .replace(/\s+/g, " ")
+ .slice(0, FIELD_LIMITS.name) || CONFIG_DEFAULTS.name;
+ }
+
+ function printWithPageTitle() {
+ const previousTitle = document.title;
+ document.title = printableTitle();
+ const restoreTitle = () => {
+ document.title = nostrLabel("client_title", currentUiLang()) || previousTitle;
+ window.removeEventListener("focus", restoreTitle);
+ };
+ window.addEventListener("focus", restoreTitle, { once: true });
+ window.print();
+ window.setTimeout(restoreTitle, 1500);
+ }
+
function localeDefaults(lang) {
const data = localeData(lang);
const landing = data.landing || {};
@@ -1023,6 +1118,8 @@ Join us in a revolution that values truth and transparency. Together, we can bui
el("field-url").value = defaults.url;
el("field-footer-message").value = defaults.footer_message;
syncUiLanguage(defaults.lang);
+ syncAcceptedFieldValues();
+ setFitStatus("");
}
function applyLanguageChange(lang) {
@@ -1154,9 +1251,9 @@ Join us in a revolution that values truth and transparency. Together, we can bui
.replace(/\r?\n/g, " ");
}
- function buildPayloadFromForm() {
+ function rawPayloadFromForm() {
const lang = supportedLang(el("field-lang").value || FALLBACK_LANG);
- const payload = {
+ return {
type: "voxvera_flyer",
version: 1,
folder_name: slugify(el("field-folder-name").value),
@@ -1173,6 +1270,10 @@ Join us in a revolution that values truth and transparency. Together, we can bui
attachment_filename: "",
qr_target: "flyer_url"
};
+ }
+
+ function buildPayloadFromForm() {
+ const payload = rawPayloadFromForm();
validatePayload(payload);
return payload;
}
@@ -1571,28 +1672,72 @@ Join us in a revolution that values truth and transparency. Together, we can bui