@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/node/text.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { $node } from '@jvs-milkdown/utils'
|
|
2
|
+
|
|
3
|
+
import { withMeta } from '../__internal__'
|
|
4
|
+
|
|
5
|
+
/// The bottom-level node.
|
|
6
|
+
export const textSchema = $node('text', () => ({
|
|
7
|
+
group: 'inline',
|
|
8
|
+
parseMarkdown: {
|
|
9
|
+
match: ({ type }) => type === 'text',
|
|
10
|
+
runner: (state, node) => {
|
|
11
|
+
state.addText(node.value as string)
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
toMarkdown: {
|
|
15
|
+
match: (node) => node.type.name === 'text',
|
|
16
|
+
runner: (state, node) => {
|
|
17
|
+
state.addNode('text', undefined, node.text as string)
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
withMeta(textSchema, {
|
|
23
|
+
displayName: 'NodeSchema<text>',
|
|
24
|
+
group: 'Text',
|
|
25
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
|
|
2
|
+
import { AddMarkStep, ReplaceStep } from '@jvs-milkdown/prose/transform'
|
|
3
|
+
import { $prose } from '@jvs-milkdown/utils'
|
|
4
|
+
|
|
5
|
+
import { withMeta } from '../__internal__'
|
|
6
|
+
import { hardbreakSchema } from '../node'
|
|
7
|
+
|
|
8
|
+
/// This plugin is used to clear the marks around the hardbreak node.
|
|
9
|
+
export const hardbreakClearMarkPlugin = $prose((ctx) => {
|
|
10
|
+
return new Plugin({
|
|
11
|
+
key: new PluginKey('MILKDOWN_HARDBREAK_MARKS'),
|
|
12
|
+
appendTransaction: (trs, _oldState, newState) => {
|
|
13
|
+
if (!trs.length) return
|
|
14
|
+
|
|
15
|
+
const [tr] = trs
|
|
16
|
+
if (!tr) return
|
|
17
|
+
|
|
18
|
+
const [step] = tr.steps
|
|
19
|
+
|
|
20
|
+
const isInsertHr = tr.getMeta('hardbreak')
|
|
21
|
+
if (isInsertHr) {
|
|
22
|
+
if (!(step instanceof ReplaceStep)) return
|
|
23
|
+
|
|
24
|
+
const { from } = step as unknown as { from: number }
|
|
25
|
+
return newState.tr.setNodeMarkup(
|
|
26
|
+
from,
|
|
27
|
+
hardbreakSchema.type(ctx),
|
|
28
|
+
undefined,
|
|
29
|
+
[]
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isAddMarkStep = step instanceof AddMarkStep
|
|
34
|
+
if (isAddMarkStep) {
|
|
35
|
+
let _tr = newState.tr
|
|
36
|
+
const { from, to } = step as unknown as { from: number; to: number }
|
|
37
|
+
newState.doc.nodesBetween(from, to, (node, pos) => {
|
|
38
|
+
if (node.type === hardbreakSchema.type(ctx))
|
|
39
|
+
_tr = _tr.setNodeMarkup(
|
|
40
|
+
pos,
|
|
41
|
+
hardbreakSchema.type(ctx),
|
|
42
|
+
undefined,
|
|
43
|
+
[]
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return _tr
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
withMeta(hardbreakClearMarkPlugin, {
|
|
56
|
+
displayName: 'Prose<hardbreakClearMarkPlugin>',
|
|
57
|
+
group: 'Prose',
|
|
58
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
|
|
2
|
+
import { $ctx, $prose } from '@jvs-milkdown/utils'
|
|
3
|
+
|
|
4
|
+
import { withMeta } from '../__internal__'
|
|
5
|
+
|
|
6
|
+
/// This slice contains the nodes that within which the hardbreak will be ignored.
|
|
7
|
+
export const hardbreakFilterNodes = $ctx(
|
|
8
|
+
['table', 'code_block'],
|
|
9
|
+
'hardbreakFilterNodes'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
withMeta(hardbreakFilterNodes, {
|
|
13
|
+
displayName: 'Ctx<hardbreakFilterNodes>',
|
|
14
|
+
group: 'Prose',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/// This plugin is used to filter the hardbreak node.
|
|
18
|
+
/// If the hardbreak is going to be inserted within a node that is in the `hardbreakFilterNodes`, ignore it.
|
|
19
|
+
export const hardbreakFilterPlugin = $prose((ctx) => {
|
|
20
|
+
const notIn = ctx.get(hardbreakFilterNodes.key)
|
|
21
|
+
return new Plugin({
|
|
22
|
+
key: new PluginKey('MILKDOWN_HARDBREAK_FILTER'),
|
|
23
|
+
filterTransaction: (tr, state) => {
|
|
24
|
+
const isInsertHr = tr.getMeta('hardbreak')
|
|
25
|
+
const [step] = tr.steps
|
|
26
|
+
if (isInsertHr && step) {
|
|
27
|
+
const { from } = step as unknown as { from: number }
|
|
28
|
+
const $from = state.doc.resolve(from)
|
|
29
|
+
let curDepth = $from.depth
|
|
30
|
+
let canApply = true
|
|
31
|
+
while (curDepth > 0) {
|
|
32
|
+
if (notIn.includes($from.node(curDepth).type.name)) canApply = false
|
|
33
|
+
|
|
34
|
+
curDepth--
|
|
35
|
+
}
|
|
36
|
+
return canApply
|
|
37
|
+
}
|
|
38
|
+
return true
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
withMeta(hardbreakFilterPlugin, {
|
|
44
|
+
displayName: 'Prose<hardbreakFilterPlugin>',
|
|
45
|
+
group: 'Prose',
|
|
46
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './remark-add-order-in-list-plugin'
|
|
2
|
+
export * from './remark-line-break'
|
|
3
|
+
export * from './remark-inline-link-plugin'
|
|
4
|
+
export * from './remark-html-transformer'
|
|
5
|
+
export * from './remark-marker-plugin'
|
|
6
|
+
export * from './remark-preserve-empty-line'
|
|
7
|
+
|
|
8
|
+
export * from './inline-nodes-cursor-plugin'
|
|
9
|
+
|
|
10
|
+
export * from './hardbreak-clear-mark-plugin'
|
|
11
|
+
export * from './hardbreak-filter-plugin'
|
|
12
|
+
|
|
13
|
+
export * from './sync-heading-id-plugin'
|
|
14
|
+
export * from './sync-list-order-plugin'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
|
|
2
|
+
import { Decoration, DecorationSet } from '@jvs-milkdown/prose/view'
|
|
3
|
+
import { $prose } from '@jvs-milkdown/utils'
|
|
4
|
+
|
|
5
|
+
import { withMeta } from '../__internal__'
|
|
6
|
+
|
|
7
|
+
/// This plugin is to solve the [chrome 98 bug](https://discuss.prosemirror.net/t/cursor-jumps-at-the-end-of-line-when-it-betweens-two-inline-nodes/4641).
|
|
8
|
+
export const inlineNodesCursorPlugin = $prose(() => {
|
|
9
|
+
let lock = false
|
|
10
|
+
const inlineNodesCursorPluginKey = new PluginKey(
|
|
11
|
+
'MILKDOWN_INLINE_NODES_CURSOR'
|
|
12
|
+
)
|
|
13
|
+
const inlineNodesCursorPlugin: Plugin = new Plugin({
|
|
14
|
+
key: inlineNodesCursorPluginKey,
|
|
15
|
+
state: {
|
|
16
|
+
init() {
|
|
17
|
+
return false
|
|
18
|
+
},
|
|
19
|
+
apply(tr) {
|
|
20
|
+
if (!tr.selection.empty) return false
|
|
21
|
+
|
|
22
|
+
const pos = tr.selection.$from
|
|
23
|
+
const left = pos.nodeBefore
|
|
24
|
+
const right = pos.nodeAfter
|
|
25
|
+
if (
|
|
26
|
+
left &&
|
|
27
|
+
right &&
|
|
28
|
+
left.isInline &&
|
|
29
|
+
!left.isText &&
|
|
30
|
+
right.isInline &&
|
|
31
|
+
!right.isText
|
|
32
|
+
)
|
|
33
|
+
return true
|
|
34
|
+
|
|
35
|
+
return false
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
props: {
|
|
39
|
+
handleDOMEvents: {
|
|
40
|
+
compositionend: (view, e) => {
|
|
41
|
+
if (lock) {
|
|
42
|
+
lock = false
|
|
43
|
+
requestAnimationFrame(() => {
|
|
44
|
+
const active = inlineNodesCursorPlugin.getState(view.state)
|
|
45
|
+
if (active) {
|
|
46
|
+
const from = view.state.selection.from
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
view.dispatch(view.state.tr.insertText(e.data || '', from))
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
return false
|
|
55
|
+
},
|
|
56
|
+
compositionstart: (view) => {
|
|
57
|
+
const active = inlineNodesCursorPlugin.getState(view.state)
|
|
58
|
+
if (active) lock = true
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
},
|
|
62
|
+
beforeinput: (view, e) => {
|
|
63
|
+
const active = inlineNodesCursorPlugin.getState(view.state)
|
|
64
|
+
if (active && e instanceof InputEvent && e.data && !lock) {
|
|
65
|
+
const from = view.state.selection.from
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
view.dispatch(view.state.tr.insertText(e.data || '', from))
|
|
68
|
+
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
decorations(state) {
|
|
76
|
+
const active = inlineNodesCursorPlugin.getState(state)
|
|
77
|
+
if (active) {
|
|
78
|
+
const pos = state.selection.$from
|
|
79
|
+
const position = pos.pos
|
|
80
|
+
const left = document.createElement('span')
|
|
81
|
+
const leftDec = Decoration.widget(position, left, {
|
|
82
|
+
side: -1,
|
|
83
|
+
})
|
|
84
|
+
const right = document.createElement('span')
|
|
85
|
+
const rightDec = Decoration.widget(position, right)
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
left.contentEditable = 'true'
|
|
88
|
+
right.contentEditable = 'true'
|
|
89
|
+
})
|
|
90
|
+
return DecorationSet.create(state.doc, [leftDec, rightDec])
|
|
91
|
+
}
|
|
92
|
+
return DecorationSet.empty
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return inlineNodesCursorPlugin
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
withMeta(inlineNodesCursorPlugin, {
|
|
101
|
+
displayName: 'Prose<inlineNodesCursorPlugin>',
|
|
102
|
+
group: 'Prose',
|
|
103
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
2
|
+
import { visit } from 'unist-util-visit'
|
|
3
|
+
|
|
4
|
+
import { withMeta } from '../__internal__'
|
|
5
|
+
|
|
6
|
+
/// This plugin is used to add order in list for remark AST.
|
|
7
|
+
export const remarkAddOrderInListPlugin = $remark(
|
|
8
|
+
'remarkAddOrderInList',
|
|
9
|
+
() => () => (tree) => {
|
|
10
|
+
visit(tree, 'list', (node) => {
|
|
11
|
+
if (node.ordered) {
|
|
12
|
+
const start = node.start ?? 1
|
|
13
|
+
node.children.forEach((child, index) => {
|
|
14
|
+
;(child as unknown as Record<string, number>).label = index + start
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
withMeta(remarkAddOrderInListPlugin.plugin, {
|
|
22
|
+
displayName: 'Remark<remarkAddOrderInListPlugin>',
|
|
23
|
+
group: 'Remark',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
withMeta(remarkAddOrderInListPlugin.options, {
|
|
27
|
+
displayName: 'RemarkConfig<remarkAddOrderInListPlugin>',
|
|
28
|
+
group: 'Remark',
|
|
29
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Node } from '@jvs-milkdown/transformer'
|
|
2
|
+
|
|
3
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
4
|
+
|
|
5
|
+
import { withMeta } from '../__internal__'
|
|
6
|
+
|
|
7
|
+
const isParent = (node: Node): node is Node & { children: Node[] } =>
|
|
8
|
+
!!(node as Node & { children: Node[] }).children
|
|
9
|
+
const isHTML = (
|
|
10
|
+
node: Node
|
|
11
|
+
): node is Node & { children: Node[]; value: unknown } => node.type === 'html'
|
|
12
|
+
|
|
13
|
+
function flatMapWithDepth(
|
|
14
|
+
ast: Node,
|
|
15
|
+
fn: (node: Node, index: number, parent: Node | null) => Node[]
|
|
16
|
+
) {
|
|
17
|
+
return transform(ast, 0, null)[0]
|
|
18
|
+
|
|
19
|
+
function transform(node: Node, index: number, parent: Node | null) {
|
|
20
|
+
if (isParent(node)) {
|
|
21
|
+
const out = []
|
|
22
|
+
for (let i = 0, n = node.children.length; i < n; i++) {
|
|
23
|
+
const nthChild = node.children[i]
|
|
24
|
+
if (nthChild) {
|
|
25
|
+
const xs = transform(nthChild, i, node)
|
|
26
|
+
if (xs) {
|
|
27
|
+
for (let j = 0, m = xs.length; j < m; j++) {
|
|
28
|
+
const item = xs[j]
|
|
29
|
+
if (item) out.push(item)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
node.children = out
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fn(node, index, parent)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// List of container node types that can contain block-level content
|
|
42
|
+
// and thus may need HTML content to be wrapped in paragraphs
|
|
43
|
+
const BLOCK_CONTAINER_TYPES = ['root', 'blockquote', 'listItem']
|
|
44
|
+
|
|
45
|
+
/// @internal
|
|
46
|
+
/// This plugin should be deprecated after we support HTML.
|
|
47
|
+
export const remarkHtmlTransformer = $remark(
|
|
48
|
+
'remarkHTMLTransformer',
|
|
49
|
+
() => () => (tree: Node) => {
|
|
50
|
+
flatMapWithDepth(tree, (node, _index, parent) => {
|
|
51
|
+
if (!isHTML(node)) return [node]
|
|
52
|
+
|
|
53
|
+
// If the parent is a block container that expects block content,
|
|
54
|
+
// wrap the HTML in a paragraph node
|
|
55
|
+
if (parent && BLOCK_CONTAINER_TYPES.includes(parent.type)) {
|
|
56
|
+
node.children = [{ ...node }]
|
|
57
|
+
delete node.value
|
|
58
|
+
;(node as { type: string }).type = 'paragraph'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return [node]
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
withMeta(remarkHtmlTransformer.plugin, {
|
|
67
|
+
displayName: 'Remark<remarkHtmlTransformer>',
|
|
68
|
+
group: 'Remark',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
withMeta(remarkHtmlTransformer.options, {
|
|
72
|
+
displayName: 'RemarkConfig<remarkHtmlTransformer>',
|
|
73
|
+
group: 'Remark',
|
|
74
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
2
|
+
import remarkInlineLinks from 'remark-inline-links'
|
|
3
|
+
|
|
4
|
+
import { withMeta } from '../__internal__'
|
|
5
|
+
|
|
6
|
+
/// This plugin wraps [remark-inline-links](https://github.com/remarkjs/remark-inline-links).
|
|
7
|
+
export const remarkInlineLinkPlugin = $remark(
|
|
8
|
+
'remarkInlineLink',
|
|
9
|
+
() => remarkInlineLinks
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
withMeta(remarkInlineLinkPlugin.plugin, {
|
|
13
|
+
displayName: 'Remark<remarkInlineLinkPlugin>',
|
|
14
|
+
group: 'Remark',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
withMeta(remarkInlineLinkPlugin.options, {
|
|
18
|
+
displayName: 'RemarkConfig<remarkInlineLinkPlugin>',
|
|
19
|
+
group: 'Remark',
|
|
20
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Node } from '@jvs-milkdown/transformer'
|
|
2
|
+
|
|
3
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
4
|
+
import { visit } from 'unist-util-visit'
|
|
5
|
+
|
|
6
|
+
import { withMeta } from '../__internal__'
|
|
7
|
+
|
|
8
|
+
/// This plugin is used to add inline line break for remark AST.
|
|
9
|
+
/// The inline line break should be treated as a `space`.
|
|
10
|
+
/// And the normal line break should be treated as a `LF`.
|
|
11
|
+
export const remarkLineBreak = $remark(
|
|
12
|
+
'remarkLineBreak',
|
|
13
|
+
() => () => (tree: Node) => {
|
|
14
|
+
const find = /[\t ]*(?:\r?\n|\r)/g
|
|
15
|
+
visit(
|
|
16
|
+
tree,
|
|
17
|
+
'text',
|
|
18
|
+
(
|
|
19
|
+
node: Node & { value: string },
|
|
20
|
+
index: number,
|
|
21
|
+
parent: Node & { children: Node[] }
|
|
22
|
+
) => {
|
|
23
|
+
if (!node.value || typeof node.value !== 'string') return
|
|
24
|
+
|
|
25
|
+
const result = []
|
|
26
|
+
let start = 0
|
|
27
|
+
|
|
28
|
+
find.lastIndex = 0
|
|
29
|
+
|
|
30
|
+
let match = find.exec(node.value)
|
|
31
|
+
|
|
32
|
+
while (match) {
|
|
33
|
+
const position = match.index
|
|
34
|
+
|
|
35
|
+
if (start !== position)
|
|
36
|
+
result.push({
|
|
37
|
+
type: 'text',
|
|
38
|
+
value: node.value.slice(start, position),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
result.push({ type: 'break', data: { isInline: true } })
|
|
42
|
+
start = position + match[0].length
|
|
43
|
+
match = find.exec(node.value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const hasResultAndIndex =
|
|
47
|
+
result.length > 0 && parent && typeof index === 'number'
|
|
48
|
+
|
|
49
|
+
if (!hasResultAndIndex) return
|
|
50
|
+
|
|
51
|
+
if (start < node.value.length)
|
|
52
|
+
result.push({ type: 'text', value: node.value.slice(start) })
|
|
53
|
+
|
|
54
|
+
parent.children.splice(index, 1, ...result)
|
|
55
|
+
return index + result.length
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
withMeta(remarkLineBreak.plugin, {
|
|
62
|
+
displayName: 'Remark<remarkLineBreak>',
|
|
63
|
+
group: 'Remark',
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
withMeta(remarkLineBreak.options, {
|
|
67
|
+
displayName: 'RemarkConfig<remarkLineBreak>',
|
|
68
|
+
group: 'Remark',
|
|
69
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Node } from '@jvs-milkdown/transformer'
|
|
2
|
+
|
|
3
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
4
|
+
import { visit } from 'unist-util-visit'
|
|
5
|
+
|
|
6
|
+
import { withMeta } from '../__internal__'
|
|
7
|
+
|
|
8
|
+
/// This plugin is used to keep the marker (`_` and `*`) of emphasis and strong nodes.
|
|
9
|
+
export const remarkMarker = $remark(
|
|
10
|
+
'remarkMarker',
|
|
11
|
+
() => () => (tree, file) => {
|
|
12
|
+
const getMarker = (node: Node) => {
|
|
13
|
+
return (file.value as string).charAt(node.position!.start.offset!)
|
|
14
|
+
}
|
|
15
|
+
visit(
|
|
16
|
+
tree,
|
|
17
|
+
(node: Node) => ['strong', 'emphasis'].includes(node.type),
|
|
18
|
+
(node: Node) => {
|
|
19
|
+
;(node as Node & { marker: string }).marker = getMarker(node)
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
withMeta(remarkMarker.plugin, {
|
|
26
|
+
displayName: 'Remark<remarkMarker>',
|
|
27
|
+
group: 'Remark',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
withMeta(remarkMarker.options, {
|
|
31
|
+
displayName: 'RemarkConfig<remarkMarker>',
|
|
32
|
+
group: 'Remark',
|
|
33
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Node } from '@jvs-milkdown/transformer'
|
|
2
|
+
|
|
3
|
+
import { $remark } from '@jvs-milkdown/utils'
|
|
4
|
+
import { visitParents } from 'unist-util-visit-parents'
|
|
5
|
+
|
|
6
|
+
import { withMeta } from '../__internal__'
|
|
7
|
+
|
|
8
|
+
function visitEmptyLine(ast: Node) {
|
|
9
|
+
return visitParents(
|
|
10
|
+
ast,
|
|
11
|
+
(node: Node) =>
|
|
12
|
+
node.type === 'html' &&
|
|
13
|
+
['<br />', '<br>', '<br >', '<br/>'].includes(
|
|
14
|
+
(node as Node & { value: string }).value?.trim()
|
|
15
|
+
),
|
|
16
|
+
(node: Node, parents: Node[]) => {
|
|
17
|
+
if (!parents.length) return
|
|
18
|
+
const parent = parents[parents.length - 1] as
|
|
19
|
+
| (Node & { children: Node[] })
|
|
20
|
+
| undefined
|
|
21
|
+
if (!parent) return
|
|
22
|
+
const index = parent.children.indexOf(node)
|
|
23
|
+
if (index === -1) return
|
|
24
|
+
|
|
25
|
+
parent.children.splice(index, 1)
|
|
26
|
+
},
|
|
27
|
+
true
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// @internal
|
|
32
|
+
/// This plugin is used to preserve the empty line.
|
|
33
|
+
/// Markdown will fold the empty line into the previous line by default.
|
|
34
|
+
/// This plugin will preserve the empty line by converting `<br />` to `line-break`.
|
|
35
|
+
/// This plugin should be used with `linebreakSchema` to work.
|
|
36
|
+
export const remarkPreserveEmptyLinePlugin = $remark(
|
|
37
|
+
'remark-preserve-empty-line',
|
|
38
|
+
() => () => visitEmptyLine
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
withMeta(remarkPreserveEmptyLinePlugin.plugin, {
|
|
42
|
+
displayName: 'Remark<remarkPreserveEmptyLine>',
|
|
43
|
+
group: 'Remark',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
withMeta(remarkPreserveEmptyLinePlugin.options, {
|
|
47
|
+
displayName: 'RemarkConfig<remarkPreserveEmptyLine>',
|
|
48
|
+
group: 'Remark',
|
|
49
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EditorView } from '@jvs-milkdown/prose/view'
|
|
2
|
+
|
|
3
|
+
import { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
|
|
4
|
+
import { $prose } from '@jvs-milkdown/utils'
|
|
5
|
+
|
|
6
|
+
import { withMeta } from '../__internal__'
|
|
7
|
+
import { headingIdGenerator, headingSchema } from '../node/heading'
|
|
8
|
+
|
|
9
|
+
/// This plugin is used to sync the heading id when the heading content changes.
|
|
10
|
+
/// It will use the `headingIdGenerator` to generate the id.
|
|
11
|
+
export const syncHeadingIdPlugin = $prose((ctx) => {
|
|
12
|
+
const headingIdPluginKey = new PluginKey('MILKDOWN_HEADING_ID')
|
|
13
|
+
|
|
14
|
+
const updateId = (view: EditorView) => {
|
|
15
|
+
if (view.composing) return
|
|
16
|
+
|
|
17
|
+
const getId = ctx.get(headingIdGenerator.key)
|
|
18
|
+
const tr = view.state.tr.setMeta('addToHistory', false)
|
|
19
|
+
|
|
20
|
+
let found = false
|
|
21
|
+
const idMap: Record<string, number> = {}
|
|
22
|
+
|
|
23
|
+
view.state.doc.descendants((node, pos) => {
|
|
24
|
+
if (node.type === headingSchema.type(ctx)) {
|
|
25
|
+
if (node.textContent.trim().length === 0) return
|
|
26
|
+
|
|
27
|
+
const attrs = node.attrs
|
|
28
|
+
let id = getId(node)
|
|
29
|
+
if (idMap[id]) {
|
|
30
|
+
idMap[id]! += 1
|
|
31
|
+
id += `-#${idMap[id]}`
|
|
32
|
+
} else {
|
|
33
|
+
idMap[id] = 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (attrs.id !== id) {
|
|
37
|
+
found = true
|
|
38
|
+
tr.setMeta(headingIdPluginKey, true).setNodeMarkup(pos, undefined, {
|
|
39
|
+
...attrs,
|
|
40
|
+
id,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (found) view.dispatch(tr)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new Plugin({
|
|
50
|
+
key: headingIdPluginKey,
|
|
51
|
+
view: (view) => {
|
|
52
|
+
updateId(view)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
update: (view, prevState) => {
|
|
56
|
+
if (view.state.doc.eq(prevState.doc)) return
|
|
57
|
+
updateId(view)
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
withMeta(syncHeadingIdPlugin, {
|
|
65
|
+
displayName: 'Prose<syncHeadingIdPlugin>',
|
|
66
|
+
group: 'Prose',
|
|
67
|
+
})
|