@jvs-milkdown/preset-commonmark 1.0.0

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/lib/__internal__/index.d.ts +3 -0
  4. package/lib/__internal__/index.d.ts.map +1 -0
  5. package/lib/__internal__/serialize-text.d.ts +4 -0
  6. package/lib/__internal__/serialize-text.d.ts.map +1 -0
  7. package/lib/__internal__/with-meta.d.ts +3 -0
  8. package/lib/__internal__/with-meta.d.ts.map +1 -0
  9. package/lib/__test__/html.spec.d.ts +2 -0
  10. package/lib/__test__/html.spec.d.ts.map +1 -0
  11. package/lib/__test__/trailing-space.spec.d.ts +2 -0
  12. package/lib/__test__/trailing-space.spec.d.ts.map +1 -0
  13. package/lib/__test__/vitest.setup.d.ts +1 -0
  14. package/lib/__test__/vitest.setup.d.ts.map +1 -0
  15. package/lib/commands/index.d.ts +20 -0
  16. package/lib/commands/index.d.ts.map +1 -0
  17. package/lib/composed/commands.d.ts +3 -0
  18. package/lib/composed/commands.d.ts.map +1 -0
  19. package/lib/composed/index.d.ts +6 -0
  20. package/lib/composed/index.d.ts.map +1 -0
  21. package/lib/composed/inputrules.d.ts +4 -0
  22. package/lib/composed/inputrules.d.ts.map +1 -0
  23. package/lib/composed/keymap.d.ts +3 -0
  24. package/lib/composed/keymap.d.ts.map +1 -0
  25. package/lib/composed/plugins.d.ts +3 -0
  26. package/lib/composed/plugins.d.ts.map +1 -0
  27. package/lib/composed/schema.d.ts +3 -0
  28. package/lib/composed/schema.d.ts.map +1 -0
  29. package/lib/index.d.ts +8 -0
  30. package/lib/index.d.ts.map +1 -0
  31. package/lib/index.js +2153 -0
  32. package/lib/index.js.map +1 -0
  33. package/lib/mark/emphasis.d.ts +7 -0
  34. package/lib/mark/emphasis.d.ts.map +1 -0
  35. package/lib/mark/index.d.ts +5 -0
  36. package/lib/mark/index.d.ts.map +1 -0
  37. package/lib/mark/inline-code.d.ts +6 -0
  38. package/lib/mark/inline-code.d.ts.map +1 -0
  39. package/lib/mark/link.d.ts +9 -0
  40. package/lib/mark/link.d.ts.map +1 -0
  41. package/lib/mark/strong.d.ts +6 -0
  42. package/lib/mark/strong.d.ts.map +1 -0
  43. package/lib/node/blockquote.d.ts +7 -0
  44. package/lib/node/blockquote.d.ts.map +1 -0
  45. package/lib/node/bullet-list.d.ts +6 -0
  46. package/lib/node/bullet-list.d.ts.map +1 -0
  47. package/lib/node/code-block.d.ts +10 -0
  48. package/lib/node/code-block.d.ts.map +1 -0
  49. package/lib/node/doc.d.ts +2 -0
  50. package/lib/node/doc.d.ts.map +1 -0
  51. package/lib/node/hardbreak.d.ts +5 -0
  52. package/lib/node/hardbreak.d.ts.map +1 -0
  53. package/lib/node/heading.d.ts +11 -0
  54. package/lib/node/heading.d.ts.map +1 -0
  55. package/lib/node/hr.d.ts +5 -0
  56. package/lib/node/hr.d.ts.map +1 -0
  57. package/lib/node/html.d.ts +3 -0
  58. package/lib/node/html.d.ts.map +1 -0
  59. package/lib/node/image.d.ts +11 -0
  60. package/lib/node/image.d.ts.map +1 -0
  61. package/lib/node/index.d.ts +14 -0
  62. package/lib/node/index.d.ts.map +1 -0
  63. package/lib/node/list-item.d.ts +8 -0
  64. package/lib/node/list-item.d.ts.map +1 -0
  65. package/lib/node/ordered-list.d.ts +6 -0
  66. package/lib/node/ordered-list.d.ts.map +1 -0
  67. package/lib/node/paragraph.d.ts +5 -0
  68. package/lib/node/paragraph.d.ts.map +1 -0
  69. package/lib/node/text.d.ts +2 -0
  70. package/lib/node/text.d.ts.map +1 -0
  71. package/lib/plugin/hardbreak-clear-mark-plugin.d.ts +2 -0
  72. package/lib/plugin/hardbreak-clear-mark-plugin.d.ts.map +1 -0
  73. package/lib/plugin/hardbreak-filter-plugin.d.ts +3 -0
  74. package/lib/plugin/hardbreak-filter-plugin.d.ts.map +1 -0
  75. package/lib/plugin/index.d.ts +12 -0
  76. package/lib/plugin/index.d.ts.map +1 -0
  77. package/lib/plugin/inline-nodes-cursor-plugin.d.ts +2 -0
  78. package/lib/plugin/inline-nodes-cursor-plugin.d.ts.map +1 -0
  79. package/lib/plugin/remark-add-order-in-list-plugin.d.ts +2 -0
  80. package/lib/plugin/remark-add-order-in-list-plugin.d.ts.map +1 -0
  81. package/lib/plugin/remark-html-transformer.d.ts +2 -0
  82. package/lib/plugin/remark-html-transformer.d.ts.map +1 -0
  83. package/lib/plugin/remark-inline-link-plugin.d.ts +2 -0
  84. package/lib/plugin/remark-inline-link-plugin.d.ts.map +1 -0
  85. package/lib/plugin/remark-line-break.d.ts +2 -0
  86. package/lib/plugin/remark-line-break.d.ts.map +1 -0
  87. package/lib/plugin/remark-marker-plugin.d.ts +2 -0
  88. package/lib/plugin/remark-marker-plugin.d.ts.map +1 -0
  89. package/lib/plugin/remark-preserve-empty-line.d.ts +2 -0
  90. package/lib/plugin/remark-preserve-empty-line.d.ts.map +1 -0
  91. package/lib/plugin/sync-heading-id-plugin.d.ts +2 -0
  92. package/lib/plugin/sync-heading-id-plugin.d.ts.map +1 -0
  93. package/lib/plugin/sync-list-order-plugin.d.ts +2 -0
  94. package/lib/plugin/sync-list-order-plugin.d.ts.map +1 -0
  95. package/lib/tsconfig.tsbuildinfo +1 -0
  96. package/package.json +44 -0
  97. package/src/__internal__/index.ts +2 -0
  98. package/src/__internal__/serialize-text.ts +21 -0
  99. package/src/__internal__/with-meta.ts +15 -0
  100. package/src/__test__/html.spec.ts +46 -0
  101. package/src/__test__/trailing-space.spec.ts +27 -0
  102. package/src/__test__/vitest.setup.ts +65 -0
  103. package/src/commands/index.ts +140 -0
  104. package/src/composed/commands.ts +72 -0
  105. package/src/composed/index.ts +5 -0
  106. package/src/composed/inputrules.ts +34 -0
  107. package/src/composed/keymap.ts +29 -0
  108. package/src/composed/plugins.ts +35 -0
  109. package/src/composed/schema.ts +92 -0
  110. package/src/index.ts +26 -0
  111. package/src/mark/emphasis.ts +130 -0
  112. package/src/mark/index.ts +4 -0
  113. package/src/mark/inline-code.ts +123 -0
  114. package/src/mark/link.ts +134 -0
  115. package/src/mark/strong.ts +130 -0
  116. package/src/node/blockquote.ts +100 -0
  117. package/src/node/bullet-list.ts +129 -0
  118. package/src/node/code-block.ts +176 -0
  119. package/src/node/doc.ts +26 -0
  120. package/src/node/hardbreak.ts +134 -0
  121. package/src/node/heading.ts +271 -0
  122. package/src/node/hr.ts +87 -0
  123. package/src/node/html.ts +66 -0
  124. package/src/node/image.ts +173 -0
  125. package/src/node/index.ts +14 -0
  126. package/src/node/list-item.ts +244 -0
  127. package/src/node/ordered-list.ts +141 -0
  128. package/src/node/paragraph.ts +136 -0
  129. package/src/node/text.ts +25 -0
  130. package/src/plugin/hardbreak-clear-mark-plugin.ts +58 -0
  131. package/src/plugin/hardbreak-filter-plugin.ts +46 -0
  132. package/src/plugin/index.ts +14 -0
  133. package/src/plugin/inline-nodes-cursor-plugin.ts +103 -0
  134. package/src/plugin/remark-add-order-in-list-plugin.ts +29 -0
  135. package/src/plugin/remark-html-transformer.ts +74 -0
  136. package/src/plugin/remark-inline-link-plugin.ts +20 -0
  137. package/src/plugin/remark-line-break.ts +69 -0
  138. package/src/plugin/remark-marker-plugin.ts +33 -0
  139. package/src/plugin/remark-preserve-empty-line.ts +49 -0
  140. package/src/plugin/sync-heading-id-plugin.ts +67 -0
  141. package/src/plugin/sync-list-order-plugin.ts +112 -0
@@ -0,0 +1,134 @@
1
+ import { commandsCtx } from '@jvs-milkdown/core'
2
+ import { Selection, TextSelection } from '@jvs-milkdown/prose/state'
3
+ import { $command, $nodeAttr, $nodeSchema, $useKeymap } from '@jvs-milkdown/utils'
4
+
5
+ import { withMeta } from '../__internal__'
6
+
7
+ /// HTML attributes for the hardbreak node.
8
+ ///
9
+ /// Default value:
10
+ /// - `data-is-inline` - Whether the hardbreak is inline.
11
+ export const hardbreakAttr = $nodeAttr('hardbreak', (node) => {
12
+ return {
13
+ 'data-type': 'hardbreak',
14
+ 'data-is-inline': node.attrs.isInline,
15
+ }
16
+ })
17
+
18
+ withMeta(hardbreakAttr, {
19
+ displayName: 'Attr<hardbreak>',
20
+ group: 'Hardbreak',
21
+ })
22
+
23
+ /// Hardbreak node schema.
24
+ export const hardbreakSchema = $nodeSchema('hardbreak', (ctx) => ({
25
+ inline: true,
26
+ group: 'inline',
27
+ attrs: {
28
+ isInline: {
29
+ default: false,
30
+ validate: 'boolean',
31
+ },
32
+ },
33
+ selectable: false,
34
+ parseDOM: [
35
+ { tag: 'br' },
36
+ {
37
+ tag: 'span[data-type="hardbreak"]',
38
+ getAttrs: () => ({ isInline: true }),
39
+ },
40
+ ],
41
+ toDOM: (node) =>
42
+ node.attrs.isInline
43
+ ? ['span', ctx.get(hardbreakAttr.key)(node), ' ']
44
+ : ['br', ctx.get(hardbreakAttr.key)(node)],
45
+ parseMarkdown: {
46
+ match: ({ type }) => type === 'break',
47
+ runner: (state, node, type) => {
48
+ state.addNode(type, {
49
+ isInline: Boolean(
50
+ (node.data as undefined | { isInline: boolean })?.isInline
51
+ ),
52
+ })
53
+ },
54
+ },
55
+ leafText: () => '\n',
56
+ toMarkdown: {
57
+ match: (node) => node.type.name === 'hardbreak',
58
+ runner: (state, node) => {
59
+ if (node.attrs.isInline) state.addNode('text', undefined, '\n')
60
+ else state.addNode('break')
61
+ },
62
+ },
63
+ }))
64
+
65
+ withMeta(hardbreakSchema.node, {
66
+ displayName: 'NodeSchema<hardbreak>',
67
+ group: 'Hardbreak',
68
+ })
69
+
70
+ withMeta(hardbreakSchema.ctx, {
71
+ displayName: 'NodeSchemaCtx<hardbreak>',
72
+ group: 'Hardbreak',
73
+ })
74
+
75
+ /// Command to insert a hardbreak.
76
+ export const insertHardbreakCommand = $command(
77
+ 'InsertHardbreak',
78
+ (ctx) => () => (state, dispatch) => {
79
+ const { selection, tr } = state
80
+ if (!(selection instanceof TextSelection)) return false
81
+
82
+ if (selection.empty) {
83
+ // Transform two successive hardbreak into a new line
84
+ const node = selection.$from.node()
85
+ if (node.childCount > 0 && node.lastChild?.type.name === 'hardbreak') {
86
+ dispatch?.(
87
+ tr
88
+ .replaceRangeWith(
89
+ selection.to - 1,
90
+ selection.to,
91
+ state.schema.node('paragraph')
92
+ )
93
+ .setSelection(Selection.near(tr.doc.resolve(selection.to)))
94
+ .scrollIntoView()
95
+ )
96
+ return true
97
+ }
98
+ }
99
+ dispatch?.(
100
+ tr
101
+ .setMeta('hardbreak', true)
102
+ .replaceSelectionWith(hardbreakSchema.type(ctx).create())
103
+ .scrollIntoView()
104
+ )
105
+ return true
106
+ }
107
+ )
108
+
109
+ withMeta(insertHardbreakCommand, {
110
+ displayName: 'Command<insertHardbreakCommand>',
111
+ group: 'Hardbreak',
112
+ })
113
+
114
+ /// Keymap for the hardbreak node.
115
+ /// - `Shift-Enter` - Insert a hardbreak.
116
+ export const hardbreakKeymap = $useKeymap('hardbreakKeymap', {
117
+ InsertHardbreak: {
118
+ shortcuts: 'Shift-Enter',
119
+ command: (ctx) => {
120
+ const commands = ctx.get(commandsCtx)
121
+ return () => commands.call(insertHardbreakCommand.key)
122
+ },
123
+ },
124
+ })
125
+
126
+ withMeta(hardbreakKeymap.ctx, {
127
+ displayName: 'KeymapCtx<hardbreak>',
128
+ group: 'Hardbreak',
129
+ })
130
+
131
+ withMeta(hardbreakKeymap.shortcuts, {
132
+ displayName: 'Keymap<hardbreak>',
133
+ group: 'Hardbreak',
134
+ })
@@ -0,0 +1,271 @@
1
+ import type { Node } from '@jvs-milkdown/prose/model'
2
+
3
+ import { commandsCtx, editorViewCtx } from '@jvs-milkdown/core'
4
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
5
+ import { setBlockType } from '@jvs-milkdown/prose/commands'
6
+ import { textblockTypeInputRule } from '@jvs-milkdown/prose/inputrules'
7
+ import {
8
+ $command,
9
+ $ctx,
10
+ $inputRule,
11
+ $nodeAttr,
12
+ $nodeSchema,
13
+ $useKeymap,
14
+ } from '@jvs-milkdown/utils'
15
+
16
+ import { serializeText, withMeta } from '../__internal__'
17
+ import { paragraphSchema } from './paragraph'
18
+
19
+ const headingIndex = Array(6)
20
+ .fill(0)
21
+ .map((_, i) => i + 1)
22
+
23
+ function defaultHeadingIdGenerator(node: Node) {
24
+ return node.textContent.toLowerCase().trim().replace(/\s+/g, '-')
25
+ }
26
+
27
+ /// This is a slice contains a function to generate heading id.
28
+ /// You can configure it to generate id in your own way.
29
+ export const headingIdGenerator = $ctx(
30
+ defaultHeadingIdGenerator,
31
+ 'headingIdGenerator'
32
+ )
33
+
34
+ withMeta(headingIdGenerator, {
35
+ displayName: 'Ctx<HeadingIdGenerator>',
36
+ group: 'Heading',
37
+ })
38
+
39
+ /// HTML attributes for heading node.
40
+ export const headingAttr = $nodeAttr('heading')
41
+
42
+ withMeta(headingAttr, {
43
+ displayName: 'Attr<heading>',
44
+ group: 'Heading',
45
+ })
46
+
47
+ /// Schema for heading node.
48
+ export const headingSchema = $nodeSchema('heading', (ctx) => {
49
+ const getId = ctx.get(headingIdGenerator.key)
50
+ return {
51
+ content: 'inline*',
52
+ group: 'block',
53
+ defining: true,
54
+ attrs: {
55
+ id: {
56
+ default: '',
57
+ validate: 'string',
58
+ },
59
+ level: {
60
+ default: 1,
61
+ validate: 'number',
62
+ },
63
+ align: { default: null },
64
+ indent: { default: 0 },
65
+ },
66
+ parseDOM: headingIndex.map((x) => ({
67
+ tag: `h${x}`,
68
+ getAttrs: (node) => {
69
+ if (!(node instanceof HTMLElement)) throw expectDomTypeError(node)
70
+
71
+ return {
72
+ level: x,
73
+ id: node.id,
74
+ align:
75
+ node.style.textAlign || node.getAttribute('data-align') || null,
76
+ indent: parseInt(node.getAttribute('data-indent') || '0', 10) || 0,
77
+ }
78
+ },
79
+ })),
80
+ toDOM: (node) => {
81
+ const { align, indent } = node.attrs
82
+ const attrs = {
83
+ ...ctx.get(headingAttr.key)(node),
84
+ id: node.attrs.id || getId(node),
85
+ } as Record<string, string>
86
+
87
+ if (align) {
88
+ attrs.style = (attrs.style || '') + `text-align: ${align};`
89
+ attrs['data-align'] = align
90
+ }
91
+ if (indent) {
92
+ attrs.style = (attrs.style || '') + `margin-left: ${indent * 2}em;`
93
+ attrs['data-indent'] = indent.toString()
94
+ }
95
+
96
+ return [`h${node.attrs.level}`, attrs, 0]
97
+ },
98
+ parseMarkdown: {
99
+ match: ({ type }) => type === 'heading',
100
+ runner: (state, node, type) => {
101
+ const depth = node.depth as number
102
+ state.openNode(type, { level: depth })
103
+ state.next(node.children)
104
+ state.closeNode()
105
+ },
106
+ },
107
+ toMarkdown: {
108
+ match: (node) => node.type.name === 'heading',
109
+ runner: (state, node) => {
110
+ state.openNode('heading', undefined, { depth: node.attrs.level })
111
+ serializeText(state, node)
112
+ state.closeNode()
113
+ },
114
+ },
115
+ }
116
+ })
117
+
118
+ withMeta(headingSchema.node, {
119
+ displayName: 'NodeSchema<heading>',
120
+ group: 'Heading',
121
+ })
122
+
123
+ withMeta(headingSchema.ctx, {
124
+ displayName: 'NodeSchemaCtx<heading>',
125
+ group: 'Heading',
126
+ })
127
+
128
+ /// This input rule can turn the selected block into heading.
129
+ /// You can input numbers of `#` and a `space` to create heading.
130
+ export const wrapInHeadingInputRule = $inputRule((ctx) => {
131
+ return textblockTypeInputRule(
132
+ /^(?<hashes>#+)\s$/,
133
+ headingSchema.type(ctx),
134
+ (match) => {
135
+ const x = match.groups?.hashes?.length || 0
136
+
137
+ const view = ctx.get(editorViewCtx)
138
+ const { $from } = view.state.selection
139
+ const node = $from.node()
140
+ if (node.type.name === 'heading') {
141
+ let level = Number(node.attrs.level) + Number(x)
142
+ if (level > 6) level = 6
143
+
144
+ return { level }
145
+ }
146
+ return { level: x }
147
+ }
148
+ )
149
+ })
150
+
151
+ withMeta(wrapInHeadingInputRule, {
152
+ displayName: 'InputRule<wrapInHeadingInputRule>',
153
+ group: 'Heading',
154
+ })
155
+
156
+ /// This command can turn the selected block into heading.
157
+ /// You can pass the level of heading to this command.
158
+ /// By default, the level is 1, which means it will create a `h1` element.
159
+ export const wrapInHeadingCommand = $command('WrapInHeading', (ctx) => {
160
+ return (level?: number) => {
161
+ level ??= 1
162
+
163
+ if (level < 1) return setBlockType(paragraphSchema.type(ctx))
164
+
165
+ return setBlockType(headingSchema.type(ctx), { level })
166
+ }
167
+ })
168
+
169
+ withMeta(wrapInHeadingCommand, {
170
+ displayName: 'Command<wrapInHeadingCommand>',
171
+ group: 'Heading',
172
+ })
173
+
174
+ /// This command can downgrade the selected heading.
175
+ /// For example, if you have a `h2` element, and you call this command, you will get a `h1` element.
176
+ /// If the element is already a `h1` element, it will turn it into a `p` element.
177
+ export const downgradeHeadingCommand = $command(
178
+ 'DowngradeHeading',
179
+ (ctx) => () => (state, dispatch, view) => {
180
+ const { $from } = state.selection
181
+ const node = $from.node()
182
+ if (
183
+ node.type !== headingSchema.type(ctx) ||
184
+ !state.selection.empty ||
185
+ $from.parentOffset !== 0
186
+ )
187
+ return false
188
+
189
+ const level = node.attrs.level - 1
190
+ if (!level)
191
+ return setBlockType(paragraphSchema.type(ctx))(state, dispatch, view)
192
+
193
+ dispatch?.(
194
+ state.tr.setNodeMarkup(state.selection.$from.before(), undefined, {
195
+ ...node.attrs,
196
+ level,
197
+ })
198
+ )
199
+ return true
200
+ }
201
+ )
202
+
203
+ withMeta(downgradeHeadingCommand, {
204
+ displayName: 'Command<downgradeHeadingCommand>',
205
+ group: 'Heading',
206
+ })
207
+
208
+ /// Keymap for heading node.
209
+ /// - `<Mod-Alt-{1-6}>`: Turn the selected block into `h{1-6}` element.
210
+ /// - `<Delete>/<Backspace>`: Downgrade the selected heading.
211
+ export const headingKeymap = $useKeymap('headingKeymap', {
212
+ TurnIntoH1: {
213
+ shortcuts: 'Mod-Alt-1',
214
+ command: (ctx) => {
215
+ const commands = ctx.get(commandsCtx)
216
+ return () => commands.call(wrapInHeadingCommand.key, 1)
217
+ },
218
+ },
219
+ TurnIntoH2: {
220
+ shortcuts: 'Mod-Alt-2',
221
+ command: (ctx) => {
222
+ const commands = ctx.get(commandsCtx)
223
+ return () => commands.call(wrapInHeadingCommand.key, 2)
224
+ },
225
+ },
226
+ TurnIntoH3: {
227
+ shortcuts: 'Mod-Alt-3',
228
+ command: (ctx) => {
229
+ const commands = ctx.get(commandsCtx)
230
+ return () => commands.call(wrapInHeadingCommand.key, 3)
231
+ },
232
+ },
233
+ TurnIntoH4: {
234
+ shortcuts: 'Mod-Alt-4',
235
+ command: (ctx) => {
236
+ const commands = ctx.get(commandsCtx)
237
+ return () => commands.call(wrapInHeadingCommand.key, 4)
238
+ },
239
+ },
240
+ TurnIntoH5: {
241
+ shortcuts: 'Mod-Alt-5',
242
+ command: (ctx) => {
243
+ const commands = ctx.get(commandsCtx)
244
+ return () => commands.call(wrapInHeadingCommand.key, 5)
245
+ },
246
+ },
247
+ TurnIntoH6: {
248
+ shortcuts: 'Mod-Alt-6',
249
+ command: (ctx) => {
250
+ const commands = ctx.get(commandsCtx)
251
+ return () => commands.call(wrapInHeadingCommand.key, 6)
252
+ },
253
+ },
254
+ DowngradeHeading: {
255
+ shortcuts: ['Delete', 'Backspace'],
256
+ command: (ctx) => {
257
+ const commands = ctx.get(commandsCtx)
258
+ return () => commands.call(downgradeHeadingCommand.key)
259
+ },
260
+ },
261
+ })
262
+
263
+ withMeta(headingKeymap.ctx, {
264
+ displayName: 'KeymapCtx<heading>',
265
+ group: 'Heading',
266
+ })
267
+
268
+ withMeta(headingKeymap.shortcuts, {
269
+ displayName: 'Keymap<heading>',
270
+ group: 'Heading',
271
+ })
package/src/node/hr.ts ADDED
@@ -0,0 +1,87 @@
1
+ import { InputRule } from '@jvs-milkdown/prose/inputrules'
2
+ import { Selection } from '@jvs-milkdown/prose/state'
3
+ import { $command, $inputRule, $nodeAttr, $nodeSchema } from '@jvs-milkdown/utils'
4
+
5
+ import { withMeta } from '../__internal__'
6
+ import { paragraphSchema } from './paragraph'
7
+
8
+ /// HTML attributes for the hr node.
9
+ export const hrAttr = $nodeAttr('hr')
10
+
11
+ withMeta(hrAttr, {
12
+ displayName: 'Attr<hr>',
13
+ group: 'Hr',
14
+ })
15
+
16
+ /// Hr node schema.
17
+ export const hrSchema = $nodeSchema('hr', (ctx) => ({
18
+ group: 'block',
19
+ parseDOM: [{ tag: 'hr' }],
20
+ toDOM: (node) => ['hr', ctx.get(hrAttr.key)(node)],
21
+ parseMarkdown: {
22
+ match: ({ type }) => type === 'thematicBreak',
23
+ runner: (state, _, type) => {
24
+ state.addNode(type)
25
+ },
26
+ },
27
+ toMarkdown: {
28
+ match: (node) => node.type.name === 'hr',
29
+ runner: (state) => {
30
+ state.addNode('thematicBreak')
31
+ },
32
+ },
33
+ }))
34
+
35
+ withMeta(hrSchema.node, {
36
+ displayName: 'NodeSchema<hr>',
37
+ group: 'Hr',
38
+ })
39
+
40
+ withMeta(hrSchema.ctx, {
41
+ displayName: 'NodeSchemaCtx<hr>',
42
+ group: 'Hr',
43
+ })
44
+
45
+ /// Input rule to insert a hr.
46
+ /// For example, `---` will be converted to a hr.
47
+ export const insertHrInputRule = $inputRule(
48
+ (ctx) =>
49
+ new InputRule(/^(?:---|___\s|\*\*\*\s)$/, (state, match, start, end) => {
50
+ const { tr } = state
51
+
52
+ if (match[0]) tr.replaceWith(start - 1, end, hrSchema.type(ctx).create())
53
+
54
+ return tr
55
+ })
56
+ )
57
+
58
+ withMeta(insertHrInputRule, {
59
+ displayName: 'InputRule<insertHrInputRule>',
60
+ group: 'Hr',
61
+ })
62
+
63
+ /// Command to insert a hr.
64
+ export const insertHrCommand = $command(
65
+ 'InsertHr',
66
+ (ctx) => () => (state, dispatch) => {
67
+ if (!dispatch) return true
68
+
69
+ const paragraph = paragraphSchema.node.type(ctx).create()
70
+ const { tr, selection } = state
71
+ const { from } = selection
72
+ const node = hrSchema.type(ctx).create()
73
+ if (!node) return true
74
+
75
+ const _tr = tr.replaceSelectionWith(node).insert(from, paragraph)
76
+ const sel = Selection.findFrom(_tr.doc.resolve(from), 1, true)
77
+ if (!sel) return true
78
+
79
+ dispatch(_tr.setSelection(sel).scrollIntoView())
80
+ return true
81
+ }
82
+ )
83
+
84
+ withMeta(insertHrCommand, {
85
+ displayName: 'Command<insertHrCommand>',
86
+ group: 'Hr',
87
+ })
@@ -0,0 +1,66 @@
1
+ import { $nodeAttr, $nodeSchema } from '@jvs-milkdown/utils'
2
+
3
+ import { withMeta } from '../__internal__'
4
+
5
+ export const htmlAttr = $nodeAttr('html')
6
+
7
+ withMeta(htmlAttr, {
8
+ displayName: 'Attr<html>',
9
+ group: 'Html',
10
+ })
11
+
12
+ export const htmlSchema = $nodeSchema('html', (ctx) => {
13
+ return {
14
+ atom: true,
15
+ group: 'inline',
16
+ inline: true,
17
+ attrs: {
18
+ value: {
19
+ default: '',
20
+ validate: 'string',
21
+ },
22
+ },
23
+ toDOM: (node) => {
24
+ const span = document.createElement('span')
25
+ const attr = {
26
+ ...ctx.get(htmlAttr.key)(node),
27
+ 'data-value': node.attrs.value,
28
+ 'data-type': 'html',
29
+ }
30
+ span.textContent = node.attrs.value
31
+ return ['span', attr, node.attrs.value]
32
+ },
33
+ parseDOM: [
34
+ {
35
+ tag: 'span[data-type="html"]',
36
+ getAttrs: (dom) => {
37
+ return {
38
+ value: dom.dataset.value ?? '',
39
+ }
40
+ },
41
+ },
42
+ ],
43
+ parseMarkdown: {
44
+ match: ({ type }) => Boolean(type === 'html'),
45
+ runner: (state, node, type) => {
46
+ state.addNode(type, { value: node.value as string })
47
+ },
48
+ },
49
+ toMarkdown: {
50
+ match: (node) => node.type.name === 'html',
51
+ runner: (state, node) => {
52
+ state.addNode('html', undefined, node.attrs.value)
53
+ },
54
+ },
55
+ }
56
+ })
57
+
58
+ withMeta(htmlSchema.node, {
59
+ displayName: 'NodeSchema<html>',
60
+ group: 'Html',
61
+ })
62
+
63
+ withMeta(htmlSchema.ctx, {
64
+ displayName: 'NodeSchemaCtx<html>',
65
+ group: 'Html',
66
+ })