diff --git a/src/components/article-creator/custom_questions/QuestionsAddCard.tsx b/src/components/article-creator/custom_questions/QuestionsAddCard.tsx
index 0b49fefc..f71cf288 100644
--- a/src/components/article-creator/custom_questions/QuestionsAddCard.tsx
+++ b/src/components/article-creator/custom_questions/QuestionsAddCard.tsx
@@ -26,7 +26,27 @@ export class QuestionsAddCard implements BlockToolConstructable {
static get toolbox() {
return {
title: "Questions",
- icon: "?",
+ icon: "?",
+ };
+ }
+
+ /**
+ * Opt this block's data out of EditorJS's own sanitizer.
+ *
+ * A block tool without a `sanitize` config inherits the merged tag list of the
+ * enabled inline tools, and EditorJS applies it to every string in the saved
+ * data. That list has no `
` or `
`, so saving an article silently
+ * stripped the line breaks out of question/explanation rich text (#372).
+ *
+ * `true` means "leave this value as-is" in EditorJS's sanitizer. That is safe
+ * here because question rich text is sanitized by `sanitizeQuestionRichText`
+ * with an explicit allow-list — on write in the editor and again at render
+ * time — rather than relying on EditorJS.
+ */
+ static get sanitize() {
+ return {
+ instanceId: true,
+ questions: true,
};
}
diff --git a/src/components/article-creator/custom_questions/RichTextEditor.tsx b/src/components/article-creator/custom_questions/RichTextEditor.tsx
index 4fd4b599..0417ea64 100644
--- a/src/components/article-creator/custom_questions/RichTextEditor.tsx
+++ b/src/components/article-creator/custom_questions/RichTextEditor.tsx
@@ -231,6 +231,13 @@ const RichTextEditor = forwardRef
(function RichTextEditor
return (
+ {/*
+ `whitespace-pre-wrap` matches the learner renderer. Questions written
+ before this box became rich text store their line breaks as newline
+ characters, and HTML whitespace collapsing rendered each of those as
+ a single space, so opening an older explanation looked like its
+ paragraphs had been merged together (#372).
+ */}
(function RichTextEditor
document.execCommand("insertHTML", false, sanitizeQuestionRichText(html || text));
emitChange();
}}
- className="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&:empty]:before:pointer-events-none [&:empty]:before:text-muted-foreground [&:empty]:before:content-[attr(data-placeholder)] [&_mark]:rounded [&_mark]:bg-yellow-200 [&_mark]:px-0.5 [&_mark]:text-gray-950"
+ className="min-h-24 w-full whitespace-pre-wrap rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&:empty]:before:pointer-events-none [&:empty]:before:text-muted-foreground [&:empty]:before:content-[attr(data-placeholder)] [&_mark]:rounded [&_mark]:bg-yellow-200 [&_mark]:px-0.5 [&_mark]:text-gray-950"
/>
{toolbar && (