@jvs-milkdown/plugin-automd 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.
@@ -0,0 +1,62 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+ import type { Attrs } from '@jvs-milkdown/prose/model'
3
+ import type { EditorState, PluginKey, Transaction } from '@jvs-milkdown/prose/state'
4
+
5
+ import { TextSelection } from '@jvs-milkdown/prose/state'
6
+
7
+ import { inlineSyncConfig } from './config'
8
+ import { getContextByState } from './context'
9
+ import { linkRegexp } from './regexp'
10
+ import { calcOffset } from './utils'
11
+
12
+ export function runReplacer(
13
+ ctx: Ctx,
14
+ key: PluginKey,
15
+ state: EditorState,
16
+ dispatch: (tr: Transaction) => void,
17
+ attrs: Attrs
18
+ ) {
19
+ const { placeholderConfig } = ctx.get(inlineSyncConfig.key)
20
+ const holePlaceholder = placeholderConfig.hole
21
+ // insert a placeholder to restore the selection
22
+ let tr = state.tr
23
+ .setMeta(key, true)
24
+ .insertText(holePlaceholder, state.selection.from)
25
+
26
+ const nextState = state.apply(tr)
27
+ const context = getContextByState(ctx, nextState)
28
+
29
+ if (!context) return
30
+
31
+ const lastUserInput = context.text.slice(
32
+ 0,
33
+ context.text.indexOf(context.placeholder)
34
+ )
35
+
36
+ const { $from } = nextState.selection
37
+ const from = $from.before()
38
+ const to = $from.after()
39
+
40
+ const offset = calcOffset(context.nextNode, from, context.placeholder)
41
+
42
+ tr = tr
43
+ .replaceWith(from, to, context.nextNode)
44
+ .setNodeMarkup(from, undefined, attrs)
45
+ // delete the placeholder
46
+ .delete(offset + 1, offset + 2)
47
+
48
+ // restore the selection
49
+ tr = tr.setSelection(TextSelection.near(tr.doc.resolve(offset + 1)))
50
+
51
+ const needsRestoreMark =
52
+ linkRegexp.test(lastUserInput) ||
53
+ ['*', '_', '~'].includes(lastUserInput.at(-1) || '')
54
+ if (needsRestoreMark && tr.selection instanceof TextSelection) {
55
+ const marks = tr.selection.$cursor?.marks() ?? []
56
+ marks.forEach((mark) => {
57
+ tr = tr.removeStoredMark(mark.type)
58
+ })
59
+ }
60
+
61
+ dispatch(tr)
62
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,91 @@
1
+ import type { Node } from '@jvs-milkdown/prose/model'
2
+
3
+ import type { SyncNodePlaceholder } from './config'
4
+
5
+ import {
6
+ asterisk,
7
+ asteriskHolder,
8
+ keepLinkRegexp,
9
+ punctuationRegexp,
10
+ underline,
11
+ underlineHolder,
12
+ } from './regexp'
13
+
14
+ export function keepLink(str: string) {
15
+ let text = str
16
+ let match = text.match(keepLinkRegexp)
17
+ while (match && match.groups) {
18
+ const { span } = match.groups
19
+ text = text.replace(keepLinkRegexp, span as string)
20
+
21
+ match = text.match(keepLinkRegexp)
22
+ }
23
+ return text
24
+ }
25
+
26
+ export function mergeSlash(str: string) {
27
+ return str
28
+ .replaceAll(/\\\\\*/g, asterisk)
29
+ .replaceAll(/\\\\_/g, underline)
30
+ .replaceAll(asterisk, asteriskHolder)
31
+ .replaceAll(underline, underlineHolder)
32
+ }
33
+
34
+ export function swap(text: string, first: number, last: number) {
35
+ const arr = text.split('')
36
+ const temp = arr[first]
37
+ if (arr[first] && arr[last]) {
38
+ arr[first] = arr[last] as string
39
+ arr[last] = temp as string
40
+ }
41
+ return arr.join('').toString()
42
+ }
43
+
44
+ export function replacePunctuation(holePlaceholder: string) {
45
+ return (text: string) => text.replace(punctuationRegexp(holePlaceholder), '')
46
+ }
47
+
48
+ export function calculatePlaceholder(placeholder: SyncNodePlaceholder) {
49
+ return (text: string) => {
50
+ const index = text.indexOf(placeholder.hole)
51
+ const left = text.charAt(index - 1)
52
+ const right = text.charAt(index + 1)
53
+ const notAWord = /[^\w]|_/
54
+
55
+ // cursor on the right
56
+ if (!right) return placeholder.punctuation
57
+
58
+ // cursor on the left
59
+ if (!left) return placeholder.char
60
+
61
+ if (notAWord.test(left) && notAWord.test(right))
62
+ return placeholder.punctuation
63
+
64
+ return placeholder.char
65
+ }
66
+ }
67
+
68
+ export function calcOffset(node: Node, from: number, placeholder: string) {
69
+ let offset = from
70
+ let find = false
71
+ node.descendants((n) => {
72
+ if (find) return false
73
+ if (!n.textContent.includes(placeholder)) {
74
+ offset += n.nodeSize
75
+ return false
76
+ }
77
+ if (n.isText) {
78
+ const i = n.text?.indexOf(placeholder)
79
+ if (i != null && i >= 0) {
80
+ find = true
81
+ offset += i
82
+ return false
83
+ }
84
+ }
85
+
86
+ // enter the node
87
+ offset += 1
88
+ return true
89
+ })
90
+ return offset
91
+ }