@thomasfosterau/effect-richtext 0.0.0 → 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 +117 -1
- 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 +200 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
declare const PositionError_base: Schema.Class<PositionError, Schema.TaggedStruct<"PositionError", {
|
|
3
|
+
readonly message: Schema.String;
|
|
4
|
+
}>, import("effect/Cause").YieldableError>;
|
|
5
|
+
/**
|
|
6
|
+
* A position or index was outside of the valid range of a node or
|
|
7
|
+
* fragment.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
* @category errors
|
|
11
|
+
*/
|
|
12
|
+
export declare class PositionError extends PositionError_base {}
|
|
13
|
+
declare const ReplaceError_base: Schema.Class<ReplaceError, Schema.TaggedStruct<"ReplaceError", {
|
|
14
|
+
readonly message: Schema.String;
|
|
15
|
+
}>, import("effect/Cause").YieldableError>;
|
|
16
|
+
/**
|
|
17
|
+
* Raised when a [`Slice`](#Slice) cannot be fitted into a document,
|
|
18
|
+
* typically by `Node.replace`.
|
|
19
|
+
*
|
|
20
|
+
* @since 0.1.0
|
|
21
|
+
* @category errors
|
|
22
|
+
*/
|
|
23
|
+
export declare class ReplaceError extends ReplaceError_base {}
|
|
24
|
+
declare const ContentError_base: Schema.Class<ContentError, Schema.TaggedStruct<"ContentError", {
|
|
25
|
+
readonly message: Schema.String;
|
|
26
|
+
}>, import("effect/Cause").YieldableError>;
|
|
27
|
+
/**
|
|
28
|
+
* Content that does not satisfy a node type's content expression or
|
|
29
|
+
* mark restrictions.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
* @category errors
|
|
33
|
+
*/
|
|
34
|
+
export declare class ContentError extends ContentError_base {}
|
|
35
|
+
declare const AttributeError_base: Schema.Class<AttributeError, Schema.TaggedStruct<"AttributeError", {
|
|
36
|
+
readonly message: Schema.String;
|
|
37
|
+
}>, import("effect/Cause").YieldableError>;
|
|
38
|
+
/**
|
|
39
|
+
* Missing, unknown, or invalid node or mark attributes.
|
|
40
|
+
*
|
|
41
|
+
* @since 0.1.0
|
|
42
|
+
* @category errors
|
|
43
|
+
*/
|
|
44
|
+
export declare class AttributeError extends AttributeError_base {}
|
|
45
|
+
declare const SchemaDefinitionError_base: Schema.Class<SchemaDefinitionError, Schema.TaggedStruct<"SchemaDefinitionError", {
|
|
46
|
+
readonly message: Schema.String;
|
|
47
|
+
}>, import("effect/Cause").YieldableError>;
|
|
48
|
+
/**
|
|
49
|
+
* A schema definition (or a content expression in it) is invalid.
|
|
50
|
+
*
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
* @category errors
|
|
53
|
+
*/
|
|
54
|
+
export declare class SchemaDefinitionError extends SchemaDefinitionError_base {}
|
|
55
|
+
declare const JSONError_base: Schema.Class<JSONError, Schema.TaggedStruct<"JSONError", {
|
|
56
|
+
readonly message: Schema.String;
|
|
57
|
+
}>, import("effect/Cause").YieldableError>;
|
|
58
|
+
/**
|
|
59
|
+
* Raised when deserializing JSON that does not describe a valid
|
|
60
|
+
* document structure for the schema.
|
|
61
|
+
*
|
|
62
|
+
* @since 0.1.0
|
|
63
|
+
* @category errors
|
|
64
|
+
*/
|
|
65
|
+
export declare class JSONError extends JSONError_base {}
|
|
66
|
+
declare const DOMSpecError_base: Schema.Class<DOMSpecError, Schema.TaggedStruct<"DOMSpecError", {
|
|
67
|
+
readonly message: Schema.String;
|
|
68
|
+
}>, import("effect/Cause").YieldableError>;
|
|
69
|
+
/**
|
|
70
|
+
* An invalid [`DOMOutputSpec`](#DOMOutputSpec) was passed to the DOM
|
|
71
|
+
* serializer (for example a misplaced content hole, or an array taken
|
|
72
|
+
* from an attribute value).
|
|
73
|
+
*
|
|
74
|
+
* @since 0.1.0
|
|
75
|
+
* @category errors
|
|
76
|
+
*/
|
|
77
|
+
export declare class DOMSpecError extends DOMSpecError_base {}
|
|
78
|
+
/**
|
|
79
|
+
* Union of all errors produced by the model.
|
|
80
|
+
*
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
* @category errors
|
|
83
|
+
*/
|
|
84
|
+
export type ModelError = PositionError | ReplaceError | ContentError | AttributeError | SchemaDefinitionError | JSONError | DOMSpecError;
|
|
85
|
+
/**
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
* @category guards
|
|
88
|
+
*/
|
|
89
|
+
export declare const isModelError: (u: unknown) => u is ModelError;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Predicate from "effect/Predicate";
|
|
2
|
+
import * as HashSet from "effect/HashSet";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
// src/model/errors.ts
|
|
5
|
+
/**
|
|
6
|
+
* Tagged errors raised by the document model.
|
|
7
|
+
*
|
|
8
|
+
* Every fallible operation in `@thomasfosterau/effect-richtext/model` reports failure
|
|
9
|
+
* through the Effect error channel using one of these errors. The
|
|
10
|
+
* `*Unsafe` variants of those operations throw the same error values
|
|
11
|
+
* instead, which makes them usable from synchronous code.
|
|
12
|
+
*
|
|
13
|
+
* @since 0.1.0
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A position or index was outside of the valid range of a node or
|
|
17
|
+
* fragment.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
* @category errors
|
|
21
|
+
*/
|
|
22
|
+
var PositionError = class extends Schema.TaggedError()("PositionError", { message: Schema.String }) {};
|
|
23
|
+
/**
|
|
24
|
+
* Raised when a [`Slice`](#Slice) cannot be fitted into a document,
|
|
25
|
+
* typically by `Node.replace`.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
* @category errors
|
|
29
|
+
*/
|
|
30
|
+
var ReplaceError = class extends Schema.TaggedError()("ReplaceError", { message: Schema.String }) {};
|
|
31
|
+
/**
|
|
32
|
+
* Content that does not satisfy a node type's content expression or
|
|
33
|
+
* mark restrictions.
|
|
34
|
+
*
|
|
35
|
+
* @since 0.1.0
|
|
36
|
+
* @category errors
|
|
37
|
+
*/
|
|
38
|
+
var ContentError = class extends Schema.TaggedError()("ContentError", { message: Schema.String }) {};
|
|
39
|
+
/**
|
|
40
|
+
* Missing, unknown, or invalid node or mark attributes.
|
|
41
|
+
*
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
* @category errors
|
|
44
|
+
*/
|
|
45
|
+
var AttributeError = class extends Schema.TaggedError()("AttributeError", { message: Schema.String }) {};
|
|
46
|
+
/**
|
|
47
|
+
* A schema definition (or a content expression in it) is invalid.
|
|
48
|
+
*
|
|
49
|
+
* @since 0.1.0
|
|
50
|
+
* @category errors
|
|
51
|
+
*/
|
|
52
|
+
var SchemaDefinitionError = class extends Schema.TaggedError()("SchemaDefinitionError", { message: Schema.String }) {};
|
|
53
|
+
/**
|
|
54
|
+
* Raised when deserializing JSON that does not describe a valid
|
|
55
|
+
* document structure for the schema.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
* @category errors
|
|
59
|
+
*/
|
|
60
|
+
var JSONError = class extends Schema.TaggedError()("JSONError", { message: Schema.String }) {};
|
|
61
|
+
/**
|
|
62
|
+
* An invalid [`DOMOutputSpec`](#DOMOutputSpec) was passed to the DOM
|
|
63
|
+
* serializer (for example a misplaced content hole, or an array taken
|
|
64
|
+
* from an attribute value).
|
|
65
|
+
*
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
* @category errors
|
|
68
|
+
*/
|
|
69
|
+
var DOMSpecError = class extends Schema.TaggedError()("DOMSpecError", { message: Schema.String }) {};
|
|
70
|
+
const modelErrorTags = HashSet.make("PositionError", "ReplaceError", "ContentError", "AttributeError", "SchemaDefinitionError", "JSONError", "DOMSpecError");
|
|
71
|
+
/**
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
* @category guards
|
|
74
|
+
*/
|
|
75
|
+
const isModelError = (u) => Predicate.isObjectOrArray(u) && HashSet.has(modelErrorTags, u._tag);
|
|
76
|
+
export { AttributeError, ContentError, DOMSpecError, JSONError, PositionError, ReplaceError, SchemaDefinitionError, isModelError };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { PositionError } from "./errors.js";
|
|
2
|
+
import { Node, NodeJSON } from "./node.js";
|
|
3
|
+
import { AnySchema, NodeNames } from "./schema.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import * as Equal from "effect/Equal";
|
|
7
|
+
import * as Hash from "effect/Hash";
|
|
8
|
+
/**
|
|
9
|
+
* Callback used by `nodesBetween` and `descendants`. Return `false` to
|
|
10
|
+
* prevent descending into a node.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
* @category models
|
|
14
|
+
*/
|
|
15
|
+
export type NodeVisitor<Sc extends AnySchema = AnySchema> = (node: Node<Sc>, pos: number, parent: Option.Option<Node<Sc>>, index: number) => boolean | void;
|
|
16
|
+
/**
|
|
17
|
+
* A fragment represents a node's collection of child nodes.
|
|
18
|
+
*
|
|
19
|
+
* Like nodes, fragments are persistent data structures, and you should
|
|
20
|
+
* not mutate them or their content. Rather, you create new instances
|
|
21
|
+
* whenever needed.
|
|
22
|
+
*
|
|
23
|
+
* Fragments implement `Equal` (structural equality) and are iterable.
|
|
24
|
+
*
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
* @category models
|
|
27
|
+
*/
|
|
28
|
+
export declare class Fragment<Sc extends AnySchema = AnySchema, out N extends NodeNames<Sc> = NodeNames<Sc>> implements Equal.Equal, Iterable<Node<Sc, N>> {
|
|
29
|
+
/** The child nodes in this fragment. */
|
|
30
|
+
readonly content: ReadonlyArray<Node<Sc, N>>;
|
|
31
|
+
/** The size of the fragment, which is the total of the size of its content nodes. */
|
|
32
|
+
readonly size: number;
|
|
33
|
+
/** @internal */
|
|
34
|
+
constructor(
|
|
35
|
+
/** The child nodes in this fragment. */
|
|
36
|
+
content: ReadonlyArray<Node<Sc, N>>, size?: number);
|
|
37
|
+
[Symbol.iterator](): Iterator<Node<Sc, N>>;
|
|
38
|
+
/**
|
|
39
|
+
* Invoke a callback for all descendant nodes between the given two
|
|
40
|
+
* positions (relative to start of this fragment). Doesn't descend
|
|
41
|
+
* into a node when the callback returns `false`.
|
|
42
|
+
*/
|
|
43
|
+
nodesBetween(from: number, to: number, f: NodeVisitor<Sc>, nodeStart?: number, parent?: Node<Sc>): void;
|
|
44
|
+
/**
|
|
45
|
+
* Call the given callback for every descendant node. `pos` will be
|
|
46
|
+
* relative to the start of the fragment. The callback may return
|
|
47
|
+
* `false` to prevent traversal of a given node's children.
|
|
48
|
+
*/
|
|
49
|
+
descendants(f: NodeVisitor<Sc>): void;
|
|
50
|
+
/** Extract the text between `from` and `to`. See `Node.textBetween`. */
|
|
51
|
+
textBetween(from: number, to: number, blockSeparator?: string | null, leafText?: string | null | ((leafNode: Node<Sc>) => string)): string;
|
|
52
|
+
/** Create a new fragment containing the combined content of this fragment and the other. */
|
|
53
|
+
append<N2 extends NodeNames<Sc>>(other: Fragment<Sc, N2>): Fragment<Sc, N | N2>;
|
|
54
|
+
/** Cut out the sub-fragment between the two given positions. */
|
|
55
|
+
cut(from: number, to?: number): Fragment<Sc, N>;
|
|
56
|
+
/** @internal */
|
|
57
|
+
cutByIndex(from: number, to: number): Fragment<Sc, N>;
|
|
58
|
+
/** Create a new fragment in which the node at the given index is replaced by the given node. */
|
|
59
|
+
replaceChild<N2 extends NodeNames<Sc>>(index: number, node: Node<Sc, N2>): Fragment<Sc, N | N2>;
|
|
60
|
+
/** Create a new fragment by prepending the given node to this fragment. */
|
|
61
|
+
addToStart<N2 extends NodeNames<Sc>>(node: Node<Sc, N2>): Fragment<Sc, N | N2>;
|
|
62
|
+
/** Create a new fragment by appending the given node to this fragment. */
|
|
63
|
+
addToEnd<N2 extends NodeNames<Sc>>(node: Node<Sc, N2>): Fragment<Sc, N | N2>;
|
|
64
|
+
/** Compare this fragment to another one. */
|
|
65
|
+
eq(other: Fragment<any, any>): boolean;
|
|
66
|
+
/** @internal */
|
|
67
|
+
[Equal.symbol](that: Equal.Equal): boolean;
|
|
68
|
+
/** @internal */
|
|
69
|
+
[Hash.symbol](): number;
|
|
70
|
+
/** The first child of the fragment, if any. */
|
|
71
|
+
get firstChild(): Option.Option<Node<Sc, N>>;
|
|
72
|
+
/** The last child of the fragment, if any. */
|
|
73
|
+
get lastChild(): Option.Option<Node<Sc, N>>;
|
|
74
|
+
/** The number of child nodes in this fragment. */
|
|
75
|
+
get childCount(): number;
|
|
76
|
+
/** Get the child node at the given index. Fails with a `PositionError` when the index is out of range. */
|
|
77
|
+
child(index: number): Effect.Effect<Node<Sc, N>, PositionError>;
|
|
78
|
+
/** Like `child`, but throws a `PositionError` when the index is out of range. */
|
|
79
|
+
childUnsafe(index: number): Node<Sc, N>;
|
|
80
|
+
/** Get the child node at the given index, if it exists. */
|
|
81
|
+
maybeChild(index: number): Option.Option<Node<Sc, N>>;
|
|
82
|
+
/** Call `f` for every child node, passing the node, its offset into this parent node, and its index. */
|
|
83
|
+
forEach(f: (node: Node<Sc, N>, offset: number, index: number) => void): void;
|
|
84
|
+
/** Find the first position at which this fragment and another fragment differ, if any. */
|
|
85
|
+
findDiffStart(other: Fragment<any, any>, pos?: number): Option.Option<number>;
|
|
86
|
+
/**
|
|
87
|
+
* Find the first position, searching from the end, at which this
|
|
88
|
+
* fragment and the given fragment differ, if any. Since this
|
|
89
|
+
* position will not be the same in both nodes, an object with two
|
|
90
|
+
* separate positions is returned.
|
|
91
|
+
*/
|
|
92
|
+
findDiffEnd(other: Fragment<any, any>, pos?: number, otherPos?: number): Option.Option<{
|
|
93
|
+
readonly a: number;
|
|
94
|
+
readonly b: number;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Find the index and inner offset corresponding to a given relative
|
|
98
|
+
* position in this fragment. The result object will be reused
|
|
99
|
+
* (overwritten) the next time the function is called.
|
|
100
|
+
*
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
findIndex(pos: number): {
|
|
104
|
+
index: number;
|
|
105
|
+
offset: number;
|
|
106
|
+
};
|
|
107
|
+
/** Return a debugging string that describes this fragment. */
|
|
108
|
+
toString(): string;
|
|
109
|
+
/** @internal */
|
|
110
|
+
toStringInner(): string;
|
|
111
|
+
/** Create a JSON-serializable representation of this fragment. */
|
|
112
|
+
toJSON(): ReadonlyArray<NodeJSON> | null;
|
|
113
|
+
/**
|
|
114
|
+
* Build a fragment from an array of nodes. Ensures that adjacent text
|
|
115
|
+
* nodes with the same marks are joined together.
|
|
116
|
+
*/
|
|
117
|
+
static fromArray<Sc extends AnySchema, N extends NodeNames<Sc>>(array: ReadonlyArray<Node<Sc, N>>): Fragment<Sc, N>;
|
|
118
|
+
/**
|
|
119
|
+
* Create a fragment from something that can be interpreted as a set
|
|
120
|
+
* of nodes. For `null`/`undefined`, it returns the empty fragment.
|
|
121
|
+
* For a fragment, the fragment itself. For a node or array of nodes,
|
|
122
|
+
* a fragment containing those nodes.
|
|
123
|
+
*/
|
|
124
|
+
static from<Sc extends AnySchema, N extends NodeNames<Sc>>(nodes?: Fragment<Sc, N> | Node<Sc, N> | ReadonlyArray<Node<Sc, N>> | null): Fragment<Sc, N>;
|
|
125
|
+
/**
|
|
126
|
+
* An empty fragment. Intended to be reused whenever a node doesn't
|
|
127
|
+
* contain anything (rather than allocating a new empty fragment for
|
|
128
|
+
* each leaf node).
|
|
129
|
+
*/
|
|
130
|
+
static readonly empty: Fragment<any, any>;
|
|
131
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { attempt } from "../internal/attempt.js";
|
|
2
|
+
import { findDiffEnd, findDiffStart } from "./diff.js";
|
|
3
|
+
import { PositionError } from "./errors.js";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
import * as Equal from "effect/Equal";
|
|
6
|
+
import * as Hash from "effect/Hash";
|
|
7
|
+
// src/model/fragment.ts
|
|
8
|
+
const isPositionError = (u) => u instanceof PositionError;
|
|
9
|
+
/**
|
|
10
|
+
* A fragment represents a node's collection of child nodes.
|
|
11
|
+
*
|
|
12
|
+
* Like nodes, fragments are persistent data structures, and you should
|
|
13
|
+
* not mutate them or their content. Rather, you create new instances
|
|
14
|
+
* whenever needed.
|
|
15
|
+
*
|
|
16
|
+
* Fragments implement `Equal` (structural equality) and are iterable.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
* @category models
|
|
20
|
+
*/
|
|
21
|
+
var Fragment = class Fragment {
|
|
22
|
+
/** @internal */
|
|
23
|
+
constructor(content, size) {
|
|
24
|
+
this.content = content;
|
|
25
|
+
this.size = size || 0;
|
|
26
|
+
if (size == null) for (let i = 0; i < content.length; i++) this.size += content[i].nodeSize;
|
|
27
|
+
}
|
|
28
|
+
[Symbol.iterator]() {
|
|
29
|
+
return this.content[Symbol.iterator]();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Invoke a callback for all descendant nodes between the given two
|
|
33
|
+
* positions (relative to start of this fragment). Doesn't descend
|
|
34
|
+
* into a node when the callback returns `false`.
|
|
35
|
+
*/
|
|
36
|
+
nodesBetween(from, to, f, nodeStart = 0, parent) {
|
|
37
|
+
const parentOption = parent ? Option.some(parent) : Option.none();
|
|
38
|
+
for (let i = 0, pos = 0; pos < to; i++) {
|
|
39
|
+
const child = this.content[i], end = pos + child.nodeSize;
|
|
40
|
+
if (end > from && f(child, nodeStart + pos, parentOption, i) !== false && child.content.size) {
|
|
41
|
+
const start = pos + 1;
|
|
42
|
+
child.nodesBetween(Math.max(0, from - start), Math.min(child.content.size, to - start), f, nodeStart + start);
|
|
43
|
+
}
|
|
44
|
+
pos = end;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Call the given callback for every descendant node. `pos` will be
|
|
49
|
+
* relative to the start of the fragment. The callback may return
|
|
50
|
+
* `false` to prevent traversal of a given node's children.
|
|
51
|
+
*/
|
|
52
|
+
descendants(f) {
|
|
53
|
+
this.nodesBetween(0, this.size, f);
|
|
54
|
+
}
|
|
55
|
+
/** Extract the text between `from` and `to`. See `Node.textBetween`. */
|
|
56
|
+
textBetween(from, to, blockSeparator, leafText) {
|
|
57
|
+
let text = "", first = true;
|
|
58
|
+
this.nodesBetween(from, to, (node, pos) => {
|
|
59
|
+
const nodeText = node.isText ? node.text.slice(Math.max(from, pos) - pos, to - pos) : !node.isLeaf ? "" : leafText ? typeof leafText === "function" ? leafText(node) : leafText : node.type.spec.leafText ? node.type.spec.leafText(node) : "";
|
|
60
|
+
if (node.isBlock && (node.isLeaf && nodeText || node.isTextblock) && blockSeparator) {
|
|
61
|
+
if (first) first = false;
|
|
62
|
+
else text += blockSeparator;
|
|
63
|
+
}
|
|
64
|
+
text += nodeText;
|
|
65
|
+
}, 0);
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
/** Create a new fragment containing the combined content of this fragment and the other. */
|
|
69
|
+
append(other) {
|
|
70
|
+
if (!other.size) return this;
|
|
71
|
+
if (!this.size) return other;
|
|
72
|
+
const last = this.content[this.content.length - 1], first = other.content[0];
|
|
73
|
+
const content = this.content.slice();
|
|
74
|
+
let i = 0;
|
|
75
|
+
if (last.isText && last.sameMarkup(first)) {
|
|
76
|
+
content[content.length - 1] = last.withText(last.text + first.text);
|
|
77
|
+
i = 1;
|
|
78
|
+
}
|
|
79
|
+
for (; i < other.content.length; i++) content.push(other.content[i]);
|
|
80
|
+
return new Fragment(content, this.size + other.size);
|
|
81
|
+
}
|
|
82
|
+
/** Cut out the sub-fragment between the two given positions. */
|
|
83
|
+
cut(from, to = this.size) {
|
|
84
|
+
if (from === 0 && to === this.size) return this;
|
|
85
|
+
const result = [];
|
|
86
|
+
let size = 0;
|
|
87
|
+
if (to > from) for (let i = 0, pos = 0; pos < to; i++) {
|
|
88
|
+
let child = this.content[i];
|
|
89
|
+
const end = pos + child.nodeSize;
|
|
90
|
+
if (end > from) {
|
|
91
|
+
if (pos < from || end > to) {
|
|
92
|
+
if (child.isText) child = child.cut(Math.max(0, from - pos), Math.min(child.text.length, to - pos));
|
|
93
|
+
else child = child.cut(Math.max(0, from - pos - 1), Math.min(child.content.size, to - pos - 1));
|
|
94
|
+
}
|
|
95
|
+
result.push(child);
|
|
96
|
+
size += child.nodeSize;
|
|
97
|
+
}
|
|
98
|
+
pos = end;
|
|
99
|
+
}
|
|
100
|
+
return new Fragment(result, size);
|
|
101
|
+
}
|
|
102
|
+
/** @internal */
|
|
103
|
+
cutByIndex(from, to) {
|
|
104
|
+
if (from === to) return Fragment.empty;
|
|
105
|
+
if (from === 0 && to === this.content.length) return this;
|
|
106
|
+
return new Fragment(this.content.slice(from, to));
|
|
107
|
+
}
|
|
108
|
+
/** Create a new fragment in which the node at the given index is replaced by the given node. */
|
|
109
|
+
replaceChild(index, node) {
|
|
110
|
+
const current = this.content[index];
|
|
111
|
+
if (current === node) return this;
|
|
112
|
+
const copy = this.content.slice();
|
|
113
|
+
const size = this.size + node.nodeSize - current.nodeSize;
|
|
114
|
+
copy[index] = node;
|
|
115
|
+
return new Fragment(copy, size);
|
|
116
|
+
}
|
|
117
|
+
/** Create a new fragment by prepending the given node to this fragment. */
|
|
118
|
+
addToStart(node) {
|
|
119
|
+
return new Fragment([node].concat(this.content), this.size + node.nodeSize);
|
|
120
|
+
}
|
|
121
|
+
/** Create a new fragment by appending the given node to this fragment. */
|
|
122
|
+
addToEnd(node) {
|
|
123
|
+
return new Fragment(this.content.concat(node), this.size + node.nodeSize);
|
|
124
|
+
}
|
|
125
|
+
/** Compare this fragment to another one. */
|
|
126
|
+
eq(other) {
|
|
127
|
+
if (this.content.length !== other.content.length) return false;
|
|
128
|
+
for (let i = 0; i < this.content.length; i++) if (!this.content[i].eq(other.content[i])) return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
/** @internal */
|
|
132
|
+
[Equal.symbol](that) {
|
|
133
|
+
return that instanceof Fragment && this.eq(that);
|
|
134
|
+
}
|
|
135
|
+
/** @internal */
|
|
136
|
+
[Hash.symbol]() {
|
|
137
|
+
return Hash.array(this.content);
|
|
138
|
+
}
|
|
139
|
+
/** The first child of the fragment, if any. */
|
|
140
|
+
get firstChild() {
|
|
141
|
+
return Option.fromUndefinedOr(this.content[0]);
|
|
142
|
+
}
|
|
143
|
+
/** The last child of the fragment, if any. */
|
|
144
|
+
get lastChild() {
|
|
145
|
+
return Option.fromUndefinedOr(this.content[this.content.length - 1]);
|
|
146
|
+
}
|
|
147
|
+
/** The number of child nodes in this fragment. */
|
|
148
|
+
get childCount() {
|
|
149
|
+
return this.content.length;
|
|
150
|
+
}
|
|
151
|
+
/** Get the child node at the given index. Fails with a `PositionError` when the index is out of range. */
|
|
152
|
+
child(index) {
|
|
153
|
+
return attempt(() => this.childUnsafe(index), isPositionError);
|
|
154
|
+
}
|
|
155
|
+
/** Like `child`, but throws a `PositionError` when the index is out of range. */
|
|
156
|
+
childUnsafe(index) {
|
|
157
|
+
const found = this.content[index];
|
|
158
|
+
if (!found) throw new PositionError({ message: "Index " + index + " out of range for " + this });
|
|
159
|
+
return found;
|
|
160
|
+
}
|
|
161
|
+
/** Get the child node at the given index, if it exists. */
|
|
162
|
+
maybeChild(index) {
|
|
163
|
+
return Option.fromUndefinedOr(this.content[index]);
|
|
164
|
+
}
|
|
165
|
+
/** Call `f` for every child node, passing the node, its offset into this parent node, and its index. */
|
|
166
|
+
forEach(f) {
|
|
167
|
+
for (let i = 0, p = 0; i < this.content.length; i++) {
|
|
168
|
+
const child = this.content[i];
|
|
169
|
+
f(child, p, i);
|
|
170
|
+
p += child.nodeSize;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Find the first position at which this fragment and another fragment differ, if any. */
|
|
174
|
+
findDiffStart(other, pos = 0) {
|
|
175
|
+
return Option.fromNullishOr(findDiffStart(this, other, pos));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Find the first position, searching from the end, at which this
|
|
179
|
+
* fragment and the given fragment differ, if any. Since this
|
|
180
|
+
* position will not be the same in both nodes, an object with two
|
|
181
|
+
* separate positions is returned.
|
|
182
|
+
*/
|
|
183
|
+
findDiffEnd(other, pos = this.size, otherPos = other.size) {
|
|
184
|
+
return Option.fromNullishOr(findDiffEnd(this, other, pos, otherPos));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Find the index and inner offset corresponding to a given relative
|
|
188
|
+
* position in this fragment. The result object will be reused
|
|
189
|
+
* (overwritten) the next time the function is called.
|
|
190
|
+
*
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
findIndex(pos) {
|
|
194
|
+
if (pos === 0) return retIndex(0, pos);
|
|
195
|
+
if (pos === this.size) return retIndex(this.content.length, pos);
|
|
196
|
+
if (pos > this.size || pos < 0) throw new PositionError({ message: `Position ${pos} outside of fragment (${this.toString()})` });
|
|
197
|
+
for (let i = 0, curPos = 0;; i++) {
|
|
198
|
+
const cur = this.content[i], end = curPos + cur.nodeSize;
|
|
199
|
+
if (end >= pos) {
|
|
200
|
+
if (end === pos) return retIndex(i + 1, end);
|
|
201
|
+
return retIndex(i, curPos);
|
|
202
|
+
}
|
|
203
|
+
curPos = end;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Return a debugging string that describes this fragment. */
|
|
207
|
+
toString() {
|
|
208
|
+
return "<" + this.toStringInner() + ">";
|
|
209
|
+
}
|
|
210
|
+
/** @internal */
|
|
211
|
+
toStringInner() {
|
|
212
|
+
return this.content.join(", ");
|
|
213
|
+
}
|
|
214
|
+
/** Create a JSON-serializable representation of this fragment. */
|
|
215
|
+
toJSON() {
|
|
216
|
+
return this.content.length ? this.content.map((n) => n.toJSON()) : null;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Build a fragment from an array of nodes. Ensures that adjacent text
|
|
220
|
+
* nodes with the same marks are joined together.
|
|
221
|
+
*/
|
|
222
|
+
static fromArray(array) {
|
|
223
|
+
if (!array.length) return Fragment.empty;
|
|
224
|
+
let joined;
|
|
225
|
+
let size = 0;
|
|
226
|
+
for (let i = 0; i < array.length; i++) {
|
|
227
|
+
const node = array[i];
|
|
228
|
+
size += node.nodeSize;
|
|
229
|
+
if (i && node.isText && array[i - 1].sameMarkup(node)) {
|
|
230
|
+
if (!joined) joined = array.slice(0, i);
|
|
231
|
+
joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text);
|
|
232
|
+
} else if (joined) joined.push(node);
|
|
233
|
+
}
|
|
234
|
+
return new Fragment(joined || array, size);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Create a fragment from something that can be interpreted as a set
|
|
238
|
+
* of nodes. For `null`/`undefined`, it returns the empty fragment.
|
|
239
|
+
* For a fragment, the fragment itself. For a node or array of nodes,
|
|
240
|
+
* a fragment containing those nodes.
|
|
241
|
+
*/
|
|
242
|
+
static from(nodes) {
|
|
243
|
+
if (!nodes) return Fragment.empty;
|
|
244
|
+
if (nodes instanceof Fragment) return nodes;
|
|
245
|
+
if (Array.isArray(nodes)) return this.fromArray(nodes);
|
|
246
|
+
if (nodes.attrs) return new Fragment([nodes], nodes.nodeSize);
|
|
247
|
+
throw new PositionError({ message: "Can not convert " + nodes + " to a Fragment" });
|
|
248
|
+
}
|
|
249
|
+
static {
|
|
250
|
+
this.empty = new Fragment([], 0);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const found = {
|
|
254
|
+
index: 0,
|
|
255
|
+
offset: 0
|
|
256
|
+
};
|
|
257
|
+
function retIndex(index, offset) {
|
|
258
|
+
found.index = index;
|
|
259
|
+
found.offset = offset;
|
|
260
|
+
return found;
|
|
261
|
+
}
|
|
262
|
+
export { Fragment };
|