@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.
Files changed (45) hide show
  1. package/package.json +63 -0
  2. package/src/CollapsePanel.tsx +35 -0
  3. package/src/DocumentDiffView.tsx +130 -0
  4. package/src/DocumentEditor.test.ts +118 -0
  5. package/src/DocumentEditor.tsx +2631 -0
  6. package/src/alignedLineDiff.test.ts +52 -0
  7. package/src/alignedLineDiff.ts +67 -0
  8. package/src/collectionAssetSrc.ts +2 -0
  9. package/src/commentUtils.test.ts +350 -0
  10. package/src/commentUtils.ts +546 -0
  11. package/src/constants/toolKeys.ts +14 -0
  12. package/src/contentFormat.test.ts +27 -0
  13. package/src/contentFormat.ts +18 -0
  14. package/src/editorExtensions.ts +155 -0
  15. package/src/extensions/appliedChangeMark.ts +115 -0
  16. package/src/extensions/autoReviewPrompts.test.ts +529 -0
  17. package/src/extensions/autoReviewPrompts.ts +1442 -0
  18. package/src/extensions/collectionImage.ts +26 -0
  19. package/src/extensions/collectionImagePaste.ts +215 -0
  20. package/src/extensions/commentMark.ts +223 -0
  21. package/src/extensions/documentAssetIdentity.ts +54 -0
  22. package/src/extensions/figure.ts +92 -0
  23. package/src/extensions/footnote.ts +186 -0
  24. package/src/extensions/indexMarker.ts +88 -0
  25. package/src/extensions/mathEditing.ts +239 -0
  26. package/src/extensions/mermaidBlock.tsx +297 -0
  27. package/src/extensions/slashCommands.test.ts +170 -0
  28. package/src/extensions/slashCommands.ts +746 -0
  29. package/src/extensions/smartTypography.test.ts +74 -0
  30. package/src/extensions/smartTypography.ts +120 -0
  31. package/src/index.ts +137 -0
  32. package/src/insertCollectionImage.test.ts +60 -0
  33. package/src/insertCollectionImage.ts +63 -0
  34. package/src/insertInlineAssetKind.test.ts +67 -0
  35. package/src/insertInlineAssetKind.ts +49 -0
  36. package/src/mermaidPreview.test.ts +54 -0
  37. package/src/mermaidPreview.ts +115 -0
  38. package/src/styled.ts +676 -0
  39. package/src/toolbar/ToolBtn.tsx +63 -0
  40. package/src/toolbar/Toolbar.test.tsx +325 -0
  41. package/src/toolbar/Toolbar.tsx +624 -0
  42. package/src/toolbar/groups/HeadingGroup.tsx +153 -0
  43. package/src/toolbar/overlayTypes.ts +28 -0
  44. package/src/useEditorExtensions.ts +22 -0
  45. 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(/&lt;/g, '<')
42
+ .replace(/&gt;/g, '>')
43
+ .replace(/&amp;/g, '&')
44
+ .replace(/&quot;/g, '"')
45
+ .replace(/&#39;/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
+ }