nuvra 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 +163 -0
- package/dist/components/document-editor.vue.d.ts +82 -0
- package/dist/components/editor-bubble-menus.vue.d.ts +14 -0
- package/dist/components/editor-canvas.vue.d.ts +30 -0
- package/dist/components/editor-color-picker.vue.d.ts +15 -0
- package/dist/components/editor-find-bar.vue.d.ts +24 -0
- package/dist/components/editor-object-overlay.vue.d.ts +10 -0
- package/dist/components/editor-page-setup.vue.d.ts +12 -0
- package/dist/components/editor-status-bar.vue.d.ts +36 -0
- package/dist/components/editor-table-picker.vue.d.ts +16 -0
- package/dist/components/editor-toolbar.vue.d.ts +38 -0
- package/dist/core/engine/blocks.d.ts +37 -0
- package/dist/core/engine/clipboard.d.ts +23 -0
- package/dist/core/engine/dom.d.ts +64 -0
- package/dist/core/engine/editing.d.ts +44 -0
- package/dist/core/engine/engine.d.ts +319 -0
- package/dist/core/engine/history.d.ts +43 -0
- package/dist/core/engine/input-rules.d.ts +17 -0
- package/dist/core/engine/keymap.d.ts +7 -0
- package/dist/core/engine/lists.d.ts +29 -0
- package/dist/core/engine/marks.d.ts +50 -0
- package/dist/core/engine/schema.d.ts +37 -0
- package/dist/core/engine/search.d.ts +65 -0
- package/dist/core/engine/selection.d.ts +30 -0
- package/dist/core/engine/tables.d.ts +59 -0
- package/dist/core/export.d.ts +21 -0
- package/dist/core/labels.d.ts +150 -0
- package/dist/core/page.d.ts +109 -0
- package/dist/core/pagination.d.ts +23 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/ui-state.d.ts +79 -0
- package/dist/editor.vue.d.ts +50 -0
- package/dist/export-cyOnlakM.js +59 -0
- package/dist/export-cyOnlakM.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4900 -0
- package/dist/index.js.map +1 -0
- package/dist/page-CEEleKNI.js +93 -0
- package/dist/page-CEEleKNI.js.map +1 -0
- package/dist/style.css +2 -0
- package/package.json +72 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { type EditorUiState } from '../ui-state';
|
|
2
|
+
import { type HeadingTag, type TextAlign, type TextDirection } from './blocks';
|
|
3
|
+
import { EditorHistory } from './history';
|
|
4
|
+
import { type ListKind } from './lists';
|
|
5
|
+
import { type MarkName, type StyleName } from './marks';
|
|
6
|
+
import { SearchController, type SearchState } from './search';
|
|
7
|
+
import { type TableCell } from './tables';
|
|
8
|
+
/** Settings the host component passes when it creates the engine. */
|
|
9
|
+
interface DocumentEngineOptions {
|
|
10
|
+
/** Initial HTML; it is sanitised before it reaches the DOM. */
|
|
11
|
+
content: string;
|
|
12
|
+
/** Whether the user may change the document. */
|
|
13
|
+
editable: boolean;
|
|
14
|
+
/** Maximum number of characters in the document, or `0` for no limit. */
|
|
15
|
+
maxLength: number;
|
|
16
|
+
/** Returns the placeholder shown while the document is empty; called on every refresh so it follows the locale. */
|
|
17
|
+
placeholder: () => string;
|
|
18
|
+
/** Receives image files pasted or dropped into the document; the selection is already at the target. */
|
|
19
|
+
onImageFiles: (files: File[]) => void;
|
|
20
|
+
}
|
|
21
|
+
/** Events emitted by the engine, mapped to their payloads. */
|
|
22
|
+
interface EngineEvents {
|
|
23
|
+
/** The document content changed. */
|
|
24
|
+
update: undefined;
|
|
25
|
+
/** The selection, pending formatting or anything else shown by the toolbar may have changed. */
|
|
26
|
+
selection: undefined;
|
|
27
|
+
/** The editable root received focus. */
|
|
28
|
+
focus: undefined;
|
|
29
|
+
/** The editable root lost focus. */
|
|
30
|
+
blur: undefined;
|
|
31
|
+
/** An IME composition started (`true`) or ended (`false`). */
|
|
32
|
+
composition: boolean;
|
|
33
|
+
/** The find results or the current match changed. */
|
|
34
|
+
search: SearchState;
|
|
35
|
+
}
|
|
36
|
+
/** Table operations offered by the table menu. */
|
|
37
|
+
export type TableCommand = 'addRowBefore' | 'addRowAfter' | 'deleteRow' | 'addColumnBefore' | 'addColumnAfter' | 'deleteColumn' | 'mergeCells' | 'splitCell' | 'toggleHeaderRow' | 'deleteTable';
|
|
38
|
+
/** Image attributes that can change after insertion; `null` or an empty value removes the attribute. */
|
|
39
|
+
interface ImageAttributes {
|
|
40
|
+
/** Horizontal placement, stored as `data-align`. */
|
|
41
|
+
align?: 'left' | 'center' | 'right';
|
|
42
|
+
/** Alternative text for screen readers. */
|
|
43
|
+
alt?: string | null;
|
|
44
|
+
/** Rendered width in CSS pixels. */
|
|
45
|
+
width?: number | null;
|
|
46
|
+
/** Rendered height in CSS pixels. */
|
|
47
|
+
height?: number | null;
|
|
48
|
+
}
|
|
49
|
+
/** Callback registered with {@link DocumentEngine.on}. */
|
|
50
|
+
type Listener<T> = (payload: T) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Framework-free rich text engine on top of a `contenteditable` element. It owns the browser events, keeps the
|
|
53
|
+
* document well-formed, records undo history and exposes every editing command the toolbar and menus need.
|
|
54
|
+
*/
|
|
55
|
+
export declare class DocumentEngine {
|
|
56
|
+
readonly root: HTMLElement;
|
|
57
|
+
private readonly options;
|
|
58
|
+
/** Undo and redo stacks of document snapshots. */
|
|
59
|
+
readonly history: EditorHistory;
|
|
60
|
+
/** Find and replace over the document text. */
|
|
61
|
+
readonly search: SearchController;
|
|
62
|
+
/** Registered listeners per event. */
|
|
63
|
+
private readonly listeners;
|
|
64
|
+
/** Removes every DOM listener added by the engine. */
|
|
65
|
+
private readonly disposers;
|
|
66
|
+
/** Whether the user may change the document. */
|
|
67
|
+
private editable;
|
|
68
|
+
/** Whether an IME composition is in progress. */
|
|
69
|
+
private composingText;
|
|
70
|
+
/** Last selection inside the document, used when focus is in a toolbar popover. */
|
|
71
|
+
private lastRange;
|
|
72
|
+
/** Formatting chosen at a collapsed caret, applied to the next typed text. */
|
|
73
|
+
private pending;
|
|
74
|
+
/** Time until which selection changes are attributed to the engine itself. */
|
|
75
|
+
private internalSelectionUntil;
|
|
76
|
+
/** Cell where a mouse drag for a cell selection started. */
|
|
77
|
+
private cellAnchor;
|
|
78
|
+
/** Rectangle of selected table cells, when several cells are selected. */
|
|
79
|
+
private cellRect;
|
|
80
|
+
/** Selection being dragged, so a drop inside the document can move instead of copy. */
|
|
81
|
+
private dragRange;
|
|
82
|
+
/** Image selected by a click, shown with resize handles. */
|
|
83
|
+
private selectedImageElement;
|
|
84
|
+
/**
|
|
85
|
+
* Takes over `root` as the editable document, loads the initial content and starts listening to its events.
|
|
86
|
+
*
|
|
87
|
+
* @param root Element that becomes the editable document.
|
|
88
|
+
* @param options Initial content, limits and callbacks.
|
|
89
|
+
*/
|
|
90
|
+
constructor(root: HTMLElement, options: DocumentEngineOptions);
|
|
91
|
+
/**
|
|
92
|
+
* Subscribes to an engine event.
|
|
93
|
+
*
|
|
94
|
+
* @returns Function that removes the listener again.
|
|
95
|
+
*/
|
|
96
|
+
on<K extends keyof EngineEvents>(event: K, listener: Listener<EngineEvents[K]>): () => void;
|
|
97
|
+
/** Removes all DOM listeners, event subscriptions and search highlights. The root element is left in place. */
|
|
98
|
+
destroy(): void;
|
|
99
|
+
/** Calls every listener of an event with its payload. */
|
|
100
|
+
private emit;
|
|
101
|
+
/** Adds a DOM listener that {@link destroy} removes again. */
|
|
102
|
+
private listen;
|
|
103
|
+
/** Whether the document holds nothing but one empty paragraph. */
|
|
104
|
+
get isEmpty(): boolean;
|
|
105
|
+
/** Whether the user may change the document. */
|
|
106
|
+
get isEditable(): boolean;
|
|
107
|
+
/** Whether an IME composition is in progress; layout work should wait until it ends. */
|
|
108
|
+
get composing(): boolean;
|
|
109
|
+
/** Image currently selected by a click, if it is still in the document. */
|
|
110
|
+
get selectedImage(): HTMLImageElement | null;
|
|
111
|
+
/** Whether several table cells are selected with the mouse. */
|
|
112
|
+
get hasCellSelection(): boolean;
|
|
113
|
+
/** Clean HTML of the document, without caret placeholders or other editor-only markup. */
|
|
114
|
+
getHTML(): string;
|
|
115
|
+
/**
|
|
116
|
+
* Replaces the whole document with sanitised HTML.
|
|
117
|
+
*
|
|
118
|
+
* @param html New content; scripts, handlers and unsupported markup are dropped.
|
|
119
|
+
* @param options.addToHistory Record the replacement as an undo step instead of clearing the history.
|
|
120
|
+
* @param options.emitUpdate Emit `update` so the host saves the new content.
|
|
121
|
+
*/
|
|
122
|
+
setContent(html: string, { addToHistory, emitUpdate }?: {
|
|
123
|
+
addToHistory?: boolean | undefined;
|
|
124
|
+
emitUpdate?: boolean | undefined;
|
|
125
|
+
}): void;
|
|
126
|
+
/** Switches between editing and read-only mode. */
|
|
127
|
+
setEditable(editable: boolean): void;
|
|
128
|
+
/** Word and character counts of the document text. */
|
|
129
|
+
getStats(): {
|
|
130
|
+
words: number;
|
|
131
|
+
characters: number;
|
|
132
|
+
};
|
|
133
|
+
/** Snapshot of the formatting at the selection, for the toolbar. */
|
|
134
|
+
getState(): EditorUiState;
|
|
135
|
+
/** Plain text of the current selection. */
|
|
136
|
+
getSelectedText(): string;
|
|
137
|
+
/**
|
|
138
|
+
* Focuses the document.
|
|
139
|
+
*
|
|
140
|
+
* @param position Place the caret at the start or end; without it the last selection is restored.
|
|
141
|
+
*/
|
|
142
|
+
focus(position?: 'start' | 'end'): void;
|
|
143
|
+
/** Marks the next selection changes as caused by the engine, so they keep pending formatting and typing groups. */
|
|
144
|
+
private expectInternalSelection;
|
|
145
|
+
/** Live selection inside the document, or a copy of the last one while focus is elsewhere (e.g. in a popover). */
|
|
146
|
+
private currentRange;
|
|
147
|
+
/** Captures the document markup and selection for the undo history. */
|
|
148
|
+
private snapshot;
|
|
149
|
+
/** Updates the placeholder state and search results, then notifies listeners. */
|
|
150
|
+
private refresh;
|
|
151
|
+
/**
|
|
152
|
+
* Runs one edit as an undo step, then normalises the document and restores the selection by text position.
|
|
153
|
+
*
|
|
154
|
+
* @param edit Changes the document at the given range and describes the resulting selection.
|
|
155
|
+
* @param kind `typing` edits join the running typing group instead of always starting a new undo step.
|
|
156
|
+
* @returns Whether the edit ran and changed something.
|
|
157
|
+
*/
|
|
158
|
+
private exec;
|
|
159
|
+
/** Normalises the document after an edit, restores the selection and notifies listeners. */
|
|
160
|
+
private finishEdit;
|
|
161
|
+
/** Records an undo step for a change that does not depend on the selection (checkboxes, image attributes). */
|
|
162
|
+
private mutate;
|
|
163
|
+
/** Deletes the selected content, if any, and returns a collapsed range where it started. */
|
|
164
|
+
private collapsedAfterDelete;
|
|
165
|
+
/** Deletes the selection as one undo step and leaves a collapsed caret where it started. */
|
|
166
|
+
private deleteSelection;
|
|
167
|
+
/** Number of characters in the document text. */
|
|
168
|
+
private characterCount;
|
|
169
|
+
/** Whether adding `extra` characters would exceed the configured maximum length. */
|
|
170
|
+
private exceedsLimit;
|
|
171
|
+
/** Remembers the selection; a selection moved by the user drops pending formatting and ends the typing group. */
|
|
172
|
+
private onSelectionChange;
|
|
173
|
+
/** Decides for every input whether the browser may apply it natively or the engine performs it instead. */
|
|
174
|
+
private onBeforeInput;
|
|
175
|
+
/**
|
|
176
|
+
* Lets the browser type plain characters itself (fast and IME friendly) and takes over when a selection has to be
|
|
177
|
+
* replaced, pending formatting applies or the caret sits outside a text block.
|
|
178
|
+
*/
|
|
179
|
+
private handleTextInput;
|
|
180
|
+
/** Deletes selections and joins blocks at their edges itself; single characters are deleted natively. */
|
|
181
|
+
private handleDeleteInput;
|
|
182
|
+
/** Repairs the structure after a native edit and applies Markdown and typography rules to typed text. */
|
|
183
|
+
private onInput;
|
|
184
|
+
/** Browsers occasionally leave text outside paragraphs or a <div> after native edits; fix it in place. */
|
|
185
|
+
private repairStructure;
|
|
186
|
+
/** Applies the input rule completed by the typed character as its own undo step. */
|
|
187
|
+
private applyInputRule;
|
|
188
|
+
/** Runs keyboard shortcuts and handles Delete, Tab and Escape for images, cell selections, tables and lists. */
|
|
189
|
+
private onKeyDown;
|
|
190
|
+
/**
|
|
191
|
+
* Tab moves between table cells (appending a row after the last one), nests list items, indents a paragraph at its
|
|
192
|
+
* start and types a tab stop inside a line. Shift+Tab reverses each of these.
|
|
193
|
+
*/
|
|
194
|
+
private handleTab;
|
|
195
|
+
/** Moves the caret to the next or previous cell; moving past the last cell appends a row as an undo step. */
|
|
196
|
+
private moveToSiblingCell;
|
|
197
|
+
/** Runs the command bound to a keyboard shortcut. */
|
|
198
|
+
private runKeyCommand;
|
|
199
|
+
/** Pastes sanitised content; lone images go to the host for upload and a URL over a selection becomes a link. */
|
|
200
|
+
private onPaste;
|
|
201
|
+
/** Inserts clipboard content at the selection; inside a code block only its plain text is used. */
|
|
202
|
+
private insertTransfer;
|
|
203
|
+
/** Puts clean HTML and plain text of the selection on the clipboard. */
|
|
204
|
+
private onCopy;
|
|
205
|
+
/** Copies the selection like {@link onCopy} and deletes it as an undo step. */
|
|
206
|
+
private onCut;
|
|
207
|
+
/** Starts dragging the selection with clean clipboard data and remembers it for a move on drop. */
|
|
208
|
+
private onDragStart;
|
|
209
|
+
/** Forgets the dragged selection once the drag ends anywhere. */
|
|
210
|
+
private onDragEnd;
|
|
211
|
+
/** Allows dropping into an editable document. */
|
|
212
|
+
private onDragOver;
|
|
213
|
+
/**
|
|
214
|
+
* Inserts dropped content at the pointer. A selection dragged within the document is moved (copied with Ctrl) in a
|
|
215
|
+
* single undo step; dropped image files go to the host for upload.
|
|
216
|
+
*/
|
|
217
|
+
private onDrop;
|
|
218
|
+
/** Selects a clicked image, clears a previous cell selection and remembers the cell where a drag may start. */
|
|
219
|
+
private onMouseDown;
|
|
220
|
+
/** Extends a rectangular cell selection while the mouse is dragged across table cells. */
|
|
221
|
+
private onMouseMove;
|
|
222
|
+
/** Toggles task list checkboxes as undo steps and opens links on Ctrl/Cmd+click. */
|
|
223
|
+
private onClick;
|
|
224
|
+
/** Deletes a selection before an IME replaces it and pauses layout work while composing. */
|
|
225
|
+
private onCompositionStart;
|
|
226
|
+
/** Repairs the structure once the IME has committed its text. */
|
|
227
|
+
private onCompositionEnd;
|
|
228
|
+
/** Reverts the last undo step. */
|
|
229
|
+
undo(): void;
|
|
230
|
+
/** Re-applies the last reverted undo step. */
|
|
231
|
+
redo(): void;
|
|
232
|
+
/** Puts a history snapshot back into the document and restores its selection. */
|
|
233
|
+
private restoreSnapshot;
|
|
234
|
+
/** Inserts plain text at the selection, replacing selected content and applying pending formatting. */
|
|
235
|
+
insertText(text: string): void;
|
|
236
|
+
/** Formatting at the start of a range, or `null` when the text there is unformatted. */
|
|
237
|
+
private captureFormat;
|
|
238
|
+
/**
|
|
239
|
+
* Enter splits the block (or leaves an empty list item or quote), Shift+Enter inserts a line break. An empty new
|
|
240
|
+
* line keeps the formatting of the text before it, as in office editors.
|
|
241
|
+
*/
|
|
242
|
+
private splitLine;
|
|
243
|
+
/** Toggles bold, italic and the other marks; at a collapsed caret it applies to the next typed text. */
|
|
244
|
+
toggleMark(mark: MarkName): void;
|
|
245
|
+
/** Sets or removes (`null`) text colour, font family or font size; at a collapsed caret for the next text. */
|
|
246
|
+
setTextStyle(name: StyleName, value: string | null): void;
|
|
247
|
+
/** Sets or removes (`null`) the highlight colour; at a collapsed caret for the next text. */
|
|
248
|
+
setHighlight(color: string | null): void;
|
|
249
|
+
/** Merges a formatting change into the pending format and keeps focus in the document. */
|
|
250
|
+
private setPending;
|
|
251
|
+
/** Removes marks from the selection and turns its blocks into plain paragraphs outside lists and quotes. */
|
|
252
|
+
clearFormatting(): void;
|
|
253
|
+
/** Turns the selected blocks into paragraphs or headings. */
|
|
254
|
+
setBlockType(tag: 'P' | HeadingTag): void;
|
|
255
|
+
/** Wraps the selected blocks in a quote, or unwraps them when they are all quoted already. */
|
|
256
|
+
toggleBlockquote(): void;
|
|
257
|
+
/** Turns the selected blocks into code blocks, or back into paragraphs. */
|
|
258
|
+
toggleCodeBlock(): void;
|
|
259
|
+
/** Aligns the selected paragraphs. */
|
|
260
|
+
setTextAlign(align: TextAlign): void;
|
|
261
|
+
/** Sets or removes (`null`) the line spacing of the selected paragraphs. */
|
|
262
|
+
setLineHeight(lineHeight: string | null): void;
|
|
263
|
+
/** Sets the writing direction of the selected paragraphs. */
|
|
264
|
+
setTextDirection(direction: TextDirection): void;
|
|
265
|
+
/** Turns the selected blocks into a list of the given kind, converts another list kind, or lifts them out. */
|
|
266
|
+
toggleList(kind: ListKind): void;
|
|
267
|
+
/** List items are nested or lifted; other paragraphs get indentation steps. */
|
|
268
|
+
indent(direction: 1 | -1): void;
|
|
269
|
+
/**
|
|
270
|
+
* Inserts a block element at the caret and places the caret after it, or inside it when `caretInside` finds a
|
|
271
|
+
* target (e.g. the first cell of a new table).
|
|
272
|
+
*/
|
|
273
|
+
private insertBlock;
|
|
274
|
+
/** Inserts a horizontal rule. */
|
|
275
|
+
insertHorizontalRule(): void;
|
|
276
|
+
/** Inserts a manual page break. */
|
|
277
|
+
insertPageBreak(): void;
|
|
278
|
+
/** Link that contains the start of the selection. */
|
|
279
|
+
getActiveLink(): HTMLAnchorElement | null;
|
|
280
|
+
/**
|
|
281
|
+
* Links the selection, edits the link under a collapsed caret, or inserts a new linked text. Unsafe URLs are ignored.
|
|
282
|
+
*
|
|
283
|
+
* @param href Link address.
|
|
284
|
+
* @param target `_blank` to open in a new tab, `null` for the same tab.
|
|
285
|
+
* @param text Label inserted when nothing is selected; defaults to the URL.
|
|
286
|
+
*/
|
|
287
|
+
setLink(href: string, target: string | null, text?: string): void;
|
|
288
|
+
/** Removes links from the selection, or the whole link under a collapsed caret. */
|
|
289
|
+
unsetLink(): void;
|
|
290
|
+
/** Inserts a centred image at the caret; unsafe sources are ignored. */
|
|
291
|
+
insertImage(src: string, alt?: string): void;
|
|
292
|
+
/** Selects an image for the resize handles and image menu, or clears the selection with `null`. */
|
|
293
|
+
selectImage(image: HTMLImageElement | null): void;
|
|
294
|
+
/** Changes image attributes as an undo step. */
|
|
295
|
+
updateImage(image: HTMLImageElement, attributes: ImageAttributes): void;
|
|
296
|
+
/** Deletes an image and places the caret in the neighbouring text. */
|
|
297
|
+
removeImage(image: HTMLImageElement): void;
|
|
298
|
+
/** Table cell that contains the start of the selection. */
|
|
299
|
+
getActiveCell(): TableCell | null;
|
|
300
|
+
/** Whether the selected cells form a rectangle that can be merged. */
|
|
301
|
+
canMergeCells(): boolean;
|
|
302
|
+
/** Whether the active cell spans several rows or columns. */
|
|
303
|
+
canSplitCell(): boolean;
|
|
304
|
+
/** Sets a column width in pixels as an undo step, e.g. after dragging a column border. */
|
|
305
|
+
resizeColumn(table: HTMLTableElement, index: number, width: number): void;
|
|
306
|
+
/** Inserts a table and places the caret in its first cell. */
|
|
307
|
+
insertTable(rows: number, cols: number, withHeaderRow: boolean): void;
|
|
308
|
+
/** Runs a table operation on the active cell or the selected cells. */
|
|
309
|
+
tableCommand(command: TableCommand): void;
|
|
310
|
+
/** Ends a rectangular cell selection. */
|
|
311
|
+
private clearCellSelection;
|
|
312
|
+
/** Empties every selected cell as one undo step. */
|
|
313
|
+
private clearSelectedCells;
|
|
314
|
+
/** Replaces the current search match as an undo step. */
|
|
315
|
+
replaceMatch(replacement: string): void;
|
|
316
|
+
/** Replaces every search match as one undo step. */
|
|
317
|
+
replaceAllMatches(replacement: string): void;
|
|
318
|
+
}
|
|
319
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TextBookmark } from './selection';
|
|
2
|
+
/** The document and selection at one point in time. */
|
|
3
|
+
export interface Snapshot {
|
|
4
|
+
/** outerHTML of each top-level block; unchanged blocks share the same string with earlier snapshots. */
|
|
5
|
+
blocks: string[];
|
|
6
|
+
/** Selection to restore together with the blocks, or `null` when the editor had no selection. */
|
|
7
|
+
bookmark: TextBookmark | null;
|
|
8
|
+
}
|
|
9
|
+
/** How a change was made: consecutive typing is grouped into one undo step, commands never are. */
|
|
10
|
+
type ChangeKind = 'typing' | 'command';
|
|
11
|
+
/** Undo/redo stacks of document snapshots with typing grouping and memory sharing between snapshots. */
|
|
12
|
+
export declare class EditorHistory {
|
|
13
|
+
private readonly limit;
|
|
14
|
+
/** States before each change, newest last. */
|
|
15
|
+
private readonly undoStack;
|
|
16
|
+
/** States replaced by undo, newest last; cleared by any new change. */
|
|
17
|
+
private readonly redoStack;
|
|
18
|
+
/** Kind of the last recorded change, `null` when the next keystroke must start a new step. */
|
|
19
|
+
private lastKind;
|
|
20
|
+
/** Time of the last recorded change, used to group typing. */
|
|
21
|
+
private lastTime;
|
|
22
|
+
/** @param limit Maximum number of steps kept on each stack. */
|
|
23
|
+
constructor(limit?: number);
|
|
24
|
+
/** Whether there is a step to undo. */
|
|
25
|
+
get canUndo(): boolean;
|
|
26
|
+
/** Whether there is an undone step to redo. */
|
|
27
|
+
get canRedo(): boolean;
|
|
28
|
+
/** Records the state before a change; `take` runs only when a new undo step actually starts. */
|
|
29
|
+
record(take: () => Snapshot, kind: ChangeKind): void;
|
|
30
|
+
/** Starts a new undo step for the next keystroke, e.g. after the caret was moved. */
|
|
31
|
+
breakGroup(): void;
|
|
32
|
+
/** Moves one step back: stores `current` for redo and returns the state to restore, or `null` when empty. */
|
|
33
|
+
undo(current: Snapshot): Snapshot | null;
|
|
34
|
+
/** Moves one step forward: stores `current` for undo and returns the state to restore, or `null` when empty. */
|
|
35
|
+
redo(current: Snapshot): Snapshot | null;
|
|
36
|
+
/** Forgets every step, e.g. after the content was replaced from outside. */
|
|
37
|
+
clear(): void;
|
|
38
|
+
/** Adds a snapshot to a stack and drops the oldest one when the limit is exceeded. */
|
|
39
|
+
private push;
|
|
40
|
+
/** Reuses identical block strings from the latest snapshot so large documents do not multiply in memory. */
|
|
41
|
+
private share;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Caret } from './editing';
|
|
2
|
+
import { type PendingFormat } from './marks';
|
|
3
|
+
/** What an applied rule leaves behind for the engine. */
|
|
4
|
+
interface InputRuleResult {
|
|
5
|
+
/** New caret position, or `null` to restore the caret by text position. */
|
|
6
|
+
caret: Caret | null;
|
|
7
|
+
/** Formatting for the next typed characters, so text after `**bold**` is not bold. */
|
|
8
|
+
pending?: PendingFormat;
|
|
9
|
+
}
|
|
10
|
+
/** A matched rule; calling it performs the document change. */
|
|
11
|
+
type InputRule = () => InputRuleResult;
|
|
12
|
+
/**
|
|
13
|
+
* Looks for a rule completed by the character that was just typed. The returned function performs the change,
|
|
14
|
+
* so the caller can record an undo step first. Rules never apply inside code.
|
|
15
|
+
*/
|
|
16
|
+
export declare const matchInputRule: (root: HTMLElement, range: Range, typed: string) => InputRule | null;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Editor commands reachable through keyboard shortcuts. */
|
|
2
|
+
export type KeyCommand = 'undo' | 'redo' | 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'subscript' | 'superscript' | 'highlight' | 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignJustify' | 'bulletList' | 'orderedList' | 'taskList' | 'blockquote' | 'codeBlock' | 'pageBreak';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the editor command for a key press, or `null` when it is not a shortcut. Physical key codes are used so
|
|
5
|
+
* shortcuts keep working with Cyrillic and other keyboard layouts; AltGr combinations are left to text input.
|
|
6
|
+
*/
|
|
7
|
+
export declare const matchKeyCommand: (event: KeyboardEvent) => KeyCommand | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The three list types; the names match the toolbar state keys. */
|
|
2
|
+
export type ListKind = 'bulletList' | 'orderedList' | 'taskList';
|
|
3
|
+
/** Kind of a `<ul>`/`<ol>` element; task lists are `<ul data-type="taskList">`. */
|
|
4
|
+
export declare const listKind: (list: Element) => ListKind;
|
|
5
|
+
/** List item containing `node`. */
|
|
6
|
+
export declare const closestListItem: (node: Node, root: HTMLElement) => HTMLElement | null;
|
|
7
|
+
/** Kind of the list the node is in, or `null` outside lists. */
|
|
8
|
+
export declare const activeListKind: (root: HTMLElement, node: Node) => ListKind | null;
|
|
9
|
+
/**
|
|
10
|
+
* Moves an item one level up. From a nested list it becomes an item of the outer list, taking the following
|
|
11
|
+
* siblings as its own sub-list; from a top-level list its content becomes plain blocks between two list halves.
|
|
12
|
+
*/
|
|
13
|
+
export declare const liftListItem: (root: HTMLElement, item: HTMLElement) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Tab / Shift+Tab inside lists: nests or lifts every selected item.
|
|
16
|
+
* @returns whether any item changed level.
|
|
17
|
+
*/
|
|
18
|
+
export declare const changeListIndent: (root: HTMLElement, range: Range, delta: 1 | -1) => boolean;
|
|
19
|
+
/** Lifts the block out of every list it is nested in ("clear formatting"). */
|
|
20
|
+
export declare const liftOutOfLists: (root: HTMLElement, block: HTMLElement) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Toolbar list button: lifts the selection out when it is already in lists of this kind, converts lists of other
|
|
23
|
+
* kinds, and otherwise wraps the selected blocks in a new list joined with same-kind neighbours.
|
|
24
|
+
*/
|
|
25
|
+
export declare const toggleList: (root: HTMLElement, range: Range, kind: ListKind) => void;
|
|
26
|
+
/** Moves `rightBlock` and everything after it in the item body into a new item placed after `item`. */
|
|
27
|
+
export declare const splitListItem: (item: HTMLElement, rightBlock: HTMLElement) => HTMLElement;
|
|
28
|
+
/** Flips a task item's checked state in both the `data-checked` attribute and its checkbox. */
|
|
29
|
+
export declare const toggleTaskItem: (item: HTMLElement) => void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Toggleable inline formatting, each rendered as its own element. */
|
|
2
|
+
export type MarkName = 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'subscript' | 'superscript';
|
|
3
|
+
/** Inline formatting stored as CSS properties on a `<span>`. */
|
|
4
|
+
export type StyleName = 'color' | 'fontFamily' | 'fontSize';
|
|
5
|
+
/** Formatting chosen while the caret is collapsed; applied to the next typed text. */
|
|
6
|
+
export interface PendingFormat {
|
|
7
|
+
/** Marks forced on (`true`) or off (`false`); missing marks follow the surrounding text. */
|
|
8
|
+
marks: Partial<Record<MarkName, boolean>>;
|
|
9
|
+
/** Span styles to apply; `null` removes the style, missing styles follow the surrounding text. */
|
|
10
|
+
styles: Partial<Record<StyleName, string | null>>;
|
|
11
|
+
/** Highlight colour; `null` removes the highlight, `undefined` follows the surrounding text. */
|
|
12
|
+
highlight?: string | null;
|
|
13
|
+
}
|
|
14
|
+
/** Colour used when a highlight is applied without choosing one, e.g. by `==text==` or Mod+Shift+H. */
|
|
15
|
+
export declare const DEFAULT_HIGHLIGHT_COLOR = "#fef08a";
|
|
16
|
+
/** Link containing `node`; links may span several blocks' worth of inline markup, so the search stops at `root`. */
|
|
17
|
+
export declare const linkAncestor: (node: Node, root: HTMLElement) => HTMLAnchorElement | null;
|
|
18
|
+
/** Text nodes with at least one selected character, skipping code blocks and text outside blocks. */
|
|
19
|
+
export declare const selectedTextNodes: (root: HTMLElement, range: Range) => Text[];
|
|
20
|
+
/** Whether the mark applies at the caret, or to every selected character of a non-collapsed range. */
|
|
21
|
+
export declare const isMarkActive: (root: HTMLElement, range: Range, mark: MarkName) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Removes the mark when the whole selection already has it, otherwise adds it to every selected character.
|
|
24
|
+
* Subscript and superscript exclude each other.
|
|
25
|
+
*/
|
|
26
|
+
export declare const toggleMark: (root: HTMLElement, range: Range, mark: MarkName) => void;
|
|
27
|
+
/** Sets (or with `null` removes) a span style on the selected text, reusing spans that already carry it. */
|
|
28
|
+
export declare const setTextStyle: (root: HTMLElement, range: Range, name: StyleName, value: string | null) => void;
|
|
29
|
+
/** Highlights the selected text with a colour, or removes the highlight with `null`. */
|
|
30
|
+
export declare const setHighlight: (root: HTMLElement, range: Range, color: string | null) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Turns the selected text into a link, replacing any link it was part of.
|
|
33
|
+
* @returns whether any text was selected.
|
|
34
|
+
*/
|
|
35
|
+
export declare const setLink: (root: HTMLElement, range: Range, href: string, target: string | null) => boolean;
|
|
36
|
+
/** Removes links from the selected text, keeping the text itself. */
|
|
37
|
+
export declare const unsetLink: (root: HTMLElement, range: Range) => void;
|
|
38
|
+
/** Removes every mark, span style, highlight and link from the selected text. */
|
|
39
|
+
export declare const clearMarks: (root: HTMLElement, range: Range) => void;
|
|
40
|
+
/** Which marks apply to `node`. */
|
|
41
|
+
export declare const readMarks: (root: HTMLElement, node: Node) => Record<MarkName, boolean>;
|
|
42
|
+
/** Value of a span style at `node`, or an empty string when none is set. */
|
|
43
|
+
export declare const readStyle: (root: HTMLElement, node: Node, name: StyleName) => string;
|
|
44
|
+
/** Highlight colour at `node`, the default colour for a `<mark>` without one, or an empty string. */
|
|
45
|
+
export declare const readHighlight: (root: HTMLElement, node: Node) => string;
|
|
46
|
+
/**
|
|
47
|
+
* Inserts text at a collapsed caret with formatting that differs from its surroundings: the inline elements
|
|
48
|
+
* around the caret are split and the text is wrapped in exactly the requested marks.
|
|
49
|
+
*/
|
|
50
|
+
export declare const insertFormattedText: (root: HTMLElement, range: Range, text: string, format: PendingFormat) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Attribute pagination writes on blocks it moved to the next sheet; editor-only. */
|
|
2
|
+
export declare const GAP_ATTRIBUTE = "data-doc-gap";
|
|
3
|
+
/** Class marking table cells in a drag selection; editor-only. */
|
|
4
|
+
export declare const SELECTED_CELL_CLASS = "doc-cell-selected";
|
|
5
|
+
/** Whether the node is a block in the editor model. */
|
|
6
|
+
export declare const isBlockNode: (node: Node | null | undefined) => node is HTMLElement;
|
|
7
|
+
/** Sets a paragraph's indentation level as `data-indent` plus the matching left margin, removing both at level 0. */
|
|
8
|
+
export declare const setIndent: (block: HTMLElement, indent: number) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the trimmed URL when it is safe to put in a link (`http(s)`, `mailto`, `tel` or relative) or an image
|
|
11
|
+
* (`http(s)`, `data:image`, `blob` or relative), otherwise `null`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const sanitizeUrl: (value: string | null, image?: boolean) => string | null;
|
|
14
|
+
/** The element holding a list item's blocks: the `<div>` of a task item, the `<li>` itself otherwise. */
|
|
15
|
+
export declare const listItemBody: (item: HTMLElement) => HTMLElement;
|
|
16
|
+
/**
|
|
17
|
+
* Gives a list item the task structure (non-editable checkbox label plus `<div>` body) with the checkbox mirroring
|
|
18
|
+
* `data-checked`, moves any stray content into the body and returns the body.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ensureTaskItem: (item: HTMLElement) => HTMLElement;
|
|
21
|
+
/**
|
|
22
|
+
* Makes a block container (document, quote, list item body, table cell) hold only well-formed blocks: stray wrappers
|
|
23
|
+
* are unwrapped, runs of inline content become paragraphs and every block is normalised. The document root also
|
|
24
|
+
* always ends with a paragraph, so the caret can be placed after tables, images and rules.
|
|
25
|
+
*/
|
|
26
|
+
export declare const normalizeContainer: (container: ParentNode & Node, isRoot?: boolean) => void;
|
|
27
|
+
/** Parses untrusted HTML without executing it and returns editor-ready blocks. */
|
|
28
|
+
export declare const sanitizeHtml: (html: string) => DocumentFragment;
|
|
29
|
+
/**
|
|
30
|
+
* Removes editor-only markup (pagination gaps, cell selection). With `forExport` it also drops caret placeholders
|
|
31
|
+
* and editing attributes, which history snapshots keep so the restored DOM is immediately editable.
|
|
32
|
+
*/
|
|
33
|
+
export declare const cleanEditorArtifacts: (scope: Element | DocumentFragment, forExport: boolean) => void;
|
|
34
|
+
/** Clean HTML of the editor content, as stored in the model and exported. */
|
|
35
|
+
export declare const serialize: (root: HTMLElement) => string;
|
|
36
|
+
/** Whether the document is a single unformatted, empty paragraph. */
|
|
37
|
+
export declare const isEmptyDocument: (root: HTMLElement) => boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Matching options of the find bar. */
|
|
2
|
+
interface SearchOptions {
|
|
3
|
+
/** Match letter case exactly. */
|
|
4
|
+
caseSensitive: boolean;
|
|
5
|
+
/** Only match whole words. */
|
|
6
|
+
wholeWord: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Match counter shown in the find bar. */
|
|
9
|
+
export interface SearchState {
|
|
10
|
+
/** Number of matches in the document. */
|
|
11
|
+
total: number;
|
|
12
|
+
/** 1-based index of the current match, 0 when there is none. */
|
|
13
|
+
current: number;
|
|
14
|
+
}
|
|
15
|
+
/** Find and replace without touching the document markup: matches are painted with CSS highlights. */
|
|
16
|
+
export declare class SearchController {
|
|
17
|
+
private readonly root;
|
|
18
|
+
private readonly onChange;
|
|
19
|
+
/** Current search term; empty when the search is inactive. */
|
|
20
|
+
private term;
|
|
21
|
+
/** Current matching options. */
|
|
22
|
+
private options;
|
|
23
|
+
/** Live ranges of all matches in document order. */
|
|
24
|
+
private results;
|
|
25
|
+
/** Index of the current match, -1 when there is none. */
|
|
26
|
+
private index;
|
|
27
|
+
/**
|
|
28
|
+
* @param root the editable document element.
|
|
29
|
+
* @param onChange called with the match counter whenever it may have changed.
|
|
30
|
+
*/
|
|
31
|
+
constructor(root: HTMLElement, onChange: (state: SearchState) => void);
|
|
32
|
+
/** Current match counter. */
|
|
33
|
+
get state(): SearchState;
|
|
34
|
+
/** Whether a search term is set, so the results must follow document edits. */
|
|
35
|
+
get active(): boolean;
|
|
36
|
+
/** Starts a new search; options not passed keep their previous value. */
|
|
37
|
+
setQuery(term: string, options?: Partial<SearchOptions>): void;
|
|
38
|
+
/** Re-runs the search, e.g. after the document changed, keeping the current match position unless `reset`. */
|
|
39
|
+
refresh(reset?: boolean): void;
|
|
40
|
+
/** Moves to the next match, wrapping around, and scrolls it into view. */
|
|
41
|
+
next(): void;
|
|
42
|
+
/** Moves to the previous match, wrapping around, and scrolls it into view. */
|
|
43
|
+
previous(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Replaces the current match; the caller records history and tidies the markup.
|
|
46
|
+
* @returns whether a match was replaced.
|
|
47
|
+
*/
|
|
48
|
+
replaceCurrent(replacement: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Replaces every match, last first so earlier ranges stay valid.
|
|
51
|
+
* @returns the number of replaced matches.
|
|
52
|
+
*/
|
|
53
|
+
replaceAll(replacement: string): number;
|
|
54
|
+
/** Ends the search and removes the highlights. */
|
|
55
|
+
clear(): void;
|
|
56
|
+
/** Finds all matches block by block; line breaks count as newlines so matches never span them. */
|
|
57
|
+
private find;
|
|
58
|
+
/** Paints all matches and, separately, the current one. */
|
|
59
|
+
private paint;
|
|
60
|
+
/** Repaints, scrolls the current match into the middle of the view and reports the counter. */
|
|
61
|
+
private reveal;
|
|
62
|
+
/** Replaces the content of one match with plain text (an empty replacement deletes the match). */
|
|
63
|
+
private replaceRange;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection positions expressed as character offsets through the document. Unlike DOM ranges they stay valid when
|
|
3
|
+
* the markup around the text is rebuilt (marks merged, blocks renamed, history restored).
|
|
4
|
+
*/
|
|
5
|
+
export interface TextBookmark {
|
|
6
|
+
/** Offset where the selection starts (for a saved selection: where the user started selecting). */
|
|
7
|
+
anchor: number;
|
|
8
|
+
/** Offset where the selection ends (for a saved selection: where the caret currently is). */
|
|
9
|
+
focus: number;
|
|
10
|
+
}
|
|
11
|
+
/** Index of a node among its parent's child nodes, or -1 when it has no parent. */
|
|
12
|
+
export declare const indexOf: (node: Node) => number;
|
|
13
|
+
/** The current selection range when it lies entirely inside `root`, otherwise `null`. */
|
|
14
|
+
export declare const getRangeWithin: (root: HTMLElement) => Range | null;
|
|
15
|
+
/** Replaces the document selection with `range`. */
|
|
16
|
+
export declare const selectRange: (range: Range) => void;
|
|
17
|
+
/** Places the caret before the first character of `element`. */
|
|
18
|
+
export declare const placeCaretAtStart: (element: HTMLElement) => Range;
|
|
19
|
+
/** Places the caret after the last character of `element`, in front of its placeholder break if it has one. */
|
|
20
|
+
export declare const placeCaretAtEnd: (element: HTMLElement) => Range;
|
|
21
|
+
/** Stores a range as document offsets; `anchor` is the start and `focus` the end. */
|
|
22
|
+
export declare const rangeToBookmark: (root: HTMLElement, range: Range) => TextBookmark;
|
|
23
|
+
/** Rebuilds a forward DOM range from a bookmark. */
|
|
24
|
+
export declare const bookmarkToRange: (root: HTMLElement, bookmark: TextBookmark) => Range;
|
|
25
|
+
/** Stores the current selection, keeping its direction, or returns `null` when it is outside `root`. */
|
|
26
|
+
export declare const saveBookmark: (root: HTMLElement) => TextBookmark | null;
|
|
27
|
+
/** Restores a selection saved with {@link saveBookmark}, including its direction. */
|
|
28
|
+
export declare const restoreBookmark: (root: HTMLElement, bookmark: TextBookmark) => void;
|
|
29
|
+
/** Scrolls the caret line into view inside the nearest scroll container. */
|
|
30
|
+
export declare const scrollSelectionIntoView: (root: HTMLElement) => void;
|