@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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/lib/composable/$command.d.ts +9 -0
  4. package/lib/composable/$command.d.ts.map +1 -0
  5. package/lib/composable/$ctx.d.ts +6 -0
  6. package/lib/composable/$ctx.d.ts.map +1 -0
  7. package/lib/composable/$input-rule.d.ts +8 -0
  8. package/lib/composable/$input-rule.d.ts.map +1 -0
  9. package/lib/composable/$mark.d.ts +11 -0
  10. package/lib/composable/$mark.d.ts.map +1 -0
  11. package/lib/composable/$node.d.ts +11 -0
  12. package/lib/composable/$node.d.ts.map +1 -0
  13. package/lib/composable/$paste-rule.d.ts +8 -0
  14. package/lib/composable/$paste-rule.d.ts.map +1 -0
  15. package/lib/composable/$prose.d.ts +9 -0
  16. package/lib/composable/$prose.d.ts.map +1 -0
  17. package/lib/composable/$shortcut.d.ts +10 -0
  18. package/lib/composable/$shortcut.d.ts.map +1 -0
  19. package/lib/composable/$view.d.ts +11 -0
  20. package/lib/composable/$view.d.ts.map +1 -0
  21. package/lib/composable/composed/$attr.d.ts +7 -0
  22. package/lib/composable/composed/$attr.d.ts.map +1 -0
  23. package/lib/composable/composed/$mark-schema.d.ts +18 -0
  24. package/lib/composable/composed/$mark-schema.d.ts.map +1 -0
  25. package/lib/composable/composed/$node-schema.d.ts +18 -0
  26. package/lib/composable/composed/$node-schema.d.ts.map +1 -0
  27. package/lib/composable/composed/$node-schema.spec.d.ts +2 -0
  28. package/lib/composable/composed/$node-schema.spec.d.ts.map +1 -0
  29. package/lib/composable/composed/$remark.d.ts +13 -0
  30. package/lib/composable/composed/$remark.d.ts.map +1 -0
  31. package/lib/composable/composed/$use-keymap.d.ts +25 -0
  32. package/lib/composable/composed/$use-keymap.d.ts.map +1 -0
  33. package/lib/composable/composed/index.d.ts +6 -0
  34. package/lib/composable/composed/index.d.ts.map +1 -0
  35. package/lib/composable/index.d.ts +12 -0
  36. package/lib/composable/index.d.ts.map +1 -0
  37. package/lib/composable/utils.d.ts +7 -0
  38. package/lib/composable/utils.d.ts.map +1 -0
  39. package/lib/index.d.ts +4 -0
  40. package/lib/index.d.ts.map +1 -0
  41. package/lib/index.js +549 -0
  42. package/lib/index.js.map +1 -0
  43. package/lib/macro/call-command.d.ts +8 -0
  44. package/lib/macro/call-command.d.ts.map +1 -0
  45. package/lib/macro/force-update.d.ts +3 -0
  46. package/lib/macro/force-update.d.ts.map +1 -0
  47. package/lib/macro/get-html.d.ts +3 -0
  48. package/lib/macro/get-html.d.ts.map +1 -0
  49. package/lib/macro/get-markdown.d.ts +6 -0
  50. package/lib/macro/get-markdown.d.ts.map +1 -0
  51. package/lib/macro/index.d.ts +12 -0
  52. package/lib/macro/index.d.ts.map +1 -0
  53. package/lib/macro/insert-pos.d.ts +3 -0
  54. package/lib/macro/insert-pos.d.ts.map +1 -0
  55. package/lib/macro/insert.d.ts +3 -0
  56. package/lib/macro/insert.d.ts.map +1 -0
  57. package/lib/macro/markdown-to-slice.d.ts +3 -0
  58. package/lib/macro/markdown-to-slice.d.ts.map +1 -0
  59. package/lib/macro/outline.d.ts +7 -0
  60. package/lib/macro/outline.d.ts.map +1 -0
  61. package/lib/macro/replace-all.d.ts +3 -0
  62. package/lib/macro/replace-all.d.ts.map +1 -0
  63. package/lib/macro/replace-range.d.ts +6 -0
  64. package/lib/macro/replace-range.d.ts.map +1 -0
  65. package/lib/macro/set-attr.d.ts +4 -0
  66. package/lib/macro/set-attr.d.ts.map +1 -0
  67. package/lib/pipe.d.ts +7 -0
  68. package/lib/pipe.d.ts.map +1 -0
  69. package/lib/tsconfig.tsbuildinfo +1 -0
  70. package/package.json +35 -0
  71. package/src/composable/$command.ts +102 -0
  72. package/src/composable/$ctx.ts +31 -0
  73. package/src/composable/$input-rule.ts +55 -0
  74. package/src/composable/$mark.ts +87 -0
  75. package/src/composable/$node.ts +90 -0
  76. package/src/composable/$paste-rule.ts +59 -0
  77. package/src/composable/$prose.ts +70 -0
  78. package/src/composable/$shortcut.ts +66 -0
  79. package/src/composable/$view.ts +110 -0
  80. package/src/composable/composed/$attr.ts +29 -0
  81. package/src/composable/composed/$mark-schema.ts +65 -0
  82. package/src/composable/composed/$node-schema.spec.ts +139 -0
  83. package/src/composable/composed/$node-schema.ts +65 -0
  84. package/src/composable/composed/$remark.ts +53 -0
  85. package/src/composable/composed/$use-keymap.ts +91 -0
  86. package/src/composable/composed/index.ts +5 -0
  87. package/src/composable/index.ts +12 -0
  88. package/src/composable/utils.ts +63 -0
  89. package/src/index.ts +3 -0
  90. package/src/macro/call-command.ts +28 -0
  91. package/src/macro/force-update.ts +14 -0
  92. package/src/macro/get-html.ts +20 -0
  93. package/src/macro/get-markdown.ts +27 -0
  94. package/src/macro/index.ts +11 -0
  95. package/src/macro/insert-pos.ts +27 -0
  96. package/src/macro/insert.ts +38 -0
  97. package/src/macro/markdown-to-slice.ts +18 -0
  98. package/src/macro/outline.ts +21 -0
  99. package/src/macro/replace-all.ts +46 -0
  100. package/src/macro/replace-range.ts +18 -0
  101. package/src/macro/set-attr.ts +16 -0
  102. package/src/pipe.ts +72 -0
@@ -0,0 +1,31 @@
1
+ import type { MilkdownPlugin, SliceType } from '@jvs-milkdown/ctx'
2
+
3
+ import { createSlice } from '@jvs-milkdown/ctx'
4
+
5
+ /// @internal
6
+ export type $Ctx<T, N extends string> = MilkdownPlugin & {
7
+ key: SliceType<T, N>
8
+ }
9
+
10
+ /// Create a slice plugin. The plugin will be registered in the `ctx` and can be accessed by other parts of the editor.
11
+ /// ```ts
12
+ /// const counterCtx = $ctx(0, 'counter');
13
+ /// ```
14
+ ///
15
+ /// Additional property:
16
+ /// - `key`: The key of the slice.
17
+ export function $ctx<T, N extends string>(value: T, name: N): $Ctx<T, N> {
18
+ const slice = createSlice(value, name)
19
+ const plugin: $Ctx<T, N> = (ctx) => {
20
+ ctx.inject(slice)
21
+ return () => {
22
+ return () => {
23
+ ctx.remove(slice)
24
+ }
25
+ }
26
+ }
27
+
28
+ plugin.key = slice
29
+
30
+ return plugin
31
+ }
@@ -0,0 +1,55 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type { InputRule } from '@jvs-milkdown/prose/inputrules'
3
+
4
+ import { SchemaReady, editorStateTimerCtx, inputRulesCtx } from '@jvs-milkdown/core'
5
+
6
+ import { addTimer } from './utils'
7
+
8
+ /// @internal
9
+ export type $InputRule = MilkdownPlugin & {
10
+ inputRule: InputRule
11
+ }
12
+
13
+ /// Create an input rule plugin.
14
+ /// It takes a factory function which returns a [prosemirror input rule](https://prosemirror.net/docs/ref/#inputrules.InputRule).
15
+ ///
16
+ /// Additional property:
17
+ /// - `inputRule`: The prosemirror input rule created.
18
+ export function $inputRule(inputRule: (ctx: Ctx) => InputRule): $InputRule {
19
+ const plugin: MilkdownPlugin = (ctx) => async () => {
20
+ await ctx.wait(SchemaReady)
21
+ const ir = inputRule(ctx)
22
+ ctx.update(inputRulesCtx, (irs) => [...irs, ir])
23
+ ;(<$InputRule>plugin).inputRule = ir
24
+
25
+ return () => {
26
+ ctx.update(inputRulesCtx, (irs) => irs.filter((x) => x !== ir))
27
+ }
28
+ }
29
+
30
+ return <$InputRule>plugin
31
+ }
32
+
33
+ /// The async version for `$inputRule`. You can use `await` in the factory when creating the input rule.
34
+ ///
35
+ /// Additional property:
36
+ /// - `inputRule`: The prosemirror input rule created.
37
+ /// - `timer`: The timer which will be resolved when the input rule is ready.
38
+ export function $inputRuleAsync(
39
+ inputRule: (ctx: Ctx) => Promise<InputRule>,
40
+ timerName?: string
41
+ ) {
42
+ return addTimer<$InputRule>(
43
+ async (ctx, plugin) => {
44
+ await ctx.wait(SchemaReady)
45
+ const ir = await inputRule(ctx)
46
+ ctx.update(inputRulesCtx, (irs) => [...irs, ir])
47
+ plugin.inputRule = ir
48
+ return () => {
49
+ ctx.update(inputRulesCtx, (irs) => irs.filter((x) => x !== ir))
50
+ }
51
+ },
52
+ editorStateTimerCtx,
53
+ timerName
54
+ )
55
+ }
@@ -0,0 +1,87 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type { MarkType } from '@jvs-milkdown/prose/model'
3
+ import type { MarkSchema } from '@jvs-milkdown/transformer'
4
+
5
+ import { marksCtx, schemaCtx, schemaTimerCtx } from '@jvs-milkdown/core'
6
+ import { missingMarkInSchema } from '@jvs-milkdown/exception'
7
+
8
+ import { addTimer } from './utils'
9
+
10
+ /// @internal
11
+ export type $Mark = MilkdownPlugin & {
12
+ id: string
13
+ schema: MarkSchema
14
+ type: (ctx: Ctx) => MarkType
15
+ }
16
+
17
+ /// Create a mark plugin.
18
+ /// It takes a mark id and a factory function.
19
+ /// The factory should return a function that returns a [mark schema](/transformer#interface-markschema).
20
+ ///
21
+ /// Additional property:
22
+ /// - `id`: The id of the mark.
23
+ /// - `schema`: The mark schema created.
24
+ /// - `type`: A function that will return the [prosemirror mark type](https://prosemirror.net/docs/ref/#model.MarkType).
25
+ export function $mark(id: string, schema: (ctx: Ctx) => MarkSchema): $Mark {
26
+ const plugin: MilkdownPlugin = (ctx) => async () => {
27
+ const markSchema = schema(ctx)
28
+ ctx.update(marksCtx, (ns) => [
29
+ ...ns.filter((n) => n[0] !== id),
30
+ [id, markSchema] as [string, MarkSchema],
31
+ ])
32
+ ;(<$Mark>plugin).id = id
33
+ ;(<$Mark>plugin).schema = markSchema
34
+
35
+ return () => {
36
+ ctx.update(marksCtx, (ns) => ns.filter(([x]) => x !== id))
37
+ }
38
+ }
39
+ ;(<$Mark>plugin).type = (ctx) => {
40
+ const markType = ctx.get(schemaCtx).marks[id]
41
+ if (!markType) throw missingMarkInSchema(id)
42
+ return markType
43
+ }
44
+
45
+ return <$Mark>plugin
46
+ }
47
+
48
+ /// The async version for `$mark`. You can use `await` in the factory when creating the mark schema.
49
+ ///
50
+ /// Additional property:
51
+ /// - `id`: The id of the mark.
52
+ /// - `schema`: The mark schema created.
53
+ /// - `type`: A function that will return the [prosemirror mark type](https://prosemirror.net/docs/ref/#model.MarkType).
54
+ /// - `timer`: The timer which will be resolved when the mark schema is ready.
55
+ export function $markAsync(
56
+ id: string,
57
+ schema: (ctx: Ctx) => Promise<MarkSchema>,
58
+ timerName?: string
59
+ ) {
60
+ const plugin = addTimer<$Mark>(
61
+ async (ctx, plugin, done) => {
62
+ const markSchema = await schema(ctx)
63
+ ctx.update(marksCtx, (ns) => [
64
+ ...ns.filter((n) => n[0] !== id),
65
+ [id, markSchema] as [string, MarkSchema],
66
+ ])
67
+
68
+ plugin.id = id
69
+ plugin.schema = markSchema
70
+ done()
71
+
72
+ return () => {
73
+ ctx.update(marksCtx, (ns) => ns.filter(([x]) => x !== id))
74
+ }
75
+ },
76
+ schemaTimerCtx,
77
+ timerName
78
+ )
79
+
80
+ plugin.type = (ctx) => {
81
+ const markType = ctx.get(schemaCtx).marks[id]
82
+ if (!markType) throw missingMarkInSchema(id)
83
+ return markType
84
+ }
85
+
86
+ return plugin
87
+ }
@@ -0,0 +1,90 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type { NodeType } from '@jvs-milkdown/prose/model'
3
+ import type { NodeSchema } from '@jvs-milkdown/transformer'
4
+
5
+ import { nodesCtx, schemaCtx, schemaTimerCtx } from '@jvs-milkdown/core'
6
+ import { missingNodeInSchema } from '@jvs-milkdown/exception'
7
+
8
+ import { addTimer } from './utils'
9
+
10
+ /// @internal
11
+ export type $Node = MilkdownPlugin & {
12
+ id: string
13
+ schema: NodeSchema
14
+ type: (ctx: Ctx) => NodeType
15
+ }
16
+
17
+ /// Create a node plugin.
18
+ /// It takes a node id and a factory function.
19
+ /// The factory should return a function that returns a [node schema](/transformer#interface-nodeschema).
20
+ ///
21
+ /// Additional property:
22
+ /// - `id`: The id of the node.
23
+ /// - `schema`: The node schema created.
24
+ /// - `type`: A function that will return the [prosemirror node type](https://prosemirror.net/docs/ref/#model.NodeType).
25
+ export function $node(id: string, schema: (ctx: Ctx) => NodeSchema): $Node {
26
+ const plugin: MilkdownPlugin = (ctx) => async () => {
27
+ const nodeSchema = schema(ctx)
28
+ ctx.update(nodesCtx, (ns) => [
29
+ ...ns.filter((n) => n[0] !== id),
30
+ [id, nodeSchema] as [string, NodeSchema],
31
+ ])
32
+ ;(<$Node>plugin).id = id
33
+ ;(<$Node>plugin).schema = nodeSchema
34
+
35
+ return () => {
36
+ ctx.update(nodesCtx, (ns) => ns.filter(([x]) => x !== id))
37
+ }
38
+ }
39
+
40
+ ;(<$Node>plugin).type = (ctx) => {
41
+ const nodeType = ctx.get(schemaCtx).nodes[id]
42
+ if (!nodeType) throw missingNodeInSchema(id)
43
+
44
+ return nodeType
45
+ }
46
+
47
+ return <$Node>plugin
48
+ }
49
+
50
+ /// The async version for `$node`. You can use `await` in the factory when creating the node schema.
51
+ ///
52
+ /// Additional property:
53
+ /// - `id`: The id of the node.
54
+ /// - `schema`: The node schema created.
55
+ /// - `type`: A function that will return the [prosemirror node type](https://prosemirror.net/docs/ref/#model.NodeType).
56
+ /// - `timer`: The timer which will be resolved when the node schema is ready.
57
+ export function $nodeAsync(
58
+ id: string,
59
+ schema: (ctx: Ctx) => Promise<NodeSchema>,
60
+ timerName?: string
61
+ ) {
62
+ const plugin = addTimer<$Node>(
63
+ async (ctx, plugin, done) => {
64
+ const nodeSchema = await schema(ctx)
65
+ ctx.update(nodesCtx, (ns) => [
66
+ ...ns.filter((n) => n[0] !== id),
67
+ [id, nodeSchema] as [string, NodeSchema],
68
+ ])
69
+
70
+ plugin.id = id
71
+ plugin.schema = nodeSchema
72
+ done()
73
+
74
+ return () => {
75
+ ctx.update(nodesCtx, (ns) => ns.filter(([x]) => x !== id))
76
+ }
77
+ },
78
+ schemaTimerCtx,
79
+ timerName
80
+ )
81
+
82
+ plugin.type = (ctx) => {
83
+ const nodeType = ctx.get(schemaCtx).nodes[id]
84
+ if (!nodeType) throw missingNodeInSchema(id)
85
+
86
+ return nodeType
87
+ }
88
+
89
+ return plugin
90
+ }
@@ -0,0 +1,59 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+
3
+ import {
4
+ pasteRulesCtx,
5
+ pasteRulesTimerCtx,
6
+ SchemaReady,
7
+ type PasteRule,
8
+ } from '@jvs-milkdown/core'
9
+
10
+ import { addTimer } from './utils'
11
+
12
+ /// @internal
13
+ export type $PasteRule = MilkdownPlugin & {
14
+ pasteRule: PasteRule
15
+ }
16
+
17
+ /// Create a paste rule plugin.
18
+ /// It takes a factory function which returns a paste rule.
19
+ ///
20
+ /// Additional property:
21
+ /// - `pasteRule`: The paste rule created.
22
+ export function $pasteRule(pasteRule: (ctx: Ctx) => PasteRule): $PasteRule {
23
+ const plugin: MilkdownPlugin = (ctx) => async () => {
24
+ await ctx.wait(SchemaReady)
25
+ const pr = pasteRule(ctx)
26
+ ctx.update(pasteRulesCtx, (prs) => [...prs, pr])
27
+ ;(<$PasteRule>plugin).pasteRule = pr
28
+
29
+ return () => {
30
+ ctx.update(pasteRulesCtx, (prs) => prs.filter((x) => x !== pr))
31
+ }
32
+ }
33
+
34
+ return <$PasteRule>plugin
35
+ }
36
+
37
+ /// The async version for `$pasteRule`. You can use `await` in the factory when creating the paste rule.
38
+ ///
39
+ /// Additional property:
40
+ /// - `pasteRule`: The paste rule created.
41
+ /// - `timer`: The timer which will be resolved when the paste rule is ready.
42
+ export function $pasteRuleAsync(
43
+ pasteRule: (ctx: Ctx) => Promise<PasteRule>,
44
+ timerName?: string
45
+ ) {
46
+ return addTimer<$PasteRule>(
47
+ async (ctx, plugin) => {
48
+ await ctx.wait(SchemaReady)
49
+ const pr = await pasteRule(ctx)
50
+ ctx.update(pasteRulesCtx, (prs) => [...prs, pr])
51
+ plugin.pasteRule = pr
52
+ return () => {
53
+ ctx.update(pasteRulesCtx, (prs) => prs.filter((x) => x !== pr))
54
+ }
55
+ },
56
+ pasteRulesTimerCtx,
57
+ timerName
58
+ )
59
+ }
@@ -0,0 +1,70 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type { Plugin, PluginKey } from '@jvs-milkdown/prose/state'
3
+
4
+ import {
5
+ SchemaReady,
6
+ editorStateTimerCtx,
7
+ prosePluginsCtx,
8
+ } from '@jvs-milkdown/core'
9
+
10
+ import { addTimer } from './utils'
11
+
12
+ /// @internal
13
+ export type $Prose = MilkdownPlugin & {
14
+ plugin: () => Plugin
15
+ key: () => PluginKey | undefined
16
+ }
17
+
18
+ /// Create a milkdown wrapper for [prosemirror plugin](https://prosemirror.net/docs/ref/#state.Plugin).
19
+ /// It takes a factory function which returns a [prosemirror plugin](https://prosemirror.net/docs/ref/#state.Plugin).
20
+ ///
21
+ /// Additional property:
22
+ /// - `plugin`: The prosemirror plugin created.
23
+ /// - `key`: The [prosemirror plugin key](https://prosemirror.net/docs/ref/#state.PluginKey) of the plugin.
24
+ export function $prose(prose: (ctx: Ctx) => Plugin): $Prose {
25
+ let prosePlugin: Plugin | undefined
26
+ const plugin: MilkdownPlugin = (ctx) => async () => {
27
+ await ctx.wait(SchemaReady)
28
+ prosePlugin = prose(ctx)
29
+ ctx.update(prosePluginsCtx, (ps) => [...ps, prosePlugin!])
30
+
31
+ return () => {
32
+ ctx.update(prosePluginsCtx, (ps) => ps.filter((x) => x !== prosePlugin))
33
+ }
34
+ }
35
+ ;(<$Prose>plugin).plugin = () => prosePlugin!
36
+ ;(<$Prose>plugin).key = () => prosePlugin!.spec.key
37
+
38
+ return <$Prose>plugin
39
+ }
40
+
41
+ /// The async version for `$prose`. You can use `await` in the factory when creating the plugin.
42
+ ///
43
+ /// Additional property:
44
+ /// - `plugin`: The prosemirror plugin created.
45
+ /// - `key`: The [prosemirror plugin key](https://prosemirror.net/docs/ref/#state.PluginKey) of the plugin.
46
+ /// - `timer`: The timer which will be resolved when the plugin is ready.
47
+ export function $proseAsync(
48
+ prose: (ctx: Ctx) => Promise<Plugin>,
49
+ timerName?: string
50
+ ) {
51
+ let prosePlugin: Plugin | undefined
52
+ const plugin = addTimer<$Prose>(
53
+ async (ctx) => {
54
+ await ctx.wait(SchemaReady)
55
+ prosePlugin = await prose(ctx)
56
+ ctx.update(prosePluginsCtx, (ps) => [...ps, prosePlugin!])
57
+
58
+ return () => {
59
+ ctx.update(prosePluginsCtx, (ps) => ps.filter((x) => x !== prosePlugin))
60
+ }
61
+ },
62
+ editorStateTimerCtx,
63
+ timerName
64
+ )
65
+
66
+ plugin.plugin = () => prosePlugin!
67
+ plugin.key = () => prosePlugin!.spec.key
68
+
69
+ return plugin
70
+ }
@@ -0,0 +1,66 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type { Command } from '@jvs-milkdown/prose/state'
3
+
4
+ import {
5
+ KeymapReady,
6
+ editorStateTimerCtx,
7
+ keymapCtx,
8
+ type KeymapItem,
9
+ } from '@jvs-milkdown/core'
10
+
11
+ import { addTimer } from './utils'
12
+
13
+ /// @internal
14
+ export type Keymap = Record<string, Command | KeymapItem>
15
+
16
+ /// @internal
17
+ export type $Shortcut = MilkdownPlugin & {
18
+ keymap: Keymap
19
+ }
20
+
21
+ /// Create a shortcut for the editor.
22
+ /// It takes a factory function which returns a [prosemirror keymap](https://prosemirror.net/docs/ref/#keymap).
23
+ ///
24
+ /// Additional property:
25
+ /// - `keymap`: The prosemirror keymap created.
26
+ export function $shortcut(shortcut: (ctx: Ctx) => Keymap): $Shortcut {
27
+ const plugin: MilkdownPlugin = (ctx) => async () => {
28
+ await ctx.wait(KeymapReady)
29
+ const km = ctx.get(keymapCtx)
30
+ const keymap = shortcut(ctx)
31
+ const dispose = km.addObjectKeymap(keymap)
32
+ ;(<$Shortcut>plugin).keymap = keymap
33
+
34
+ return () => {
35
+ dispose()
36
+ }
37
+ }
38
+
39
+ return <$Shortcut>plugin
40
+ }
41
+
42
+ /// The async version for `$shortcut`. You can use `await` in the factory when creating the keymap.
43
+ ///
44
+ /// Additional property:
45
+ /// - `keymap`: The prosemirror keymap created.
46
+ /// - `timer`: The timer which will be resolved when the plugin is ready.
47
+ export function $shortcutAsync(
48
+ shortcut: (ctx: Ctx) => Promise<Keymap>,
49
+ timerName?: string
50
+ ) {
51
+ return addTimer<$Shortcut>(
52
+ async (ctx, plugin) => {
53
+ await ctx.wait(KeymapReady)
54
+ const km = ctx.get(keymapCtx)
55
+ const keymap = await shortcut(ctx)
56
+ const dispose = km.addObjectKeymap(keymap)
57
+ plugin.keymap = keymap
58
+
59
+ return () => {
60
+ dispose()
61
+ }
62
+ },
63
+ editorStateTimerCtx,
64
+ timerName
65
+ )
66
+ }
@@ -0,0 +1,110 @@
1
+ import type { Ctx, MilkdownPlugin } from '@jvs-milkdown/ctx'
2
+ import type {
3
+ MarkViewConstructor,
4
+ NodeViewConstructor,
5
+ } from '@jvs-milkdown/prose/view'
6
+
7
+ import {
8
+ SchemaReady,
9
+ editorViewTimerCtx,
10
+ markViewCtx,
11
+ nodeViewCtx,
12
+ } from '@jvs-milkdown/core'
13
+ import { NodeType } from '@jvs-milkdown/prose/model'
14
+
15
+ import type { $Mark, $Node } from '.'
16
+
17
+ import { addTimer } from './utils'
18
+
19
+ /// @internal
20
+ export type $View<
21
+ T extends $Node | $Mark,
22
+ V extends NodeViewConstructor | MarkViewConstructor,
23
+ > = MilkdownPlugin & {
24
+ view: V
25
+ type: T
26
+ }
27
+
28
+ /// @internal
29
+ export type GetConstructor<T extends $Node | $Mark> = T extends $Node
30
+ ? NodeViewConstructor
31
+ : T extends $Mark
32
+ ? MarkViewConstructor
33
+ : NodeViewConstructor | MarkViewConstructor
34
+
35
+ /// Create a [prosemirror node/mark view](https://prosemirror.net/docs/ref/#view.NodeView) plugin.
36
+ /// It takes two arguments
37
+ /// - `type`: The node/mark plugin that needs to add a view.
38
+ /// - `view`: The factory that creates the view. It should return a function that returns a [node/mark view constructor](https://prosemirror.net/docs/ref/#view.NodeView).
39
+ ///
40
+ /// Additional property:
41
+ /// - `view`: The view created.
42
+ /// - `type`: The node/mark plugin that needs to add a view.
43
+ export function $view<
44
+ T extends $Node | $Mark,
45
+ V extends NodeViewConstructor | MarkViewConstructor = GetConstructor<T>,
46
+ >(type: T, view: (ctx: Ctx) => V): $View<T, V> {
47
+ const plugin: MilkdownPlugin = (ctx) => async () => {
48
+ await ctx.wait(SchemaReady)
49
+ const v = view(ctx)
50
+ if (type.type(ctx) instanceof NodeType)
51
+ ctx.update(nodeViewCtx, (ps) => [
52
+ ...ps,
53
+ [type.id, v] as [string, NodeViewConstructor],
54
+ ])
55
+ else
56
+ ctx.update(markViewCtx, (ps) => [
57
+ ...ps,
58
+ [type.id, v] as [string, MarkViewConstructor],
59
+ ])
60
+ ;(<$View<T, V>>plugin).view = v
61
+ ;(<$View<T, V>>plugin).type = type
62
+
63
+ return () => {
64
+ if (type.type(ctx) instanceof NodeType)
65
+ ctx.update(nodeViewCtx, (ps) => ps.filter((x) => x[0] !== type.id))
66
+ else ctx.update(markViewCtx, (ps) => ps.filter((x) => x[0] !== type.id))
67
+ }
68
+ }
69
+
70
+ return <$View<T, V>>plugin
71
+ }
72
+
73
+ /// The async version for `$view`. You can use `await` in the factory when creating the view.
74
+ ///
75
+ /// Additional property:
76
+ /// - `view`: The view created.
77
+ /// - `type`: The node/mark plugin that needs to add a view.
78
+ /// - `timer`: The timer which will be resolved when the view is ready.
79
+ export function $viewAsync<
80
+ T extends $Node | $Mark,
81
+ V extends NodeViewConstructor | MarkViewConstructor = GetConstructor<T>,
82
+ >(type: T, view: (ctx: Ctx) => Promise<V>, timerName?: string) {
83
+ return addTimer<$View<T, V>>(
84
+ async (ctx, plugin) => {
85
+ await ctx.wait(SchemaReady)
86
+ const v = await view(ctx)
87
+ if (type.type(ctx) instanceof NodeType)
88
+ ctx.update(nodeViewCtx, (ps) => [
89
+ ...ps,
90
+ [type.id, v] as [string, NodeViewConstructor],
91
+ ])
92
+ else
93
+ ctx.update(markViewCtx, (ps) => [
94
+ ...ps,
95
+ [type.id, v] as [string, MarkViewConstructor],
96
+ ])
97
+
98
+ plugin.view = v
99
+ plugin.type = type
100
+
101
+ return () => {
102
+ if (type.type(ctx) instanceof NodeType)
103
+ ctx.update(nodeViewCtx, (ps) => ps.filter((x) => x[0] !== type.id))
104
+ else ctx.update(markViewCtx, (ps) => ps.filter((x) => x[0] !== type.id))
105
+ }
106
+ },
107
+ editorViewTimerCtx,
108
+ timerName
109
+ )
110
+ }
@@ -0,0 +1,29 @@
1
+ import type { Mark, Node } from '@jvs-milkdown/prose/model'
2
+
3
+ import type { $Ctx } from '../$ctx'
4
+
5
+ import { $ctx } from '../$ctx'
6
+
7
+ /// @internal
8
+ export type $NodeAttr = $Ctx<
9
+ (node: Node) => Record<string, any>,
10
+ `${string}Attr`
11
+ >
12
+
13
+ /// Create a slice which contains the attributes for node schema.
14
+ export const $nodeAttr = (
15
+ name: string,
16
+ value: (node: Node) => Record<string, any> = () => ({})
17
+ ): $NodeAttr => $ctx(value, `${name}Attr`)
18
+
19
+ /// @internal
20
+ export type $MarkAttr = $Ctx<
21
+ (node: Mark) => Record<string, any>,
22
+ `${string}Attr`
23
+ >
24
+
25
+ /// Create a slice which contains the attributes for mark schema.
26
+ export const $markAttr = (
27
+ name: string,
28
+ value: (mark: Mark) => Record<string, any> = () => ({})
29
+ ): $MarkAttr => $ctx(value, `${name}Attr`)
@@ -0,0 +1,65 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+ import type { MarkSchema } from '@jvs-milkdown/transformer'
3
+
4
+ import type { $Ctx } from '../$ctx'
5
+ import type { $Mark } from '../$mark'
6
+
7
+ import { $ctx } from '../$ctx'
8
+ import { $mark } from '../$mark'
9
+
10
+ /// @internal
11
+ export type GetMarkSchema = (ctx: Ctx) => MarkSchema
12
+
13
+ /// @internal
14
+ export type $MarkSchema<T extends string> = [
15
+ schemaCtx: $Ctx<GetMarkSchema, T>,
16
+ schema: $Mark,
17
+ ] & {
18
+ id: $Mark['id']
19
+ type: $Mark['type']
20
+ mark: $Mark
21
+ ctx: $Ctx<GetMarkSchema, T>
22
+ key: $Ctx<GetMarkSchema, T>['key']
23
+ extendSchema: (
24
+ handler: (prev: GetMarkSchema) => GetMarkSchema
25
+ ) => $MarkSchema<T>
26
+ }
27
+
28
+ /// Create a plugin for mark schema.
29
+ /// The first parameter is the id of the mark schema.
30
+ /// The second parameter is the function that returns the mark schema.
31
+ ///
32
+ /// The function will return a plugin with additional properties:
33
+ /// - `id`: The id of the mark schema.
34
+ /// - `type`: A function witch will return the type of the mark schema.
35
+ /// - `ctx`: The context of the mark schema.
36
+ /// - `mark`: The mark schema plugin.
37
+ /// - `schema`: The mark schema.
38
+ /// - `key`: The key of slice which contains the mark schema factory.
39
+ /// - `extendSchema`: A function witch will return a plugin that can extend the mark schema.
40
+ export function $markSchema<T extends string>(
41
+ id: T,
42
+ schema: GetMarkSchema
43
+ ): $MarkSchema<T> {
44
+ const schemaCtx = $ctx(schema, id)
45
+
46
+ const markSchema = $mark(id, (ctx) => {
47
+ const userSchema = ctx.get(schemaCtx.key)
48
+ return userSchema(ctx)
49
+ })
50
+
51
+ const result = [schemaCtx, markSchema] as $MarkSchema<T>
52
+ result.id = markSchema.id
53
+ result.mark = markSchema
54
+
55
+ result.type = (ctx: Ctx) => markSchema.type(ctx)
56
+ result.ctx = schemaCtx
57
+ result.key = schemaCtx.key
58
+ result.extendSchema = (handler) => {
59
+ const nextSchema = handler(schema)
60
+
61
+ return $markSchema(id, nextSchema)
62
+ }
63
+
64
+ return result
65
+ }