@stll/folio-core 0.1.2 → 0.2.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/dist/docx/headerFooterParser.js +3 -0
- package/dist/docx/headerFooterVerbatim.d.ts +19 -0
- package/dist/docx/headerFooterVerbatim.js +25 -0
- package/dist/docx/index.d.ts +2 -0
- package/dist/docx/index.js +2 -0
- package/dist/docx/metafileRaster.d.ts +30 -0
- package/dist/docx/metafileRaster.js +80 -0
- package/dist/docx/paragraphParser.js +5 -1
- package/dist/docx/parser.d.ts +12 -2
- package/dist/docx/parser.js +31 -1
- package/dist/docx/serializer/headerFooterSerializer.js +3 -0
- package/dist/docx/styleParser.js +2 -2
- package/dist/docx/tableParser.js +2 -3
- package/dist/docx/unzip.js +7 -1
- package/dist/docx/wrapTypes.d.ts +8 -1
- package/dist/docx/wrapTypes.js +0 -8
- package/dist/docx/xmlParser.d.ts +7 -1
- package/dist/docx/xmlParser.js +17 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +2776 -0
- package/dist/i18n/messages/catalogs.gen.js +2742 -0
- package/dist/i18n/messages/messages.gen.d.ts +166 -0
- package/dist/i18n/messages/messages.gen.js +0 -0
- package/dist/i18n/messages.d.ts +13 -0
- package/dist/i18n/messages.js +39 -0
- package/dist/layout-bridge/cellSelectionHighlight.d.ts +18 -0
- package/dist/layout-bridge/cellSelectionHighlight.js +36 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -2
- package/dist/layout-bridge/convert/headerFooterLayout.js +39 -15
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-bridge/headerFooterLayout.d.ts +50 -0
- package/dist/layout-bridge/headerFooterLayout.js +216 -0
- package/dist/layout-bridge/measuring/measureBlocksPipeline.d.ts +33 -0
- package/dist/layout-bridge/measuring/measureBlocksPipeline.js +0 -0
- package/dist/layout-bridge/sectionColumns.d.ts +11 -0
- package/dist/layout-bridge/sectionColumns.js +21 -0
- package/dist/layout-bridge/tableInsertHover.d.ts +42 -0
- package/dist/layout-bridge/tableInsertHover.js +102 -0
- package/dist/layout-engine/index.d.ts +2 -1
- package/dist/layout-engine/index.js +2 -1
- package/dist/layout-engine/measure/lineBreaks.d.ts +10 -0
- package/dist/layout-engine/measure/lineBreaks.js +30 -0
- package/dist/layout-engine/measure/measureContainer.js +1 -1
- package/dist/layout-engine/measure/measureParagraph.js +63 -22
- package/dist/layout-engine/pmPageIndex.d.ts +24 -0
- package/dist/layout-engine/pmPageIndex.js +37 -0
- package/dist/layout-engine/types.d.ts +2 -1
- package/dist/layout-painter/imageLayout.d.ts +101 -0
- package/dist/layout-painter/imageLayout.js +161 -0
- package/dist/layout-painter/renderPage.d.ts +16 -1
- package/dist/layout-painter/renderPage.js +28 -1
- package/dist/layout-painter/renderParagraph.js +2 -1
- package/dist/layout-painter/sdtBoundary.d.ts +24 -1
- package/dist/layout-painter/sdtBoundary.js +40 -1
- package/dist/managers/AutoSaveManager.d.ts +89 -0
- package/dist/managers/AutoSaveManager.js +279 -0
- package/dist/managers/ClipboardManager.d.ts +31 -0
- package/dist/managers/ClipboardManager.js +147 -0
- package/dist/paged-layout/paragraphFlash.d.ts +17 -0
- package/dist/paged-layout/paragraphFlash.js +56 -0
- package/dist/paged-layout/paragraphFlashTypes.d.ts +18 -0
- package/dist/paged-layout/paragraphFlashTypes.js +0 -0
- package/dist/paged-layout/rangeProjection.d.ts +23 -1
- package/dist/paged-layout/rangeProjection.js +34 -1
- package/dist/prosemirror/cellDragSelection.d.ts +34 -0
- package/dist/prosemirror/cellDragSelection.js +69 -0
- package/dist/prosemirror/commands/sectionBreak.d.ts +15 -0
- package/dist/prosemirror/commands/sectionBreak.js +54 -0
- package/dist/prosemirror/commentIdAllocator.d.ts +32 -0
- package/dist/prosemirror/commentIdAllocator.js +35 -0
- package/dist/prosemirror/commentOps.d.ts +35 -0
- package/dist/prosemirror/commentOps.js +104 -0
- package/dist/prosemirror/conversion/toProseDoc.js +29 -6
- package/dist/prosemirror/extensions/index.d.ts +3 -0
- package/dist/prosemirror/extensions/index.js +3 -0
- package/dist/prosemirror/imageCommit.d.ts +42 -0
- package/dist/prosemirror/imageCommit.js +118 -0
- package/dist/prosemirror/paraText.d.ts +26 -0
- package/dist/prosemirror/paraText.js +73 -0
- package/dist/prosemirror/plugins/templateDirectives.d.ts +21 -1
- package/dist/prosemirror/plugins/templateDirectives.js +40 -1
- package/dist/prosemirror/queries.d.ts +46 -0
- package/dist/prosemirror/queries.js +74 -0
- package/dist/prosemirror/styles/styleResolver.d.ts +43 -1
- package/dist/prosemirror/styles/styleResolver.js +34 -0
- package/dist/prosemirror/tableResize.d.ts +49 -0
- package/dist/prosemirror/tableResize.js +162 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +106 -0
- package/dist/prosemirror/utils/extractTrackedChanges.js +379 -0
- package/dist/prosemirror/utils/visualLineNavigation.d.ts +27 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +217 -0
- package/dist/style-engine/index.d.ts +2 -2
- package/dist/style-engine/styleEngine.d.ts +14 -1
- package/dist/style-engine/styleEngine.js +15 -0
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +12 -1
- package/dist/utils/findVerticalScrollParent.d.ts +20 -0
- package/dist/utils/findVerticalScrollParent.js +30 -0
- package/dist/utils/fontResolver.d.ts +22 -3
- package/dist/utils/fontResolver.js +216 -31
- package/dist/utils/headerFooter.js +2 -0
- package/package.json +9 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//#region src/i18n/messages/messages.gen.d.ts
|
|
2
|
+
type Messages = {
|
|
3
|
+
"folio": {
|
|
4
|
+
"acceptChange": "Accept Change";
|
|
5
|
+
"accessDenied": "Access denied. You may not have permission to access this file.";
|
|
6
|
+
"alignCenter": "Center";
|
|
7
|
+
"alignLeft": "Align Left";
|
|
8
|
+
"alignRight": "Align Right";
|
|
9
|
+
"alignmentGroup": "Alignment";
|
|
10
|
+
"bold": "Bold";
|
|
11
|
+
"boldShortcut": "Bold (Ctrl+B)";
|
|
12
|
+
"bulletList": "Bullet List";
|
|
13
|
+
"checkingDocxEditDescription": "stella is still checking whether this DOCX can be edited safely. Try again when the document finishes loading.";
|
|
14
|
+
"checkingDocxEditTitle": "Checking document";
|
|
15
|
+
"clearDate": "Clear date";
|
|
16
|
+
"clipboardReadFailed": "Couldn't read the clipboard. Check clipboard permissions and try again.";
|
|
17
|
+
"comment": "Comment";
|
|
18
|
+
"comments": {
|
|
19
|
+
"addPlaceholder": "Add a comment...";
|
|
20
|
+
"cancel": "Cancel";
|
|
21
|
+
"hideAll": "Hide comments";
|
|
22
|
+
"moreReplies": "{count} more replies";
|
|
23
|
+
"noComments": "No comments yet.";
|
|
24
|
+
"reply": "Reply";
|
|
25
|
+
"replyPlaceholder": "Reply or add others with @";
|
|
26
|
+
"showAll": "All comments";
|
|
27
|
+
"unknownAuthor": "Unknown";
|
|
28
|
+
"visibility": "Comments";
|
|
29
|
+
};
|
|
30
|
+
"contentControlDateAriaLabel": "Pick a date";
|
|
31
|
+
"contentControlDropdownAriaLabel": "Dropdown options";
|
|
32
|
+
"contentControlDropdownNoOptions": "No options";
|
|
33
|
+
"copy": "Copy";
|
|
34
|
+
"copyLink": "Copy link";
|
|
35
|
+
"cut": "Cut";
|
|
36
|
+
"decreaseIndent": "Decrease Indent";
|
|
37
|
+
"delete": "Delete";
|
|
38
|
+
"deleteColumn": "Delete column";
|
|
39
|
+
"deleteRow": "Delete row";
|
|
40
|
+
"deleted": "Deleted";
|
|
41
|
+
"discardChanges": "Discard";
|
|
42
|
+
"dismiss": "Dismiss";
|
|
43
|
+
"displayText": "Display text";
|
|
44
|
+
"doneEditing": "Done editing";
|
|
45
|
+
"doubleClickFooter": "Double-click to add footer";
|
|
46
|
+
"doubleClickHeader": "Double-click to add header";
|
|
47
|
+
"editAuthRequired": "Refresh stella and sign in again before editing this document.";
|
|
48
|
+
"editDownloadFailed": "The document could not be downloaded for editing.";
|
|
49
|
+
"editLink": "Edit link";
|
|
50
|
+
"editOpenFailed": "The document could not be opened for editing.";
|
|
51
|
+
"editOpenFailedTitle": "Could not open DOCX";
|
|
52
|
+
"editPermissionDenied": "You do not have permission to edit this document.";
|
|
53
|
+
"editSaveFailedTitle": "Could not save DOCX";
|
|
54
|
+
"editSessionTakenOver": "Another tab, window, or device opened this document for editing, so this session was released. Unsaved local changes are preserved — reopen the doc to continue.";
|
|
55
|
+
"findReplace": {
|
|
56
|
+
"close": "Close search";
|
|
57
|
+
"find": "Find";
|
|
58
|
+
"findAndReplace": "Find and replace";
|
|
59
|
+
"findPlaceholder": "Search document...";
|
|
60
|
+
"findText": "Find text";
|
|
61
|
+
"matchCase": "Match case";
|
|
62
|
+
"matchCounter": "{current} / {total}";
|
|
63
|
+
"next": "Next match";
|
|
64
|
+
"nextShortcut": "Next match (Enter)";
|
|
65
|
+
"noResults": "No results";
|
|
66
|
+
"previous": "Previous match";
|
|
67
|
+
"previousShortcut": "Previous match (Shift+Enter)";
|
|
68
|
+
"replaceAll": "Replace all";
|
|
69
|
+
"replaceCurrent": "Replace current match";
|
|
70
|
+
"replacePlaceholder": "Replacement text...";
|
|
71
|
+
"replaceText": "Replacement text";
|
|
72
|
+
"wholeWords": "Whole words";
|
|
73
|
+
};
|
|
74
|
+
"finishEditing": "Finish editing";
|
|
75
|
+
"fontColor": "Font color";
|
|
76
|
+
"fontGroup": "Font";
|
|
77
|
+
"fontSize": "Font size";
|
|
78
|
+
"formatPainter": "Format Painter";
|
|
79
|
+
"formatPainterShortcut": "Format Painter (Ctrl+Shift+C)";
|
|
80
|
+
"formattingToolbar": "Formatting toolbar";
|
|
81
|
+
"hideDetails": "Hide details";
|
|
82
|
+
"historyGroup": "History";
|
|
83
|
+
"increaseIndent": "Increase Indent";
|
|
84
|
+
"insertColumnLeft": "Insert column left";
|
|
85
|
+
"insertColumnRight": "Insert column right";
|
|
86
|
+
"insertGroup": "Insert";
|
|
87
|
+
"insertImage": "Insert image";
|
|
88
|
+
"insertPageBreak": "Insert page break";
|
|
89
|
+
"insertRowAbove": "Insert row above";
|
|
90
|
+
"insertRowBelow": "Insert row below";
|
|
91
|
+
"insertTable": "Insert table";
|
|
92
|
+
"insertTableOfContents": "Insert table of contents";
|
|
93
|
+
"inserted": "Inserted";
|
|
94
|
+
"italic": "Italic";
|
|
95
|
+
"italicShortcut": "Italic (Ctrl+I)";
|
|
96
|
+
"justify": "Justify";
|
|
97
|
+
"listFormatting": "List formatting";
|
|
98
|
+
"listsGroup": "Lists";
|
|
99
|
+
"loadingDocument": "Loading document...";
|
|
100
|
+
"loadingEditor": "Loading editor...";
|
|
101
|
+
"lockFile": "Lock file";
|
|
102
|
+
"markupView": {
|
|
103
|
+
"allMarkup": "All Markup";
|
|
104
|
+
"noMarkup": "No Markup";
|
|
105
|
+
"original": "Original";
|
|
106
|
+
"simple": "Simple Markup";
|
|
107
|
+
};
|
|
108
|
+
"moreFormatting": "More formatting";
|
|
109
|
+
"networkError": "Network error. Please check your internet connection and try again.";
|
|
110
|
+
"nextChange": "Next Change";
|
|
111
|
+
"numberedList": "Numbered List";
|
|
112
|
+
"parseError": "The document could not be parsed. It may be corrupted or in an unsupported format.";
|
|
113
|
+
"paste": "Paste";
|
|
114
|
+
"pasteUnformatted": "Paste without formatting";
|
|
115
|
+
"previousChange": "Previous Change";
|
|
116
|
+
"printRedirect": "To print, download the document and open it in your word processor.";
|
|
117
|
+
"redo": "Redo";
|
|
118
|
+
"redoShortcut": "Redo (Ctrl+Y)";
|
|
119
|
+
"rejectChange": "Reject Change";
|
|
120
|
+
"removeLink": "Remove link";
|
|
121
|
+
"renderErrorDescription": "Something went wrong showing this part of the document. Reload the page — if it keeps happening, let us know.";
|
|
122
|
+
"ruler": {
|
|
123
|
+
"bottomMargin": "Bottom margin";
|
|
124
|
+
"firstLineIndent": "First line indent";
|
|
125
|
+
"horizontal": "Horizontal ruler";
|
|
126
|
+
"leftIndent": "Left indent";
|
|
127
|
+
"rightIndent": "Right indent";
|
|
128
|
+
"toggle": "Ruler";
|
|
129
|
+
"topMargin": "Top margin";
|
|
130
|
+
"vertical": "Vertical ruler";
|
|
131
|
+
};
|
|
132
|
+
"saveCheckpointFailedDescription": "The DOCX was created locally, but stella could not upload the checkpoint. Keep the editor open and try saving again.";
|
|
133
|
+
"saveCheckpointFailedTitle": "Could not upload save";
|
|
134
|
+
"saveEditorUnavailableDescription": "The editor is not ready yet. Wait for the document to finish loading, then try saving again.";
|
|
135
|
+
"saveEditorUnavailableTitle": "Editor not ready";
|
|
136
|
+
"saveSerializeFailedDescription": "Folio could not produce a safe DOCX file from the current edit. Your session is still open.";
|
|
137
|
+
"saveSerializeFailedTitle": "Could not create DOCX";
|
|
138
|
+
"savingDocument": "Saving...";
|
|
139
|
+
"selectAll": "Select all";
|
|
140
|
+
"showDetails": "Show details";
|
|
141
|
+
"somethingWentWrong": "Something went wrong";
|
|
142
|
+
"synced": "Synced";
|
|
143
|
+
"syncing": "Syncing";
|
|
144
|
+
"tableBordersAll": "All borders";
|
|
145
|
+
"tableBordersNone": "No borders";
|
|
146
|
+
"textDirection": "Text direction";
|
|
147
|
+
"textEditingMenu": "Text editing menu";
|
|
148
|
+
"textFormattingGroup": "Text formatting";
|
|
149
|
+
"toggleTrackChanges": "Toggle Track Changes";
|
|
150
|
+
"trackingOff": "Tracking: Off";
|
|
151
|
+
"trackingOn": "Tracking: On";
|
|
152
|
+
"tryAgain": "Try Again";
|
|
153
|
+
"underline": "Underline";
|
|
154
|
+
"underlineShortcut": "Underline (Ctrl+U)";
|
|
155
|
+
"undo": "Undo";
|
|
156
|
+
"undoShortcut": "Undo (Ctrl+Z)";
|
|
157
|
+
"unlockFile": "Unlock file";
|
|
158
|
+
"unlockForEditing": "Unlock to edit";
|
|
159
|
+
"unsupportedDocxEditDescription": "This DOCX contains structures Folio cannot safely rewrite. To avoid corrupting the file, download it and edit it in Word.";
|
|
160
|
+
"unsupportedDocxEditTitle": "Editing blocked";
|
|
161
|
+
"viewGroup": "View";
|
|
162
|
+
"zoomGroup": "Zoom";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
//#endregion
|
|
166
|
+
export type { Messages as default };
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/i18n/messages.d.ts
|
|
2
|
+
declare const FOLIO_LOCALES: readonly ["en", "de", "fr", "es", "cs", "ar", "et", "he", "hi", "hu", "lt", "lv", "pl", "pt-BR", "sk", "tr", "zh-CN"];
|
|
3
|
+
type FolioLocale = (typeof FOLIO_LOCALES)[number];
|
|
4
|
+
type FolioMessageTree = {
|
|
5
|
+
[key: string]: string | FolioMessageTree;
|
|
6
|
+
};
|
|
7
|
+
type FolioMessages = {
|
|
8
|
+
folio: FolioMessageTree;
|
|
9
|
+
};
|
|
10
|
+
declare const isFolioLocale: (locale: string) => locale is FolioLocale;
|
|
11
|
+
declare const getFolioMessages: (locale: string) => FolioMessages;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { FOLIO_LOCALES, FolioLocale, FolioMessages, getFolioMessages, isFolioLocale };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CATALOGS as CATALOGS$1 } from "./messages/catalogs.gen.js";
|
|
2
|
+
//#region src/i18n/messages.ts
|
|
3
|
+
const FOLIO_LOCALES = [
|
|
4
|
+
"en",
|
|
5
|
+
"de",
|
|
6
|
+
"fr",
|
|
7
|
+
"es",
|
|
8
|
+
"cs",
|
|
9
|
+
"ar",
|
|
10
|
+
"et",
|
|
11
|
+
"he",
|
|
12
|
+
"hi",
|
|
13
|
+
"hu",
|
|
14
|
+
"lt",
|
|
15
|
+
"lv",
|
|
16
|
+
"pl",
|
|
17
|
+
"pt-BR",
|
|
18
|
+
"sk",
|
|
19
|
+
"tr",
|
|
20
|
+
"zh-CN"
|
|
21
|
+
];
|
|
22
|
+
const CATALOGS = CATALOGS$1;
|
|
23
|
+
const FOLIO_LOCALE_SET = new Set(FOLIO_LOCALES);
|
|
24
|
+
const isFolioLocale = (locale) => FOLIO_LOCALE_SET.has(locale);
|
|
25
|
+
const baseLanguageOf = (locale) => {
|
|
26
|
+
try {
|
|
27
|
+
return new Intl.Locale(locale).language;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const getFolioMessages = (locale) => {
|
|
33
|
+
if (isFolioLocale(locale)) return CATALOGS[locale];
|
|
34
|
+
const base = baseLanguageOf(locale);
|
|
35
|
+
if (base !== null && isFolioLocale(base)) return CATALOGS[base];
|
|
36
|
+
return CATALOGS.en;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { FOLIO_LOCALES, getFolioMessages, isFolioLocale };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EditorState } from "prosemirror-state";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-bridge/cellSelectionHighlight.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Apply the `.layout-table-cell-selected` class to painted layout cells
|
|
6
|
+
* matching a CellSelection in the PM state. Clears the class everywhere
|
|
7
|
+
* (within scope) first so toggling off (or moving to a TextSelection) erases
|
|
8
|
+
* prior highlights.
|
|
9
|
+
*
|
|
10
|
+
* Duck-types CellSelection via `$anchorCell` / `forEachCell` rather than
|
|
11
|
+
* `instanceof` to dodge bundling issues across `prosemirror-tables` copies
|
|
12
|
+
* (the same trick used inline in the selection-overlay update).
|
|
13
|
+
*/
|
|
14
|
+
declare function applyCellSelectionHighlight(pagesContainer: HTMLElement, state: EditorState, options?: {
|
|
15
|
+
scope?: "body" | "header" | "footer";
|
|
16
|
+
}): void;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { applyCellSelectionHighlight };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/layout-bridge/cellSelectionHighlight.ts
|
|
2
|
+
/**
|
|
3
|
+
* Apply the `.layout-table-cell-selected` class to painted layout cells
|
|
4
|
+
* matching a CellSelection in the PM state. Clears the class everywhere
|
|
5
|
+
* (within scope) first so toggling off (or moving to a TextSelection) erases
|
|
6
|
+
* prior highlights.
|
|
7
|
+
*
|
|
8
|
+
* Duck-types CellSelection via `$anchorCell` / `forEachCell` rather than
|
|
9
|
+
* `instanceof` to dodge bundling issues across `prosemirror-tables` copies
|
|
10
|
+
* (the same trick used inline in the selection-overlay update).
|
|
11
|
+
*/
|
|
12
|
+
function applyCellSelectionHighlight(pagesContainer, state, options = {}) {
|
|
13
|
+
const scope = options.scope ?? "body";
|
|
14
|
+
const scopeClass = scope === "body" ? "layout-page-content" : `layout-page-${scope}`;
|
|
15
|
+
const scopeSelector = `.${scopeClass} .layout-table-cell`;
|
|
16
|
+
const prevSelected = pagesContainer.querySelectorAll(`.${scopeClass} .layout-table-cell-selected`);
|
|
17
|
+
for (const el of Array.from(prevSelected)) el.classList.remove("layout-table-cell-selected");
|
|
18
|
+
const sel = state.selection;
|
|
19
|
+
if (!("$anchorCell" in sel && typeof sel.forEachCell === "function") || !sel.forEachCell) return;
|
|
20
|
+
const selectedRanges = [];
|
|
21
|
+
sel.forEachCell((node, pos) => {
|
|
22
|
+
selectedRanges.push([pos, pos + node.nodeSize]);
|
|
23
|
+
});
|
|
24
|
+
const allCells = pagesContainer.querySelectorAll(scopeSelector);
|
|
25
|
+
for (const cellEl of Array.from(allCells)) {
|
|
26
|
+
const pmStartAttr = cellEl.dataset["pmStart"];
|
|
27
|
+
if (pmStartAttr === void 0) continue;
|
|
28
|
+
const pmPos = Number(pmStartAttr);
|
|
29
|
+
for (const [start, end] of selectedRanges) if (pmPos >= start && pmPos < end) {
|
|
30
|
+
cellEl.classList.add("layout-table-cell-selected");
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { applyCellSelectionHighlight };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
-
import { FlowBlock, HeaderFooterContent, ImageRun, Measure, PageMargins } from "../../layout-engine/types.js";
|
|
2
|
+
import { FlowBlock, HeaderFooterContent, ImageRun, ImageRunPosition, Measure, PageMargins } from "../../layout-engine/types.js";
|
|
3
3
|
import { MeasureBlocksFn } from "./footnoteLayout.js";
|
|
4
4
|
import { Node } from "prosemirror-model";
|
|
5
5
|
|
|
@@ -13,6 +13,7 @@ type HeaderFooterMetrics = {
|
|
|
13
13
|
margins: PageMargins;
|
|
14
14
|
};
|
|
15
15
|
declare function normalizeHeaderFooterMeasureBlocks(blocks: FlowBlock[]): FlowBlock[];
|
|
16
|
+
declare function resolveHeaderFooterPositionedVisualTop(position: ImageRunPosition | undefined, elementHeight: number, sourceY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
|
|
16
17
|
declare function resolveHeaderFooterVisualTop(run: ImageRun, paragraphY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
|
|
17
18
|
declare function calculateHeaderFooterVisualBounds(blocks: FlowBlock[], measures: Measure[], flowHeight: number, metrics: HeaderFooterMetrics): {
|
|
18
19
|
visualTop: number;
|
|
@@ -71,4 +72,4 @@ declare function convertHeaderFooterToContent(headerFooter: document_d_exports.H
|
|
|
71
72
|
*/
|
|
72
73
|
declare function convertHeaderFooterPmDocToContent(pmDoc: Node | null | undefined, contentWidth: number, metrics: HeaderFooterMetrics, options: Omit<ConvertHeaderFooterOptions, "styles">): HeaderFooterContent | undefined;
|
|
73
74
|
//#endregion
|
|
74
|
-
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterVisualTop };
|
|
75
|
+
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -98,28 +98,43 @@ function normalizeTableBlock(block) {
|
|
|
98
98
|
function getPositionAlignment(axis) {
|
|
99
99
|
return axis?.align ?? axis?.alignment;
|
|
100
100
|
}
|
|
101
|
-
function
|
|
101
|
+
function resolveHeaderFooterPositionedVisualTop(position, elementHeight, sourceY, flowHeight, metrics) {
|
|
102
102
|
const flowTop = metrics.section === "header" ? metrics.margins.header ?? 48 : metrics.pageSize.h - (metrics.margins.footer ?? 48) - flowHeight;
|
|
103
|
-
const vertical =
|
|
104
|
-
if (!vertical) return
|
|
103
|
+
const vertical = position?.vertical;
|
|
104
|
+
if (!vertical) return sourceY;
|
|
105
105
|
const align = getPositionAlignment(vertical);
|
|
106
106
|
const offsetPx = vertical.posOffset !== void 0 ? emuToPixels(vertical.posOffset) : void 0;
|
|
107
107
|
if (vertical.relativeTo === "page") {
|
|
108
108
|
if (offsetPx !== void 0) return offsetPx - flowTop;
|
|
109
109
|
if (align === "top") return -flowTop;
|
|
110
|
-
if (align === "bottom") return metrics.pageSize.h -
|
|
111
|
-
if (align === "center") return (metrics.pageSize.h -
|
|
110
|
+
if (align === "bottom") return metrics.pageSize.h - elementHeight - flowTop;
|
|
111
|
+
if (align === "center") return (metrics.pageSize.h - elementHeight) / 2 - flowTop;
|
|
112
112
|
}
|
|
113
113
|
if (vertical.relativeTo === "margin") {
|
|
114
114
|
const marginTop = metrics.margins.top;
|
|
115
115
|
const marginHeight = metrics.pageSize.h - metrics.margins.top - metrics.margins.bottom;
|
|
116
116
|
if (offsetPx !== void 0) return marginTop + offsetPx - flowTop;
|
|
117
117
|
if (align === "top") return marginTop - flowTop;
|
|
118
|
-
if (align === "bottom") return marginTop + marginHeight -
|
|
119
|
-
if (align === "center") return marginTop + (marginHeight -
|
|
118
|
+
if (align === "bottom") return marginTop + marginHeight - elementHeight - flowTop;
|
|
119
|
+
if (align === "center") return marginTop + (marginHeight - elementHeight) / 2 - flowTop;
|
|
120
120
|
}
|
|
121
|
-
if (offsetPx !== void 0) return
|
|
122
|
-
return
|
|
121
|
+
if (offsetPx !== void 0) return sourceY + offsetPx;
|
|
122
|
+
return sourceY;
|
|
123
|
+
}
|
|
124
|
+
function resolveHeaderFooterVisualTop(run, paragraphY, flowHeight, metrics) {
|
|
125
|
+
return resolveHeaderFooterPositionedVisualTop(run.position, run.height, paragraphY, flowHeight, metrics);
|
|
126
|
+
}
|
|
127
|
+
function pageBandInHeaderFooterCoords(metrics, flowHeight) {
|
|
128
|
+
const flowTop = metrics.section === "header" ? metrics.margins.header ?? 48 : metrics.pageSize.h - (metrics.margins.footer ?? 48) - flowHeight;
|
|
129
|
+
return {
|
|
130
|
+
top: -flowTop,
|
|
131
|
+
bottom: metrics.pageSize.h - flowTop
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** True when a float's box overlaps the page band in header/footer coordinates. */
|
|
135
|
+
function floatIntersectsPageBand(blockTop, blockBottom, metrics, flowHeight) {
|
|
136
|
+
const band = pageBandInHeaderFooterCoords(metrics, flowHeight);
|
|
137
|
+
return blockBottom > band.top && blockTop < band.bottom;
|
|
123
138
|
}
|
|
124
139
|
function resolveHeaderFooterFloatingTableVisualTop(floating, measure, sourceY, flowHeight, metrics) {
|
|
125
140
|
const flowTop = metrics.section === "header" ? metrics.margins.header ?? 48 : metrics.pageSize.h - (metrics.margins.footer ?? 48) - flowHeight;
|
|
@@ -156,8 +171,10 @@ function calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics
|
|
|
156
171
|
if (run.kind !== "image") continue;
|
|
157
172
|
if (!run.position && !isFloatingImageRun(run)) continue;
|
|
158
173
|
const runTop = resolveHeaderFooterVisualTop(run, paragraphStartY, flowHeight, metrics);
|
|
174
|
+
const runBottom = runTop + run.height;
|
|
175
|
+
if (!floatIntersectsPageBand(runTop, runBottom, metrics, flowHeight)) continue;
|
|
159
176
|
visualTop = Math.min(visualTop, runTop);
|
|
160
|
-
visualBottom = Math.max(visualBottom,
|
|
177
|
+
visualBottom = Math.max(visualBottom, runBottom);
|
|
161
178
|
}
|
|
162
179
|
cursorY = paragraphBottomY;
|
|
163
180
|
} else {
|
|
@@ -178,11 +195,18 @@ function calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics
|
|
|
178
195
|
cursorY = blockBottomY;
|
|
179
196
|
} else if (block.kind === "table" && block.floating && measure.kind === "table") {
|
|
180
197
|
const blockTop = resolveHeaderFooterFloatingTableVisualTop(block.floating, measure, cursorY, flowHeight, metrics);
|
|
181
|
-
|
|
182
|
-
|
|
198
|
+
const blockBottom = blockTop + blockHeight;
|
|
199
|
+
if (floatIntersectsPageBand(blockTop, blockBottom, metrics, flowHeight)) {
|
|
200
|
+
visualTop = Math.min(visualTop, blockTop);
|
|
201
|
+
visualBottom = Math.max(visualBottom, blockBottom);
|
|
202
|
+
}
|
|
183
203
|
} else if (block.kind === "textBox" && isPositionedHeaderFooterTextBoxBlock(block) && measure.kind === "textBox") {
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
const blockTop = resolveHeaderFooterPositionedVisualTop(block.position, measure.height, cursorY, flowHeight, metrics);
|
|
205
|
+
const blockBottom = blockTop + measure.height;
|
|
206
|
+
if (floatIntersectsPageBand(blockTop, blockBottom, metrics, flowHeight)) {
|
|
207
|
+
visualTop = Math.min(visualTop, blockTop);
|
|
208
|
+
visualBottom = Math.max(visualBottom, blockBottom);
|
|
209
|
+
}
|
|
186
210
|
}
|
|
187
211
|
}
|
|
188
212
|
}
|
|
@@ -429,4 +453,4 @@ function serializeRunFmt(run) {
|
|
|
429
453
|
return JSON.stringify(out);
|
|
430
454
|
}
|
|
431
455
|
//#endregion
|
|
432
|
-
export { calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterVisualTop };
|
|
456
|
+
export { calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -814,6 +814,7 @@ function convertParagraph(node, startPos, options) {
|
|
|
814
814
|
id: nextBlockId(),
|
|
815
815
|
runs,
|
|
816
816
|
attrs,
|
|
817
|
+
...pmAttrs.paraId ? { paraId: pmAttrs.paraId } : {},
|
|
817
818
|
...bookmarkNames && bookmarkNames.length > 0 ? { bookmarks: bookmarkNames } : {},
|
|
818
819
|
pmStart: startPos,
|
|
819
820
|
pmEnd: startPos + node.nodeSize
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { EditorView } from "prosemirror-view";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-bridge/headerFooterLayout.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Drop the cached HF host + span lists. Hosts/painters call this after
|
|
6
|
+
* a repaint (or HF mode toggle) so the next caret / selection compute
|
|
7
|
+
* re-walks the DOM. Public so adapters can call it from their painter
|
|
8
|
+
* commit signal.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
declare function invalidateHfDomCache(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Viewport-relative caret rect for a persistent HF EditorView's selection
|
|
15
|
+
* head. Resolves against the painter's `data-pm-start`/`data-pm-end` spans
|
|
16
|
+
* inside `.layout-page-header` / `.layout-page-footer`. The same HF doc is
|
|
17
|
+
* painted on every page (multi-page docs, titlePg), so this walks every
|
|
18
|
+
* candidate host and picks the one whose spans bracket the PM head; falls
|
|
19
|
+
* back to the first so empty paragraphs still resolve to a paragraph anchor.
|
|
20
|
+
*
|
|
21
|
+
* Public so the React + Vue adapters can share a single implementation
|
|
22
|
+
* (`packages/{react,vue}` adapters used to carry byte-identical copies).
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
declare function computeHfCaretRectFromView(view: EditorView, section: "header" | "footer", doc?: globalThis.Document): {
|
|
27
|
+
top: number;
|
|
28
|
+
left: number;
|
|
29
|
+
height: number;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Selection-rect set for a non-empty HF selection, projected against the
|
|
33
|
+
* painted HF spans. Mirror of `computeSelectionRectsFromDom` but scoped to
|
|
34
|
+
* `.layout-page-header` / `.layout-page-footer` instead of the body. Used
|
|
35
|
+
* so the painter draws a visible highlight when the user drag-selects
|
|
36
|
+
* inside a header/footer in edit mode.
|
|
37
|
+
*
|
|
38
|
+
* Returns viewport-relative `{top, left, width, height}` rects. Empty
|
|
39
|
+
* array when selection is collapsed or no painted spans overlap the range.
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
declare function computeHfSelectionRectsFromView(view: EditorView, section: "header" | "footer", doc?: globalThis.Document): Array<{
|
|
44
|
+
top: number;
|
|
45
|
+
left: number;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
}>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { computeHfCaretRectFromView, computeHfSelectionRectsFromView, invalidateHfDomCache };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
//#region src/layout-bridge/headerFooterLayout.ts
|
|
2
|
+
const hfDomCache = {
|
|
3
|
+
header: null,
|
|
4
|
+
footer: null
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Drop the cached HF host + span lists. Hosts/painters call this after
|
|
8
|
+
* a repaint (or HF mode toggle) so the next caret / selection compute
|
|
9
|
+
* re-walks the DOM. Public so adapters can call it from their painter
|
|
10
|
+
* commit signal.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
function invalidateHfDomCache() {
|
|
15
|
+
hfDomCache.header = null;
|
|
16
|
+
hfDomCache.footer = null;
|
|
17
|
+
}
|
|
18
|
+
function getHfDomSnapshot(section, doc) {
|
|
19
|
+
const hosts = doc.querySelectorAll(`.layout-page-${section}`);
|
|
20
|
+
if (hosts.length === 0) return null;
|
|
21
|
+
const win = doc.defaultView;
|
|
22
|
+
const vpCenter = win ? win.innerHeight / 2 : 0;
|
|
23
|
+
let host = hosts[0];
|
|
24
|
+
if (!host) return null;
|
|
25
|
+
let bestDist = Infinity;
|
|
26
|
+
for (const h of Array.from(hosts)) {
|
|
27
|
+
const r = h.getBoundingClientRect();
|
|
28
|
+
const dist = Math.abs((r.top + r.bottom) / 2 - vpCenter);
|
|
29
|
+
if (dist < bestDist) {
|
|
30
|
+
bestDist = dist;
|
|
31
|
+
host = h;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const cached = hfDomCache[section];
|
|
35
|
+
if (cached && cached.host === host && cached.host.isConnected) return cached;
|
|
36
|
+
const spans = Array.from(host.querySelectorAll("span[data-pm-start][data-pm-end]"));
|
|
37
|
+
const ranged = Array.from(host.querySelectorAll("[data-pm-start][data-pm-end]"));
|
|
38
|
+
const snapshot = {
|
|
39
|
+
host,
|
|
40
|
+
spans,
|
|
41
|
+
ranged
|
|
42
|
+
};
|
|
43
|
+
hfDomCache[section] = snapshot;
|
|
44
|
+
return snapshot;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Viewport-relative caret rect for a persistent HF EditorView's selection
|
|
48
|
+
* head. Resolves against the painter's `data-pm-start`/`data-pm-end` spans
|
|
49
|
+
* inside `.layout-page-header` / `.layout-page-footer`. The same HF doc is
|
|
50
|
+
* painted on every page (multi-page docs, titlePg), so this walks every
|
|
51
|
+
* candidate host and picks the one whose spans bracket the PM head; falls
|
|
52
|
+
* back to the first so empty paragraphs still resolve to a paragraph anchor.
|
|
53
|
+
*
|
|
54
|
+
* Public so the React + Vue adapters can share a single implementation
|
|
55
|
+
* (`packages/{react,vue}` adapters used to carry byte-identical copies).
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
function computeHfCaretRectFromView(view, section, doc = globalThis.document) {
|
|
60
|
+
const sel = view.state.selection;
|
|
61
|
+
if (!sel.empty) return null;
|
|
62
|
+
const pmPos = sel.head;
|
|
63
|
+
const snapshot = getHfDomSnapshot(section, doc);
|
|
64
|
+
if (!snapshot) return null;
|
|
65
|
+
const { host, spans } = snapshot;
|
|
66
|
+
for (const span of spans) {
|
|
67
|
+
const start = Number(span.dataset["pmStart"]);
|
|
68
|
+
const end = Number(span.dataset["pmEnd"]);
|
|
69
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) continue;
|
|
70
|
+
if (pmPos >= start && pmPos <= end) {
|
|
71
|
+
const range = host.ownerDocument.createRange();
|
|
72
|
+
const walker = host.ownerDocument.createTreeWalker(span, NodeFilter.SHOW_TEXT);
|
|
73
|
+
let remaining = pmPos - start;
|
|
74
|
+
let textNode = walker.nextNode();
|
|
75
|
+
while (textNode) {
|
|
76
|
+
const len = textNode.data.length;
|
|
77
|
+
if (remaining <= len) {
|
|
78
|
+
try {
|
|
79
|
+
range.setStart(textNode, remaining);
|
|
80
|
+
range.setEnd(textNode, remaining);
|
|
81
|
+
const rect = range.getClientRects()[0] ?? range.getBoundingClientRect();
|
|
82
|
+
if (rect && rect.height > 0) return {
|
|
83
|
+
top: rect.top,
|
|
84
|
+
left: rect.left,
|
|
85
|
+
height: rect.height
|
|
86
|
+
};
|
|
87
|
+
} catch {}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
remaining -= len;
|
|
91
|
+
textNode = walker.nextNode();
|
|
92
|
+
}
|
|
93
|
+
const spanRect = span.getBoundingClientRect();
|
|
94
|
+
const ratio = (pmPos - start) / Math.max(1, end - start);
|
|
95
|
+
return {
|
|
96
|
+
top: spanRect.top,
|
|
97
|
+
left: spanRect.left + spanRect.width * ratio,
|
|
98
|
+
height: spanRect.height
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const anchor = host.querySelector(`[data-pm-start="${pmPos}"]`);
|
|
103
|
+
if (anchor) {
|
|
104
|
+
const rect = anchor.getBoundingClientRect();
|
|
105
|
+
return {
|
|
106
|
+
top: rect.top,
|
|
107
|
+
left: rect.left + 1,
|
|
108
|
+
height: rect.height || 16
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const ranged = snapshot.ranged;
|
|
112
|
+
let bestEl = null;
|
|
113
|
+
let bestSpan = Infinity;
|
|
114
|
+
for (const el of ranged) {
|
|
115
|
+
const start = Number(el.dataset["pmStart"]);
|
|
116
|
+
const end = Number(el.dataset["pmEnd"]);
|
|
117
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) continue;
|
|
118
|
+
if (pmPos < start || pmPos > end) continue;
|
|
119
|
+
const span = end - start;
|
|
120
|
+
if (span < bestSpan) {
|
|
121
|
+
bestSpan = span;
|
|
122
|
+
bestEl = el;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (bestEl) {
|
|
126
|
+
const rect = bestEl.getBoundingClientRect();
|
|
127
|
+
const atEnd = pmPos >= Number(bestEl.dataset["pmEnd"]);
|
|
128
|
+
return {
|
|
129
|
+
top: rect.top,
|
|
130
|
+
left: atEnd ? rect.right : rect.left + 1,
|
|
131
|
+
height: rect.height || 16
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
let trailingEl = null;
|
|
135
|
+
let trailingStart = -Infinity;
|
|
136
|
+
for (const el of ranged) {
|
|
137
|
+
const start = Number(el.dataset["pmStart"]);
|
|
138
|
+
if (!Number.isFinite(start)) continue;
|
|
139
|
+
if (start > pmPos) continue;
|
|
140
|
+
if (start > trailingStart) {
|
|
141
|
+
trailingStart = start;
|
|
142
|
+
trailingEl = el;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (trailingEl) {
|
|
146
|
+
const rect = trailingEl.getBoundingClientRect();
|
|
147
|
+
return {
|
|
148
|
+
top: rect.top,
|
|
149
|
+
left: rect.right,
|
|
150
|
+
height: rect.height || 16
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const hostRect = host.getBoundingClientRect();
|
|
154
|
+
return {
|
|
155
|
+
top: hostRect.top + 2,
|
|
156
|
+
left: hostRect.left + 2,
|
|
157
|
+
height: 16
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Selection-rect set for a non-empty HF selection, projected against the
|
|
162
|
+
* painted HF spans. Mirror of `computeSelectionRectsFromDom` but scoped to
|
|
163
|
+
* `.layout-page-header` / `.layout-page-footer` instead of the body. Used
|
|
164
|
+
* so the painter draws a visible highlight when the user drag-selects
|
|
165
|
+
* inside a header/footer in edit mode.
|
|
166
|
+
*
|
|
167
|
+
* Returns viewport-relative `{top, left, width, height}` rects. Empty
|
|
168
|
+
* array when selection is collapsed or no painted spans overlap the range.
|
|
169
|
+
*
|
|
170
|
+
* @public
|
|
171
|
+
*/
|
|
172
|
+
function computeHfSelectionRectsFromView(view, section, doc = globalThis.document) {
|
|
173
|
+
const sel = view.state.selection;
|
|
174
|
+
if (sel.empty) return [];
|
|
175
|
+
const from = sel.from;
|
|
176
|
+
const to = sel.to;
|
|
177
|
+
const out = [];
|
|
178
|
+
const snapshot = getHfDomSnapshot(section, doc);
|
|
179
|
+
if (!snapshot) return out;
|
|
180
|
+
const { host, spans } = snapshot;
|
|
181
|
+
for (const spanEl of spans) {
|
|
182
|
+
const pmStart = Number(spanEl.dataset["pmStart"]);
|
|
183
|
+
const pmEnd = Number(spanEl.dataset["pmEnd"]);
|
|
184
|
+
if (!Number.isFinite(pmStart) || !Number.isFinite(pmEnd)) continue;
|
|
185
|
+
if (pmEnd <= from || pmStart >= to) continue;
|
|
186
|
+
if (spanEl.classList.contains("layout-run-tab")) {
|
|
187
|
+
const rect = spanEl.getBoundingClientRect();
|
|
188
|
+
out.push({
|
|
189
|
+
top: rect.top,
|
|
190
|
+
left: rect.left,
|
|
191
|
+
width: rect.width,
|
|
192
|
+
height: rect.height
|
|
193
|
+
});
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
let textNode = null;
|
|
197
|
+
if (spanEl.firstChild?.nodeType === Node.TEXT_NODE) textNode = spanEl.firstChild;
|
|
198
|
+
else if (spanEl.firstChild?.nodeType === Node.ELEMENT_NODE && spanEl.firstChild.tagName === "A" && spanEl.firstChild.firstChild?.nodeType === Node.TEXT_NODE) textNode = spanEl.firstChild.firstChild;
|
|
199
|
+
if (!textNode) continue;
|
|
200
|
+
const startChar = Math.max(0, from - pmStart);
|
|
201
|
+
const endChar = Math.min(textNode.length, to - pmStart);
|
|
202
|
+
if (startChar >= endChar) continue;
|
|
203
|
+
const range = host.ownerDocument.createRange();
|
|
204
|
+
range.setStart(textNode, startChar);
|
|
205
|
+
range.setEnd(textNode, endChar);
|
|
206
|
+
for (const rect of Array.from(range.getClientRects())) out.push({
|
|
207
|
+
top: rect.top,
|
|
208
|
+
left: rect.left,
|
|
209
|
+
width: rect.width,
|
|
210
|
+
height: rect.height
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { computeHfCaretRectFromView, computeHfSelectionRectsFromView, invalidateHfDomCache };
|