From 3dbebfbc9345d2603908f32c0dabebc0ff21feb3 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Sun, 14 Dec 2025 09:24:00 +0200 Subject: Add community text submissions, and make custom text usable for study Submissions - POST /api/texts proposes a passage; nothing reaches players until a moderator approves it. GET /api/texts serves the approved set, which the client merges on top of its bundled packs at startup. - Validation the server enforces rather than trusts: category from a fixed list, 40 to 600 characters, no control characters (a newline makes a passage untypeable in a single-line input), attribution length, and a unique index on md5(lower(btrim(content))) so the same passage cannot be submitted twice under different whitespace or casing. - Moderation is a flag on users. The queue and the review endpoint both refuse a non-moderator, and reviewing an already-reviewed submission is a 404 rather than a silent second write. - Submissions are rate limited per user: enough for a real contributor, not enough to fill the queue from a script. - A Contribute screen carries the form, your own submissions with their status, and - for moderators only - the review queue. This is the half of TypeRacer's model the packs could not reach by authoring: their corpus is roughly twelve thousand passages, grown by submission. Custom text as a study tool - Imported documents are kept between visits, with how far through each one you are. Custom text lived only in memory, so importing a set of notes and reloading the page lost them - fine for pasting a paragraph to race, useless for working through a file over several sittings. - Position is recorded when a segment is finished, not when the next is started, so closing the tab after a segment does not lose it. - Markdown is chunked as markdown: fenced code blocks are kept whole and typed line by line, and the decoration - hashes, asterisks, backticks, link brackets, table pipes - is stripped so what you retype is the material rather than the punctuation around it. - Everything stays on the device. Notes are not uploaded anywhere. Fixed while doing it: a chunk could contain a newline, which cannot be typed in a single-line input at all. Any paragraph with a line break inside it -- ordinary in notes and in wrapped prose - produced an unfinishable segment. Whitespace inside a chunk is now flattened. --- web/src/siteFooter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'web/src/siteFooter.js') diff --git a/web/src/siteFooter.js b/web/src/siteFooter.js index 1d5139e..eef193b 100644 --- a/web/src/siteFooter.js +++ b/web/src/siteFooter.js @@ -39,7 +39,7 @@ async function share(result) { /// `result` is optional: with one, Share offers the score just achieved; /// without, it offers the site. -export function renderSiteFooter(root, { result, onShowPrivacy } = {}) { +export function renderSiteFooter(root, { result, onShowPrivacy, onShowContribute } = {}) { const wrap = document.createElement('div'); wrap.className = 'site-footer'; wrap.innerHTML = ` @@ -47,6 +47,8 @@ export function renderSiteFooter(root, { result, onShowPrivacy } = {}) { · GitHub · + + · · v0.1.0 @@ -61,6 +63,7 @@ export function renderSiteFooter(root, { result, onShowPrivacy } = {}) { setTimeout(() => { shareBtn.textContent = 'Share'; }, 1800); }); wrap.querySelector('[data-action="privacy"]').addEventListener('click', () => onShowPrivacy?.()); + wrap.querySelector('[data-action="contribute"]').addEventListener('click', () => onShowContribute?.()); return () => wrap.remove(); } -- cgit v1.2.3