@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 type { Node as ProseNode } from '@jvs-milkdown/prose/model'
2
+
3
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
4
+ import { toggleMark } from '@jvs-milkdown/prose/commands'
5
+ import { TextSelection } from '@jvs-milkdown/prose/state'
6
+ import { $command, $markAttr, $markSchema } from '@jvs-milkdown/utils'
7
+
8
+ import { withMeta } from '../__internal__'
9
+
10
+ /// HTML attributes for the link mark.
11
+ export const linkAttr = $markAttr('link')
12
+
13
+ withMeta(linkAttr, {
14
+ displayName: 'Attr<link>',
15
+ group: 'Link',
16
+ })
17
+
18
+ /// Link mark schema.
19
+ export const linkSchema = $markSchema('link', (ctx) => ({
20
+ attrs: {
21
+ href: { validate: 'string' },
22
+ title: { default: null, validate: 'string|null' },
23
+ },
24
+ parseDOM: [
25
+ {
26
+ tag: 'a[href]',
27
+ getAttrs: (dom) => {
28
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
29
+
30
+ return {
31
+ href: dom.getAttribute('href'),
32
+ title: dom.getAttribute('title'),
33
+ }
34
+ },
35
+ },
36
+ ],
37
+ toDOM: (mark) => ['a', { ...ctx.get(linkAttr.key)(mark), ...mark.attrs }],
38
+ parseMarkdown: {
39
+ match: (node) => node.type === 'link',
40
+ runner: (state, node, markType) => {
41
+ const url = node.url as string
42
+ const title = node.title as string
43
+ state.openMark(markType, { href: url, title })
44
+ state.next(node.children)
45
+ state.closeMark(markType)
46
+ },
47
+ },
48
+ toMarkdown: {
49
+ match: (mark) => mark.type.name === 'link',
50
+ runner: (state, mark) => {
51
+ state.withMark(mark, 'link', undefined, {
52
+ title: mark.attrs.title,
53
+ url: mark.attrs.href,
54
+ })
55
+ },
56
+ },
57
+ }))
58
+
59
+ withMeta(linkSchema.mark, {
60
+ displayName: 'MarkSchema<link>',
61
+ group: 'Link',
62
+ })
63
+
64
+ /// @internal
65
+ export interface UpdateLinkCommandPayload {
66
+ href?: string
67
+ title?: string
68
+ }
69
+ /// A command to toggle the link mark.
70
+ /// You can pass the `href` and `title` to the link.
71
+ export const toggleLinkCommand = $command(
72
+ 'ToggleLink',
73
+ (ctx) =>
74
+ (payload: UpdateLinkCommandPayload = {}) =>
75
+ toggleMark(linkSchema.type(ctx), payload)
76
+ )
77
+
78
+ withMeta(toggleLinkCommand, {
79
+ displayName: 'Command<toggleLinkCommand>',
80
+ group: 'Link',
81
+ })
82
+
83
+ /// A command to update the link mark.
84
+ /// You can pass the `href` and `title` to update the link.
85
+ export const updateLinkCommand = $command(
86
+ 'UpdateLink',
87
+ (ctx) =>
88
+ (payload: UpdateLinkCommandPayload = {}) =>
89
+ (state, dispatch) => {
90
+ if (!dispatch) return false
91
+
92
+ let node: ProseNode | undefined
93
+ let pos = -1
94
+ const { selection } = state
95
+ const { from, to } = selection
96
+ state.doc.nodesBetween(from, from === to ? to + 1 : to, (n, p) => {
97
+ if (linkSchema.type(ctx).isInSet(n.marks)) {
98
+ node = n
99
+ pos = p
100
+ return false
101
+ }
102
+
103
+ return undefined
104
+ })
105
+
106
+ if (!node) return false
107
+
108
+ const mark = node.marks.find(({ type }) => type === linkSchema.type(ctx))
109
+ if (!mark) return false
110
+
111
+ const start = pos
112
+ const end = pos + node.nodeSize
113
+ const { tr } = state
114
+ const linkMark = linkSchema
115
+ .type(ctx)
116
+ .create({ ...mark.attrs, ...payload })
117
+ if (!linkMark) return false
118
+
119
+ dispatch(
120
+ tr
121
+ .removeMark(start, end, mark)
122
+ .addMark(start, end, linkMark)
123
+ .setSelection(new TextSelection(tr.selection.$anchor))
124
+ .scrollIntoView()
125
+ )
126
+
127
+ return true
128
+ }
129
+ )
130
+
131
+ withMeta(updateLinkCommand, {
132
+ displayName: 'Command<updateLinkCommand>',
133
+ group: 'Link',
134
+ })
@@ -0,0 +1,130 @@
1
+ import { commandsCtx, remarkStringifyOptionsCtx } from '@jvs-milkdown/core'
2
+ import { markRule } from '@jvs-milkdown/prose'
3
+ import { toggleMark } from '@jvs-milkdown/prose/commands'
4
+ import {
5
+ $command,
6
+ $inputRule,
7
+ $markAttr,
8
+ $markSchema,
9
+ $useKeymap,
10
+ } from '@jvs-milkdown/utils'
11
+
12
+ import { withMeta } from '../__internal__'
13
+
14
+ /// HTML attributes for the strong mark.
15
+ export const strongAttr = $markAttr('strong')
16
+
17
+ withMeta(strongAttr, {
18
+ displayName: 'Attr<strong>',
19
+ group: 'Strong',
20
+ })
21
+
22
+ /// Strong mark schema.
23
+ export const strongSchema = $markSchema('strong', (ctx) => ({
24
+ attrs: {
25
+ marker: {
26
+ default: ctx.get(remarkStringifyOptionsCtx).strong || '*',
27
+ validate: 'string',
28
+ },
29
+ },
30
+ parseDOM: [
31
+ // This works around a Google Docs misbehavior where
32
+ // pasted content will be inexplicably wrapped in `<b>`
33
+ // tags with a font-weight normal.
34
+ {
35
+ tag: 'b',
36
+ getAttrs: (node: HTMLElement) =>
37
+ node.style.fontWeight != 'normal' && null,
38
+ },
39
+ { tag: 'strong' },
40
+ { style: 'font-style', getAttrs: (value) => (value === 'bold') as false },
41
+ { style: 'font-weight=400', clearMark: (m) => m.type.name == 'strong' },
42
+ {
43
+ style: 'font-weight',
44
+ getAttrs: (value: string) =>
45
+ /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
46
+ },
47
+ ],
48
+ toDOM: (mark) => ['strong', ctx.get(strongAttr.key)(mark)],
49
+ parseMarkdown: {
50
+ match: (node) => node.type === 'strong',
51
+ runner: (state, node, markType) => {
52
+ state.openMark(markType, { marker: node.marker })
53
+ state.next(node.children)
54
+ state.closeMark(markType)
55
+ },
56
+ },
57
+ toMarkdown: {
58
+ match: (mark) => mark.type.name === 'strong',
59
+ runner: (state, mark) => {
60
+ state.withMark(mark, 'strong', undefined, {
61
+ marker: mark.attrs.marker,
62
+ })
63
+ },
64
+ },
65
+ }))
66
+
67
+ withMeta(strongSchema.mark, {
68
+ displayName: 'MarkSchema<strong>',
69
+ group: 'Strong',
70
+ })
71
+
72
+ withMeta(strongSchema.ctx, {
73
+ displayName: 'MarkSchemaCtx<strong>',
74
+ group: 'Strong',
75
+ })
76
+
77
+ /// A command to toggle the strong mark.
78
+ export const toggleStrongCommand = $command('ToggleStrong', (ctx) => () => {
79
+ return toggleMark(strongSchema.type(ctx))
80
+ })
81
+
82
+ withMeta(toggleStrongCommand, {
83
+ displayName: 'Command<toggleStrongCommand>',
84
+ group: 'Strong',
85
+ })
86
+
87
+ /// A input rule that will capture the strong mark.
88
+ export const strongInputRule = $inputRule((ctx) => {
89
+ // Avoid matching when the opening delimiter is directly adjacent to alphanumeric characters,
90
+ // colon or slash (to prevent matches inside file paths, URLs, or intra-word like `a**b**c`).
91
+ // Also ensure the closing delimiter is not followed by such characters (mirrors strike-through rule).
92
+ return markRule(
93
+ /(?<![\w:/])(?:\*\*|__)([^*_]+?)(?:\*\*|__)(?![\w/])$/,
94
+ strongSchema.type(ctx),
95
+ {
96
+ getAttr: (match) => {
97
+ return {
98
+ marker: match[0].startsWith('*') ? '*' : '_',
99
+ }
100
+ },
101
+ }
102
+ )
103
+ })
104
+
105
+ withMeta(strongInputRule, {
106
+ displayName: 'InputRule<strong>',
107
+ group: 'Strong',
108
+ })
109
+
110
+ /// Keymap for the strong mark.
111
+ /// - `Mod-b` - Toggle the strong mark.
112
+ export const strongKeymap = $useKeymap('strongKeymap', {
113
+ ToggleBold: {
114
+ shortcuts: ['Mod-b'],
115
+ command: (ctx) => {
116
+ const commands = ctx.get(commandsCtx)
117
+ return () => commands.call(toggleStrongCommand.key)
118
+ },
119
+ },
120
+ })
121
+
122
+ withMeta(strongKeymap.ctx, {
123
+ displayName: 'KeymapCtx<strong>',
124
+ group: 'Strong',
125
+ })
126
+
127
+ withMeta(strongKeymap.shortcuts, {
128
+ displayName: 'Keymap<strong>',
129
+ group: 'Strong',
130
+ })
@@ -0,0 +1,100 @@
1
+ import type { $NodeSchema } from '@jvs-milkdown/utils'
2
+
3
+ import { commandsCtx } from '@jvs-milkdown/core'
4
+ import { wrapIn } from '@jvs-milkdown/prose/commands'
5
+ import { wrappingInputRule } from '@jvs-milkdown/prose/inputrules'
6
+ import {
7
+ $command,
8
+ $inputRule,
9
+ $nodeAttr,
10
+ $nodeSchema,
11
+ $useKeymap,
12
+ } from '@jvs-milkdown/utils'
13
+
14
+ import { withMeta } from '../__internal__'
15
+
16
+ /// HTML attributes for blockquote node.
17
+ export const blockquoteAttr = $nodeAttr('blockquote')
18
+
19
+ withMeta(blockquoteAttr, {
20
+ displayName: 'Attr<blockquote>',
21
+ group: 'Blockquote',
22
+ })
23
+
24
+ /// Schema for blockquote node.
25
+ export const blockquoteSchema: $NodeSchema<'blockquote'> = $nodeSchema(
26
+ 'blockquote',
27
+ (ctx) => ({
28
+ content: 'block+',
29
+ group: 'block',
30
+ defining: true,
31
+ parseDOM: [{ tag: 'blockquote' }],
32
+ toDOM: (node) => ['blockquote', ctx.get(blockquoteAttr.key)(node), 0],
33
+ parseMarkdown: {
34
+ match: ({ type }) => type === 'blockquote',
35
+ runner: (state, node, type) => {
36
+ state.openNode(type).next(node.children).closeNode()
37
+ },
38
+ },
39
+ toMarkdown: {
40
+ match: (node) => node.type.name === 'blockquote',
41
+ runner: (state, node) => {
42
+ state.openNode('blockquote').next(node.content).closeNode()
43
+ },
44
+ },
45
+ })
46
+ )
47
+
48
+ withMeta(blockquoteSchema.node, {
49
+ displayName: 'NodeSchema<blockquote>',
50
+ group: 'Blockquote',
51
+ })
52
+
53
+ withMeta(blockquoteSchema.ctx, {
54
+ displayName: 'NodeSchemaCtx<blockquote>',
55
+ group: 'Blockquote',
56
+ })
57
+
58
+ /// This input rule will convert a line that starts with `> ` into a blockquote.
59
+ /// You can type `> ` at the start of a line to create a blockquote.
60
+ export const wrapInBlockquoteInputRule = $inputRule((ctx) =>
61
+ wrappingInputRule(/^\s*>\s$/, blockquoteSchema.type(ctx))
62
+ )
63
+
64
+ withMeta(wrapInBlockquoteInputRule, {
65
+ displayName: 'InputRule<wrapInBlockquoteInputRule>',
66
+ group: 'Blockquote',
67
+ })
68
+
69
+ /// This command will wrap the current selection in a blockquote.
70
+ export const wrapInBlockquoteCommand = $command(
71
+ 'WrapInBlockquote',
72
+ (ctx) => () => wrapIn(blockquoteSchema.type(ctx))
73
+ )
74
+
75
+ withMeta(wrapInBlockquoteCommand, {
76
+ displayName: 'Command<wrapInBlockquoteCommand>',
77
+ group: 'Blockquote',
78
+ })
79
+
80
+ /// Keymap for blockquote.
81
+ /// - `Mod-Shift-b`: Wrap selection in blockquote.
82
+ export const blockquoteKeymap = $useKeymap('blockquoteKeymap', {
83
+ WrapInBlockquote: {
84
+ shortcuts: 'Mod-Shift-b',
85
+ command: (ctx) => {
86
+ const commands = ctx.get(commandsCtx)
87
+ return () => commands.call(wrapInBlockquoteCommand.key)
88
+ },
89
+ },
90
+ })
91
+
92
+ withMeta(blockquoteKeymap.ctx, {
93
+ displayName: 'KeymapCtx<blockquote>',
94
+ group: 'Blockquote',
95
+ })
96
+
97
+ withMeta(blockquoteKeymap.shortcuts, {
98
+ displayName: 'Keymap<blockquote>',
99
+ group: 'Blockquote',
100
+ })
@@ -0,0 +1,129 @@
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 bullet list node.
16
+ export const bulletListAttr = $nodeAttr('bulletList')
17
+
18
+ withMeta(bulletListAttr, {
19
+ displayName: 'Attr<bulletList>',
20
+ group: 'BulletList',
21
+ })
22
+
23
+ /// Schema for bullet list node.
24
+ export const bulletListSchema = $nodeSchema('bullet_list', (ctx) => {
25
+ return {
26
+ content: 'listItem+',
27
+ group: 'block',
28
+ attrs: {
29
+ spread: {
30
+ default: false,
31
+ validate: 'boolean',
32
+ },
33
+ },
34
+ parseDOM: [
35
+ {
36
+ tag: 'ul',
37
+ getAttrs: (dom) => {
38
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
39
+
40
+ return {
41
+ spread: dom.dataset.spread === 'true',
42
+ }
43
+ },
44
+ },
45
+ ],
46
+ toDOM: (node) => {
47
+ return [
48
+ 'ul',
49
+ {
50
+ ...ctx.get(bulletListAttr.key)(node),
51
+ 'data-spread': node.attrs.spread,
52
+ },
53
+ 0,
54
+ ]
55
+ },
56
+ parseMarkdown: {
57
+ match: ({ type, ordered }) => type === 'list' && !ordered,
58
+ runner: (state, node, type) => {
59
+ const spread = node.spread != null ? `${node.spread}` : 'false'
60
+ state.openNode(type, { spread }).next(node.children).closeNode()
61
+ },
62
+ },
63
+ toMarkdown: {
64
+ match: (node) => node.type.name === 'bullet_list',
65
+ runner: (state, node) => {
66
+ state
67
+ .openNode('list', undefined, {
68
+ ordered: false,
69
+ spread: node.attrs.spread,
70
+ })
71
+ .next(node.content)
72
+ .closeNode()
73
+ },
74
+ },
75
+ }
76
+ })
77
+
78
+ withMeta(bulletListSchema.node, {
79
+ displayName: 'NodeSchema<bulletList>',
80
+ group: 'BulletList',
81
+ })
82
+
83
+ withMeta(bulletListSchema.ctx, {
84
+ displayName: 'NodeSchemaCtx<bulletList>',
85
+ group: 'BulletList',
86
+ })
87
+
88
+ /// Input rule for wrapping a block in bullet list node.
89
+ export const wrapInBulletListInputRule = $inputRule((ctx) =>
90
+ wrappingInputRule(/^\s*([-+*])\s$/, bulletListSchema.type(ctx))
91
+ )
92
+
93
+ withMeta(wrapInBulletListInputRule, {
94
+ displayName: 'InputRule<wrapInBulletListInputRule>',
95
+ group: 'BulletList',
96
+ })
97
+
98
+ /// Command for creating bullet list node.
99
+ export const wrapInBulletListCommand = $command(
100
+ 'WrapInBulletList',
101
+ (ctx) => () => wrapIn(bulletListSchema.type(ctx))
102
+ )
103
+
104
+ withMeta(wrapInBulletListCommand, {
105
+ displayName: 'Command<wrapInBulletListCommand>',
106
+ group: 'BulletList',
107
+ })
108
+
109
+ /// Keymap for bullet list node.
110
+ /// - `Mod-Alt-8`: Wrap a block in bullet list.
111
+ export const bulletListKeymap = $useKeymap('bulletListKeymap', {
112
+ WrapInBulletList: {
113
+ shortcuts: 'Mod-Alt-8',
114
+ command: (ctx) => {
115
+ const commands = ctx.get(commandsCtx)
116
+ return () => commands.call(wrapInBulletListCommand.key)
117
+ },
118
+ },
119
+ })
120
+
121
+ withMeta(bulletListKeymap.ctx, {
122
+ displayName: 'KeymapCtx<bulletListKeymap>',
123
+ group: 'BulletList',
124
+ })
125
+
126
+ withMeta(bulletListKeymap.shortcuts, {
127
+ displayName: 'Keymap<bulletListKeymap>',
128
+ group: 'BulletList',
129
+ })
@@ -0,0 +1,176 @@
1
+ import { commandsCtx } from '@jvs-milkdown/core'
2
+ import { expectDomTypeError } from '@jvs-milkdown/exception'
3
+ import { setBlockType } from '@jvs-milkdown/prose/commands'
4
+ import { textblockTypeInputRule } 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 code block node.
16
+ export const codeBlockAttr = $nodeAttr('codeBlock', () => ({
17
+ pre: {},
18
+ code: {},
19
+ }))
20
+
21
+ withMeta(codeBlockAttr, {
22
+ displayName: 'Attr<codeBlock>',
23
+ group: 'CodeBlock',
24
+ })
25
+
26
+ /// Schema for code block node.
27
+ export const codeBlockSchema = $nodeSchema('code_block', (ctx) => {
28
+ return {
29
+ content: 'text*',
30
+ group: 'block',
31
+ marks: '',
32
+ defining: true,
33
+ code: true,
34
+ attrs: {
35
+ language: {
36
+ default: '',
37
+ validate: 'string',
38
+ },
39
+ },
40
+ parseDOM: [
41
+ {
42
+ tag: 'pre',
43
+ preserveWhitespace: 'full',
44
+ getAttrs: (dom) => {
45
+ if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
46
+
47
+ return { language: dom.dataset.language }
48
+ },
49
+ },
50
+ ],
51
+ toDOM: (node) => {
52
+ const attr = ctx.get(codeBlockAttr.key)(node)
53
+ const language = node.attrs.language
54
+ const languageAttrs =
55
+ language && language.length > 0
56
+ ? { 'data-language': language }
57
+ : undefined
58
+
59
+ return [
60
+ 'pre',
61
+ {
62
+ ...attr.pre,
63
+ ...languageAttrs,
64
+ },
65
+ ['code', attr.code, 0],
66
+ ]
67
+ },
68
+ parseMarkdown: {
69
+ match: ({ type }) => type === 'code',
70
+ runner: (state, node, type) => {
71
+ const language = node.lang ?? ''
72
+ const value = node.value as string | null
73
+ state.openNode(type, { language })
74
+ if (value) state.addText(value)
75
+
76
+ state.closeNode()
77
+ },
78
+ },
79
+ toMarkdown: {
80
+ match: (node) => node.type.name === 'code_block',
81
+ runner: (state, node) => {
82
+ state.addNode('code', undefined, node.content.firstChild?.text || '', {
83
+ lang: node.attrs.language,
84
+ })
85
+ },
86
+ },
87
+ }
88
+ })
89
+
90
+ withMeta(codeBlockSchema.node, {
91
+ displayName: 'NodeSchema<codeBlock>',
92
+ group: 'CodeBlock',
93
+ })
94
+
95
+ withMeta(codeBlockSchema.ctx, {
96
+ displayName: 'NodeSchemaCtx<codeBlock>',
97
+ group: 'CodeBlock',
98
+ })
99
+
100
+ /// A input rule for creating code block.
101
+ /// For example, ` ```javascript ` will create a code block with language javascript.
102
+ export const createCodeBlockInputRule = $inputRule((ctx) =>
103
+ textblockTypeInputRule(
104
+ /^```(?<language>[a-z]*)?[\s\n]$/,
105
+ codeBlockSchema.type(ctx),
106
+ (match) => ({
107
+ language: match.groups?.language ?? '',
108
+ })
109
+ )
110
+ )
111
+
112
+ withMeta(createCodeBlockInputRule, {
113
+ displayName: 'InputRule<createCodeBlockInputRule>',
114
+ group: 'CodeBlock',
115
+ })
116
+
117
+ /// A command for creating code block.
118
+ /// You can pass the language of the code block as the parameter.
119
+ export const createCodeBlockCommand = $command(
120
+ 'CreateCodeBlock',
121
+ (ctx) =>
122
+ (language = '') =>
123
+ setBlockType(codeBlockSchema.type(ctx), { language })
124
+ )
125
+
126
+ withMeta(createCodeBlockCommand, {
127
+ displayName: 'Command<createCodeBlockCommand>',
128
+ group: 'CodeBlock',
129
+ })
130
+
131
+ /// A command for updating the code block language of the target position.
132
+ export const updateCodeBlockLanguageCommand = $command(
133
+ 'UpdateCodeBlockLanguage',
134
+ () =>
135
+ (
136
+ { pos, language }: { pos: number; language: string } = {
137
+ pos: -1,
138
+ language: '',
139
+ }
140
+ ) =>
141
+ (state, dispatch) => {
142
+ if (pos >= 0) {
143
+ dispatch?.(state.tr.setNodeAttribute(pos, 'language', language))
144
+ return true
145
+ }
146
+
147
+ return false
148
+ }
149
+ )
150
+
151
+ withMeta(updateCodeBlockLanguageCommand, {
152
+ displayName: 'Command<updateCodeBlockLanguageCommand>',
153
+ group: 'CodeBlock',
154
+ })
155
+
156
+ /// Keymap for code block.
157
+ /// - `Mod-Alt-c`: Create a code block.
158
+ export const codeBlockKeymap = $useKeymap('codeBlockKeymap', {
159
+ CreateCodeBlock: {
160
+ shortcuts: 'Mod-Alt-c',
161
+ command: (ctx) => {
162
+ const commands = ctx.get(commandsCtx)
163
+ return () => commands.call(createCodeBlockCommand.key)
164
+ },
165
+ },
166
+ })
167
+
168
+ withMeta(codeBlockKeymap.ctx, {
169
+ displayName: 'KeymapCtx<codeBlock>',
170
+ group: 'CodeBlock',
171
+ })
172
+
173
+ withMeta(codeBlockKeymap.shortcuts, {
174
+ displayName: 'Keymap<codeBlock>',
175
+ group: 'CodeBlock',
176
+ })
@@ -0,0 +1,26 @@
1
+ import { $node } from '@jvs-milkdown/utils'
2
+
3
+ import { withMeta } from '../__internal__'
4
+
5
+ /// The top-level document node.
6
+ export const docSchema = $node('doc', () => ({
7
+ content: 'block+',
8
+ parseMarkdown: {
9
+ match: ({ type }) => type === 'root',
10
+ runner: (state, node, type) => {
11
+ state.injectRoot(node, type)
12
+ },
13
+ },
14
+ toMarkdown: {
15
+ match: (node) => node.type.name === 'doc',
16
+ runner: (state, node) => {
17
+ state.openNode('root')
18
+ state.next(node.content)
19
+ },
20
+ },
21
+ }))
22
+
23
+ withMeta(docSchema, {
24
+ displayName: 'NodeSchema<doc>',
25
+ group: 'Doc',
26
+ })