nextcloud-libraries / nextcloud-libraries/nextcloud-vue
[RFC] Support ||spoiler|| syntax in NcRichText
Nobody has claimed this yet.
- Dominant language
- Vue
- Stars
- 246
- Forks
- 99
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 103
Description
Summary
NcRichText renders user-generated markdown across Nextcloud apps (e.g. Talk). Spoilers are a nice addition in chat/comment contexts.
RFC to add support for inline spoilers in NcRichText using the Discord-style and Telegram-style syntax. Spoiler content renders hidden behind a bar and is revealed on click - before that .
Discord (p.s. images are out of scope of this RFC)
Telegram
Syntax
Markdown has no standard spoiler syntax (see commonmark/commonmark-spec/issues/570)
Suggested convention: ||…|| (double pipes, inline).
This is a ||hidden|| word.
Behavior
- Gating: extended markdown only (useExtendedMarkdown) — spoilers are an extension, alongside GFM.
- Reveal: click to reveal. State is per-instance and one-way (once revealed, stays revealed).
- Accessibility: to be discussed - if revealed on click, maybe pass to readers as-is, or wrap with "spoiler alert" - keyboard a11y should be respected + aria-expanded + descriptive aria-label.
Proposed implementation
Follows the existing custom-plugin pattern in emarkAutolink, remarkPlaceholder,remarkUnescape, remarkStripCode).
- remarkSpoiler.ts — remark transformer that visits text nodes, splits on ||…|| (handles multiple matches per node), and emits a dedicated spoiler element node carrying the inner text.
- Pipeline registration in NcRichText.vue → renderMarkdown(), gated on useExtendedMarkdown, placed after remarkGfm (verified: GFM leaves ||…|| untouched in normal text, so ordering is safe — see Limitations for the table exception).
- NcTextSpoiler.vue — small <script setup> component (sibling of NcRichTextExternalLink.vue) wrapping a with internal revealed ref; hidden state styled via CSS module. (animated or not?)
- createElement mapping in NcRichText.vue — branch rendering the spoiler node to NcRichTextSpoiler.
- unit tests in remarkSpoiler.spec.js, mirroring autoLink.spec.js, playwrigth tests with kbd a11y and clicks
To discuss - if we make it client-side only, or with server involvement (pass spoiler text with richObjectParameters)
Limitations (v1)
- No nested inline formatting inside a spoiler: ||bold|| renders the ** literally. Formatting around a spoiler (||x||) works. This matches the library's behavior too; full nesting would require a micromark syntax extension. Should be documented.
- No spoilers inside GFM table cells: GFM's table tokenizer consumes | as cell delimiters before any transformer runs. Matches Discord/GFM behavior — escape as | in tables. Should be documented.
Alternative: existing library
remark-inline-spoiler implements exactly this syntax via a proper micromark tokenizer (micromark-extension-inline-spoiler + mdast-util-inline-spoiler), but:
- it is new and has small amount of downloads (although maintained)
- it also does not support nested markdown
- it adds a 3-package dependency tree, where we can do it with a small helper as we already have
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing custom remark plugins and renderMarkdown() in NcRichText.vue, then review the proposed remarkSpoiler.ts and NcTextSpoiler.vue boundaries. Use remarkSpoiler.spec.js and the planned Playwright keyboard-accessibility and click tests to define done, including extended-markdown gating, reveal behavior, and documented limitations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100