@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/lib/__internal__/index.d.ts +2 -0
- package/lib/__internal__/index.d.ts.map +1 -0
- package/lib/__internal__/with-meta.d.ts +3 -0
- package/lib/__internal__/with-meta.d.ts.map +1 -0
- package/lib/config.d.ts +24 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/context.d.ts +12 -0
- package/lib/context.d.ts.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +328 -0
- package/lib/index.js.map +1 -0
- package/lib/inline-sync-plugin.d.ts +2 -0
- package/lib/inline-sync-plugin.d.ts.map +1 -0
- package/lib/regexp.d.ts +10 -0
- package/lib/regexp.d.ts.map +1 -0
- package/lib/replacer.d.ts +5 -0
- package/lib/replacer.d.ts.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/utils.d.ts +9 -0
- package/lib/utils.d.ts.map +1 -0
- package/package.json +37 -0
- package/src/__internal__/index.ts +1 -0
- package/src/__internal__/with-meta.ts +15 -0
- package/src/config.ts +84 -0
- package/src/context.ts +301 -0
- package/src/index.ts +9 -0
- package/src/inline-sync-plugin.ts +67 -0
- package/src/regexp.ts +20 -0
- package/src/replacer.ts +62 -0
- package/src/utils.ts +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020-present Mirone
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @jvs-milkdown/plugin-automd
|
|
2
|
+
|
|
3
|
+
The block plugin of [milkdown](https://milkdown.dev/).
|
|
4
|
+
|
|
5
|
+
# Official Documentation
|
|
6
|
+
|
|
7
|
+
Documentation can be found on the [Milkdown website](https://milkdown.dev/).
|
|
8
|
+
|
|
9
|
+
# License
|
|
10
|
+
|
|
11
|
+
Milkdown is open sourced software licensed under [MIT license](https://github.com/Milkdown/milkdown/blob/main/LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/__internal__/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-meta.d.ts","sourceRoot":"","sources":["../../src/__internal__/with-meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAE7D,wBAAgB,QAAQ,CAAC,CAAC,SAAS,cAAc,EAC/C,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,GAC9C,CAAC,CASH"}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx';
|
|
2
|
+
import type { Node, NodeType } from '@jvs-milkdown/prose/model';
|
|
3
|
+
import type { Transaction } from '@jvs-milkdown/prose/state';
|
|
4
|
+
export type ShouldSyncNode = (context: {
|
|
5
|
+
prevNode: Node;
|
|
6
|
+
nextNode: Node;
|
|
7
|
+
ctx: Ctx;
|
|
8
|
+
tr: Transaction;
|
|
9
|
+
text: string;
|
|
10
|
+
}) => boolean;
|
|
11
|
+
export interface SyncNodePlaceholder {
|
|
12
|
+
hole: string;
|
|
13
|
+
punctuation: string;
|
|
14
|
+
char: string;
|
|
15
|
+
}
|
|
16
|
+
export interface InlineSyncConfig {
|
|
17
|
+
placeholderConfig: SyncNodePlaceholder;
|
|
18
|
+
shouldSyncNode: ShouldSyncNode;
|
|
19
|
+
globalNodes: Array<NodeType | string>;
|
|
20
|
+
movePlaceholder: (placeholderToMove: string, text: string) => string;
|
|
21
|
+
}
|
|
22
|
+
export declare const defaultConfig: InlineSyncConfig;
|
|
23
|
+
export declare const inlineSyncConfig: import("@jvs-milkdown/utils").$Ctx<InlineSyncConfig, "inlineSyncConfig">;
|
|
24
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAQ5D,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE;IACrC,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;IACd,GAAG,EAAE,GAAG,CAAA;IACR,EAAE,EAAE,WAAW,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,KAAK,OAAO,CAAA;AAGb,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAGD,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,mBAAmB,CAAA;IACtC,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,EAAE,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAA;IACrC,eAAe,EAAE,CAAC,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACrE;AAGD,eAAO,MAAM,aAAa,EAAE,gBA4B3B,CAAA;AAaD,eAAO,MAAM,gBAAgB,0EAG5B,CAAA"}
|
package/lib/context.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Ctx } from '@jvs-milkdown/ctx';
|
|
2
|
+
import type { Node } from '@jvs-milkdown/prose/model';
|
|
3
|
+
import type { EditorState } from '@jvs-milkdown/prose/state';
|
|
4
|
+
interface InlineSyncContext {
|
|
5
|
+
text: string;
|
|
6
|
+
prevNode: Node;
|
|
7
|
+
nextNode: Node;
|
|
8
|
+
placeholder: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getContextByState(ctx: Ctx, state: EditorState): InlineSyncContext | null;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAsB5D,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB;AA8ED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,WAAW,GACjB,iBAAiB,GAAG,IAAI,CA8L1B"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAKvD,cAAc,UAAU,CAAA;AACxB,cAAc,sBAAsB,CAAA;AAEpC,eAAO,MAAM,MAAM,EAAE,cAAc,EAAyC,CAAA"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { $ctx, $prose, pipe } from "@jvs-milkdown/utils";
|
|
2
|
+
import { editorViewCtx, parserCtx, serializerCtx } from "@jvs-milkdown/core";
|
|
3
|
+
import { Plugin, PluginKey, TextSelection } from "@jvs-milkdown/prose/state";
|
|
4
|
+
import { Fragment } from "@jvs-milkdown/prose/model";
|
|
5
|
+
//#region src/__internal__/with-meta.ts
|
|
6
|
+
function withMeta(plugin, meta) {
|
|
7
|
+
Object.assign(plugin, { meta: {
|
|
8
|
+
package: "@jvs-milkdown/plugin-automd",
|
|
9
|
+
...meta
|
|
10
|
+
} });
|
|
11
|
+
return plugin;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/regexp.ts
|
|
15
|
+
var linkRegexp = /\[([^\]]+)]\([^\s\]]+\)/;
|
|
16
|
+
var emailCandidateRegexp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+$/;
|
|
17
|
+
var trailingPunctuationRegexp = /[.,:;!?\s\u2042\u2234\u2205]+$/;
|
|
18
|
+
var keepLinkRegexp = /\[(?<span>((www|https:\/\/|http:\/\/)[^\s\]]+))]\((?<url>[^\s\]]+)\)/;
|
|
19
|
+
function punctuationRegexp(holePlaceholder) {
|
|
20
|
+
return new RegExp(`\\\\(?=[^\\w\\s${holePlaceholder}\\\\]|_)`, "g");
|
|
21
|
+
}
|
|
22
|
+
var ZERO_WIDTH_SPACE = "";
|
|
23
|
+
var asterisk = `${ZERO_WIDTH_SPACE}*`;
|
|
24
|
+
var asteriskHolder = `${ZERO_WIDTH_SPACE}*`;
|
|
25
|
+
var underline = `${ZERO_WIDTH_SPACE}_`;
|
|
26
|
+
var underlineHolder = `${ZERO_WIDTH_SPACE}⎽`;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/utils.ts
|
|
29
|
+
function keepLink(str) {
|
|
30
|
+
let text = str;
|
|
31
|
+
let match = text.match(keepLinkRegexp);
|
|
32
|
+
while (match && match.groups) {
|
|
33
|
+
const { span } = match.groups;
|
|
34
|
+
text = text.replace(keepLinkRegexp, span);
|
|
35
|
+
match = text.match(keepLinkRegexp);
|
|
36
|
+
}
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
function mergeSlash(str) {
|
|
40
|
+
return str.replaceAll(/\\\\\*/g, asterisk).replaceAll(/\\\\_/g, underline).replaceAll(asterisk, asteriskHolder).replaceAll(underline, underlineHolder);
|
|
41
|
+
}
|
|
42
|
+
function swap(text, first, last) {
|
|
43
|
+
const arr = text.split("");
|
|
44
|
+
const temp = arr[first];
|
|
45
|
+
if (arr[first] && arr[last]) {
|
|
46
|
+
arr[first] = arr[last];
|
|
47
|
+
arr[last] = temp;
|
|
48
|
+
}
|
|
49
|
+
return arr.join("").toString();
|
|
50
|
+
}
|
|
51
|
+
function replacePunctuation(holePlaceholder) {
|
|
52
|
+
return (text) => text.replace(punctuationRegexp(holePlaceholder), "");
|
|
53
|
+
}
|
|
54
|
+
function calculatePlaceholder(placeholder) {
|
|
55
|
+
return (text) => {
|
|
56
|
+
const index = text.indexOf(placeholder.hole);
|
|
57
|
+
const left = text.charAt(index - 1);
|
|
58
|
+
const right = text.charAt(index + 1);
|
|
59
|
+
const notAWord = /[^\w]|_/;
|
|
60
|
+
if (!right) return placeholder.punctuation;
|
|
61
|
+
if (!left) return placeholder.char;
|
|
62
|
+
if (notAWord.test(left) && notAWord.test(right)) return placeholder.punctuation;
|
|
63
|
+
return placeholder.char;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function calcOffset(node, from, placeholder) {
|
|
67
|
+
let offset = from;
|
|
68
|
+
let find = false;
|
|
69
|
+
node.descendants((n) => {
|
|
70
|
+
if (find) return false;
|
|
71
|
+
if (!n.textContent.includes(placeholder)) {
|
|
72
|
+
offset += n.nodeSize;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (n.isText) {
|
|
76
|
+
const i = n.text?.indexOf(placeholder);
|
|
77
|
+
if (i != null && i >= 0) {
|
|
78
|
+
find = true;
|
|
79
|
+
offset += i;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
offset += 1;
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
return offset;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/config.ts
|
|
90
|
+
var defaultConfig = {
|
|
91
|
+
placeholderConfig: {
|
|
92
|
+
hole: "∅",
|
|
93
|
+
punctuation: "⁂",
|
|
94
|
+
char: "∴"
|
|
95
|
+
},
|
|
96
|
+
globalNodes: ["footnote_definition"],
|
|
97
|
+
shouldSyncNode: ({ prevNode, nextNode }) => prevNode.inlineContent && nextNode && prevNode.type === nextNode.type && !prevNode.eq(nextNode),
|
|
98
|
+
movePlaceholder: (placeholderToMove, text) => {
|
|
99
|
+
const symbolsNeedToMove = ["*", "_"];
|
|
100
|
+
let index = text.indexOf(placeholderToMove);
|
|
101
|
+
while (symbolsNeedToMove.includes(text[index - 1] || "") && symbolsNeedToMove.includes(text[index + 1] || "")) {
|
|
102
|
+
text = swap(text, index, index + 1);
|
|
103
|
+
index = index + 1;
|
|
104
|
+
}
|
|
105
|
+
return text;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var inlineSyncConfig = $ctx(defaultConfig, "inlineSyncConfig");
|
|
109
|
+
withMeta(inlineSyncConfig, {
|
|
110
|
+
displayName: "Ctx<inlineSyncConfig>",
|
|
111
|
+
group: "Prose"
|
|
112
|
+
});
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/context.ts
|
|
115
|
+
function getNodeFromSelection(state) {
|
|
116
|
+
return state.selection.$from.node();
|
|
117
|
+
}
|
|
118
|
+
function getMarkdown(ctx, state, node, globalNode) {
|
|
119
|
+
return ctx.get(serializerCtx)(state.schema.topNodeType.create(void 0, [node, ...globalNode]));
|
|
120
|
+
}
|
|
121
|
+
function addPlaceholder(ctx, markdown) {
|
|
122
|
+
const config = ctx.get(inlineSyncConfig.key);
|
|
123
|
+
const holePlaceholder = config.placeholderConfig.hole;
|
|
124
|
+
const [firstLine = "", ...rest] = markdown.split("\n\n");
|
|
125
|
+
const movePlaceholder = (text) => config.movePlaceholder(holePlaceholder, text);
|
|
126
|
+
let text = pipe(replacePunctuation(holePlaceholder), movePlaceholder, keepLink, mergeSlash)(firstLine);
|
|
127
|
+
const placeholder = calculatePlaceholder(config.placeholderConfig)(text);
|
|
128
|
+
text = text.replace(holePlaceholder, placeholder);
|
|
129
|
+
text = [text, ...rest].join("\n\n");
|
|
130
|
+
return [text, placeholder];
|
|
131
|
+
}
|
|
132
|
+
function getNewNode(ctx, text) {
|
|
133
|
+
const parsed = ctx.get(parserCtx)(text);
|
|
134
|
+
if (!parsed) return null;
|
|
135
|
+
return parsed.firstChild;
|
|
136
|
+
}
|
|
137
|
+
function collectGlobalNodes(ctx, state) {
|
|
138
|
+
const { globalNodes } = ctx.get(inlineSyncConfig.key);
|
|
139
|
+
const nodes = [];
|
|
140
|
+
state.doc.descendants((node) => {
|
|
141
|
+
if (globalNodes.includes(node.type.name) || globalNodes.includes(node.type)) {
|
|
142
|
+
nodes.push(node);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return nodes;
|
|
147
|
+
}
|
|
148
|
+
var removeGlobalFromText = (text) => text.split("\n\n")[0] || "";
|
|
149
|
+
function onlyHTML(node) {
|
|
150
|
+
return node.childCount === 1 && node.child(0).type.name === "html";
|
|
151
|
+
}
|
|
152
|
+
function getContextByState(ctx, state) {
|
|
153
|
+
try {
|
|
154
|
+
const globalNode = collectGlobalNodes(ctx, state);
|
|
155
|
+
const node = getNodeFromSelection(state);
|
|
156
|
+
const [text, placeholder] = addPlaceholder(ctx, getMarkdown(ctx, state, node, globalNode));
|
|
157
|
+
let newNode = getNewNode(ctx, text);
|
|
158
|
+
if (!newNode || node.type !== newNode.type || onlyHTML(newNode)) return null;
|
|
159
|
+
newNode.attrs = { ...node.attrs };
|
|
160
|
+
let modified = false;
|
|
161
|
+
const children = [];
|
|
162
|
+
const rawNodes = [];
|
|
163
|
+
newNode.content.forEach((c) => rawNodes.push(c));
|
|
164
|
+
const mergedNodes = [];
|
|
165
|
+
for (const curr of rawNodes) {
|
|
166
|
+
if (!curr) continue;
|
|
167
|
+
const prev = mergedNodes[mergedNodes.length - 1];
|
|
168
|
+
const prevIsLink = prev && prev.marks.some((m) => m.type.name === "link");
|
|
169
|
+
const currIsText = curr.isText && !curr.marks.some((m) => m.type.name === "link");
|
|
170
|
+
if (prevIsLink && currIsText && prev.text && curr.text) {
|
|
171
|
+
const combined = prev.text + curr.text;
|
|
172
|
+
mergedNodes[mergedNodes.length - 1] = state.schema.text(combined, prev.marks);
|
|
173
|
+
} else mergedNodes.push(curr);
|
|
174
|
+
}
|
|
175
|
+
if (mergedNodes.length !== rawNodes.length) modified = true;
|
|
176
|
+
mergedNodes.forEach((child) => {
|
|
177
|
+
const linkMark = child.marks.find((m) => m.type.name === "link");
|
|
178
|
+
if (!linkMark || !child.text) {
|
|
179
|
+
children.push(child);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const text = child.text;
|
|
183
|
+
const match = text.match(trailingPunctuationRegexp);
|
|
184
|
+
const suffix = match ? match[0] : "";
|
|
185
|
+
const trimmed = text.slice(0, text.length - suffix.length);
|
|
186
|
+
if (emailCandidateRegexp.test(trimmed)) {
|
|
187
|
+
if (suffix.length > 0) modified = true;
|
|
188
|
+
const newMarks = child.marks.map((m) => {
|
|
189
|
+
if (m.type.name === "link") return m.type.create({
|
|
190
|
+
...m.attrs,
|
|
191
|
+
href: `mailto:${trimmed}`
|
|
192
|
+
});
|
|
193
|
+
return m;
|
|
194
|
+
});
|
|
195
|
+
if (trimmed.length > 0) children.push(state.schema.text(trimmed, newMarks));
|
|
196
|
+
if (suffix.length > 0) children.push(state.schema.text(suffix, child.marks.filter((m) => m.type.name !== "link")));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const splitMatch = text.match(/^([a-zA-Z0-9._%+\-@]+)(.*)$/);
|
|
200
|
+
if (splitMatch) {
|
|
201
|
+
const possibleEmail = splitMatch[1];
|
|
202
|
+
const rest = splitMatch[2];
|
|
203
|
+
if (possibleEmail && rest && emailCandidateRegexp.test(possibleEmail) && rest.length > 0) {
|
|
204
|
+
modified = true;
|
|
205
|
+
const newMarks = child.marks.map((m) => {
|
|
206
|
+
if (m.type.name === "link") return m.type.create({
|
|
207
|
+
...m.attrs,
|
|
208
|
+
href: `mailto:${possibleEmail}`
|
|
209
|
+
});
|
|
210
|
+
return m;
|
|
211
|
+
});
|
|
212
|
+
children.push(state.schema.text(possibleEmail, newMarks));
|
|
213
|
+
children.push(state.schema.text(rest, child.marks.filter((m) => m.type.name !== "link")));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const href = linkMark.attrs.href;
|
|
218
|
+
if (typeof href === "string" && href.startsWith("mailto:") && href.includes(text)) {
|
|
219
|
+
modified = true;
|
|
220
|
+
children.push(state.schema.text(text, child.marks.filter((m) => m.type.name !== "link")));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
children.push(child);
|
|
224
|
+
});
|
|
225
|
+
if (modified) newNode = newNode.copy(Fragment.from(children));
|
|
226
|
+
newNode.descendants((node) => {
|
|
227
|
+
const link = node.marks.find((mark) => mark.type.name === "link");
|
|
228
|
+
if (link && node.text?.includes(placeholder) && link.attrs.href.includes(placeholder)) link.attrs.href = link.attrs.href.replace(placeholder, "");
|
|
229
|
+
const href = link?.attrs.href;
|
|
230
|
+
const text = node.text?.replace(placeholder, "") || "";
|
|
231
|
+
if (link && href?.startsWith("mailto:")) {
|
|
232
|
+
const address = href.slice(7);
|
|
233
|
+
const isValidCandidate = emailCandidateRegexp.test(text.trim());
|
|
234
|
+
const isAddressIncomplete = !address.includes("@");
|
|
235
|
+
const isTextSimple = !text.includes(" ") && text.length > 0;
|
|
236
|
+
const shouldSync = text.startsWith(address) || address.startsWith(text) || isAddressIncomplete && isTextSimple;
|
|
237
|
+
if (isValidCandidate && shouldSync) link.attrs.href = `mailto:${text.trim()}`;
|
|
238
|
+
else if (!isValidCandidate) node.marks = node.marks.filter((mark) => mark.type.name !== "link");
|
|
239
|
+
}
|
|
240
|
+
if (node.text?.includes(asteriskHolder) || node.text?.includes(underlineHolder)) node.text = node.text.replaceAll(asteriskHolder, asterisk).replaceAll(underlineHolder, underline);
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
text: removeGlobalFromText(text),
|
|
244
|
+
prevNode: node,
|
|
245
|
+
nextNode: newNode,
|
|
246
|
+
placeholder
|
|
247
|
+
};
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/replacer.ts
|
|
254
|
+
function runReplacer(ctx, key, state, dispatch, attrs) {
|
|
255
|
+
const { placeholderConfig } = ctx.get(inlineSyncConfig.key);
|
|
256
|
+
const holePlaceholder = placeholderConfig.hole;
|
|
257
|
+
let tr = state.tr.setMeta(key, true).insertText(holePlaceholder, state.selection.from);
|
|
258
|
+
const nextState = state.apply(tr);
|
|
259
|
+
const context = getContextByState(ctx, nextState);
|
|
260
|
+
if (!context) return;
|
|
261
|
+
const lastUserInput = context.text.slice(0, context.text.indexOf(context.placeholder));
|
|
262
|
+
const { $from } = nextState.selection;
|
|
263
|
+
const from = $from.before();
|
|
264
|
+
const to = $from.after();
|
|
265
|
+
const offset = calcOffset(context.nextNode, from, context.placeholder);
|
|
266
|
+
tr = tr.replaceWith(from, to, context.nextNode).setNodeMarkup(from, void 0, attrs).delete(offset + 1, offset + 2);
|
|
267
|
+
tr = tr.setSelection(TextSelection.near(tr.doc.resolve(offset + 1)));
|
|
268
|
+
if ((linkRegexp.test(lastUserInput) || [
|
|
269
|
+
"*",
|
|
270
|
+
"_",
|
|
271
|
+
"~"
|
|
272
|
+
].includes(lastUserInput.at(-1) || "")) && tr.selection instanceof TextSelection) (tr.selection.$cursor?.marks() ?? []).forEach((mark) => {
|
|
273
|
+
tr = tr.removeStoredMark(mark.type);
|
|
274
|
+
});
|
|
275
|
+
dispatch(tr);
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/inline-sync-plugin.ts
|
|
279
|
+
var inlineSyncPlugin = $prose((ctx) => {
|
|
280
|
+
let requestId = null;
|
|
281
|
+
const inlineSyncPluginKey = new PluginKey("MILKDOWN_INLINE_SYNC");
|
|
282
|
+
return new Plugin({
|
|
283
|
+
key: inlineSyncPluginKey,
|
|
284
|
+
state: {
|
|
285
|
+
init: () => {
|
|
286
|
+
return null;
|
|
287
|
+
},
|
|
288
|
+
apply: (tr, _value, _oldState, newState) => {
|
|
289
|
+
const view = ctx.get(editorViewCtx);
|
|
290
|
+
if (!view.hasFocus?.() || !view.editable) return null;
|
|
291
|
+
if (!tr.docChanged) return null;
|
|
292
|
+
if (tr.getMeta(inlineSyncPluginKey)) return null;
|
|
293
|
+
const context = getContextByState(ctx, newState);
|
|
294
|
+
if (!context) return null;
|
|
295
|
+
if (requestId) {
|
|
296
|
+
cancelAnimationFrame(requestId);
|
|
297
|
+
requestId = null;
|
|
298
|
+
}
|
|
299
|
+
const { prevNode, nextNode, text } = context;
|
|
300
|
+
const { shouldSyncNode } = ctx.get(inlineSyncConfig.key);
|
|
301
|
+
if (!shouldSyncNode({
|
|
302
|
+
prevNode,
|
|
303
|
+
nextNode,
|
|
304
|
+
ctx,
|
|
305
|
+
tr,
|
|
306
|
+
text
|
|
307
|
+
})) return null;
|
|
308
|
+
requestId = requestAnimationFrame(() => {
|
|
309
|
+
requestId = null;
|
|
310
|
+
const { dispatch, state } = ctx.get(editorViewCtx);
|
|
311
|
+
runReplacer(ctx, inlineSyncPluginKey, state, dispatch, prevNode.attrs);
|
|
312
|
+
});
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
withMeta(inlineSyncPlugin, {
|
|
319
|
+
displayName: "Prose<inlineSyncPlugin>",
|
|
320
|
+
group: "Prose"
|
|
321
|
+
});
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/index.ts
|
|
324
|
+
var automd = [inlineSyncConfig, inlineSyncPlugin];
|
|
325
|
+
//#endregion
|
|
326
|
+
export { automd, defaultConfig, inlineSyncConfig, inlineSyncPlugin };
|
|
327
|
+
|
|
328
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/__internal__/with-meta.ts","../src/regexp.ts","../src/utils.ts","../src/config.ts","../src/context.ts","../src/replacer.ts","../src/inline-sync-plugin.ts","../src/index.ts"],"sourcesContent":["import type { Meta, MilkdownPlugin } from '@jvs-milkdown/ctx'\n\nexport function withMeta<T extends MilkdownPlugin>(\n plugin: T,\n meta: Partial<Meta> & Pick<Meta, 'displayName'>\n): T {\n Object.assign(plugin, {\n meta: {\n package: '@jvs-milkdown/plugin-automd',\n ...meta,\n },\n })\n\n return plugin\n}\n","export const linkRegexp = /\\[([^\\]]+)]\\([^\\s\\]]+\\)/\n\nexport const emailCandidateRegexp =\n /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9.-]+$/\n\nexport const trailingPunctuationRegexp = /[.,:;!?\\s\\u2042\\u2234\\u2205]+$/\n\nexport const keepLinkRegexp =\n /\\[(?<span>((www|https:\\/\\/|http:\\/\\/)[^\\s\\]]+))]\\((?<url>[^\\s\\]]+)\\)/\n\nexport function punctuationRegexp(holePlaceholder: string) {\n return new RegExp(`\\\\\\\\(?=[^\\\\w\\\\s${holePlaceholder}\\\\\\\\]|_)`, 'g')\n}\n\nconst ZERO_WIDTH_SPACE = '\\u200B'\n\nexport const asterisk = `${ZERO_WIDTH_SPACE}*`\nexport const asteriskHolder = `${ZERO_WIDTH_SPACE}*`\nexport const underline = `${ZERO_WIDTH_SPACE}_`\nexport const underlineHolder = `${ZERO_WIDTH_SPACE}⎽`\n","import type { Node } from '@jvs-milkdown/prose/model'\n\nimport type { SyncNodePlaceholder } from './config'\n\nimport {\n asterisk,\n asteriskHolder,\n keepLinkRegexp,\n punctuationRegexp,\n underline,\n underlineHolder,\n} from './regexp'\n\nexport function keepLink(str: string) {\n let text = str\n let match = text.match(keepLinkRegexp)\n while (match && match.groups) {\n const { span } = match.groups\n text = text.replace(keepLinkRegexp, span as string)\n\n match = text.match(keepLinkRegexp)\n }\n return text\n}\n\nexport function mergeSlash(str: string) {\n return str\n .replaceAll(/\\\\\\\\\\*/g, asterisk)\n .replaceAll(/\\\\\\\\_/g, underline)\n .replaceAll(asterisk, asteriskHolder)\n .replaceAll(underline, underlineHolder)\n}\n\nexport function swap(text: string, first: number, last: number) {\n const arr = text.split('')\n const temp = arr[first]\n if (arr[first] && arr[last]) {\n arr[first] = arr[last] as string\n arr[last] = temp as string\n }\n return arr.join('').toString()\n}\n\nexport function replacePunctuation(holePlaceholder: string) {\n return (text: string) => text.replace(punctuationRegexp(holePlaceholder), '')\n}\n\nexport function calculatePlaceholder(placeholder: SyncNodePlaceholder) {\n return (text: string) => {\n const index = text.indexOf(placeholder.hole)\n const left = text.charAt(index - 1)\n const right = text.charAt(index + 1)\n const notAWord = /[^\\w]|_/\n\n // cursor on the right\n if (!right) return placeholder.punctuation\n\n // cursor on the left\n if (!left) return placeholder.char\n\n if (notAWord.test(left) && notAWord.test(right))\n return placeholder.punctuation\n\n return placeholder.char\n }\n}\n\nexport function calcOffset(node: Node, from: number, placeholder: string) {\n let offset = from\n let find = false\n node.descendants((n) => {\n if (find) return false\n if (!n.textContent.includes(placeholder)) {\n offset += n.nodeSize\n return false\n }\n if (n.isText) {\n const i = n.text?.indexOf(placeholder)\n if (i != null && i >= 0) {\n find = true\n offset += i\n return false\n }\n }\n\n // enter the node\n offset += 1\n return true\n })\n return offset\n}\n","import type { Ctx } from '@jvs-milkdown/ctx'\nimport type { Node, NodeType } from '@jvs-milkdown/prose/model'\nimport type { Transaction } from '@jvs-milkdown/prose/state'\n\nimport { $ctx } from '@jvs-milkdown/utils'\n\nimport { withMeta } from './__internal__'\nimport { swap } from './utils'\n\n/// @internal\nexport type ShouldSyncNode = (context: {\n prevNode: Node\n nextNode: Node\n ctx: Ctx\n tr: Transaction\n text: string\n}) => boolean\n\n/// @internal\nexport interface SyncNodePlaceholder {\n hole: string\n punctuation: string\n char: string\n}\n\n/// @internal\nexport interface InlineSyncConfig {\n placeholderConfig: SyncNodePlaceholder\n shouldSyncNode: ShouldSyncNode\n globalNodes: Array<NodeType | string>\n movePlaceholder: (placeholderToMove: string, text: string) => string\n}\n\n/// @internal\nexport const defaultConfig: InlineSyncConfig = {\n placeholderConfig: {\n hole: '∅',\n punctuation: '⁂',\n char: '∴',\n },\n globalNodes: ['footnote_definition'],\n shouldSyncNode: ({ prevNode, nextNode }) =>\n prevNode.inlineContent &&\n nextNode &&\n // if node type changes, do not sync\n prevNode.type === nextNode.type &&\n // if two node fully equal, we don't modify them\n !prevNode.eq(nextNode),\n movePlaceholder: (placeholderToMove: string, text: string) => {\n const symbolsNeedToMove = ['*', '_']\n\n let index = text.indexOf(placeholderToMove)\n while (\n symbolsNeedToMove.includes(text[index - 1] || '') &&\n symbolsNeedToMove.includes(text[index + 1] || '')\n ) {\n text = swap(text, index, index + 1)\n index = index + 1\n }\n\n return text\n },\n}\n\n/// A slice that contains the inline sync config.\n/// You can set value to this slice to change the config.\n///\n/// ```typescript\n/// ctx.update(inlineSyncConfigCtx, (prevCfg) => ({\n/// ...prevCfg,\n/// // your config\n/// }));\n/// ```\n///\n/// You can find the default config [here](https://github.com/Milkdown/milkdown/blob/main/packages/plugin-automd/src/config.ts).\nexport const inlineSyncConfig = $ctx<InlineSyncConfig, 'inlineSyncConfig'>(\n defaultConfig,\n 'inlineSyncConfig'\n)\n\nwithMeta(inlineSyncConfig, {\n displayName: 'Ctx<inlineSyncConfig>',\n group: 'Prose',\n})\n","import type { Ctx } from '@jvs-milkdown/ctx'\nimport type { Node } from '@jvs-milkdown/prose/model'\nimport type { EditorState } from '@jvs-milkdown/prose/state'\n\nimport { parserCtx, serializerCtx } from '@jvs-milkdown/core'\nimport { Fragment } from '@jvs-milkdown/prose/model'\nimport { pipe } from '@jvs-milkdown/utils'\n\nimport { inlineSyncConfig } from './config'\nimport {\n asterisk,\n asteriskHolder,\n emailCandidateRegexp,\n underline,\n underlineHolder,\n trailingPunctuationRegexp,\n} from './regexp'\nimport {\n calculatePlaceholder,\n keepLink,\n mergeSlash,\n replacePunctuation,\n} from './utils'\n\ninterface InlineSyncContext {\n text: string\n prevNode: Node\n nextNode: Node\n placeholder: string\n}\n\nfunction getNodeFromSelection(state: EditorState) {\n return state.selection.$from.node()\n}\n\nfunction getMarkdown(\n ctx: Ctx,\n state: EditorState,\n node: Node,\n globalNode: Node[]\n) {\n const serializer = ctx.get(serializerCtx)\n const doc = state.schema.topNodeType.create(undefined, [node, ...globalNode])\n\n return serializer(doc)\n}\n\nfunction addPlaceholder(ctx: Ctx, markdown: string) {\n const config = ctx.get(inlineSyncConfig.key)\n const holePlaceholder = config.placeholderConfig.hole\n\n const [firstLine = '', ...rest] = markdown.split('\\n\\n')\n\n const movePlaceholder = (text: string) =>\n config.movePlaceholder(holePlaceholder, text)\n\n const handleText = pipe(\n replacePunctuation(holePlaceholder),\n movePlaceholder,\n keepLink,\n mergeSlash\n )\n\n let text = handleText(firstLine)\n const placeholder = calculatePlaceholder(config.placeholderConfig)(text)\n\n text = text.replace(holePlaceholder, placeholder)\n\n text = [text, ...rest].join('\\n\\n')\n\n return [text, placeholder] as [markdown: string, placeholder: string]\n}\n\nfunction getNewNode(ctx: Ctx, text: string) {\n const parser = ctx.get(parserCtx)\n const parsed = parser(text)\n\n if (!parsed) return null\n\n return parsed.firstChild\n}\n\nfunction collectGlobalNodes(ctx: Ctx, state: EditorState) {\n const { globalNodes } = ctx.get(inlineSyncConfig.key)\n const nodes: Node[] = []\n\n state.doc.descendants((node) => {\n if (\n globalNodes.includes(node.type.name) ||\n globalNodes.includes(node.type)\n ) {\n nodes.push(node)\n return false\n }\n\n return undefined\n })\n\n return nodes\n}\n\nconst removeGlobalFromText = (text: string) => text.split('\\n\\n')[0] || ''\n\nfunction onlyHTML(node: Node) {\n return node.childCount === 1 && node.child(0).type.name === 'html'\n}\n\nexport function getContextByState(\n ctx: Ctx,\n state: EditorState\n): InlineSyncContext | null {\n try {\n const globalNode = collectGlobalNodes(ctx, state)\n const node = getNodeFromSelection(state)\n\n const markdown = getMarkdown(ctx, state, node, globalNode)\n const [text, placeholder] = addPlaceholder(ctx, markdown)\n\n let newNode = getNewNode(ctx, text)\n\n if (!newNode || node.type !== newNode.type || onlyHTML(newNode)) return null\n\n // @ts-expect-error hijack the node attribute\n newNode.attrs = { ...node.attrs }\n\n let modified = false\n const children: Node[] = []\n\n // Pass 1: Merge adjacent Link + Text nodes\n const rawNodes: Node[] = []\n newNode.content.forEach((c) => rawNodes.push(c))\n\n const mergedNodes: Node[] = []\n for (const curr of rawNodes) {\n if (!curr) continue\n\n const prev = mergedNodes[mergedNodes.length - 1]\n\n const prevIsLink = prev && prev.marks.some((m) => m.type.name === 'link')\n const currIsText =\n curr.isText && !curr.marks.some((m) => m.type.name === 'link')\n\n if (prevIsLink && currIsText && prev.text && curr.text) {\n const combined = prev.text + curr.text\n mergedNodes[mergedNodes.length - 1] = state.schema.text(\n combined,\n prev.marks\n )\n } else {\n mergedNodes.push(curr)\n }\n }\n if (mergedNodes.length !== rawNodes.length) {\n modified = true\n }\n mergedNodes.forEach((child) => {\n const linkMark = child.marks.find((m) => m.type.name === 'link')\n if (!linkMark || !child.text) {\n children.push(child)\n return\n }\n\n const text = child.text\n const match = text.match(trailingPunctuationRegexp)\n const suffix = match ? match[0] : ''\n const trimmed = text.slice(0, text.length - suffix.length)\n\n // Strategy 1: Suffix split (existing)\n if (emailCandidateRegexp.test(trimmed)) {\n if (suffix.length > 0) modified = true\n const newMarks = child.marks.map((m) => {\n if (m.type.name === 'link') {\n return m.type.create({ ...m.attrs, href: `mailto:${trimmed}` })\n }\n return m\n })\n\n if (trimmed.length > 0) {\n children.push(state.schema.text(trimmed, newMarks))\n }\n if (suffix.length > 0) {\n children.push(\n state.schema.text(\n suffix,\n child.marks.filter((m) => m.type.name !== 'link')\n )\n )\n }\n return\n }\n\n // Strategy 2: Prefix split (fallback)\n const splitMatch = text.match(/^([a-zA-Z0-9._%+\\-@]+)(.*)$/)\n if (splitMatch) {\n const possibleEmail = splitMatch[1]\n const rest = splitMatch[2]\n\n if (\n possibleEmail &&\n rest &&\n emailCandidateRegexp.test(possibleEmail) &&\n rest.length > 0\n ) {\n modified = true\n const newMarks = child.marks.map((m) => {\n if (m.type.name === 'link') {\n return m.type.create({\n ...m.attrs,\n href: `mailto:${possibleEmail}`,\n })\n }\n return m\n })\n children.push(state.schema.text(possibleEmail, newMarks))\n children.push(\n state.schema.text(\n rest,\n child.marks.filter((m) => m.type.name !== 'link')\n )\n )\n return\n }\n }\n\n // Fallback: If it's a mailto link and looks like an autolink (href contains text),\n const href = linkMark.attrs.href\n if (\n typeof href === 'string' &&\n href.startsWith('mailto:') &&\n href.includes(text)\n ) {\n modified = true\n children.push(\n state.schema.text(\n text,\n child.marks.filter((m) => m.type.name !== 'link')\n )\n )\n return\n }\n\n children.push(child)\n })\n\n if (modified) {\n newNode = newNode.copy(Fragment.from(children))\n }\n\n newNode.descendants((node) => {\n const marks = node.marks\n const link = marks.find((mark) => mark.type.name === 'link')\n if (\n link &&\n node.text?.includes(placeholder) &&\n link.attrs.href.includes(placeholder)\n ) {\n // @ts-expect-error hijack the mark attribute\n link.attrs.href = link.attrs.href.replace(placeholder, '')\n }\n\n const href = link?.attrs.href\n const text = node.text?.replace(placeholder, '') || ''\n if (link && href?.startsWith('mailto:')) {\n const address = href.slice(7)\n const isValidCandidate = emailCandidateRegexp.test(text.trim())\n const isAddressIncomplete = !address.includes('@')\n const isTextSimple = !text.includes(' ') && text.length > 0\n const shouldSync =\n text.startsWith(address) ||\n address.startsWith(text) ||\n (isAddressIncomplete && isTextSimple)\n\n if (isValidCandidate && shouldSync) {\n // @ts-expect-error hijack the mark attribute\n link.attrs.href = `mailto:${text.trim()}`\n } else if (!isValidCandidate) {\n // @ts-expect-error hijack mark\n node.marks = node.marks.filter((mark) => mark.type.name !== 'link')\n }\n }\n if (\n node.text?.includes(asteriskHolder) ||\n node.text?.includes(underlineHolder)\n ) {\n // @ts-expect-error hijack the attribute\n node.text = node.text\n .replaceAll(asteriskHolder, asterisk)\n .replaceAll(underlineHolder, underline)\n }\n })\n\n return {\n text: removeGlobalFromText(text),\n prevNode: node,\n nextNode: newNode,\n placeholder,\n }\n } catch {\n return null\n }\n}\n","import type { Ctx } from '@jvs-milkdown/ctx'\nimport type { Attrs } from '@jvs-milkdown/prose/model'\nimport type { EditorState, PluginKey, Transaction } from '@jvs-milkdown/prose/state'\n\nimport { TextSelection } from '@jvs-milkdown/prose/state'\n\nimport { inlineSyncConfig } from './config'\nimport { getContextByState } from './context'\nimport { linkRegexp } from './regexp'\nimport { calcOffset } from './utils'\n\nexport function runReplacer(\n ctx: Ctx,\n key: PluginKey,\n state: EditorState,\n dispatch: (tr: Transaction) => void,\n attrs: Attrs\n) {\n const { placeholderConfig } = ctx.get(inlineSyncConfig.key)\n const holePlaceholder = placeholderConfig.hole\n // insert a placeholder to restore the selection\n let tr = state.tr\n .setMeta(key, true)\n .insertText(holePlaceholder, state.selection.from)\n\n const nextState = state.apply(tr)\n const context = getContextByState(ctx, nextState)\n\n if (!context) return\n\n const lastUserInput = context.text.slice(\n 0,\n context.text.indexOf(context.placeholder)\n )\n\n const { $from } = nextState.selection\n const from = $from.before()\n const to = $from.after()\n\n const offset = calcOffset(context.nextNode, from, context.placeholder)\n\n tr = tr\n .replaceWith(from, to, context.nextNode)\n .setNodeMarkup(from, undefined, attrs)\n // delete the placeholder\n .delete(offset + 1, offset + 2)\n\n // restore the selection\n tr = tr.setSelection(TextSelection.near(tr.doc.resolve(offset + 1)))\n\n const needsRestoreMark =\n linkRegexp.test(lastUserInput) ||\n ['*', '_', '~'].includes(lastUserInput.at(-1) || '')\n if (needsRestoreMark && tr.selection instanceof TextSelection) {\n const marks = tr.selection.$cursor?.marks() ?? []\n marks.forEach((mark) => {\n tr = tr.removeStoredMark(mark.type)\n })\n }\n\n dispatch(tr)\n}\n","import type { Ctx } from '@jvs-milkdown/ctx'\n\nimport { editorViewCtx } from '@jvs-milkdown/core'\nimport { Plugin, PluginKey } from '@jvs-milkdown/prose/state'\nimport { $prose } from '@jvs-milkdown/utils'\n\nimport { withMeta } from './__internal__'\nimport { inlineSyncConfig } from './config'\nimport { getContextByState } from './context'\nimport { runReplacer } from './replacer'\n\n/// This plugin is used to sync the inline mark.\n/// It will create and remove marks automatically according to the user input.\n///\n/// When users type something, the plugin will transform the line (for better performance) to real markdown AST by serializer\n/// and render the AST to dom by parser, thus the input texts can be displayed correctly.\nexport const inlineSyncPlugin = $prose((ctx: Ctx) => {\n let requestId: number | null = null\n const inlineSyncPluginKey = new PluginKey('MILKDOWN_INLINE_SYNC')\n\n return new Plugin<null>({\n key: inlineSyncPluginKey,\n state: {\n init: () => {\n return null\n },\n apply: (tr, _value, _oldState, newState) => {\n const view = ctx.get(editorViewCtx)\n if (!view.hasFocus?.() || !view.editable) return null\n\n if (!tr.docChanged) return null\n\n const meta = tr.getMeta(inlineSyncPluginKey)\n if (meta) return null\n\n const context = getContextByState(ctx, newState)\n if (!context) return null\n\n if (requestId) {\n cancelAnimationFrame(requestId)\n requestId = null\n }\n\n const { prevNode, nextNode, text } = context\n\n const { shouldSyncNode } = ctx.get(inlineSyncConfig.key)\n\n if (!shouldSyncNode({ prevNode, nextNode, ctx, tr, text })) return null\n\n requestId = requestAnimationFrame(() => {\n requestId = null\n\n const { dispatch, state } = ctx.get(editorViewCtx)\n\n runReplacer(ctx, inlineSyncPluginKey, state, dispatch, prevNode.attrs)\n })\n\n return null\n },\n },\n })\n})\n\nwithMeta(inlineSyncPlugin, {\n displayName: 'Prose<inlineSyncPlugin>',\n group: 'Prose',\n})\n","import type { MilkdownPlugin } from '@jvs-milkdown/ctx'\n\nimport { inlineSyncConfig } from './config'\nimport { inlineSyncPlugin } from './inline-sync-plugin'\n\nexport * from './config'\nexport * from './inline-sync-plugin'\n\nexport const automd: MilkdownPlugin[] = [inlineSyncConfig, inlineSyncPlugin]\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-sync-plugin.d.ts","sourceRoot":"","sources":["../src/inline-sync-plugin.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,gBAAgB,sCA6C3B,CAAA"}
|
package/lib/regexp.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const linkRegexp: RegExp;
|
|
2
|
+
export declare const emailCandidateRegexp: RegExp;
|
|
3
|
+
export declare const trailingPunctuationRegexp: RegExp;
|
|
4
|
+
export declare const keepLinkRegexp: RegExp;
|
|
5
|
+
export declare function punctuationRegexp(holePlaceholder: string): RegExp;
|
|
6
|
+
export declare const asterisk = "\u200B*";
|
|
7
|
+
export declare const asteriskHolder = "\u200B\uFF0A";
|
|
8
|
+
export declare const underline = "\u200B_";
|
|
9
|
+
export declare const underlineHolder = "\u200B\u23BD";
|
|
10
|
+
//# sourceMappingURL=regexp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regexp.d.ts","sourceRoot":"","sources":["../src/regexp.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,QAA4B,CAAA;AAEnD,eAAO,MAAM,oBAAoB,QACqB,CAAA;AAEtD,eAAO,MAAM,yBAAyB,QAAmC,CAAA;AAEzE,eAAO,MAAM,cAAc,QAC6C,CAAA;AAExE,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,MAAM,UAExD;AAID,eAAO,MAAM,QAAQ,YAAyB,CAAA;AAC9C,eAAO,MAAM,cAAc,iBAAyB,CAAA;AACpD,eAAO,MAAM,SAAS,YAAyB,CAAA;AAC/C,eAAO,MAAM,eAAe,iBAAyB,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
export declare function runReplacer(ctx: Ctx, key: PluginKey, state: EditorState, dispatch: (tr: Transaction) => void, attrs: Attrs): void;
|
|
5
|
+
//# sourceMappingURL=replacer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replacer.d.ts","sourceRoot":"","sources":["../src/replacer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AASpF,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,EACnC,KAAK,EAAE,KAAK,QA6Cb"}
|