@milkdown/crepe 7.12.1 → 7.13.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/lib/cjs/feature/block-edit/index.js +248 -229
- package/lib/cjs/feature/block-edit/index.js.map +1 -1
- package/lib/cjs/feature/latex/index.js +41 -3
- package/lib/cjs/feature/latex/index.js.map +1 -1
- package/lib/cjs/feature/placeholder/index.js.map +1 -1
- package/lib/cjs/feature/toolbar/index.js +213 -160
- package/lib/cjs/feature/toolbar/index.js.map +1 -1
- package/lib/cjs/index.js +408 -390
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/feature/block-edit/index.js +250 -231
- package/lib/esm/feature/block-edit/index.js.map +1 -1
- package/lib/esm/feature/latex/index.js +42 -4
- package/lib/esm/feature/latex/index.js.map +1 -1
- package/lib/esm/feature/placeholder/index.js.map +1 -1
- package/lib/esm/feature/toolbar/index.js +217 -164
- package/lib/esm/feature/toolbar/index.js.map +1 -1
- package/lib/esm/index.js +414 -396
- package/lib/esm/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/feature/block-edit/index.d.ts +78 -38
- package/lib/types/feature/block-edit/index.d.ts.map +1 -1
- package/lib/types/feature/block-edit/menu/config.d.ts +3 -2
- package/lib/types/feature/block-edit/menu/config.d.ts.map +1 -1
- package/lib/types/feature/block-edit/menu/utils.d.ts +2 -24
- package/lib/types/feature/block-edit/menu/utils.d.ts.map +1 -1
- package/lib/types/feature/latex/command.d.ts +2 -0
- package/lib/types/feature/latex/command.d.ts.map +1 -0
- package/lib/types/feature/latex/index.d.ts.map +1 -1
- package/lib/types/feature/toolbar/component.d.ts +1 -1
- package/lib/types/feature/toolbar/component.d.ts.map +1 -1
- package/lib/types/feature/toolbar/config.d.ts +12 -0
- package/lib/types/feature/toolbar/config.d.ts.map +1 -0
- package/lib/types/feature/toolbar/index.d.ts +3 -0
- package/lib/types/feature/toolbar/index.d.ts.map +1 -1
- package/lib/types/utils/checker.d.ts +4 -0
- package/lib/types/utils/checker.d.ts.map +1 -0
- package/lib/types/utils/group-builder.d.ts +43 -0
- package/lib/types/utils/group-builder.d.ts.map +1 -0
- package/lib/types/utils/index.d.ts +3 -3
- package/lib/types/utils/index.d.ts.map +1 -1
- package/lib/types/utils/types.d.ts +4 -0
- package/lib/types/utils/types.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/feature/block-edit/index.ts +78 -38
- package/src/feature/block-edit/menu/config.ts +306 -240
- package/src/feature/block-edit/menu/utils.ts +1 -106
- package/src/feature/latex/command.ts +48 -0
- package/src/feature/latex/index.ts +2 -0
- package/src/feature/toolbar/component.tsx +44 -194
- package/src/feature/toolbar/config.ts +136 -0
- package/src/feature/toolbar/index.ts +3 -0
- package/src/utils/checker.ts +11 -0
- package/src/utils/group-builder.ts +68 -0
- package/src/utils/index.ts +3 -11
- package/src/utils/types.ts +9 -0
- package/lib/types/feature/block-edit/menu/group-builder.d.ts +0 -29
- package/lib/types/feature/block-edit/menu/group-builder.d.ts.map +0 -1
- package/src/feature/block-edit/menu/group-builder.ts +0 -49
|
@@ -1,109 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Attrs, NodeType } from '@milkdown/kit/prose/model'
|
|
3
|
-
import type { Command, Transaction } from '@milkdown/kit/prose/state'
|
|
4
|
-
|
|
5
|
-
import { findWrapping } from '@milkdown/kit/prose/transform'
|
|
6
|
-
|
|
7
|
-
export interface MenuItem {
|
|
8
|
-
index: number
|
|
9
|
-
key: string
|
|
1
|
+
export type SlashMenuItem = {
|
|
10
2
|
label: string
|
|
11
3
|
icon: string
|
|
12
|
-
onRun: (ctx: Ctx) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type WithRange<T, HasIndex extends true | false = true> = HasIndex extends true
|
|
16
|
-
? T & { range: [start: number, end: number] }
|
|
17
|
-
: T
|
|
18
|
-
|
|
19
|
-
export type MenuItemGroup<HasIndex extends true | false = true> = WithRange<
|
|
20
|
-
{
|
|
21
|
-
key: string
|
|
22
|
-
label: string
|
|
23
|
-
items: HasIndex extends true ? MenuItem[] : Omit<MenuItem, 'index'>[]
|
|
24
|
-
},
|
|
25
|
-
HasIndex
|
|
26
|
-
>
|
|
27
|
-
|
|
28
|
-
export function clearRange(tr: Transaction) {
|
|
29
|
-
const { $from, $to } = tr.selection
|
|
30
|
-
const { pos: from } = $from
|
|
31
|
-
const { pos: to } = $to
|
|
32
|
-
tr = tr.deleteRange(from - $from.node().content.size, to)
|
|
33
|
-
return tr
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function setBlockType(
|
|
37
|
-
tr: Transaction,
|
|
38
|
-
nodeType: NodeType,
|
|
39
|
-
attrs: Attrs | null = null
|
|
40
|
-
) {
|
|
41
|
-
const { from, to } = tr.selection
|
|
42
|
-
return tr.setBlockType(from, to, nodeType, attrs)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function wrapInBlockType(
|
|
46
|
-
tr: Transaction,
|
|
47
|
-
nodeType: NodeType,
|
|
48
|
-
attrs: Attrs | null = null
|
|
49
|
-
) {
|
|
50
|
-
const { $from, $to } = tr.selection
|
|
51
|
-
|
|
52
|
-
const range = $from.blockRange($to)
|
|
53
|
-
const wrapping = range && findWrapping(range, nodeType, attrs)
|
|
54
|
-
if (!wrapping) return null
|
|
55
|
-
|
|
56
|
-
return tr.wrap(range, wrapping)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function addBlockType(
|
|
60
|
-
tr: Transaction,
|
|
61
|
-
nodeType: NodeType,
|
|
62
|
-
attrs: Attrs | null = null
|
|
63
|
-
) {
|
|
64
|
-
const node = nodeType.createAndFill(attrs)
|
|
65
|
-
if (!node) return null
|
|
66
|
-
|
|
67
|
-
return tr.replaceSelectionWith(node)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function clearContentAndSetBlockType(
|
|
71
|
-
nodeType: NodeType,
|
|
72
|
-
attrs: Attrs | null = null
|
|
73
|
-
): Command {
|
|
74
|
-
return (state, dispatch) => {
|
|
75
|
-
if (dispatch) {
|
|
76
|
-
const tr = setBlockType(clearRange(state.tr), nodeType, attrs)
|
|
77
|
-
dispatch(tr.scrollIntoView())
|
|
78
|
-
}
|
|
79
|
-
return true
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function clearContentAndWrapInBlockType(
|
|
84
|
-
nodeType: NodeType,
|
|
85
|
-
attrs: Attrs | null = null
|
|
86
|
-
): Command {
|
|
87
|
-
return (state, dispatch) => {
|
|
88
|
-
const tr = wrapInBlockType(clearRange(state.tr), nodeType, attrs)
|
|
89
|
-
if (!tr) return false
|
|
90
|
-
|
|
91
|
-
if (dispatch) dispatch(tr.scrollIntoView())
|
|
92
|
-
|
|
93
|
-
return true
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function clearContentAndAddBlockType(
|
|
98
|
-
nodeType: NodeType,
|
|
99
|
-
attrs: Attrs | null = null
|
|
100
|
-
): Command {
|
|
101
|
-
return (state, dispatch) => {
|
|
102
|
-
const tr = addBlockType(clearRange(state.tr), nodeType, attrs)
|
|
103
|
-
if (!tr) return false
|
|
104
|
-
|
|
105
|
-
if (dispatch) dispatch(tr.scrollIntoView())
|
|
106
|
-
|
|
107
|
-
return true
|
|
108
|
-
}
|
|
109
4
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Node } from '@milkdown/kit/prose/model'
|
|
2
|
+
|
|
3
|
+
import { findNodeInSelection } from '@milkdown/kit/prose'
|
|
4
|
+
import { NodeSelection, TextSelection } from '@milkdown/kit/prose/state'
|
|
5
|
+
import { $command } from '@milkdown/kit/utils'
|
|
6
|
+
|
|
7
|
+
import { mathInlineSchema } from './inline-latex'
|
|
8
|
+
|
|
9
|
+
export const toggleLatexCommand = $command('ToggleLatex', (ctx) => {
|
|
10
|
+
return () => (state, dispatch) => {
|
|
11
|
+
const {
|
|
12
|
+
hasNode: hasLatex,
|
|
13
|
+
pos: latexPos,
|
|
14
|
+
target: latexNode,
|
|
15
|
+
} = findNodeInSelection(state, mathInlineSchema.type(ctx))
|
|
16
|
+
|
|
17
|
+
const { selection, doc, tr } = state
|
|
18
|
+
if (!hasLatex) {
|
|
19
|
+
const text = doc.textBetween(selection.from, selection.to)
|
|
20
|
+
let _tr = tr.replaceSelectionWith(
|
|
21
|
+
mathInlineSchema.type(ctx).create({
|
|
22
|
+
value: text,
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
if (dispatch) {
|
|
26
|
+
dispatch(
|
|
27
|
+
_tr.setSelection(NodeSelection.create(_tr.doc, selection.from))
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { from, to } = selection
|
|
34
|
+
if (!latexNode || latexPos < 0) return false
|
|
35
|
+
|
|
36
|
+
let _tr = tr.delete(latexPos, latexPos + 1)
|
|
37
|
+
const content = (latexNode as Node).attrs.value
|
|
38
|
+
_tr = _tr.insertText(content, latexPos)
|
|
39
|
+
if (dispatch) {
|
|
40
|
+
dispatch(
|
|
41
|
+
_tr.setSelection(
|
|
42
|
+
TextSelection.create(_tr.doc, from, to + content.length - 1)
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
})
|
|
@@ -9,6 +9,7 @@ import { crepeFeatureConfig, useCrepeFeatures } from '../../core/slice'
|
|
|
9
9
|
import { CrepeFeature } from '../../feature'
|
|
10
10
|
import { confirmIcon } from '../../icons'
|
|
11
11
|
import { blockLatexSchema } from './block-latex'
|
|
12
|
+
import { toggleLatexCommand } from './command'
|
|
12
13
|
import { mathInlineSchema } from './inline-latex'
|
|
13
14
|
import { inlineLatexTooltip } from './inline-tooltip/tooltip'
|
|
14
15
|
import { LatexInlineTooltip } from './inline-tooltip/view'
|
|
@@ -59,6 +60,7 @@ export const latex: DefineFeature<LatexFeatureConfig> = (editor, config) => {
|
|
|
59
60
|
.use(mathInlineInputRule)
|
|
60
61
|
.use(mathBlockInputRule)
|
|
61
62
|
.use(blockLatexSchema)
|
|
63
|
+
.use(toggleLatexCommand)
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
function renderLatex(content: string, options?: KatexOptions) {
|
|
@@ -1,43 +1,21 @@
|
|
|
1
1
|
import type { Ctx } from '@milkdown/kit/ctx'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Selection } from '@milkdown/kit/prose/state'
|
|
3
3
|
|
|
4
4
|
import { Icon } from '@milkdown/kit/component'
|
|
5
|
-
import {
|
|
6
|
-
import { commandsCtx, editorViewCtx } from '@milkdown/kit/core'
|
|
7
|
-
import {
|
|
8
|
-
emphasisSchema,
|
|
9
|
-
inlineCodeSchema,
|
|
10
|
-
linkSchema,
|
|
11
|
-
strongSchema,
|
|
12
|
-
toggleEmphasisCommand,
|
|
13
|
-
toggleInlineCodeCommand,
|
|
14
|
-
toggleStrongCommand,
|
|
15
|
-
} from '@milkdown/kit/preset/commonmark'
|
|
16
|
-
import {
|
|
17
|
-
strikethroughSchema,
|
|
18
|
-
toggleStrikethroughCommand,
|
|
19
|
-
} from '@milkdown/kit/preset/gfm'
|
|
20
|
-
import {
|
|
21
|
-
NodeSelection,
|
|
22
|
-
TextSelection,
|
|
23
|
-
type Selection,
|
|
24
|
-
} from '@milkdown/kit/prose/state'
|
|
5
|
+
import { editorCtx, EditorStatus } from '@milkdown/kit/core'
|
|
25
6
|
import clsx from 'clsx'
|
|
26
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
defineComponent,
|
|
9
|
+
type Ref,
|
|
10
|
+
type ShallowRef,
|
|
11
|
+
h,
|
|
12
|
+
Fragment,
|
|
13
|
+
computed,
|
|
14
|
+
} from 'vue'
|
|
27
15
|
|
|
28
16
|
import type { ToolbarFeatureConfig } from '.'
|
|
29
17
|
|
|
30
|
-
import {
|
|
31
|
-
import { CrepeFeature } from '../../feature'
|
|
32
|
-
import {
|
|
33
|
-
boldIcon,
|
|
34
|
-
codeIcon,
|
|
35
|
-
functionsIcon,
|
|
36
|
-
italicIcon,
|
|
37
|
-
linkIcon,
|
|
38
|
-
strikethroughIcon,
|
|
39
|
-
} from '../../icons'
|
|
40
|
-
import { mathInlineSchema } from '../latex/inline-latex'
|
|
18
|
+
import { getGroups, type ToolbarItem } from './config'
|
|
41
19
|
|
|
42
20
|
h
|
|
43
21
|
Fragment
|
|
@@ -74,181 +52,53 @@ export const Toolbar = defineComponent<ToolbarProps>({
|
|
|
74
52
|
},
|
|
75
53
|
},
|
|
76
54
|
setup(props) {
|
|
77
|
-
const { ctx,
|
|
55
|
+
const { ctx, config } = props
|
|
78
56
|
|
|
79
57
|
const onClick = (fn: (ctx: Ctx) => void) => (e: MouseEvent) => {
|
|
80
58
|
e.preventDefault()
|
|
81
59
|
ctx && fn(ctx)
|
|
82
60
|
}
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return doc.rangeHasMark(selection.from, selection.to, mark)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const containsNode = (node: NodeType) => {
|
|
94
|
-
const selection = props.selection.value
|
|
95
|
-
if (!ctx || !selection) return false
|
|
96
|
-
const { state } = ctx.get(editorViewCtx)
|
|
97
|
-
if (!state) return false
|
|
98
|
-
const { doc } = state
|
|
99
|
-
if (selection instanceof NodeSelection) {
|
|
100
|
-
return selection.node.type === node
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const { from, to } = selection
|
|
62
|
+
function checkActive(checker: ToolbarItem['active']) {
|
|
63
|
+
// make sure the function subscribed to vue reactive
|
|
64
|
+
props.selection.value
|
|
65
|
+
// Check if the edtior is ready
|
|
66
|
+
const status = ctx.get(editorCtx).status
|
|
67
|
+
if (status !== EditorStatus.Created) return false
|
|
104
68
|
|
|
105
|
-
|
|
106
|
-
doc.nodesBetween(from, to, (n) => {
|
|
107
|
-
if (n.type === node) {
|
|
108
|
-
hasNode = true
|
|
109
|
-
return false
|
|
110
|
-
}
|
|
111
|
-
return true
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
return hasNode
|
|
69
|
+
return checker(ctx)
|
|
115
70
|
}
|
|
116
71
|
|
|
117
|
-
const
|
|
118
|
-
const isLatexEnabled = flags?.includes(CrepeFeature.Latex)
|
|
119
|
-
|
|
120
|
-
const toggleLatex = (ctx: Ctx) => {
|
|
121
|
-
const hasLatex = containsNode(mathInlineSchema.type(ctx))
|
|
122
|
-
const view = ctx.get(editorViewCtx)
|
|
123
|
-
const { selection, doc, tr } = view.state
|
|
124
|
-
if (!hasLatex) {
|
|
125
|
-
const text = doc.textBetween(selection.from, selection.to)
|
|
126
|
-
let _tr = tr.replaceSelectionWith(
|
|
127
|
-
mathInlineSchema.type(ctx).create({
|
|
128
|
-
value: text,
|
|
129
|
-
})
|
|
130
|
-
)
|
|
131
|
-
view.dispatch(
|
|
132
|
-
_tr.setSelection(NodeSelection.create(_tr.doc, selection.from))
|
|
133
|
-
)
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const { from, to } = selection
|
|
138
|
-
let pos = -1
|
|
139
|
-
let node: Node | null = null
|
|
140
|
-
doc.nodesBetween(from, to, (n, p) => {
|
|
141
|
-
if (node) return false
|
|
142
|
-
if (n.type === mathInlineSchema.type(ctx)) {
|
|
143
|
-
pos = p
|
|
144
|
-
node = n
|
|
145
|
-
return false
|
|
146
|
-
}
|
|
147
|
-
return true
|
|
148
|
-
})
|
|
149
|
-
if (!node || pos < 0) return
|
|
150
|
-
|
|
151
|
-
let _tr = tr.delete(pos, pos + 1)
|
|
152
|
-
const content = (node as Node).attrs.value
|
|
153
|
-
_tr = _tr.insertText(content, pos)
|
|
154
|
-
view.dispatch(
|
|
155
|
-
_tr.setSelection(
|
|
156
|
-
TextSelection.create(_tr.doc, from, to + content.length - 1)
|
|
157
|
-
)
|
|
158
|
-
)
|
|
159
|
-
}
|
|
72
|
+
const groupInfo = computed(() => getGroups(config, ctx))
|
|
160
73
|
|
|
161
74
|
return () => {
|
|
162
75
|
return (
|
|
163
76
|
<>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
})}
|
|
187
|
-
>
|
|
188
|
-
<Icon icon={config?.italicIcon ?? italicIcon} />
|
|
189
|
-
</button>
|
|
190
|
-
<button
|
|
191
|
-
type="button"
|
|
192
|
-
class={clsx(
|
|
193
|
-
'toolbar-item',
|
|
194
|
-
ctx && isActive(strikethroughSchema.type(ctx)) && 'active'
|
|
195
|
-
)}
|
|
196
|
-
onPointerdown={onClick((ctx) => {
|
|
197
|
-
const commands = ctx.get(commandsCtx)
|
|
198
|
-
commands.call(toggleStrikethroughCommand.key)
|
|
199
|
-
})}
|
|
200
|
-
>
|
|
201
|
-
<Icon icon={config?.strikethroughIcon ?? strikethroughIcon} />
|
|
202
|
-
</button>
|
|
203
|
-
<div class="divider"></div>
|
|
204
|
-
<button
|
|
205
|
-
type="button"
|
|
206
|
-
class={clsx(
|
|
207
|
-
'toolbar-item',
|
|
208
|
-
ctx && isActive(inlineCodeSchema.type(ctx)) && 'active'
|
|
209
|
-
)}
|
|
210
|
-
onPointerdown={onClick((ctx) => {
|
|
211
|
-
const commands = ctx.get(commandsCtx)
|
|
212
|
-
commands.call(toggleInlineCodeCommand.key)
|
|
213
|
-
})}
|
|
214
|
-
>
|
|
215
|
-
<Icon icon={config?.codeIcon ?? codeIcon} />
|
|
216
|
-
</button>
|
|
217
|
-
{isLatexEnabled && (
|
|
218
|
-
<button
|
|
219
|
-
type="button"
|
|
220
|
-
class={clsx(
|
|
221
|
-
'toolbar-item',
|
|
222
|
-
ctx && containsNode(mathInlineSchema.type(ctx)) && 'active'
|
|
223
|
-
)}
|
|
224
|
-
onPointerdown={onClick(toggleLatex)}
|
|
225
|
-
>
|
|
226
|
-
<Icon icon={config?.latexIcon ?? functionsIcon} />
|
|
227
|
-
</button>
|
|
228
|
-
)}
|
|
229
|
-
<button
|
|
230
|
-
type="button"
|
|
231
|
-
class={clsx(
|
|
232
|
-
'toolbar-item',
|
|
233
|
-
ctx && isActive(linkSchema.type(ctx)) && 'active'
|
|
234
|
-
)}
|
|
235
|
-
onPointerdown={onClick((ctx) => {
|
|
236
|
-
const view = ctx.get(editorViewCtx)
|
|
237
|
-
const { selection } = view.state
|
|
238
|
-
|
|
239
|
-
if (isActive(linkSchema.type(ctx))) {
|
|
240
|
-
ctx
|
|
241
|
-
.get(linkTooltipAPI.key)
|
|
242
|
-
.removeLink(selection.from, selection.to)
|
|
243
|
-
return
|
|
77
|
+
{groupInfo.value
|
|
78
|
+
.map((group) => {
|
|
79
|
+
return group.items.map((item) => {
|
|
80
|
+
return (
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class={clsx(
|
|
84
|
+
'toolbar-item',
|
|
85
|
+
ctx && checkActive(item.active) && 'active'
|
|
86
|
+
)}
|
|
87
|
+
onPointerdown={onClick(item.onRun)}
|
|
88
|
+
>
|
|
89
|
+
<Icon icon={item.icon} />
|
|
90
|
+
</button>
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
.reduce((acc, curr, index) => {
|
|
95
|
+
if (index === 0) {
|
|
96
|
+
acc.push(...curr)
|
|
97
|
+
} else {
|
|
98
|
+
acc.push(<div class="divider"></div>, ...curr)
|
|
244
99
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
hide?.()
|
|
248
|
-
})}
|
|
249
|
-
>
|
|
250
|
-
<Icon icon={config?.linkIcon ?? linkIcon} />
|
|
251
|
-
</button>
|
|
100
|
+
return acc
|
|
101
|
+
}, [])}
|
|
252
102
|
</>
|
|
253
103
|
)
|
|
254
104
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Ctx } from '@milkdown/kit/ctx'
|
|
2
|
+
|
|
3
|
+
import { toggleLinkCommand } from '@milkdown/kit/component/link-tooltip'
|
|
4
|
+
import { commandsCtx } from '@milkdown/kit/core'
|
|
5
|
+
import {
|
|
6
|
+
emphasisSchema,
|
|
7
|
+
inlineCodeSchema,
|
|
8
|
+
isMarkSelectedCommand,
|
|
9
|
+
isNodeSelectedCommand,
|
|
10
|
+
linkSchema,
|
|
11
|
+
strongSchema,
|
|
12
|
+
toggleEmphasisCommand,
|
|
13
|
+
toggleInlineCodeCommand,
|
|
14
|
+
toggleStrongCommand,
|
|
15
|
+
} from '@milkdown/kit/preset/commonmark'
|
|
16
|
+
import {
|
|
17
|
+
strikethroughSchema,
|
|
18
|
+
toggleStrikethroughCommand,
|
|
19
|
+
} from '@milkdown/kit/preset/gfm'
|
|
20
|
+
|
|
21
|
+
import type { ToolbarFeatureConfig } from '.'
|
|
22
|
+
|
|
23
|
+
import { CrepeFeature } from '..'
|
|
24
|
+
import { useCrepeFeatures } from '../../core/slice'
|
|
25
|
+
import {
|
|
26
|
+
boldIcon,
|
|
27
|
+
codeIcon,
|
|
28
|
+
functionsIcon,
|
|
29
|
+
italicIcon,
|
|
30
|
+
linkIcon,
|
|
31
|
+
strikethroughIcon,
|
|
32
|
+
} from '../../icons'
|
|
33
|
+
import { GroupBuilder } from '../../utils/group-builder'
|
|
34
|
+
import { toggleLatexCommand } from '../latex/command'
|
|
35
|
+
import { mathInlineSchema } from '../latex/inline-latex'
|
|
36
|
+
|
|
37
|
+
export type ToolbarItem = {
|
|
38
|
+
active: (ctx: Ctx) => boolean
|
|
39
|
+
icon: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getGroups(config?: ToolbarFeatureConfig, ctx?: Ctx) {
|
|
43
|
+
const groupBuilder = new GroupBuilder<ToolbarItem>()
|
|
44
|
+
|
|
45
|
+
groupBuilder
|
|
46
|
+
.addGroup('formatting', 'Formatting')
|
|
47
|
+
.addItem('bold', {
|
|
48
|
+
icon: config?.boldIcon ?? boldIcon,
|
|
49
|
+
active: (ctx) => {
|
|
50
|
+
const commands = ctx.get(commandsCtx)
|
|
51
|
+
return commands.call(isMarkSelectedCommand.key, strongSchema.type(ctx))
|
|
52
|
+
},
|
|
53
|
+
onRun: (ctx) => {
|
|
54
|
+
const commands = ctx.get(commandsCtx)
|
|
55
|
+
commands.call(toggleStrongCommand.key)
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
.addItem('italic', {
|
|
59
|
+
icon: config?.italicIcon ?? italicIcon,
|
|
60
|
+
active: (ctx) => {
|
|
61
|
+
const commands = ctx.get(commandsCtx)
|
|
62
|
+
return commands.call(
|
|
63
|
+
isMarkSelectedCommand.key,
|
|
64
|
+
emphasisSchema.type(ctx)
|
|
65
|
+
)
|
|
66
|
+
},
|
|
67
|
+
onRun: (ctx) => {
|
|
68
|
+
const commands = ctx.get(commandsCtx)
|
|
69
|
+
commands.call(toggleEmphasisCommand.key)
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
.addItem('strikethrough', {
|
|
73
|
+
icon: config?.strikethroughIcon ?? strikethroughIcon,
|
|
74
|
+
active: (ctx) => {
|
|
75
|
+
const commands = ctx.get(commandsCtx)
|
|
76
|
+
return commands.call(
|
|
77
|
+
isMarkSelectedCommand.key,
|
|
78
|
+
strikethroughSchema.type(ctx)
|
|
79
|
+
)
|
|
80
|
+
},
|
|
81
|
+
onRun: (ctx) => {
|
|
82
|
+
const commands = ctx.get(commandsCtx)
|
|
83
|
+
commands.call(toggleStrikethroughCommand.key)
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const functionGroup = groupBuilder.addGroup('function', 'Function')
|
|
88
|
+
functionGroup.addItem('code', {
|
|
89
|
+
icon: config?.codeIcon ?? codeIcon,
|
|
90
|
+
active: (ctx) => {
|
|
91
|
+
const commands = ctx.get(commandsCtx)
|
|
92
|
+
return commands.call(
|
|
93
|
+
isMarkSelectedCommand.key,
|
|
94
|
+
inlineCodeSchema.type(ctx)
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
onRun: (ctx) => {
|
|
98
|
+
const commands = ctx.get(commandsCtx)
|
|
99
|
+
commands.call(toggleInlineCodeCommand.key)
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const flags = ctx && useCrepeFeatures(ctx).get()
|
|
104
|
+
const isLatexEnabled = flags?.includes(CrepeFeature.Latex)
|
|
105
|
+
if (isLatexEnabled) {
|
|
106
|
+
functionGroup.addItem('latex', {
|
|
107
|
+
icon: config?.latexIcon ?? functionsIcon,
|
|
108
|
+
active: (ctx) => {
|
|
109
|
+
const commands = ctx.get(commandsCtx)
|
|
110
|
+
return commands.call(
|
|
111
|
+
isNodeSelectedCommand.key,
|
|
112
|
+
mathInlineSchema.type(ctx)
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
onRun: (ctx) => {
|
|
116
|
+
const commands = ctx.get(commandsCtx)
|
|
117
|
+
commands.call(toggleLatexCommand.key)
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
functionGroup.addItem('link', {
|
|
122
|
+
icon: config?.linkIcon ?? linkIcon,
|
|
123
|
+
active: (ctx) => {
|
|
124
|
+
const commands = ctx.get(commandsCtx)
|
|
125
|
+
return commands.call(isMarkSelectedCommand.key, linkSchema.type(ctx))
|
|
126
|
+
},
|
|
127
|
+
onRun: (ctx) => {
|
|
128
|
+
const commands = ctx.get(commandsCtx)
|
|
129
|
+
commands.call(toggleLinkCommand.key)
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
config?.buildToolbar?.(groupBuilder)
|
|
134
|
+
|
|
135
|
+
return groupBuilder.build()
|
|
136
|
+
}
|
|
@@ -10,7 +10,9 @@ import { TooltipProvider, tooltipFactory } from '@milkdown/kit/plugin/tooltip'
|
|
|
10
10
|
import { TextSelection } from '@milkdown/kit/prose/state'
|
|
11
11
|
import { createApp, ref, shallowRef, type App, type ShallowRef } from 'vue'
|
|
12
12
|
|
|
13
|
+
import type { GroupBuilder } from '../../utils'
|
|
13
14
|
import type { DefineFeature } from '../shared'
|
|
15
|
+
import type { ToolbarItem } from './config'
|
|
14
16
|
|
|
15
17
|
import { crepeFeatureConfig } from '../../core/slice'
|
|
16
18
|
import { CrepeFeature } from '../../feature'
|
|
@@ -23,6 +25,7 @@ interface ToolbarConfig {
|
|
|
23
25
|
linkIcon: string
|
|
24
26
|
strikethroughIcon: string
|
|
25
27
|
latexIcon: string
|
|
28
|
+
buildToolbar: (builder: GroupBuilder<ToolbarItem>) => void
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export type ToolbarFeatureConfig = Partial<ToolbarConfig>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Selection } from '@milkdown/kit/prose/state'
|
|
2
|
+
|
|
3
|
+
export function isInCodeBlock(selection: Selection) {
|
|
4
|
+
const type = selection.$from.parent.type
|
|
5
|
+
return type.name === 'code_block'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isInList(selection: Selection) {
|
|
9
|
+
const type = selection.$from.node(selection.$from.depth - 1)?.type
|
|
10
|
+
return type?.name === 'list_item'
|
|
11
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Ctx } from '@milkdown/kit/ctx'
|
|
2
|
+
|
|
3
|
+
export type MenuItem<T> = {
|
|
4
|
+
index: number
|
|
5
|
+
key: string
|
|
6
|
+
onRun: (ctx: Ctx) => void
|
|
7
|
+
} & T
|
|
8
|
+
|
|
9
|
+
type WithRange<T, HasIndex extends true | false = true> = HasIndex extends true
|
|
10
|
+
? T & { range: [start: number, end: number] }
|
|
11
|
+
: T
|
|
12
|
+
|
|
13
|
+
export type MenuItemGroup<T, HasIndex extends true | false = true> = WithRange<
|
|
14
|
+
{
|
|
15
|
+
key: string
|
|
16
|
+
label: string
|
|
17
|
+
items: HasIndex extends true ? MenuItem<T>[] : Omit<MenuItem<T>, 'index'>[]
|
|
18
|
+
},
|
|
19
|
+
HasIndex
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
export class GroupBuilder<T> {
|
|
23
|
+
#groups: MenuItemGroup<T, false>[] = []
|
|
24
|
+
|
|
25
|
+
clear = () => {
|
|
26
|
+
this.#groups = []
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#getGroupInstance = (group: MenuItemGroup<T, false>) => {
|
|
31
|
+
const groupInstance = {
|
|
32
|
+
group,
|
|
33
|
+
addItem: (key: string, item: Omit<MenuItem<T>, 'key' | 'index'>) => {
|
|
34
|
+
const data = { ...item, key } as MenuItem<T>
|
|
35
|
+
group.items.push(data)
|
|
36
|
+
return groupInstance
|
|
37
|
+
},
|
|
38
|
+
clear: () => {
|
|
39
|
+
group.items = []
|
|
40
|
+
return groupInstance
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
return groupInstance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
addGroup = (key: string, label: string) => {
|
|
47
|
+
const items: Omit<MenuItem<T>, 'index'>[] = []
|
|
48
|
+
const group: MenuItemGroup<T, false> = {
|
|
49
|
+
key,
|
|
50
|
+
label,
|
|
51
|
+
items,
|
|
52
|
+
}
|
|
53
|
+
this.#groups.push(group)
|
|
54
|
+
|
|
55
|
+
return this.#getGroupInstance(group)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getGroup = (key: string) => {
|
|
59
|
+
const group = this.#groups.find((group) => group.key === key)
|
|
60
|
+
if (!group) throw new Error(`Group with key ${key} not found`)
|
|
61
|
+
|
|
62
|
+
return this.#getGroupInstance(group)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
build = () => {
|
|
66
|
+
return this.#groups
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
const type = selection.$from.parent.type
|
|
5
|
-
return type.name === 'code_block'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function isInList(selection: Selection) {
|
|
9
|
-
const type = selection.$from.node(selection.$from.depth - 1)?.type
|
|
10
|
-
return type?.name === 'list_item'
|
|
11
|
-
}
|
|
1
|
+
export * from './checker'
|
|
2
|
+
export * from './group-builder'
|
|
3
|
+
export * from './types'
|