Preserve line breaks in question rich text (fixes #372) - #373
Merged
Famousmaster206 merged 1 commit intoAug 18, 2026
Merged
Conversation
Line breaks typed into the Explanation box disappeared. Two separate causes, both from the rich-text question editor: EditorJS stripped them on save. QuestionsAddCard declared no `sanitize` config, so EditorJS sanitized the saved block data with the merged tag list of the enabled inline tools, which contains no `<br>` or `<div>`. Saving an article rewrote First.<div><br></div><div>Second.</div> as `First.Second.`. Declaring `sanitize` opts the block out; question rich text is already sanitized by `sanitizeQuestionRichText` with an explicit allow-list, on write and again at render. The editor collapsed legacy newlines. The box is a `contentEditable`, so `white-space: normal` applied, but questions written before it became rich text store their breaks as newline characters. HTML collapsed each one to a space, so opening an older explanation showed its paragraphs already merged even though the stored data and the learner view were both fine. `whitespace-pre-wrap` matches the learner renderer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #372 — line breaks typed into the Explanation box were removed after saving.
This turned out to be two independent bugs, both introduced by the rich-text question editor in #362. Either one alone produces the reported symptom, so both need fixing.
1. EditorJS deleted the line breaks on save
QuestionsAddCarddeclared nosanitizeconfig. A block tool without one inherits the merged tag list of the enabled inline tools, and EditorJS applies it to every string in the saved block data. That list contains no<br>and no<div>, soeditor.save()silently rewrote every question and explanation:This is the "text entered on separate lines is combined into one paragraph" half of the report. It only affects the article creator — the admin test page (
/admin/subject/.../test/[id]) writes straight to Firestore and never goes through EditorJS, which is why the bug looked inconsistent.Fixed by declaring a
sanitizeconfig.truemeans "leave this value as-is" in EditorJS's sanitizer. That is safe because question rich text is already sanitized bysanitizeQuestionRichText(DOMPurify with an explicit allow-list, no attributes) — once when the editor writes it, and again inRenderContentat render time. The stricter, purpose-built sanitizer stays in charge; we just stop EditorJS from applying a different vocabulary on top of it.2. The editor collapsed newlines in existing questions
The Explanation box is now a
contentEditable, which iswhite-space: normal. Questions written before it became rich text store their breaks as plain newline characters — 910 of 1048 explanations currently in production. HTML collapses each\nto a single space, so opening any older question showed its paragraphs already merged, before saving anything.This is the "a space is inserted where the line break previously existed" half of the report, and it explains why re-adding the breaks and re-saving never helped: the author was editing a view that had already lost them. The stored data and the learner-facing view were both correct the whole time — only the editor was wrong.
Fixed by adding
whitespace-pre-wrapto the editor, matching thewhitespace-pre-wrapthe learner renderer (RenderContent) already uses. The two views now agree, which is also the last acceptance criterion on the issue.Related
One thing a reviewer should know
Cause 1 means EditorJS is no longer stripping attributes from question data, so the
data-emptymarkers EditorJS stamps onto elements now persist into stored content. They are inert —renderNodeswitches on tag name and ignores attributes entirely, and DOMPurify still strips every non-data-*attribute at render — but they are noise.I tried removing them with
ALLOW_DATA_ATTR: falseand reverted it: EditorJS re-stampsdata-emptycontinuously, so the sanitized HTML then differs from the live DOM on every keystroke, causingemitChangeto rewriteinnerHTMLand reset the caret. Typing came out reversed (.hpargarap dnoceS). A proper fix needs normalized comparison inemitChangeand the value effect, which is out of scope here and worth its own issue.Pull request type
Two files, 29 insertions, 2 deletions. No API, data-format, or schema changes, and no migration needed — existing content in both the old plain-text form and the new rich-text form renders correctly.
Demo
The bug is about exact saved values, so the clearest demo is the string that reaches storage. Typing
First paragraph./ Enter / Enter /Second paragraph.into an Explanation box, then saving:Before
After
Opening an existing question whose explanation is stored as
"First paragraph.\n\nSecond paragraph.":How Has This Been Tested? How can the reviewer test it?
How it was tested. I drove the real components in a headless browser through the actual EditorJS save path (not a stand-in), using temporary harness pages that mounted
QuestionsAddCardin a real EditorJS instance plusRenderContentfor the learner view. The harness pages were deleted before committing; only the two source files are in this PR.The acceptance checks, taken from the issue's criteria, score 6/6 with this change and 1/6 without it. On unfixed code the failure output is exactly the reported symptom — typing four lines and saving yields
"Alpha.Beta.Gamma.Delta.". Checks covered:I also confirmed against production data that explanations really are stored in two different shapes (910 of 1048 with plain
\n), which is what motivated fix 2.npm run lintreports no new warnings, andnpm run buildcompiles successfully.How to test it yourself.
The regression that shipped (cause 1):
First paragraph., press Enter twice, typeSecond paragraph.mainthe two paragraphs come back merged into one; with this branch the blank line is still there.The existing-content half (cause 2):
mainthe paragraphs are run together with a space; with this branch they are on separate lines, matching what the learner already sees.Worth confirming no regressions: bold / italic / underline / highlight still apply and toggle, pasting still works, LaTeX (
$@...$) and```code blocks still render, and the admin test page editor (/admin/subject/.../test/[id]) behaves as before — it was never affected by cause 1.Checklist