@thomasfosterau/effect-richtext 0.0.0-stage → 0.1.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 +48 -0
- package/README.md +118 -2
- package/dist/_virtual/_rolldown/runtime.js +12 -0
- package/dist/commands/commands.d.ts +323 -0
- package/dist/commands/commands.js +722 -0
- package/dist/commands/internal.js +43 -0
- package/dist/commands/keymaps.d.ts +46 -0
- package/dist/commands/keymaps.js +64 -0
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +3 -0
- package/dist/compat/commands.d.ts +250 -0
- package/dist/compat/commands.js +275 -0
- package/dist/compat/internal/core.d.ts +20 -0
- package/dist/compat/internal/core.js +148 -0
- package/dist/compat/internal/props.js +82 -0
- package/dist/compat/keymap.d.ts +21 -0
- package/dist/compat/keymap.js +38 -0
- package/dist/compat/model.d.ts +893 -0
- package/dist/compat/model.js +1212 -0
- package/dist/compat/orderedmap.d.ts +74 -0
- package/dist/compat/orderedmap.js +124 -0
- package/dist/compat/schema-basic.d.ts +38 -0
- package/dist/compat/schema-basic.js +238 -0
- package/dist/compat/schema-list.d.ts +81 -0
- package/dist/compat/schema-list.js +156 -0
- package/dist/compat/state.d.ts +415 -0
- package/dist/compat/state.js +689 -0
- package/dist/compat/test-builder.d.ts +98 -0
- package/dist/compat/test-builder.js +197 -0
- package/dist/compat/transform.d.ts +458 -0
- package/dist/compat/transform.js +803 -0
- package/dist/compat/view.d.ts +411 -0
- package/dist/compat/view.js +409 -0
- package/dist/dropcursor.d.ts +46 -0
- package/dist/dropcursor.js +176 -0
- package/dist/extensions/defaults.d.ts +716 -0
- package/dist/extensions/defaults.js +209 -0
- package/dist/extensions/editor.d.ts +246 -0
- package/dist/extensions/editor.js +271 -0
- package/dist/extensions/extension.d.ts +375 -0
- package/dist/extensions/extension.js +233 -0
- package/dist/extensions.d.ts +4 -0
- package/dist/extensions.js +4 -0
- package/dist/gapcursor.d.ts +66 -0
- package/dist/gapcursor.js +273 -0
- package/dist/history.d.ts +178 -0
- package/dist/history.js +412 -0
- package/dist/html.d.ts +158 -0
- package/dist/html.js +164 -0
- package/dist/inputrules.d.ts +271 -0
- package/dist/inputrules.js +321 -0
- package/dist/internal/attempt.js +15 -0
- package/dist/internal/json.js +29 -0
- package/dist/keymap/keymap.d.ts +130 -0
- package/dist/keymap/keymap.js +159 -0
- package/dist/keymap.d.ts +2 -0
- package/dist/keymap.js +1 -0
- package/dist/markdown/errors.d.ts +23 -0
- package/dist/markdown/errors.js +26 -0
- package/dist/markdown/from_markdown.d.ts +185 -0
- package/dist/markdown/from_markdown.js +266 -0
- package/dist/markdown/schema.d.ts +522 -0
- package/dist/markdown/schema.js +292 -0
- package/dist/markdown/to_markdown.d.ts +251 -0
- package/dist/markdown/to_markdown.js +413 -0
- package/dist/markdown.d.ts +5 -0
- package/dist/markdown.js +5 -0
- package/dist/model/attrsCodec.js +48 -0
- package/dist/model/comparedeep.js +17 -0
- package/dist/model/content.d.ts +232 -0
- package/dist/model/content.js +630 -0
- package/dist/model/diff.js +66 -0
- package/dist/model/dom.d.ts +3 -0
- package/dist/model/errors.d.ts +90 -0
- package/dist/model/errors.js +76 -0
- package/dist/model/fragment.d.ts +131 -0
- package/dist/model/fragment.js +262 -0
- package/dist/model/from_dom.d.ts +299 -0
- package/dist/model/from_dom.js +618 -0
- package/dist/model/json.d.ts +37 -0
- package/dist/model/json.js +126 -0
- package/dist/model/mark.d.ts +73 -0
- package/dist/model/mark.js +121 -0
- package/dist/model/node.d.ts +267 -0
- package/dist/model/node.js +434 -0
- package/dist/model/replace.d.ts +78 -0
- package/dist/model/replace.js +207 -0
- package/dist/model/resolvedpos.d.ts +196 -0
- package/dist/model/resolvedpos.js +305 -0
- package/dist/model/schema.d.ts +740 -0
- package/dist/model/schema.js +451 -0
- package/dist/model/to_dom.d.ts +169 -0
- package/dist/model/to_dom.js +238 -0
- package/dist/model.d.ts +12 -0
- package/dist/model.js +12 -0
- package/dist/placeholder.d.ts +53 -0
- package/dist/placeholder.js +79 -0
- package/dist/reactive/observer.d.ts +54 -0
- package/dist/reactive/observer.js +61 -0
- package/dist/reactive/selectors.d.ts +22 -0
- package/dist/reactive/selectors.js +61 -0
- package/dist/reactive/stateref.d.ts +72 -0
- package/dist/reactive/stateref.js +71 -0
- package/dist/reactive/streams.d.ts +83 -0
- package/dist/reactive/streams.js +85 -0
- package/dist/reactive.d.ts +16 -0
- package/dist/reactive.js +26 -0
- package/dist/schema-basic.d.ts +448 -0
- package/dist/schema-basic.js +269 -0
- package/dist/schema-list/commands.d.ts +66 -0
- package/dist/schema-list/commands.js +203 -0
- package/dist/schema-list.d.ts +92 -0
- package/dist/schema-list.js +100 -0
- package/dist/state/errors.d.ts +39 -0
- package/dist/state/errors.js +32 -0
- package/dist/state/plugin.d.ts +232 -0
- package/dist/state/plugin.js +82 -0
- package/dist/state/selection.d.ts +297 -0
- package/dist/state/selection.js +486 -0
- package/dist/state/state.d.ts +212 -0
- package/dist/state/state.js +489 -0
- package/dist/state/transaction.d.ts +145 -0
- package/dist/state/transaction.js +195 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.js +6 -0
- package/dist/test-builder.d.ts +3582 -0
- package/dist/test-builder.js +239 -0
- package/dist/transform/attr_step.d.ts +60 -0
- package/dist/transform/attr_step.js +105 -0
- package/dist/transform/errors.d.ts +50 -0
- package/dist/transform/errors.js +35 -0
- package/dist/transform/map.d.ts +182 -0
- package/dist/transform/map.js +280 -0
- package/dist/transform/mark.js +98 -0
- package/dist/transform/mark_set_step.js +69 -0
- package/dist/transform/mark_step.d.ts +113 -0
- package/dist/transform/mark_step.js +225 -0
- package/dist/transform/replace.d.ts +18 -0
- package/dist/transform/replace.js +381 -0
- package/dist/transform/replace_step.d.ts +109 -0
- package/dist/transform/replace_step.js +175 -0
- package/dist/transform/step.d.ts +157 -0
- package/dist/transform/step.js +168 -0
- package/dist/transform/structure.d.ts +89 -0
- package/dist/transform/structure.js +350 -0
- package/dist/transform/transform.d.ts +248 -0
- package/dist/transform/transform.js +373 -0
- package/dist/transform.d.ts +10 -0
- package/dist/transform.js +10 -0
- package/dist/view/browser.js +39 -0
- package/dist/view/capturekeys.js +282 -0
- package/dist/view/clipboard.d.ts +8 -0
- package/dist/view/clipboard.js +245 -0
- package/dist/view/decoration.d.ts +339 -0
- package/dist/view/decoration.js +566 -0
- package/dist/view/dom.d.ts +18 -0
- package/dist/view/dom.js +140 -0
- package/dist/view/domchange.js +295 -0
- package/dist/view/domcoords.d.ts +22 -0
- package/dist/view/domcoords.js +448 -0
- package/dist/view/domobserver.d.ts +47 -0
- package/dist/view/domobserver.js +328 -0
- package/dist/view/errors.d.ts +18 -0
- package/dist/view/errors.js +21 -0
- package/dist/view/input.d.ts +58 -0
- package/dist/view/input.js +704 -0
- package/dist/view/selection.js +198 -0
- package/dist/view/view.d.ts +699 -0
- package/dist/view/view.js +623 -0
- package/dist/view/viewdesc.d.ts +236 -0
- package/dist/view/viewdesc.js +1205 -0
- package/dist/view-css.d.ts +43 -0
- package/dist/view-css.js +124 -0
- package/dist/view.d.ts +6 -0
- package/dist/view.js +4 -0
- package/package.json +206 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { keymap } from "../keymap/keymap.js";
|
|
2
|
+
import * as Arr from "effect/Array";
|
|
3
|
+
import * as Order from "effect/Order";
|
|
4
|
+
// src/extensions/extension.ts
|
|
5
|
+
/**
|
|
6
|
+
* Extensions: composable, strongly typed bundles of schema specs,
|
|
7
|
+
* plugins, keymaps and commands.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Extension priorities. Within a merged extension, facets with a higher
|
|
13
|
+
* priority come first: their node and mark specs come earlier in the
|
|
14
|
+
* schema (which matters for the default node of a group and for parse
|
|
15
|
+
* rule precedence), their plugins and keymaps come earlier in the
|
|
16
|
+
* plugin list (so their key bindings and props take precedence), and
|
|
17
|
+
* their commands win over commands with the same name. Facets with the
|
|
18
|
+
* same priority keep their declaration order.
|
|
19
|
+
*
|
|
20
|
+
* @since 0.1.0
|
|
21
|
+
* @category models
|
|
22
|
+
*/
|
|
23
|
+
const Priority = {
|
|
24
|
+
lowest: 0,
|
|
25
|
+
low: 1,
|
|
26
|
+
default: 2,
|
|
27
|
+
high: 3,
|
|
28
|
+
highest: 4
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
* @category symbols
|
|
33
|
+
*/
|
|
34
|
+
const ExtensionTypeId = Symbol.for("effect-richtext/extensions/Extension");
|
|
35
|
+
/**
|
|
36
|
+
* An extension: a tree of facets (node and mark specs, plugins,
|
|
37
|
+
* keymaps and commands), each with an optional priority. `T` records,
|
|
38
|
+
* at the type level, what the extension contributes. Extensions are
|
|
39
|
+
* immutable values; combine them with `union` and turn them into an
|
|
40
|
+
* editor with `createEditor`.
|
|
41
|
+
*
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
* @category models
|
|
44
|
+
*/
|
|
45
|
+
var Extension = class {
|
|
46
|
+
/** @internal */
|
|
47
|
+
constructor(spec, children, priority) {
|
|
48
|
+
this.spec = spec;
|
|
49
|
+
this.children = children;
|
|
50
|
+
this.priority = priority;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Whether a value is an `Extension`.
|
|
55
|
+
*
|
|
56
|
+
* @since 0.1.0
|
|
57
|
+
* @category guards
|
|
58
|
+
*/
|
|
59
|
+
const isExtension = (u) => u instanceof Extension;
|
|
60
|
+
/**
|
|
61
|
+
* Define an extension from node and mark specs, plugins, a keymap and
|
|
62
|
+
* commands. Every field is optional; the types of all of them are
|
|
63
|
+
* recorded in the extension's type.
|
|
64
|
+
*
|
|
65
|
+
* ```ts skip-type-checking
|
|
66
|
+
* const bold = defineExtension({
|
|
67
|
+
* marks: { strong: { parseDOM: [{ tag: "strong" }], toDOM: () => ["strong", 0] } },
|
|
68
|
+
* commands: { toggleBold: () => (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) },
|
|
69
|
+
* keymap: { "Mod-b": (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) }
|
|
70
|
+
* })
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Throws a `KeymapError` when a key name in `keymap` is invalid.
|
|
74
|
+
*
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
* @category constructors
|
|
77
|
+
*/
|
|
78
|
+
const defineExtension = (spec) => {
|
|
79
|
+
const plugins = spec.plugins ? [...spec.plugins] : [];
|
|
80
|
+
if (spec.keymap) plugins.push(keymap(spec.keymap));
|
|
81
|
+
return new Extension({
|
|
82
|
+
nodes: spec.nodes,
|
|
83
|
+
marks: spec.marks,
|
|
84
|
+
plugins,
|
|
85
|
+
commands: spec.commands
|
|
86
|
+
}, [], void 0);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Merge extensions. The result contributes everything its parts
|
|
90
|
+
* contribute: node, mark and command types are intersected, `E` and `R`
|
|
91
|
+
* are unions. At runtime facets are ordered by priority, then by
|
|
92
|
+
* declaration order (depth first); including the same extension value
|
|
93
|
+
* more than once only uses its first occurrence.
|
|
94
|
+
*
|
|
95
|
+
* @since 0.1.0
|
|
96
|
+
* @category combinators
|
|
97
|
+
*/
|
|
98
|
+
const union = (...extensions) => new Extension(void 0, extensions, void 0);
|
|
99
|
+
/**
|
|
100
|
+
* Give an extension (and every part of it that doesn't set its own
|
|
101
|
+
* priority) a priority.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
* @category combinators
|
|
105
|
+
*/
|
|
106
|
+
const withPriority = (extension, priority) => new Extension(void 0, [extension], priority);
|
|
107
|
+
/**
|
|
108
|
+
* Define an extension adding a single node spec.
|
|
109
|
+
*
|
|
110
|
+
* @since 0.1.0
|
|
111
|
+
* @category constructors
|
|
112
|
+
*/
|
|
113
|
+
const defineNodeSpec = (name, spec) => new Extension({ nodes: { [name]: spec } }, [], void 0);
|
|
114
|
+
/**
|
|
115
|
+
* Define an extension adding a single mark spec.
|
|
116
|
+
*
|
|
117
|
+
* @since 0.1.0
|
|
118
|
+
* @category constructors
|
|
119
|
+
*/
|
|
120
|
+
const defineMarkSpec = (name, spec) => new Extension({ marks: { [name]: spec } }, [], void 0);
|
|
121
|
+
/**
|
|
122
|
+
* Define an extension adding plugins: a plugin, an array of plugins, or
|
|
123
|
+
* a function creating them from the editor's schema.
|
|
124
|
+
*
|
|
125
|
+
* @since 0.1.0
|
|
126
|
+
* @category constructors
|
|
127
|
+
*/
|
|
128
|
+
const definePlugin = (plugin) => new Extension({ plugins: [plugin] }, [], void 0);
|
|
129
|
+
/**
|
|
130
|
+
* Define an extension adding a keymap (see `keymap`). Throws a
|
|
131
|
+
* `KeymapError` when a key name is invalid or bound twice.
|
|
132
|
+
*
|
|
133
|
+
* @since 0.1.0
|
|
134
|
+
* @category constructors
|
|
135
|
+
*/
|
|
136
|
+
const defineKeymap = (bindings) => defineExtension({ keymap: bindings });
|
|
137
|
+
/**
|
|
138
|
+
* Define an extension adding commands. Each command is given as a
|
|
139
|
+
* function from the command's arguments to a `Command`; the argument
|
|
140
|
+
* types become part of the editor's `commands`, `can()` and `chain()`
|
|
141
|
+
* APIs.
|
|
142
|
+
*
|
|
143
|
+
* ```ts skip-type-checking
|
|
144
|
+
* const ext = defineCommands({
|
|
145
|
+
* insertHello: (times: number) => (state, dispatch) => ...
|
|
146
|
+
* })
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* For commands whose argument types depend on the final schema, declare
|
|
150
|
+
* a `CommandSignature` and cast the extension (see `CommandsExtension`).
|
|
151
|
+
*
|
|
152
|
+
* @since 0.1.0
|
|
153
|
+
* @category constructors
|
|
154
|
+
*/
|
|
155
|
+
const defineCommands = (commands) => new Extension({ commands }, [], void 0);
|
|
156
|
+
const mergeSpec = (base, over) => {
|
|
157
|
+
if (!base) return over;
|
|
158
|
+
const attrs = base.attrs || over.attrs ? {
|
|
159
|
+
...base.attrs,
|
|
160
|
+
...over.attrs
|
|
161
|
+
} : void 0;
|
|
162
|
+
const merged = {
|
|
163
|
+
...base,
|
|
164
|
+
...over
|
|
165
|
+
};
|
|
166
|
+
return attrs ? {
|
|
167
|
+
...merged,
|
|
168
|
+
attrs
|
|
169
|
+
} : merged;
|
|
170
|
+
};
|
|
171
|
+
const byPriorityDesc = (a, b) => Order.Number(b.priority, a.priority);
|
|
172
|
+
/**
|
|
173
|
+
* Flatten an extension tree. Facets are sorted by priority (highest
|
|
174
|
+
* first), keeping declaration order for equal priorities. Node and mark
|
|
175
|
+
* specs with the same name are shallow-merged (attributes merged), with
|
|
176
|
+
* the higher-priority spec winning, at the position of the first
|
|
177
|
+
* occurrence; for commands with the same name the higher-priority one
|
|
178
|
+
* wins.
|
|
179
|
+
*
|
|
180
|
+
* @internal
|
|
181
|
+
*/
|
|
182
|
+
const flatten = (extension) => {
|
|
183
|
+
const collected = [];
|
|
184
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
185
|
+
const visit = (ext, inherited) => {
|
|
186
|
+
if (seen.has(ext)) return;
|
|
187
|
+
seen.add(ext);
|
|
188
|
+
const priority = ext.priority ?? inherited;
|
|
189
|
+
if (ext.spec) collected.push({
|
|
190
|
+
spec: ext.spec,
|
|
191
|
+
priority
|
|
192
|
+
});
|
|
193
|
+
for (const child of ext.children) visit(child, priority);
|
|
194
|
+
};
|
|
195
|
+
visit(extension, Priority.default);
|
|
196
|
+
const facets = Arr.sort(collected, byPriorityDesc);
|
|
197
|
+
const nodes = {};
|
|
198
|
+
const marks = {};
|
|
199
|
+
const plugins = [];
|
|
200
|
+
const commands = {};
|
|
201
|
+
for (const { spec } of facets) {
|
|
202
|
+
for (const name in spec.nodes) if (!(name in nodes)) nodes[name] = void 0;
|
|
203
|
+
for (const name in spec.marks) if (!(name in marks)) marks[name] = void 0;
|
|
204
|
+
if (spec.plugins) plugins.push(...spec.plugins);
|
|
205
|
+
for (const name in spec.commands) if (!(name in commands)) commands[name] = spec.commands[name];
|
|
206
|
+
}
|
|
207
|
+
for (let i = facets.length - 1; i >= 0; i--) {
|
|
208
|
+
const spec = facets[i].spec;
|
|
209
|
+
for (const name in spec.nodes) nodes[name] = mergeSpec(nodes[name], spec.nodes[name]);
|
|
210
|
+
for (const name in spec.marks) marks[name] = mergeSpec(marks[name], spec.marks[name]);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
nodes,
|
|
214
|
+
marks,
|
|
215
|
+
plugins,
|
|
216
|
+
commands
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Resolve plugin inputs against a schema.
|
|
221
|
+
*
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
const resolvePlugins = (inputs, schema) => {
|
|
225
|
+
const result = [];
|
|
226
|
+
const add = (input) => {
|
|
227
|
+
if (Array.isArray(input)) result.push(...input);
|
|
228
|
+
else result.push(input);
|
|
229
|
+
};
|
|
230
|
+
for (const input of inputs) add(typeof input === "function" ? input(schema) : input);
|
|
231
|
+
return result;
|
|
232
|
+
};
|
|
233
|
+
export { Extension, ExtensionTypeId, Priority, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, flatten, isExtension, resolvePlugins, union, withPriority };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BaseCommands, ListCommands, NodeTypeCommand, ToggleMarkCommand, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineDoc, defineEm, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineLink, defineList, defineParagraph, defineStrong, defineText } from "./extensions/defaults.js";
|
|
2
|
+
import { CanCommands, ChainRun, ChainedCommands, CommandFn, CreateEditorError, Editor, EditorCommands, EditorContent, EditorOptions, EditorTypeId, MarkBuilder, MarkBuilders, MountProps, NodeBuilder, NodeBuilders, NodeChild, SchemaOf, createEditor, createEditorUnsafe } from "./extensions/editor.js";
|
|
3
|
+
import { AnyExtension, CommandArgsOf, CommandCreator, CommandCreators, CommandSignature, CommandType, CommandTypes, CommandsError, CommandsExtension, CommandsRequirements, EmptyTypes, Extension, ExtensionCommands, ExtensionMarks, ExtensionNodes, ExtensionSpec, ExtensionTypeId, ExtensionTypes, MergeTypes, PluginInput, Priority, SpecTypes, TypesOf, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, isExtension, union, withPriority } from "./extensions/extension.js";
|
|
4
|
+
export { type AnyExtension, BaseCommands, CanCommands, ChainRun, ChainedCommands, type CommandArgsOf, type CommandCreator, type CommandCreators, CommandFn, type CommandSignature, type CommandType, type CommandTypes, type CommandsError, type CommandsExtension, type CommandsRequirements, CreateEditorError, Editor, EditorCommands, EditorContent, EditorOptions, EditorTypeId, type EmptyTypes, Extension, type ExtensionCommands, type ExtensionMarks, type ExtensionNodes, type ExtensionSpec, ExtensionTypeId, type ExtensionTypes, ListCommands, MarkBuilder, MarkBuilders, type MergeTypes, MountProps, NodeBuilder, NodeBuilders, NodeChild, NodeTypeCommand, type PluginInput, Priority, SchemaOf, type SpecTypes, ToggleMarkCommand, type TypesOf, createEditor, createEditorUnsafe, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineCommands, defineDoc, defineEm, defineExtension, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineKeymap, defineLink, defineList, defineMarkSpec, defineNodeSpec, defineParagraph, definePlugin, defineStrong, defineText, isExtension, union, withPriority };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineDoc, defineEm, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineLink, defineList, defineParagraph, defineStrong, defineText } from "./extensions/defaults.js";
|
|
2
|
+
import { Editor, EditorTypeId, createEditor, createEditorUnsafe } from "./extensions/editor.js";
|
|
3
|
+
import { Extension, ExtensionTypeId, Priority, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, isExtension, union, withPriority } from "./extensions/extension.js";
|
|
4
|
+
export { Editor, EditorTypeId, Extension, ExtensionTypeId, Priority, createEditor, createEditorUnsafe, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineCommands, defineDoc, defineEm, defineExtension, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineKeymap, defineLink, defineList, defineMarkSpec, defineNodeSpec, defineParagraph, definePlugin, defineStrong, defineText, isExtension, union, withPriority };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ResolvedPos } from "./model/resolvedpos.js";
|
|
2
|
+
import { Slice } from "./model/replace.js";
|
|
3
|
+
import { AnySchema } from "./model/schema.js";
|
|
4
|
+
import { Node } from "./model/node.js";
|
|
5
|
+
import "./model.js";
|
|
6
|
+
import { Mappable } from "./transform/map.js";
|
|
7
|
+
import "./state.js";
|
|
8
|
+
import { Selection, SelectionBookmark, SelectionJSON } from "./state/selection.js";
|
|
9
|
+
import { Plugin } from "./state/plugin.js";
|
|
10
|
+
import "./transform.js";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
/**
|
|
13
|
+
* Gap cursor selections are represented using this class. Its `$anchor`
|
|
14
|
+
* and `$head` properties both point at the cursor position.
|
|
15
|
+
*
|
|
16
|
+
* Node specs may set `allowGapCursor` to explicitly allow or disallow
|
|
17
|
+
* gap cursors inside nodes of that type, and `createGapCursor` to make
|
|
18
|
+
* the positions around nodes of that type valid gap cursor positions.
|
|
19
|
+
*
|
|
20
|
+
* @since 0.1.0
|
|
21
|
+
* @category models
|
|
22
|
+
*/
|
|
23
|
+
export declare class GapCursor<Sc extends AnySchema = AnySchema> extends Selection<Sc> {
|
|
24
|
+
/** Create a gap cursor. */
|
|
25
|
+
constructor($pos: ResolvedPos<Sc>);
|
|
26
|
+
get visible(): boolean;
|
|
27
|
+
map(doc: Node<Sc>, mapping: Mappable): Selection<Sc>;
|
|
28
|
+
content(): Slice<Sc>;
|
|
29
|
+
eq(other: Selection<any>): boolean;
|
|
30
|
+
toJSON(): SelectionJSON;
|
|
31
|
+
/** @internal */
|
|
32
|
+
static fromJSONUnsafe<Sc extends AnySchema>(doc: Node<Sc>, json: any): GapCursor<Sc>;
|
|
33
|
+
getBookmark(): SelectionBookmark<Sc>;
|
|
34
|
+
/** Whether a gap cursor is valid at the given position. */
|
|
35
|
+
static valid($pos: ResolvedPos<any>): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Find a valid gap cursor position starting at `$pos` and searching in
|
|
38
|
+
* direction `dir` (negative is backwards). When `mustMove` is true,
|
|
39
|
+
* `$pos` itself is not considered.
|
|
40
|
+
*/
|
|
41
|
+
static findGapCursorFrom<Sc extends AnySchema>($pos: ResolvedPos<Sc>, dir: number, mustMove?: boolean): Option.Option<ResolvedPos<Sc>>;
|
|
42
|
+
/** @internal */
|
|
43
|
+
static findGapCursorFromOrNull<Sc extends AnySchema>($pos: ResolvedPos<Sc>, dir: number, mustMove?: boolean): ResolvedPos<Sc> | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a gap cursor plugin. When enabled, this will capture clicks
|
|
47
|
+
* near and arrow-key-motion past places that don't have a normally
|
|
48
|
+
* selectable position nearby, and create a gap cursor selection for
|
|
49
|
+
* them. The cursor is drawn as an element with class
|
|
50
|
+
* `ProseMirror-gapcursor`. You can either include `gapCursorCSS` or add
|
|
51
|
+
* your own styles to make it visible.
|
|
52
|
+
*
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
* @category constructors
|
|
55
|
+
*/
|
|
56
|
+
export declare const gapCursor: () => Plugin<undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* The gap cursor's stylesheet (from `prosemirror-gapcursor`'s
|
|
59
|
+
* `style/gapcursor.css`). Add it to the page (for example with
|
|
60
|
+
* `injectCSS` from `@thomasfosterau/effect-richtext/view-css`) or provide your own
|
|
61
|
+
* styles for `.ProseMirror-gapcursor`.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
* @category styles
|
|
65
|
+
*/
|
|
66
|
+
export declare const gapCursorCSS: string;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { keydownHandler } from "./keymap/keymap.js";
|
|
2
|
+
import { Fragment } from "./model/fragment.js";
|
|
3
|
+
import { JSONError } from "./model/errors.js";
|
|
4
|
+
import { Slice } from "./model/replace.js";
|
|
5
|
+
import { NodeSelection, Selection, TextSelection } from "./state/selection.js";
|
|
6
|
+
import { Plugin } from "./state/plugin.js";
|
|
7
|
+
import { Decoration, DecorationSet } from "./view/decoration.js";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Predicate from "effect/Predicate";
|
|
11
|
+
// src/gapcursor.ts
|
|
12
|
+
/**
|
|
13
|
+
* Gap cursors: a selection type (and plugin) for focusing places that
|
|
14
|
+
* don't allow regular selection, such as positions between two
|
|
15
|
+
* adjacent block nodes that aren't textblocks. Ported from
|
|
16
|
+
* `prosemirror-gapcursor`.
|
|
17
|
+
*
|
|
18
|
+
* Importing this module registers the `GapCursor` selection class for
|
|
19
|
+
* JSON deserialization (under the ID `"gapcursor"`).
|
|
20
|
+
*
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
*/
|
|
23
|
+
const child = (node, index) => node.content.content[index];
|
|
24
|
+
/**
|
|
25
|
+
* Gap cursor selections are represented using this class. Its `$anchor`
|
|
26
|
+
* and `$head` properties both point at the cursor position.
|
|
27
|
+
*
|
|
28
|
+
* Node specs may set `allowGapCursor` to explicitly allow or disallow
|
|
29
|
+
* gap cursors inside nodes of that type, and `createGapCursor` to make
|
|
30
|
+
* the positions around nodes of that type valid gap cursor positions.
|
|
31
|
+
*
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
* @category models
|
|
34
|
+
*/
|
|
35
|
+
var GapCursor = class GapCursor extends Selection {
|
|
36
|
+
/** Create a gap cursor. */
|
|
37
|
+
constructor($pos) {
|
|
38
|
+
super($pos, $pos);
|
|
39
|
+
}
|
|
40
|
+
get visible() {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
map(doc, mapping) {
|
|
44
|
+
const $pos = doc.resolveUnsafe(mapping.map(this.head));
|
|
45
|
+
return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
|
|
46
|
+
}
|
|
47
|
+
content() {
|
|
48
|
+
return Slice.empty;
|
|
49
|
+
}
|
|
50
|
+
eq(other) {
|
|
51
|
+
return other instanceof GapCursor && other.head === this.head;
|
|
52
|
+
}
|
|
53
|
+
toJSON() {
|
|
54
|
+
return {
|
|
55
|
+
type: "gapcursor",
|
|
56
|
+
pos: this.head
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** @internal */
|
|
60
|
+
static fromJSONUnsafe(doc, json) {
|
|
61
|
+
if (!Predicate.isNumber(json.pos)) throw new JSONError({ message: "Invalid input for GapCursor.fromJSON" });
|
|
62
|
+
return new GapCursor(doc.resolveUnsafe(json.pos));
|
|
63
|
+
}
|
|
64
|
+
getBookmark() {
|
|
65
|
+
return new GapBookmark(this.anchor);
|
|
66
|
+
}
|
|
67
|
+
/** Whether a gap cursor is valid at the given position. */
|
|
68
|
+
static valid($pos) {
|
|
69
|
+
const parent = $pos.parent;
|
|
70
|
+
if (parent.inlineContent || !closedBefore($pos) || !closedAfter($pos)) return false;
|
|
71
|
+
const override = parent.type.spec.allowGapCursor;
|
|
72
|
+
if (override != null) return !!override;
|
|
73
|
+
const deflt = parent.contentMatchAtUnsafe($pos.index()).defaultTypeOrNull;
|
|
74
|
+
return !!deflt && deflt.isTextblock;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find a valid gap cursor position starting at `$pos` and searching in
|
|
78
|
+
* direction `dir` (negative is backwards). When `mustMove` is true,
|
|
79
|
+
* `$pos` itself is not considered.
|
|
80
|
+
*/
|
|
81
|
+
static findGapCursorFrom($pos, dir, mustMove = false) {
|
|
82
|
+
return Option.fromNullishOr(GapCursor.findGapCursorFromOrNull($pos, dir, mustMove));
|
|
83
|
+
}
|
|
84
|
+
/** @internal */
|
|
85
|
+
static findGapCursorFromOrNull($pos, dir, mustMove = false) {
|
|
86
|
+
search: for (;;) {
|
|
87
|
+
if (!mustMove && GapCursor.valid($pos)) return $pos;
|
|
88
|
+
let pos = $pos.pos, next = null;
|
|
89
|
+
for (let d = $pos.depth;; d--) {
|
|
90
|
+
const parent = $pos.node(d);
|
|
91
|
+
if (dir > 0 ? $pos.indexAfter(d) < parent.childCount : $pos.index(d) > 0) {
|
|
92
|
+
next = child(parent, dir > 0 ? $pos.indexAfter(d) : $pos.index(d) - 1);
|
|
93
|
+
break;
|
|
94
|
+
} else if (d === 0) return null;
|
|
95
|
+
pos += dir;
|
|
96
|
+
const $cur = $pos.doc.resolveUnsafe(pos);
|
|
97
|
+
if (GapCursor.valid($cur)) return $cur;
|
|
98
|
+
}
|
|
99
|
+
for (;;) {
|
|
100
|
+
const inside = dir > 0 ? next.content.content[0] : next.content.content[next.childCount - 1];
|
|
101
|
+
if (!inside) {
|
|
102
|
+
if (next.isAtom && !next.isText && !NodeSelection.isSelectable(next)) {
|
|
103
|
+
$pos = $pos.doc.resolveUnsafe(pos + next.nodeSize * dir);
|
|
104
|
+
mustMove = false;
|
|
105
|
+
continue search;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
next = inside;
|
|
110
|
+
pos += dir;
|
|
111
|
+
const $cur = $pos.doc.resolveUnsafe(pos);
|
|
112
|
+
if (GapCursor.valid($cur)) return $cur;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
Selection.jsonID("gapcursor", GapCursor);
|
|
119
|
+
var GapBookmark = class GapBookmark {
|
|
120
|
+
constructor(pos) {
|
|
121
|
+
this.pos = pos;
|
|
122
|
+
}
|
|
123
|
+
map(mapping) {
|
|
124
|
+
return new GapBookmark(mapping.map(this.pos));
|
|
125
|
+
}
|
|
126
|
+
resolve(doc) {
|
|
127
|
+
const $pos = doc.resolveUnsafe(this.pos);
|
|
128
|
+
return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function needsGap(type) {
|
|
132
|
+
return type.isAtom || !!type.spec.isolating || !!type.spec.createGapCursor;
|
|
133
|
+
}
|
|
134
|
+
function closedBefore($pos) {
|
|
135
|
+
for (let d = $pos.depth; d >= 0; d--) {
|
|
136
|
+
const index = $pos.index(d), parent = $pos.node(d);
|
|
137
|
+
if (index === 0) {
|
|
138
|
+
if (parent.type.spec.isolating) return true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (let before = child(parent, index - 1);; before = child(before, before.childCount - 1)) {
|
|
142
|
+
if (before.childCount === 0 && !before.inlineContent || needsGap(before.type)) return true;
|
|
143
|
+
if (before.inlineContent) return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function closedAfter($pos) {
|
|
149
|
+
for (let d = $pos.depth; d >= 0; d--) {
|
|
150
|
+
const index = $pos.indexAfter(d), parent = $pos.node(d);
|
|
151
|
+
if (index === parent.childCount) {
|
|
152
|
+
if (parent.type.spec.isolating) return true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
for (let after = child(parent, index);; after = child(after, 0)) {
|
|
156
|
+
if (after.childCount === 0 && !after.inlineContent || needsGap(after.type)) return true;
|
|
157
|
+
if (after.inlineContent) return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a gap cursor plugin. When enabled, this will capture clicks
|
|
164
|
+
* near and arrow-key-motion past places that don't have a normally
|
|
165
|
+
* selectable position nearby, and create a gap cursor selection for
|
|
166
|
+
* them. The cursor is drawn as an element with class
|
|
167
|
+
* `ProseMirror-gapcursor`. You can either include `gapCursorCSS` or add
|
|
168
|
+
* your own styles to make it visible.
|
|
169
|
+
*
|
|
170
|
+
* @since 0.1.0
|
|
171
|
+
* @category constructors
|
|
172
|
+
*/
|
|
173
|
+
const gapCursor = () => new Plugin({ props: {
|
|
174
|
+
decorations: drawGapCursor,
|
|
175
|
+
createSelectionBetween(_view, $anchor, $head) {
|
|
176
|
+
return $anchor.pos === $head.pos && GapCursor.valid($head) ? new GapCursor($head) : null;
|
|
177
|
+
},
|
|
178
|
+
handleClick,
|
|
179
|
+
handleKeyDown,
|
|
180
|
+
handleDOMEvents: { beforeinput }
|
|
181
|
+
} });
|
|
182
|
+
function arrow(axis, dir) {
|
|
183
|
+
const dirStr = axis === "vert" ? dir > 0 ? "down" : "up" : dir > 0 ? "right" : "left";
|
|
184
|
+
return (state, dispatch, view) => Effect.suspend(() => {
|
|
185
|
+
const sel = state.selection;
|
|
186
|
+
let $start = dir > 0 ? sel.$to : sel.$from, mustMove = sel.empty;
|
|
187
|
+
if (sel instanceof TextSelection) {
|
|
188
|
+
if (!view || !view.endOfTextblock(dirStr) || $start.depth === 0) return Effect.succeed(false);
|
|
189
|
+
mustMove = false;
|
|
190
|
+
$start = state.doc.resolveUnsafe(dir > 0 ? $start.afterUnsafe() : $start.beforeUnsafe());
|
|
191
|
+
}
|
|
192
|
+
const $found = GapCursor.findGapCursorFromOrNull($start, dir, mustMove);
|
|
193
|
+
if (!$found) return Effect.succeed(false);
|
|
194
|
+
if (!dispatch) return Effect.succeed(true);
|
|
195
|
+
return Effect.as(dispatch(state.tr.setSelectionUnsafe(new GapCursor($found))), true);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const handleKeyDown = keydownHandler({
|
|
199
|
+
ArrowLeft: arrow("horiz", -1),
|
|
200
|
+
ArrowRight: arrow("horiz", 1),
|
|
201
|
+
ArrowUp: arrow("vert", -1),
|
|
202
|
+
ArrowDown: arrow("vert", 1)
|
|
203
|
+
});
|
|
204
|
+
function handleClick(view, pos, event) {
|
|
205
|
+
if (!view || !view.editable) return false;
|
|
206
|
+
const $pos = view.state.doc.resolveUnsafe(pos);
|
|
207
|
+
if (!GapCursor.valid($pos)) return false;
|
|
208
|
+
const clickPos = Option.getOrNull(view.posAtCoords({
|
|
209
|
+
left: event.clientX,
|
|
210
|
+
top: event.clientY
|
|
211
|
+
}));
|
|
212
|
+
if (clickPos && clickPos.inside > -1) {
|
|
213
|
+
const node = view.state.doc.nodeAtOrNull(clickPos.inside);
|
|
214
|
+
if (node && NodeSelection.isSelectable(node)) return false;
|
|
215
|
+
}
|
|
216
|
+
return Effect.as(view.dispatch(view.state.tr.setSelectionUnsafe(new GapCursor($pos))), true);
|
|
217
|
+
}
|
|
218
|
+
function beforeinput(view, event) {
|
|
219
|
+
if (event.inputType !== "insertCompositionText" || !(view.state.selection instanceof GapCursor)) return false;
|
|
220
|
+
const { $from } = view.state.selection;
|
|
221
|
+
const insert = $from.parent.contentMatchAtUnsafe($from.index()).findWrappingOrNull(view.state.schema.nodes.text);
|
|
222
|
+
if (!insert) return false;
|
|
223
|
+
let frag = Fragment.empty;
|
|
224
|
+
for (let i = insert.length - 1; i >= 0; i--) frag = Fragment.from(insert[i].createAndFillOrNull(null, frag));
|
|
225
|
+
const tr = view.state.tr.replaceUnsafe($from.pos, $from.pos, new Slice(frag, 0, 0));
|
|
226
|
+
tr.setSelectionUnsafe(TextSelection.near(tr.doc.resolveUnsafe($from.pos + 1)));
|
|
227
|
+
return Effect.as(view.dispatch(tr), false);
|
|
228
|
+
}
|
|
229
|
+
function drawGapCursor(state) {
|
|
230
|
+
if (!(state.selection instanceof GapCursor)) return null;
|
|
231
|
+
const widget = Decoration.widget(state.selection.head, (view) => {
|
|
232
|
+
const node = view.dom.ownerDocument.createElement("div");
|
|
233
|
+
node.className = "ProseMirror-gapcursor";
|
|
234
|
+
return node;
|
|
235
|
+
}, { key: "gapcursor" });
|
|
236
|
+
return DecorationSet.create(state.doc, [widget]);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The gap cursor's stylesheet (from `prosemirror-gapcursor`'s
|
|
240
|
+
* `style/gapcursor.css`). Add it to the page (for example with
|
|
241
|
+
* `injectCSS` from `@thomasfosterau/effect-richtext/view-css`) or provide your own
|
|
242
|
+
* styles for `.ProseMirror-gapcursor`.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.1.0
|
|
245
|
+
* @category styles
|
|
246
|
+
*/
|
|
247
|
+
const gapCursorCSS = `.ProseMirror-gapcursor {
|
|
248
|
+
display: none;
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
position: absolute;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.ProseMirror-gapcursor:after {
|
|
254
|
+
content: "";
|
|
255
|
+
display: block;
|
|
256
|
+
position: absolute;
|
|
257
|
+
top: -2px;
|
|
258
|
+
width: 20px;
|
|
259
|
+
border-top: 1px solid black;
|
|
260
|
+
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@keyframes ProseMirror-cursor-blink {
|
|
264
|
+
to {
|
|
265
|
+
visibility: hidden;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.ProseMirror-focused .ProseMirror-gapcursor {
|
|
270
|
+
display: block;
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
export { GapCursor, gapCursor, gapCursorCSS };
|