creo-edit 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 +21 -0
- package/README.md +169 -0
- package/dist/clipboard/drop.d.ts +17 -0
- package/dist/clipboard/htmlParser.d.ts +18 -0
- package/dist/clipboard/htmlSerializer.d.ts +9 -0
- package/dist/commands/imageCommands.d.ts +32 -0
- package/dist/commands/insertCommands.d.ts +33 -0
- package/dist/commands/listCommands.d.ts +19 -0
- package/dist/commands/markCommands.d.ts +21 -0
- package/dist/commands/navigationCommands.d.ts +27 -0
- package/dist/commands/structuralCommands.d.ts +22 -0
- package/dist/commands/textCommands.d.ts +18 -0
- package/dist/controller/history.d.ts +31 -0
- package/dist/controller/navigation.d.ts +18 -0
- package/dist/controller/selection.d.ts +25 -0
- package/dist/controller/wordBoundary.d.ts +25 -0
- package/dist/createEditor.d.ts +252 -0
- package/dist/dom/anchorMap.d.ts +27 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +7833 -0
- package/dist/index.js.map +72 -0
- package/dist/input/keymap.d.ts +47 -0
- package/dist/input/mobile.d.ts +13 -0
- package/dist/input/nativeInput.d.ts +27 -0
- package/dist/markdown/serialize.d.ts +2 -0
- package/dist/model/blockText.d.ts +42 -0
- package/dist/model/cellAccess.d.ts +2 -0
- package/dist/model/doc.d.ts +45 -0
- package/dist/model/fractional.d.ts +57 -0
- package/dist/model/rebalance.d.ts +12 -0
- package/dist/model/types.d.ts +133 -0
- package/dist/plugin/anchorCodec.d.ts +18 -0
- package/dist/plugin/atomic.d.ts +2 -0
- package/dist/plugin/builtin.d.ts +10 -0
- package/dist/plugin/decorations.d.ts +35 -0
- package/dist/plugin/htmlCodec.d.ts +8 -0
- package/dist/plugin/keymapMatch.d.ts +2 -0
- package/dist/plugin/registry.d.ts +29 -0
- package/dist/plugin/runsAt.d.ts +9 -0
- package/dist/plugin/serializeCodec.d.ts +6 -0
- package/dist/plugin/triggers.d.ts +33 -0
- package/dist/plugin/types.d.ts +188 -0
- package/dist/plugins/add-block/index.d.ts +17 -0
- package/dist/plugins/calendar/index.d.ts +7 -0
- package/dist/plugins/calendar/view.d.ts +29 -0
- package/dist/plugins/cells/codecs.d.ts +6 -0
- package/dist/plugins/cells/commands.d.ts +5 -0
- package/dist/plugins/cells/controls.d.ts +3 -0
- package/dist/plugins/cells/htmlCodec.d.ts +6 -0
- package/dist/plugins/cells/index.d.ts +3 -0
- package/dist/plugins/cells/views.d.ts +27 -0
- package/dist/plugins/drag-handle/index.d.ts +6 -0
- package/dist/plugins/infinite-scroll/index.d.ts +44 -0
- package/dist/plugins/md-shortcuts/index.d.ts +2 -0
- package/dist/plugins/search/engine.d.ts +33 -0
- package/dist/plugins/search/highlight.d.ts +13 -0
- package/dist/plugins/search/index.d.ts +5 -0
- package/dist/plugins/search/navigate.d.ts +15 -0
- package/dist/plugins/search/styles.d.ts +1 -0
- package/dist/plugins/search/types.d.ts +73 -0
- package/dist/plugins/search/ui.d.ts +2 -0
- package/dist/plugins/slash/index.d.ts +12 -0
- package/dist/plugins/slash/items.d.ts +21 -0
- package/dist/plugins/slash/menu.d.ts +17 -0
- package/dist/plugins/styles.css +233 -0
- package/dist/render/DocView.d.ts +7 -0
- package/dist/render/InlineRunsView.d.ts +7 -0
- package/dist/render/blocks/CodeBlockView.d.ts +7 -0
- package/dist/render/blocks/HeadingView.d.ts +7 -0
- package/dist/render/blocks/ImageView.d.ts +8 -0
- package/dist/render/blocks/ListItemView.d.ts +7 -0
- package/dist/render/blocks/ParagraphView.d.ts +7 -0
- package/dist/virtual/VirtualDoc.d.ts +28 -0
- package/dist/virtual/heightIndex.d.ts +36 -0
- package/package.json +53 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { PublicView, Store } from "creo";
|
|
2
|
+
import type { Anchor, Block, BlockId, BlockSpec, DocState, InlineRun, Selection } from "../model/types";
|
|
3
|
+
export type RunsCtx = {
|
|
4
|
+
runs: InlineRun[];
|
|
5
|
+
setRuns: (newRuns: InlineRun[]) => Block;
|
|
6
|
+
};
|
|
7
|
+
export type DomPoint = {
|
|
8
|
+
node: Node;
|
|
9
|
+
offset: number;
|
|
10
|
+
};
|
|
11
|
+
export type AnchorCodec = {
|
|
12
|
+
/**
|
|
13
|
+
* Given the outer block element + a DOM hit (node, localOffset) inside it,
|
|
14
|
+
* produce an Anchor. Plugins for tables / columns walk into their cell
|
|
15
|
+
* sub-element and extract row/col from data-cell / data-col before
|
|
16
|
+
* computing the visible-character offset.
|
|
17
|
+
*/
|
|
18
|
+
domToAnchor(blockEl: HTMLElement, hit: Node, localOffset: number): Anchor | null;
|
|
19
|
+
/**
|
|
20
|
+
* Given an Anchor, locate the (DOM node, offset) within the block's
|
|
21
|
+
* mounted DOM. Returns null when the relevant sub-element isn't currently
|
|
22
|
+
* mounted (e.g. virtualized off-screen).
|
|
23
|
+
*/
|
|
24
|
+
anchorToDom(blockEl: HTMLElement, a: Anchor): DomPoint | null;
|
|
25
|
+
/**
|
|
26
|
+
* Return the scope element for IME composition diffing — e.g. the active
|
|
27
|
+
* <td> for a table caret, or the active <div data-col> for columns.
|
|
28
|
+
* Defaults to the block element itself for blocks without sub-scopes.
|
|
29
|
+
*/
|
|
30
|
+
domScope?(blockEl: HTMLElement, a: Anchor): HTMLElement | null;
|
|
31
|
+
};
|
|
32
|
+
export type HtmlParseCtx = {
|
|
33
|
+
/** Active inline marks (b/i/u/s/code) collected from ancestor elements. */
|
|
34
|
+
marks: import("../model/types").Mark[];
|
|
35
|
+
};
|
|
36
|
+
export type HtmlBlockCodec = {
|
|
37
|
+
/**
|
|
38
|
+
* HTML tag names this block claims when parsing. The parser walks fragment
|
|
39
|
+
* children, finds the first registered codec whose `matchHTML` includes
|
|
40
|
+
* the tag, and calls `parseHTML`. Order = plugin registration order, so
|
|
41
|
+
* built-ins should register `table` before generic `<div>` parsers.
|
|
42
|
+
*/
|
|
43
|
+
matchHTML?: string[];
|
|
44
|
+
parseHTML?(el: HTMLElement, ctx: HtmlParseCtx): BlockSpec | null;
|
|
45
|
+
serializeHTML?(b: Block): string;
|
|
46
|
+
};
|
|
47
|
+
export type SerializeCodec = {
|
|
48
|
+
serialize(b: Block): unknown;
|
|
49
|
+
deserialize(s: unknown, id: BlockId): BlockSpec;
|
|
50
|
+
};
|
|
51
|
+
export type BlockDef<B extends Block = Block> = {
|
|
52
|
+
/** Discriminator — must match block.type. */
|
|
53
|
+
type: B["type"];
|
|
54
|
+
/** Creo view rendering the block. Receives the block + a stable key. */
|
|
55
|
+
view: PublicView<{
|
|
56
|
+
block: B;
|
|
57
|
+
key?: string;
|
|
58
|
+
}, void>;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the runs slot at `anchor`. Defaults to "block.runs if present,
|
|
61
|
+
* else null" (covers all text-bearing blocks). Override for blocks with
|
|
62
|
+
* nested cells (table, columns).
|
|
63
|
+
*/
|
|
64
|
+
runsAt?(b: B, a: Anchor): RunsCtx | null;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the block is "text-bearing" — has a top-level `runs: InlineRun[]`
|
|
67
|
+
* field that text commands operate on directly. Inferred from `runsAt`
|
|
68
|
+
* presence at registration time when omitted; defaults to true if the
|
|
69
|
+
* block exposes a `runs` field at runtime.
|
|
70
|
+
*/
|
|
71
|
+
isTextBearing?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* "Atomic" non-editable block — caret can only sit before (side 0) or
|
|
74
|
+
* after (side 1), never inside. Path encoding is `[side]`. The view should
|
|
75
|
+
* render the outer element with `contenteditable="false"` so the browser
|
|
76
|
+
* places the native caret around the block, not inside it. Backspace /
|
|
77
|
+
* Delete on the block deletes the whole block. Implies `isTextBearing:
|
|
78
|
+
* false` and uses `atomicCodec` by default if no `anchorCodec` is given.
|
|
79
|
+
*/
|
|
80
|
+
isAtomic?: boolean;
|
|
81
|
+
/** DOM ↔ anchor mapping. Optional — text-bearing blocks fall back to a
|
|
82
|
+
* shared default that walks visible text by character offset. */
|
|
83
|
+
anchorCodec?: AnchorCodec;
|
|
84
|
+
/** HTML round-trip. Optional — only needed for blocks that survive
|
|
85
|
+
* copy / paste with external apps. */
|
|
86
|
+
htmlCodec?: HtmlBlockCodec;
|
|
87
|
+
/** JSON SerializedBlock round-trip. Required for blocks that should
|
|
88
|
+
* survive `toJSON()` / `setDoc()`. */
|
|
89
|
+
serializeCodec?: SerializeCodec;
|
|
90
|
+
};
|
|
91
|
+
export type CommandCtx = {
|
|
92
|
+
docStore: Store<DocState>;
|
|
93
|
+
selStore: Store<Selection>;
|
|
94
|
+
};
|
|
95
|
+
export type CommandDef<P = unknown> = {
|
|
96
|
+
t: string;
|
|
97
|
+
/** Run the command. Return `false` to signal the command did not apply
|
|
98
|
+
* (e.g. arrow-nav at the table edge); the keymap dispatcher uses this to
|
|
99
|
+
* decide whether to preventDefault. Returning void or true means handled. */
|
|
100
|
+
run(ctx: CommandCtx, payload: P): boolean | void;
|
|
101
|
+
};
|
|
102
|
+
export type KeymapDef = {
|
|
103
|
+
/**
|
|
104
|
+
* Chord string. Modifiers join with "+". The platform-specific Mod token
|
|
105
|
+
* resolves to Cmd on macOS, Ctrl elsewhere. Examples:
|
|
106
|
+
* "Mod+B", "Mod+Shift+S", "Tab", "Shift+Tab", "ArrowLeft".
|
|
107
|
+
*/
|
|
108
|
+
chord: string;
|
|
109
|
+
when?(ctx: CommandCtx): boolean;
|
|
110
|
+
/** Command t + payload to dispatch when chord matches. */
|
|
111
|
+
command: {
|
|
112
|
+
t: string;
|
|
113
|
+
payload?: unknown;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
export type TriggerCtx = {
|
|
117
|
+
/** Anchor of the trigger character that fired the match. */
|
|
118
|
+
at: Anchor;
|
|
119
|
+
docStore: Store<DocState>;
|
|
120
|
+
selStore: Store<Selection>;
|
|
121
|
+
/**
|
|
122
|
+
* Dispatch a command. Accepts the typed `Command` shape (preferred for
|
|
123
|
+
* built-ins so payload fields land in the right place) or the open
|
|
124
|
+
* `{ t: string; payload?: unknown }` shape for plugin commands.
|
|
125
|
+
*
|
|
126
|
+
* Two-arg form `(t, payload)` is sugar for `{ t, payload }`.
|
|
127
|
+
*/
|
|
128
|
+
dispatch(cmd: {
|
|
129
|
+
t: string;
|
|
130
|
+
[k: string]: unknown;
|
|
131
|
+
}): void;
|
|
132
|
+
dispatch(t: string, payload?: unknown): void;
|
|
133
|
+
/** Element where popover UI should anchor. */
|
|
134
|
+
caretRect(): DOMRect | null;
|
|
135
|
+
/**
|
|
136
|
+
* Request the trigger manager to close this trigger. Idempotent. Call
|
|
137
|
+
* after committing the trigger's action so subsequent keystrokes (e.g.
|
|
138
|
+
* Enter to split a block) flow through to the editor instead of being
|
|
139
|
+
* captured by a stale controller.
|
|
140
|
+
*/
|
|
141
|
+
close(): void;
|
|
142
|
+
};
|
|
143
|
+
export type TriggerController = {
|
|
144
|
+
onTextChange?(query: string): void;
|
|
145
|
+
onKey?(e: KeyboardEvent): boolean;
|
|
146
|
+
close(): void;
|
|
147
|
+
};
|
|
148
|
+
export type TriggerDef = {
|
|
149
|
+
/**
|
|
150
|
+
* String prefix or RegExp matched against the most recently inserted
|
|
151
|
+
* characters at the caret. String matches treat the value as a literal
|
|
152
|
+
* trigger char (e.g. "/").
|
|
153
|
+
*/
|
|
154
|
+
match: string | RegExp;
|
|
155
|
+
open(ctx: TriggerCtx): TriggerController | null;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* DecorationManager passed to mount fns so plugins can read state-without-
|
|
159
|
+
* subscribing-to-doc — e.g. "is this block currently hovered?". Decorations
|
|
160
|
+
* MUST NOT subscribe directly to docStore inside mount; that would re-render
|
|
161
|
+
* on every keystroke. Read live state via this handle on pointer events.
|
|
162
|
+
*/
|
|
163
|
+
export type DecorationHandle = {
|
|
164
|
+
hoveredBlock(): import("../model/types").BlockId | null;
|
|
165
|
+
};
|
|
166
|
+
export type DecorationDef = {
|
|
167
|
+
id: string;
|
|
168
|
+
match(b: Block): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Mount the decoration's UI into the supplied `host` element. Return an
|
|
171
|
+
* optional cleanup fn (called on unmount). The decoration's UI is plain
|
|
172
|
+
* DOM — plugins that want a creo subtree create their own creo app
|
|
173
|
+
* inside `mount` and dispose it in the returned cleanup.
|
|
174
|
+
*/
|
|
175
|
+
mount(block: Block, blockEl: HTMLElement, host: HTMLElement, handle: DecorationHandle): (() => void) | void;
|
|
176
|
+
layer: "left" | "right" | "top" | "bottom" | "absolute";
|
|
177
|
+
};
|
|
178
|
+
export type EditorPlugin = {
|
|
179
|
+
name: string;
|
|
180
|
+
blocks?: BlockDef<Block>[];
|
|
181
|
+
commands?: CommandDef<unknown>[];
|
|
182
|
+
keymap?: KeymapDef[];
|
|
183
|
+
triggers?: TriggerDef[];
|
|
184
|
+
decorations?: DecorationDef[];
|
|
185
|
+
/** Tag prefixes whose history snapshots may coalesce. Defaults are
|
|
186
|
+
* ["text:"]; plugins can declare their own (e.g. "myPlugin:typing"). */
|
|
187
|
+
historyCoalescePrefixes?: string[];
|
|
188
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Block } from "../../model/types";
|
|
2
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
3
|
+
import { type SlashItem } from "../slash/items";
|
|
4
|
+
export type AddBlockOptions = {
|
|
5
|
+
/** Show on hover only (default). */
|
|
6
|
+
hoverOnly?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Custom click handler. When provided, replaces the default behavior of
|
|
9
|
+
* opening the picker menu — useful for hosts that want a different UI
|
|
10
|
+
* or that want to insert a fixed block kind without prompting.
|
|
11
|
+
*/
|
|
12
|
+
onClick?: (block: Block, blockEl: HTMLElement) => void;
|
|
13
|
+
/** Replace or extend the items shown in the picker. Defaults to the
|
|
14
|
+
* same set as the slash menu. */
|
|
15
|
+
items?: SlashItem[];
|
|
16
|
+
};
|
|
17
|
+
export declare function addBlockPlugin(opts?: AddBlockOptions): EditorPlugin;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
2
|
+
import type { SlashItem } from "../slash/items";
|
|
3
|
+
/** Slash-menu item — append to your slash items array to expose calendar
|
|
4
|
+
* insertion at the `/` trigger. */
|
|
5
|
+
export declare const calendarSlashItem: SlashItem;
|
|
6
|
+
export declare function calendarPlugin(): EditorPlugin;
|
|
7
|
+
export { CalendarView, DateMarkerView, calendarHelpers } from "./view";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CalendarBlock, DateMarkerBlock } from "../../model/types";
|
|
2
|
+
declare function todayIso(): string;
|
|
3
|
+
declare function formatIso(d: Date): string;
|
|
4
|
+
declare function parseIso(s: string): Date;
|
|
5
|
+
declare function addDays(d: Date, n: number): Date;
|
|
6
|
+
export declare const CalendarView: (props: {
|
|
7
|
+
block: CalendarBlock;
|
|
8
|
+
} & {
|
|
9
|
+
key?: import("creo").Key;
|
|
10
|
+
ref?: import("creo").Ref<void> | undefined;
|
|
11
|
+
}, slot?: import("creo").SlotContent) => void;
|
|
12
|
+
/**
|
|
13
|
+
* DateMarkerView — slim non-editable atomic block that renders a single
|
|
14
|
+
* "Wednesday, 2 Sep." line. Used as a separator block in journal /
|
|
15
|
+
* planner UX where the user writes editable paragraphs between days.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DateMarkerView: (props: {
|
|
18
|
+
block: DateMarkerBlock;
|
|
19
|
+
} & {
|
|
20
|
+
key?: import("creo").Key;
|
|
21
|
+
ref?: import("creo").Ref<void> | undefined;
|
|
22
|
+
}, slot?: import("creo").SlotContent) => void;
|
|
23
|
+
export declare const calendarHelpers: {
|
|
24
|
+
todayIso: typeof todayIso;
|
|
25
|
+
formatIso: typeof formatIso;
|
|
26
|
+
parseIso: typeof parseIso;
|
|
27
|
+
addDays: typeof addDays;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Anchor, ColumnsBlock, TableBlock } from "../../model/types";
|
|
2
|
+
import type { AnchorCodec, RunsCtx } from "../../plugin/types";
|
|
3
|
+
export declare function tableRunsAt(b: TableBlock, a: Anchor): RunsCtx | null;
|
|
4
|
+
export declare const tableAnchorCodec: AnchorCodec;
|
|
5
|
+
export declare function columnsRunsAt(b: ColumnsBlock, a: Anchor): RunsCtx | null;
|
|
6
|
+
export declare const columnsAnchorCodec: AnchorCodec;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DocState, Selection } from "../../model/types";
|
|
2
|
+
import type { CommandDef } from "../../plugin/types";
|
|
3
|
+
export declare function isInTable(doc: DocState, sel: Selection): boolean;
|
|
4
|
+
export declare function isInColumns(doc: DocState, sel: Selection): boolean;
|
|
5
|
+
export declare const tableCommandDefs: CommandDef<unknown>[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Block, BlockSpec, Mark } from "../../model/types";
|
|
2
|
+
export declare function parseTableHTML(el: HTMLElement, ctx: {
|
|
3
|
+
marks: Mark[];
|
|
4
|
+
}): BlockSpec | null;
|
|
5
|
+
export declare function serializeTableHTML(b: Block): string;
|
|
6
|
+
export declare function serializeColumnsHTML(b: Block): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ColumnsBlock, TableBlock } from "../../model/types";
|
|
2
|
+
/**
|
|
3
|
+
* Render a TableBlock as <table><tbody> with keyed rows + cells.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: intermediate primitives (tbody, tr) get FRESH prop objects each
|
|
6
|
+
* render, not the `_` no-props constant. The reconciler skips descent
|
|
7
|
+
* when an intermediate primitive's props are reference-equal to the
|
|
8
|
+
* previous render — using `_` everywhere causes that shortcut to fire
|
|
9
|
+
* and changes inside cells (e.g. typed text) never make it to the DOM.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TableViewPlugin: (props: {
|
|
12
|
+
block: TableBlock;
|
|
13
|
+
} & {
|
|
14
|
+
key?: import("creo").Key;
|
|
15
|
+
ref?: import("creo").Ref<void> | undefined;
|
|
16
|
+
}, slot?: import("creo").SlotContent) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Render a ColumnsBlock as a flex row of equal-width column divs. Each
|
|
19
|
+
* column carries `data-block-id` (so caret-overlay queries find the owner)
|
|
20
|
+
* AND `data-col="<index>"` so pointToAnchor can reconstruct the column.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ColumnsViewPlugin: (props: {
|
|
23
|
+
block: ColumnsBlock;
|
|
24
|
+
} & {
|
|
25
|
+
key?: import("creo").Key;
|
|
26
|
+
ref?: import("creo").Ref<void> | undefined;
|
|
27
|
+
}, slot?: import("creo").SlotContent) => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
2
|
+
export type DragHandleOptions = {
|
|
3
|
+
/** Show on hover only (default). When `false`, handles are always visible. */
|
|
4
|
+
hoverOnly?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function dragHandlePlugin(opts?: DragHandleOptions): EditorPlugin;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BlockId, DistOmit, BlockSpec, DocState, Selection } from "../../model/types";
|
|
2
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
3
|
+
type Input = DistOmit<BlockSpec, "id"> & {
|
|
4
|
+
id?: BlockId;
|
|
5
|
+
};
|
|
6
|
+
export type InfiniteScrollEditor = {
|
|
7
|
+
docStore: {
|
|
8
|
+
get: () => DocState;
|
|
9
|
+
subscribe: (fn: () => void) => () => void;
|
|
10
|
+
};
|
|
11
|
+
selStore: {
|
|
12
|
+
get: () => Selection;
|
|
13
|
+
};
|
|
14
|
+
appendBlocks: (specs: Input[]) => BlockId[];
|
|
15
|
+
prependBlocks: (specs: Input[]) => BlockId[];
|
|
16
|
+
};
|
|
17
|
+
export type InfiniteScrollOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* The scrolling element to watch. Either an element, or a getter (the
|
|
20
|
+
* element may not exist at editor-mount time). When omitted, the
|
|
21
|
+
* plugin walks up from the editor root looking for the nearest
|
|
22
|
+
* `overflow-y: auto|scroll` ancestor; falls back to `window`.
|
|
23
|
+
*/
|
|
24
|
+
scrollContainer?: HTMLElement | (() => HTMLElement | null);
|
|
25
|
+
/** Called when the user scrolls within `threshold` of the bottom. */
|
|
26
|
+
loadAfter?: (editor: InfiniteScrollEditor) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Called when the user scrolls within `threshold` of the top. The
|
|
29
|
+
* plugin re-anchors `scrollTop` after the load so the viewport doesn't
|
|
30
|
+
* jump.
|
|
31
|
+
*/
|
|
32
|
+
loadBefore?: (editor: InfiniteScrollEditor) => void;
|
|
33
|
+
/** Pixel distance from an edge that triggers a load. Default: 240. */
|
|
34
|
+
threshold?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Minimum gap between successive triggers in the SAME direction (ms).
|
|
37
|
+
* Prevents back-to-back appends while the user is mid-flick. Default
|
|
38
|
+
* 60ms — short enough to feel responsive while letting the renderer
|
|
39
|
+
* commit a frame in between.
|
|
40
|
+
*/
|
|
41
|
+
cooldownMs?: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function infiniteScrollPlugin(opts: InfiniteScrollOptions): EditorPlugin;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Anchor, Block, BlockId, DocState } from "../../model/types";
|
|
2
|
+
export type SearchMatch = {
|
|
3
|
+
blockId: BlockId;
|
|
4
|
+
start: Anchor;
|
|
5
|
+
end: Anchor;
|
|
6
|
+
/** Slice of the matched text — used for backend results that don't
|
|
7
|
+
* resolve to a live block; engine results include it for parity. */
|
|
8
|
+
snippet?: string;
|
|
9
|
+
};
|
|
10
|
+
export type SearchOpts = {
|
|
11
|
+
caseSensitive: boolean;
|
|
12
|
+
wholeWord: boolean;
|
|
13
|
+
regex: boolean;
|
|
14
|
+
};
|
|
15
|
+
type Slot = {
|
|
16
|
+
text: string;
|
|
17
|
+
/** Anchor path prefix this slot belongs to. Concatenated with charOffset
|
|
18
|
+
* to form the final anchor path. */
|
|
19
|
+
prefix: number[];
|
|
20
|
+
};
|
|
21
|
+
/** Yield every searchable text slot for a block, with the anchor prefix
|
|
22
|
+
* needed to address positions inside it. Skips non-text blocks (img,
|
|
23
|
+
* calendar, date-marker). */
|
|
24
|
+
export declare function slotsOf(block: Block): Generator<Slot>;
|
|
25
|
+
/** Build a sticky-global regex for the query + options. Throws if the user
|
|
26
|
+
* supplied an invalid `regex: true` pattern — caller should catch and show
|
|
27
|
+
* a UI error. */
|
|
28
|
+
export declare function buildMatcher(query: string, opts: SearchOpts): RegExp;
|
|
29
|
+
/** Scan every block in the doc and return all matches, in document order. */
|
|
30
|
+
export declare function searchDoc(doc: DocState, query: string, opts: SearchOpts): SearchMatch[];
|
|
31
|
+
/** Stable key for dedupe across re-scans. */
|
|
32
|
+
export declare function matchKey(m: SearchMatch): string;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SearchMatch } from "./engine";
|
|
2
|
+
export declare const HL_ALL = "creo-search";
|
|
3
|
+
export declare const HL_CURRENT = "creo-search-current";
|
|
4
|
+
export declare function isHighlightApiSupported(): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Sync the named highlights with the current match list and active index.
|
|
7
|
+
*
|
|
8
|
+
* Returns the number of matches that landed in DOM (i.e. mounted). The
|
|
9
|
+
* caller can use it for diagnostics — e.g. if `0 < total`, some matches
|
|
10
|
+
* are off-screen and waiting on virtualization to mount their blocks.
|
|
11
|
+
*/
|
|
12
|
+
export declare function paintHighlights(root: HTMLElement, matches: readonly SearchMatch[], activeIndex: number): number;
|
|
13
|
+
export declare function clearHighlights(): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
2
|
+
import type { SearchOptions } from "./types";
|
|
3
|
+
export type { SearchController, SearchOptions, SearchSource, SearchState, SearchToggle, } from "./types";
|
|
4
|
+
export type { SearchMatch, SearchOpts } from "./engine";
|
|
5
|
+
export declare function searchPlugin(opts?: SearchOptions): EditorPlugin;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BlockId, DocState } from "../../model/types";
|
|
2
|
+
import type { SearchMatch } from "./engine";
|
|
3
|
+
import type { SearchSource } from "./types";
|
|
4
|
+
export type EditorScrollHandle = {
|
|
5
|
+
docStore: {
|
|
6
|
+
get(): DocState;
|
|
7
|
+
};
|
|
8
|
+
scrollToBlock(blockId: BlockId, opts?: {
|
|
9
|
+
block?: "start" | "center" | "end" | "nearest";
|
|
10
|
+
behavior?: ScrollBehavior;
|
|
11
|
+
}): void;
|
|
12
|
+
};
|
|
13
|
+
export declare function jumpToMatch(editor: EditorScrollHandle, match: SearchMatch, source?: SearchSource): Promise<void>;
|
|
14
|
+
export declare function nextIndex(current: number, total: number): number;
|
|
15
|
+
export declare function prevIndex(current: number, total: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureStylesInjected(): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { BlockId } from "../../model/types";
|
|
2
|
+
import type { SearchMatch, SearchOpts } from "./engine";
|
|
3
|
+
export type { SearchMatch, SearchOpts } from "./engine";
|
|
4
|
+
export type SearchToggle = "caseSensitive" | "wholeWord" | "regex";
|
|
5
|
+
export type SearchSource = {
|
|
6
|
+
/** Replace (or augment) the in-doc scan. Async OK. */
|
|
7
|
+
search(query: string, opts: SearchOpts): SearchMatch[] | Promise<SearchMatch[]>;
|
|
8
|
+
/** Called before jump-to-match for a blockId not yet in docStore. The
|
|
9
|
+
* host should load the chunk that contains the block and resolve when
|
|
10
|
+
* the block lands in docStore. */
|
|
11
|
+
ensureLoaded?(blockId: BlockId): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export type SearchToggleOpt = {
|
|
14
|
+
initial?: boolean;
|
|
15
|
+
show?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type SearchOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* When true, the plugin claims `Mod+F` and prevents the browser's find
|
|
20
|
+
* UI from opening. Default: `false` — opt-in to avoid surprising users
|
|
21
|
+
* who expect native Cmd+F.
|
|
22
|
+
*/
|
|
23
|
+
interceptBrowserFind?: boolean;
|
|
24
|
+
/** Initial values + UI visibility for each toggle. Omitted toggles use
|
|
25
|
+
* `{ initial: false, show: false }`. */
|
|
26
|
+
toggles?: {
|
|
27
|
+
caseSensitive?: SearchToggleOpt;
|
|
28
|
+
wholeWord?: SearchToggleOpt;
|
|
29
|
+
regex?: SearchToggleOpt;
|
|
30
|
+
};
|
|
31
|
+
/** Backend search source (for infinite-scroll). When omitted, the
|
|
32
|
+
* plugin scans `docStore` only. */
|
|
33
|
+
source?: SearchSource;
|
|
34
|
+
/**
|
|
35
|
+
* Render the UI yourself. When provided, the default panel is NOT
|
|
36
|
+
* mounted — the callback gets a controller and a host element to
|
|
37
|
+
* render whatever it wants. Return a cleanup fn called on plugin
|
|
38
|
+
* teardown.
|
|
39
|
+
*/
|
|
40
|
+
renderUI?: (controller: SearchController, host: HTMLElement) => () => void;
|
|
41
|
+
/** Debounce (ms) on input change. Default: 80. */
|
|
42
|
+
debounceMs?: number;
|
|
43
|
+
};
|
|
44
|
+
export type SearchState = {
|
|
45
|
+
isOpen: boolean;
|
|
46
|
+
query: string;
|
|
47
|
+
caseSensitive: boolean;
|
|
48
|
+
wholeWord: boolean;
|
|
49
|
+
regex: boolean;
|
|
50
|
+
matches: readonly SearchMatch[];
|
|
51
|
+
activeIndex: number;
|
|
52
|
+
/** Last engine error (e.g. invalid regex) — UI shows it inline. */
|
|
53
|
+
error: string | null;
|
|
54
|
+
};
|
|
55
|
+
export type SearchController = {
|
|
56
|
+
open(): void;
|
|
57
|
+
close(): void;
|
|
58
|
+
toggleOpen(): void;
|
|
59
|
+
isOpen(): boolean;
|
|
60
|
+
setQuery(q: string): void;
|
|
61
|
+
query(): string;
|
|
62
|
+
setToggle(t: SearchToggle, v: boolean): void;
|
|
63
|
+
toggle(t: SearchToggle): boolean;
|
|
64
|
+
matches(): readonly SearchMatch[];
|
|
65
|
+
activeIndex(): number;
|
|
66
|
+
setActiveIndex(i: number): void;
|
|
67
|
+
next(): void;
|
|
68
|
+
prev(): void;
|
|
69
|
+
/** Subscribe to state changes; returns unsubscribe. */
|
|
70
|
+
subscribe(fn: () => void): () => void;
|
|
71
|
+
/** Snapshot the full state. */
|
|
72
|
+
state(): SearchState;
|
|
73
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EditorPlugin } from "../../plugin/types";
|
|
2
|
+
import { type SlashItem } from "./items";
|
|
3
|
+
export type SlashOptions = {
|
|
4
|
+
/** Replace or extend the default item list. */
|
|
5
|
+
items?: SlashItem[];
|
|
6
|
+
/** Custom filter — defaults to case-insensitive substring on title+keywords. */
|
|
7
|
+
filter?: (items: SlashItem[], query: string) => SlashItem[];
|
|
8
|
+
};
|
|
9
|
+
export declare function slashCommandsPlugin(opts?: SlashOptions): EditorPlugin;
|
|
10
|
+
export type { SlashItem } from "./items";
|
|
11
|
+
export { defaultSlashItems, defaultFilter } from "./items";
|
|
12
|
+
export { mountSlashMenu, type MenuHandle, type MenuOptions } from "./menu";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CommandCtx } from "../../plugin/types";
|
|
2
|
+
import type { DispatchableCommand } from "../../createEditor";
|
|
3
|
+
export type SlashItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Lowercase keywords used by the default fuzzy filter. */
|
|
8
|
+
keywords?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Run the action. The slash menu has already removed the trigger char(s)
|
|
11
|
+
* from the document by the time this fires, and the caret is back where
|
|
12
|
+
* the trigger was — this fn just dispatches whatever should happen.
|
|
13
|
+
*/
|
|
14
|
+
run(ctx: CommandCtx & {
|
|
15
|
+
dispatch: (cmd: DispatchableCommand) => void;
|
|
16
|
+
}): void;
|
|
17
|
+
};
|
|
18
|
+
export declare const defaultSlashItems: SlashItem[];
|
|
19
|
+
/** Default fuzzy filter — case-insensitive substring match against title +
|
|
20
|
+
* keywords. Plugins can pass a custom matcher via slashCommandsPlugin opts. */
|
|
21
|
+
export declare function defaultFilter(items: SlashItem[], query: string): SlashItem[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SlashItem } from "./items";
|
|
2
|
+
export type MenuOptions = {
|
|
3
|
+
items: SlashItem[];
|
|
4
|
+
filter?: (items: SlashItem[], query: string) => SlashItem[];
|
|
5
|
+
caretRect: DOMRect | null;
|
|
6
|
+
/** Called when the user picks an item (Enter or click). */
|
|
7
|
+
onPick(item: SlashItem): void;
|
|
8
|
+
/** Called when the user hits Escape, clicks away, or otherwise dismisses. */
|
|
9
|
+
onCancel(): void;
|
|
10
|
+
};
|
|
11
|
+
export type MenuHandle = {
|
|
12
|
+
setQuery(q: string): void;
|
|
13
|
+
/** Returns true if the key was consumed (arrow nav / Enter / Esc). */
|
|
14
|
+
handleKey(e: KeyboardEvent): boolean;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
};
|
|
17
|
+
export declare function mountSlashMenu(opts: MenuOptions): MenuHandle;
|