@aseity/idocs-preview 0.3.0 → 0.5.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/CHANGELOG.md +12 -0
- package/README.md +59 -3
- package/dist/core/display.d.ts +90 -0
- package/dist/core/editor.d.ts +180 -0
- package/dist/core/editor.js +533 -0
- package/dist/core/engine.js +132 -38
- package/dist/core/index.d.ts +6 -19
- package/dist/core/index.js +13 -17
- package/dist/core/protocol.d.ts +87 -0
- package/dist/core/protocol.js +13 -1
- package/dist/view/edit-selection.d.ts +82 -0
- package/dist/view/edit-selection.js +786 -0
- package/dist/view/editor.d.ts +78 -0
- package/dist/view/editor.js +1485 -0
- package/dist/view/find.d.ts +2 -1
- package/dist/view/find.js +7 -0
- package/dist/view/ime.d.ts +63 -0
- package/dist/view/ime.js +352 -0
- package/dist/view/index.d.ts +4 -0
- package/dist/view/index.js +3 -0
- package/dist/view/selection-layer.d.ts +1 -0
- package/dist/view/selection-layer.js +4 -4
- package/dist/view/workspace-styles.d.ts +1 -0
- package/dist/view/workspace-styles.js +939 -0
- package/dist/view/workspace.d.ts +77 -0
- package/dist/view/workspace.js +1121 -0
- package/package.json +8 -3
- package/wasm/idocs_wasm.d.ts +30 -0
- package/wasm/idocs_wasm.js +147 -0
- package/wasm/idocs_wasm_bg.wasm +0 -0
- package/wasm/idocs_wasm_bg.wasm.d.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @aseity/idocs-preview
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Match the caret to the text being edited and move it to the next line after Shift+Enter. Keep edits and undo history when changing paragraph formatting, handle shorthand table rows, and focus newly inserted paragraphs, headings, and callouts. Improve excerpt selection, the narrow toolbar, and page thumbnails.
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Add `openEditor` for stateful editing of iDocs source documents, with source snapshots, undo and redo, revision tracking, and DOCX export. Add `mountEditor` for Canvas text editing with Chinese IME input and `mountEditorWorkspace` for page thumbnails, an outline, formatting controls, and paragraph, table, and image edits. Existing preview APIs remain available. Hosts own source persistence and editor session disposal.
|
|
14
|
+
|
|
3
15
|
## 0.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# iDocs preview
|
|
1
|
+
# iDocs preview and editor
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(`.idoc`). A Web Worker measures browser-local fonts with Canvas and lays out
|
|
3
|
+
Browser preview for Word documents (`.docx`) and iDocs source documents
|
|
4
|
+
(`.idoc`), with an interactive editor for iDocs source documents. A Web Worker measures browser-local fonts with Canvas and lays out
|
|
5
5
|
the document in WASM. Canvas draws text using the same font settings. No text
|
|
6
6
|
font downloads or Local Font Access permissions are required. Different installed
|
|
7
7
|
fonts can change line breaks and page counts.
|
|
@@ -55,6 +55,62 @@ during server rendering.
|
|
|
55
55
|
owns a session and a view; `mountPreview(container, doc)` shows a document you
|
|
56
56
|
opened yourself. Both re-export the core types.
|
|
57
57
|
|
|
58
|
+
## Editing iDocs source documents
|
|
59
|
+
|
|
60
|
+
Open an editor session and mount the workspace in a container with a height:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const { openEditor } = await import("@aseity/idocs-preview/core");
|
|
64
|
+
const { mountEditorWorkspace } = await import("@aseity/idocs-preview/view");
|
|
65
|
+
|
|
66
|
+
const session = await openEditor({ source, name: "report.idoc" });
|
|
67
|
+
const workspace = mountEditorWorkspace(container, session, { railOpen: true });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`source` is an iDocs YAML string or a `Uint8Array`. Use `readResource` in
|
|
71
|
+
`openEditor` to resolve referenced images. The workspace includes page thumbnails,
|
|
72
|
+
an outline, formatting controls, and an icon-only insert dock. It supports text
|
|
73
|
+
selection, Chinese IME input, undo and redo, paragraph styles, tables, and images.
|
|
74
|
+
Table cells are editable in both array rows and rows written with `cells:`.
|
|
75
|
+
Edits preserve the source form of each row.
|
|
76
|
+
Inserting a paragraph, heading, or callout from the dock focuses the new text.
|
|
77
|
+
A new callout uses the document's `theme.boxStyles.callout` when present.
|
|
78
|
+
The default text is left-aligned, with no paragraph indents and compact paragraph spacing.
|
|
79
|
+
For callout colors, the editor uses the box style, then `theme.colors.calloutBg`
|
|
80
|
+
and `theme.colors.accent`. The host can supply fallback `calloutColors`
|
|
81
|
+
as hex values. The selected style or colors are stored in the document.
|
|
82
|
+
When the formatting controls overflow, use the toolbar arrows to scroll.
|
|
83
|
+
In the narrowest panels, the arrows scroll the entire toolbar.
|
|
84
|
+
The caret and IME composition use the font size and color at the insertion point.
|
|
85
|
+
Press Shift+Enter for a line break within the paragraph. The caret moves to the start of the new line.
|
|
86
|
+
The page rail updates its contents while open.
|
|
87
|
+
Use `mountEditor(container, session)` for the page editor without workspace controls.
|
|
88
|
+
|
|
89
|
+
Save the source snapshot through your host, then mark that revision as saved:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const snapshot = await session.snapshot();
|
|
93
|
+
await saveSource(snapshot.source);
|
|
94
|
+
await session.markSaved({ revision: snapshot.revision });
|
|
95
|
+
const docx = await session.exportDocx();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`saveSource` is your application's persistence function. Subscribe to
|
|
99
|
+
`session.subscribe(listener)` to observe revisions, unsaved changes, and layout
|
|
100
|
+
state. Supply `onPickImage` to the workspace to enable image insertion.
|
|
101
|
+
Supply `mountInsertButtons` to render the insert dock buttons using your host's
|
|
102
|
+
tooltips. Render each button in its provided container, use its label and class
|
|
103
|
+
name, call `bind` with the mounted button, and call `activate` on click.
|
|
104
|
+
Return a cleanup function to unmount the buttons when the workspace is destroyed.
|
|
105
|
+
Supply `onQuoteSelection` to enable **询问 AI**. The callback receives
|
|
106
|
+
`{ text, path, page }` from the current layout, including unsaved edits. The
|
|
107
|
+
page index starts at zero. An excerpt includes TeX for formulas in the selection.
|
|
108
|
+
Clicking a read-only line or formula offers a quote when it has text or TeX.
|
|
109
|
+
|
|
110
|
+
When closing the editor, call `workspace.destroy()` and `session.dispose()`.
|
|
111
|
+
The workspace does not own the session. Existing preview APIs remain available.
|
|
112
|
+
The development page enables the workspace with `?edit=1`.
|
|
113
|
+
|
|
58
114
|
## Opening documents
|
|
59
115
|
|
|
60
116
|
`open(bytes, { name?, kind?, readResource? })` takes a `Uint8Array` or
|
package/dist/core/display.d.ts
CHANGED
|
@@ -150,6 +150,88 @@ export interface LineGlyph {
|
|
|
150
150
|
/** UTF-16 offset within the line's text. */
|
|
151
151
|
offset: number;
|
|
152
152
|
}
|
|
153
|
+
export type Affinity = 'upstream' | 'downstream';
|
|
154
|
+
export type GeneratedRole = 'listMarker' | 'headingNumber' | 'captionLabel' | 'equationNumber' | 'noteNumber';
|
|
155
|
+
export type ReadOnlyReason = 'aliasShared' | 'generated' | 'repeatedHeader' | 'unsupportedStory' | 'unsupportedStructure' | 'protectedInline';
|
|
156
|
+
export type Revision = string & {
|
|
157
|
+
readonly __brand: 'Revision';
|
|
158
|
+
};
|
|
159
|
+
export type SessionId = string & {
|
|
160
|
+
readonly __brand: 'SessionId';
|
|
161
|
+
};
|
|
162
|
+
export type FlowId = string & {
|
|
163
|
+
readonly __brand: 'FlowId';
|
|
164
|
+
};
|
|
165
|
+
export type BlockId = string & {
|
|
166
|
+
readonly __brand: 'BlockId';
|
|
167
|
+
};
|
|
168
|
+
export type DisplayUtf16 = number & {
|
|
169
|
+
readonly __brand: 'DisplayUtf16';
|
|
170
|
+
};
|
|
171
|
+
export type ScalarUtf16 = number & {
|
|
172
|
+
readonly __brand: 'ScalarUtf16';
|
|
173
|
+
};
|
|
174
|
+
export type FlowOffset = number & {
|
|
175
|
+
readonly __brand: 'FlowOffset';
|
|
176
|
+
};
|
|
177
|
+
export interface Anchor {
|
|
178
|
+
readonly session: SessionId;
|
|
179
|
+
readonly flow: FlowId;
|
|
180
|
+
readonly offset: FlowOffset;
|
|
181
|
+
readonly affinity: Affinity;
|
|
182
|
+
}
|
|
183
|
+
export interface CaretStop {
|
|
184
|
+
readonly displayOffset: DisplayUtf16;
|
|
185
|
+
readonly anchor: Anchor;
|
|
186
|
+
}
|
|
187
|
+
export type BlockStyle = 'body' | 'heading1' | 'heading2' | 'heading3';
|
|
188
|
+
export type TextAlign = 'left' | 'center' | 'right' | 'justify';
|
|
189
|
+
export type LineSource = {
|
|
190
|
+
readonly kind: 'editable';
|
|
191
|
+
readonly path: string;
|
|
192
|
+
readonly sourceStart: ScalarUtf16;
|
|
193
|
+
readonly sourceEnd: ScalarUtf16;
|
|
194
|
+
readonly stops: readonly CaretStop[];
|
|
195
|
+
readonly blockStyle?: BlockStyle;
|
|
196
|
+
readonly align?: TextAlign;
|
|
197
|
+
} | {
|
|
198
|
+
readonly kind: 'atomic';
|
|
199
|
+
readonly path?: string;
|
|
200
|
+
readonly reason: ReadOnlyReason;
|
|
201
|
+
readonly role?: GeneratedRole;
|
|
202
|
+
readonly before?: Anchor;
|
|
203
|
+
readonly after?: Anchor;
|
|
204
|
+
};
|
|
205
|
+
export interface LineSourceSegment {
|
|
206
|
+
readonly start: DisplayUtf16;
|
|
207
|
+
readonly end: DisplayUtf16;
|
|
208
|
+
readonly source: LineSource;
|
|
209
|
+
}
|
|
210
|
+
export type LineOccurrence = {
|
|
211
|
+
readonly kind: 'original';
|
|
212
|
+
} | {
|
|
213
|
+
readonly kind: 'repeatedHeader';
|
|
214
|
+
readonly canonicalPath: string;
|
|
215
|
+
} | {
|
|
216
|
+
readonly kind: 'readOnlyStory';
|
|
217
|
+
};
|
|
218
|
+
export interface CaretStyle {
|
|
219
|
+
readonly family: string;
|
|
220
|
+
readonly declaredFamily: string;
|
|
221
|
+
readonly role: string;
|
|
222
|
+
readonly size: number;
|
|
223
|
+
readonly bold: boolean;
|
|
224
|
+
readonly italic: boolean;
|
|
225
|
+
readonly color: string;
|
|
226
|
+
readonly ascent: number;
|
|
227
|
+
readonly descent: number;
|
|
228
|
+
readonly rise: number;
|
|
229
|
+
}
|
|
230
|
+
export interface CaretStyleSpan {
|
|
231
|
+
readonly start: number;
|
|
232
|
+
readonly end: number;
|
|
233
|
+
readonly style: CaretStyle;
|
|
234
|
+
}
|
|
153
235
|
/** One laid-out line's glyph table, for hit testing. */
|
|
154
236
|
export interface TextLine {
|
|
155
237
|
story: Story;
|
|
@@ -169,6 +251,14 @@ export interface TextLine {
|
|
|
169
251
|
/** Read-only in opened previews; indexed glyph objects are snapshots. */
|
|
170
252
|
glyphs: readonly LineGlyph[];
|
|
171
253
|
turn?: [number, number];
|
|
254
|
+
sourceSegments?: readonly LineSourceSegment[];
|
|
255
|
+
occurrence?: LineOccurrence;
|
|
256
|
+
caretStyle?: CaretStyle;
|
|
257
|
+
caretStyles?: readonly CaretStyleSpan[];
|
|
258
|
+
formulas?: readonly {
|
|
259
|
+
readonly offset: DisplayUtf16;
|
|
260
|
+
readonly tex: string;
|
|
261
|
+
}[];
|
|
172
262
|
}
|
|
173
263
|
export interface DisplayPage {
|
|
174
264
|
number: number;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { type Affinity, type Anchor, type BlockId, type BlockStyle, type CaretStop, type DisplayUtf16, type FlowId, type FlowOffset, type GeneratedRole, type LineOccurrence, type LineSource, type LineSourceSegment, type ReadOnlyReason, type Revision, type ScalarUtf16, type SessionId, type TextAlign, type TextLine } from './display.js';
|
|
2
|
+
import { type Endpoint, type PreviewDiagnostic, type PreviewDocument, type Read, type SessionOptions } from './protocol.js';
|
|
3
|
+
export type { Affinity, Anchor, BlockId, CaretStop, DisplayUtf16, FlowId, FlowOffset, GeneratedRole, LineOccurrence, LineSource, LineSourceSegment, ReadOnlyReason, Revision, ScalarUtf16, SessionId, };
|
|
4
|
+
export interface EditRange {
|
|
5
|
+
readonly revision: Revision;
|
|
6
|
+
readonly start: Anchor;
|
|
7
|
+
readonly end: Anchor;
|
|
8
|
+
}
|
|
9
|
+
export interface Selection {
|
|
10
|
+
readonly revision: Revision;
|
|
11
|
+
readonly range: EditRange;
|
|
12
|
+
readonly reversed: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type Mark = 'bold' | 'italic' | 'underline' | 'strike';
|
|
15
|
+
export interface CalloutColors {
|
|
16
|
+
readonly fill: string;
|
|
17
|
+
readonly border: string;
|
|
18
|
+
}
|
|
19
|
+
export type InsertBlock = {
|
|
20
|
+
kind: 'paragraph';
|
|
21
|
+
text?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'heading';
|
|
24
|
+
level: 1 | 2 | 3;
|
|
25
|
+
text?: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'callout';
|
|
28
|
+
text?: string;
|
|
29
|
+
colors?: CalloutColors;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'table';
|
|
32
|
+
rows: number;
|
|
33
|
+
cols: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'image';
|
|
36
|
+
src: string;
|
|
37
|
+
alt?: string;
|
|
38
|
+
widthRatio?: number;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'pageBreak';
|
|
41
|
+
};
|
|
42
|
+
export type TableAction = 'insertRowAbove' | 'insertRowBelow' | 'insertColumnLeft' | 'insertColumnRight' | 'deleteRow' | 'deleteColumn' | 'toggleHeaderRow';
|
|
43
|
+
export type ImageAlign = 'left' | 'center' | 'right';
|
|
44
|
+
export type Edit = {
|
|
45
|
+
kind: 'replaceText';
|
|
46
|
+
range: EditRange;
|
|
47
|
+
text: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'setMark';
|
|
50
|
+
range: EditRange;
|
|
51
|
+
mark: Mark;
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'splitBlock';
|
|
55
|
+
at: Anchor;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'mergeBackward';
|
|
58
|
+
at: Anchor;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'setBlockStyle';
|
|
61
|
+
at: Anchor;
|
|
62
|
+
style: BlockStyle;
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'setAlignment';
|
|
65
|
+
at: Anchor;
|
|
66
|
+
align: TextAlign;
|
|
67
|
+
} | {
|
|
68
|
+
kind: 'insertBlock';
|
|
69
|
+
after?: Anchor;
|
|
70
|
+
block: InsertBlock;
|
|
71
|
+
} | {
|
|
72
|
+
kind: 'editTable';
|
|
73
|
+
at: Anchor;
|
|
74
|
+
action: TableAction;
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'editImage';
|
|
77
|
+
path: string;
|
|
78
|
+
widthRatio?: number;
|
|
79
|
+
align?: ImageAlign;
|
|
80
|
+
src?: string;
|
|
81
|
+
alt?: string;
|
|
82
|
+
};
|
|
83
|
+
export interface SourceSnapshot {
|
|
84
|
+
readonly revision: Revision;
|
|
85
|
+
readonly source: string;
|
|
86
|
+
}
|
|
87
|
+
export interface RenderStats {
|
|
88
|
+
readonly localResolve: boolean;
|
|
89
|
+
readonly paragraphsLaid: number;
|
|
90
|
+
readonly paragraphsReused: number;
|
|
91
|
+
readonly passes: number;
|
|
92
|
+
}
|
|
93
|
+
export interface Commit {
|
|
94
|
+
readonly revision: Revision;
|
|
95
|
+
readonly selection: Selection | null;
|
|
96
|
+
readonly changedBlocks: readonly BlockId[];
|
|
97
|
+
readonly localResolve: boolean;
|
|
98
|
+
readonly diagnostics: readonly PreviewDiagnostic[];
|
|
99
|
+
}
|
|
100
|
+
export interface EditorFrame {
|
|
101
|
+
readonly revision: Revision;
|
|
102
|
+
readonly document: PreviewDocument;
|
|
103
|
+
readonly stats: RenderStats;
|
|
104
|
+
}
|
|
105
|
+
export type LayoutState = {
|
|
106
|
+
kind: 'pending';
|
|
107
|
+
previous: EditorFrame | null;
|
|
108
|
+
} | {
|
|
109
|
+
kind: 'ready';
|
|
110
|
+
frame: EditorFrame;
|
|
111
|
+
} | {
|
|
112
|
+
kind: 'failed';
|
|
113
|
+
revision: Revision;
|
|
114
|
+
message: string;
|
|
115
|
+
diagnostics: readonly PreviewDiagnostic[];
|
|
116
|
+
previous: EditorFrame | null;
|
|
117
|
+
};
|
|
118
|
+
export interface EditorState {
|
|
119
|
+
readonly revision: Revision;
|
|
120
|
+
readonly dirty: boolean;
|
|
121
|
+
readonly selection: Selection | null;
|
|
122
|
+
readonly canUndo: boolean;
|
|
123
|
+
readonly canRedo: boolean;
|
|
124
|
+
readonly layout: LayoutState;
|
|
125
|
+
}
|
|
126
|
+
export interface OpenEditorOptions {
|
|
127
|
+
source: string | Uint8Array;
|
|
128
|
+
name?: string;
|
|
129
|
+
readResource?: Read;
|
|
130
|
+
runtime?: SessionOptions;
|
|
131
|
+
}
|
|
132
|
+
export interface EditorSession {
|
|
133
|
+
state(): EditorState;
|
|
134
|
+
subscribe(listener: (state: EditorState) => void): () => void;
|
|
135
|
+
range(input: {
|
|
136
|
+
revision: Revision;
|
|
137
|
+
start: Anchor;
|
|
138
|
+
end: Anchor;
|
|
139
|
+
}): Promise<EditRange>;
|
|
140
|
+
apply(input: {
|
|
141
|
+
base: Revision;
|
|
142
|
+
edit: Edit;
|
|
143
|
+
}): Promise<Commit>;
|
|
144
|
+
undo(input: {
|
|
145
|
+
base: Revision;
|
|
146
|
+
}): Promise<Commit>;
|
|
147
|
+
redo(input: {
|
|
148
|
+
base: Revision;
|
|
149
|
+
}): Promise<Commit>;
|
|
150
|
+
snapshot(): Promise<SourceSnapshot>;
|
|
151
|
+
markSaved(input: {
|
|
152
|
+
revision: Revision;
|
|
153
|
+
}): Promise<void>;
|
|
154
|
+
refresh(input: {
|
|
155
|
+
base: Revision;
|
|
156
|
+
source: string | Uint8Array;
|
|
157
|
+
}): Promise<Commit>;
|
|
158
|
+
exportDocx(): Promise<Uint8Array>;
|
|
159
|
+
dispose(): void;
|
|
160
|
+
}
|
|
161
|
+
export type LineHit = {
|
|
162
|
+
kind: 'editable';
|
|
163
|
+
anchor: Anchor;
|
|
164
|
+
displayOffset: DisplayUtf16;
|
|
165
|
+
x: number;
|
|
166
|
+
} | {
|
|
167
|
+
kind: 'atomic';
|
|
168
|
+
reason: ReadOnlyReason;
|
|
169
|
+
role?: GeneratedRole;
|
|
170
|
+
before?: Anchor;
|
|
171
|
+
after?: Anchor;
|
|
172
|
+
segment?: LineSourceSegment;
|
|
173
|
+
} | {
|
|
174
|
+
kind: 'unmapped';
|
|
175
|
+
};
|
|
176
|
+
export declare function caretXForOffset(line: TextLine, target: number): number;
|
|
177
|
+
export declare function locateLineSource(line: TextLine, xPt: number): LineHit;
|
|
178
|
+
export declare function lineCaretStop(line: TextLine, anchor: Anchor): CaretStop | null;
|
|
179
|
+
export declare function lineCaretX(line: TextLine, anchor: Anchor): number | null;
|
|
180
|
+
export declare function openEditorWith(worker: Endpoint, options: OpenEditorOptions): Promise<EditorSession>;
|