dotCMS / dotCMS/core

Block Editor 2.0: Highlight mark not registered — Story Block field renders blank for content containing highlight marks (parity regression with legacy editor)

Open
#37,145 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS: Block Editor OKR : Customer Support Team : Maintenance
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Problem Statement

The new TipTap-v3 Block Editor (core-web/libs/new-block-editor) does not register the Highlight mark. Any content carrying highlight marks — authored while the legacy editor was active — cannot be deserialized by the new schema, and the entire field renders blank.

Legacy editor New editor
Registers Highlight dot-block-editor.component.ts:35,737 ❌ absent from editor-extensions.ts:72-178
Renders affected content ✅ correctly ❌ blank field
Why the whole field disappears

TipTap deserializes the document as a single atomic operation, so one unknown mark aborts Node.fromJSON for the entire doc. With enableContentCheck at its default (off), TipTap logs a warning and falls back to an empty document:

[tiptap warn]: Invalid content. Passed value: {type: 'doc', attrs: {…}, content: Array(19)}
Error: RangeError: There is no mark type highlight in this schema

All 19 blocks are lost in the editor — the stored JSON is intact, the editor simply cannot open it.

The underlying defect: marks bypass sanitisation

The new editor already guards against unknown nodespreserveUnknownNodesInDocument swaps them for the dotUnsupportedBlock placeholder. But getKnownNodeNames reads only editor.schema.nodes:

// core-web/libs/new-block-editor/src/lib/editor/editor.component.ts:128
function getKnownNodeNames(editor: Editor): Set<string> {
    return new Set(Object.keys(editor.schema.nodes));   // ← nodes only; marks skip this entirely
}

Registering Highlight fixes this instance. The unsanitised mark path is a broader hardening concern and is out of scope for this issue — recorded here only so the observation is not lost.

[!NOTE]
Where the marks come from. The legacy editor never exposed a Highlight button — there is no toggleHighlight anywhere in the codebase. It applies the mark as a temporary selection indicator in its link popover, so the author can see which text is being linked once focus moves to the search input:

// libs/block-editor/.../dot-link-editor-popover.component.ts:372
private focusSearchInput() {
    this.editor().commands.setHighlight();   // paints the selection
    this.searchInput?.nativeElement.focus();
}

When unsetHighlight() never runs — popover closed unexpectedly, or the contentlet saved while it was open — the mark is persisted. The reporting customer's JSON shows exactly this: a highlight run sitting immediately beside a link run. So these marks are UI residue, not authorial intent, and the legacy editor is still producing them today. The new editor solved the same UI problem with ProseMirror Decorations (view-only, never serialized) in SelectionPreserveExtension, which is why it has no need for the mark itself.

Same class of defect as #36572 (Underline mark, shipped in 26.07.17-01). That fix was scoped to Underline only, which is why Highlight is still unregistered.

Second defect found while testing: array-shaped values blank the field too

Opening the same contentlet through the UVE side panel blanks the field with a different error:

[tiptap warn]: Invalid content. Passed value: {0: {…}, 1: {…}, … 15: {…}, content: undefined}
Error: RangeError: Unknown node type: undefined

Note the shape of the value: numeric keys plus content: undefined. That is an array spread into an object. A Block Editor value reaches setContent either as a { type: 'doc', content: [...] } document or as a bare array of nodes — and the new editor's helper spreads unconditionally:

// core-web/libs/new-block-editor/src/lib/editor/editor.component.ts
function preserveUnknownNodesInDocument(parsed, knownNodeNames) {
    return {
        ...parsed,                                              // array → { 0: node, 1: node, … }
        content: preserveUnknownBlockNodes(parsed.content, ...)  // array.content → undefined
    };
}

Spreading an array drops the document type, so TipTap throws Unknown node type: undefined. The legacy editor branches on the shape before preserving:

// libs/block-editor/.../dot-block-editor.component.ts:773
const preservedContent = Array.isArray(content)
    ? preserveUnknownBlockNodes(content, this.#knownEditorNodeNames)
    : { ...content, content: preserveUnknownBlockNodes(content.content, ...) };

The new editor lost that Array.isArray branch.

This is independent of Highlight — it is deterministic for any array-shaped value, whatever marks the content carries. It is also independent of the mark fix above: the two defects share only the symptom (blank field).

Steps to Reproduce
  1. Start dotCMS ≥ 26.07.04-01 (no flag overrides).
  2. Create or update a contentlet so its Block Editor field contains a highlight mark:
    { "type": "paragraph", "content": [
        { "type": "text", "marks": [{ "type": "highlight" }], "text": "Good Credit History:" }
    ]}
    
  3. Confirm the mark persisted: GET /api/v1/content/{identifier}
  4. Open the contentlet in the editor → the field renders blank, console shows RangeError: There is no mark type highlight in this schema.
  5. Compare against the legacy editor (DOT_FEATURE_FLAG_NEW_BLOCK_EDITOR=false) → the same contentlet renders correctly, highlight applied.
Acceptance Criteria
Expected Behavior
  • The new Block Editor registers @tiptap/extension-highlight (already a dependency at core-web/package.json:107), at parity with the legacy editor.
  • Existing content containing highlight marks round-trips through the new editor — marks are not stripped and the field is not emptied.
  • Every mark registered by the legacy editor is audited for parity, not just Highlight.
  • A Block Editor value passed as a bare array of nodes (as the UVE side panel does) loads correctly instead of being spread into an object and rejected as Unknown node type: undefined.
Actual Behavior
  • The whole Block Editor field renders blank; only a console warning indicates why.

[!WARNING]
Risk of data loss. The field loads via setContent(..., { emitUpdate: false }), so merely opening the contentlet does not dirty it — but the editor now holds an empty document. Any subsequent edit fires onUpdate, and a Save persists the empty body over the real one.

dotCMS Version

26.08.03-01

Severity

High - Major functionality broken

Links
  • Related: #36572 — same defect for the Underline mark

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with core-web/libs/new-block-editor/src/lib/editor/extensions/editor-extensions.ts and editor.component.ts, then compare the legacy editor references cited in the issue. Reproduce both highlight-mark and bare-array inputs, and inspect the existing Block Editor coverage. Done means legacy marks are audited for parity, highlighted content round-trips without blanking, and array-shaped values load successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.