@puredesktop/platform-editor 1.0.0-beta.1
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 +63 -0
- package/src/CollapsePanel.tsx +35 -0
- package/src/DocumentDiffView.tsx +130 -0
- package/src/DocumentEditor.test.ts +118 -0
- package/src/DocumentEditor.tsx +2631 -0
- package/src/alignedLineDiff.test.ts +52 -0
- package/src/alignedLineDiff.ts +67 -0
- package/src/collectionAssetSrc.ts +2 -0
- package/src/commentUtils.test.ts +350 -0
- package/src/commentUtils.ts +546 -0
- package/src/constants/toolKeys.ts +14 -0
- package/src/contentFormat.test.ts +27 -0
- package/src/contentFormat.ts +18 -0
- package/src/editorExtensions.ts +155 -0
- package/src/extensions/appliedChangeMark.ts +115 -0
- package/src/extensions/autoReviewPrompts.test.ts +529 -0
- package/src/extensions/autoReviewPrompts.ts +1442 -0
- package/src/extensions/collectionImage.ts +26 -0
- package/src/extensions/collectionImagePaste.ts +215 -0
- package/src/extensions/commentMark.ts +223 -0
- package/src/extensions/documentAssetIdentity.ts +54 -0
- package/src/extensions/figure.ts +92 -0
- package/src/extensions/footnote.ts +186 -0
- package/src/extensions/indexMarker.ts +88 -0
- package/src/extensions/mathEditing.ts +239 -0
- package/src/extensions/mermaidBlock.tsx +297 -0
- package/src/extensions/slashCommands.test.ts +170 -0
- package/src/extensions/slashCommands.ts +746 -0
- package/src/extensions/smartTypography.test.ts +74 -0
- package/src/extensions/smartTypography.ts +120 -0
- package/src/index.ts +137 -0
- package/src/insertCollectionImage.test.ts +60 -0
- package/src/insertCollectionImage.ts +63 -0
- package/src/insertInlineAssetKind.test.ts +67 -0
- package/src/insertInlineAssetKind.ts +49 -0
- package/src/mermaidPreview.test.ts +54 -0
- package/src/mermaidPreview.ts +115 -0
- package/src/styled.ts +676 -0
- package/src/toolbar/ToolBtn.tsx +63 -0
- package/src/toolbar/Toolbar.test.tsx +325 -0
- package/src/toolbar/Toolbar.tsx +624 -0
- package/src/toolbar/groups/HeadingGroup.tsx +153 -0
- package/src/toolbar/overlayTypes.ts +28 -0
- package/src/useEditorExtensions.ts +22 -0
- package/src/utils/markdownUtils.ts +331 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type mermaidType from 'mermaid'
|
|
2
|
+
|
|
3
|
+
export type MermaidPreviewTheme = 'default' | 'dark'
|
|
4
|
+
|
|
5
|
+
export interface RenderMermaidPreviewSvgOptions {
|
|
6
|
+
theme?: MermaidPreviewTheme
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ExpandMermaidBlocksOptions {
|
|
10
|
+
theme?: MermaidPreviewTheme
|
|
11
|
+
wrapperClass?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let mermaidPromise: Promise<typeof mermaidType> | null = null
|
|
15
|
+
let mermaidInitializedTheme: MermaidPreviewTheme | null = null
|
|
16
|
+
|
|
17
|
+
async function loadMermaid(
|
|
18
|
+
theme: MermaidPreviewTheme,
|
|
19
|
+
): Promise<typeof mermaidType> {
|
|
20
|
+
mermaidPromise ??= import('mermaid').then(module => module.default)
|
|
21
|
+
const mermaid = await mermaidPromise
|
|
22
|
+
if (mermaidInitializedTheme !== theme) {
|
|
23
|
+
mermaid.initialize({
|
|
24
|
+
startOnLoad: false,
|
|
25
|
+
securityLevel: 'strict',
|
|
26
|
+
theme,
|
|
27
|
+
suppressErrorRendering: true,
|
|
28
|
+
})
|
|
29
|
+
mermaidInitializedTheme = theme
|
|
30
|
+
}
|
|
31
|
+
return mermaid
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function platformThemeIsDark(): boolean {
|
|
35
|
+
if (typeof document === 'undefined') return false
|
|
36
|
+
return document.documentElement.dataset.platformTheme === 'dark'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function decodeHtmlEntities(text: string): string {
|
|
40
|
+
return text
|
|
41
|
+
.replace(/</g, '<')
|
|
42
|
+
.replace(/>/g, '>')
|
|
43
|
+
.replace(/&/g, '&')
|
|
44
|
+
.replace(/"/g, '"')
|
|
45
|
+
.replace(/'/g, "'")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const MERMAID_CODE_PATTERN =
|
|
49
|
+
/<code\b[^>]*class=["'][^"']*language-mermaid[^"']*["'][^>]*>([\s\S]*?)<\/code>/i
|
|
50
|
+
|
|
51
|
+
const MERMAID_BLOCK_PATTERN =
|
|
52
|
+
/<(?:figure|div)\b[^>]*data-type=["']mermaid["'][^>]*>[\s\S]*?<\/(?:figure|div)>/gi
|
|
53
|
+
|
|
54
|
+
/** Extract mermaid source from serialized editor/shell HTML (regex — safe for `-->`). */
|
|
55
|
+
export function extractMermaidSourceFromHtml(html: string): string {
|
|
56
|
+
const match = html.match(MERMAID_CODE_PATTERN)
|
|
57
|
+
return decodeHtmlEntities(match?.[1] ?? '').trim()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function renderMermaidPreviewSvg(
|
|
61
|
+
sourceCode: string,
|
|
62
|
+
options: RenderMermaidPreviewSvgOptions = {},
|
|
63
|
+
): Promise<string> {
|
|
64
|
+
const theme = options.theme ?? (platformThemeIsDark() ? 'dark' : 'default')
|
|
65
|
+
const mermaid = await loadMermaid(theme)
|
|
66
|
+
const diagram = sourceCode.trim() || 'flowchart TD\n A[Empty diagram]'
|
|
67
|
+
const result = await mermaid.render(
|
|
68
|
+
`platform-mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
69
|
+
diagram,
|
|
70
|
+
)
|
|
71
|
+
return result.svg
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Replace stored mermaid blocks with rendered SVG (print/PDF/static HTML). */
|
|
75
|
+
export async function expandMermaidBlocksForPrint(
|
|
76
|
+
html: string,
|
|
77
|
+
options: ExpandMermaidBlocksOptions = {},
|
|
78
|
+
): Promise<string> {
|
|
79
|
+
const theme = options.theme ?? 'default'
|
|
80
|
+
const wrapperClass = options.wrapperClass ?? 'platform-print-mermaid'
|
|
81
|
+
const matches = [...html.matchAll(MERMAID_BLOCK_PATTERN)]
|
|
82
|
+
if (matches.length === 0) return html
|
|
83
|
+
|
|
84
|
+
const replacements: Array<{ start: number; end: number; text: string }> = []
|
|
85
|
+
|
|
86
|
+
for (const match of matches) {
|
|
87
|
+
const block = match[0]
|
|
88
|
+
const start = match.index ?? html.indexOf(block)
|
|
89
|
+
if (start < 0) continue
|
|
90
|
+
const source = extractMermaidSourceFromHtml(block)
|
|
91
|
+
if (!source) continue
|
|
92
|
+
try {
|
|
93
|
+
const svg = await renderMermaidPreviewSvg(source, { theme })
|
|
94
|
+
replacements.push({
|
|
95
|
+
start,
|
|
96
|
+
end: start + block.length,
|
|
97
|
+
text: `<figure data-type="mermaid"><div class="${wrapperClass}">${svg}</div></figure>`,
|
|
98
|
+
})
|
|
99
|
+
} catch {
|
|
100
|
+
// Keep source markup when render fails.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (replacements.length === 0) return html
|
|
105
|
+
|
|
106
|
+
replacements.sort((a, b) => b.start - a.start)
|
|
107
|
+
let result = html
|
|
108
|
+
for (const replacement of replacements) {
|
|
109
|
+
result =
|
|
110
|
+
result.slice(0, replacement.start) +
|
|
111
|
+
replacement.text +
|
|
112
|
+
result.slice(replacement.end)
|
|
113
|
+
}
|
|
114
|
+
return result
|
|
115
|
+
}
|