Skip to content

[RFC] Support ||spoiler|| syntax in NcRichText #8784

Description

@Antreesy

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)
Image

Telegram
Image

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).

  1. 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.
  2. 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).
  3. 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?)
  4. createElement mapping in NcRichText.vue — branch rendering the spoiler node to NcRichTextSpoiler.
  5. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. to triagedesignDesign, UX, interface and interaction designenhancementNew feature or requestfeature: richtextRelated to the richtext component

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions