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 && (