@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,173 @@
1
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
2
+ import { findSelectedNodeOfType } from '@jvs-milkdown/prose'
3
+ import { InputRule } from '@jvs-milkdown/prose/inputrules'
4
+ import { $command, $inputRule, $nodeAttr, $nodeSchema } from '@jvs-milkdown/utils'
5
+
6
+ import { withMeta } from '../__internal__'
7
+
8
+ /// HTML attributes for image node.
9
+ export const imageAttr = $nodeAttr('image')
10
+
11
+ withMeta(imageAttr, {
12
+ displayName: 'Attr<image>',
13
+ group: 'Image',
14
+ })
15
+
16
+ /// Schema for image node.
17
+ export const imageSchema = $nodeSchema('image', (ctx) => {
18
+ return {
19
+ inline: true,
20
+ group: 'inline',
21
+ selectable: true,
22
+ draggable: true,
23
+ marks: '',
24
+ atom: true,
25
+ defining: true,
26
+ isolating: true,
27
+ attrs: {
28
+ src: { default: '', validate: 'string' },
29
+ alt: { default: '', validate: 'string' },
30
+ title: { default: '', validate: 'string' },
31
+ },
32
+ parseDOM: [
33
+ {
34
+ tag: 'img[src]',
35
+ getAttrs: (dom) => {
36
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
37
+
38
+ return {
39
+ src: dom.getAttribute('src') || '',
40
+ alt: dom.getAttribute('alt') || '',
41
+ title: dom.getAttribute('title') || dom.getAttribute('alt') || '',
42
+ }
43
+ },
44
+ },
45
+ ],
46
+ toDOM: (node) => {
47
+ return ['img', { ...ctx.get(imageAttr.key)(node), ...node.attrs }]
48
+ },
49
+ parseMarkdown: {
50
+ match: ({ type }) => type === 'image',
51
+ runner: (state, node, type) => {
52
+ const url = node.url as string
53
+ const alt = node.alt as string
54
+ const title = node.title as string
55
+ state.addNode(type, {
56
+ src: url,
57
+ alt,
58
+ title,
59
+ })
60
+ },
61
+ },
62
+ toMarkdown: {
63
+ match: (node) => node.type.name === 'image',
64
+ runner: (state, node) => {
65
+ state.addNode('image', undefined, undefined, {
66
+ title: node.attrs.title,
67
+ url: node.attrs.src,
68
+ alt: node.attrs.alt,
69
+ })
70
+ },
71
+ },
72
+ }
73
+ })
74
+
75
+ withMeta(imageSchema.node, {
76
+ displayName: 'NodeSchema<image>',
77
+ group: 'Image',
78
+ })
79
+
80
+ withMeta(imageSchema.ctx, {
81
+ displayName: 'NodeSchemaCtx<image>',
82
+ group: 'Image',
83
+ })
84
+
85
+ /// @internal
86
+ export interface UpdateImageCommandPayload {
87
+ src?: string
88
+ title?: string
89
+ alt?: string
90
+ }
91
+
92
+ /// This command will insert a image node.
93
+ /// You can pass a payload to set `src`, `alt` and `title` for the image node.
94
+ export const insertImageCommand = $command(
95
+ 'InsertImage',
96
+ (ctx) =>
97
+ (payload: UpdateImageCommandPayload = {}) =>
98
+ (state, dispatch) => {
99
+ if (!dispatch) return true
100
+
101
+ const { src = '', alt = '', title = '' } = payload
102
+
103
+ const node = imageSchema.type(ctx).create({ src, alt, title })
104
+ if (!node) return true
105
+
106
+ dispatch(state.tr.replaceSelectionWith(node).scrollIntoView())
107
+ return true
108
+ }
109
+ )
110
+
111
+ withMeta(insertImageCommand, {
112
+ displayName: 'Command<insertImageCommand>',
113
+ group: 'Image',
114
+ })
115
+
116
+ /// This command will update the selected image node.
117
+ /// You can pass a payload to update `src`, `alt` and `title` for the image node.
118
+ export const updateImageCommand = $command(
119
+ 'UpdateImage',
120
+ (ctx) =>
121
+ (payload: UpdateImageCommandPayload = {}) =>
122
+ (state, dispatch) => {
123
+ const nodeWithPos = findSelectedNodeOfType(
124
+ state.selection,
125
+ imageSchema.type(ctx)
126
+ )
127
+ if (!nodeWithPos) return false
128
+
129
+ const { node, pos } = nodeWithPos
130
+
131
+ const newAttrs = { ...node.attrs }
132
+ const { src, alt, title } = payload
133
+ if (src !== undefined) newAttrs.src = src
134
+ if (alt !== undefined) newAttrs.alt = alt
135
+ if (title !== undefined) newAttrs.title = title
136
+
137
+ dispatch?.(
138
+ state.tr.setNodeMarkup(pos, undefined, newAttrs).scrollIntoView()
139
+ )
140
+ return true
141
+ }
142
+ )
143
+
144
+ withMeta(updateImageCommand, {
145
+ displayName: 'Command<updateImageCommand>',
146
+ group: 'Image',
147
+ })
148
+
149
+ /// This input rule will insert a image node.
150
+ /// You can input `![alt](src "title")` to insert a image node.
151
+ /// The `title` is optional.
152
+ export const insertImageInputRule = $inputRule(
153
+ (ctx) =>
154
+ new InputRule(
155
+ /!\[(?<alt>.*?)]\((?<filename>.*?)\s*(?="|\))"?(?<title>[^"]+)?"?\)/,
156
+ (state, match, start, end) => {
157
+ const [matched, alt, src = '', title] = match
158
+ if (matched)
159
+ return state.tr.replaceWith(
160
+ start,
161
+ end,
162
+ imageSchema.type(ctx).create({ src, alt, title })
163
+ )
164
+
165
+ return null
166
+ }
167
+ )
168
+ )
169
+
170
+ withMeta(insertImageInputRule, {
171
+ displayName: 'InputRule<insertImageInputRule>',
172
+ group: 'Image',
173
+ })
@@ -0,0 +1,14 @@
1
+ export * from './doc'
2
+ export * from './heading'
3
+ export * from './blockquote'
4
+ export * from './code-block'
5
+ export * from './image'
6
+ export * from './hardbreak'
7
+ export * from './hr'
8
+ export * from './bullet-list'
9
+ export * from './ordered-list'
10
+ export * from './list-item'
11
+ export * from './paragraph'
12
+ export * from './text'
13
+
14
+ export * from './html'
@@ -0,0 +1,244 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+
3
+ import { commandsCtx } from '@jvs-milkdown/core'
4
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
5
+ import { joinBackward } from '@jvs-milkdown/prose/commands'
6
+ import {
7
+ liftListItem,
8
+ sinkListItem,
9
+ splitListItem,
10
+ } from '@jvs-milkdown/prose/schema-list'
11
+ import { type Command, TextSelection } from '@jvs-milkdown/prose/state'
12
+ import { $command, $nodeAttr, $nodeSchema, $useKeymap } from '@jvs-milkdown/utils'
13
+
14
+ import { withMeta } from '../__internal__'
15
+
16
+ /// HTML attributes for list item node.
17
+ export const listItemAttr = $nodeAttr('listItem')
18
+
19
+ withMeta(listItemAttr, {
20
+ displayName: 'Attr<listItem>',
21
+ group: 'ListItem',
22
+ })
23
+
24
+ /// Schema for list item node.
25
+ export const listItemSchema = $nodeSchema('list_item', (ctx) => ({
26
+ group: 'listItem',
27
+ content: 'paragraph block*',
28
+ attrs: {
29
+ label: {
30
+ default: '•',
31
+ validate: 'string',
32
+ },
33
+ listType: {
34
+ default: 'bullet',
35
+ validate: 'string',
36
+ },
37
+ spread: {
38
+ default: true,
39
+ validate: 'boolean',
40
+ },
41
+ },
42
+ defining: true,
43
+ parseDOM: [
44
+ {
45
+ tag: 'li',
46
+ getAttrs: (dom) => {
47
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
48
+
49
+ return {
50
+ label: dom.dataset.label,
51
+ listType: dom.dataset.listType,
52
+ spread: dom.dataset.spread === 'true',
53
+ }
54
+ },
55
+ },
56
+ ],
57
+ toDOM: (node) => [
58
+ 'li',
59
+ {
60
+ ...ctx.get(listItemAttr.key)(node),
61
+ 'data-label': node.attrs.label,
62
+ 'data-list-type': node.attrs.listType,
63
+ 'data-spread': node.attrs.spread,
64
+ },
65
+ 0,
66
+ ],
67
+ parseMarkdown: {
68
+ match: ({ type }) => type === 'listItem',
69
+ runner: (state, node, type) => {
70
+ const label = node.label != null ? `${node.label}.` : '•'
71
+ const listType = node.label != null ? 'ordered' : 'bullet'
72
+ const spread = node.spread != null ? `${node.spread}` : 'true'
73
+ state.openNode(type, { label, listType, spread })
74
+ state.next(node.children)
75
+ state.closeNode()
76
+ },
77
+ },
78
+ toMarkdown: {
79
+ match: (node) => node.type.name === 'list_item',
80
+ runner: (state, node) => {
81
+ state.openNode('listItem', undefined, {
82
+ spread: node.attrs.spread,
83
+ })
84
+ state.next(node.content)
85
+ state.closeNode()
86
+ },
87
+ },
88
+ }))
89
+
90
+ withMeta(listItemSchema.node, {
91
+ displayName: 'NodeSchema<listItem>',
92
+ group: 'ListItem',
93
+ })
94
+
95
+ withMeta(listItemSchema.ctx, {
96
+ displayName: 'NodeSchemaCtx<listItem>',
97
+ group: 'ListItem',
98
+ })
99
+
100
+ /// The command to sink 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
+ /// ```
112
+ export const sinkListItemCommand = $command(
113
+ 'SinkListItem',
114
+ (ctx) => () => sinkListItem(listItemSchema.type(ctx))
115
+ )
116
+
117
+ withMeta(sinkListItemCommand, {
118
+ displayName: 'Command<sinkListItemCommand>',
119
+ group: 'ListItem',
120
+ })
121
+
122
+ /// The command to lift list item.
123
+ ///
124
+ /// For example:
125
+ /// ```md
126
+ /// * List item 1
127
+ /// * List item 2 <- cursor here
128
+ /// ```
129
+ /// Will get:
130
+ /// ```md
131
+ /// * List item 1
132
+ /// * List item 2
133
+ /// ```
134
+ export const liftListItemCommand = $command(
135
+ 'LiftListItem',
136
+ (ctx) => () => liftListItem(listItemSchema.type(ctx))
137
+ )
138
+
139
+ withMeta(liftListItemCommand, {
140
+ displayName: 'Command<liftListItemCommand>',
141
+ group: 'ListItem',
142
+ })
143
+
144
+ /// The command to split a list item.
145
+ ///
146
+ /// For example:
147
+ /// ```md
148
+ /// * List item 1
149
+ /// * List item 2 <- cursor here
150
+ /// ```
151
+ /// Will get:
152
+ /// ```md
153
+ /// * List item 1
154
+ /// * List item 2
155
+ /// * <- cursor here
156
+ /// ```
157
+ export const splitListItemCommand = $command(
158
+ 'SplitListItem',
159
+ (ctx) => () => splitListItem(listItemSchema.type(ctx))
160
+ )
161
+
162
+ withMeta(splitListItemCommand, {
163
+ displayName: 'Command<splitListItemCommand>',
164
+ group: 'ListItem',
165
+ })
166
+
167
+ function liftFirstListItem(ctx: Ctx): Command {
168
+ return (state, dispatch, view) => {
169
+ const { selection } = state
170
+ if (!(selection instanceof TextSelection)) return false
171
+
172
+ const { empty, $from } = selection
173
+
174
+ // selection should be empty and at the start of the node
175
+ if (!empty || $from.parentOffset !== 0) return false
176
+
177
+ const parentItem = $from.node(-1)
178
+ // selection should be in list item
179
+ if (parentItem.type !== listItemSchema.type(ctx)) return false
180
+
181
+ return joinBackward(state, dispatch, view)
182
+ }
183
+ }
184
+
185
+ /// The command to remove list item **only if**:
186
+ ///
187
+ /// - Selection is at the start of the list item.
188
+ /// - List item is the only child of the list.
189
+ ///
190
+ /// Most of the time, you shouldn't use this command directly.
191
+ export const liftFirstListItemCommand = $command(
192
+ 'LiftFirstListItem',
193
+ (ctx) => () => liftFirstListItem(ctx)
194
+ )
195
+
196
+ withMeta(liftFirstListItemCommand, {
197
+ displayName: 'Command<liftFirstListItemCommand>',
198
+ group: 'ListItem',
199
+ })
200
+
201
+ /// Keymap for list item node.
202
+ /// - `<Enter>`: Split the current list item.
203
+ /// - `<Tab>/<Mod-]>`: Sink the current list item.
204
+ /// - `<Shift-Tab>/<Mod-[>`: Lift the current list item.
205
+ export const listItemKeymap = $useKeymap('listItemKeymap', {
206
+ NextListItem: {
207
+ shortcuts: 'Enter',
208
+ command: (ctx) => {
209
+ const commands = ctx.get(commandsCtx)
210
+ return () => commands.call(splitListItemCommand.key)
211
+ },
212
+ },
213
+ SinkListItem: {
214
+ shortcuts: ['Tab', 'Mod-]'],
215
+ command: (ctx) => {
216
+ const commands = ctx.get(commandsCtx)
217
+ return () => commands.call(sinkListItemCommand.key)
218
+ },
219
+ },
220
+ LiftListItem: {
221
+ shortcuts: ['Shift-Tab', 'Mod-['],
222
+ command: (ctx) => {
223
+ const commands = ctx.get(commandsCtx)
224
+ return () => commands.call(liftListItemCommand.key)
225
+ },
226
+ },
227
+ LiftFirstListItem: {
228
+ shortcuts: ['Backspace', 'Delete'],
229
+ command: (ctx) => {
230
+ const commands = ctx.get(commandsCtx)
231
+ return () => commands.call(liftFirstListItemCommand.key)
232
+ },
233
+ },
234
+ })
235
+
236
+ withMeta(listItemKeymap.ctx, {
237
+ displayName: 'KeymapCtx<listItem>',
238
+ group: 'ListItem',
239
+ })
240
+
241
+ withMeta(listItemKeymap.shortcuts, {
242
+ displayName: 'Keymap<listItem>',
243
+ group: 'ListItem',
244
+ })
@@ -0,0 +1,141 @@
1
+ import { commandsCtx } from '@jvs-milkdown/core'
2
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
3
+ import { wrapIn } from '@jvs-milkdown/prose/commands'
4
+ import { wrappingInputRule } from '@jvs-milkdown/prose/inputrules'
5
+ import {
6
+ $command,
7
+ $inputRule,
8
+ $nodeAttr,
9
+ $nodeSchema,
10
+ $useKeymap,
11
+ } from '@jvs-milkdown/utils'
12
+
13
+ import { withMeta } from '../__internal__'
14
+
15
+ /// HTML attributes for ordered list node.
16
+ export const orderedListAttr = $nodeAttr('orderedList')
17
+
18
+ withMeta(orderedListAttr, {
19
+ displayName: 'Attr<orderedList>',
20
+ group: 'OrderedList',
21
+ })
22
+
23
+ /// Schema for ordered list node.
24
+ export const orderedListSchema = $nodeSchema('ordered_list', (ctx) => ({
25
+ content: 'listItem+',
26
+ group: 'block',
27
+ attrs: {
28
+ order: {
29
+ default: 1,
30
+ validate: 'number',
31
+ },
32
+ spread: {
33
+ default: false,
34
+ validate: 'boolean',
35
+ },
36
+ },
37
+ parseDOM: [
38
+ {
39
+ tag: 'ol',
40
+ getAttrs: (dom) => {
41
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
42
+
43
+ return {
44
+ spread: dom.dataset.spread,
45
+ order: dom.hasAttribute('start')
46
+ ? Number(dom.getAttribute('start'))
47
+ : 1,
48
+ }
49
+ },
50
+ },
51
+ ],
52
+ toDOM: (node) => [
53
+ 'ol',
54
+ {
55
+ ...ctx.get(orderedListAttr.key)(node),
56
+ ...(node.attrs.order === 1 ? {} : { start: node.attrs.order }),
57
+ 'data-spread': node.attrs.spread,
58
+ },
59
+ 0,
60
+ ],
61
+ parseMarkdown: {
62
+ match: ({ type, ordered }) => type === 'list' && !!ordered,
63
+ runner: (state, node, type) => {
64
+ const spread = node.spread != null ? `${node.spread}` : 'true'
65
+ state
66
+ .openNode(type, { spread, order: node.start ?? 1 })
67
+ .next(node.children)
68
+ .closeNode()
69
+ },
70
+ },
71
+ toMarkdown: {
72
+ match: (node) => node.type.name === 'ordered_list',
73
+ runner: (state, node) => {
74
+ state.openNode('list', undefined, {
75
+ ordered: true,
76
+ start: node.attrs.order ?? 1,
77
+ spread: node.attrs.spread === 'true',
78
+ })
79
+ state.next(node.content)
80
+ state.closeNode()
81
+ },
82
+ },
83
+ }))
84
+
85
+ withMeta(orderedListSchema.node, {
86
+ displayName: 'NodeSchema<orderedList>',
87
+ group: 'OrderedList',
88
+ })
89
+
90
+ withMeta(orderedListSchema.ctx, {
91
+ displayName: 'NodeSchemaCtx<orderedList>',
92
+ group: 'OrderedList',
93
+ })
94
+
95
+ /// Input rule for wrapping a block in ordered list node.
96
+ export const wrapInOrderedListInputRule = $inputRule((ctx) =>
97
+ wrappingInputRule(
98
+ /^\s*(\d+)\.\s$/,
99
+ orderedListSchema.type(ctx),
100
+ (match) => ({ order: Number(match[1]) }),
101
+ (match, node) => node.childCount + node.attrs.order === Number(match[1])
102
+ )
103
+ )
104
+
105
+ withMeta(wrapInOrderedListInputRule, {
106
+ displayName: 'InputRule<wrapInOrderedListInputRule>',
107
+ group: 'OrderedList',
108
+ })
109
+
110
+ /// Command for wrapping a block in ordered list node.
111
+ export const wrapInOrderedListCommand = $command(
112
+ 'WrapInOrderedList',
113
+ (ctx) => () => wrapIn(orderedListSchema.type(ctx))
114
+ )
115
+
116
+ withMeta(wrapInOrderedListCommand, {
117
+ displayName: 'Command<wrapInOrderedListCommand>',
118
+ group: 'OrderedList',
119
+ })
120
+
121
+ /// Keymap for ordered list node.
122
+ /// - `Mod-Alt-7`: Wrap a block in ordered list.
123
+ export const orderedListKeymap = $useKeymap('orderedListKeymap', {
124
+ WrapInOrderedList: {
125
+ shortcuts: 'Mod-Alt-7',
126
+ command: (ctx) => {
127
+ const commands = ctx.get(commandsCtx)
128
+ return () => commands.call(wrapInOrderedListCommand.key)
129
+ },
130
+ },
131
+ })
132
+
133
+ withMeta(orderedListKeymap.ctx, {
134
+ displayName: 'KeymapCtx<orderedList>',
135
+ group: 'OrderedList',
136
+ })
137
+
138
+ withMeta(orderedListKeymap.shortcuts, {
139
+ displayName: 'Keymap<orderedList>',
140
+ group: 'OrderedList',
141
+ })
@@ -0,0 +1,136 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+
3
+ import { commandsCtx, editorViewCtx } from '@jvs-milkdown/core'
4
+ import { setBlockType } from '@jvs-milkdown/prose/commands'
5
+ import { $command, $nodeAttr, $nodeSchema, $useKeymap } from '@jvs-milkdown/utils'
6
+
7
+ import { serializeText, withMeta } from '../__internal__'
8
+ import { remarkPreserveEmptyLinePlugin } from '../plugin/remark-preserve-empty-line'
9
+
10
+ /// HTML attributes for paragraph node.
11
+ export const paragraphAttr = $nodeAttr('paragraph')
12
+
13
+ withMeta(paragraphAttr, {
14
+ displayName: 'Attr<paragraph>',
15
+ group: 'Paragraph',
16
+ })
17
+
18
+ /// Schema for paragraph node.
19
+ export const paragraphSchema = $nodeSchema('paragraph', (ctx) => ({
20
+ content: 'inline*',
21
+ group: 'block',
22
+ attrs: {
23
+ align: { default: null },
24
+ indent: { default: 0 },
25
+ },
26
+ parseDOM: [
27
+ {
28
+ tag: 'p',
29
+ getAttrs: (dom) => {
30
+ if (!(dom instanceof HTMLElement)) return null
31
+ return {
32
+ align: dom.style.textAlign || dom.getAttribute('data-align') || null,
33
+ indent: parseInt(dom.getAttribute('data-indent') || '0', 10) || 0,
34
+ }
35
+ },
36
+ },
37
+ ],
38
+ toDOM: (node) => {
39
+ const { align, indent } = node.attrs
40
+ const attrs = { ...ctx.get(paragraphAttr.key)(node) } as Record<
41
+ string,
42
+ string
43
+ >
44
+ if (align) {
45
+ attrs.style = (attrs.style || '') + `text-align: ${align};`
46
+ attrs['data-align'] = align
47
+ }
48
+ if (indent) {
49
+ attrs.style = (attrs.style || '') + `margin-left: ${indent * 2}em;`
50
+ attrs['data-indent'] = indent.toString()
51
+ }
52
+ return ['p', attrs, 0]
53
+ },
54
+ parseMarkdown: {
55
+ match: (node) => node.type === 'paragraph',
56
+ runner: (state, node, type) => {
57
+ state.openNode(type)
58
+ if (node.children) state.next(node.children)
59
+ else state.addText((node.value || '') as string)
60
+
61
+ state.closeNode()
62
+ },
63
+ },
64
+ toMarkdown: {
65
+ match: (node) => node.type.name === 'paragraph',
66
+ runner: (state, node) => {
67
+ const view = ctx.get(editorViewCtx)
68
+ const lastNode = view.state?.doc.lastChild
69
+
70
+ state.openNode('paragraph')
71
+ if (
72
+ (!node.content || node.content.size === 0) &&
73
+ node !== lastNode &&
74
+ shouldPreserveEmptyLine(ctx)
75
+ ) {
76
+ state.addNode('html', undefined, '<br />')
77
+ } else {
78
+ serializeText(state, node)
79
+ }
80
+ state.closeNode()
81
+ },
82
+ },
83
+ }))
84
+
85
+ function shouldPreserveEmptyLine(ctx: Ctx) {
86
+ let shouldPreserveEmptyLine = false
87
+ try {
88
+ ctx.get(remarkPreserveEmptyLinePlugin.id)
89
+ shouldPreserveEmptyLine = true
90
+ } catch {
91
+ shouldPreserveEmptyLine = false
92
+ }
93
+ return shouldPreserveEmptyLine
94
+ }
95
+
96
+ withMeta(paragraphSchema.node, {
97
+ displayName: 'NodeSchema<paragraph>',
98
+ group: 'Paragraph',
99
+ })
100
+ withMeta(paragraphSchema.ctx, {
101
+ displayName: 'NodeSchemaCtx<paragraph>',
102
+ group: 'Paragraph',
103
+ })
104
+
105
+ /// This command can turn the selected block into paragraph.
106
+ export const turnIntoTextCommand = $command(
107
+ 'TurnIntoText',
108
+ (ctx) => () => setBlockType(paragraphSchema.type(ctx))
109
+ )
110
+
111
+ withMeta(turnIntoTextCommand, {
112
+ displayName: 'Command<turnIntoTextCommand>',
113
+ group: 'Paragraph',
114
+ })
115
+
116
+ /// Keymap for paragraph node.
117
+ /// - `<Mod-Alt-0>`: Turn the selected block into paragraph.
118
+ export const paragraphKeymap = $useKeymap('paragraphKeymap', {
119
+ TurnIntoText: {
120
+ shortcuts: 'Mod-Alt-0',
121
+ command: (ctx) => {
122
+ const commands = ctx.get(commandsCtx)
123
+ return () => commands.call(turnIntoTextCommand.key)
124
+ },
125
+ },
126
+ })
127
+
128
+ withMeta(paragraphKeymap.ctx, {
129
+ displayName: 'KeymapCtx<paragraph>',
130
+ group: 'Paragraph',
131
+ })
132
+
133
+ withMeta(paragraphKeymap.shortcuts, {
134
+ displayName: 'Keymap<paragraph>',
135
+ group: 'Paragraph',
136
+ })