@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,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(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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 }
|