@milkdown/preset-commonmark 6.5.4 → 7.0.0-next.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 (154) hide show
  1. package/lib/composed/commands.d.ts +3 -0
  2. package/lib/composed/commands.d.ts.map +1 -0
  3. package/lib/composed/index.d.ts +6 -0
  4. package/lib/composed/index.d.ts.map +1 -0
  5. package/lib/composed/inputrules.d.ts +3 -0
  6. package/lib/composed/inputrules.d.ts.map +1 -0
  7. package/lib/composed/keymap.d.ts +3 -0
  8. package/lib/composed/keymap.d.ts.map +1 -0
  9. package/lib/composed/plugins.d.ts +3 -0
  10. package/lib/composed/plugins.d.ts.map +1 -0
  11. package/lib/composed/schema.d.ts +3 -0
  12. package/lib/composed/schema.d.ts.map +1 -0
  13. package/lib/index.d.ts +4 -29
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.es.js +1099 -1386
  16. package/lib/index.es.js.map +1 -1
  17. package/lib/mark/emphasis.d.ts +5 -0
  18. package/lib/mark/emphasis.d.ts.map +1 -0
  19. package/lib/mark/index.d.ts +3 -4
  20. package/lib/mark/index.d.ts.map +1 -1
  21. package/lib/mark/inline-code.d.ts +5 -0
  22. package/lib/mark/inline-code.d.ts.map +1 -0
  23. package/lib/mark/link.d.ts +8 -10
  24. package/lib/mark/link.d.ts.map +1 -1
  25. package/lib/mark/strong.d.ts +4 -2
  26. package/lib/mark/strong.d.ts.map +1 -1
  27. package/lib/node/blockquote.d.ts +6 -2
  28. package/lib/node/blockquote.d.ts.map +1 -1
  29. package/lib/node/bullet-list.d.ts +5 -2
  30. package/lib/node/bullet-list.d.ts.map +1 -1
  31. package/lib/node/code-block.d.ts +10 -0
  32. package/lib/node/code-block.d.ts.map +1 -0
  33. package/lib/node/doc.d.ts +1 -1
  34. package/lib/node/doc.d.ts.map +1 -1
  35. package/lib/node/hardbreak.d.ts +4 -6
  36. package/lib/node/hardbreak.d.ts.map +1 -1
  37. package/lib/node/heading.d.ts +7 -12
  38. package/lib/node/heading.d.ts.map +1 -1
  39. package/lib/node/hr.d.ts +4 -2
  40. package/lib/node/hr.d.ts.map +1 -1
  41. package/lib/node/image.d.ts +10 -11
  42. package/lib/node/image.d.ts.map +1 -1
  43. package/lib/node/index.d.ts +6 -8
  44. package/lib/node/index.d.ts.map +1 -1
  45. package/lib/node/list-item.d.ts +6 -7
  46. package/lib/node/list-item.d.ts.map +1 -1
  47. package/lib/node/ordered-list.d.ts +5 -2
  48. package/lib/node/ordered-list.d.ts.map +1 -1
  49. package/lib/node/paragraph.d.ts +4 -2
  50. package/lib/node/paragraph.d.ts.map +1 -1
  51. package/lib/node/text.d.ts +1 -1
  52. package/lib/node/text.d.ts.map +1 -1
  53. package/lib/plugin/hardbreak-clear-mark-plugin.d.ts +2 -0
  54. package/lib/plugin/hardbreak-clear-mark-plugin.d.ts.map +1 -0
  55. package/lib/plugin/hardbreak-filter-plugin.d.ts +3 -0
  56. package/lib/plugin/hardbreak-filter-plugin.d.ts.map +1 -0
  57. package/lib/plugin/index.d.ts +9 -3
  58. package/lib/plugin/index.d.ts.map +1 -1
  59. package/lib/plugin/inline-nodes-cursor-plugin.d.ts +2 -0
  60. package/lib/plugin/inline-nodes-cursor-plugin.d.ts.map +1 -0
  61. package/lib/plugin/{inline-sync → inline-sync-plugin}/config.d.ts +2 -2
  62. package/lib/plugin/inline-sync-plugin/config.d.ts.map +1 -0
  63. package/lib/plugin/{inline-sync → inline-sync-plugin}/context.d.ts +1 -2
  64. package/lib/plugin/inline-sync-plugin/context.d.ts.map +1 -0
  65. package/lib/plugin/inline-sync-plugin/index.d.ts +3 -0
  66. package/lib/plugin/inline-sync-plugin/index.d.ts.map +1 -0
  67. package/lib/plugin/inline-sync-plugin/inline-sync-plugin.d.ts +2 -0
  68. package/lib/plugin/inline-sync-plugin/inline-sync-plugin.d.ts.map +1 -0
  69. package/lib/plugin/{inline-sync → inline-sync-plugin}/regexp.d.ts +0 -0
  70. package/lib/plugin/inline-sync-plugin/regexp.d.ts.map +1 -0
  71. package/lib/plugin/{inline-sync → inline-sync-plugin}/replacer.d.ts +1 -1
  72. package/lib/plugin/inline-sync-plugin/replacer.d.ts.map +1 -0
  73. package/lib/plugin/{inline-sync → inline-sync-plugin}/utils.d.ts +0 -0
  74. package/lib/plugin/inline-sync-plugin/utils.d.ts.map +1 -0
  75. package/lib/plugin/remark-add-order-in-list-plugin.d.ts +2 -0
  76. package/lib/plugin/remark-add-order-in-list-plugin.d.ts.map +1 -0
  77. package/lib/plugin/remark-inline-link-plugin.d.ts +2 -0
  78. package/lib/plugin/remark-inline-link-plugin.d.ts.map +1 -0
  79. package/lib/plugin/remark-line-break.d.ts +2 -0
  80. package/lib/plugin/remark-line-break.d.ts.map +1 -0
  81. package/lib/plugin/sync-heading-id-plugin.d.ts +2 -0
  82. package/lib/plugin/sync-heading-id-plugin.d.ts.map +1 -0
  83. package/lib/plugin/sync-list-order-plugin.d.ts +2 -0
  84. package/lib/plugin/sync-list-order-plugin.d.ts.map +1 -0
  85. package/package.json +9 -7
  86. package/src/composed/commands.ts +31 -0
  87. package/src/composed/index.ts +6 -0
  88. package/src/composed/inputrules.ts +13 -0
  89. package/src/composed/keymap.ts +20 -0
  90. package/src/composed/plugins.ts +23 -0
  91. package/src/composed/schema.ts +55 -0
  92. package/src/index.ts +6 -52
  93. package/src/mark/emphasis.ts +47 -0
  94. package/src/mark/index.ts +3 -10
  95. package/src/mark/inline-code.ts +70 -0
  96. package/src/mark/link.ts +96 -247
  97. package/src/mark/strong.ts +41 -36
  98. package/src/node/blockquote.ts +39 -33
  99. package/src/node/bullet-list.ts +62 -55
  100. package/src/node/code-block.ts +103 -0
  101. package/src/node/doc.ts +18 -22
  102. package/src/node/hardbreak.ts +68 -117
  103. package/src/node/heading.ts +175 -284
  104. package/src/node/hr.ts +57 -57
  105. package/src/node/image.ts +113 -209
  106. package/src/node/index.ts +6 -35
  107. package/src/node/list-item.ts +125 -141
  108. package/src/node/ordered-list.ts +70 -65
  109. package/src/node/paragraph.ts +54 -50
  110. package/src/node/text.ts +14 -16
  111. package/src/plugin/hardbreak-clear-mark-plugin.ts +45 -0
  112. package/src/plugin/hardbreak-filter-plugin.ts +33 -0
  113. package/src/plugin/index.ts +12 -14
  114. package/src/plugin/{inline-nodes-cursor.ts → inline-nodes-cursor-plugin.ts} +5 -8
  115. package/src/plugin/{inline-sync → inline-sync-plugin}/config.ts +18 -3
  116. package/src/plugin/{inline-sync → inline-sync-plugin}/context.ts +4 -6
  117. package/src/plugin/inline-sync-plugin/index.ts +4 -0
  118. package/src/plugin/{inline-sync/index.ts → inline-sync-plugin/inline-sync-plugin.ts} +14 -11
  119. package/src/plugin/{inline-sync → inline-sync-plugin}/regexp.ts +0 -0
  120. package/src/plugin/{inline-sync → inline-sync-plugin}/replacer.ts +3 -3
  121. package/src/plugin/{inline-sync → inline-sync-plugin}/utils.ts +0 -0
  122. package/src/plugin/remark-add-order-in-list-plugin.ts +16 -0
  123. package/src/plugin/remark-inline-link-plugin.ts +6 -0
  124. package/src/plugin/remark-line-break.ts +44 -0
  125. package/src/plugin/sync-heading-id-plugin.ts +55 -0
  126. package/src/plugin/sync-list-order-plugin.ts +57 -0
  127. package/lib/mark/code-inline.d.ts +0 -3
  128. package/lib/mark/code-inline.d.ts.map +0 -1
  129. package/lib/mark/em.d.ts +0 -3
  130. package/lib/mark/em.d.ts.map +0 -1
  131. package/lib/node/code-fence.d.ts +0 -7
  132. package/lib/node/code-fence.d.ts.map +0 -1
  133. package/lib/plugin/add-order-in-list.d.ts +0 -3
  134. package/lib/plugin/add-order-in-list.d.ts.map +0 -1
  135. package/lib/plugin/filter-html.d.ts +0 -3
  136. package/lib/plugin/filter-html.d.ts.map +0 -1
  137. package/lib/plugin/inline-nodes-cursor.d.ts +0 -7
  138. package/lib/plugin/inline-nodes-cursor.d.ts.map +0 -1
  139. package/lib/plugin/inline-sync/config.d.ts.map +0 -1
  140. package/lib/plugin/inline-sync/context.d.ts.map +0 -1
  141. package/lib/plugin/inline-sync/index.d.ts +0 -6
  142. package/lib/plugin/inline-sync/index.d.ts.map +0 -1
  143. package/lib/plugin/inline-sync/regexp.d.ts.map +0 -1
  144. package/lib/plugin/inline-sync/replacer.d.ts.map +0 -1
  145. package/lib/plugin/inline-sync/utils.d.ts.map +0 -1
  146. package/lib/supported-keys.d.ts +0 -23
  147. package/lib/supported-keys.d.ts.map +0 -1
  148. package/src/mark/code-inline.ts +0 -66
  149. package/src/mark/em.ts +0 -42
  150. package/src/node/code-fence.ts +0 -245
  151. package/src/plugin/add-order-in-list.ts +0 -19
  152. package/src/plugin/filter-html.ts +0 -43
  153. package/src/supported-keys.ts +0 -25
  154. package/src/types.d.ts +0 -5
package/src/node/image.ts CHANGED
@@ -1,224 +1,128 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import type { ThemeImageType, ThemeInputChipType } from '@milkdown/core'
3
- import { commandsCtx, createCmd, createCmdKey } from '@milkdown/core'
4
2
  import { expectDomTypeError } from '@milkdown/exception'
5
3
  import { findSelectedNodeOfType } from '@milkdown/prose'
6
4
  import { InputRule } from '@milkdown/prose/inputrules'
7
- import { Plugin, PluginKey } from '@milkdown/prose/state'
8
- import type { EditorView, NodeView } from '@milkdown/prose/view'
9
- import { createNode } from '@milkdown/utils'
5
+ import { $command, $inputRule, $nodeAttr, $nodeSchema } from '@milkdown/utils'
10
6
 
11
- export const ModifyImage = createCmdKey<string>('ModifyImage')
12
- export const InsertImage = createCmdKey<string>('InsertImage')
13
- const id = 'image'
14
- export interface ImageOptions {
15
- isBlock: boolean
16
- placeholder: string
17
- input: {
18
- placeholder: string
19
- buttonText?: string
20
- }
21
- }
22
- const key = new PluginKey('MILKDOWN_IMAGE_INPUT')
7
+ /// HTML attributes for image node.
8
+ export const imageAttr = $nodeAttr('image')
23
9
 
24
- export const image = createNode<string, ImageOptions>((utils, options) => {
10
+ /// Schema for image node.
11
+ export const imageSchema = $nodeSchema('image', (ctx) => {
25
12
  return {
26
- id: 'image',
27
- schema: () => ({
28
- inline: true,
29
- group: 'inline',
30
- selectable: true,
31
- draggable: true,
32
- marks: '',
33
- atom: true,
34
- defining: true,
35
- isolating: true,
36
- attrs: {
37
- src: { default: '' },
38
- alt: { default: '' },
39
- title: { default: '' },
40
- },
41
- parseDOM: [
42
- {
43
- tag: 'img[src]',
44
- getAttrs: (dom) => {
45
- if (!(dom instanceof HTMLElement))
46
- throw expectDomTypeError(dom)
47
-
48
- return {
49
- src: dom.getAttribute('src') || '',
50
- alt: dom.getAttribute('alt') || '',
51
- title: dom.getAttribute('title') || dom.getAttribute('alt') || '',
52
- }
53
- },
13
+ inline: true,
14
+ group: 'inline',
15
+ selectable: true,
16
+ draggable: true,
17
+ marks: '',
18
+ atom: true,
19
+ defining: true,
20
+ isolating: true,
21
+ attrs: {
22
+ src: { default: '' },
23
+ alt: { default: '' },
24
+ title: { default: '' },
25
+ },
26
+ parseDOM: [
27
+ {
28
+ tag: 'img[src]',
29
+ getAttrs: (dom) => {
30
+ if (!(dom instanceof HTMLElement))
31
+ throw expectDomTypeError(dom)
32
+
33
+ return {
34
+ src: dom.getAttribute('src') || '',
35
+ alt: dom.getAttribute('alt') || '',
36
+ title: dom.getAttribute('title') || dom.getAttribute('alt') || '',
37
+ }
54
38
  },
55
- ],
56
- toDOM: (node) => {
57
- return [
58
- 'img',
59
- {
60
- ...node.attrs,
61
- class: utils.getClassName(node.attrs, id),
62
- },
63
- ]
64
39
  },
65
- parseMarkdown: {
66
- match: ({ type }) => type === id,
67
- runner: (state, node, type) => {
68
- const url = node.url as string
69
- const alt = node.alt as string
70
- const title = node.title as string
71
- state.addNode(type, {
72
- src: url,
73
- alt,
74
- title,
75
- })
76
- },
40
+ ],
41
+ toDOM: (node) => {
42
+ return ['img', { ...ctx.get(imageAttr.key)(node), ...node.attrs }]
43
+ },
44
+ parseMarkdown: {
45
+ match: ({ type }) => type === 'image',
46
+ runner: (state, node, type) => {
47
+ const url = node.url as string
48
+ const alt = node.alt as string
49
+ const title = node.title as string
50
+ state.addNode(type, {
51
+ src: url,
52
+ alt,
53
+ title,
54
+ })
77
55
  },
78
- toMarkdown: {
79
- match: node => node.type.name === id,
80
- runner: (state, node) => {
81
- state.addNode('image', undefined, undefined, {
82
- title: node.attrs.title,
83
- url: node.attrs.src,
84
- alt: node.attrs.alt,
85
- })
86
- },
56
+ },
57
+ toMarkdown: {
58
+ match: node => node.type.name === 'image',
59
+ runner: (state, node) => {
60
+ state.addNode('image', undefined, undefined, {
61
+ title: node.attrs.title,
62
+ url: node.attrs.src,
63
+ alt: node.attrs.alt,
64
+ })
87
65
  },
88
- }),
89
- commands: type => [
90
- createCmd(InsertImage, (src = '') => (state, dispatch) => {
91
- if (!dispatch)
92
- return true
93
- const { tr } = state
94
- const node = type.create({ src })
95
- if (!node)
96
- return true
97
-
98
- const _tr = tr.replaceSelectionWith(node)
99
- dispatch(_tr.scrollIntoView())
100
- return true
101
- }),
102
- createCmd(ModifyImage, (src = '') => (state, dispatch) => {
103
- const node = findSelectedNodeOfType(state.selection, type)
104
- if (!node)
105
- return false
106
-
107
- const { tr } = state
108
- dispatch?.(
109
- tr.setNodeMarkup(node.pos, undefined, { ...node.node.attrs, loading: true, src }).scrollIntoView(),
110
- )
111
-
112
- return true
113
- }),
114
- ],
115
- inputRules: type => [
116
- new InputRule(
117
- /!\[(?<alt>.*?)]\((?<filename>.*?)\s*(?="|\))"?(?<title>[^"]+)?"?\)/,
118
- (state, match, start, end) => {
119
- const [okay, alt, src = '', title] = match
120
- const { tr } = state
121
- if (okay)
122
- tr.replaceWith(start, end, type.create({ src, alt, title }))
123
-
124
- return tr
125
- },
126
- ),
127
- ],
128
- view: () => (node) => {
129
- let currNode = node
130
-
131
- const placeholder = options?.placeholder ?? 'Add an Image'
132
- const isBlock = options?.isBlock ?? false
133
- const renderer = utils.themeManager.get<ThemeImageType>('image', {
134
- placeholder,
135
- isBlock,
136
- })
137
-
138
- if (!renderer)
139
- return {} as NodeView
140
-
141
- const { dom, onUpdate } = renderer
142
- onUpdate(currNode)
143
-
144
- return {
145
- dom,
146
- update: (updatedNode) => {
147
- if (updatedNode.type.name !== id)
148
- return false
149
-
150
- currNode = updatedNode
151
- onUpdate(currNode)
152
-
153
- return true
154
- },
155
- selectNode: () => {
156
- dom.classList.add('ProseMirror-selectednode')
157
- },
158
- deselectNode: () => {
159
- dom.classList.remove('ProseMirror-selectednode')
160
- },
161
- }
162
66
  },
163
- prosePlugins: (type, ctx) => {
164
- return [
165
- new Plugin({
166
- key,
167
- view: (editorView) => {
168
- const inputChipRenderer = utils.themeManager.get<ThemeInputChipType>('input-chip', {
169
- placeholder: options?.input?.placeholder ?? 'Input Image Link',
170
- buttonText: options?.input?.buttonText,
171
- onUpdate: (value) => {
172
- ctx.get(commandsCtx).call(ModifyImage, value)
173
- },
174
- })
175
- if (!inputChipRenderer)
176
- return {}
177
- const shouldDisplay = (view: EditorView) => {
178
- return Boolean(
179
- view.hasFocus() && type && findSelectedNodeOfType(view.state.selection, type),
180
- )
181
- }
182
- const getCurrentLink = (view: EditorView) => {
183
- const result = findSelectedNodeOfType(view.state.selection, type)
184
- if (!result)
185
- return
186
-
187
- const value = result.node.attrs.src
188
- return value
189
- }
190
- const renderByView = (view: EditorView) => {
191
- if (!view.editable)
192
- return
193
-
194
- const display = shouldDisplay(view)
195
- if (display) {
196
- inputChipRenderer.show(view)
197
- inputChipRenderer.update(getCurrentLink(view))
198
- }
199
- else {
200
- inputChipRenderer.hide()
201
- }
202
- }
203
- inputChipRenderer.init(editorView)
204
- renderByView(editorView)
67
+ }
68
+ })
205
69
 
206
- return {
207
- update: (view, prevState) => {
208
- const isEqualSelection
209
- = prevState?.doc.eq(view.state.doc) && prevState.selection.eq(view.state.selection)
210
- if (isEqualSelection)
211
- return
70
+ /// @internal
71
+ export type UpdateImageCommandPayload = {
72
+ src?: string
73
+ title?: string
74
+ alt?: string
75
+ }
212
76
 
213
- renderByView(view)
214
- },
215
- destroy: () => {
216
- inputChipRenderer.destroy()
217
- },
218
- }
219
- },
220
- }),
221
- ]
222
- },
223
- }
77
+ /// This command will insert a image node.
78
+ /// You can pass a payload to set `src`, `alt` and `title` for the image node.
79
+ export const insertImageCommand = $command('InsertImage', () => (payload: UpdateImageCommandPayload = {}) =>
80
+ (state, dispatch) => {
81
+ if (!dispatch)
82
+ return true
83
+
84
+ const { src = '', alt = '', title = '' } = payload
85
+
86
+ const node = imageSchema.type().create({ src, alt, title })
87
+ if (!node)
88
+ return true
89
+
90
+ dispatch(state.tr.replaceSelectionWith(node).scrollIntoView())
91
+ return true
92
+ })
93
+
94
+ /// This command will update the selected image node.
95
+ /// You can pass a payload to update `src`, `alt` and `title` for the image node.
96
+ export const updateImageCommand = $command('UpdateImage', () => (payload: UpdateImageCommandPayload = {}) => (state, dispatch) => {
97
+ const nodeWithPos = findSelectedNodeOfType(state.selection, imageSchema.type())
98
+ if (!nodeWithPos)
99
+ return false
100
+
101
+ const { node, pos } = nodeWithPos
102
+
103
+ const newAttrs = { ...node.attrs }
104
+ const { src, alt, title } = payload
105
+ if (src !== undefined)
106
+ newAttrs.src = src
107
+ if (alt !== undefined)
108
+ newAttrs.alt = alt
109
+ if (title !== undefined)
110
+ newAttrs.title = title
111
+
112
+ dispatch?.(state.tr.setNodeMarkup(pos, undefined, newAttrs).scrollIntoView())
113
+ return true
224
114
  })
115
+
116
+ /// This input rule will insert a image node.
117
+ /// You can input `![alt](src "title")` to insert a image node.
118
+ /// The `title` is optional.
119
+ export const insertImageInputRule = $inputRule(() => new InputRule(
120
+ /!\[(?<alt>.*?)]\((?<filename>.*?)\s*(?="|\))"?(?<title>[^"]+)?"?\)/,
121
+ (state, match, start, end) => {
122
+ const [matched, alt, src = '', title] = match
123
+ if (matched)
124
+ return state.tr.replaceWith(start, end, imageSchema.type().create({ src, alt, title }))
125
+
126
+ return null
127
+ },
128
+ ))
package/src/node/index.ts CHANGED
@@ -1,43 +1,14 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import type { AtomPlugin } from '@milkdown/utils'
3
2
 
4
- import { blockquote } from './blockquote'
5
- import { bulletList } from './bullet-list'
6
- import { codeFence } from './code-fence'
7
- import { doc } from './doc'
8
- import { hardbreak } from './hardbreak'
9
- import { heading } from './heading'
10
- import { hr } from './hr'
11
- import { image } from './image'
12
- import { listItem } from './list-item'
13
- import { orderedList } from './ordered-list'
14
- import { paragraph } from './paragraph'
15
- import { text } from './text'
16
-
17
- export const nodes: AtomPlugin[] = [
18
- doc(),
19
- paragraph(),
20
- hardbreak(),
21
- blockquote(),
22
- codeFence(),
23
- bulletList(),
24
- orderedList(),
25
- listItem(),
26
- heading(),
27
- hr(),
28
- image(),
29
- text(),
30
- ]
31
-
32
- export * from './blockquote'
33
- export * from './bullet-list'
34
- export * from './code-fence'
35
3
  export * from './doc'
36
- export * from './hardbreak'
37
4
  export * from './heading'
38
- export * from './hr'
5
+ export * from './blockquote'
6
+ export * from './code-block'
39
7
  export * from './image'
40
- export * from './list-item'
8
+ export * from './hardbreak'
9
+ export * from './hr'
10
+ export * from './bullet-list'
41
11
  export * from './ordered-list'
12
+ export * from './list-item'
42
13
  export * from './paragraph'
43
14
  export * from './text'
@@ -1,157 +1,141 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import { createCmd, createCmdKey } from '@milkdown/core'
2
+ import { commandsCtx } from '@milkdown/core'
3
3
  import { expectDomTypeError } from '@milkdown/exception'
4
- import { getNodeFromSchema } from '@milkdown/prose'
5
- import { wrappingInputRule } from '@milkdown/prose/inputrules'
6
- import type { NodeType } from '@milkdown/prose/model'
7
4
  import { liftListItem, sinkListItem, splitListItem } from '@milkdown/prose/schema-list'
8
- import type { EditorState, Transaction } from '@milkdown/prose/state'
9
- import { Plugin, PluginKey } from '@milkdown/prose/state'
10
- import { createNode, createShortcut } from '@milkdown/utils'
5
+ import { $command, $nodeAttr, $nodeSchema, $useKeymap } from '@milkdown/utils'
11
6
 
12
- import { SupportedKeys } from '../supported-keys'
7
+ /// HTML attributes for list item node.
8
+ export const listItemAttr = $nodeAttr('listItem')
13
9
 
14
- type Keys = SupportedKeys['SinkListItem'] | SupportedKeys['LiftListItem'] | SupportedKeys['NextListItem']
15
-
16
- const id = 'list_item'
17
-
18
- export const SplitListItem = createCmdKey('SplitListItem')
19
- export const SinkListItem = createCmdKey('SinkListItem')
20
- export const LiftListItem = createCmdKey('LiftListItem')
21
-
22
- const keepListOrderPluginKey = new PluginKey('MILKDOWN_KEEP_LIST_ORDER')
23
-
24
- const createKeepListOrderPlugin = (type: NodeType) => {
25
- const walkThrough = (state: EditorState, callback: (tr: Transaction) => void) => {
26
- const orderedListType = getNodeFromSchema('ordered_list', state.schema)
27
- let tr = state.tr
28
- state.doc.descendants((node, pos, parent, index) => {
29
- if (node.type === type && parent?.type === orderedListType) {
30
- let changed = false
31
- const attrs = { ...node.attrs }
32
- if (node.attrs.listType !== 'ordered') {
33
- attrs.listType = 'ordered'
34
- changed = true
35
- }
36
-
37
- const base = parent?.maybeChild(0)
38
- if (base && base.type === type && base.attrs.listType === 'ordered') {
39
- attrs.label = `${index + 1}.`
40
- changed = true
41
- }
42
-
43
- if (node.attrs.label === '•') {
44
- attrs.label = `${index + 1}.`
45
- changed = true
46
- }
47
-
48
- if (changed)
49
- tr = tr.setNodeMarkup(pos, undefined, attrs)
50
- }
51
- })
52
- callback(tr)
53
- }
54
- return new Plugin({
55
- key: keepListOrderPluginKey,
56
- appendTransaction: (transactions, _oldState, nextState) => {
57
- let tr: Transaction | null = null
58
- if (transactions.some(transaction => transaction.docChanged)) {
59
- walkThrough(nextState, (t) => {
60
- tr = t
61
- })
62
- }
63
-
64
- return tr
10
+ /// Schema for list item node.
11
+ export const listItemSchema = $nodeSchema('list_item', ctx => ({
12
+ group: 'listItem',
13
+ content: 'paragraph block*',
14
+ attrs: {
15
+ label: {
16
+ default: '',
17
+ },
18
+ listType: {
19
+ default: 'bullet',
20
+ },
21
+ spread: {
22
+ default: 'true',
65
23
  },
66
- })
67
- }
24
+ },
25
+ defining: true,
26
+ parseDOM: [
27
+ {
28
+ tag: 'li',
29
+ getAttrs: (dom) => {
30
+ if (!(dom instanceof HTMLElement))
31
+ throw expectDomTypeError(dom)
68
32
 
69
- export const listItem = createNode<Keys>(utils => ({
70
- id,
71
- schema: () => ({
72
- group: 'listItem',
73
- content: 'paragraph block*',
74
- attrs: {
75
- label: {
76
- default: '•',
77
- },
78
- listType: {
79
- default: 'bullet',
80
- },
81
- spread: {
82
- default: 'true',
33
+ return {
34
+ label: dom.dataset.label,
35
+ listType: dom.dataset['list-type'],
36
+ spread: dom.dataset.spread,
37
+ }
83
38
  },
84
39
  },
85
- defining: true,
86
- parseDOM: [
87
- {
88
- tag: 'li.list-item',
89
- getAttrs: (dom) => {
90
- if (!(dom instanceof HTMLElement))
91
- throw expectDomTypeError(dom)
40
+ ],
41
+ toDOM: node => [
42
+ 'li',
43
+ {
44
+ ...ctx.get(listItemAttr.key)(node),
45
+ 'data-label': node.attrs.label,
46
+ 'data-list-type': node.attrs.listType,
47
+ 'data-spread': node.attrs.spread,
48
+ },
49
+ 0,
50
+ ],
51
+ parseMarkdown: {
52
+ match: ({ type }) => type === 'listItem',
53
+ runner: (state, node, type) => {
54
+ const label = node.label != null ? `${node.label}.` : '•'
55
+ const listType = node.label != null ? 'ordered' : 'bullet'
56
+ const spread = node.spread != null ? `${node.spread}` : 'true'
57
+ state.openNode(type, { label, listType, spread })
58
+ state.next(node.children)
59
+ state.closeNode()
60
+ },
61
+ },
62
+ toMarkdown: {
63
+ match: node => node.type.name === 'list_item',
64
+ runner: (state, node) => {
65
+ state.openNode('listItem', undefined, { spread: node.attrs.spread === 'true' })
66
+ state.next(node.content)
67
+ state.closeNode()
68
+ },
69
+ },
70
+ }))
92
71
 
93
- return {
94
- label: dom.dataset.label,
95
- listType: dom.dataset['list-type'],
96
- spread: dom.dataset.spread,
97
- }
98
- },
99
- contentElement: (dom) => {
100
- if (!(dom instanceof HTMLElement))
101
- throw expectDomTypeError(dom)
72
+ /// The command to sink list item.
73
+ ///
74
+ /// For example:
75
+ /// ```md
76
+ /// * List item 1
77
+ /// * List item 2 <- cursor here
78
+ /// ```
79
+ /// Will get:
80
+ /// ```md
81
+ /// * List item 1
82
+ /// * List item 2
83
+ /// ```
84
+ export const sinkListItemCommand = $command('SinkListItem', () => () => sinkListItem(listItemSchema.type()))
102
85
 
103
- const body = dom.querySelector<HTMLElement>('.list-item_body')
104
- if (!body)
105
- return dom
86
+ /// The command to lift list item.
87
+ ///
88
+ /// For example:
89
+ /// ```md
90
+ /// * List item 1
91
+ /// * List item 2 <- cursor here
92
+ /// ```
93
+ /// Will get:
94
+ /// ```md
95
+ /// * List item 1
96
+ /// * List item 2
97
+ /// ```
98
+ export const liftListItemCommand = $command('SplitListItem', () => () => liftListItem(listItemSchema.type()))
106
99
 
107
- return body
108
- },
109
- },
110
- { tag: 'li' },
111
- ],
112
- toDOM: (node) => {
113
- return [
114
- 'li',
115
- {
116
- 'class': utils.getClassName(node.attrs, 'list-item'),
117
- 'data-label': node.attrs.label,
118
- 'data-list-type': node.attrs.listType,
119
- 'data-spread': node.attrs.spread,
120
- },
121
- ['div', { class: utils.getClassName(node.attrs, 'list-item_label') }, node.attrs.label],
122
- ['div', { class: utils.getClassName(node.attrs, 'list-item_body') }, 0],
123
- ]
100
+ /// The command to split a list item.
101
+ ///
102
+ /// For example:
103
+ /// ```md
104
+ /// * List item 1
105
+ /// * List item 2 <- cursor here
106
+ /// ```
107
+ /// Will get:
108
+ /// ```md
109
+ /// * List item 1
110
+ /// * List item 2
111
+ /// * <- cursor here
112
+ /// ```
113
+ export const splitListItemCommand = $command('SplitListItem', () => () => splitListItem(listItemSchema.type()))
114
+
115
+ /// Keymap for list item node.
116
+ /// - `<Enter>`: Split the current list item.
117
+ /// - `<Tab>/<Mod-]>`: Sink the current list item.
118
+ /// - `<Shift-Tab>/<Mod-[>`: Lift the current list item.
119
+ export const listItemKeymap = $useKeymap('listItemKeymap', {
120
+ NextListItem: {
121
+ shortcuts: 'Enter',
122
+ command: (ctx) => {
123
+ const commands = ctx.get(commandsCtx)
124
+ return () => commands.call(splitListItemCommand.key)
124
125
  },
125
- parseMarkdown: {
126
- match: ({ type, checked }) => type === 'listItem' && checked === null,
127
- runner: (state, node, type) => {
128
- const label = node.label != null ? `${node.label}.` : '•'
129
- const listType = node.label != null ? 'ordered' : 'bullet'
130
- const spread = node.spread != null ? `${node.spread}` : 'true'
131
- state.openNode(type, { label, listType, spread })
132
- state.next(node.children)
133
- state.closeNode()
134
- },
126
+ },
127
+ SinkListItem: {
128
+ shortcuts: ['Tab', 'Mod-]'],
129
+ command: (ctx) => {
130
+ const commands = ctx.get(commandsCtx)
131
+ return () => commands.call(sinkListItemCommand.key)
135
132
  },
136
- toMarkdown: {
137
- match: node => node.type.name === id,
138
- runner: (state, node) => {
139
- state.openNode('listItem', undefined, { spread: node.attrs.spread === 'true' })
140
- state.next(node.content)
141
- state.closeNode()
142
- },
133
+ },
134
+ LiftListItem: {
135
+ shortcuts: ['Shift-Tab', 'Mod-['],
136
+ command: (ctx) => {
137
+ const commands = ctx.get(commandsCtx)
138
+ return () => commands.call(liftListItemCommand.key)
143
139
  },
144
- }),
145
- inputRules: nodeType => [wrappingInputRule(/^\s*([-+*])\s$/, nodeType)],
146
- commands: nodeType => [
147
- createCmd(SplitListItem, () => splitListItem(nodeType)),
148
- createCmd(SinkListItem, () => sinkListItem(nodeType)),
149
- createCmd(LiftListItem, () => liftListItem(nodeType)),
150
- ],
151
- shortcuts: {
152
- [SupportedKeys.NextListItem]: createShortcut(SplitListItem, 'Enter'),
153
- [SupportedKeys.SinkListItem]: createShortcut(SinkListItem, 'Mod-]'),
154
- [SupportedKeys.LiftListItem]: createShortcut(LiftListItem, 'Mod-['),
155
140
  },
156
- prosePlugins: nodeType => [createKeepListOrderPlugin(nodeType)],
157
- }))
141
+ })