@puredesktop/platform-editor 1.0.0-beta.1 → 1.0.0-beta.2
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.
- package/package.json +1 -1
- package/src/CollapsePanel.tsx +35 -35
- package/src/DocumentDiffView.tsx +130 -130
- package/src/DocumentEditor.test.ts +118 -118
- package/src/DocumentEditor.tsx +2651 -2631
- package/src/alignedLineDiff.test.ts +52 -52
- package/src/alignedLineDiff.ts +67 -67
- package/src/collectionAssetSrc.ts +2 -2
- package/src/commentUtils.test.ts +350 -350
- package/src/commentUtils.ts +546 -546
- package/src/constants/toolKeys.ts +14 -14
- package/src/contentFormat.test.ts +27 -27
- package/src/contentFormat.ts +18 -18
- package/src/editorExtensions.ts +155 -155
- package/src/extensions/appliedChangeMark.ts +115 -115
- package/src/extensions/autoReviewPrompts.test.ts +529 -529
- package/src/extensions/autoReviewPrompts.ts +1442 -1442
- package/src/extensions/collectionImage.ts +26 -26
- package/src/extensions/collectionImagePaste.ts +215 -215
- package/src/extensions/commentMark.ts +223 -223
- package/src/extensions/documentAssetIdentity.ts +54 -54
- package/src/extensions/figure.ts +92 -92
- package/src/extensions/footnote.ts +186 -186
- package/src/extensions/indexMarker.ts +88 -88
- package/src/extensions/mathEditing.ts +239 -239
- package/src/extensions/mermaidBlock.tsx +297 -297
- package/src/extensions/slashCommands.test.ts +170 -170
- package/src/extensions/slashCommands.ts +746 -746
- package/src/extensions/smartTypography.test.ts +74 -74
- package/src/extensions/smartTypography.ts +120 -120
- package/src/index.ts +138 -137
- package/src/insertCollectionImage.test.ts +60 -60
- package/src/insertCollectionImage.ts +63 -63
- package/src/insertInlineAssetKind.test.ts +67 -67
- package/src/insertInlineAssetKind.ts +49 -49
- package/src/mermaidPreview.test.ts +54 -54
- package/src/mermaidPreview.ts +115 -115
- package/src/styled.ts +697 -676
- package/src/toolbar/ToolBtn.tsx +77 -63
- package/src/toolbar/Toolbar.test.tsx +325 -325
- package/src/toolbar/Toolbar.tsx +662 -624
- package/src/toolbar/groups/HeadingGroup.tsx +175 -153
- package/src/toolbar/overlayTypes.ts +28 -28
- package/src/useEditorExtensions.ts +22 -22
- package/src/utils/markdownUtils.ts +331 -331
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import type { Editor } from '@tiptap/core'
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { insertCollectionImage } from './insertCollectionImage.js'
|
|
4
|
-
|
|
5
|
-
function createMockEditor(): {
|
|
6
|
-
editor: Editor
|
|
7
|
-
insertContent: ReturnType<typeof vi.fn>
|
|
8
|
-
run: ReturnType<typeof vi.fn>
|
|
9
|
-
} {
|
|
10
|
-
const run = vi.fn()
|
|
11
|
-
const insertContent = vi.fn().mockReturnThis()
|
|
12
|
-
const chain = {
|
|
13
|
-
focus: vi.fn().mockReturnThis(),
|
|
14
|
-
insertContent,
|
|
15
|
-
run,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const editor = {
|
|
19
|
-
isDestroyed: false,
|
|
20
|
-
chain: vi.fn(() => chain),
|
|
21
|
-
schema: { nodes: { image: {} } },
|
|
22
|
-
} as unknown as Editor
|
|
23
|
-
|
|
24
|
-
return { editor, insertContent, run }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe('insertCollectionImage', () => {
|
|
28
|
-
it('inserts image node with data URL and collectionAssetSrc', () => {
|
|
29
|
-
const { editor, insertContent, run } = createMockEditor()
|
|
30
|
-
const bytes = Uint8Array.from([0x89, 0x50, 0x4e, 0x47])
|
|
31
|
-
|
|
32
|
-
insertCollectionImage(editor, {
|
|
33
|
-
relativePath: 'assets/figures/chart.png',
|
|
34
|
-
alt: 'Chart',
|
|
35
|
-
bytes,
|
|
36
|
-
mimeType: 'image/png',
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
expect(insertContent).toHaveBeenCalledWith({
|
|
40
|
-
type: 'image',
|
|
41
|
-
attrs: {
|
|
42
|
-
src: expect.stringMatching(/^data:image\/png;base64,/),
|
|
43
|
-
alt: 'Chart',
|
|
44
|
-
collectionAssetSrc: 'assets/figures/chart.png',
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
expect(run).toHaveBeenCalled()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('returns false when editor is missing', () => {
|
|
51
|
-
expect(
|
|
52
|
-
insertCollectionImage(null, {
|
|
53
|
-
relativePath: 'assets/a.png',
|
|
54
|
-
alt: 'A',
|
|
55
|
-
bytes: new Uint8Array([1]),
|
|
56
|
-
mimeType: 'image/png',
|
|
57
|
-
}),
|
|
58
|
-
).toBe(false)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { insertCollectionImage } from './insertCollectionImage.js'
|
|
4
|
+
|
|
5
|
+
function createMockEditor(): {
|
|
6
|
+
editor: Editor
|
|
7
|
+
insertContent: ReturnType<typeof vi.fn>
|
|
8
|
+
run: ReturnType<typeof vi.fn>
|
|
9
|
+
} {
|
|
10
|
+
const run = vi.fn()
|
|
11
|
+
const insertContent = vi.fn().mockReturnThis()
|
|
12
|
+
const chain = {
|
|
13
|
+
focus: vi.fn().mockReturnThis(),
|
|
14
|
+
insertContent,
|
|
15
|
+
run,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const editor = {
|
|
19
|
+
isDestroyed: false,
|
|
20
|
+
chain: vi.fn(() => chain),
|
|
21
|
+
schema: { nodes: { image: {} } },
|
|
22
|
+
} as unknown as Editor
|
|
23
|
+
|
|
24
|
+
return { editor, insertContent, run }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('insertCollectionImage', () => {
|
|
28
|
+
it('inserts image node with data URL and collectionAssetSrc', () => {
|
|
29
|
+
const { editor, insertContent, run } = createMockEditor()
|
|
30
|
+
const bytes = Uint8Array.from([0x89, 0x50, 0x4e, 0x47])
|
|
31
|
+
|
|
32
|
+
insertCollectionImage(editor, {
|
|
33
|
+
relativePath: 'assets/figures/chart.png',
|
|
34
|
+
alt: 'Chart',
|
|
35
|
+
bytes,
|
|
36
|
+
mimeType: 'image/png',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(insertContent).toHaveBeenCalledWith({
|
|
40
|
+
type: 'image',
|
|
41
|
+
attrs: {
|
|
42
|
+
src: expect.stringMatching(/^data:image\/png;base64,/),
|
|
43
|
+
alt: 'Chart',
|
|
44
|
+
collectionAssetSrc: 'assets/figures/chart.png',
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
expect(run).toHaveBeenCalled()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('returns false when editor is missing', () => {
|
|
51
|
+
expect(
|
|
52
|
+
insertCollectionImage(null, {
|
|
53
|
+
relativePath: 'assets/a.png',
|
|
54
|
+
alt: 'A',
|
|
55
|
+
bytes: new Uint8Array([1]),
|
|
56
|
+
mimeType: 'image/png',
|
|
57
|
+
}),
|
|
58
|
+
).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import type { Editor } from '@tiptap/core'
|
|
2
|
-
|
|
3
|
-
export function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
4
|
-
let binary = ''
|
|
5
|
-
const chunk = 0x8000
|
|
6
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
7
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunk))
|
|
8
|
-
}
|
|
9
|
-
return btoa(binary)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface InsertCollectionImageOptions {
|
|
13
|
-
relativePath: string
|
|
14
|
-
alt: string
|
|
15
|
-
bytes: Uint8Array
|
|
16
|
-
mimeType: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Insert a file collection asset — data URL display + relative path for storage normalize. */
|
|
20
|
-
export function insertCollectionImage(
|
|
21
|
-
editor: Editor | null | undefined,
|
|
22
|
-
options: InsertCollectionImageOptions,
|
|
23
|
-
): boolean {
|
|
24
|
-
if (!editor || editor.isDestroyed) return false
|
|
25
|
-
const imageNode = editor.schema.nodes.image
|
|
26
|
-
if (!imageNode) return false
|
|
27
|
-
|
|
28
|
-
const dataUrl = `data:${options.mimeType};base64,${uint8ArrayToBase64(
|
|
29
|
-
options.bytes,
|
|
30
|
-
)}`
|
|
31
|
-
editor
|
|
32
|
-
.chain()
|
|
33
|
-
.focus()
|
|
34
|
-
.insertContent({
|
|
35
|
-
type: 'image',
|
|
36
|
-
attrs: {
|
|
37
|
-
src: dataUrl,
|
|
38
|
-
alt: options.alt,
|
|
39
|
-
collectionAssetSrc: options.relativePath,
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
.run()
|
|
43
|
-
return true
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function insertCollectionImageFromBinaryResult(
|
|
47
|
-
editor: Editor | null | undefined,
|
|
48
|
-
options: {
|
|
49
|
-
relativePath: string
|
|
50
|
-
alt: string
|
|
51
|
-
mimeType: string
|
|
52
|
-
base64: string
|
|
53
|
-
},
|
|
54
|
-
): boolean {
|
|
55
|
-
const binary = atob(options.base64)
|
|
56
|
-
const bytes = Uint8Array.from(binary, char => char.charCodeAt(0))
|
|
57
|
-
return insertCollectionImage(editor, {
|
|
58
|
-
relativePath: options.relativePath,
|
|
59
|
-
alt: options.alt,
|
|
60
|
-
bytes,
|
|
61
|
-
mimeType: options.mimeType,
|
|
62
|
-
})
|
|
63
|
-
}
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
export function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
4
|
+
let binary = ''
|
|
5
|
+
const chunk = 0x8000
|
|
6
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
7
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk))
|
|
8
|
+
}
|
|
9
|
+
return btoa(binary)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface InsertCollectionImageOptions {
|
|
13
|
+
relativePath: string
|
|
14
|
+
alt: string
|
|
15
|
+
bytes: Uint8Array
|
|
16
|
+
mimeType: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Insert a file collection asset — data URL display + relative path for storage normalize. */
|
|
20
|
+
export function insertCollectionImage(
|
|
21
|
+
editor: Editor | null | undefined,
|
|
22
|
+
options: InsertCollectionImageOptions,
|
|
23
|
+
): boolean {
|
|
24
|
+
if (!editor || editor.isDestroyed) return false
|
|
25
|
+
const imageNode = editor.schema.nodes.image
|
|
26
|
+
if (!imageNode) return false
|
|
27
|
+
|
|
28
|
+
const dataUrl = `data:${options.mimeType};base64,${uint8ArrayToBase64(
|
|
29
|
+
options.bytes,
|
|
30
|
+
)}`
|
|
31
|
+
editor
|
|
32
|
+
.chain()
|
|
33
|
+
.focus()
|
|
34
|
+
.insertContent({
|
|
35
|
+
type: 'image',
|
|
36
|
+
attrs: {
|
|
37
|
+
src: dataUrl,
|
|
38
|
+
alt: options.alt,
|
|
39
|
+
collectionAssetSrc: options.relativePath,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
.run()
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function insertCollectionImageFromBinaryResult(
|
|
47
|
+
editor: Editor | null | undefined,
|
|
48
|
+
options: {
|
|
49
|
+
relativePath: string
|
|
50
|
+
alt: string
|
|
51
|
+
mimeType: string
|
|
52
|
+
base64: string
|
|
53
|
+
},
|
|
54
|
+
): boolean {
|
|
55
|
+
const binary = atob(options.base64)
|
|
56
|
+
const bytes = Uint8Array.from(binary, char => char.charCodeAt(0))
|
|
57
|
+
return insertCollectionImage(editor, {
|
|
58
|
+
relativePath: options.relativePath,
|
|
59
|
+
alt: options.alt,
|
|
60
|
+
bytes,
|
|
61
|
+
mimeType: options.mimeType,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import type { Editor } from '@tiptap/core'
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { insertInlineAssetKind } from './insertInlineAssetKind.js'
|
|
4
|
-
|
|
5
|
-
function createMockEditor(
|
|
6
|
-
overrides: {
|
|
7
|
-
insertMermaidBlock?: ReturnType<typeof vi.fn>
|
|
8
|
-
insertContent?: ReturnType<typeof vi.fn>
|
|
9
|
-
insertBlockMath?: ReturnType<typeof vi.fn>
|
|
10
|
-
} = {},
|
|
11
|
-
): Editor {
|
|
12
|
-
const run = vi.fn()
|
|
13
|
-
const chain = {
|
|
14
|
-
focus: vi.fn().mockReturnThis(),
|
|
15
|
-
insertMermaidBlock:
|
|
16
|
-
overrides.insertMermaidBlock ?? vi.fn().mockReturnThis(),
|
|
17
|
-
insertContent: overrides.insertContent ?? vi.fn().mockReturnThis(),
|
|
18
|
-
insertBlockMath: overrides.insertBlockMath ?? vi.fn().mockReturnThis(),
|
|
19
|
-
run,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
isDestroyed: false,
|
|
24
|
-
chain: vi.fn(() => chain),
|
|
25
|
-
} as unknown as Editor
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('insertInlineAssetKind', () => {
|
|
29
|
-
it('inserts mermaid from sourceCode', () => {
|
|
30
|
-
const insertMermaidBlock = vi.fn().mockReturnThis()
|
|
31
|
-
const editor = createMockEditor({ insertMermaidBlock })
|
|
32
|
-
|
|
33
|
-
insertInlineAssetKind(editor, 'mermaid', 'flowchart LR\n A --> B')
|
|
34
|
-
|
|
35
|
-
expect(insertMermaidBlock).toHaveBeenCalledWith('flowchart LR\n A --> B')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('inserts table HTML from sourceCode', () => {
|
|
39
|
-
const insertContent = vi.fn().mockReturnThis()
|
|
40
|
-
const editor = createMockEditor({ insertContent })
|
|
41
|
-
const table = '<table><tbody><tr><td>Cell</td></tr></tbody></table>'
|
|
42
|
-
|
|
43
|
-
insertInlineAssetKind(editor, 'table', table)
|
|
44
|
-
|
|
45
|
-
expect(insertContent).toHaveBeenCalledWith(table)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('inserts block math via insertBlockMath when available', () => {
|
|
49
|
-
const insertBlockMath = vi.fn().mockReturnThis()
|
|
50
|
-
const editor = createMockEditor({ insertBlockMath })
|
|
51
|
-
|
|
52
|
-
insertInlineAssetKind(editor, 'math', '\\alpha + \\beta')
|
|
53
|
-
|
|
54
|
-
expect(insertBlockMath).toHaveBeenCalledWith({
|
|
55
|
-
latex: '\\alpha + \\beta',
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('no-ops when editor is destroyed', () => {
|
|
60
|
-
const editor = createMockEditor()
|
|
61
|
-
Object.defineProperty(editor, 'isDestroyed', { value: true })
|
|
62
|
-
|
|
63
|
-
insertInlineAssetKind(editor, 'mermaid', 'flowchart TD\n A --> B')
|
|
64
|
-
|
|
65
|
-
expect(editor.chain).not.toHaveBeenCalled()
|
|
66
|
-
})
|
|
67
|
-
})
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { insertInlineAssetKind } from './insertInlineAssetKind.js'
|
|
4
|
+
|
|
5
|
+
function createMockEditor(
|
|
6
|
+
overrides: {
|
|
7
|
+
insertMermaidBlock?: ReturnType<typeof vi.fn>
|
|
8
|
+
insertContent?: ReturnType<typeof vi.fn>
|
|
9
|
+
insertBlockMath?: ReturnType<typeof vi.fn>
|
|
10
|
+
} = {},
|
|
11
|
+
): Editor {
|
|
12
|
+
const run = vi.fn()
|
|
13
|
+
const chain = {
|
|
14
|
+
focus: vi.fn().mockReturnThis(),
|
|
15
|
+
insertMermaidBlock:
|
|
16
|
+
overrides.insertMermaidBlock ?? vi.fn().mockReturnThis(),
|
|
17
|
+
insertContent: overrides.insertContent ?? vi.fn().mockReturnThis(),
|
|
18
|
+
insertBlockMath: overrides.insertBlockMath ?? vi.fn().mockReturnThis(),
|
|
19
|
+
run,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
isDestroyed: false,
|
|
24
|
+
chain: vi.fn(() => chain),
|
|
25
|
+
} as unknown as Editor
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('insertInlineAssetKind', () => {
|
|
29
|
+
it('inserts mermaid from sourceCode', () => {
|
|
30
|
+
const insertMermaidBlock = vi.fn().mockReturnThis()
|
|
31
|
+
const editor = createMockEditor({ insertMermaidBlock })
|
|
32
|
+
|
|
33
|
+
insertInlineAssetKind(editor, 'mermaid', 'flowchart LR\n A --> B')
|
|
34
|
+
|
|
35
|
+
expect(insertMermaidBlock).toHaveBeenCalledWith('flowchart LR\n A --> B')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('inserts table HTML from sourceCode', () => {
|
|
39
|
+
const insertContent = vi.fn().mockReturnThis()
|
|
40
|
+
const editor = createMockEditor({ insertContent })
|
|
41
|
+
const table = '<table><tbody><tr><td>Cell</td></tr></tbody></table>'
|
|
42
|
+
|
|
43
|
+
insertInlineAssetKind(editor, 'table', table)
|
|
44
|
+
|
|
45
|
+
expect(insertContent).toHaveBeenCalledWith(table)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('inserts block math via insertBlockMath when available', () => {
|
|
49
|
+
const insertBlockMath = vi.fn().mockReturnThis()
|
|
50
|
+
const editor = createMockEditor({ insertBlockMath })
|
|
51
|
+
|
|
52
|
+
insertInlineAssetKind(editor, 'math', '\\alpha + \\beta')
|
|
53
|
+
|
|
54
|
+
expect(insertBlockMath).toHaveBeenCalledWith({
|
|
55
|
+
latex: '\\alpha + \\beta',
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('no-ops when editor is destroyed', () => {
|
|
60
|
+
const editor = createMockEditor()
|
|
61
|
+
Object.defineProperty(editor, 'isDestroyed', { value: true })
|
|
62
|
+
|
|
63
|
+
insertInlineAssetKind(editor, 'mermaid', 'flowchart TD\n A --> B')
|
|
64
|
+
|
|
65
|
+
expect(editor.chain).not.toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import type { Editor } from '@tiptap/core'
|
|
2
|
-
|
|
3
|
-
export type InlineAssetInsertKind = 'mermaid' | 'table' | 'math'
|
|
4
|
-
|
|
5
|
-
const DEFAULT_MERMAID = `flowchart TD
|
|
6
|
-
A[Start] --> B[End]`
|
|
7
|
-
|
|
8
|
-
const DEFAULT_TABLE = '<table><tbody><tr><td></td></tr></tbody></table>'
|
|
9
|
-
|
|
10
|
-
const DEFAULT_MATH = 'E = mc^2'
|
|
11
|
-
|
|
12
|
-
/** TipTap commands for inline collection assets — no paths, no IO. */
|
|
13
|
-
export function insertInlineAssetKind(
|
|
14
|
-
editor: Editor | null | undefined,
|
|
15
|
-
kind: InlineAssetInsertKind,
|
|
16
|
-
sourceCode?: string,
|
|
17
|
-
): void {
|
|
18
|
-
if (!editor || editor.isDestroyed) return
|
|
19
|
-
|
|
20
|
-
switch (kind) {
|
|
21
|
-
case 'mermaid':
|
|
22
|
-
editor
|
|
23
|
-
.chain()
|
|
24
|
-
.focus()
|
|
25
|
-
.insertMermaidBlock(sourceCode?.trim() || DEFAULT_MERMAID)
|
|
26
|
-
.run()
|
|
27
|
-
return
|
|
28
|
-
case 'table':
|
|
29
|
-
editor
|
|
30
|
-
.chain()
|
|
31
|
-
.focus()
|
|
32
|
-
.insertContent(sourceCode?.trim() || DEFAULT_TABLE)
|
|
33
|
-
.run()
|
|
34
|
-
return
|
|
35
|
-
case 'math': {
|
|
36
|
-
const latex = sourceCode?.trim() || DEFAULT_MATH
|
|
37
|
-
const commands = editor.chain().focus() as ReturnType<Editor['chain']> & {
|
|
38
|
-
insertBlockMath?: (opts: {
|
|
39
|
-
latex: string
|
|
40
|
-
}) => ReturnType<Editor['chain']>
|
|
41
|
-
}
|
|
42
|
-
if (typeof commands.insertBlockMath === 'function') {
|
|
43
|
-
commands.insertBlockMath({ latex }).run()
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
editor.chain().focus().insertContent(`$$${latex}$$`).run()
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
import type { Editor } from '@tiptap/core'
|
|
2
|
+
|
|
3
|
+
export type InlineAssetInsertKind = 'mermaid' | 'table' | 'math'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MERMAID = `flowchart TD
|
|
6
|
+
A[Start] --> B[End]`
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TABLE = '<table><tbody><tr><td></td></tr></tbody></table>'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MATH = 'E = mc^2'
|
|
11
|
+
|
|
12
|
+
/** TipTap commands for inline collection assets — no paths, no IO. */
|
|
13
|
+
export function insertInlineAssetKind(
|
|
14
|
+
editor: Editor | null | undefined,
|
|
15
|
+
kind: InlineAssetInsertKind,
|
|
16
|
+
sourceCode?: string,
|
|
17
|
+
): void {
|
|
18
|
+
if (!editor || editor.isDestroyed) return
|
|
19
|
+
|
|
20
|
+
switch (kind) {
|
|
21
|
+
case 'mermaid':
|
|
22
|
+
editor
|
|
23
|
+
.chain()
|
|
24
|
+
.focus()
|
|
25
|
+
.insertMermaidBlock(sourceCode?.trim() || DEFAULT_MERMAID)
|
|
26
|
+
.run()
|
|
27
|
+
return
|
|
28
|
+
case 'table':
|
|
29
|
+
editor
|
|
30
|
+
.chain()
|
|
31
|
+
.focus()
|
|
32
|
+
.insertContent(sourceCode?.trim() || DEFAULT_TABLE)
|
|
33
|
+
.run()
|
|
34
|
+
return
|
|
35
|
+
case 'math': {
|
|
36
|
+
const latex = sourceCode?.trim() || DEFAULT_MATH
|
|
37
|
+
const commands = editor.chain().focus() as ReturnType<Editor['chain']> & {
|
|
38
|
+
insertBlockMath?: (opts: {
|
|
39
|
+
latex: string
|
|
40
|
+
}) => ReturnType<Editor['chain']>
|
|
41
|
+
}
|
|
42
|
+
if (typeof commands.insertBlockMath === 'function') {
|
|
43
|
+
commands.insertBlockMath({ latex }).run()
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
editor.chain().focus().insertContent(`$$${latex}$$`).run()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
const mermaidRender = vi.hoisted(() => vi.fn())
|
|
4
|
-
|
|
5
|
-
vi.mock('mermaid', () => ({
|
|
6
|
-
default: {
|
|
7
|
-
initialize: vi.fn(),
|
|
8
|
-
render: mermaidRender,
|
|
9
|
-
},
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
expandMermaidBlocksForPrint,
|
|
14
|
-
extractMermaidSourceFromHtml,
|
|
15
|
-
renderMermaidPreviewSvg,
|
|
16
|
-
} from './mermaidPreview.js'
|
|
17
|
-
|
|
18
|
-
describe('mermaidPreview', () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
mermaidRender.mockReset()
|
|
21
|
-
mermaidRender.mockResolvedValue({
|
|
22
|
-
svg: '<svg data-testid="mermaid"></svg>',
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('extractMermaidSourceFromHtml preserves arrow syntax', () => {
|
|
27
|
-
const html =
|
|
28
|
-
'<figure data-type="mermaid"><pre><code class="language-mermaid">flowchart TD\n A --> B</code></pre></figure>'
|
|
29
|
-
|
|
30
|
-
expect(extractMermaidSourceFromHtml(html)).toBe('flowchart TD\n A --> B')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('renderMermaidPreviewSvg returns svg from mermaid.render', async () => {
|
|
34
|
-
const svg = await renderMermaidPreviewSvg('flowchart TD\n A --> B', {
|
|
35
|
-
theme: 'default',
|
|
36
|
-
})
|
|
37
|
-
expect(mermaidRender).toHaveBeenCalled()
|
|
38
|
-
expect(svg).toContain('data-testid="mermaid"')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('expandMermaidBlocksForPrint replaces blocks with SVG wrapper', async () => {
|
|
42
|
-
const html =
|
|
43
|
-
'<figure data-type="mermaid"><pre><code class="language-mermaid">flowchart TD\n A --> B</code></pre></figure>'
|
|
44
|
-
|
|
45
|
-
const expanded = await expandMermaidBlocksForPrint(html, {
|
|
46
|
-
theme: 'default',
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(mermaidRender).toHaveBeenCalled()
|
|
50
|
-
expect(expanded).toContain('platform-print-mermaid')
|
|
51
|
-
expect(expanded).toContain('<svg data-testid="mermaid"></svg>')
|
|
52
|
-
expect(expanded).not.toContain('language-mermaid')
|
|
53
|
-
})
|
|
54
|
-
})
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const mermaidRender = vi.hoisted(() => vi.fn())
|
|
4
|
+
|
|
5
|
+
vi.mock('mermaid', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
initialize: vi.fn(),
|
|
8
|
+
render: mermaidRender,
|
|
9
|
+
},
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
expandMermaidBlocksForPrint,
|
|
14
|
+
extractMermaidSourceFromHtml,
|
|
15
|
+
renderMermaidPreviewSvg,
|
|
16
|
+
} from './mermaidPreview.js'
|
|
17
|
+
|
|
18
|
+
describe('mermaidPreview', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
mermaidRender.mockReset()
|
|
21
|
+
mermaidRender.mockResolvedValue({
|
|
22
|
+
svg: '<svg data-testid="mermaid"></svg>',
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('extractMermaidSourceFromHtml preserves arrow syntax', () => {
|
|
27
|
+
const html =
|
|
28
|
+
'<figure data-type="mermaid"><pre><code class="language-mermaid">flowchart TD\n A --> B</code></pre></figure>'
|
|
29
|
+
|
|
30
|
+
expect(extractMermaidSourceFromHtml(html)).toBe('flowchart TD\n A --> B')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('renderMermaidPreviewSvg returns svg from mermaid.render', async () => {
|
|
34
|
+
const svg = await renderMermaidPreviewSvg('flowchart TD\n A --> B', {
|
|
35
|
+
theme: 'default',
|
|
36
|
+
})
|
|
37
|
+
expect(mermaidRender).toHaveBeenCalled()
|
|
38
|
+
expect(svg).toContain('data-testid="mermaid"')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('expandMermaidBlocksForPrint replaces blocks with SVG wrapper', async () => {
|
|
42
|
+
const html =
|
|
43
|
+
'<figure data-type="mermaid"><pre><code class="language-mermaid">flowchart TD\n A --> B</code></pre></figure>'
|
|
44
|
+
|
|
45
|
+
const expanded = await expandMermaidBlocksForPrint(html, {
|
|
46
|
+
theme: 'default',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(mermaidRender).toHaveBeenCalled()
|
|
50
|
+
expect(expanded).toContain('platform-print-mermaid')
|
|
51
|
+
expect(expanded).toContain('<svg data-testid="mermaid"></svg>')
|
|
52
|
+
expect(expanded).not.toContain('language-mermaid')
|
|
53
|
+
})
|
|
54
|
+
})
|