@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
@@ -1,315 +1,206 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import type { Ctx } from '@milkdown/core'
3
- import { createCmd, createCmdKey, editorViewCtx, getPalette, schemaCtx } from '@milkdown/core'
2
+ import { commandsCtx, editorViewCtx } from '@milkdown/core'
4
3
  import { expectDomTypeError } from '@milkdown/exception'
5
- import { cloneTr } from '@milkdown/prose'
6
4
  import { setBlockType } from '@milkdown/prose/commands'
7
5
  import { textblockTypeInputRule } from '@milkdown/prose/inputrules'
8
- import type { Node, NodeType } from '@milkdown/prose/model'
6
+ import type { Node } from '@milkdown/prose/model'
9
7
  import { Fragment } from '@milkdown/prose/model'
10
- import type { EditorState, Transaction } from '@milkdown/prose/state'
11
- import { Plugin, PluginKey } from '@milkdown/prose/state'
12
- import { Decoration, DecorationSet } from '@milkdown/prose/view'
13
- import type { ThemeUtils } from '@milkdown/utils'
14
- import { createNode, createShortcut } from '@milkdown/utils'
15
-
16
- import { SupportedKeys } from '../supported-keys'
8
+ import { $command, $ctx, $inputRule, $nodeAttr, $nodeSchema, $useKeymap } from '@milkdown/utils'
9
+ import { paragraphSchema } from './paragraph'
17
10
 
18
11
  const headingIndex = Array(6)
19
12
  .fill(0)
20
13
  .map((_, i) => i + 1)
21
14
 
22
- type Keys =
23
- | SupportedKeys['H1']
24
- | SupportedKeys['H2']
25
- | SupportedKeys['H3']
26
- | SupportedKeys['H4']
27
- | SupportedKeys['H5']
28
- | SupportedKeys['H6']
29
- | SupportedKeys['DowngradeHeading']
30
-
31
- export const TurnIntoHeading = createCmdKey<number>('TurnIntoHeading')
32
- export const DowngradeHeading = createCmdKey('DowngradeHeading')
33
-
34
- export const headingIdPluginKey = new PluginKey('MILKDOWN_HEADING_ID')
35
- export const headingHashPluginKey = new PluginKey('MILKDOWN_HEADING_HASH')
36
-
37
- const createId = (node: Node) =>
15
+ const defaultHeadingIdGenerator = (node: Node) =>
38
16
  node.textContent
39
17
  .replace(/[\p{P}\p{S}]/gu, '')
40
18
  .replace(/\s/g, '-')
41
19
  .toLowerCase()
42
20
  .trim()
43
21
 
44
- const headingIdPlugin = (ctx: Ctx, type: NodeType, getId: (node: Node) => string): Plugin => {
45
- let lock = false
46
- const walkThrough = (state: EditorState, callback: (tr: Transaction) => void) => {
47
- const tr = state.tr.setMeta('addToHistory', false)
48
- let found = false
49
- state.doc.descendants((node, pos) => {
50
- if (node.type === type && !lock) {
51
- if (node.textContent.trim().length === 0)
52
- return
53
-
54
- const attrs = node.attrs
55
- const id = getId(node)
56
-
57
- if (attrs.id !== id) {
58
- found = true
59
- tr.setMeta(headingIdPluginKey, true).setNodeMarkup(pos, undefined, {
60
- ...attrs,
61
- id,
62
- })
63
- }
64
- }
65
- })
66
- if (found)
67
- callback(tr)
68
- }
69
- return new Plugin({
70
- key: headingIdPluginKey,
71
- props: {
72
- handleDOMEvents: {
73
- compositionstart: () => {
74
- lock = true
75
- return false
76
- },
77
- compositionend: () => {
78
- lock = false
79
- const view = ctx.get(editorViewCtx)
80
- setTimeout(() => {
81
- walkThrough(view.state, tr => view.dispatch(tr))
82
- }, 0)
83
- return false
84
- },
22
+ /// This is a slice contains a function to generate heading id.
23
+ /// You can configure it to generate id in your own way.
24
+ export const headingIdGenerator = $ctx(defaultHeadingIdGenerator, 'headingIdGenerator')
25
+
26
+ /// HTML attributes for heading node.
27
+ export const headingAttr = $nodeAttr('heading')
28
+
29
+ /// Schema for heading node.
30
+ export const headingSchema = $nodeSchema('heading', (ctx) => {
31
+ const getId = ctx.get(headingIdGenerator.key)
32
+ return {
33
+ content: 'inline*',
34
+ group: 'block',
35
+ defining: true,
36
+ attrs: {
37
+ id: {
38
+ default: '',
85
39
  },
86
- },
87
- appendTransaction: (transactions, _, nextState) => {
88
- let tr: Transaction | null = null
89
-
90
- if (
91
- transactions.every(transaction => !transaction.getMeta(headingIdPluginKey))
92
- && transactions.some(transaction => transaction.docChanged)
93
- ) {
94
- walkThrough(nextState, (t) => {
95
- tr = t
96
- })
97
- }
98
-
99
- return tr
100
- },
101
- view: (view) => {
102
- const doc = view.state.doc
103
- let tr = view.state.tr.setMeta('addToHistory', false)
104
- doc.descendants((node, pos) => {
105
- if (node.type.name === 'heading' && node.attrs.level) {
106
- if (!node.attrs.id) {
107
- tr = tr.setNodeMarkup(pos, undefined, {
108
- ...node.attrs,
109
- id: getId(node),
110
- })
111
- }
112
- }
113
- })
114
- view.dispatch(tr)
115
- return {}
116
- },
117
- })
118
- }
119
-
120
- const headingHashPlugin = (ctx: Ctx, type: NodeType, utils: ThemeUtils): Plugin => {
121
- return new Plugin({
122
- key: headingHashPluginKey,
123
- state: {
124
- init: () => {
125
- return DecorationSet.empty
40
+ level: {
41
+ default: 1,
126
42
  },
127
- apply: (tr) => {
128
- const view = ctx.get(editorViewCtx)
129
- if (!view.hasFocus?.() || !view.editable)
130
- return DecorationSet.empty
131
-
132
- const { $from } = tr.selection
133
- const node = $from.node()
134
- if (node.type !== type)
135
- return DecorationSet.empty
136
-
137
- const level = node.attrs.level
138
- const getHashes = (level: number) => {
139
- return Array(level)
140
- .fill(0)
141
- .map(_ => '#')
142
- .join('')
143
- }
144
- const widget = document.createElement('span')
145
- widget.textContent = getHashes(level)
146
- widget.contentEditable = 'false'
147
- utils.themeManager.onFlush(() => {
148
- const style = utils.getStyle(({ css }) => {
149
- const palette = getPalette(utils.themeManager)
150
- return css`
151
- margin-right: 4px;
152
- color: ${palette('primary')};
153
- `
154
- })
155
- if (style)
156
- widget.className = style
157
- })
43
+ },
44
+ parseDOM: headingIndex.map(x => ({
45
+ tag: `h${x}`,
46
+ getAttrs: (node) => {
47
+ if (!(node instanceof HTMLElement))
48
+ throw expectDomTypeError(node)
158
49
 
159
- const deco = Decoration.widget($from.before() + 1, widget, { side: -1 })
160
- return DecorationSet.create(tr.doc, [deco])
50
+ return { level: x, id: node.id }
161
51
  },
162
- },
163
- props: {
164
- handleDOMEvents: {
165
- focus: (view) => {
166
- const tr = cloneTr(view.state.tr)
167
- view.dispatch(tr)
168
- return false
52
+ })),
53
+ toDOM: (node) => {
54
+ return [
55
+ `h${node.attrs.level}`,
56
+ {
57
+ ...ctx.get(headingAttr.key)(node),
58
+ id: node.attrs.id || getId(node),
169
59
  },
60
+ 0,
61
+ ]
62
+ },
63
+ parseMarkdown: {
64
+ match: ({ type }) => type === 'heading',
65
+ runner: (state, node, type) => {
66
+ const depth = node.depth as number
67
+ state.openNode(type, { level: depth })
68
+ state.next(node.children)
69
+ state.closeNode()
170
70
  },
171
- decorations(this: Plugin, state) {
172
- return this.getState(state)
71
+ },
72
+ toMarkdown: {
73
+ match: node => node.type.name === 'heading',
74
+ runner: (state, node) => {
75
+ state.openNode('heading', undefined, { depth: node.attrs.level })
76
+ const lastIsHardbreak = node.childCount >= 1 && node.lastChild?.type.name === 'hardbreak'
77
+ if (lastIsHardbreak) {
78
+ const contentArr: Node[] = []
79
+ node.content.forEach((n, _, i) => {
80
+ if (i === node.childCount - 1)
81
+ return
82
+
83
+ contentArr.push(n)
84
+ })
85
+ state.next(Fragment.fromArray(contentArr))
86
+ }
87
+ else {
88
+ state.next(node.content)
89
+ }
90
+ state.closeNode()
173
91
  },
174
92
  },
93
+ }
94
+ })
95
+
96
+ /// This input rule can turn the selected block into heading.
97
+ /// You can input numbers of `#` and a `space` to create heading.
98
+ export const wrapInHeadingInputRule = $inputRule((ctx) => {
99
+ return textblockTypeInputRule(/^(?<hashes>#+)\s$/, headingSchema.type(), (match) => {
100
+ const x = match.groups?.hashes?.length || 0
101
+
102
+ const view = ctx.get(editorViewCtx)
103
+ const { $from } = view.state.selection
104
+ const node = $from.node()
105
+ if (node.type.name === 'heading') {
106
+ let level = Number(node.attrs.level) + Number(x)
107
+ if (level > 6)
108
+ level = 6
109
+
110
+ return { level }
111
+ }
112
+ return { level: x }
175
113
  })
176
- }
177
-
178
- export const heading = createNode<Keys, { getId: (node: Node) => string; displayHashtag: boolean }>(
179
- (utils, options) => {
180
- const id = 'heading'
181
-
182
- const getId = options?.getId ?? createId
183
- const displayHashtag = options?.displayHashtag ?? true
114
+ })
184
115
 
185
- return {
186
- id,
187
- schema: () => ({
188
- content: 'inline*',
189
- group: 'block',
190
- defining: true,
191
- attrs: {
192
- id: {
193
- default: '',
194
- },
195
- level: {
196
- default: 1,
197
- },
198
- },
199
- parseDOM: headingIndex.map(x => ({
200
- tag: `h${x}`,
201
- getAttrs: (node) => {
202
- if (!(node instanceof HTMLElement))
203
- throw expectDomTypeError(node)
116
+ /// This command can turn the selected block into heading.
117
+ /// You can pass the level of heading to this command.
118
+ /// By default, the level is 1, which means it will create a `h1` element.
119
+ export const wrapInHeadingCommand = $command('WrapInHeading', () => {
120
+ return (level?: number) => {
121
+ level ??= 1
204
122
 
205
- return { level: x, id: node.id }
206
- },
207
- })),
208
- toDOM: (node) => {
209
- return [
210
- `h${node.attrs.level}`,
211
- {
212
- id: node.attrs.id || getId(node),
213
- class: utils.getClassName(node.attrs, `heading h${node.attrs.level}`),
214
- },
215
- 0,
216
- ]
217
- },
218
- parseMarkdown: {
219
- match: ({ type }) => type === id,
220
- runner: (state, node, type) => {
221
- const depth = node.depth as number
222
- state.openNode(type, { level: depth })
223
- state.next(node.children)
224
- state.closeNode()
225
- },
226
- },
227
- toMarkdown: {
228
- match: node => node.type.name === id,
229
- runner: (state, node) => {
230
- state.openNode('heading', undefined, { depth: node.attrs.level })
231
- const lastIsHardbreak = node.childCount >= 1 && node.lastChild?.type.name === 'hardbreak'
232
- if (lastIsHardbreak) {
233
- const contentArr: Node[] = []
234
- node.content.forEach((n, _, i) => {
235
- if (i === node.childCount - 1)
236
- return
123
+ if (level < 1)
124
+ return setBlockType(paragraphSchema.type())
237
125
 
238
- contentArr.push(n)
239
- })
240
- state.next(Fragment.fromArray(contentArr))
241
- }
242
- else {
243
- state.next(node.content)
244
- }
245
- state.closeNode()
246
- },
247
- },
126
+ return setBlockType(headingSchema.type(), { level })
127
+ }
128
+ })
129
+
130
+ /// This command can downgrade the selected heading.
131
+ /// For example, if you have a `h2` element, and you call this command, you will get a `h1` element.
132
+ /// If the element is already a `h1` element, it will turn it into a `p` element.
133
+ export const downgradeHeadingCommand = $command('DowngradeHeading', () => () =>
134
+ (state, dispatch, view) => {
135
+ const { $from } = state.selection
136
+ const node = $from.node()
137
+ if (node.type !== headingSchema.type() || !state.selection.empty || $from.parentOffset !== 0)
138
+ return false
139
+
140
+ const level = node.attrs.level - 1
141
+ if (!level)
142
+ return setBlockType(paragraphSchema.type())(state, dispatch, view)
143
+
144
+ dispatch?.(
145
+ state.tr.setNodeMarkup(state.selection.$from.before(), undefined, {
146
+ ...node.attrs,
147
+ level,
248
148
  }),
249
- inputRules: (type, ctx) =>
250
- headingIndex.map(x =>
251
- textblockTypeInputRule(new RegExp(`^(#{1,${x}})\\s$`), type, () => {
252
- const view = ctx.get(editorViewCtx)
253
- const { $from } = view.state.selection
254
- const node = $from.node()
255
- if (node.type.name === 'heading') {
256
- let level = Number(node.attrs.level) + Number(x)
257
- if (level > 6)
258
- level = 6
259
-
260
- return {
261
- level,
262
- }
263
- }
264
- return {
265
- level: x,
266
- }
267
- }),
268
- ),
269
- commands: (type, ctx) => [
270
- createCmd(TurnIntoHeading, (level = 1) => {
271
- if (level < 1)
272
- return setBlockType(level === 0 ? ctx.get(schemaCtx).nodes.paragraph || type : type)
273
-
274
- return setBlockType(level === 0 ? ctx.get(schemaCtx).nodes.paragraph || type : type, { level })
275
- }),
276
- createCmd(DowngradeHeading, () => {
277
- return (state, dispatch, view) => {
278
- const { $from } = state.selection
279
- const node = $from.node()
280
- if (node.type !== type || !state.selection.empty || $from.parentOffset !== 0)
281
- return false
282
-
283
- const level = node.attrs.level - 1
284
- if (!level)
285
- return setBlockType(ctx.get(schemaCtx).nodes.paragraph || type)(state, dispatch, view)
286
-
287
- dispatch?.(
288
- state.tr.setNodeMarkup(state.selection.$from.before(), undefined, {
289
- ...node.attrs,
290
- level,
291
- }),
292
- )
293
- return true
294
- }
295
- }),
296
- ],
297
- shortcuts: {
298
- [SupportedKeys.H1]: createShortcut(TurnIntoHeading, 'Mod-Alt-1', 1),
299
- [SupportedKeys.H2]: createShortcut(TurnIntoHeading, 'Mod-Alt-2', 2),
300
- [SupportedKeys.H3]: createShortcut(TurnIntoHeading, 'Mod-Alt-3', 3),
301
- [SupportedKeys.H4]: createShortcut(TurnIntoHeading, 'Mod-Alt-4', 4),
302
- [SupportedKeys.H5]: createShortcut(TurnIntoHeading, 'Mod-Alt-5', 5),
303
- [SupportedKeys.H6]: createShortcut(TurnIntoHeading, 'Mod-Alt-6', 6),
304
- [SupportedKeys.DowngradeHeading]: createShortcut(DowngradeHeading, ['Backspace', 'Delete']),
305
- },
306
- prosePlugins: (type, ctx) => {
307
- const plugins = [headingIdPlugin(ctx, type, getId)]
308
- if (displayHashtag)
309
- plugins.push(headingHashPlugin(ctx, type, utils))
149
+ )
150
+ return true
151
+ })
310
152
 
311
- return plugins
312
- },
313
- }
153
+ /// Keymap for heading node.
154
+ /// - `<Mod-Alt-{1-6}>`: Turn the selected block into `h{1-6}` element.
155
+ /// - `<Delete>/<Backspace>`: Downgrade the selected heading.
156
+ export const headingKeymap = $useKeymap('headingKeymap', {
157
+ TurnIntoH1: {
158
+ shortcuts: 'Mod-Alt-1',
159
+ command: (ctx) => {
160
+ const commands = ctx.get(commandsCtx)
161
+ return () => commands.call(wrapInHeadingCommand.key, 1)
162
+ },
163
+ },
164
+ TurnIntoH2: {
165
+ shortcuts: 'Mod-Alt-2',
166
+ command: (ctx) => {
167
+ const commands = ctx.get(commandsCtx)
168
+ return () => commands.call(wrapInHeadingCommand.key, 2)
169
+ },
170
+ },
171
+ TurnIntoH3: {
172
+ shortcuts: 'Mod-Alt-3',
173
+ command: (ctx) => {
174
+ const commands = ctx.get(commandsCtx)
175
+ return () => commands.call(wrapInHeadingCommand.key, 3)
176
+ },
177
+ },
178
+ TurnIntoH4: {
179
+ shortcuts: 'Mod-Alt-4',
180
+ command: (ctx) => {
181
+ const commands = ctx.get(commandsCtx)
182
+ return () => commands.call(wrapInHeadingCommand.key, 3)
183
+ },
184
+ },
185
+ TurnIntoH5: {
186
+ shortcuts: 'Mod-Alt-5',
187
+ command: (ctx) => {
188
+ const commands = ctx.get(commandsCtx)
189
+ return () => commands.call(wrapInHeadingCommand.key, 3)
190
+ },
191
+ },
192
+ TurnIntoH6: {
193
+ shortcuts: 'Mod-Alt-6',
194
+ command: (ctx) => {
195
+ const commands = ctx.get(commandsCtx)
196
+ return () => commands.call(wrapInHeadingCommand.key, 3)
197
+ },
198
+ },
199
+ DowngradeHeading: {
200
+ shortcuts: ['Delete', 'Backspace'],
201
+ command: (ctx) => {
202
+ const commands = ctx.get(commandsCtx)
203
+ return () => commands.call(downgradeHeadingCommand.key)
204
+ },
314
205
  },
315
- )
206
+ })
package/src/node/hr.ts CHANGED
@@ -1,61 +1,61 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import { createCmd, createCmdKey, schemaCtx } from '@milkdown/core'
3
2
  import { InputRule } from '@milkdown/prose/inputrules'
4
3
  import { Selection } from '@milkdown/prose/state'
5
- import { createNode } from '@milkdown/utils'
6
-
7
- const id = 'hr'
8
- export const InsertHr = createCmdKey<string>('InsertHr')
9
- export const hr = createNode((utils) => {
10
- return {
11
- id,
12
- schema: () => ({
13
- group: 'block',
14
- parseDOM: [{ tag: 'hr' }],
15
- toDOM: node => ['hr', { class: utils.getClassName(node.attrs, id) }],
16
- parseMarkdown: {
17
- match: ({ type }) => type === 'thematicBreak',
18
- runner: (state, _, type) => {
19
- state.addNode(type)
20
- },
21
- },
22
- toMarkdown: {
23
- match: node => node.type.name === id,
24
- runner: (state) => {
25
- state.addNode('thematicBreak')
26
- },
27
- },
28
- }),
29
- inputRules: type => [
30
- new InputRule(/^(?:---|___\s|\*\*\*\s)$/, (state, match, start, end) => {
31
- const { tr } = state
32
-
33
- if (match[0])
34
- tr.replaceWith(start - 1, end, type.create())
35
-
36
- return tr
37
- }),
38
- ],
39
- commands: (type, ctx) => [
40
- createCmd(InsertHr, () => (state, dispatch) => {
41
- if (!dispatch)
42
- return true
43
-
44
- const paragraph = ctx.get(schemaCtx).node('paragraph')
45
- const { tr, selection } = state
46
- const { from } = selection
47
- const node = type.create()
48
- if (!node)
49
- return true
50
-
51
- const _tr = tr.replaceSelectionWith(node).insert(from, paragraph)
52
- const sel = Selection.findFrom(_tr.doc.resolve(from), 1, true)
53
- if (!sel)
54
- return true
55
-
56
- dispatch(_tr.setSelection(sel).scrollIntoView())
57
- return true
58
- }),
59
- ],
60
- }
4
+ import { $command, $inputRule, $nodeAttr, $nodeSchema } from '@milkdown/utils'
5
+ import { paragraphSchema } from './paragraph'
6
+
7
+ /// HTML attributes for the hr node.
8
+ export const hrAttr = $nodeAttr('hr')
9
+
10
+ /// Hr node schema.
11
+ export const hrSchema = $nodeSchema('hr', ctx => ({
12
+ group: 'block',
13
+ parseDOM: [{ tag: 'hr' }],
14
+ toDOM: node => ['hr', ctx.get(hrAttr.key)(node)],
15
+ parseMarkdown: {
16
+ match: ({ type }) => type === 'thematicBreak',
17
+ runner: (state, _, type) => {
18
+ state.addNode(type)
19
+ },
20
+ },
21
+ toMarkdown: {
22
+ match: node => node.type.name === 'hr',
23
+ runner: (state) => {
24
+ state.addNode('thematicBreak')
25
+ },
26
+ },
27
+ }))
28
+
29
+ /// Input rule to insert a hr.
30
+ /// For example, `---` will be converted to a hr.
31
+ export const insertHrInputRule = $inputRule(() => new InputRule(
32
+ /^(?:---|___\s|\*\*\*\s)$/, (state, match, start, end) => {
33
+ const { tr } = state
34
+
35
+ if (match[0])
36
+ tr.replaceWith(start - 1, end, hrSchema.type().create())
37
+
38
+ return tr
39
+ },
40
+ ))
41
+
42
+ /// Command to insert a hr.
43
+ export const insertHrCommand = $command('InsertHr', () => () => (state, dispatch) => {
44
+ if (!dispatch)
45
+ return true
46
+
47
+ const paragraph = paragraphSchema.node.type().create()
48
+ const { tr, selection } = state
49
+ const { from } = selection
50
+ const node = hrSchema.type().create()
51
+ if (!node)
52
+ return true
53
+
54
+ const _tr = tr.replaceSelectionWith(node).insert(from, paragraph)
55
+ const sel = Selection.findFrom(_tr.doc.resolve(from), 1, true)
56
+ if (!sel)
57
+ return true
58
+
59
+ dispatch(_tr.setSelection(sel).scrollIntoView())
60
+ return true
61
61
  })