Fix copying HTML comments in the rich text editor
What does this MR do and why?
The rich text editor's copy and cut handler builds the text/html clipboard payload with ProseMirror's DOMSerializer, which renders every node in the selection through its toDOM spec. The htmlComment node (an HTML comment, shown as a grey dashed placeholder) had a Vue node view but no renderHTML, so the serializer threw this.nodes[node.type.name] is not a function for any selection containing a comment. The throw happens before the handler writes any clipboard format or prevents the default action, so the browser's native copy runs instead: the clipboard gets the node view's DOM as HTML, and pasting that either turns the comment into plain text or, when the selection also holds a label reference, throws again while parsing. Issue and merge request templates put an HTML comment in most descriptions, so selecting a description and copying it failed for anyone using the rich text editor.
Give the comment node a DOM representation: an empty comment element that carries its text in data-description. Other applications paste nothing for it, the same as for a real HTML comment, and the editor's HTML parser reads it back through the comment tag rule the markdown deserializer already feeds (the deserializer turns <!-- --> into comment elements before parsing). The text/x-gfm payload, which the editor prefers when pasting into itself and which the plain text editor reads, keeps carrying <!-- text --> unchanged, and text/plain keeps only the visible text.
Three node types exist only to host a plugin or a parse rule and never enter a document (colorChip, loading, suggestions). They get a minimal renderer so the editor factory spec can assert, without exceptions, that every node and mark type in the schema has a DOM representation. prosemirror-view renders through the same toDOM in its drag serializer and as the fallback for nodes without a node view, so the invariant covers those paths too.
Nothing changes for selections without a comment: the HTML, plain text and markdown payloads for paragraphs, lists, tables and colour chips are the same as before.
Resolves #498708 (closed)
References
- Reproduced with the reporter's steps on the merge base: a description containing
<!-- describe the bug -->and a label reference, select all, copy, delete, paste. The copy logsUncaught TypeError: this.nodes[t.type.name] is not a functionfromcopy_paste.js, and the paste logsCannot read properties of null (reading 'className')fromreference.jsand inserts nothing. Without the label, the paste brings the comment's text back as a normal paragraph. Cut fails in two shapes: when the selection starts in a text paragraph, Chrome performs its native cut and the paste brings the comment back as italic text; when the selection starts on the comment block, nothing is cut and the clipboard keeps its previous content. - New jest examples: 9 fail at the merge base and pass with this change. In the copy/paste extension spec, copying and cutting a selection with a comment writes
text/plain,text/htmlandtext/x-gfmand prevents the default action, cut still removes the selection, and pasting the HTML payload back restores the comment node. A newhtml_commentextension spec covers the deserializer'scommentelement, the rendered element and its round trip. The editor factory spec asserts that every node type (except the top node) and every mark type has a DOM representation; at the merge base it listscolorChip,htmlComment,loadingandsuggestions. The touched specs pass 75/75 under Vue 2 and Vue 3. - The comment node arrived in Add support for rendering HTML comments in rich text editor (!158618) with a node view and no
renderHTML; the clipboard handler has usedDOMSerializersince 2023. - The issue's second error is the paste of the natively copied HTML:
referenceLabelparsesspan.gl-label, and the label chip's node view DOM has no anchor, sogetAnchor(element).classNamefails. With the copy handler working, a paste inside the editor takes thetext/x-gfmpath and never parses that HTML. Hardening that parse rule against external HTML is left out of this change.
Screenshots or screen recordings
Every row is staged in real headless Chrome with the real clipboard: the markdown is entered in the plain text editor, the editor is switched to rich text, the content is selected and copied or cut with the keyboard, then pasted. Before the fix the copy handler throws before writing to the clipboard, so the before side shows what the browser's native copy leaves behind: the comment's text as a normal paragraph (or as italic text after a native cut), an empty editor when the paste itself fails on the label chip's HTML, or nothing cut and the previous clipboard content pasted when the selection starts on the comment block. The four Unchanged rows are byte-identical between before and after.
Recording
The issue's case as a sequence: all selected, copied and deleted, pasted. The first two frames are byte-identical between the two sides; they diverge at the paste.
| Before | After |
|---|---|
![]() |
![]() |
How to set up and validate locally
- In a project that has a label (
bugbelow), open the new issue page. With the description in plain text mode, enter:<!-- describe the bug --> ~bug is broken - Switch to rich text editing: the comment renders as a grey dashed placeholder and the label as a chip.
- Select all and copy. Before the fix, the browser console shows
Uncaught TypeError: this.nodes[t.type.name] is not a function. After the fix, no error. - Delete the content and paste. Before the fix, nothing is pasted and the console shows
Cannot read properties of null (reading 'className'). After the fix, the comment and the label come back. - Repeat with cut instead of copy (once with the selection starting on the comment block, once starting in a paragraph above it), with the comment inside a list item and inside a table cell, and with a selection that only partly covers the paragraphs around the comment.
- Switch to plain text editing, select all in the text area and paste: the markdown includes
<!-- describe the bug -->. - Regression checks: copy and paste a plain paragraph, a nested list, a table and a paragraph with a colour chip such as
`#FF0000`; they behave as before.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

























