@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/lib/__internal__/index.d.ts +3 -0
- package/lib/__internal__/index.d.ts.map +1 -0
- package/lib/__internal__/serialize-text.d.ts +4 -0
- package/lib/__internal__/serialize-text.d.ts.map +1 -0
- package/lib/__internal__/with-meta.d.ts +3 -0
- package/lib/__internal__/with-meta.d.ts.map +1 -0
- package/lib/__test__/html.spec.d.ts +2 -0
- package/lib/__test__/html.spec.d.ts.map +1 -0
- package/lib/__test__/trailing-space.spec.d.ts +2 -0
- package/lib/__test__/trailing-space.spec.d.ts.map +1 -0
- package/lib/__test__/vitest.setup.d.ts +1 -0
- package/lib/__test__/vitest.setup.d.ts.map +1 -0
- package/lib/commands/index.d.ts +20 -0
- package/lib/commands/index.d.ts.map +1 -0
- package/lib/composed/commands.d.ts +3 -0
- package/lib/composed/commands.d.ts.map +1 -0
- package/lib/composed/index.d.ts +6 -0
- package/lib/composed/index.d.ts.map +1 -0
- package/lib/composed/inputrules.d.ts +4 -0
- package/lib/composed/inputrules.d.ts.map +1 -0
- package/lib/composed/keymap.d.ts +3 -0
- package/lib/composed/keymap.d.ts.map +1 -0
- package/lib/composed/plugins.d.ts +3 -0
- package/lib/composed/plugins.d.ts.map +1 -0
- package/lib/composed/schema.d.ts +3 -0
- package/lib/composed/schema.d.ts.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2153 -0
- package/lib/index.js.map +1 -0
- package/lib/mark/emphasis.d.ts +7 -0
- package/lib/mark/emphasis.d.ts.map +1 -0
- package/lib/mark/index.d.ts +5 -0
- package/lib/mark/index.d.ts.map +1 -0
- package/lib/mark/inline-code.d.ts +6 -0
- package/lib/mark/inline-code.d.ts.map +1 -0
- package/lib/mark/link.d.ts +9 -0
- package/lib/mark/link.d.ts.map +1 -0
- package/lib/mark/strong.d.ts +6 -0
- package/lib/mark/strong.d.ts.map +1 -0
- package/lib/node/blockquote.d.ts +7 -0
- package/lib/node/blockquote.d.ts.map +1 -0
- package/lib/node/bullet-list.d.ts +6 -0
- package/lib/node/bullet-list.d.ts.map +1 -0
- package/lib/node/code-block.d.ts +10 -0
- package/lib/node/code-block.d.ts.map +1 -0
- package/lib/node/doc.d.ts +2 -0
- package/lib/node/doc.d.ts.map +1 -0
- package/lib/node/hardbreak.d.ts +5 -0
- package/lib/node/hardbreak.d.ts.map +1 -0
- package/lib/node/heading.d.ts +11 -0
- package/lib/node/heading.d.ts.map +1 -0
- package/lib/node/hr.d.ts +5 -0
- package/lib/node/hr.d.ts.map +1 -0
- package/lib/node/html.d.ts +3 -0
- package/lib/node/html.d.ts.map +1 -0
- package/lib/node/image.d.ts +11 -0
- package/lib/node/image.d.ts.map +1 -0
- package/lib/node/index.d.ts +14 -0
- package/lib/node/index.d.ts.map +1 -0
- package/lib/node/list-item.d.ts +8 -0
- package/lib/node/list-item.d.ts.map +1 -0
- package/lib/node/ordered-list.d.ts +6 -0
- package/lib/node/ordered-list.d.ts.map +1 -0
- package/lib/node/paragraph.d.ts +5 -0
- package/lib/node/paragraph.d.ts.map +1 -0
- package/lib/node/text.d.ts +2 -0
- package/lib/node/text.d.ts.map +1 -0
- package/lib/plugin/hardbreak-clear-mark-plugin.d.ts +2 -0
- package/lib/plugin/hardbreak-clear-mark-plugin.d.ts.map +1 -0
- package/lib/plugin/hardbreak-filter-plugin.d.ts +3 -0
- package/lib/plugin/hardbreak-filter-plugin.d.ts.map +1 -0
- package/lib/plugin/index.d.ts +12 -0
- package/lib/plugin/index.d.ts.map +1 -0
- package/lib/plugin/inline-nodes-cursor-plugin.d.ts +2 -0
- package/lib/plugin/inline-nodes-cursor-plugin.d.ts.map +1 -0
- package/lib/plugin/remark-add-order-in-list-plugin.d.ts +2 -0
- package/lib/plugin/remark-add-order-in-list-plugin.d.ts.map +1 -0
- package/lib/plugin/remark-html-transformer.d.ts +2 -0
- package/lib/plugin/remark-html-transformer.d.ts.map +1 -0
- package/lib/plugin/remark-inline-link-plugin.d.ts +2 -0
- package/lib/plugin/remark-inline-link-plugin.d.ts.map +1 -0
- package/lib/plugin/remark-line-break.d.ts +2 -0
- package/lib/plugin/remark-line-break.d.ts.map +1 -0
- package/lib/plugin/remark-marker-plugin.d.ts +2 -0
- package/lib/plugin/remark-marker-plugin.d.ts.map +1 -0
- package/lib/plugin/remark-preserve-empty-line.d.ts +2 -0
- package/lib/plugin/remark-preserve-empty-line.d.ts.map +1 -0
- package/lib/plugin/sync-heading-id-plugin.d.ts +2 -0
- package/lib/plugin/sync-heading-id-plugin.d.ts.map +1 -0
- package/lib/plugin/sync-list-order-plugin.d.ts +2 -0
- package/lib/plugin/sync-list-order-plugin.d.ts.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +44 -0
- package/src/__internal__/index.ts +2 -0
- package/src/__internal__/serialize-text.ts +21 -0
- package/src/__internal__/with-meta.ts +15 -0
- package/src/__test__/html.spec.ts +46 -0
- package/src/__test__/trailing-space.spec.ts +27 -0
- package/src/__test__/vitest.setup.ts +65 -0
- package/src/commands/index.ts +140 -0
- package/src/composed/commands.ts +72 -0
- package/src/composed/index.ts +5 -0
- package/src/composed/inputrules.ts +34 -0
- package/src/composed/keymap.ts +29 -0
- package/src/composed/plugins.ts +35 -0
- package/src/composed/schema.ts +92 -0
- package/src/index.ts +26 -0
- package/src/mark/emphasis.ts +130 -0
- package/src/mark/index.ts +4 -0
- package/src/mark/inline-code.ts +123 -0
- package/src/mark/link.ts +134 -0
- package/src/mark/strong.ts +130 -0
- package/src/node/blockquote.ts +100 -0
- package/src/node/bullet-list.ts +129 -0
- package/src/node/code-block.ts +176 -0
- package/src/node/doc.ts +26 -0
- package/src/node/hardbreak.ts +134 -0
- package/src/node/heading.ts +271 -0
- package/src/node/hr.ts +87 -0
- package/src/node/html.ts +66 -0
- package/src/node/image.ts +173 -0
- package/src/node/index.ts +14 -0
- package/src/node/list-item.ts +244 -0
- package/src/node/ordered-list.ts +141 -0
- package/src/node/paragraph.ts +136 -0
- package/src/node/text.ts +25 -0
- package/src/plugin/hardbreak-clear-mark-plugin.ts +58 -0
- package/src/plugin/hardbreak-filter-plugin.ts +46 -0
- package/src/plugin/index.ts +14 -0
- package/src/plugin/inline-nodes-cursor-plugin.ts +103 -0
- package/src/plugin/remark-add-order-in-list-plugin.ts +29 -0
- package/src/plugin/remark-html-transformer.ts +74 -0
- package/src/plugin/remark-inline-link-plugin.ts +20 -0
- package/src/plugin/remark-line-break.ts +69 -0
- package/src/plugin/remark-marker-plugin.ts +33 -0
- package/src/plugin/remark-preserve-empty-line.ts +49 -0
- package/src/plugin/sync-heading-id-plugin.ts +67 -0
- package/src/plugin/sync-list-order-plugin.ts +112 -0
package/src/mark/link.ts
ADDED
|
@@ -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
|
+
})
|
package/src/node/doc.ts
ADDED
|
@@ -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
|
+
})
|