the editor reads back the alignment it wrote
Editing one paragraph silently un-aligned the others. A right-aligned line lost its alignment because somebody justified something else — which reads as the alignment buttons applying to the whole document rather than the selection, and is not that at all. THE ROUND TRIP WAS LOSSY IN ONE DIRECTION. The editor emits `style="text-align: right"`; the site's sanitiser refuses style attributes — one is a way to position something over the rest of the page — and converts alignment to `class="rt-right"`. That half always worked. Coming back out, tiptap parses styles and knows nothing about those classes. So a document loaded from the database arrived with every alignment already gone, and the next save wrote the loss back. Whatever you set in the current session survived; everything set before it did not. Converting classes to styles as the editor is seeded makes the two ends symmetric, and the conversion is the sanitiser's own table read backwards. The comparison in the re-seed effect had to move into the editor's dialect too, or every load looks like a change: the stored html and getHTML() never match. `rt-font-*` is not converted, and that is a known gap rather than an oversight — this editor has no font control, so a stored font class has nothing to become and would be dropped either way. It wants a font button before it wants a parser. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-2
@@ -179,6 +179,32 @@ const Toolbar = ({ editor }: { editor: Editor }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Stored alignment classes, back into the inline styles tiptap understands.
|
||||
*
|
||||
* ── The round trip was LOSSY, and this is the half that was missing ──
|
||||
*
|
||||
* The editor emits `style="text-align: right"`. The site's sanitiser refuses style attributes — one is
|
||||
* a way to position something over the rest of the page — and converts alignment to `class="rt-right"`
|
||||
* on the way in. That half has always worked.
|
||||
*
|
||||
* Coming back out, tiptap parses styles and knows nothing about those classes. So a document loaded
|
||||
* from the database arrived with every alignment gone, and the next save wrote that loss back. Editing
|
||||
* one paragraph silently un-aligned the others: a right-aligned line lost its alignment because
|
||||
* somebody justified something else.
|
||||
*
|
||||
* Converting here makes the two ends symmetric — class in, style out, style in, class out — and the
|
||||
* conversion is the sanitiser's own table read backwards.
|
||||
*
|
||||
* `rt-font-*` is NOT converted, and that is a known gap rather than an oversight: this editor has no
|
||||
* font control, so a stored font class has nothing to become and would be dropped either way. It wants
|
||||
* a font button before it wants a parser.
|
||||
*/
|
||||
const ALIGNMENT_CLASS = /class="rt-(left|center|right|justify)"/g;
|
||||
|
||||
const toEditorHtml = (html: string): string =>
|
||||
html.replace(ALIGNMENT_CLASS, (_whole, align: string) => `style="text-align: ${align}"`);
|
||||
|
||||
export const RichText = ({
|
||||
value,
|
||||
onChange,
|
||||
@@ -196,7 +222,7 @@ export const RichText = ({
|
||||
// clicking a link to edit its text opens it and loses whatever was unsaved.
|
||||
Link.configure({ openOnClick: false }),
|
||||
],
|
||||
content: value,
|
||||
content: toEditorHtml(value),
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class:
|
||||
@@ -216,7 +242,11 @@ export const RichText = ({
|
||||
jumps to the end on every keystroke.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (editor && value !== editor.getHTML()) editor.commands.setContent(value, { emitUpdate: false });
|
||||
if (!editor) return;
|
||||
// Compared in the EDITOR's dialect, or every load looks like a change: the stored html says
|
||||
// `class="rt-right"` and getHTML() says `style="text-align: right"`, which never match.
|
||||
const incoming = toEditorHtml(value);
|
||||
if (incoming !== editor.getHTML()) editor.commands.setContent(incoming, { emitUpdate: false });
|
||||
}, [editor, value]);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
Reference in New Issue
Block a user