@jvs-milkdown/components 1.2.13 → 1.2.15
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/diff-block/config.d.ts +10 -0
- package/lib/diff-block/config.d.ts.map +1 -0
- package/lib/diff-block/index.d.ts +7 -0
- package/lib/diff-block/index.d.ts.map +1 -0
- package/lib/diff-block/index.js +296 -0
- package/lib/diff-block/index.js.map +1 -0
- package/lib/diff-block/remark-plugin.d.ts +2 -0
- package/lib/diff-block/remark-plugin.d.ts.map +1 -0
- package/lib/diff-block/schema.d.ts +3 -0
- package/lib/diff-block/schema.d.ts.map +1 -0
- package/lib/diff-block/view/index.d.ts +3 -0
- package/lib/diff-block/view/index.d.ts.map +1 -0
- package/lib/diff-block/view/node-view.d.ts +25 -0
- package/lib/diff-block/view/node-view.d.ts.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +294 -2
- package/lib/index.js.map +1 -1
- package/lib/table-block/index.js +2 -2
- package/lib/table-block/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -10
- package/src/diff-block/config.ts +42 -0
- package/src/diff-block/index.ts +19 -0
- package/src/diff-block/remark-plugin.ts +59 -0
- package/src/diff-block/schema.ts +86 -0
- package/src/diff-block/view/index.ts +22 -0
- package/src/diff-block/view/node-view.ts +154 -0
- package/src/index.ts +1 -0
- package/src/table-block/view/component.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jvs-milkdown/components",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.15",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"milkdown",
|
|
6
6
|
"milkdown plugin"
|
|
@@ -46,19 +46,24 @@
|
|
|
46
46
|
"./table-block": {
|
|
47
47
|
"types": "./lib/table-block/index.d.ts",
|
|
48
48
|
"import": "./lib/table-block/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./diff-block": {
|
|
51
|
+
"types": "./lib/diff-block/index.d.ts",
|
|
52
|
+
"import": "./lib/diff-block/index.js"
|
|
49
53
|
}
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
56
|
+
"@codemirror/merge": "^6.12.1",
|
|
52
57
|
"@floating-ui/dom": "^1.5.1",
|
|
53
|
-
"@jvs-milkdown/core": "^1.2.
|
|
54
|
-
"@jvs-milkdown/ctx": "^1.2.
|
|
55
|
-
"@jvs-milkdown/exception": "^1.2.
|
|
56
|
-
"@jvs-milkdown/plugin-tooltip": "^1.2.
|
|
57
|
-
"@jvs-milkdown/preset-commonmark": "^1.2.
|
|
58
|
-
"@jvs-milkdown/preset-gfm": "^1.2.
|
|
59
|
-
"@jvs-milkdown/prose": "^1.2.
|
|
60
|
-
"@jvs-milkdown/transformer": "^1.2.
|
|
61
|
-
"@jvs-milkdown/utils": "^1.2.
|
|
58
|
+
"@jvs-milkdown/core": "^1.2.15",
|
|
59
|
+
"@jvs-milkdown/ctx": "^1.2.15",
|
|
60
|
+
"@jvs-milkdown/exception": "^1.2.15",
|
|
61
|
+
"@jvs-milkdown/plugin-tooltip": "^1.2.15",
|
|
62
|
+
"@jvs-milkdown/preset-commonmark": "^1.2.15",
|
|
63
|
+
"@jvs-milkdown/preset-gfm": "^1.2.15",
|
|
64
|
+
"@jvs-milkdown/prose": "^1.2.15",
|
|
65
|
+
"@jvs-milkdown/transformer": "^1.2.15",
|
|
66
|
+
"@jvs-milkdown/utils": "^1.2.15",
|
|
62
67
|
"@types/lodash-es": "^4.17.12",
|
|
63
68
|
"clsx": "^2.0.0",
|
|
64
69
|
"dompurify": "^3.2.5",
|
|
@@ -104,6 +109,9 @@
|
|
|
104
109
|
],
|
|
105
110
|
"table-block": [
|
|
106
111
|
"./lib/table-block/index.d.ts"
|
|
112
|
+
],
|
|
113
|
+
"diff-block": [
|
|
114
|
+
"./lib/diff-block/index.d.ts"
|
|
107
115
|
]
|
|
108
116
|
}
|
|
109
117
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Extension } from '@codemirror/state'
|
|
2
|
+
|
|
3
|
+
import { $ctx } from '@jvs-milkdown/utils'
|
|
4
|
+
|
|
5
|
+
import { withMeta } from '../__internal__/meta'
|
|
6
|
+
|
|
7
|
+
export interface DiffBlockConfig {
|
|
8
|
+
extensions: Extension[]
|
|
9
|
+
languages: Array<string>
|
|
10
|
+
renderLanguage: (language: string, selected: boolean) => HTMLElement
|
|
11
|
+
theme: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const defaultConfig: DiffBlockConfig = {
|
|
15
|
+
extensions: [],
|
|
16
|
+
languages: [
|
|
17
|
+
'text',
|
|
18
|
+
'typescript',
|
|
19
|
+
'javascript',
|
|
20
|
+
'html',
|
|
21
|
+
'css',
|
|
22
|
+
'json',
|
|
23
|
+
'markdown',
|
|
24
|
+
],
|
|
25
|
+
renderLanguage: (language, selected) => {
|
|
26
|
+
const span = document.createElement('span')
|
|
27
|
+
span.className = 'milkdown-diff-block-language'
|
|
28
|
+
span.textContent = language
|
|
29
|
+
if (selected) {
|
|
30
|
+
span.classList.add('selected')
|
|
31
|
+
}
|
|
32
|
+
return span
|
|
33
|
+
},
|
|
34
|
+
theme: 'dark',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const diffBlockConfig = $ctx(defaultConfig, 'diffBlockConfig')
|
|
38
|
+
|
|
39
|
+
withMeta(diffBlockConfig, {
|
|
40
|
+
displayName: 'Ctx<diffBlockConfig>',
|
|
41
|
+
group: 'DiffBlock',
|
|
42
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MilkdownPlugin } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { diffBlockConfig } from './config'
|
|
4
|
+
import { diffBlockSchema } from './schema'
|
|
5
|
+
import { diffBlockView } from './view'
|
|
6
|
+
|
|
7
|
+
export * from './config'
|
|
8
|
+
export * from './schema'
|
|
9
|
+
export * from './view'
|
|
10
|
+
export * from './remark-plugin'
|
|
11
|
+
|
|
12
|
+
import { remarkDiffBlockPlugin } from './remark-plugin'
|
|
13
|
+
|
|
14
|
+
export const diffBlock: MilkdownPlugin[] = [
|
|
15
|
+
diffBlockConfig,
|
|
16
|
+
remarkDiffBlockPlugin,
|
|
17
|
+
diffBlockSchema,
|
|
18
|
+
diffBlockView,
|
|
19
|
+
].flat()
|
|
@@ -0,0 +1,59 @@
|
|
|
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__/meta'
|
|
7
|
+
|
|
8
|
+
function visitDiffBlock(ast: Node) {
|
|
9
|
+
return visit(
|
|
10
|
+
ast,
|
|
11
|
+
'code',
|
|
12
|
+
(
|
|
13
|
+
node: Node & { lang?: string; value?: string },
|
|
14
|
+
index: number,
|
|
15
|
+
parent: Node & { children: Node[] }
|
|
16
|
+
) => {
|
|
17
|
+
if (node.lang !== 'diff_block') return
|
|
18
|
+
|
|
19
|
+
let originalText = ''
|
|
20
|
+
let modifiedText = node.value || ''
|
|
21
|
+
let language = 'text'
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (modifiedText.startsWith('{')) {
|
|
25
|
+
const parsed = JSON.parse(modifiedText)
|
|
26
|
+
originalText = parsed.originalText || ''
|
|
27
|
+
modifiedText = parsed.modifiedText || ''
|
|
28
|
+
language = parsed.language || 'text'
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// fallback
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const newNode = {
|
|
35
|
+
type: 'diff_block',
|
|
36
|
+
originalText,
|
|
37
|
+
modifiedText,
|
|
38
|
+
language,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
parent.children.splice(index, 1, newNode as any)
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const remarkDiffBlockPlugin = $remark(
|
|
47
|
+
'remark-diff-block',
|
|
48
|
+
() => () => visitDiffBlock
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
withMeta(remarkDiffBlockPlugin.plugin, {
|
|
52
|
+
displayName: 'Remark<remarkDiffBlock>',
|
|
53
|
+
group: 'DiffBlock',
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
withMeta(remarkDiffBlockPlugin.options, {
|
|
57
|
+
displayName: 'RemarkConfig<remarkDiffBlock>',
|
|
58
|
+
group: 'DiffBlock',
|
|
59
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { expectDomTypeError } from '@jvs-milkdown/exception'
|
|
2
|
+
import { $nodeSchema } from '@jvs-milkdown/utils'
|
|
3
|
+
|
|
4
|
+
import { withMeta } from '../__internal__/meta'
|
|
5
|
+
|
|
6
|
+
export const DIFF_BLOCK_DATA_TYPE = 'diff-block'
|
|
7
|
+
|
|
8
|
+
export const diffBlockSchema = $nodeSchema('diff_block', () => {
|
|
9
|
+
return {
|
|
10
|
+
content: 'text*',
|
|
11
|
+
marks: '',
|
|
12
|
+
group: 'block',
|
|
13
|
+
selectable: true,
|
|
14
|
+
isolating: true,
|
|
15
|
+
code: true,
|
|
16
|
+
attrs: {
|
|
17
|
+
originalText: { default: '' },
|
|
18
|
+
modifiedText: { default: '' },
|
|
19
|
+
language: { default: 'text' },
|
|
20
|
+
},
|
|
21
|
+
parseDOM: [
|
|
22
|
+
{
|
|
23
|
+
tag: `div[data-type="${DIFF_BLOCK_DATA_TYPE}"]`,
|
|
24
|
+
preserveWhitespace: 'full',
|
|
25
|
+
getAttrs: (dom) => {
|
|
26
|
+
if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom)
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
originalText: dom.getAttribute('data-original-text') || '',
|
|
30
|
+
modifiedText: dom.getAttribute('data-modified-text') || '',
|
|
31
|
+
language: dom.getAttribute('data-language') || 'text',
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
toDOM: (node) => {
|
|
37
|
+
return [
|
|
38
|
+
'div',
|
|
39
|
+
{
|
|
40
|
+
'data-type': DIFF_BLOCK_DATA_TYPE,
|
|
41
|
+
'data-original-text': node.attrs.originalText,
|
|
42
|
+
'data-modified-text': node.attrs.modifiedText,
|
|
43
|
+
'data-language': node.attrs.language,
|
|
44
|
+
class: 'milkdown-diff-block',
|
|
45
|
+
},
|
|
46
|
+
['code', { spellcheck: 'false' }, 0],
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
parseMarkdown: {
|
|
50
|
+
match: ({ type }) => type === 'diff_block',
|
|
51
|
+
runner: (state, node, type) => {
|
|
52
|
+
const originalText = (node as any).originalText as string
|
|
53
|
+
const modifiedText = (node as any).modifiedText as string
|
|
54
|
+
const language = (node as any).language as string
|
|
55
|
+
|
|
56
|
+
state.addNode(type, {
|
|
57
|
+
originalText: originalText || '',
|
|
58
|
+
modifiedText: modifiedText || '',
|
|
59
|
+
language: language || 'text',
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
toMarkdown: {
|
|
64
|
+
match: (node) => node.type.name === 'diff_block',
|
|
65
|
+
runner: (state, node) => {
|
|
66
|
+
state.addNode('code', undefined, undefined, {
|
|
67
|
+
lang: 'diff_block',
|
|
68
|
+
value: JSON.stringify(
|
|
69
|
+
{
|
|
70
|
+
originalText: node.attrs.originalText,
|
|
71
|
+
modifiedText: node.attrs.modifiedText,
|
|
72
|
+
language: node.attrs.language,
|
|
73
|
+
},
|
|
74
|
+
null,
|
|
75
|
+
2
|
|
76
|
+
),
|
|
77
|
+
})
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
withMeta(diffBlockSchema.node, {
|
|
84
|
+
displayName: 'NodeSchema<diff_block>',
|
|
85
|
+
group: 'DiffBlock',
|
|
86
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NodeViewConstructor } from '@jvs-milkdown/prose/view'
|
|
2
|
+
|
|
3
|
+
import { $view } from '@jvs-milkdown/utils'
|
|
4
|
+
|
|
5
|
+
import { withMeta } from '../../__internal__/meta'
|
|
6
|
+
import { diffBlockConfig } from '../config'
|
|
7
|
+
import { diffBlockSchema } from '../schema'
|
|
8
|
+
import { DiffBlockNodeView } from './node-view'
|
|
9
|
+
|
|
10
|
+
export const diffBlockView = $view(
|
|
11
|
+
diffBlockSchema.node,
|
|
12
|
+
(ctx): NodeViewConstructor => {
|
|
13
|
+
const config = ctx.get(diffBlockConfig.key)
|
|
14
|
+
return (node, view, getPos) =>
|
|
15
|
+
new DiffBlockNodeView(node, view, getPos, config)
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
withMeta(diffBlockView, {
|
|
20
|
+
displayName: 'NodeView<diff_block>',
|
|
21
|
+
group: 'DiffBlock',
|
|
22
|
+
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Node } from '@jvs-milkdown/prose/model'
|
|
2
|
+
import type { EditorView, NodeView } from '@jvs-milkdown/prose/view'
|
|
3
|
+
|
|
4
|
+
import { unifiedMergeView } from '@codemirror/merge'
|
|
5
|
+
import { Compartment, EditorState } from '@codemirror/state'
|
|
6
|
+
import {
|
|
7
|
+
EditorView as CodeMirror,
|
|
8
|
+
type ViewUpdate,
|
|
9
|
+
drawSelection,
|
|
10
|
+
} from '@codemirror/view'
|
|
11
|
+
import { ref, watchEffect, type WatchHandle } from 'vue'
|
|
12
|
+
|
|
13
|
+
import type { DiffBlockConfig } from '../config'
|
|
14
|
+
|
|
15
|
+
export class DiffBlockNodeView implements NodeView {
|
|
16
|
+
dom: HTMLElement
|
|
17
|
+
cm: CodeMirror
|
|
18
|
+
|
|
19
|
+
selected = ref(false)
|
|
20
|
+
|
|
21
|
+
private updating = false
|
|
22
|
+
private disposeSelectedWatcher: WatchHandle
|
|
23
|
+
|
|
24
|
+
private readonly readOnlyConf: Compartment
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
public node: Node,
|
|
28
|
+
public view: EditorView,
|
|
29
|
+
public getPos: () => number | undefined,
|
|
30
|
+
public config: DiffBlockConfig
|
|
31
|
+
) {
|
|
32
|
+
this.readOnlyConf = new Compartment()
|
|
33
|
+
|
|
34
|
+
this.cm = new CodeMirror({
|
|
35
|
+
doc: this.node.attrs.modifiedText || '',
|
|
36
|
+
root: this.view.root,
|
|
37
|
+
extensions: [
|
|
38
|
+
this.readOnlyConf.of(EditorState.readOnly.of(true)),
|
|
39
|
+
drawSelection(),
|
|
40
|
+
unifiedMergeView({
|
|
41
|
+
original: this.node.attrs.originalText || '',
|
|
42
|
+
mergeControls: true,
|
|
43
|
+
}),
|
|
44
|
+
CodeMirror.updateListener.of(this.forwardUpdate),
|
|
45
|
+
...config.extensions,
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
this.dom = document.createElement('div')
|
|
50
|
+
this.dom.className = 'milkdown-diff-block'
|
|
51
|
+
this.dom.appendChild(this.cm.dom)
|
|
52
|
+
|
|
53
|
+
this.disposeSelectedWatcher = watchEffect(() => {
|
|
54
|
+
const isSelected = this.selected.value
|
|
55
|
+
if (isSelected) {
|
|
56
|
+
this.dom.classList.add('selected')
|
|
57
|
+
} else {
|
|
58
|
+
this.dom.classList.remove('selected')
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private forwardUpdate = (update: ViewUpdate) => {
|
|
64
|
+
if (this.updating) return
|
|
65
|
+
if (!update.docChanged) return
|
|
66
|
+
|
|
67
|
+
// If the doc changed via CodeMirror merge controls (accept/reject),
|
|
68
|
+
// sync it back to ProseMirror's modifiedText attribute.
|
|
69
|
+
const pos = this.getPos()
|
|
70
|
+
if (pos == null) return
|
|
71
|
+
|
|
72
|
+
const newText = update.state.doc.toString()
|
|
73
|
+
const tr = this.view.state.tr.setNodeAttribute(pos, 'modifiedText', newText)
|
|
74
|
+
this.view.dispatch(tr)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setSelection(anchor: number, head: number) {
|
|
78
|
+
if (!this.cm.dom.isConnected) return
|
|
79
|
+
|
|
80
|
+
this.cm.focus()
|
|
81
|
+
this.updating = true
|
|
82
|
+
this.cm.dispatch({ selection: { anchor, head } })
|
|
83
|
+
this.updating = false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
update(node: Node) {
|
|
87
|
+
if (node.type !== this.node.type) return false
|
|
88
|
+
|
|
89
|
+
if (this.updating) return true
|
|
90
|
+
|
|
91
|
+
this.node = node
|
|
92
|
+
|
|
93
|
+
const modifiedText = node.attrs.modifiedText || ''
|
|
94
|
+
|
|
95
|
+
// Reconfigure unifiedMergeView with the current original text if needed
|
|
96
|
+
// (We might need to recreate it if originalText changes, but typically it doesn't change from outside)
|
|
97
|
+
|
|
98
|
+
const change = computeChange(this.cm.state.doc.toString(), modifiedText)
|
|
99
|
+
if (change) {
|
|
100
|
+
this.updating = true
|
|
101
|
+
this.cm.dispatch({
|
|
102
|
+
changes: { from: change.from, to: change.to, insert: change.text },
|
|
103
|
+
})
|
|
104
|
+
this.updating = false
|
|
105
|
+
}
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
selectNode() {
|
|
110
|
+
this.selected.value = true
|
|
111
|
+
this.cm.focus()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
deselectNode() {
|
|
115
|
+
this.selected.value = false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
stopEvent() {
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
destroy() {
|
|
123
|
+
this.cm.destroy()
|
|
124
|
+
this.disposeSelectedWatcher()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function computeChange(
|
|
129
|
+
oldVal: string,
|
|
130
|
+
newVal: string
|
|
131
|
+
): { from: number; to: number; text: string } | null {
|
|
132
|
+
if (oldVal === newVal) return null
|
|
133
|
+
|
|
134
|
+
let start = 0
|
|
135
|
+
let oldEnd = oldVal.length
|
|
136
|
+
let newEnd = newVal.length
|
|
137
|
+
|
|
138
|
+
while (
|
|
139
|
+
start < oldEnd &&
|
|
140
|
+
oldVal.charCodeAt(start) === newVal.charCodeAt(start)
|
|
141
|
+
)
|
|
142
|
+
++start
|
|
143
|
+
|
|
144
|
+
while (
|
|
145
|
+
oldEnd > start &&
|
|
146
|
+
newEnd > start &&
|
|
147
|
+
oldVal.charCodeAt(oldEnd - 1) === newVal.charCodeAt(newEnd - 1)
|
|
148
|
+
) {
|
|
149
|
+
oldEnd--
|
|
150
|
+
newEnd--
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { from: start, to: oldEnd, text: newVal.slice(start, newEnd) }
|
|
154
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -467,14 +467,14 @@ export const TableBlock = defineComponent<TableBlockProps>({
|
|
|
467
467
|
let shouldUpdate = false
|
|
468
468
|
for (const mut of mutations) {
|
|
469
469
|
if (mut.type === 'childList') {
|
|
470
|
-
for (const node of mut.addedNodes) {
|
|
470
|
+
for (const node of Array.from(mut.addedNodes)) {
|
|
471
471
|
if (
|
|
472
472
|
node instanceof HTMLElement &&
|
|
473
473
|
['TR', 'TD', 'TH', 'TBODY'].includes(node.nodeName)
|
|
474
474
|
)
|
|
475
475
|
shouldUpdate = true
|
|
476
476
|
}
|
|
477
|
-
for (const node of mut.removedNodes) {
|
|
477
|
+
for (const node of Array.from(mut.removedNodes)) {
|
|
478
478
|
if (
|
|
479
479
|
node instanceof HTMLElement &&
|
|
480
480
|
['TR', 'TD', 'TH', 'TBODY'].includes(node.nodeName)
|