Skip to content

FEATURE: Support all bbcode tags in the rich editor - #107

Open
renato wants to merge 7 commits into
mainfrom
rich-editor-extension
Open

FEATURE: Support all bbcode tags in the rich editor#107
renato wants to merge 7 commits into
mainfrom
rich-editor-extension

Conversation

@renato

@renato renato commented Aug 3, 2026

Copy link
Copy Markdown

Previously, opening a post that used any of this plugin's bbcode tags ([color], [size], [left], [list=a], etc.) in the rich text editor failed with "The rich text editor doesn't support all features used in this post" and forced the markdown editor.

This change adds a rich editor extension that parses and serializes every tag the plugin supports — inline styling as marks, alignment/indent/[ot]/[edit] as block nodes, and typed [list=X] lists — so bbcode posts open in the rich editor and keep cooking to the same output.

Each list item's content is now wrapped in hidden paragraph tokens, which render as nothing but let prosemirror-markdown's own list specs parse the item. Cooked sepquotes carry a data-tag, so [ot] and [edit] are told apart structurally instead of by their localized label.

Two things to note:

  • The source is normalized on a round trip: tags are lowercased, [li] items become [*], untyped [list]/[ul]/[ol] become markdown lists, a same-type tag nested in one with the same value drops out, and attribute quoting is canonicalized. Editing a bbcode post in the rich editor rewrites its source accordingly.
  • Content the editor can't represent exactly is declined instead, leaving the post to the markdown editor with its source intact: tag values outside the charsets the cook sanitizer allows, and a same-type tag nested in one with a differing value, since a mark set holds one mark per type and nested [size] percentages compound when cooked.

@ZogStriP ZogStriP left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅

Opening a post that used any of this plugin's tags ([color], [size],
[left], [list=a], etc.) in the rich text editor failed with "The rich
text editor doesn't support all features used in this post" and forced
the markdown editor.

Add a rich editor extension covering every tag the plugin supports:
inline styling as marks, alignment, [indent], [ot] and [edit] as block
nodes, and typed [list=X] lists. A tag value is only accepted when it
matches the charsets the cook sanitizer allows, so the editor can't show
styling the rendered post drops, and a value can't carry extra style
declarations into the editor's DOM.

Content the editor can't represent exactly is declined rather than
rewritten, leaving the post to the markdown editor with its source
intact: values outside those charsets, and a same-type tag nested in one
with a differing value, since a mark set holds one mark per type and
nested [size] percentages compound when cooked.

Cooking changes with it, without changing what a post renders as. Cooked
sepquotes carry a data-tag, so [ot] and [edit] are told apart
structurally instead of by their localized label, which no longer works
once a post is read under another locale. Typed lists get their own token
type and every item's content is wrapped in hidden paragraph tokens,
which render as nothing but let prosemirror-markdown's own list specs
parse them.
@renato
renato force-pushed the rich-editor-extension branch from 7f0de84 to 407dff7 Compare August 5, 2026 18:13
renato added 4 commits August 6, 2026 12:38
Track whether an inline bbcode open token was ours, so its close ends
the mark we opened rather than whichever one happened to be on top. The
block wraps already did this; the inline path popped unconditionally.

Build the cook sanitizer's allowlist and the editor's value checks from
one set of charsets, so the two can't drift. The shared module sits
under discourse-markdown/ because only that path is loaded into the
server-side cooking context.

Guard the parse state reads that assumed a node was open, and say in a
comment why declining a token fails the parse.

Assert that a declined post leaves the editor empty rather than that it
renders no span, which a silently dropped tag would also satisfy, and
drop the round trip cases that restate an exact round trip already
asserted in the extension test.
A value needing quotes that contains every supported delimiter leaves
the serializer no pair to wrap it in, and its fallback strips the double
quotes. Round trip the value through the serializer and decline it when
it doesn't survive, rather than duplicating the delimiter list here.

The fidelity tests compared the cooked output of the editor's value
against the cooked source, which a declined parse satisfies on its own:
the editor is left empty and the value keeps the original markdown.
Assert the editor rendered something first.
A substring check on the written tag accepts a value whose own trailing
quote was stripped, because the wrapper's closing quote lands in the
same place and makes the tag look like it still holds the value.

Parse the written tag back with core's own parser and require the value
to match, so both halves of the round trip are the canonical ones. The
regression case puts the quote at the boundary, where the substring
check passed.
@renato renato closed this Aug 7, 2026
@renato renato reopened this Aug 7, 2026
renato added 2 commits August 7, 2026 16:15
An item in a typed list reused the generic list item node, whose content
allows nested lists and extra paragraphs. The cook reads a line per
item, so saving such a structure emitted nested [list] tags that render
as literal text, and extra paragraphs that collapse into line breaks.
Give typed lists their own single-paragraph item node, emitted as its
own token type from the same li tag, so the cooked HTML is unchanged and
the editor can't build what the cook can't represent. Pasted content
that doesn't fit is reshaped to the closest structure that does.

A pasted bare generic font family (monospace, serif, system-ui) or CSS
keyword was accepted as a font value, but cooked values are always
quoted, which turns a generic into a literal font name and drops the
formatting. Decline bare generics and keywords; a quoted family stays a
literal name, exactly what a cooked post carries.
Drop the ones restating what the code or a test name already says, and
compress the mechanism notes that grew while the change was reviewed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants