Full document, spreadsheet, slideshow, and diagram tooling
1import { Mark, mergeAttributes } from '@tiptap/core';
2import type { CommentAttrs } from '../types.js';
3
4interface CommentOptions {
5 HTMLAttributes: Record<string, string>;
6}
7
8/**
9 * Comment mark — stores inline comments as marks on text ranges.
10 * Each comment has: id, author, timestamp, text.
11 * Rendered as highlighted spans with data attributes for popover display.
12 */
13export const Comment = Mark.create<CommentOptions>({
14 name: 'comment',
15
16 addOptions() {
17 return {
18 HTMLAttributes: {},
19 };
20 },
21
22 addAttributes() {
23 return {
24 commentId: {
25 default: null,
26 parseHTML: (el: HTMLElement) => el.getAttribute('data-comment-id'),
27 renderHTML: (attrs: Record<string, string | null>) => ({ 'data-comment-id': attrs.commentId }),
28 },
29 author: {
30 default: null,
31 parseHTML: (el: HTMLElement) => el.getAttribute('data-comment-author'),
32 renderHTML: (attrs: Record<string, string | null>) => ({ 'data-comment-author': attrs.author }),
33 },
34 timestamp: {
35 default: null,
36 parseHTML: (el: HTMLElement) => el.getAttribute('data-comment-timestamp'),
37 renderHTML: (attrs: Record<string, string | null>) => ({ 'data-comment-timestamp': attrs.timestamp }),
38 },
39 text: {
40 default: null,
41 parseHTML: (el: HTMLElement) => el.getAttribute('data-comment-text'),
42 renderHTML: (attrs: Record<string, string | null>) => ({ 'data-comment-text': attrs.text }),
43 },
44 };
45 },
46
47 parseHTML() {
48 return [
49 {
50 tag: 'span[data-comment-id]',
51 },
52 ];
53 },
54
55 renderHTML({ HTMLAttributes }) {
56 return [
57 'span',
58 mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
59 class: 'comment-mark',
60 }),
61 0,
62 ];
63 },
64
65 addCommands() {
66 return {
67 setComment: (attrs: CommentAttrs) => ({ commands }) => {
68 return commands.setMark(this.name, attrs);
69 },
70 unsetComment: () => ({ commands }) => {
71 return commands.unsetMark(this.name);
72 },
73 };
74 },
75});