@jvs-milkdown/utils 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 +9 -0
- package/lib/composable/$command.d.ts +9 -0
- package/lib/composable/$command.d.ts.map +1 -0
- package/lib/composable/$ctx.d.ts +6 -0
- package/lib/composable/$ctx.d.ts.map +1 -0
- package/lib/composable/$input-rule.d.ts +8 -0
- package/lib/composable/$input-rule.d.ts.map +1 -0
- package/lib/composable/$mark.d.ts +11 -0
- package/lib/composable/$mark.d.ts.map +1 -0
- package/lib/composable/$node.d.ts +11 -0
- package/lib/composable/$node.d.ts.map +1 -0
- package/lib/composable/$paste-rule.d.ts +8 -0
- package/lib/composable/$paste-rule.d.ts.map +1 -0
- package/lib/composable/$prose.d.ts +9 -0
- package/lib/composable/$prose.d.ts.map +1 -0
- package/lib/composable/$shortcut.d.ts +10 -0
- package/lib/composable/$shortcut.d.ts.map +1 -0
- package/lib/composable/$view.d.ts +11 -0
- package/lib/composable/$view.d.ts.map +1 -0
- package/lib/composable/composed/$attr.d.ts +7 -0
- package/lib/composable/composed/$attr.d.ts.map +1 -0
- package/lib/composable/composed/$mark-schema.d.ts +18 -0
- package/lib/composable/composed/$mark-schema.d.ts.map +1 -0
- package/lib/composable/composed/$node-schema.d.ts +18 -0
- package/lib/composable/composed/$node-schema.d.ts.map +1 -0
- package/lib/composable/composed/$node-schema.spec.d.ts +2 -0
- package/lib/composable/composed/$node-schema.spec.d.ts.map +1 -0
- package/lib/composable/composed/$remark.d.ts +13 -0
- package/lib/composable/composed/$remark.d.ts.map +1 -0
- package/lib/composable/composed/$use-keymap.d.ts +25 -0
- package/lib/composable/composed/$use-keymap.d.ts.map +1 -0
- package/lib/composable/composed/index.d.ts +6 -0
- package/lib/composable/composed/index.d.ts.map +1 -0
- package/lib/composable/index.d.ts +12 -0
- package/lib/composable/index.d.ts.map +1 -0
- package/lib/composable/utils.d.ts +7 -0
- package/lib/composable/utils.d.ts.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +549 -0
- package/lib/index.js.map +1 -0
- package/lib/macro/call-command.d.ts +8 -0
- package/lib/macro/call-command.d.ts.map +1 -0
- package/lib/macro/force-update.d.ts +3 -0
- package/lib/macro/force-update.d.ts.map +1 -0
- package/lib/macro/get-html.d.ts +3 -0
- package/lib/macro/get-html.d.ts.map +1 -0
- package/lib/macro/get-markdown.d.ts +6 -0
- package/lib/macro/get-markdown.d.ts.map +1 -0
- package/lib/macro/index.d.ts +12 -0
- package/lib/macro/index.d.ts.map +1 -0
- package/lib/macro/insert-pos.d.ts +3 -0
- package/lib/macro/insert-pos.d.ts.map +1 -0
- package/lib/macro/insert.d.ts +3 -0
- package/lib/macro/insert.d.ts.map +1 -0
- package/lib/macro/markdown-to-slice.d.ts +3 -0
- package/lib/macro/markdown-to-slice.d.ts.map +1 -0
- package/lib/macro/outline.d.ts +7 -0
- package/lib/macro/outline.d.ts.map +1 -0
- package/lib/macro/replace-all.d.ts +3 -0
- package/lib/macro/replace-all.d.ts.map +1 -0
- package/lib/macro/replace-range.d.ts +6 -0
- package/lib/macro/replace-range.d.ts.map +1 -0
- package/lib/macro/set-attr.d.ts +4 -0
- package/lib/macro/set-attr.d.ts.map +1 -0
- package/lib/pipe.d.ts +7 -0
- package/lib/pipe.d.ts.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -0
- package/src/composable/$command.ts +102 -0
- package/src/composable/$ctx.ts +31 -0
- package/src/composable/$input-rule.ts +55 -0
- package/src/composable/$mark.ts +87 -0
- package/src/composable/$node.ts +90 -0
- package/src/composable/$paste-rule.ts +59 -0
- package/src/composable/$prose.ts +70 -0
- package/src/composable/$shortcut.ts +66 -0
- package/src/composable/$view.ts +110 -0
- package/src/composable/composed/$attr.ts +29 -0
- package/src/composable/composed/$mark-schema.ts +65 -0
- package/src/composable/composed/$node-schema.spec.ts +139 -0
- package/src/composable/composed/$node-schema.ts +65 -0
- package/src/composable/composed/$remark.ts +53 -0
- package/src/composable/composed/$use-keymap.ts +91 -0
- package/src/composable/composed/index.ts +5 -0
- package/src/composable/index.ts +12 -0
- package/src/composable/utils.ts +63 -0
- package/src/index.ts +3 -0
- package/src/macro/call-command.ts +28 -0
- package/src/macro/force-update.ts +14 -0
- package/src/macro/get-html.ts +20 -0
- package/src/macro/get-markdown.ts +27 -0
- package/src/macro/index.ts +11 -0
- package/src/macro/insert-pos.ts +27 -0
- package/src/macro/insert.ts +38 -0
- package/src/macro/markdown-to-slice.ts +18 -0
- package/src/macro/outline.ts +21 -0
- package/src/macro/replace-all.ts +46 -0
- package/src/macro/replace-range.ts +18 -0
- package/src/macro/set-attr.ts +16 -0
- package/src/pipe.ts +72 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Editor, schemaCtx } from '@jvs-milkdown/core'
|
|
2
|
+
import { test, expect } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { $nodeSchema } from './$node-schema'
|
|
5
|
+
|
|
6
|
+
const docSchema = $nodeSchema('doc', () => {
|
|
7
|
+
return {
|
|
8
|
+
content: 'block*',
|
|
9
|
+
parseMarkdown: {
|
|
10
|
+
match: ({ type }) => type === 'root',
|
|
11
|
+
runner: (state, node, type) => {
|
|
12
|
+
state.injectRoot(node, type)
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
toMarkdown: {
|
|
16
|
+
match: (node) => node.type.name === 'doc',
|
|
17
|
+
runner: () => {},
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const textSchema = $nodeSchema('text', () => ({
|
|
23
|
+
group: 'inline',
|
|
24
|
+
parseMarkdown: {
|
|
25
|
+
match: ({ type }) => type === 'text',
|
|
26
|
+
runner: () => {},
|
|
27
|
+
},
|
|
28
|
+
toMarkdown: {
|
|
29
|
+
match: (node) => node.type.name === 'text',
|
|
30
|
+
runner: () => {},
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
const paragraphSchema = $nodeSchema('paragraph', () => ({
|
|
35
|
+
content: 'inline*',
|
|
36
|
+
group: 'block',
|
|
37
|
+
parseDOM: [{ tag: 'p' }],
|
|
38
|
+
toDOM: () => ['p', 0],
|
|
39
|
+
parseMarkdown: {
|
|
40
|
+
match: (node) => node.type === 'paragraph',
|
|
41
|
+
runner: () => {},
|
|
42
|
+
},
|
|
43
|
+
toMarkdown: {
|
|
44
|
+
match: (node) => node.type.name === 'paragraph',
|
|
45
|
+
runner: () => {},
|
|
46
|
+
},
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
test('create schema', async () => {
|
|
50
|
+
const editor = Editor.make()
|
|
51
|
+
editor.use(docSchema)
|
|
52
|
+
editor.use(textSchema)
|
|
53
|
+
editor.use(paragraphSchema)
|
|
54
|
+
await editor.create()
|
|
55
|
+
|
|
56
|
+
expect(docSchema.node.schema.content).toEqual('block*')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('extend schema', async () => {
|
|
60
|
+
const extended = docSchema.extendSchema((prev) => (ctx) => ({
|
|
61
|
+
...prev(ctx),
|
|
62
|
+
defining: true,
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
const editor = await Editor.make()
|
|
66
|
+
.use(docSchema)
|
|
67
|
+
.use(extended)
|
|
68
|
+
.use(paragraphSchema)
|
|
69
|
+
.use(textSchema)
|
|
70
|
+
.create()
|
|
71
|
+
|
|
72
|
+
expect(extended.node.schema.defining).toBe(true)
|
|
73
|
+
expect(docSchema.node.schema.defining).toBe(undefined)
|
|
74
|
+
|
|
75
|
+
const schema = editor.ctx.get(schemaCtx)
|
|
76
|
+
expect(schema.nodes.doc?.spec).toEqual(extended.node.schema)
|
|
77
|
+
expect(Object.keys(schema.nodes)).toEqual(['doc', 'paragraph', 'text'])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('double extend schema', async () => {
|
|
81
|
+
const extended = docSchema.extendSchema((prev) => (ctx) => ({
|
|
82
|
+
...prev(ctx),
|
|
83
|
+
defining: true,
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
const extended2 = extended.extendSchema((prev) => (ctx) => ({
|
|
87
|
+
...prev(ctx),
|
|
88
|
+
atom: true,
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
const editor = await Editor.make()
|
|
92
|
+
.use(docSchema)
|
|
93
|
+
.use(extended)
|
|
94
|
+
.use(extended2)
|
|
95
|
+
.use(paragraphSchema)
|
|
96
|
+
.use(textSchema)
|
|
97
|
+
.create()
|
|
98
|
+
|
|
99
|
+
expect(extended2.node.schema.atom).toBe(true)
|
|
100
|
+
expect(extended2.node.schema.defining).toBe(true)
|
|
101
|
+
|
|
102
|
+
expect(extended.node.schema.atom).toBe(undefined)
|
|
103
|
+
expect(extended.node.schema.defining).toBe(true)
|
|
104
|
+
|
|
105
|
+
expect(docSchema.node.schema.atom).toBe(undefined)
|
|
106
|
+
expect(docSchema.node.schema.defining).toBe(undefined)
|
|
107
|
+
|
|
108
|
+
const schema = editor.ctx.get(schemaCtx)
|
|
109
|
+
expect(schema.nodes.doc?.spec).toEqual(extended2.node.schema)
|
|
110
|
+
expect(Object.keys(schema.nodes)).toEqual(['doc', 'paragraph', 'text'])
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('should can register extended schema only', async () => {
|
|
114
|
+
const extended = docSchema.extendSchema((prev) => (ctx) => ({
|
|
115
|
+
...prev(ctx),
|
|
116
|
+
defining: true,
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
const extended2 = extended.extendSchema((prev) => (ctx) => ({
|
|
120
|
+
...prev(ctx),
|
|
121
|
+
atom: true,
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
const editor = await Editor.make()
|
|
125
|
+
.use(extended2)
|
|
126
|
+
.use(paragraphSchema)
|
|
127
|
+
.use(textSchema)
|
|
128
|
+
.create()
|
|
129
|
+
|
|
130
|
+
expect(extended2.node.schema.atom).toBe(true)
|
|
131
|
+
expect(extended2.node.schema.defining).toBe(true)
|
|
132
|
+
|
|
133
|
+
// this schema is not registered, so it should be undefined
|
|
134
|
+
expect(extended.node.schema).toBe(undefined)
|
|
135
|
+
|
|
136
|
+
const schema = editor.ctx.get(schemaCtx)
|
|
137
|
+
expect(schema.nodes.doc?.spec).toEqual(extended2.node.schema)
|
|
138
|
+
expect(Object.keys(schema.nodes)).toEqual(['doc', 'paragraph', 'text'])
|
|
139
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
import type { NodeSchema } from '@jvs-milkdown/transformer'
|
|
3
|
+
|
|
4
|
+
import type { $Ctx } from '../$ctx'
|
|
5
|
+
import type { $Node } from '../$node'
|
|
6
|
+
|
|
7
|
+
import { $ctx } from '../$ctx'
|
|
8
|
+
import { $node } from '../$node'
|
|
9
|
+
|
|
10
|
+
/// @internal
|
|
11
|
+
export type GetNodeSchema = (ctx: Ctx) => NodeSchema
|
|
12
|
+
|
|
13
|
+
/// @internal
|
|
14
|
+
export type $NodeSchema<T extends string> = [
|
|
15
|
+
schemaCtx: $Ctx<GetNodeSchema, T>,
|
|
16
|
+
schema: $Node,
|
|
17
|
+
] & {
|
|
18
|
+
id: $Node['id']
|
|
19
|
+
type: $Node['type']
|
|
20
|
+
node: $Node
|
|
21
|
+
ctx: $Ctx<GetNodeSchema, T>
|
|
22
|
+
key: $Ctx<GetNodeSchema, T>['key']
|
|
23
|
+
extendSchema: (
|
|
24
|
+
handler: (prev: GetNodeSchema) => GetNodeSchema
|
|
25
|
+
) => $NodeSchema<T>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Create a plugin for node schema.
|
|
29
|
+
/// The first parameter is the id of the node schema.
|
|
30
|
+
/// The second parameter is the function that returns the node schema.
|
|
31
|
+
///
|
|
32
|
+
/// The function will return a plugin with additional properties:
|
|
33
|
+
/// - `id`: The id of the node schema.
|
|
34
|
+
/// - `type`: A function witch will return the type of the node schema.
|
|
35
|
+
/// - `ctx`: The context of the node schema.
|
|
36
|
+
/// - `node`: The node schema plugin.
|
|
37
|
+
/// - `schema`: The node schema.
|
|
38
|
+
/// - `key`: The key of slice which contains the node schema factory.
|
|
39
|
+
/// - `extendSchema`: A function witch will return a plugin that can extend the node schema.
|
|
40
|
+
export function $nodeSchema<T extends string>(
|
|
41
|
+
id: T,
|
|
42
|
+
schema: GetNodeSchema
|
|
43
|
+
): $NodeSchema<T> {
|
|
44
|
+
const schemaCtx = $ctx(schema, id)
|
|
45
|
+
|
|
46
|
+
const nodeSchema = $node(id, (ctx) => {
|
|
47
|
+
const userSchema = ctx.get(schemaCtx.key)
|
|
48
|
+
return userSchema(ctx)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const result = [schemaCtx, nodeSchema] as $NodeSchema<T>
|
|
52
|
+
result.id = nodeSchema.id
|
|
53
|
+
result.node = nodeSchema
|
|
54
|
+
|
|
55
|
+
result.type = (ctx: Ctx) => nodeSchema.type(ctx)
|
|
56
|
+
result.ctx = schemaCtx
|
|
57
|
+
result.key = schemaCtx.key
|
|
58
|
+
result.extendSchema = (handler) => {
|
|
59
|
+
const nextSchema = handler(schema)
|
|
60
|
+
|
|
61
|
+
return $nodeSchema(id, nextSchema)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
|
|
2
|
+
import type { RemarkPlugin, RemarkPluginRaw } from '@jvs-milkdown/transformer'
|
|
3
|
+
|
|
4
|
+
import { InitReady, remarkPluginsCtx } from '@jvs-milkdown/core'
|
|
5
|
+
|
|
6
|
+
import type { $Ctx } from '../$ctx'
|
|
7
|
+
|
|
8
|
+
import { $ctx } from '../$ctx'
|
|
9
|
+
|
|
10
|
+
/// @internal
|
|
11
|
+
export type $Remark<Id extends string, Options> = [
|
|
12
|
+
optionsCtx: $Ctx<Options, Id>,
|
|
13
|
+
plugin: MilkdownPlugin,
|
|
14
|
+
] & {
|
|
15
|
+
id: Id
|
|
16
|
+
plugin: MilkdownPlugin
|
|
17
|
+
options: $Ctx<Options, Id>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Create a milkdown wrapper for [remark plugin](https://github.com/remarkjs/remark/blob/main/doc/plugins.md).
|
|
21
|
+
/// It takes a factory function which returns a [remark plugin](https://github.com/remarkjs/remark/blob/main/doc/plugins.md).
|
|
22
|
+
///
|
|
23
|
+
/// Additional property:
|
|
24
|
+
/// - `id`: The id of the remark plugin.
|
|
25
|
+
/// - `plugin`: The remark plugin created.
|
|
26
|
+
/// - `options`: The ctx contains the options of the remark plugin.
|
|
27
|
+
export function $remark<Id extends string, Options>(
|
|
28
|
+
id: Id,
|
|
29
|
+
remark: (ctx: Ctx) => RemarkPluginRaw<Options>,
|
|
30
|
+
initialOptions?: Options
|
|
31
|
+
): $Remark<Id, Options> {
|
|
32
|
+
const options = $ctx<Options, Id>(initialOptions ?? ({} as Options), id)
|
|
33
|
+
const plugin: MilkdownPlugin = (ctx) => async () => {
|
|
34
|
+
await ctx.wait(InitReady)
|
|
35
|
+
const re = remark(ctx)
|
|
36
|
+
const remarkPlugin: RemarkPlugin<Options> = {
|
|
37
|
+
plugin: re,
|
|
38
|
+
options: ctx.get(options.key),
|
|
39
|
+
}
|
|
40
|
+
ctx.update(remarkPluginsCtx, (rp) => [...rp, remarkPlugin as RemarkPlugin])
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
ctx.update(remarkPluginsCtx, (rp) => rp.filter((x) => x !== remarkPlugin))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = [options, plugin] as $Remark<Id, Options>
|
|
48
|
+
result.id = id
|
|
49
|
+
result.plugin = plugin
|
|
50
|
+
result.options = options
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Ctx, SliceType } from '@jvs-milkdown/ctx'
|
|
2
|
+
import type { Command } from '@jvs-milkdown/prose/state'
|
|
3
|
+
|
|
4
|
+
import type { $Ctx } from '../$ctx'
|
|
5
|
+
import type { $Shortcut, Keymap } from '../$shortcut'
|
|
6
|
+
|
|
7
|
+
import { $ctx } from '../$ctx'
|
|
8
|
+
import { $shortcut } from '../$shortcut'
|
|
9
|
+
|
|
10
|
+
/// @internal
|
|
11
|
+
export type KeymapConfig<K extends string> = Record<
|
|
12
|
+
K,
|
|
13
|
+
{
|
|
14
|
+
shortcuts: string | string[]
|
|
15
|
+
priority?: number
|
|
16
|
+
}
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
/// @internal
|
|
20
|
+
export interface KeymapItem {
|
|
21
|
+
shortcuts: string | string[]
|
|
22
|
+
priority?: number
|
|
23
|
+
command: (ctx: Ctx) => Command
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// @internal
|
|
27
|
+
export type UserKeymapConfig<Key extends string> = Record<Key, KeymapItem>
|
|
28
|
+
|
|
29
|
+
/// @internal
|
|
30
|
+
export type $UserKeymap<N extends string, Key extends string> = [
|
|
31
|
+
$Ctx<KeymapConfig<Key>, `${N}Keymap`>,
|
|
32
|
+
$Shortcut,
|
|
33
|
+
] & {
|
|
34
|
+
key: SliceType<KeymapConfig<Key>, `${N}Keymap`>
|
|
35
|
+
keymap: Keymap
|
|
36
|
+
ctx: $Ctx<KeymapConfig<Key>, `${N}Keymap`>
|
|
37
|
+
shortcuts: $Shortcut
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Create a keymap which can be customized by user.
|
|
41
|
+
/// It takes two arguments:
|
|
42
|
+
/// - `name`: The name of the keymap.
|
|
43
|
+
/// - `userKeymap`: The keymap config which contains the shortcuts and the command.
|
|
44
|
+
export function $useKeymap<N extends string, Key extends string>(
|
|
45
|
+
name: N,
|
|
46
|
+
userKeymap: UserKeymapConfig<Key>
|
|
47
|
+
) {
|
|
48
|
+
const key = Object.fromEntries(
|
|
49
|
+
Object.entries<KeymapItem>(userKeymap).map(
|
|
50
|
+
([key, { shortcuts, priority }]) => {
|
|
51
|
+
return [key, { shortcuts, priority }]
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
) as KeymapConfig<Key>
|
|
55
|
+
|
|
56
|
+
const keymapDef = $ctx<KeymapConfig<Key>, `${N}Keymap`>(key, `${name}Keymap`)
|
|
57
|
+
|
|
58
|
+
const shortcuts = $shortcut((ctx) => {
|
|
59
|
+
const keys = ctx.get(keymapDef.key)
|
|
60
|
+
|
|
61
|
+
const keymapTuple = Object.entries<KeymapItem>(userKeymap).flatMap(
|
|
62
|
+
([key, { command }]) => {
|
|
63
|
+
const target = keys[key as Key]
|
|
64
|
+
const targetKeys = [target.shortcuts].flat()
|
|
65
|
+
const priority = target.priority
|
|
66
|
+
|
|
67
|
+
return targetKeys.map(
|
|
68
|
+
(targetKey) =>
|
|
69
|
+
[
|
|
70
|
+
targetKey,
|
|
71
|
+
{
|
|
72
|
+
key: targetKey,
|
|
73
|
+
onRun: command,
|
|
74
|
+
priority,
|
|
75
|
+
},
|
|
76
|
+
] as const
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return Object.fromEntries(keymapTuple)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const result = [keymapDef, shortcuts] as $UserKeymap<N, Key>
|
|
85
|
+
result.ctx = keymapDef
|
|
86
|
+
result.shortcuts = shortcuts
|
|
87
|
+
result.key = keymapDef.key
|
|
88
|
+
result.keymap = shortcuts.keymap
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './$command'
|
|
2
|
+
export * from './$input-rule'
|
|
3
|
+
export * from './$paste-rule'
|
|
4
|
+
export * from './$mark'
|
|
5
|
+
export * from './$node'
|
|
6
|
+
export * from './$prose'
|
|
7
|
+
export * from './$shortcut'
|
|
8
|
+
export * from './$view'
|
|
9
|
+
export * from './$ctx'
|
|
10
|
+
|
|
11
|
+
export * from './composed'
|
|
12
|
+
export * from './utils'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Cleanup,
|
|
3
|
+
Ctx,
|
|
4
|
+
MilkdownPlugin,
|
|
5
|
+
SliceType,
|
|
6
|
+
TimerType,
|
|
7
|
+
} from '@jvs-milkdown/ctx'
|
|
8
|
+
|
|
9
|
+
import { createTimer } from '@jvs-milkdown/ctx'
|
|
10
|
+
import { customAlphabet } from 'nanoid'
|
|
11
|
+
|
|
12
|
+
/// @internal
|
|
13
|
+
export const nanoid = customAlphabet('abcedfghicklmn', 10)
|
|
14
|
+
|
|
15
|
+
/// @internal
|
|
16
|
+
export type WithTimer<T> = T & { timer: TimerType }
|
|
17
|
+
|
|
18
|
+
/// @internal
|
|
19
|
+
export function addTimer<
|
|
20
|
+
T extends MilkdownPlugin,
|
|
21
|
+
PluginWithTimer extends T = WithTimer<T>,
|
|
22
|
+
>(
|
|
23
|
+
runner: (
|
|
24
|
+
ctx: Ctx,
|
|
25
|
+
plugin: PluginWithTimer,
|
|
26
|
+
done: () => void
|
|
27
|
+
) => Promise<void | Cleanup>,
|
|
28
|
+
injectTo: SliceType<TimerType[], string>,
|
|
29
|
+
timerName?: string
|
|
30
|
+
): PluginWithTimer {
|
|
31
|
+
const timer = createTimer(timerName || nanoid())
|
|
32
|
+
let doneCalled = false
|
|
33
|
+
|
|
34
|
+
const plugin: MilkdownPlugin = (ctx) => {
|
|
35
|
+
ctx.record(timer)
|
|
36
|
+
ctx.update(injectTo, (x) => x.concat(timer))
|
|
37
|
+
|
|
38
|
+
return async () => {
|
|
39
|
+
const done = () => {
|
|
40
|
+
ctx.done(timer)
|
|
41
|
+
doneCalled = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cleanup = await runner(ctx, <PluginWithTimer>plugin, done)
|
|
45
|
+
|
|
46
|
+
if (!doneCalled) ctx.done(timer)
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
ctx.update(injectTo, (x) => x.filter((y) => y !== timer))
|
|
50
|
+
ctx.clearTimer(timer)
|
|
51
|
+
if (cleanup) {
|
|
52
|
+
const result = cleanup()
|
|
53
|
+
if (result && 'then' in result) {
|
|
54
|
+
result.catch(console.error)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
;(<T & { timer: TimerType }>plugin).timer = timer
|
|
61
|
+
|
|
62
|
+
return <PluginWithTimer>plugin
|
|
63
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CmdKey } from '@jvs-milkdown/core'
|
|
2
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
3
|
+
|
|
4
|
+
import { commandsCtx } from '@jvs-milkdown/core'
|
|
5
|
+
|
|
6
|
+
type InferParams<T> = T extends CmdKey<infer U> ? U : never
|
|
7
|
+
|
|
8
|
+
/// Call a command. You can pass the command key and the payload to the macro.
|
|
9
|
+
export function callCommand<T extends CmdKey<any>>(
|
|
10
|
+
slice: string,
|
|
11
|
+
payload?: InferParams<T>
|
|
12
|
+
): (ctx: Ctx) => boolean
|
|
13
|
+
export function callCommand<T>(
|
|
14
|
+
slice: CmdKey<T>,
|
|
15
|
+
payload?: T
|
|
16
|
+
): (ctx: Ctx) => boolean
|
|
17
|
+
export function callCommand(
|
|
18
|
+
slice: string | CmdKey<any>,
|
|
19
|
+
payload?: any
|
|
20
|
+
): (ctx: Ctx) => boolean
|
|
21
|
+
export function callCommand(
|
|
22
|
+
slice: string | CmdKey<any>,
|
|
23
|
+
payload?: any
|
|
24
|
+
): (ctx: Ctx) => boolean {
|
|
25
|
+
return (ctx: Ctx) => {
|
|
26
|
+
return ctx.get(commandsCtx).call(slice, payload)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx } from '@jvs-milkdown/core'
|
|
4
|
+
|
|
5
|
+
/// Force update the editor.
|
|
6
|
+
export function forceUpdate() {
|
|
7
|
+
return (ctx: Ctx): void => {
|
|
8
|
+
const view = ctx.get(editorViewCtx)
|
|
9
|
+
const { tr } = view.state
|
|
10
|
+
|
|
11
|
+
const nextTr = Object.assign(Object.create(tr), tr).setTime(Date.now())
|
|
12
|
+
return view.dispatch(nextTr)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx, schemaCtx } from '@jvs-milkdown/core'
|
|
4
|
+
import { DOMSerializer } from '@jvs-milkdown/prose/model'
|
|
5
|
+
|
|
6
|
+
/// Get content of the editor as HTML string.
|
|
7
|
+
export function getHTML() {
|
|
8
|
+
return (ctx: Ctx): string => {
|
|
9
|
+
const div = document.createElement('div')
|
|
10
|
+
const schema = ctx.get(schemaCtx)
|
|
11
|
+
const view = ctx.get(editorViewCtx)
|
|
12
|
+
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(
|
|
13
|
+
view.state.doc.content
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
div.appendChild(fragment)
|
|
17
|
+
|
|
18
|
+
return div.innerHTML
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx, schemaCtx, serializerCtx } from '@jvs-milkdown/core'
|
|
4
|
+
|
|
5
|
+
/// Get content of the editor as markdown string.
|
|
6
|
+
/// If range is provided, it will return the markdown string of the range.
|
|
7
|
+
/// If range is not provided, it will return the markdown string of the whole document.
|
|
8
|
+
export function getMarkdown(range?: { from: number; to: number }) {
|
|
9
|
+
return (ctx: Ctx): string => {
|
|
10
|
+
const view = ctx.get(editorViewCtx)
|
|
11
|
+
const schema = ctx.get(schemaCtx)
|
|
12
|
+
const serializer = ctx.get(serializerCtx)
|
|
13
|
+
|
|
14
|
+
if (!range) {
|
|
15
|
+
return serializer(view.state.doc)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const state = view.state
|
|
19
|
+
const slice = state.doc.slice(range.from, range.to, true)
|
|
20
|
+
const doc = schema.topNodeType.createAndFill(null, slice.content)
|
|
21
|
+
if (!doc) {
|
|
22
|
+
console.error('No document found')
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
return serializer(doc)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './call-command'
|
|
2
|
+
export * from './force-update'
|
|
3
|
+
export * from './get-html'
|
|
4
|
+
export * from './get-markdown'
|
|
5
|
+
export * from './insert'
|
|
6
|
+
export * from './outline'
|
|
7
|
+
export * from './replace-all'
|
|
8
|
+
export * from './set-attr'
|
|
9
|
+
export * from './insert-pos'
|
|
10
|
+
export * from './replace-range'
|
|
11
|
+
export * from './markdown-to-slice'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx } from '@jvs-milkdown/core'
|
|
4
|
+
|
|
5
|
+
import { markdownToSlice } from './markdown-to-slice'
|
|
6
|
+
|
|
7
|
+
/// Insert markdown string to the given position.
|
|
8
|
+
/// If inline is true, the markdown will be inserted as inline text.
|
|
9
|
+
/// If inline is false, the markdown will be inserted as block text.
|
|
10
|
+
export function insertPos(
|
|
11
|
+
markdown: string,
|
|
12
|
+
pos: number,
|
|
13
|
+
inline: boolean = false
|
|
14
|
+
) {
|
|
15
|
+
return (ctx: Ctx) => {
|
|
16
|
+
const slice = markdownToSlice(markdown)(ctx)
|
|
17
|
+
const view = ctx.get(editorViewCtx)
|
|
18
|
+
const toPos = view.state.doc.resolve(pos)
|
|
19
|
+
|
|
20
|
+
const min = 0
|
|
21
|
+
const max = view.state.doc.content.size
|
|
22
|
+
const resolved = inline ? toPos.pos : toPos.after(toPos.depth - 1)
|
|
23
|
+
const to = Math.min(Math.max(resolved, min), max)
|
|
24
|
+
|
|
25
|
+
view.dispatch(view.state.tr.replace(resolved, to, slice))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx, parserCtx, schemaCtx } from '@jvs-milkdown/core'
|
|
4
|
+
import { isTextOnlySlice } from '@jvs-milkdown/prose'
|
|
5
|
+
import { DOMParser, DOMSerializer, Slice } from '@jvs-milkdown/prose/model'
|
|
6
|
+
|
|
7
|
+
/// Insert markdown string into the editor.
|
|
8
|
+
export function insert(markdown: string, inline: boolean = false) {
|
|
9
|
+
return (ctx: Ctx) => {
|
|
10
|
+
const view = ctx.get(editorViewCtx)
|
|
11
|
+
const parser = ctx.get(parserCtx)
|
|
12
|
+
const doc = parser(markdown)
|
|
13
|
+
if (!doc) return
|
|
14
|
+
|
|
15
|
+
if (!inline) {
|
|
16
|
+
const contentSlice = view.state.selection.content()
|
|
17
|
+
return view.dispatch(
|
|
18
|
+
view.state.tr
|
|
19
|
+
.replaceSelection(
|
|
20
|
+
new Slice(doc.content, contentSlice.openStart, contentSlice.openEnd)
|
|
21
|
+
)
|
|
22
|
+
.scrollIntoView()
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const schema = ctx.get(schemaCtx)
|
|
27
|
+
const dom = DOMSerializer.fromSchema(schema).serializeFragment(doc.content)
|
|
28
|
+
const domParser = DOMParser.fromSchema(schema)
|
|
29
|
+
const slice = domParser.parseSlice(dom)
|
|
30
|
+
const node = isTextOnlySlice(slice)
|
|
31
|
+
if (node) {
|
|
32
|
+
view.dispatch(view.state.tr.replaceSelectionWith(node, true))
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
view.dispatch(view.state.tr.replaceSelection(slice))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { parserCtx, schemaCtx } from '@jvs-milkdown/core'
|
|
4
|
+
import { DOMSerializer, DOMParser } from '@jvs-milkdown/prose/model'
|
|
5
|
+
|
|
6
|
+
/// Convert markdown string to slice.
|
|
7
|
+
export function markdownToSlice(markdown: string) {
|
|
8
|
+
return (ctx: Ctx) => {
|
|
9
|
+
const parser = ctx.get(parserCtx)
|
|
10
|
+
const doc = parser(markdown)
|
|
11
|
+
const schema = ctx.get(schemaCtx)
|
|
12
|
+
const dom = DOMSerializer.fromSchema(schema).serializeFragment(doc.content)
|
|
13
|
+
const domParser = DOMParser.fromSchema(schema)
|
|
14
|
+
const slice = domParser.parseSlice(dom)
|
|
15
|
+
|
|
16
|
+
return slice
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx'
|
|
2
|
+
|
|
3
|
+
import { editorViewCtx } from '@jvs-milkdown/core'
|
|
4
|
+
|
|
5
|
+
/// Get outline of the editor.
|
|
6
|
+
export function outline() {
|
|
7
|
+
return (ctx: Ctx): Array<{ text: string; level: number; id: string }> => {
|
|
8
|
+
const view = ctx.get(editorViewCtx)
|
|
9
|
+
const data: { text: string; level: number; id: string }[] = []
|
|
10
|
+
const doc = view.state.doc
|
|
11
|
+
doc.descendants((node) => {
|
|
12
|
+
if (node.type.name === 'heading' && node.attrs.level)
|
|
13
|
+
data.push({
|
|
14
|
+
text: node.textContent,
|
|
15
|
+
level: node.attrs.level,
|
|
16
|
+
id: node.attrs.id,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
return data
|
|
20
|
+
}
|
|
21
|
+
}
|