@sobree/core 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 +106 -0
- package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
- package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
- package/dist/blob/cache.d.ts +69 -0
- package/dist/blob/fetch.d.ts +18 -0
- package/dist/blob/hash.d.ts +13 -0
- package/dist/blob/index.d.ts +33 -0
- package/dist/blob/memory.d.ts +2 -0
- package/dist/blob/types.d.ts +80 -0
- package/dist/createSobree.d.ts +132 -0
- package/dist/doc/api.d.ts +132 -0
- package/dist/doc/builders.d.ts +42 -0
- package/dist/doc/pageSetupBridge.d.ts +26 -0
- package/dist/doc/parts.d.ts +18 -0
- package/dist/doc/runs.d.ts +47 -0
- package/dist/doc/styles.d.ts +19 -0
- package/dist/doc/types.d.ts +800 -0
- package/dist/doc/walk.d.ts +30 -0
- package/dist/docx/export/contentTypes.d.ts +35 -0
- package/dist/docx/export/context.d.ts +59 -0
- package/dist/docx/export/document.d.ts +19 -0
- package/dist/docx/export/drawings.d.ts +10 -0
- package/dist/docx/export/headers.d.ts +19 -0
- package/dist/docx/export/index.d.ts +14 -0
- package/dist/docx/export/runs.d.ts +8 -0
- package/dist/docx/export/styles.d.ts +8 -0
- package/dist/docx/export/zip.d.ts +13 -0
- package/dist/docx/import/anchoredFrames.d.ts +34 -0
- package/dist/docx/import/comments.d.ts +3 -0
- package/dist/docx/import/document.d.ts +57 -0
- package/dist/docx/import/flowFrames.d.ts +11 -0
- package/dist/docx/import/footnotes.d.ts +3 -0
- package/dist/docx/import/headers.d.ts +50 -0
- package/dist/docx/import/index.d.ts +12 -0
- package/dist/docx/import/inlineFrames.d.ts +62 -0
- package/dist/docx/import/numbering.d.ts +2 -0
- package/dist/docx/import/paragraph.d.ts +24 -0
- package/dist/docx/import/paragraphs.d.ts +27 -0
- package/dist/docx/import/rels.d.ts +5 -0
- package/dist/docx/import/runs.d.ts +64 -0
- package/dist/docx/import/settings.d.ts +48 -0
- package/dist/docx/import/styles.d.ts +3 -0
- package/dist/docx/import/tables.d.ts +12 -0
- package/dist/docx/import/unzip.d.ts +13 -0
- package/dist/docx/shared/namespaces.d.ts +31 -0
- package/dist/docx/shared/pageSize.d.ts +27 -0
- package/dist/docx/shared/shading.d.ts +2 -0
- package/dist/docx/shared/units.d.ts +35 -0
- package/dist/docx/shared/xml.d.ts +29 -0
- package/dist/docx/types.d.ts +98 -0
- package/dist/editor/index.d.ts +1078 -0
- package/dist/editor/internal/blockRegistry.d.ts +91 -0
- package/dist/editor/internal/mutations.d.ts +63 -0
- package/dist/editor/internal/positionMap.d.ts +35 -0
- package/dist/editor/table.d.ts +96 -0
- package/dist/editor/view/docRenderer/anchorLayer.d.ts +26 -0
- package/dist/editor/view/docRenderer/block.d.ts +13 -0
- package/dist/editor/view/docRenderer/fontFallback.d.ts +28 -0
- package/dist/editor/view/docRenderer/index.d.ts +18 -0
- package/dist/editor/view/docRenderer/inline.d.ts +15 -0
- package/dist/editor/view/docRenderer/inlineFrame.d.ts +4 -0
- package/dist/editor/view/docRenderer/lists.d.ts +28 -0
- package/dist/editor/view/docRenderer/paragraph.d.ts +2 -0
- package/dist/editor/view/docRenderer/properties.d.ts +2 -0
- package/dist/editor/view/docRenderer/table.d.ts +15 -0
- package/dist/editor/view/docRenderer/units.d.ts +48 -0
- package/dist/editor/view/docSerialize/block.d.ts +14 -0
- package/dist/editor/view/docSerialize/index.d.ts +8 -0
- package/dist/editor/view/docSerialize/inline.d.ts +11 -0
- package/dist/editor/view/docSerialize/table.d.ts +12 -0
- package/dist/editor/view/imageResize.d.ts +16 -0
- package/dist/embed/floatingCorner.d.ts +44 -0
- package/dist/embed/viewport.d.ts +133 -0
- package/dist/fonts/embedAPI.d.ts +33 -0
- package/dist/fonts/emit.d.ts +24 -0
- package/dist/fonts/fontFaceRegistry.d.ts +20 -0
- package/dist/fonts/fsType.d.ts +36 -0
- package/dist/fonts/index.d.ts +19 -0
- package/dist/fonts/liveness.d.ts +2 -0
- package/dist/fonts/odttf.d.ts +33 -0
- package/dist/fonts/parse.d.ts +29 -0
- package/dist/fonts/types.d.ts +52 -0
- package/dist/headless.d.ts +168 -0
- package/dist/history/history.d.ts +100 -0
- package/dist/history/index.d.ts +4 -0
- package/dist/history/types.d.ts +54 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +10561 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/parse.d.ts +6 -0
- package/dist/pagination/cost.d.ts +32 -0
- package/dist/pagination/index.d.ts +2 -0
- package/dist/pagination/paginate.d.ts +10 -0
- package/dist/pagination/postConditions.d.ts +10 -0
- package/dist/pagination/types.d.ts +94 -0
- package/dist/paperStack/pageSetup.d.ts +42 -0
- package/dist/paperStack/paginationAdapter/buildItems.d.ts +19 -0
- package/dist/paperStack/paginationAdapter/distribute.d.ts +23 -0
- package/dist/paperStack/paginationAdapter/index.d.ts +18 -0
- package/dist/paperStack/paginationAdapter/paragraphLines.d.ts +23 -0
- package/dist/paperStack/paginationAdapter/splitList.d.ts +19 -0
- package/dist/paperStack/paginationAdapter/splitParagraph.d.ts +21 -0
- package/dist/paperStack/paginationAdapter/types.d.ts +30 -0
- package/dist/paperStack/paper.d.ts +107 -0
- package/dist/paperStack/paperStack.d.ts +245 -0
- package/dist/plugin.d.ts +24 -0
- package/dist/plugins/marks.d.ts +49 -0
- package/dist/plugins/sections.d.ts +15 -0
- package/dist/presence/attach.d.ts +48 -0
- package/dist/presence/awareness.d.ts +28 -0
- package/dist/presence/index.d.ts +19 -0
- package/dist/presence/overlay.d.ts +28 -0
- package/dist/presence/state.d.ts +36 -0
- package/dist/sobree.d.ts +211 -0
- package/dist/tokens.css +144 -0
- package/dist/util/selection.d.ts +13 -0
- package/dist/ydoc/apply.d.ts +68 -0
- package/dist/ydoc/index.d.ts +18 -0
- package/dist/ydoc/project.d.ts +41 -0
- package/dist/ydoc/runs.d.ts +51 -0
- package/dist/ydoc/schema.d.ts +123 -0
- package/dist/ydoc/seed.d.ts +45 -0
- package/dist/ydoc/textDiff.d.ts +59 -0
- package/dist/zoneEdit/index.d.ts +22 -0
- package/package.json +61 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { PageSetup } from './pageSetup';
|
|
2
|
+
import { AnchoredFrame, Block, NamedStyle, NumberingDefinition, SectionProperties } from '../doc/types';
|
|
3
|
+
/**
|
|
4
|
+
* Sourced from `SobreeDocument`: the rich AST + the dependencies
|
|
5
|
+
* `renderBlocks` needs (numbering, named styles, embedded media bytes).
|
|
6
|
+
* When set, every page renders headers/footers from this AST and
|
|
7
|
+
* ignores the `PageSetup.header.default` / `footer.default` string
|
|
8
|
+
* templates. Cleared (set back to `null`) when the doc has no
|
|
9
|
+
* header/footer parts — falls back to the legacy text path.
|
|
10
|
+
*/
|
|
11
|
+
export interface RichZonesSource {
|
|
12
|
+
headerFooterBodies: Record<string, Block[]>;
|
|
13
|
+
numbering: readonly NumberingDefinition[];
|
|
14
|
+
styles: readonly NamedStyle[];
|
|
15
|
+
rawParts: Record<string, Uint8Array>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stack of Paper elements. The stack's root is the single contentEditable
|
|
19
|
+
* region; each paper's header/footer is contentEditable=false. Blocks of
|
|
20
|
+
* editor content are physically distributed across the papers'
|
|
21
|
+
* `.paper-content` elements. Editing naturally crosses page boundaries
|
|
22
|
+
* because the whole stack is one editable region.
|
|
23
|
+
*
|
|
24
|
+
* Call `repaginate()` after content changes or after page setup changes.
|
|
25
|
+
*/
|
|
26
|
+
export declare class PaperStack {
|
|
27
|
+
readonly root: HTMLElement;
|
|
28
|
+
private papers;
|
|
29
|
+
private setup;
|
|
30
|
+
/**
|
|
31
|
+
* Layout-side zoom tier currently applied to an ancestor (via the Viewport's
|
|
32
|
+
* CSS `zoom`). Measurements from offsetHeight/offsetTop come back in zoomed
|
|
33
|
+
* pixels, so the page budget must scale the same way.
|
|
34
|
+
*/
|
|
35
|
+
private renderTier;
|
|
36
|
+
/** Optional observer fired after every successful repagination. */
|
|
37
|
+
private paginateListeners;
|
|
38
|
+
/**
|
|
39
|
+
* Per-section property overrides. When set, each Paper applies the
|
|
40
|
+
* section's settings (currently just `vAlign`) based on the
|
|
41
|
+
* `data-section-index` stamped on its first block. Sobree provides
|
|
42
|
+
* this from the live document; absent → all pages use `setup`.
|
|
43
|
+
*/
|
|
44
|
+
private sections;
|
|
45
|
+
/**
|
|
46
|
+
* Optional rich-AST source for header/footer rendering. When present,
|
|
47
|
+
* each paper's header/footer is rendered from the matching part body
|
|
48
|
+
* (resolved through `sections[i].headerRefs` / `footerRefs`) instead
|
|
49
|
+
* of from the `PageSetup` string template. Set via `setRichZones`.
|
|
50
|
+
*/
|
|
51
|
+
private richZones;
|
|
52
|
+
/**
|
|
53
|
+
* Document's floating-layer frames (the new architecture replacing
|
|
54
|
+
* the lifter + framePictures hacks). When set, each paper is painted
|
|
55
|
+
* with the subset of frames whose anchor resolves to that page;
|
|
56
|
+
* paper.setAnchoredFrames does the per-page filtering and DOM swap.
|
|
57
|
+
* `null` → no floating layer at all (skeleton state during Phase B).
|
|
58
|
+
*/
|
|
59
|
+
private anchoredFrames;
|
|
60
|
+
/** Reused blob-URL cache across renders so the same image isn't re-uploaded. */
|
|
61
|
+
private readonly anchorPictureUrlCache;
|
|
62
|
+
constructor(container: HTMLElement, setup: PageSetup);
|
|
63
|
+
/**
|
|
64
|
+
* Provide the document's sections for per-page property application.
|
|
65
|
+
* Re-applied immediately so a setSections call without a content
|
|
66
|
+
* change still updates papers (e.g. user edits a section's vAlign in
|
|
67
|
+
* the future Page Setup section selector).
|
|
68
|
+
*/
|
|
69
|
+
setSections(sections: readonly SectionProperties[]): void;
|
|
70
|
+
/**
|
|
71
|
+
* Install or clear the rich AST source for header/footer rendering.
|
|
72
|
+
* Re-renders zones immediately so the swap is visible without waiting
|
|
73
|
+
* for the next paginate. Pass `null` to revert to the legacy text-
|
|
74
|
+
* template path (no rich body available).
|
|
75
|
+
*/
|
|
76
|
+
setRichZones(source: RichZonesSource | null): void;
|
|
77
|
+
/**
|
|
78
|
+
* Install or clear the document's floating-layer frames. Each frame's
|
|
79
|
+
* `anchor.paragraphIndex` (when set) decides which page receives it
|
|
80
|
+
* after the next repaginate; frames without a paragraph index land
|
|
81
|
+
* on the first page of their section. Pass `null` to clear.
|
|
82
|
+
*
|
|
83
|
+
* Call after `setRichZones`/`updateBodyBlocks` so the next render
|
|
84
|
+
* picks up the new floating content alongside body flow.
|
|
85
|
+
*/
|
|
86
|
+
setAnchoredFrames(frames: readonly AnchoredFrame[] | null): void;
|
|
87
|
+
getSetup(): PageSetup;
|
|
88
|
+
getPageCount(): number;
|
|
89
|
+
onPaginate(cb: (pageCount: number) => void): () => void;
|
|
90
|
+
/** First paper's content — initial render target for a new editor. */
|
|
91
|
+
get primaryContent(): HTMLElement;
|
|
92
|
+
/** First paper card — for viewport fit-to-page calculations. */
|
|
93
|
+
get firstPaper(): HTMLElement;
|
|
94
|
+
/** First paper's row wrapper (paper + comments sidebar) — for fit-to-width. */
|
|
95
|
+
get firstPaperRow(): HTMLElement;
|
|
96
|
+
/** All content hosts in document order — for serialization. */
|
|
97
|
+
get contentHosts(): HTMLElement[];
|
|
98
|
+
updateSetup(setup: PageSetup): void;
|
|
99
|
+
/**
|
|
100
|
+
* Called by the Viewport when the layout-zoom tier changes. In Chromium,
|
|
101
|
+
* CSS `zoom` doesn't change `offsetHeight`/`offsetTop`, so measurements
|
|
102
|
+
* stay logical and the page budget is unaffected. We still re-paginate
|
|
103
|
+
* defensively — tier changes re-render text at a new resolution, which
|
|
104
|
+
* can nudge line wrapping at sub-pixel boundaries.
|
|
105
|
+
*/
|
|
106
|
+
setRenderTier(tier: number): void;
|
|
107
|
+
/**
|
|
108
|
+
* Redistribute blocks across papers so no paper overflows. Creates or
|
|
109
|
+
* removes papers as needed.
|
|
110
|
+
*
|
|
111
|
+
* Delegates to the pure paginator in `src/pagination/` via the DOM
|
|
112
|
+
* adapter. Before paginating, blocks are **consolidated** into the first
|
|
113
|
+
* paper's content so their `offsetTop` values share one coordinate system,
|
|
114
|
+
* and consecutive `<p>` fragments sharing a `data-pag-pid` (previous-split
|
|
115
|
+
* siblings of the same logical paragraph) are **merged** back into a
|
|
116
|
+
* single paragraph — so each repagination starts from the clean logical
|
|
117
|
+
* flow instead of accumulating inter-fragment margins.
|
|
118
|
+
*/
|
|
119
|
+
repaginate(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Per-page footnote pinning. After body pagination completes, scan
|
|
122
|
+
* each paper for footnote references (`<sup class="sobree-footnote-ref">`)
|
|
123
|
+
* and populate that paper's `.paper-footnotes` zone with the cited
|
|
124
|
+
* bodies — matching Word's "footnote bodies render at the bottom of
|
|
125
|
+
* the page where they're referenced" behaviour.
|
|
126
|
+
*
|
|
127
|
+
* Footnote bodies are sourced from two places:
|
|
128
|
+
* 1. The doc-end `<aside class="sobree-footnotes">` the renderer
|
|
129
|
+
* initially appends to paper[0].content. We harvest the `<li>`s
|
|
130
|
+
* out of it before the aside itself gets re-paginated as a block.
|
|
131
|
+
* 2. Any footnote zones already populated from a previous repaginate
|
|
132
|
+
* pass — those bodies stay in scope across iterations.
|
|
133
|
+
*
|
|
134
|
+
* Caveat: body budget is NOT reduced for footnote-zone height. On
|
|
135
|
+
* pages with many footnotes, the footnote zone may extend past the
|
|
136
|
+
* page bottom into the footer area. True budget-reservation is its
|
|
137
|
+
* own paginator feature.
|
|
138
|
+
*/
|
|
139
|
+
private distributeFootnotes;
|
|
140
|
+
/**
|
|
141
|
+
* One round of consolidate → merge → paginate → distribute.
|
|
142
|
+
* Re-entrant: each call re-collects blocks from every paper, so the
|
|
143
|
+
* iterative loop in `repaginate` can call this multiple times with
|
|
144
|
+
* shrinking budgets to absorb post-split slippage.
|
|
145
|
+
*/
|
|
146
|
+
private runPaginationOnce;
|
|
147
|
+
/**
|
|
148
|
+
* Build a `pageHeights[]` array from observed footnote-zone heights.
|
|
149
|
+
* Entry `i` = `baselineBudgetPx - footnoteZoneHeight(i)`. Pages
|
|
150
|
+
* without footnotes get the full baseline. Returned array trims
|
|
151
|
+
* trailing entries equal to the baseline, so consumers can detect
|
|
152
|
+
* "no per-page overrides needed" via `length === 0`.
|
|
153
|
+
*/
|
|
154
|
+
private computePageHeights;
|
|
155
|
+
/**
|
|
156
|
+
* Largest content overflow across all papers, in CSS px. Zero means
|
|
157
|
+
* every paper fits within its content area. Used by the iterative
|
|
158
|
+
* `repaginate` to detect post-split slippage that needs another
|
|
159
|
+
* pagination pass with a tighter budget.
|
|
160
|
+
*/
|
|
161
|
+
private maxPaperOverflowPx;
|
|
162
|
+
/**
|
|
163
|
+
* For every Paper, look at its first content block's `data-section-index`
|
|
164
|
+
* and apply that section's overrides. Falls back silently when no
|
|
165
|
+
* sections were provided or a paper is empty.
|
|
166
|
+
*/
|
|
167
|
+
private applyPerSectionSettings;
|
|
168
|
+
private emitPaginate;
|
|
169
|
+
destroy(): void;
|
|
170
|
+
private pageContentHeightPx;
|
|
171
|
+
private collectAllBlocks;
|
|
172
|
+
private ensurePaperCount;
|
|
173
|
+
private renderAllZones;
|
|
174
|
+
/**
|
|
175
|
+
* Filter the document's `anchoredFrames` per paper and ask each Paper
|
|
176
|
+
* to swap its anchor-layer DOM accordingly. A frame's destination
|
|
177
|
+
* page is determined by its anchor:
|
|
178
|
+
* - `paragraphIndex` set → page that ended up containing that
|
|
179
|
+
* body block (looked up by `data-block-index` stamped on
|
|
180
|
+
* rendered children).
|
|
181
|
+
* - paragraphIndex absent → first paper of the frame's section.
|
|
182
|
+
*
|
|
183
|
+
* Cheap and idempotent; safe to call after any layout change.
|
|
184
|
+
*/
|
|
185
|
+
private paintAnchorLayers;
|
|
186
|
+
/**
|
|
187
|
+
* Walk every paper's content children once and record, for each
|
|
188
|
+
* `data-block-index="N"` element, the paper index that holds it.
|
|
189
|
+
* The renderer stamps that attribute when emitting body paragraphs;
|
|
190
|
+
* this is the only piece of mutual knowledge between body flow and
|
|
191
|
+
* the floating layer needed for paragraph anchoring.
|
|
192
|
+
*/
|
|
193
|
+
private buildParagraphPageIndex;
|
|
194
|
+
/**
|
|
195
|
+
* Section index a paper belongs to. Read off the first content
|
|
196
|
+
* block's `data-section-index` (stamped by `renderBlocks`). Empty
|
|
197
|
+
* papers and the no-sections case fall back to 0.
|
|
198
|
+
*/
|
|
199
|
+
private sectionIndexForPage;
|
|
200
|
+
/**
|
|
201
|
+
* True when this paper is the first page of its section. Used to
|
|
202
|
+
* select the `first`-typed header/footer ref when the section has
|
|
203
|
+
* `titlePage = true`.
|
|
204
|
+
*/
|
|
205
|
+
private isFirstPaperOfSection;
|
|
206
|
+
/**
|
|
207
|
+
* Resolve which header/footer Block[] applies to a paper. Walks the
|
|
208
|
+
* section's refs by type and looks up the corresponding part body in
|
|
209
|
+
* `richZones.headerFooterBodies`. Returns `null` when no rich body
|
|
210
|
+
* applies (no refs, or `richZones` not set).
|
|
211
|
+
*/
|
|
212
|
+
private pickRichZone;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Walk the paginator's output back-to-front, collapsing trailing pages
|
|
216
|
+
* whose blocks are all visually empty into the previous page.
|
|
217
|
+
*
|
|
218
|
+
* "Visually empty" = an empty paragraph: a `<p>` (or list-item `<li>`)
|
|
219
|
+
* with no text content and no embedded image / table / break. We keep
|
|
220
|
+
* the empty blocks (round-trip fidelity demands every paragraph mark
|
|
221
|
+
* stays in the AST) — we just stop reserving a fresh page for them.
|
|
222
|
+
*
|
|
223
|
+
* Stops collapsing the moment a page contains any non-empty block, so
|
|
224
|
+
* a real "last page with just a signature line" still gets its own
|
|
225
|
+
* page. Idempotent; safe to call on a single-page result.
|
|
226
|
+
*/
|
|
227
|
+
/**
|
|
228
|
+
* Walk paginator output forward, absorbing pages whose total content
|
|
229
|
+
* height is ≤ 15% of `budgetPx` (the page-content budget) into the
|
|
230
|
+
* previous page. These tiny tail-pages are paginator widows — usually
|
|
231
|
+
* a 1-line overflow or an LRPB-induced sub-section that didn't have
|
|
232
|
+
* room. Both Word and LO tolerate a bit of bottom-margin spill in
|
|
233
|
+
* exchange for not spending an entire fresh page on the runt.
|
|
234
|
+
*
|
|
235
|
+
* Safety: only absorbs pages whose content height is smaller than the
|
|
236
|
+
* previous page's slack PLUS the widow tolerance (~20% of budget).
|
|
237
|
+
* If absorbing would visibly push content far past the page bottom,
|
|
238
|
+
* we leave the runt page alone — better an underfilled page than
|
|
239
|
+
* unreadable overflow.
|
|
240
|
+
*
|
|
241
|
+
* Run AFTER `collapseTrailingEmptyPages` so trailing whitespace is
|
|
242
|
+
* already merged and we're working on the real content layout.
|
|
243
|
+
*/
|
|
244
|
+
export declare function collapseUnderfilledPages(pages: readonly HTMLElement[][], budgetPx: number): HTMLElement[][];
|
|
245
|
+
export declare function collapseTrailingEmptyPages(pages: readonly HTMLElement[][]): HTMLElement[][];
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Editor } from './editor';
|
|
2
|
+
import { Viewport } from './embed/viewport';
|
|
3
|
+
import { Sobree } from './sobree';
|
|
4
|
+
export interface PluginContext {
|
|
5
|
+
/** The framework-free editor kernel. */
|
|
6
|
+
editor: Editor;
|
|
7
|
+
/** The Sobree façade — paper stack, page setup, mode, headers/footers. */
|
|
8
|
+
sobree: Sobree;
|
|
9
|
+
/** The viewport — pannable / zoomable stage. */
|
|
10
|
+
viewport: Viewport;
|
|
11
|
+
/** The element `createSobree()` was mounted into. */
|
|
12
|
+
host: HTMLElement;
|
|
13
|
+
}
|
|
14
|
+
export interface SobreePluginInstance {
|
|
15
|
+
/** Tear down everything `setup` allocated. Called on createSobree's
|
|
16
|
+
* destroy, in reverse-of-setup order. */
|
|
17
|
+
destroy(): void;
|
|
18
|
+
}
|
|
19
|
+
export interface SobreePlugin {
|
|
20
|
+
/** Diagnostic name surfaced in setup-failure logs. Optional. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** Mount the plugin against the editor surface. Returns a destroyer. */
|
|
23
|
+
setup(ctx: PluginContext): SobreePluginInstance;
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Range as ApiRange } from '../doc/api';
|
|
2
|
+
import { RunProperties } from '../doc/types';
|
|
3
|
+
import { Editor, WrapTag } from '../editor';
|
|
4
|
+
/**
|
|
5
|
+
* Mark toggle helpers — shared by the floating toolbar's mark buttons
|
|
6
|
+
* and the keyboard plugin (Ctrl+B / I / U / …).
|
|
7
|
+
*
|
|
8
|
+
* "Mark" = a boolean / enum run property that maps 1:1 to a `wrapRange`
|
|
9
|
+
* tag: `strong`, `em`, `u`, `s`, `sup`, `sub`. (`mark`/highlight is
|
|
10
|
+
* passthrough — not a toggle, since highlight is colour-valued.)
|
|
11
|
+
*/
|
|
12
|
+
/** Tag → run property key for toggle detection. */
|
|
13
|
+
export declare const MARK_PROP: Record<string, keyof RunProperties>;
|
|
14
|
+
/** Expected "on" value for each mark. */
|
|
15
|
+
export declare const MARK_ON: Record<string, RunProperties[keyof RunProperties]>;
|
|
16
|
+
export type ToggleableMark = "strong" | "em" | "u" | "s" | "sup" | "sub";
|
|
17
|
+
/**
|
|
18
|
+
* Standard mark commands registered on every Editor's command bus.
|
|
19
|
+
* Owned by the marks module so a single source of truth backs the
|
|
20
|
+
* keyboard plugin (`@sobree/keyboard`), the floating toolbar
|
|
21
|
+
* (`@sobree/block-tools`), and any custom UI / agent dispatch.
|
|
22
|
+
*/
|
|
23
|
+
export interface MarkCommandDef {
|
|
24
|
+
name: string;
|
|
25
|
+
title: string;
|
|
26
|
+
tag: ToggleableMark;
|
|
27
|
+
}
|
|
28
|
+
export declare const MARK_COMMAND_DEFS: readonly MarkCommandDef[];
|
|
29
|
+
/**
|
|
30
|
+
* Apply or clear a mark across `range` based on its current state. If
|
|
31
|
+
* every text run in the range already carries the mark's "on" value,
|
|
32
|
+
* the call clears it; otherwise the mark is applied.
|
|
33
|
+
*
|
|
34
|
+
* `mark` (highlight) is delegated straight to `wrapRange` — it's not a
|
|
35
|
+
* toggle since highlight is a colour, not a boolean.
|
|
36
|
+
*/
|
|
37
|
+
export declare function toggleMark(editor: Editor, range: ApiRange, tag: WrapTag): void;
|
|
38
|
+
/**
|
|
39
|
+
* True when every non-empty text run inside `range` has the mark's "on"
|
|
40
|
+
* value. Walks hyperlink children. Multi-block ranges always read as
|
|
41
|
+
* inactive so a click sets the mark before clearing it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isMarkActive(editor: Editor, range: ApiRange, tag: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Pick the right range to operate on: live DOM selection if any,
|
|
46
|
+
* otherwise the full extent of the block at the caret. Matches the
|
|
47
|
+
* "press Ctrl+B with caret only → bold the whole paragraph" UX.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rangeAtSelection(editor: Editor): ApiRange | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Editor } from '../editor';
|
|
2
|
+
/**
|
|
3
|
+
* Section commands plugin.
|
|
4
|
+
*
|
|
5
|
+
* Registers `section.insertBreakAfter` — inserts a `SectionBreak` block
|
|
6
|
+
* after the caret's current block. The new section inherits the
|
|
7
|
+
* previous section's properties (cloned so later edits don't bleed
|
|
8
|
+
* back) so visual continuity is preserved until the user edits the
|
|
9
|
+
* new section in Page Setup.
|
|
10
|
+
*
|
|
11
|
+
* The keyboard plugin's Ctrl+Shift+Enter dispatches this command, the
|
|
12
|
+
* change-type popover does too, and a future Insert menu would as well
|
|
13
|
+
* — same dispatch path for every trigger.
|
|
14
|
+
*/
|
|
15
|
+
export declare function attachSections(editor: Editor): () => void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Editor } from '../editor';
|
|
2
|
+
import { AwarenessLike } from './awareness';
|
|
3
|
+
import { PresenceState, PresenceUser } from './state';
|
|
4
|
+
export interface AttachPresenceOptions {
|
|
5
|
+
/** This peer's identity. Published in the local state's `user` field. */
|
|
6
|
+
user: PresenceUser;
|
|
7
|
+
/**
|
|
8
|
+
* Called whenever any peer's presence (including this one) changes.
|
|
9
|
+
* The map is `clientID → PresenceState` for every peer currently
|
|
10
|
+
* publishing valid Sobree presence. The local peer's clientID
|
|
11
|
+
* (matches `awareness.clientID` / `editor.ydoc.clientID`) is
|
|
12
|
+
* included so callers can render their own caret too if they want.
|
|
13
|
+
*/
|
|
14
|
+
onChange?: (peers: Map<number, PresenceState>) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Default to `true` — publish the local user's selection on every
|
|
17
|
+
* `editor.on("selection")` event. Pass `false` if the caller wants
|
|
18
|
+
* to manage own-selection publishing manually.
|
|
19
|
+
*/
|
|
20
|
+
publishOwnSelection?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface PresenceHandle {
|
|
23
|
+
/** Snapshot of every peer's current state, keyed by clientID. */
|
|
24
|
+
getPeers(): Map<number, PresenceState>;
|
|
25
|
+
/**
|
|
26
|
+
* Push a manual state update. Useful when the local user changes
|
|
27
|
+
* name / color, or to explicitly clear the selection on focus loss.
|
|
28
|
+
*/
|
|
29
|
+
setLocalState(state: Partial<PresenceState>): void;
|
|
30
|
+
/** Tear down: unsubscribe from awareness + editor events. Clears
|
|
31
|
+
* the local published state so peers see the user leave. */
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wire an `Awareness` instance into the editor.
|
|
36
|
+
*
|
|
37
|
+
* - Publishes the local user's `user` + `selection` state on every
|
|
38
|
+
* selection change.
|
|
39
|
+
* - Subscribes to remote peers' state via awareness `"change"`
|
|
40
|
+
* events; surfaces them via `onChange(peers)` and `getPeers()`.
|
|
41
|
+
*
|
|
42
|
+
* Does NOT render an overlay — pass the peers to your own renderer,
|
|
43
|
+
* or wire `attachPresenceOverlay` from this package for the default
|
|
44
|
+
* caret + range-highlight rendering.
|
|
45
|
+
*
|
|
46
|
+
* Returns a `PresenceHandle` with manual-update and teardown.
|
|
47
|
+
*/
|
|
48
|
+
export declare function attachPresence(editor: Editor, awareness: AwarenessLike, opts: AttachPresenceOptions): PresenceHandle;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal awareness interface — a structural subset of
|
|
3
|
+
* `y-protocols/awareness`'s `Awareness` class. We don't depend on
|
|
4
|
+
* `y-protocols` from `@sobree/core` (it's an optional concern); the
|
|
5
|
+
* interface here is structurally compatible so an `Awareness`
|
|
6
|
+
* instance passes typecheck without any glue.
|
|
7
|
+
*
|
|
8
|
+
* The shape:
|
|
9
|
+
*
|
|
10
|
+
* - `clientID` — the local peer's id (matches `ydoc.clientID`)
|
|
11
|
+
* - `setLocalState(state | null)` — publish your own state
|
|
12
|
+
* - `setLocalStateField(field, value)` — patch one field
|
|
13
|
+
* - `getStates()` — read every peer's state as `Map<clientID, state>`
|
|
14
|
+
* - `on("change", cb) / off("change", cb)` — subscribe to changes
|
|
15
|
+
*/
|
|
16
|
+
export interface AwarenessLike {
|
|
17
|
+
readonly clientID: number;
|
|
18
|
+
setLocalState(state: Record<string, unknown> | null): void;
|
|
19
|
+
setLocalStateField(field: string, value: unknown): void;
|
|
20
|
+
getStates(): Map<number, Record<string, unknown>>;
|
|
21
|
+
on(event: "change" | "update", cb: (changes: AwarenessChanges, origin: unknown) => void): void;
|
|
22
|
+
off(event: "change" | "update", cb: (changes: AwarenessChanges, origin: unknown) => void): void;
|
|
23
|
+
}
|
|
24
|
+
export interface AwarenessChanges {
|
|
25
|
+
added: number[];
|
|
26
|
+
updated: number[];
|
|
27
|
+
removed: number[];
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presence — remote peer cursors and selection highlights.
|
|
3
|
+
*
|
|
4
|
+
* Builds on the Y.Doc backing in `@sobree/core`. Pass an `Awareness`
|
|
5
|
+
* instance (from `y-protocols/awareness`, surfaced via
|
|
6
|
+
* `@sobree/collab-providers` handles) and a user identity; pluck
|
|
7
|
+
* remote peers via `getPeers()` or render the default overlay.
|
|
8
|
+
*
|
|
9
|
+
* See `concepts/architecture` ("Y.Doc store") for the bigger picture
|
|
10
|
+
* and `api/create-sobree` ("Y.Doc + collaboration") for the
|
|
11
|
+
* multi-phase roadmap.
|
|
12
|
+
*/
|
|
13
|
+
export { attachPresence } from './attach';
|
|
14
|
+
export type { AttachPresenceOptions, PresenceHandle, } from './attach';
|
|
15
|
+
export { attachPresenceOverlay } from './overlay';
|
|
16
|
+
export type { AttachPresenceOverlayOptions, PresenceOverlayHandle, } from './overlay';
|
|
17
|
+
export { isPresenceState, presenceSelectionFromEditor, } from './state';
|
|
18
|
+
export type { PresenceState, PresenceSelection, PresenceUser, } from './state';
|
|
19
|
+
export type { AwarenessLike, AwarenessChanges } from './awareness';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Editor } from '../editor';
|
|
2
|
+
import { AwarenessLike } from './awareness';
|
|
3
|
+
import { PresenceHandle, AttachPresenceOptions } from './attach';
|
|
4
|
+
export interface AttachPresenceOverlayOptions extends AttachPresenceOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Element to render the overlay into. Should be an ancestor of the
|
|
7
|
+
* paper stack and `position: relative` (or `position: absolute`).
|
|
8
|
+
* The factory adds `position: relative` if `static`. Most embedders
|
|
9
|
+
* pass `createSobree(...).viewport.slot`.
|
|
10
|
+
*/
|
|
11
|
+
container: HTMLElement;
|
|
12
|
+
/**
|
|
13
|
+
* Element whose `data-block-id` lookup resolves to block DOM
|
|
14
|
+
* elements. Default: `container`. Pass a different host if your
|
|
15
|
+
* paper stack is layered separately.
|
|
16
|
+
*/
|
|
17
|
+
blockHost?: HTMLElement;
|
|
18
|
+
}
|
|
19
|
+
export interface PresenceOverlayHandle {
|
|
20
|
+
/** Underlying presence handle — read peers, push manual updates. */
|
|
21
|
+
readonly presence: PresenceHandle;
|
|
22
|
+
/** Force re-render (e.g. after a layout-affecting change the
|
|
23
|
+
* overlay didn't observe directly). */
|
|
24
|
+
refresh(): void;
|
|
25
|
+
/** Tear down DOM + listeners. */
|
|
26
|
+
destroy(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare function attachPresenceOverlay(editor: Editor, awareness: AwarenessLike, opts: AttachPresenceOverlayOptions): PresenceOverlayHandle;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Selection as EditorSelection } from '../doc/api';
|
|
2
|
+
/** Per-peer state published to other peers via Yjs awareness. */
|
|
3
|
+
export interface PresenceState {
|
|
4
|
+
user: PresenceUser;
|
|
5
|
+
/**
|
|
6
|
+
* Where this peer's cursor / range sits. `null` if focus left the
|
|
7
|
+
* editor. Position is identified by `blockId` (stable across the
|
|
8
|
+
* Y.Doc — see `BlockRegistry`) + character offsets, so it survives
|
|
9
|
+
* re-pagination and structural mutations.
|
|
10
|
+
*/
|
|
11
|
+
selection: PresenceSelection | null;
|
|
12
|
+
}
|
|
13
|
+
export interface PresenceUser {
|
|
14
|
+
/** Stable peer id (a UUID, an auth user id, etc). Random per page-load
|
|
15
|
+
* is fine if you don't have auth. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Display name for overlays / tooltips. */
|
|
18
|
+
name: string;
|
|
19
|
+
/** CSS color for the caret / range highlight (e.g. "#f59e0b"). */
|
|
20
|
+
color: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PresenceSelection {
|
|
23
|
+
/** BlockRegistry id of the focused block. Both `anchor` and `focus`
|
|
24
|
+
* point inside this block (cross-block selections are clipped to
|
|
25
|
+
* the focus block for the overlay). */
|
|
26
|
+
blockId: string;
|
|
27
|
+
/** Caret position when anchor === focus; range start otherwise. */
|
|
28
|
+
anchor: number;
|
|
29
|
+
/** Caret position when anchor === focus; range end otherwise. */
|
|
30
|
+
focus: number;
|
|
31
|
+
}
|
|
32
|
+
/** Convenience view: the editor's live `Selection` collapsed to a
|
|
33
|
+
* `PresenceSelection` referencing the focused block by id. */
|
|
34
|
+
export declare function presenceSelectionFromEditor(sel: EditorSelection): PresenceSelection | null;
|
|
35
|
+
/** Type guard — narrows an unknown awareness state value to PresenceState. */
|
|
36
|
+
export declare function isPresenceState(value: unknown): value is PresenceState;
|