@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.
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,153 +1,175 @@
1
- import {
2
- useEffect,
3
- useRef,
4
- useState,
5
- type CSSProperties,
6
- type ReactElement,
7
- } from 'react'
8
- import { useCurrentEditor, useEditorState } from '@tiptap/react'
9
- import { styled } from 'styled-components'
10
- import { DownOutlined } from '@ant-design/icons'
11
- import { ToolBtn } from '../ToolBtn.js'
12
- import { CollapsePanel } from '../../CollapsePanel.js'
13
-
14
- interface HeadingOption {
15
- label: string
16
- level: 0 | 1 | 2 | 3
17
- style?: CSSProperties
18
- }
19
-
20
- const OPTIONS: HeadingOption[] = [
21
- { label: 'Normal text', level: 0, style: { fontSize: 13 } },
22
- { label: 'Title', level: 1, style: { fontSize: 22, fontWeight: 500 } },
23
- { label: 'Heading', level: 2, style: { fontSize: 18, fontWeight: 500 } },
24
- { label: 'Subheading', level: 3, style: { fontSize: 15, fontWeight: 500 } },
25
- ]
26
-
27
- export function HeadingGroup(): ReactElement {
28
- const { editor } = useCurrentEditor()
29
- const isH1 = useEditorState({
30
- editor,
31
- selector: ctx => ctx.editor?.isActive('heading', { level: 1 }) ?? false,
32
- })
33
- const isH2 = useEditorState({
34
- editor,
35
- selector: ctx => ctx.editor?.isActive('heading', { level: 2 }) ?? false,
36
- })
37
- const isH3 = useEditorState({
38
- editor,
39
- selector: ctx => ctx.editor?.isActive('heading', { level: 3 }) ?? false,
40
- })
41
-
42
- const [open, setOpen] = useState(false)
43
- const wrapperRef = useRef<HTMLDivElement>(null)
44
-
45
- const activeLevel = (isH1 && 1) || (isH2 && 2) || (isH3 && 3) || 0
46
- const current = OPTIONS.find(o => o.level === activeLevel) ?? OPTIONS[0]
47
-
48
- useEffect(() => {
49
- if (!open) return
50
- const onDown = (e: MouseEvent) => {
51
- if (!wrapperRef.current?.contains(e.target as Node)) setOpen(false)
52
- }
53
- document.addEventListener('mousedown', onDown)
54
- return () => document.removeEventListener('mousedown', onDown)
55
- }, [open])
56
-
57
- const apply = (level: 0 | 1 | 2 | 3) => {
58
- setOpen(false)
59
- if (!editor) return
60
- if (level === 0) editor.chain().focus().setParagraph().run()
61
- else editor.chain().focus().toggleHeading({ level }).run()
62
- }
63
-
64
- return (
65
- <Wrapper ref={wrapperRef}>
66
- <Trigger
67
- $active={activeLevel !== 0}
68
- onClick={() => setOpen(o => !o)}
69
- title="Text style"
70
- type="button"
71
- >
72
- <span>{current.label}</span>
73
- <DownOutlined />
74
- </Trigger>
75
-
76
- <Panel open={open}>
77
- {OPTIONS.map(opt => (
78
- <Option
79
- key={opt.level}
80
- $active={opt.level === activeLevel}
81
- style={opt.style}
82
- onClick={() => apply(opt.level)}
83
- type="button"
84
- >
85
- {opt.label}
86
- </Option>
87
- ))}
88
- </Panel>
89
- </Wrapper>
90
- )
91
- }
92
-
93
- const Wrapper = styled.div`
94
- position: relative;
95
- `
96
-
97
- const Trigger = styled(ToolBtn)`
98
- gap: 6px;
99
- min-width: 128px;
100
- padding: 0 8px 0 10px;
101
-
102
- > span:first-child {
103
- flex: 1;
104
- text-align: left;
105
- }
106
-
107
- .anticon {
108
- font-size: 10px;
109
- opacity: 0.6;
110
- }
111
- `
112
-
113
- const Panel = styled(CollapsePanel)`
114
- background: var(--platform-colors-surface);
115
- border: 1px solid var(--platform-colors-border);
116
- border-radius: 6px;
117
- box-shadow: 0 6px 18px rgb(from var(--platform-colors-text) r g b / 12%);
118
- left: 0;
119
- min-width: 180px;
120
- padding: 0;
121
- position: absolute;
122
- top: calc(100% + 4px);
123
- z-index: 20;
124
-
125
- &[data-open='false'] {
126
- border: none;
127
- box-shadow: none;
128
- pointer-events: none;
129
- }
130
- `
131
-
132
- const Option = styled.button<{ $active?: boolean }>`
133
- align-items: center;
134
- background: ${({ $active }) =>
135
- $active
136
- ? 'rgb(from var(--platform-colors-accent) r g b / 12%)'
137
- : 'transparent'};
138
- border: none;
139
- border-radius: 4px;
140
- color: ${({ $active }) =>
141
- $active ? 'var(--platform-colors-accent)' : 'var(--platform-colors-text)'};
142
- cursor: pointer;
143
- display: flex;
144
- font-family: var(--platform-typography-font-family);
145
- padding: 6px 10px;
146
- text-align: left;
147
- transition: background 0.12s ease;
148
- width: 100%;
149
-
150
- &:hover {
151
- background: rgb(from var(--platform-colors-text) r g b / 8%);
152
- }
153
- `
1
+ import {
2
+ useEffect,
3
+ useRef,
4
+ useState,
5
+ type CSSProperties,
6
+ type ReactElement,
7
+ } from 'react'
8
+ import { useCurrentEditor, useEditorState } from '@tiptap/react'
9
+ import { styled } from 'styled-components'
10
+ import { DownOutlined } from '@ant-design/icons'
11
+ import { ToolBtn } from '../ToolBtn.js'
12
+ import { CollapsePanel } from '../../CollapsePanel.js'
13
+
14
+ interface HeadingOption {
15
+ label: string
16
+ level: 0 | 1 | 2 | 3
17
+ style?: CSSProperties
18
+ }
19
+
20
+ const OPTIONS: HeadingOption[] = [
21
+ { label: 'Normal text', level: 0, style: { fontSize: 13 } },
22
+ { label: 'Title', level: 1, style: { fontSize: 22, fontWeight: 500 } },
23
+ { label: 'Heading', level: 2, style: { fontSize: 18, fontWeight: 500 } },
24
+ { label: 'Subheading', level: 3, style: { fontSize: 15, fontWeight: 500 } },
25
+ ]
26
+
27
+ export function HeadingGroup(): ReactElement {
28
+ const { editor } = useCurrentEditor()
29
+ const isH1 = useEditorState({
30
+ editor,
31
+ selector: ctx => ctx.editor?.isActive('heading', { level: 1 }) ?? false,
32
+ })
33
+ const isH2 = useEditorState({
34
+ editor,
35
+ selector: ctx => ctx.editor?.isActive('heading', { level: 2 }) ?? false,
36
+ })
37
+ const isH3 = useEditorState({
38
+ editor,
39
+ selector: ctx => ctx.editor?.isActive('heading', { level: 3 }) ?? false,
40
+ })
41
+
42
+ const [open, setOpen] = useState(false)
43
+ const wrapperRef = useRef<HTMLDivElement>(null)
44
+
45
+ const activeLevel = (isH1 && 1) || (isH2 && 2) || (isH3 && 3) || 0
46
+ const current = OPTIONS.find(o => o.level === activeLevel) ?? OPTIONS[0]
47
+
48
+ useEffect(() => {
49
+ if (!open) return
50
+ const onDown = (e: MouseEvent) => {
51
+ if (!wrapperRef.current?.contains(e.target as Node)) setOpen(false)
52
+ }
53
+ document.addEventListener('mousedown', onDown)
54
+ return () => document.removeEventListener('mousedown', onDown)
55
+ }, [open])
56
+
57
+ const apply = (level: 0 | 1 | 2 | 3) => {
58
+ setOpen(false)
59
+ if (!editor) return
60
+ if (level === 0) editor.chain().focus().setParagraph().run()
61
+ else editor.chain().focus().toggleHeading({ level }).run()
62
+ }
63
+
64
+ return (
65
+ <Wrapper ref={wrapperRef}>
66
+ <Trigger
67
+ $active={activeLevel !== 0}
68
+ onClick={() => setOpen(o => !o)}
69
+ title="Text style"
70
+ type="button"
71
+ >
72
+ <span>{current.label}</span>
73
+ <DownOutlined />
74
+ </Trigger>
75
+
76
+ <Panel open={open}>
77
+ {OPTIONS.map(opt => (
78
+ <Option
79
+ key={opt.level}
80
+ $active={opt.level === activeLevel}
81
+ style={opt.style}
82
+ onClick={() => apply(opt.level)}
83
+ type="button"
84
+ >
85
+ {opt.label}
86
+ </Option>
87
+ ))}
88
+ </Panel>
89
+ </Wrapper>
90
+ )
91
+ }
92
+
93
+ const Wrapper = styled.div`
94
+ position: relative;
95
+ `
96
+
97
+ const Trigger = styled(ToolBtn)`
98
+ gap: 6px;
99
+ min-width: 128px;
100
+ padding: 0 8px 0 10px;
101
+
102
+ > span:first-child {
103
+ flex: 1;
104
+ text-align: left;
105
+ }
106
+
107
+ .anticon {
108
+ font-size: 10px;
109
+ opacity: 0.6;
110
+ }
111
+ `
112
+
113
+ const Panel = styled(CollapsePanel)`
114
+ background: var(
115
+ --editor-toolbar-panel,
116
+ var(--editor-paper, var(--platform-colors-surface))
117
+ );
118
+ border: 1px solid
119
+ var(
120
+ --editor-toolbar-border,
121
+ var(--editor-paper-border, var(--platform-colors-border))
122
+ );
123
+ border-radius: 6px;
124
+ box-shadow: 0 6px 18px
125
+ rgb(
126
+ from var(
127
+ --editor-toolbar-text,
128
+ var(--editor-paper-text, var(--platform-colors-text))
129
+ )
130
+ r g b / 12%
131
+ );
132
+ left: 0;
133
+ min-width: 180px;
134
+ padding: 0;
135
+ position: absolute;
136
+ top: calc(100% + 4px);
137
+ z-index: 20;
138
+
139
+ &[data-open='false'] {
140
+ border: none;
141
+ box-shadow: none;
142
+ pointer-events: none;
143
+ }
144
+ `
145
+
146
+ const Option = styled.button<{ $active?: boolean }>`
147
+ align-items: center;
148
+ background: ${({ $active }) =>
149
+ $active
150
+ ? 'rgb(from var(--editor-toolbar-text, var(--editor-paper-text, var(--platform-colors-text))) r g b / 8%)'
151
+ : 'transparent'};
152
+ border: none;
153
+ border-radius: 4px;
154
+ color: var(
155
+ --editor-toolbar-text,
156
+ var(--editor-paper-text, var(--platform-colors-text))
157
+ );
158
+ cursor: pointer;
159
+ display: flex;
160
+ font-family: var(--platform-typography-font-family);
161
+ padding: 6px 10px;
162
+ text-align: left;
163
+ transition: background 0.12s ease;
164
+ width: 100%;
165
+
166
+ &:hover {
167
+ background: rgb(
168
+ from var(
169
+ --editor-toolbar-text,
170
+ var(--editor-paper-text, var(--platform-colors-text))
171
+ )
172
+ r g b / 10%
173
+ );
174
+ }
175
+ `
@@ -1,28 +1,28 @@
1
- import type { ComponentType } from 'react'
2
- import type { CommentType } from '../commentUtils.js'
3
-
4
- export interface EditorLinkPromptProps {
5
- open: boolean
6
- value: string
7
- onChange: (value: string) => void
8
- onConfirm: () => void
9
- onCancel: () => void
10
- }
11
-
12
- export interface EditorCommentPromptProps {
13
- open: boolean
14
- value: string
15
- commentType: CommentType
16
- commentTypeOptions: Array<{ value: CommentType; label: string }>
17
- onChange: (value: string) => void
18
- onCommentTypeChange: (value: CommentType) => void
19
- onConfirm: () => void
20
- onCancel: () => void
21
- confirmDisabled?: boolean
22
- }
23
-
24
- /** Host-provided dialogs for toolbar prompts (use platform-ui overlays in apps). */
25
- export interface EditorToolbarOverlays {
26
- LinkPrompt: ComponentType<EditorLinkPromptProps>
27
- CommentPrompt: ComponentType<EditorCommentPromptProps>
28
- }
1
+ import type { ComponentType } from 'react'
2
+ import type { CommentType } from '../commentUtils.js'
3
+
4
+ export interface EditorLinkPromptProps {
5
+ open: boolean
6
+ value: string
7
+ onChange: (value: string) => void
8
+ onConfirm: () => void
9
+ onCancel: () => void
10
+ }
11
+
12
+ export interface EditorCommentPromptProps {
13
+ open: boolean
14
+ value: string
15
+ commentType: CommentType
16
+ commentTypeOptions: Array<{ value: CommentType; label: string }>
17
+ onChange: (value: string) => void
18
+ onCommentTypeChange: (value: CommentType) => void
19
+ onConfirm: () => void
20
+ onCancel: () => void
21
+ confirmDisabled?: boolean
22
+ }
23
+
24
+ /** Host-provided dialogs for toolbar prompts (use platform-ui overlays in apps). */
25
+ export interface EditorToolbarOverlays {
26
+ LinkPrompt: ComponentType<EditorLinkPromptProps>
27
+ CommentPrompt: ComponentType<EditorCommentPromptProps>
28
+ }
@@ -1,22 +1,22 @@
1
- import { useMemo } from 'react'
2
- import {
3
- buildExtensions,
4
- type BuildExtensionsOptions,
5
- type EditorFeatures,
6
- } from './editorExtensions.js'
7
- import type { Extensions } from '@tiptap/react'
8
-
9
- export function useEditorExtensions(
10
- opts: {
11
- placeholder?: string
12
- features?: EditorFeatures
13
- } = {},
14
- ): Extensions {
15
- const { placeholder, features } = opts
16
- return useMemo(
17
- () => buildExtensions({ placeholder, features }),
18
- [placeholder, features],
19
- )
20
- }
21
-
22
- export type { BuildExtensionsOptions, EditorFeatures }
1
+ import { useMemo } from 'react'
2
+ import {
3
+ buildExtensions,
4
+ type BuildExtensionsOptions,
5
+ type EditorFeatures,
6
+ } from './editorExtensions.js'
7
+ import type { Extensions } from '@tiptap/react'
8
+
9
+ export function useEditorExtensions(
10
+ opts: {
11
+ placeholder?: string
12
+ features?: EditorFeatures
13
+ } = {},
14
+ ): Extensions {
15
+ const { placeholder, features } = opts
16
+ return useMemo(
17
+ () => buildExtensions({ placeholder, features }),
18
+ [placeholder, features],
19
+ )
20
+ }
21
+
22
+ export type { BuildExtensionsOptions, EditorFeatures }