@milkdown/preset-commonmark 7.15.0 → 7.15.2
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/lib/__test__/html.spec.d.ts +2 -0
- package/lib/__test__/html.spec.d.ts.map +1 -0
- package/lib/index.js +54 -50
- package/lib/index.js.map +1 -1
- package/lib/mark/strong.d.ts.map +1 -1
- package/lib/node/heading.d.ts.map +1 -1
- package/lib/plugin/remark-html-transformer.d.ts.map +1 -1
- package/lib/plugin/sync-list-order-plugin.d.ts.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/__test__/html.spec.ts +46 -0
- package/src/mark/emphasis.ts +1 -1
- package/src/mark/strong.ts +14 -7
- package/src/node/heading.ts +1 -5
- package/src/plugin/remark-html-transformer.ts +7 -1
- package/src/plugin/sync-list-order-plugin.ts +64 -45
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest'
|
|
2
|
+
import type { EditorView } from '@milkdown/prose/view'
|
|
3
|
+
|
|
4
|
+
import { defaultValueCtx, Editor, editorViewCtx } from '@milkdown/core'
|
|
5
|
+
import { expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { commonmark } from '..'
|
|
8
|
+
|
|
9
|
+
function createEditor() {
|
|
10
|
+
const editor = Editor.make()
|
|
11
|
+
editor.use(commonmark)
|
|
12
|
+
return editor
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const htmlInBlockquote = {
|
|
16
|
+
name: 'htmlInBlockquote',
|
|
17
|
+
defaultValue: `
|
|
18
|
+
> <p>Hello, world!</p>
|
|
19
|
+
`,
|
|
20
|
+
check: (view: EditorView) => {
|
|
21
|
+
expect(view.dom.querySelector('blockquote')).toBeInTheDocument()
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const htmlInListItem = {
|
|
26
|
+
name: 'htmlInListItem',
|
|
27
|
+
defaultValue: `
|
|
28
|
+
* <p>List item with HTML</p>
|
|
29
|
+
`,
|
|
30
|
+
check: (view: EditorView) => {
|
|
31
|
+
expect(view.dom.querySelector('li')).toBeInTheDocument()
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
;[htmlInBlockquote, htmlInListItem].forEach(({ name, defaultValue, check }) => {
|
|
36
|
+
it(`should render html in ${name}`, async () => {
|
|
37
|
+
const editor = createEditor()
|
|
38
|
+
editor.config((ctx) => {
|
|
39
|
+
ctx.set(defaultValueCtx, defaultValue)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
await editor.create()
|
|
43
|
+
|
|
44
|
+
check(editor.ctx.get(editorViewCtx))
|
|
45
|
+
})
|
|
46
|
+
})
|
package/src/mark/emphasis.ts
CHANGED
|
@@ -91,7 +91,7 @@ withMeta(emphasisStarInputRule, {
|
|
|
91
91
|
|
|
92
92
|
/// Input rule for use `_` to create emphasis mark.
|
|
93
93
|
export const emphasisUnderscoreInputRule = $inputRule((ctx) => {
|
|
94
|
-
return markRule(
|
|
94
|
+
return markRule(/\b_(?![_\s])(.*?[^_\s])_\b/, emphasisSchema.type(ctx), {
|
|
95
95
|
getAttr: () => ({
|
|
96
96
|
marker: '_',
|
|
97
97
|
}),
|
package/src/mark/strong.ts
CHANGED
|
@@ -86,13 +86,20 @@ withMeta(toggleStrongCommand, {
|
|
|
86
86
|
|
|
87
87
|
/// A input rule that will capture the strong mark.
|
|
88
88
|
export const strongInputRule = $inputRule((ctx) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
)
|
|
96
103
|
})
|
|
97
104
|
|
|
98
105
|
withMeta(strongInputRule, {
|
package/src/node/heading.ts
CHANGED
|
@@ -21,11 +21,7 @@ const headingIndex = Array(6)
|
|
|
21
21
|
.map((_, i) => i + 1)
|
|
22
22
|
|
|
23
23
|
function defaultHeadingIdGenerator(node: Node) {
|
|
24
|
-
return node.textContent
|
|
25
|
-
.toLowerCase()
|
|
26
|
-
.trim()
|
|
27
|
-
.replace(/[^\w\s-]/g, '')
|
|
28
|
-
.replace(/\s+/g, '-')
|
|
24
|
+
return node.textContent.toLowerCase().trim().replace(/\s+/g, '-')
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
/// This is a slice contains a function to generate heading id.
|
|
@@ -38,6 +38,10 @@ function flatMapWithDepth(
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
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
|
+
|
|
41
45
|
/// @internal
|
|
42
46
|
/// This plugin should be deprecated after we support HTML.
|
|
43
47
|
export const remarkHtmlTransformer = $remark(
|
|
@@ -46,7 +50,9 @@ export const remarkHtmlTransformer = $remark(
|
|
|
46
50
|
flatMapWithDepth(tree, (node, _index, parent) => {
|
|
47
51
|
if (!isHTML(node)) return [node]
|
|
48
52
|
|
|
49
|
-
|
|
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)) {
|
|
50
56
|
node.children = [{ ...node }]
|
|
51
57
|
delete node.value
|
|
52
58
|
;(node as { type: string }).type = 'paragraph'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Node } from '@milkdown/prose/model'
|
|
2
|
+
import type { EditorState, Transaction } from '@milkdown/prose/state'
|
|
2
3
|
|
|
3
4
|
import { Plugin, PluginKey } from '@milkdown/prose/state'
|
|
4
5
|
import { $prose } from '@milkdown/utils'
|
|
@@ -10,13 +11,24 @@ import { orderedListSchema } from '../node/ordered-list'
|
|
|
10
11
|
|
|
11
12
|
/// This plugin is used to keep the label of list item up to date in ordered list.
|
|
12
13
|
export const syncListOrderPlugin = $prose((ctx) => {
|
|
13
|
-
const syncOrderLabel = (
|
|
14
|
-
|
|
14
|
+
const syncOrderLabel = (
|
|
15
|
+
transactions: readonly Transaction[],
|
|
16
|
+
_oldState: EditorState,
|
|
17
|
+
newState: EditorState
|
|
18
|
+
) => {
|
|
19
|
+
// Skip if composing or not editable
|
|
20
|
+
if (
|
|
21
|
+
!newState.selection ||
|
|
22
|
+
transactions.some(
|
|
23
|
+
(tr) => tr.getMeta('addToHistory') === false || !tr.isGeneric
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
return null
|
|
15
27
|
|
|
16
28
|
const orderedListType = orderedListSchema.type(ctx)
|
|
17
29
|
const bulletListType = bulletListSchema.type(ctx)
|
|
18
30
|
const listItemType = listItemSchema.type(ctx)
|
|
19
|
-
|
|
31
|
+
|
|
20
32
|
const handleNodeItem = (
|
|
21
33
|
attrs: Record<string, any>,
|
|
22
34
|
index: number
|
|
@@ -31,57 +43,64 @@ export const syncListOrderPlugin = $prose((ctx) => {
|
|
|
31
43
|
return changed
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
let tr =
|
|
46
|
+
let tr = newState.tr
|
|
35
47
|
let needDispatch = false
|
|
36
|
-
state.doc.descendants((node, pos, parent, index) => {
|
|
37
|
-
if (node.type === bulletListType) {
|
|
38
|
-
const base = node.maybeChild(0)
|
|
39
|
-
if (base?.type === listItemType && base.attrs.listType === 'ordered') {
|
|
40
|
-
needDispatch = true
|
|
41
|
-
tr.setNodeMarkup(pos, orderedListType, { spread: 'true' })
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
node.type === listItemType &&
|
|
54
|
-
parent?.type === orderedListType
|
|
55
|
-
) {
|
|
56
|
-
const attrs = { ...node.attrs }
|
|
57
|
-
let changed = false
|
|
58
|
-
if (attrs.listType !== 'ordered') {
|
|
59
|
-
attrs.listType = 'ordered'
|
|
60
|
-
changed = true
|
|
61
|
-
}
|
|
49
|
+
newState.doc.descendants(
|
|
50
|
+
(node: Node, pos: number, parent: Node | null, index: number) => {
|
|
51
|
+
if (node.type === bulletListType) {
|
|
52
|
+
const base = node.maybeChild(0)
|
|
53
|
+
if (
|
|
54
|
+
base?.type === listItemType &&
|
|
55
|
+
base.attrs.listType === 'ordered'
|
|
56
|
+
) {
|
|
57
|
+
needDispatch = true
|
|
58
|
+
tr.setNodeMarkup(pos, orderedListType, { spread: 'true' })
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
node.descendants(
|
|
61
|
+
(
|
|
62
|
+
child: Node,
|
|
63
|
+
pos: number,
|
|
64
|
+
_parent: Node | null,
|
|
65
|
+
index: number
|
|
66
|
+
) => {
|
|
67
|
+
if (child.type === listItemType) {
|
|
68
|
+
const attrs = { ...child.attrs }
|
|
69
|
+
const changed = handleNodeItem(attrs, index)
|
|
70
|
+
if (changed) tr = tr.setNodeMarkup(pos, undefined, attrs)
|
|
71
|
+
}
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
} else if (
|
|
77
|
+
node.type === listItemType &&
|
|
78
|
+
parent?.type === orderedListType
|
|
79
|
+
) {
|
|
80
|
+
const attrs = { ...node.attrs }
|
|
81
|
+
let changed = false
|
|
82
|
+
if (attrs.listType !== 'ordered') {
|
|
83
|
+
attrs.listType = 'ordered'
|
|
84
|
+
changed = true
|
|
85
|
+
}
|
|
65
86
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
const base = parent?.maybeChild(0)
|
|
88
|
+
if (base) changed = handleNodeItem(attrs, index)
|
|
89
|
+
|
|
90
|
+
if (changed) {
|
|
91
|
+
tr = tr.setNodeMarkup(pos, undefined, attrs)
|
|
92
|
+
needDispatch = true
|
|
93
|
+
}
|
|
69
94
|
}
|
|
70
95
|
}
|
|
71
|
-
|
|
96
|
+
)
|
|
72
97
|
|
|
73
|
-
|
|
98
|
+
return needDispatch ? tr.setMeta('addToHistory', false) : null
|
|
74
99
|
}
|
|
100
|
+
|
|
75
101
|
return new Plugin({
|
|
76
102
|
key: new PluginKey('MILKDOWN_KEEP_LIST_ORDER'),
|
|
77
|
-
|
|
78
|
-
syncOrderLabel(view)
|
|
79
|
-
return {
|
|
80
|
-
update: (view) => {
|
|
81
|
-
syncOrderLabel(view)
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
},
|
|
103
|
+
appendTransaction: syncOrderLabel,
|
|
85
104
|
})
|
|
86
105
|
})
|
|
87
106
|
|