diff options
| author | srdusr <[email protected]> | 2025-12-14 09:24:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-12-14 09:24:00 +0200 |
| commit | 3dbebfbc9345d2603908f32c0dabebc0ff21feb3 (patch) | |
| tree | 61a4f60187046a087424845eb8e22239ccd7d6ee /web/src/styles.css | |
| parent | 8fc4440150ce84c9cc3f5ec13d50beb58a5b65bc (diff) | |
| download | typerpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.tar.gz typerpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.zip | |
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.
Diffstat (limited to 'web/src/styles.css')
| -rw-r--r-- | web/src/styles.css | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/web/src/styles.css b/web/src/styles.css index 6a04db2..bb92393 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -2920,3 +2920,132 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar { color: var(--primary-color); border-color: var(--primary-color); } + +/* Community submissions: the form, your own history, and the review queue. */ +.contribute-form { width: 100%; max-width: 34rem; } + +.contribute-count { + align-self: flex-end; + font-size: 0.7rem; + color: var(--sub-color); + font-family: 'JetBrains Mono', monospace; +} + +.contribute-len.bad { color: var(--error-color); } + +.submission-list { + display: flex; + flex-direction: column; + gap: 0.6rem; + width: 100%; + max-width: 40rem; +} + +.submission-row { + border: 1px solid var(--neutral-color); + border-radius: 8px; + padding: 0.75rem 0.9rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + text-align: left; +} + +.submission-head { + display: flex; + align-items: center; + gap: 0.6rem; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--sub-color); +} + +.submission-status { margin-left: auto; } +.submission-status.status-approved { color: var(--primary-color); } +.submission-status.status-rejected { color: var(--error-color); } +.submission-status.status-pending { color: var(--warning-color); } + +.submission-content { + font-family: 'JetBrains Mono', monospace; + font-size: 0.85rem; + color: var(--text-color); + line-height: 1.5; +} + +.submission-attribution, +.submission-reason { + font-size: 0.75rem; + color: var(--sub-color); +} + +.submission-reason { color: var(--error-color); } + +.submission-actions { display: flex; gap: 0.5rem; } + +/* Imported documents, kept between visits. Notes are worked through over + several sittings, so the list shows how far into each one you are. */ +.custom-library { + display: flex; + flex-direction: column; + gap: 0.35rem; + width: 100%; + margin-bottom: 0.5rem; +} + +.custom-doc { + display: flex; + align-items: stretch; + gap: 0.35rem; +} + +.custom-doc-open { + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.15rem; + background: none; + border: 1px solid var(--neutral-color); + border-radius: 6px; + padding: 0.5rem 0.7rem; + color: var(--text-color); + font-family: inherit; + cursor: pointer; + text-align: left; + min-width: 0; +} + +.custom-doc-open:hover { + border-color: var(--primary-color); +} + +.custom-doc-name { + font-size: 0.85rem; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.custom-doc-progress { + font-size: 0.7rem; + color: var(--sub-color); + font-family: 'JetBrains Mono', monospace; +} + +.custom-doc-remove { + flex: 0 0 auto; + width: 2rem; + background: none; + border: 1px solid transparent; + border-radius: 6px; + color: var(--sub-color); + font-size: 1.1rem; + cursor: pointer; +} + +.custom-doc-remove:hover { + color: var(--error-color); + border-color: color-mix(in srgb, var(--error-color) 45%, transparent); +} |