@puredesktop/platform-editor 1.0.0-beta.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/CollapsePanel.tsx +35 -35
  3. package/src/DocumentDiffView.tsx +130 -130
  4. package/src/DocumentEditor.test.ts +118 -118
  5. package/src/DocumentEditor.tsx +2651 -2631
  6. package/src/alignedLineDiff.test.ts +52 -52
  7. package/src/alignedLineDiff.ts +67 -67
  8. package/src/collectionAssetSrc.ts +2 -2
  9. package/src/commentUtils.test.ts +350 -350
  10. package/src/commentUtils.ts +546 -546
  11. package/src/constants/toolKeys.ts +14 -14
  12. package/src/contentFormat.test.ts +27 -27
  13. package/src/contentFormat.ts +18 -18
  14. package/src/editorExtensions.ts +155 -155
  15. package/src/extensions/appliedChangeMark.ts +115 -115
  16. package/src/extensions/autoReviewPrompts.test.ts +529 -529
  17. package/src/extensions/autoReviewPrompts.ts +1442 -1442
  18. package/src/extensions/collectionImage.ts +26 -26
  19. package/src/extensions/collectionImagePaste.ts +215 -215
  20. package/src/extensions/commentMark.ts +223 -223
  21. package/src/extensions/documentAssetIdentity.ts +54 -54
  22. package/src/extensions/figure.ts +92 -92
  23. package/src/extensions/footnote.ts +186 -186
  24. package/src/extensions/indexMarker.ts +88 -88
  25. package/src/extensions/mathEditing.ts +239 -239
  26. package/src/extensions/mermaidBlock.tsx +297 -297
  27. package/src/extensions/slashCommands.test.ts +170 -170
  28. package/src/extensions/slashCommands.ts +746 -746
  29. package/src/extensions/smartTypography.test.ts +74 -74
  30. package/src/extensions/smartTypography.ts +120 -120
  31. package/src/index.ts +138 -137
  32. package/src/insertCollectionImage.test.ts +60 -60
  33. package/src/insertCollectionImage.ts +63 -63
  34. package/src/insertInlineAssetKind.test.ts +67 -67
  35. package/src/insertInlineAssetKind.ts +49 -49
  36. package/src/mermaidPreview.test.ts +54 -54
  37. package/src/mermaidPreview.ts +115 -115
  38. package/src/styled.ts +697 -676
  39. package/src/toolbar/ToolBtn.tsx +77 -63
  40. package/src/toolbar/Toolbar.test.tsx +325 -325
  41. package/src/toolbar/Toolbar.tsx +662 -624
  42. package/src/toolbar/groups/HeadingGroup.tsx +175 -153
  43. package/src/toolbar/overlayTypes.ts +28 -28
  44. package/src/useEditorExtensions.ts +22 -22
  45. package/src/utils/markdownUtils.ts +331 -331
@@ -1,14 +1,14 @@
1
- export const TOOL_KEYS = {
2
- LINK: 'link',
3
- BULLET_LIST: 'bulletList',
4
- ORDERED_LIST: 'orderedList',
5
- CODE: 'code',
6
- BOLD: 'bold',
7
- ITALIC: 'italic',
8
- UNDERLINE: 'underline',
9
- TASK_LIST: 'taskList',
10
- BLOCKQUOTE: 'blockquote',
11
- CODE_BLOCK: 'codeBlock',
12
- INDEX_MARKER: 'indexMarker',
13
- TABLE: 'table',
14
- } as const
1
+ export const TOOL_KEYS = {
2
+ LINK: 'link',
3
+ BULLET_LIST: 'bulletList',
4
+ ORDERED_LIST: 'orderedList',
5
+ CODE: 'code',
6
+ BOLD: 'bold',
7
+ ITALIC: 'italic',
8
+ UNDERLINE: 'underline',
9
+ TASK_LIST: 'taskList',
10
+ BLOCKQUOTE: 'blockquote',
11
+ CODE_BLOCK: 'codeBlock',
12
+ INDEX_MARKER: 'indexMarker',
13
+ TABLE: 'table',
14
+ } as const
@@ -1,27 +1,27 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { resolveEditorContent } from './contentFormat.js'
3
-
4
- describe('resolveEditorContent', () => {
5
- it('parses markdown with inline index-marker HTML when source is markdown', () => {
6
- const input = `# The Permissions Question
7
-
8
- Every product eventually faces a <a class="ix" data-term="permissions">permissions</a> question.
9
-
10
- ## A test that doesn't exist`
11
-
12
- const resolved = resolveEditorContent(input, 'markdown')
13
-
14
- expect(resolved).toContain('<h1>The Permissions Question</h1>')
15
- expect(resolved).toContain('<h2>A test that doesn&#39;t exist</h2>')
16
- expect(resolved).toContain(
17
- '<p>Every product eventually faces a <a class="ix" data-term="permissions">permissions</a> question.</p>',
18
- )
19
- expect(resolved).not.toContain('# The Permissions Question')
20
- expect(resolved).not.toContain("## A test that doesn't exist")
21
- })
22
-
23
- it('keeps real html fragments untouched when source is html', () => {
24
- const input = '<h1>Introduction</h1><p>Already html</p>'
25
- expect(resolveEditorContent(input, 'html')).toBe(input)
26
- })
27
- })
1
+ import { describe, expect, it } from 'vitest'
2
+ import { resolveEditorContent } from './contentFormat.js'
3
+
4
+ describe('resolveEditorContent', () => {
5
+ it('parses markdown with inline index-marker HTML when source is markdown', () => {
6
+ const input = `# The Permissions Question
7
+
8
+ Every product eventually faces a <a class="ix" data-term="permissions">permissions</a> question.
9
+
10
+ ## A test that doesn't exist`
11
+
12
+ const resolved = resolveEditorContent(input, 'markdown')
13
+
14
+ expect(resolved).toContain('<h1>The Permissions Question</h1>')
15
+ expect(resolved).toContain('<h2>A test that doesn&#39;t exist</h2>')
16
+ expect(resolved).toContain(
17
+ '<p>Every product eventually faces a <a class="ix" data-term="permissions">permissions</a> question.</p>',
18
+ )
19
+ expect(resolved).not.toContain('# The Permissions Question')
20
+ expect(resolved).not.toContain("## A test that doesn't exist")
21
+ })
22
+
23
+ it('keeps real html fragments untouched when source is html', () => {
24
+ const input = '<h1>Introduction</h1><p>Already html</p>'
25
+ expect(resolveEditorContent(input, 'html')).toBe(input)
26
+ })
27
+ })
@@ -1,18 +1,18 @@
1
- import { toHtml } from './utils/markdownUtils.js'
2
-
3
- export type EditorInputFormat = 'auto' | 'markdown' | 'html'
4
-
5
- const looksLikeHtmlDocument = (str: string): boolean =>
6
- /^\s*<(?:!doctype|html|body|article|section|main|div|p|h[1-6]|ul|ol|li|blockquote|table|figure)\b/i.test(
7
- str,
8
- )
9
-
10
- export function resolveEditorContent(
11
- value: string,
12
- inputFormat: EditorInputFormat = 'auto',
13
- ): string {
14
- if (!value) return value
15
- if (inputFormat === 'markdown') return toHtml(value)
16
- if (inputFormat === 'html') return value
17
- return looksLikeHtmlDocument(value) ? value : toHtml(value)
18
- }
1
+ import { toHtml } from './utils/markdownUtils.js'
2
+
3
+ export type EditorInputFormat = 'auto' | 'markdown' | 'html'
4
+
5
+ const looksLikeHtmlDocument = (str: string): boolean =>
6
+ /^\s*<(?:!doctype|html|body|article|section|main|div|p|h[1-6]|ul|ol|li|blockquote|table|figure)\b/i.test(
7
+ str,
8
+ )
9
+
10
+ export function resolveEditorContent(
11
+ value: string,
12
+ inputFormat: EditorInputFormat = 'auto',
13
+ ): string {
14
+ if (!value) return value
15
+ if (inputFormat === 'markdown') return toHtml(value)
16
+ if (inputFormat === 'html') return value
17
+ return looksLikeHtmlDocument(value) ? value : toHtml(value)
18
+ }
@@ -1,155 +1,155 @@
1
- import StarterKit from '@tiptap/starter-kit'
2
- import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
3
- import { CharacterCount } from '@tiptap/extension-character-count'
4
- import Placeholder from '@tiptap/extension-placeholder'
5
- import { Mathematics } from '@tiptap/extension-mathematics'
6
- import {
7
- Table,
8
- TableCell,
9
- TableHeader,
10
- TableRow,
11
- } from '@tiptap/extension-table'
12
- import { Subscript } from '@tiptap/extension-subscript'
13
- import { Superscript } from '@tiptap/extension-superscript'
14
- import { TaskList } from '@tiptap/extension-task-list'
15
- import { TaskItem } from '@tiptap/extension-task-item'
16
- import { TextAlign } from '@tiptap/extension-text-align'
17
- import { Highlight } from '@tiptap/extension-highlight'
18
- import { Image } from '@tiptap/extension-image'
19
- import type { Extensions } from '@tiptap/react'
20
- import { common, createLowlight } from 'lowlight'
21
- import 'katex/dist/katex.min.css'
22
- import { Figure } from './extensions/figure.js'
23
- import { IndexMarker } from './extensions/indexMarker.js'
24
- import { Footnote } from './extensions/footnote.js'
25
- import { MathEditing } from './extensions/mathEditing.js'
26
- import { MermaidBlock } from './extensions/mermaidBlock.js'
27
- import { DocumentAssetIdentity } from './extensions/documentAssetIdentity.js'
28
- import { SmartTypography } from './extensions/smartTypography.js'
29
- import { CommentMark } from './extensions/commentMark.js'
30
- import { AppliedChangeMark } from './extensions/appliedChangeMark.js'
31
- import { AutoReviewPrompts } from './extensions/autoReviewPrompts.js'
32
-
33
- const lowlight = createLowlight(common)
34
-
35
- /** Optional markup capabilities — enable per app; no host or filesystem behavior. */
36
- export interface EditorFeatures {
37
- tables?: boolean
38
- taskList?: boolean
39
- highlight?: boolean
40
- math?: boolean
41
- figures?: boolean
42
- mermaid?: boolean
43
- footnotes?: boolean
44
- smartTypography?: boolean
45
- /** In-document review marks (toolbar must pass `enableComments` on DocumentEditor). */
46
- comments?: boolean
47
- /** Prompt batching and validated auto editorial mark application. */
48
- autoReviewPrompts?: boolean
49
- /** Book-style index markers (`a.ix`). */
50
- indexMarkers?: boolean
51
- /** `data-asset-id` on math/table nodes for external asset indexes. */
52
- documentAssetIdentity?: boolean
53
- }
54
-
55
- const DEFAULT_FEATURES: Required<EditorFeatures> = {
56
- tables: true,
57
- taskList: true,
58
- highlight: true,
59
- math: true,
60
- figures: true,
61
- mermaid: true,
62
- footnotes: true,
63
- smartTypography: true,
64
- comments: false,
65
- autoReviewPrompts: false,
66
- indexMarkers: false,
67
- documentAssetIdentity: false,
68
- }
69
-
70
- export interface BuildExtensionsOptions {
71
- placeholder?: string
72
- features?: EditorFeatures
73
- }
74
-
75
- /**
76
- * Build the shared TipTap extension set. Apps may concat further extensions
77
- * (e.g. custom image paste) or pass the result to DocumentEditor.
78
- */
79
- export function buildExtensions(opts: BuildExtensionsOptions = {}): Extensions {
80
- const features = { ...DEFAULT_FEATURES, ...opts.features }
81
- const extensions: Extensions = [
82
- StarterKit.configure({
83
- link: { openOnClick: false },
84
- heading: { levels: [1, 2, 3] },
85
- codeBlock: false,
86
- }),
87
- CodeBlockLowlight.configure({
88
- lowlight,
89
- defaultLanguage: 'plaintext',
90
- }),
91
- CharacterCount,
92
- Subscript,
93
- Superscript,
94
- TextAlign.configure({ types: ['heading', 'paragraph'] }),
95
- ]
96
-
97
- if (features.documentAssetIdentity) {
98
- extensions.push(DocumentAssetIdentity)
99
- }
100
- if (features.tables) {
101
- extensions.push(
102
- Table.configure({ resizable: true }),
103
- TableCell,
104
- TableHeader,
105
- TableRow,
106
- )
107
- }
108
- if (features.taskList) {
109
- extensions.push(TaskList, TaskItem.configure({ nested: true }))
110
- }
111
- if (features.highlight) {
112
- extensions.push(Highlight)
113
- }
114
- if (features.math) {
115
- extensions.push(
116
- Mathematics.configure({
117
- katexOptions: { throwOnError: false },
118
- }),
119
- MathEditing,
120
- )
121
- }
122
- if (features.figures) {
123
- extensions.push(
124
- Image.configure({ inline: false, allowBase64: false }),
125
- Figure,
126
- )
127
- }
128
- if (features.mermaid) {
129
- extensions.push(MermaidBlock)
130
- }
131
- if (features.indexMarkers) {
132
- extensions.push(IndexMarker)
133
- }
134
- if (features.comments) {
135
- extensions.push(CommentMark)
136
- extensions.push(AppliedChangeMark)
137
- }
138
- if (features.autoReviewPrompts) {
139
- extensions.push(AutoReviewPrompts)
140
- }
141
- if (features.footnotes) {
142
- extensions.push(Footnote)
143
- }
144
- if (features.smartTypography) {
145
- extensions.push(SmartTypography)
146
- }
147
-
148
- extensions.push(
149
- Placeholder.configure({
150
- placeholder: opts.placeholder ?? 'Start writing…',
151
- }),
152
- )
153
-
154
- return extensions
155
- }
1
+ import StarterKit from '@tiptap/starter-kit'
2
+ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
3
+ import { CharacterCount } from '@tiptap/extension-character-count'
4
+ import Placeholder from '@tiptap/extension-placeholder'
5
+ import { Mathematics } from '@tiptap/extension-mathematics'
6
+ import {
7
+ Table,
8
+ TableCell,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@tiptap/extension-table'
12
+ import { Subscript } from '@tiptap/extension-subscript'
13
+ import { Superscript } from '@tiptap/extension-superscript'
14
+ import { TaskList } from '@tiptap/extension-task-list'
15
+ import { TaskItem } from '@tiptap/extension-task-item'
16
+ import { TextAlign } from '@tiptap/extension-text-align'
17
+ import { Highlight } from '@tiptap/extension-highlight'
18
+ import { Image } from '@tiptap/extension-image'
19
+ import type { Extensions } from '@tiptap/react'
20
+ import { common, createLowlight } from 'lowlight'
21
+ import 'katex/dist/katex.min.css'
22
+ import { Figure } from './extensions/figure.js'
23
+ import { IndexMarker } from './extensions/indexMarker.js'
24
+ import { Footnote } from './extensions/footnote.js'
25
+ import { MathEditing } from './extensions/mathEditing.js'
26
+ import { MermaidBlock } from './extensions/mermaidBlock.js'
27
+ import { DocumentAssetIdentity } from './extensions/documentAssetIdentity.js'
28
+ import { SmartTypography } from './extensions/smartTypography.js'
29
+ import { CommentMark } from './extensions/commentMark.js'
30
+ import { AppliedChangeMark } from './extensions/appliedChangeMark.js'
31
+ import { AutoReviewPrompts } from './extensions/autoReviewPrompts.js'
32
+
33
+ const lowlight = createLowlight(common)
34
+
35
+ /** Optional markup capabilities — enable per app; no host or filesystem behavior. */
36
+ export interface EditorFeatures {
37
+ tables?: boolean
38
+ taskList?: boolean
39
+ highlight?: boolean
40
+ math?: boolean
41
+ figures?: boolean
42
+ mermaid?: boolean
43
+ footnotes?: boolean
44
+ smartTypography?: boolean
45
+ /** In-document review marks (toolbar must pass `enableComments` on DocumentEditor). */
46
+ comments?: boolean
47
+ /** Prompt batching and validated auto editorial mark application. */
48
+ autoReviewPrompts?: boolean
49
+ /** Book-style index markers (`a.ix`). */
50
+ indexMarkers?: boolean
51
+ /** `data-asset-id` on math/table nodes for external asset indexes. */
52
+ documentAssetIdentity?: boolean
53
+ }
54
+
55
+ const DEFAULT_FEATURES: Required<EditorFeatures> = {
56
+ tables: true,
57
+ taskList: true,
58
+ highlight: true,
59
+ math: true,
60
+ figures: true,
61
+ mermaid: true,
62
+ footnotes: true,
63
+ smartTypography: true,
64
+ comments: false,
65
+ autoReviewPrompts: false,
66
+ indexMarkers: false,
67
+ documentAssetIdentity: false,
68
+ }
69
+
70
+ export interface BuildExtensionsOptions {
71
+ placeholder?: string
72
+ features?: EditorFeatures
73
+ }
74
+
75
+ /**
76
+ * Build the shared TipTap extension set. Apps may concat further extensions
77
+ * (e.g. custom image paste) or pass the result to DocumentEditor.
78
+ */
79
+ export function buildExtensions(opts: BuildExtensionsOptions = {}): Extensions {
80
+ const features = { ...DEFAULT_FEATURES, ...opts.features }
81
+ const extensions: Extensions = [
82
+ StarterKit.configure({
83
+ link: { openOnClick: false },
84
+ heading: { levels: [1, 2, 3] },
85
+ codeBlock: false,
86
+ }),
87
+ CodeBlockLowlight.configure({
88
+ lowlight,
89
+ defaultLanguage: 'plaintext',
90
+ }),
91
+ CharacterCount,
92
+ Subscript,
93
+ Superscript,
94
+ TextAlign.configure({ types: ['heading', 'paragraph'] }),
95
+ ]
96
+
97
+ if (features.documentAssetIdentity) {
98
+ extensions.push(DocumentAssetIdentity)
99
+ }
100
+ if (features.tables) {
101
+ extensions.push(
102
+ Table.configure({ resizable: true }),
103
+ TableCell,
104
+ TableHeader,
105
+ TableRow,
106
+ )
107
+ }
108
+ if (features.taskList) {
109
+ extensions.push(TaskList, TaskItem.configure({ nested: true }))
110
+ }
111
+ if (features.highlight) {
112
+ extensions.push(Highlight)
113
+ }
114
+ if (features.math) {
115
+ extensions.push(
116
+ Mathematics.configure({
117
+ katexOptions: { throwOnError: false },
118
+ }),
119
+ MathEditing,
120
+ )
121
+ }
122
+ if (features.figures) {
123
+ extensions.push(
124
+ Image.configure({ inline: false, allowBase64: false }),
125
+ Figure,
126
+ )
127
+ }
128
+ if (features.mermaid) {
129
+ extensions.push(MermaidBlock)
130
+ }
131
+ if (features.indexMarkers) {
132
+ extensions.push(IndexMarker)
133
+ }
134
+ if (features.comments) {
135
+ extensions.push(CommentMark)
136
+ extensions.push(AppliedChangeMark)
137
+ }
138
+ if (features.autoReviewPrompts) {
139
+ extensions.push(AutoReviewPrompts)
140
+ }
141
+ if (features.footnotes) {
142
+ extensions.push(Footnote)
143
+ }
144
+ if (features.smartTypography) {
145
+ extensions.push(SmartTypography)
146
+ }
147
+
148
+ extensions.push(
149
+ Placeholder.configure({
150
+ placeholder: opts.placeholder ?? 'Start writing…',
151
+ }),
152
+ )
153
+
154
+ return extensions
155
+ }
@@ -1,115 +1,115 @@
1
- import { Mark, mergeAttributes } from '@tiptap/core'
2
-
3
- declare module '@tiptap/core' {
4
- interface Commands<ReturnType> {
5
- appliedChangeMark: {
6
- setAppliedChangeMark: (attrs: {
7
- originalText: string
8
- replacementText: string
9
- sourceCommentId?: string
10
- sourceType?: string
11
- actor?: string
12
- createdAt?: string
13
- }) => ReturnType
14
- unsetAppliedChangeMark: () => ReturnType
15
- }
16
- }
17
- }
18
-
19
- export const AppliedChangeMark = Mark.create({
20
- name: 'appliedChangeMark',
21
- inclusive: false,
22
-
23
- addAttributes() {
24
- return {
25
- originalText: {
26
- default: null,
27
- parseHTML: el => el.getAttribute('data-pm-applied-original'),
28
- renderHTML: (attrs: { originalText?: string }) =>
29
- attrs.originalText
30
- ? { 'data-pm-applied-original': attrs.originalText }
31
- : {},
32
- },
33
- replacementText: {
34
- default: null,
35
- parseHTML: el => el.getAttribute('data-pm-applied-replacement'),
36
- renderHTML: (attrs: { replacementText?: string }) =>
37
- attrs.replacementText
38
- ? { 'data-pm-applied-replacement': attrs.replacementText }
39
- : {},
40
- },
41
- sourceCommentId: {
42
- default: null,
43
- parseHTML: el => el.getAttribute('data-pm-applied-source-comment-id'),
44
- renderHTML: (attrs: { sourceCommentId?: string }) =>
45
- attrs.sourceCommentId
46
- ? { 'data-pm-applied-source-comment-id': attrs.sourceCommentId }
47
- : {},
48
- },
49
- sourceType: {
50
- default: null,
51
- parseHTML: el => el.getAttribute('data-pm-applied-source-type'),
52
- renderHTML: (attrs: { sourceType?: string }) =>
53
- attrs.sourceType ? { 'data-pm-applied-source-type': attrs.sourceType } : {},
54
- },
55
- actor: {
56
- default: null,
57
- parseHTML: el => el.getAttribute('data-pm-applied-actor'),
58
- renderHTML: (attrs: { actor?: string }) =>
59
- attrs.actor ? { 'data-pm-applied-actor': attrs.actor } : {},
60
- },
61
- createdAt: {
62
- default: null,
63
- parseHTML: el => el.getAttribute('data-pm-applied-created-at'),
64
- renderHTML: (attrs: { createdAt?: string }) =>
65
- attrs.createdAt ? { 'data-pm-applied-created-at': attrs.createdAt } : {},
66
- },
67
- }
68
- },
69
-
70
- parseHTML() {
71
- return [{ tag: 'span[data-pm-applied-change="true"]' }]
72
- },
73
-
74
- renderHTML({ HTMLAttributes, mark }) {
75
- const attrs = mark.attrs as {
76
- originalText?: string
77
- replacementText?: string
78
- actor?: string
79
- createdAt?: string
80
- }
81
- const title = [
82
- attrs.actor ? `${attrs.actor} applied edit` : 'Applied edit',
83
- attrs.originalText ? `Original: ${attrs.originalText}` : null,
84
- attrs.replacementText ? `Replacement: ${attrs.replacementText}` : null,
85
- attrs.createdAt ?? null,
86
- ]
87
- .filter(Boolean)
88
- .join('\n')
89
- return [
90
- 'span',
91
- mergeAttributes(HTMLAttributes, {
92
- 'data-pm-applied-change': 'true',
93
- class: 'applied-change-mark',
94
- title,
95
- }),
96
- 0,
97
- ]
98
- },
99
-
100
- addCommands() {
101
- return {
102
- setAppliedChangeMark:
103
- attrs =>
104
- ({ commands, state }) => {
105
- const { from, to } = state.selection
106
- if (from === to) return false
107
- return commands.setMark(this.name, attrs)
108
- },
109
- unsetAppliedChangeMark:
110
- () =>
111
- ({ commands }) =>
112
- commands.unsetMark(this.name),
113
- }
114
- },
115
- })
1
+ import { Mark, mergeAttributes } from '@tiptap/core'
2
+
3
+ declare module '@tiptap/core' {
4
+ interface Commands<ReturnType> {
5
+ appliedChangeMark: {
6
+ setAppliedChangeMark: (attrs: {
7
+ originalText: string
8
+ replacementText: string
9
+ sourceCommentId?: string
10
+ sourceType?: string
11
+ actor?: string
12
+ createdAt?: string
13
+ }) => ReturnType
14
+ unsetAppliedChangeMark: () => ReturnType
15
+ }
16
+ }
17
+ }
18
+
19
+ export const AppliedChangeMark = Mark.create({
20
+ name: 'appliedChangeMark',
21
+ inclusive: false,
22
+
23
+ addAttributes() {
24
+ return {
25
+ originalText: {
26
+ default: null,
27
+ parseHTML: el => el.getAttribute('data-pm-applied-original'),
28
+ renderHTML: (attrs: { originalText?: string }) =>
29
+ attrs.originalText
30
+ ? { 'data-pm-applied-original': attrs.originalText }
31
+ : {},
32
+ },
33
+ replacementText: {
34
+ default: null,
35
+ parseHTML: el => el.getAttribute('data-pm-applied-replacement'),
36
+ renderHTML: (attrs: { replacementText?: string }) =>
37
+ attrs.replacementText
38
+ ? { 'data-pm-applied-replacement': attrs.replacementText }
39
+ : {},
40
+ },
41
+ sourceCommentId: {
42
+ default: null,
43
+ parseHTML: el => el.getAttribute('data-pm-applied-source-comment-id'),
44
+ renderHTML: (attrs: { sourceCommentId?: string }) =>
45
+ attrs.sourceCommentId
46
+ ? { 'data-pm-applied-source-comment-id': attrs.sourceCommentId }
47
+ : {},
48
+ },
49
+ sourceType: {
50
+ default: null,
51
+ parseHTML: el => el.getAttribute('data-pm-applied-source-type'),
52
+ renderHTML: (attrs: { sourceType?: string }) =>
53
+ attrs.sourceType ? { 'data-pm-applied-source-type': attrs.sourceType } : {},
54
+ },
55
+ actor: {
56
+ default: null,
57
+ parseHTML: el => el.getAttribute('data-pm-applied-actor'),
58
+ renderHTML: (attrs: { actor?: string }) =>
59
+ attrs.actor ? { 'data-pm-applied-actor': attrs.actor } : {},
60
+ },
61
+ createdAt: {
62
+ default: null,
63
+ parseHTML: el => el.getAttribute('data-pm-applied-created-at'),
64
+ renderHTML: (attrs: { createdAt?: string }) =>
65
+ attrs.createdAt ? { 'data-pm-applied-created-at': attrs.createdAt } : {},
66
+ },
67
+ }
68
+ },
69
+
70
+ parseHTML() {
71
+ return [{ tag: 'span[data-pm-applied-change="true"]' }]
72
+ },
73
+
74
+ renderHTML({ HTMLAttributes, mark }) {
75
+ const attrs = mark.attrs as {
76
+ originalText?: string
77
+ replacementText?: string
78
+ actor?: string
79
+ createdAt?: string
80
+ }
81
+ const title = [
82
+ attrs.actor ? `${attrs.actor} applied edit` : 'Applied edit',
83
+ attrs.originalText ? `Original: ${attrs.originalText}` : null,
84
+ attrs.replacementText ? `Replacement: ${attrs.replacementText}` : null,
85
+ attrs.createdAt ?? null,
86
+ ]
87
+ .filter(Boolean)
88
+ .join('\n')
89
+ return [
90
+ 'span',
91
+ mergeAttributes(HTMLAttributes, {
92
+ 'data-pm-applied-change': 'true',
93
+ class: 'applied-change-mark',
94
+ title,
95
+ }),
96
+ 0,
97
+ ]
98
+ },
99
+
100
+ addCommands() {
101
+ return {
102
+ setAppliedChangeMark:
103
+ attrs =>
104
+ ({ commands, state }) => {
105
+ const { from, to } = state.selection
106
+ if (from === to) return false
107
+ return commands.setMark(this.name, attrs)
108
+ },
109
+ unsetAppliedChangeMark:
110
+ () =>
111
+ ({ commands }) =>
112
+ commands.unsetMark(this.name),
113
+ }
114
+ },
115
+ })