@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.
Files changed (58) hide show
  1. package/lib/cjs/feature/block-edit/index.js +248 -229
  2. package/lib/cjs/feature/block-edit/index.js.map +1 -1
  3. package/lib/cjs/feature/latex/index.js +41 -3
  4. package/lib/cjs/feature/latex/index.js.map +1 -1
  5. package/lib/cjs/feature/placeholder/index.js.map +1 -1
  6. package/lib/cjs/feature/toolbar/index.js +213 -160
  7. package/lib/cjs/feature/toolbar/index.js.map +1 -1
  8. package/lib/cjs/index.js +408 -390
  9. package/lib/cjs/index.js.map +1 -1
  10. package/lib/esm/feature/block-edit/index.js +250 -231
  11. package/lib/esm/feature/block-edit/index.js.map +1 -1
  12. package/lib/esm/feature/latex/index.js +42 -4
  13. package/lib/esm/feature/latex/index.js.map +1 -1
  14. package/lib/esm/feature/placeholder/index.js.map +1 -1
  15. package/lib/esm/feature/toolbar/index.js +217 -164
  16. package/lib/esm/feature/toolbar/index.js.map +1 -1
  17. package/lib/esm/index.js +414 -396
  18. package/lib/esm/index.js.map +1 -1
  19. package/lib/tsconfig.tsbuildinfo +1 -1
  20. package/lib/types/feature/block-edit/index.d.ts +78 -38
  21. package/lib/types/feature/block-edit/index.d.ts.map +1 -1
  22. package/lib/types/feature/block-edit/menu/config.d.ts +3 -2
  23. package/lib/types/feature/block-edit/menu/config.d.ts.map +1 -1
  24. package/lib/types/feature/block-edit/menu/utils.d.ts +2 -24
  25. package/lib/types/feature/block-edit/menu/utils.d.ts.map +1 -1
  26. package/lib/types/feature/latex/command.d.ts +2 -0
  27. package/lib/types/feature/latex/command.d.ts.map +1 -0
  28. package/lib/types/feature/latex/index.d.ts.map +1 -1
  29. package/lib/types/feature/toolbar/component.d.ts +1 -1
  30. package/lib/types/feature/toolbar/component.d.ts.map +1 -1
  31. package/lib/types/feature/toolbar/config.d.ts +12 -0
  32. package/lib/types/feature/toolbar/config.d.ts.map +1 -0
  33. package/lib/types/feature/toolbar/index.d.ts +3 -0
  34. package/lib/types/feature/toolbar/index.d.ts.map +1 -1
  35. package/lib/types/utils/checker.d.ts +4 -0
  36. package/lib/types/utils/checker.d.ts.map +1 -0
  37. package/lib/types/utils/group-builder.d.ts +43 -0
  38. package/lib/types/utils/group-builder.d.ts.map +1 -0
  39. package/lib/types/utils/index.d.ts +3 -3
  40. package/lib/types/utils/index.d.ts.map +1 -1
  41. package/lib/types/utils/types.d.ts +4 -0
  42. package/lib/types/utils/types.d.ts.map +1 -0
  43. package/package.json +2 -2
  44. package/src/feature/block-edit/index.ts +78 -38
  45. package/src/feature/block-edit/menu/config.ts +306 -240
  46. package/src/feature/block-edit/menu/utils.ts +1 -106
  47. package/src/feature/latex/command.ts +48 -0
  48. package/src/feature/latex/index.ts +2 -0
  49. package/src/feature/toolbar/component.tsx +44 -194
  50. package/src/feature/toolbar/config.ts +136 -0
  51. package/src/feature/toolbar/index.ts +3 -0
  52. package/src/utils/checker.ts +11 -0
  53. package/src/utils/group-builder.ts +68 -0
  54. package/src/utils/index.ts +3 -11
  55. package/src/utils/types.ts +9 -0
  56. package/lib/types/feature/block-edit/menu/group-builder.d.ts +0 -29
  57. package/lib/types/feature/block-edit/menu/group-builder.d.ts.map +0 -1
  58. package/src/feature/block-edit/menu/group-builder.ts +0 -49
@@ -1,109 +1,4 @@
1
- import type { Ctx } from '@milkdown/kit/ctx'
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 { MarkType, NodeType, Node } from '@milkdown/kit/prose/model'
2
+ import type { Selection } from '@milkdown/kit/prose/state'
3
3
 
4
4
  import { Icon } from '@milkdown/kit/component'
5
- import { linkTooltipAPI } from '@milkdown/kit/component/link-tooltip'
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 { defineComponent, type Ref, type ShallowRef, h, Fragment } from 'vue'
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 { useCrepeFeatures } from '../../core/slice'
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, hide, config } = props
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
- const isActive = (mark: MarkType) => {
85
- const selection = props.selection.value
86
- if (!ctx || !selection) return false
87
- const { state } = ctx.get(editorViewCtx)
88
- if (!state) return false
89
- const { doc } = state
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
- let hasNode = false
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 flags = useCrepeFeatures(ctx).get()
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
- <button
165
- type="button"
166
- class={clsx(
167
- 'toolbar-item',
168
- ctx && isActive(strongSchema.type(ctx)) && 'active'
169
- )}
170
- onPointerdown={onClick((ctx) => {
171
- const commands = ctx.get(commandsCtx)
172
- commands.call(toggleStrongCommand.key)
173
- })}
174
- >
175
- <Icon icon={config?.boldIcon ?? boldIcon} />
176
- </button>
177
- <button
178
- type="button"
179
- class={clsx(
180
- 'toolbar-item',
181
- ctx && isActive(emphasisSchema.type(ctx)) && 'active'
182
- )}
183
- onPointerdown={onClick((ctx) => {
184
- const commands = ctx.get(commandsCtx)
185
- commands.call(toggleEmphasisCommand.key)
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
- ctx.get(linkTooltipAPI.key).addLink(selection.from, selection.to)
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
+ }
@@ -1,11 +1,3 @@
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
- }
1
+ export * from './checker'
2
+ export * from './group-builder'
3
+ export * from './types'