docxodus 9.7.0 → 9.8.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 +22 -0
- package/README.md +0 -1
- package/dist/editor.bundle.js +290 -76
- package/dist/editor.d.ts +4 -4
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +55 -8
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +290 -76
- package/dist/embed.iife.js +290 -76
- package/dist/page-geometry.d.ts +50 -3
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +63 -6
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +253 -68
- package/dist/pagination.d.ts +33 -8
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +198 -78
- package/dist/pagination.js.map +1 -1
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +6 -6
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/package.json +13 -5
- package/dist/wasm/index.html +0 -238
- package/dist/wasm/main.js +0 -26
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation
|
|
4
|
+
Copyright (c) 2025-2026 John Scrudato IV
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
package/dist/editor.bundle.js
CHANGED
|
@@ -165,7 +165,7 @@ var DocxodusEditor = (() => {
|
|
|
165
165
|
var DEFAULT_PAGE_WIDTH = 612;
|
|
166
166
|
var DEFAULT_PAGE_HEIGHT = 792;
|
|
167
167
|
var DEFAULT_MARGIN = 72;
|
|
168
|
-
var
|
|
168
|
+
var DEFAULT_HEADER_FOOTER_DISTANCE = 36;
|
|
169
169
|
function pxToPt(px) {
|
|
170
170
|
return px * 0.75;
|
|
171
171
|
}
|
|
@@ -181,8 +181,8 @@ var DocxodusEditor = (() => {
|
|
|
181
181
|
const marginRight = parseFloat(section.dataset.marginRight || "") || DEFAULT_MARGIN;
|
|
182
182
|
const marginBottom = parseFloat(section.dataset.marginBottom || "") || DEFAULT_MARGIN;
|
|
183
183
|
const marginLeft = parseFloat(section.dataset.marginLeft || "") || DEFAULT_MARGIN;
|
|
184
|
-
const
|
|
185
|
-
const
|
|
184
|
+
const headerDistance = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
|
|
185
|
+
const footerDistance = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
|
|
186
186
|
return {
|
|
187
187
|
pageWidth,
|
|
188
188
|
pageHeight,
|
|
@@ -192,8 +192,37 @@ var DocxodusEditor = (() => {
|
|
|
192
192
|
marginRight,
|
|
193
193
|
marginBottom,
|
|
194
194
|
marginLeft,
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
headerDistance,
|
|
196
|
+
footerDistance,
|
|
197
|
+
headerHeight: headerDistance,
|
|
198
|
+
footerHeight: footerDistance
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
var MIN_BODY_BAND_PT = 12;
|
|
202
|
+
function resolvePageBands(dims, headerContentHeight, footerContentHeight) {
|
|
203
|
+
const headerTop = dims.headerDistance;
|
|
204
|
+
const footerBottom = dims.pageHeight - dims.footerDistance;
|
|
205
|
+
const headerReach = headerContentHeight > 0 ? headerTop + headerContentHeight : 0;
|
|
206
|
+
const footerReach = footerContentHeight > 0 ? footerBottom - footerContentHeight : dims.pageHeight;
|
|
207
|
+
let bodyTop = Math.max(dims.marginTop, headerReach);
|
|
208
|
+
let bodyBottom = Math.min(dims.pageHeight - dims.marginBottom, footerReach);
|
|
209
|
+
const shortfall = MIN_BODY_BAND_PT - (bodyBottom - bodyTop);
|
|
210
|
+
if (shortfall > 0) {
|
|
211
|
+
const headerGrowth = bodyTop - dims.marginTop;
|
|
212
|
+
const footerGrowth = dims.pageHeight - dims.marginBottom - bodyBottom;
|
|
213
|
+
const growth = headerGrowth + footerGrowth;
|
|
214
|
+
if (growth > 0) {
|
|
215
|
+
bodyTop -= shortfall * headerGrowth / growth;
|
|
216
|
+
bodyBottom += shortfall * footerGrowth / growth;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
headerTop,
|
|
221
|
+
headerHeight: Math.max(0, Math.min(headerContentHeight, bodyTop - headerTop)),
|
|
222
|
+
bodyTop,
|
|
223
|
+
bodyHeight: Math.max(0, bodyBottom - bodyTop),
|
|
224
|
+
footerTop: footerBottom - Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom)),
|
|
225
|
+
footerHeight: Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom))
|
|
197
226
|
};
|
|
198
227
|
}
|
|
199
228
|
function sectionWrappers(root) {
|
|
@@ -470,9 +499,9 @@ var DocxodusEditor = (() => {
|
|
|
470
499
|
*/
|
|
471
500
|
smallestEffectiveContentHeight(dims, sectionIndex) {
|
|
472
501
|
return Math.min(
|
|
473
|
-
this.
|
|
474
|
-
this.
|
|
475
|
-
this.
|
|
502
|
+
this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
|
|
503
|
+
this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
|
|
504
|
+
this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
|
|
476
505
|
);
|
|
477
506
|
}
|
|
478
507
|
/**
|
|
@@ -996,7 +1025,7 @@ var DocxodusEditor = (() => {
|
|
|
996
1025
|
/**
|
|
997
1026
|
* Adds footnotes to a page container, including continuation content.
|
|
998
1027
|
*/
|
|
999
|
-
addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
|
|
1028
|
+
addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
|
|
1000
1029
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
1001
1030
|
if (footnoteIds.length === 0 && !hasContinuation) {
|
|
1002
1031
|
return;
|
|
@@ -1007,12 +1036,12 @@ var DocxodusEditor = (() => {
|
|
|
1007
1036
|
const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
|
|
1008
1037
|
const maxFootnoteHeight = Math.min(
|
|
1009
1038
|
footnoteHeight,
|
|
1010
|
-
|
|
1039
|
+
bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
|
|
1011
1040
|
);
|
|
1012
1041
|
const footnotesDiv = document.createElement("div");
|
|
1013
1042
|
footnotesDiv.className = `${this.cssPrefix}footnotes`;
|
|
1014
1043
|
footnotesDiv.style.position = "absolute";
|
|
1015
|
-
footnotesDiv.style.bottom = `${dims.
|
|
1044
|
+
footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
|
|
1016
1045
|
footnotesDiv.style.left = `${dims.marginLeft}pt`;
|
|
1017
1046
|
footnotesDiv.style.width = `${dims.contentWidth}pt`;
|
|
1018
1047
|
footnotesDiv.style.boxSizing = "border-box";
|
|
@@ -1086,50 +1115,51 @@ var DocxodusEditor = (() => {
|
|
|
1086
1115
|
return sectionHf.footerDefault;
|
|
1087
1116
|
}
|
|
1088
1117
|
/**
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1118
|
+
* The header, body, and footer bands for one page position.
|
|
1119
|
+
*
|
|
1120
|
+
* The single owner of "where does anything sit vertically on this page" — placement in
|
|
1121
|
+
* {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
|
|
1122
|
+
* read it, so those three cannot disagree about where the body ends.
|
|
1123
|
+
*
|
|
1124
|
+
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
1125
|
+
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
1094
1126
|
*/
|
|
1095
|
-
|
|
1127
|
+
getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
|
|
1096
1128
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
const headerExpansion = headerHeight - dims.marginTop;
|
|
1126
|
-
const footerExpansion = footerHeight - dims.marginBottom;
|
|
1127
|
-
const contentHeight = dims.contentHeight - headerExpansion - footerExpansion;
|
|
1128
|
-
return { headerHeight, footerHeight, contentHeight };
|
|
1129
|
+
return resolvePageBands(
|
|
1130
|
+
dims,
|
|
1131
|
+
this.selectStoryHeight(
|
|
1132
|
+
sectionHf?.headerFirstHeight,
|
|
1133
|
+
sectionHf?.headerEvenHeight,
|
|
1134
|
+
sectionHf?.headerDefaultHeight,
|
|
1135
|
+
pageInSection,
|
|
1136
|
+
globalPageNumber
|
|
1137
|
+
),
|
|
1138
|
+
this.selectStoryHeight(
|
|
1139
|
+
sectionHf?.footerFirstHeight,
|
|
1140
|
+
sectionHf?.footerEvenHeight,
|
|
1141
|
+
sectionHf?.footerDefaultHeight,
|
|
1142
|
+
pageInSection,
|
|
1143
|
+
globalPageNumber
|
|
1144
|
+
)
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* The measured height of the running story this page position selects, mirroring
|
|
1149
|
+
* {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
|
|
1150
|
+
*/
|
|
1151
|
+
selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
|
|
1152
|
+
if (pageInSection === 1 && first != null) return first;
|
|
1153
|
+
if (globalPageNumber % 2 === 0 && even != null) return even;
|
|
1154
|
+
return fallback ?? 0;
|
|
1129
1155
|
}
|
|
1130
1156
|
/**
|
|
1131
1157
|
* Measures the content height of a header or footer element.
|
|
1132
|
-
*
|
|
1158
|
+
*
|
|
1159
|
+
* This is what tells {@link resolvePageBands} whether the story stays inside its margin or
|
|
1160
|
+
* pushes the body, so it must measure the story ALONE — any padding added here would have to
|
|
1161
|
+
* be added to the rendered band too, and the two drifting apart is exactly how a header
|
|
1162
|
+
* silently starts overlapping body text.
|
|
1133
1163
|
*/
|
|
1134
1164
|
measureHeaderFooterHeight(source, contentWidth) {
|
|
1135
1165
|
const measureContainer = document.createElement("div");
|
|
@@ -1137,7 +1167,6 @@ var DocxodusEditor = (() => {
|
|
|
1137
1167
|
measureContainer.style.visibility = "hidden";
|
|
1138
1168
|
measureContainer.style.width = `${contentWidth}pt`;
|
|
1139
1169
|
measureContainer.style.left = "-9999px";
|
|
1140
|
-
measureContainer.style.paddingBottom = "4pt";
|
|
1141
1170
|
for (const child of Array.from(source.childNodes)) {
|
|
1142
1171
|
measureContainer.appendChild(child.cloneNode(true));
|
|
1143
1172
|
}
|
|
@@ -1157,7 +1186,7 @@ var DocxodusEditor = (() => {
|
|
|
1157
1186
|
let currentContent = [];
|
|
1158
1187
|
let pageNumber = startPageNumber;
|
|
1159
1188
|
let pageInSection = 1;
|
|
1160
|
-
let {
|
|
1189
|
+
let { bodyHeight: effectiveContentHeight } = this.getPageBands(
|
|
1161
1190
|
dims,
|
|
1162
1191
|
sectionIndex,
|
|
1163
1192
|
pageInSection,
|
|
@@ -1192,8 +1221,8 @@ var DocxodusEditor = (() => {
|
|
|
1192
1221
|
pageNumber++;
|
|
1193
1222
|
pageInSection++;
|
|
1194
1223
|
currentContent = [];
|
|
1195
|
-
const
|
|
1196
|
-
effectiveContentHeight =
|
|
1224
|
+
const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
|
|
1225
|
+
effectiveContentHeight = newBands.bodyHeight;
|
|
1197
1226
|
remainingHeight = effectiveContentHeight;
|
|
1198
1227
|
prevMarginBottomPt = 0;
|
|
1199
1228
|
currentFootnoteIds = [];
|
|
@@ -1255,7 +1284,7 @@ var DocxodusEditor = (() => {
|
|
|
1255
1284
|
) + additionalChainFootnoteHeight;
|
|
1256
1285
|
const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
|
|
1257
1286
|
if (currentChainHeight > currentAvailableHeight) {
|
|
1258
|
-
const
|
|
1287
|
+
const nextPageBands = this.getPageBands(
|
|
1259
1288
|
dims,
|
|
1260
1289
|
sectionIndex,
|
|
1261
1290
|
pageInSection + 1,
|
|
@@ -1271,7 +1300,7 @@ var DocxodusEditor = (() => {
|
|
|
1271
1300
|
dims.contentWidth,
|
|
1272
1301
|
nextPageContinuation
|
|
1273
1302
|
);
|
|
1274
|
-
if (freshChainBodyHeight + freshChainFootnoteHeight <=
|
|
1303
|
+
if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
|
|
1275
1304
|
finishPage();
|
|
1276
1305
|
}
|
|
1277
1306
|
}
|
|
@@ -1440,6 +1469,161 @@ var DocxodusEditor = (() => {
|
|
|
1440
1469
|
if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
|
|
1441
1470
|
}
|
|
1442
1471
|
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
|
|
1474
|
+
*
|
|
1475
|
+
* The converter deliberately carries the OOXML bases and offsets as data instead of flattening
|
|
1476
|
+
* them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
|
|
1477
|
+
* bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
|
|
1478
|
+
* from the clipped text column into the page box and position it in one shared point space.
|
|
1479
|
+
*/
|
|
1480
|
+
positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
|
|
1481
|
+
const anchors = Array.from(
|
|
1482
|
+
contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
|
|
1483
|
+
);
|
|
1484
|
+
if (anchors.length === 0) return;
|
|
1485
|
+
const pageRect = pageBox.getBoundingClientRect();
|
|
1486
|
+
const pixelsPerPoint = pageRect.width / dims.pageWidth;
|
|
1487
|
+
if (!(pixelsPerPoint > 0)) return;
|
|
1488
|
+
for (const anchor of anchors) {
|
|
1489
|
+
const staticRect = anchor.getBoundingClientRect();
|
|
1490
|
+
const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
|
|
1491
|
+
const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
|
|
1492
|
+
const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
|
|
1493
|
+
const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
|
|
1494
|
+
const context = {
|
|
1495
|
+
staticLeft: toPageX(staticRect.left),
|
|
1496
|
+
staticTop: toPageY(staticRect.top),
|
|
1497
|
+
lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
|
|
1498
|
+
paragraphLeft: toPageX(paragraphRect.left),
|
|
1499
|
+
paragraphTop: toPageY(paragraphRect.top),
|
|
1500
|
+
paragraphWidth: paragraphRect.width / pixelsPerPoint,
|
|
1501
|
+
paragraphHeight: paragraphRect.height / pixelsPerPoint
|
|
1502
|
+
};
|
|
1503
|
+
const widthReference = this.horizontalAnchorReference(
|
|
1504
|
+
anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
|
|
1505
|
+
dims,
|
|
1506
|
+
pageNumber,
|
|
1507
|
+
context
|
|
1508
|
+
);
|
|
1509
|
+
const widthPercent = this.anchorNumber(anchor, "width-percent");
|
|
1510
|
+
if (widthPercent !== void 0) {
|
|
1511
|
+
anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
|
|
1512
|
+
}
|
|
1513
|
+
const heightReference = this.verticalAnchorReference(
|
|
1514
|
+
anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
|
|
1515
|
+
dims,
|
|
1516
|
+
pageNumber,
|
|
1517
|
+
context
|
|
1518
|
+
);
|
|
1519
|
+
const heightPercent = this.anchorNumber(anchor, "height-percent");
|
|
1520
|
+
if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
|
|
1521
|
+
anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
|
|
1522
|
+
}
|
|
1523
|
+
const sizedRect = anchor.getBoundingClientRect();
|
|
1524
|
+
const width = sizedRect.width / pixelsPerPoint;
|
|
1525
|
+
const height = sizedRect.height / pixelsPerPoint;
|
|
1526
|
+
const horizontal2 = this.horizontalAnchorReference(
|
|
1527
|
+
anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
|
|
1528
|
+
dims,
|
|
1529
|
+
pageNumber,
|
|
1530
|
+
context
|
|
1531
|
+
);
|
|
1532
|
+
const vertical2 = this.verticalAnchorReference(
|
|
1533
|
+
anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
|
|
1534
|
+
dims,
|
|
1535
|
+
pageNumber,
|
|
1536
|
+
context
|
|
1537
|
+
);
|
|
1538
|
+
const left2 = this.resolveAnchorAxis(
|
|
1539
|
+
horizontal2,
|
|
1540
|
+
width,
|
|
1541
|
+
anchor.getAttribute("data-docx-anchor-h-align"),
|
|
1542
|
+
this.anchorNumber(anchor, "h-offset"),
|
|
1543
|
+
pageNumber
|
|
1544
|
+
);
|
|
1545
|
+
const top2 = this.resolveAnchorAxis(
|
|
1546
|
+
vertical2,
|
|
1547
|
+
height,
|
|
1548
|
+
anchor.getAttribute("data-docx-anchor-v-align"),
|
|
1549
|
+
this.anchorNumber(anchor, "v-offset"),
|
|
1550
|
+
pageNumber
|
|
1551
|
+
);
|
|
1552
|
+
pageBox.appendChild(anchor);
|
|
1553
|
+
anchor.style.position = "absolute";
|
|
1554
|
+
anchor.style.left = `${left2}pt`;
|
|
1555
|
+
anchor.style.top = `${top2}pt`;
|
|
1556
|
+
anchor.style.margin = "0";
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
anchorNumber(anchor, suffix) {
|
|
1560
|
+
const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
|
|
1561
|
+
if (raw === null) return void 0;
|
|
1562
|
+
const value = Number(raw);
|
|
1563
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1564
|
+
}
|
|
1565
|
+
horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
|
|
1566
|
+
const leftMargin = { start: 0, size: dims.marginLeft };
|
|
1567
|
+
const rightMargin = {
|
|
1568
|
+
start: dims.marginLeft + dims.contentWidth,
|
|
1569
|
+
size: dims.marginRight
|
|
1570
|
+
};
|
|
1571
|
+
switch (relativeFrom) {
|
|
1572
|
+
case "page":
|
|
1573
|
+
return { start: 0, size: dims.pageWidth };
|
|
1574
|
+
case "character":
|
|
1575
|
+
return { start: context.staticLeft, size: 0 };
|
|
1576
|
+
case "leftMargin":
|
|
1577
|
+
return leftMargin;
|
|
1578
|
+
case "rightMargin":
|
|
1579
|
+
return rightMargin;
|
|
1580
|
+
case "insideMargin":
|
|
1581
|
+
return pageNumber % 2 === 1 ? leftMargin : rightMargin;
|
|
1582
|
+
case "outsideMargin":
|
|
1583
|
+
return pageNumber % 2 === 1 ? rightMargin : leftMargin;
|
|
1584
|
+
case "paragraph":
|
|
1585
|
+
return { start: context.paragraphLeft, size: context.paragraphWidth };
|
|
1586
|
+
case "column":
|
|
1587
|
+
case "margin":
|
|
1588
|
+
default:
|
|
1589
|
+
return { start: dims.marginLeft, size: dims.contentWidth };
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
|
|
1593
|
+
const topMargin = { start: 0, size: dims.marginTop };
|
|
1594
|
+
const bottomMargin = {
|
|
1595
|
+
start: dims.marginTop + dims.contentHeight,
|
|
1596
|
+
size: dims.marginBottom
|
|
1597
|
+
};
|
|
1598
|
+
switch (relativeFrom) {
|
|
1599
|
+
case "page":
|
|
1600
|
+
return { start: 0, size: dims.pageHeight };
|
|
1601
|
+
case "paragraph":
|
|
1602
|
+
return { start: context.paragraphTop, size: context.paragraphHeight };
|
|
1603
|
+
case "line":
|
|
1604
|
+
return { start: context.staticTop, size: context.lineHeight };
|
|
1605
|
+
case "topMargin":
|
|
1606
|
+
return topMargin;
|
|
1607
|
+
case "bottomMargin":
|
|
1608
|
+
return bottomMargin;
|
|
1609
|
+
case "insideMargin":
|
|
1610
|
+
return pageNumber % 2 === 1 ? topMargin : bottomMargin;
|
|
1611
|
+
case "outsideMargin":
|
|
1612
|
+
return pageNumber % 2 === 1 ? bottomMargin : topMargin;
|
|
1613
|
+
case "margin":
|
|
1614
|
+
default:
|
|
1615
|
+
return { start: dims.marginTop, size: dims.contentHeight };
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
|
|
1619
|
+
if (offset !== void 0) return reference.start + offset;
|
|
1620
|
+
let fraction = 0;
|
|
1621
|
+
if (alignment === "center") fraction = 0.5;
|
|
1622
|
+
else if (alignment === "right" || alignment === "bottom") fraction = 1;
|
|
1623
|
+
else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
|
|
1624
|
+
else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
|
|
1625
|
+
return reference.start + (reference.size - objectSize) * fraction;
|
|
1626
|
+
}
|
|
1443
1627
|
/**
|
|
1444
1628
|
* Creates a page container element.
|
|
1445
1629
|
*/
|
|
@@ -1466,22 +1650,22 @@ var DocxodusEditor = (() => {
|
|
|
1466
1650
|
pageBox.dataset.pageNumber = String(pageNumber);
|
|
1467
1651
|
pageBox.dataset.sectionIndex = String(sectionIndex);
|
|
1468
1652
|
pageBox.dataset.pageInSection = String(pageInSection);
|
|
1469
|
-
const
|
|
1653
|
+
const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
|
|
1470
1654
|
const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
|
|
1471
1655
|
if (headerSource) {
|
|
1472
1656
|
const headerDiv = document.createElement("div");
|
|
1473
1657
|
headerDiv.className = `${this.cssPrefix}header`;
|
|
1474
1658
|
headerDiv.style.position = "absolute";
|
|
1475
|
-
headerDiv.style.top =
|
|
1659
|
+
headerDiv.style.top = `${bands.headerTop}pt`;
|
|
1660
|
+
headerDiv.style.bottom = "auto";
|
|
1476
1661
|
headerDiv.style.left = `${dims.marginLeft}pt`;
|
|
1477
1662
|
headerDiv.style.width = `${dims.contentWidth}pt`;
|
|
1478
|
-
headerDiv.style.height = `${
|
|
1663
|
+
headerDiv.style.height = `${bands.headerHeight}pt`;
|
|
1479
1664
|
headerDiv.style.overflow = "hidden";
|
|
1480
1665
|
headerDiv.style.boxSizing = "border-box";
|
|
1481
1666
|
headerDiv.style.display = "flex";
|
|
1482
1667
|
headerDiv.style.flexDirection = "column";
|
|
1483
|
-
headerDiv.style.justifyContent = "flex-
|
|
1484
|
-
headerDiv.style.paddingBottom = "4pt";
|
|
1668
|
+
headerDiv.style.justifyContent = "flex-start";
|
|
1485
1669
|
for (const child of Array.from(headerSource.childNodes)) {
|
|
1486
1670
|
const clonedheaderDiv = child.cloneNode(true);
|
|
1487
1671
|
if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
|
|
@@ -1489,8 +1673,8 @@ var DocxodusEditor = (() => {
|
|
|
1489
1673
|
}
|
|
1490
1674
|
pageBox.appendChild(headerDiv);
|
|
1491
1675
|
}
|
|
1492
|
-
const contentAreaTop =
|
|
1493
|
-
const contentAreaHeight =
|
|
1676
|
+
const contentAreaTop = bands.bodyTop;
|
|
1677
|
+
const contentAreaHeight = bands.bodyHeight;
|
|
1494
1678
|
const contentArea = document.createElement("div");
|
|
1495
1679
|
contentArea.className = `${this.cssPrefix}content`;
|
|
1496
1680
|
contentArea.style.position = "absolute";
|
|
@@ -1505,23 +1689,23 @@ var DocxodusEditor = (() => {
|
|
|
1505
1689
|
pageBox.appendChild(contentArea);
|
|
1506
1690
|
const hasContinuation = continuation && continuation.remainingElements.length > 0;
|
|
1507
1691
|
if (footnoteIds.length > 0 || hasContinuation) {
|
|
1508
|
-
this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
|
|
1692
|
+
this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
|
|
1509
1693
|
}
|
|
1510
1694
|
const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
|
|
1511
1695
|
if (footerSource) {
|
|
1512
1696
|
const footerDiv = document.createElement("div");
|
|
1513
1697
|
footerDiv.className = `${this.cssPrefix}footer`;
|
|
1514
1698
|
footerDiv.style.position = "absolute";
|
|
1515
|
-
footerDiv.style.
|
|
1699
|
+
footerDiv.style.top = "auto";
|
|
1700
|
+
footerDiv.style.bottom = `${dims.footerDistance}pt`;
|
|
1516
1701
|
footerDiv.style.left = `${dims.marginLeft}pt`;
|
|
1517
1702
|
footerDiv.style.width = `${dims.contentWidth}pt`;
|
|
1518
|
-
footerDiv.style.height = `${
|
|
1703
|
+
footerDiv.style.height = `${bands.footerHeight}pt`;
|
|
1519
1704
|
footerDiv.style.overflow = "hidden";
|
|
1520
1705
|
footerDiv.style.boxSizing = "border-box";
|
|
1521
1706
|
footerDiv.style.display = "flex";
|
|
1522
1707
|
footerDiv.style.flexDirection = "column";
|
|
1523
|
-
footerDiv.style.justifyContent = "flex-
|
|
1524
|
-
footerDiv.style.paddingTop = "4pt";
|
|
1708
|
+
footerDiv.style.justifyContent = "flex-end";
|
|
1525
1709
|
for (const child of Array.from(footerSource.childNodes)) {
|
|
1526
1710
|
const clonedfooterDiv = child.cloneNode(true);
|
|
1527
1711
|
if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
|
|
@@ -1536,6 +1720,7 @@ var DocxodusEditor = (() => {
|
|
|
1536
1720
|
pageBox.appendChild(pageNum);
|
|
1537
1721
|
}
|
|
1538
1722
|
this.containerElement.appendChild(pageBox);
|
|
1723
|
+
this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
|
|
1539
1724
|
const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
|
|
1540
1725
|
if (notesEl) {
|
|
1541
1726
|
const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
|
|
@@ -4910,6 +5095,19 @@ var DocxodusEditor = (() => {
|
|
|
4910
5095
|
function isListBlock(block) {
|
|
4911
5096
|
return !!block.querySelector(":scope > [data-list-marker]");
|
|
4912
5097
|
}
|
|
5098
|
+
function ensureListMarkerSeparator(block) {
|
|
5099
|
+
const marker = block.querySelector(":scope > [data-list-marker]");
|
|
5100
|
+
if (!marker) return;
|
|
5101
|
+
const next = marker.nextElementSibling;
|
|
5102
|
+
if (next?.hasAttribute("data-editor-list-separator")) return;
|
|
5103
|
+
const separator = block.ownerDocument.createElement("span");
|
|
5104
|
+
separator.setAttribute("data-editor-list-separator", "");
|
|
5105
|
+
separator.setAttribute("data-list-marker", "true");
|
|
5106
|
+
separator.setAttribute("aria-hidden", "true");
|
|
5107
|
+
separator.style.display = "none";
|
|
5108
|
+
separator.textContent = " ";
|
|
5109
|
+
marker.after(separator);
|
|
5110
|
+
}
|
|
4913
5111
|
var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
|
|
4914
5112
|
function isGeneratedChrome(node) {
|
|
4915
5113
|
return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
|
|
@@ -6200,6 +6398,7 @@ var DocxodusEditor = (() => {
|
|
|
6200
6398
|
const unid = el.getAttribute("data-anchor");
|
|
6201
6399
|
if (!unid || !this.anchorIdOf(el)) return;
|
|
6202
6400
|
el.setAttribute("contenteditable", "true");
|
|
6401
|
+
ensureListMarkerSeparator(el);
|
|
6203
6402
|
el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
|
|
6204
6403
|
el.dataset.committedText = blockContentText(el);
|
|
6205
6404
|
el.addEventListener("focus", () => {
|
|
@@ -7062,8 +7261,8 @@ var DocxodusEditor = (() => {
|
|
|
7062
7261
|
* editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
|
|
7063
7262
|
*
|
|
7064
7263
|
* Body blocks only: Word disallows a note reference inside a header/footer story or inside
|
|
7065
|
-
* another note, and the session rejects those with `AnchorWrongKind`.
|
|
7066
|
-
*
|
|
7264
|
+
* another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
|
|
7265
|
+
* the first notes section when needed and renumbers every affected citation in place.
|
|
7067
7266
|
*/
|
|
7068
7267
|
insertFootnote(markdown = "New footnote.") {
|
|
7069
7268
|
this.insertNote("footnote", markdown);
|
|
@@ -7455,7 +7654,7 @@ var DocxodusEditor = (() => {
|
|
|
7455
7654
|
* the current node — never a section div (multi-child) or the edit root. */
|
|
7456
7655
|
unitWrapperOf(el) {
|
|
7457
7656
|
let n = el;
|
|
7458
|
-
while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
|
|
7657
|
+
while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-section-index") && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
|
|
7459
7658
|
n = n.parentElement;
|
|
7460
7659
|
}
|
|
7461
7660
|
return n;
|
|
@@ -7491,6 +7690,7 @@ var DocxodusEditor = (() => {
|
|
|
7491
7690
|
const pureRemoved = diff.removed.filter(
|
|
7492
7691
|
(i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
|
|
7493
7692
|
);
|
|
7693
|
+
const appliedFresh = /* @__PURE__ */ new Set();
|
|
7494
7694
|
const nodeAt = (j) => {
|
|
7495
7695
|
const oi = diff.keep.get(j);
|
|
7496
7696
|
if (oi !== void 0) return oldNodes[oi];
|
|
@@ -7498,7 +7698,7 @@ var DocxodusEditor = (() => {
|
|
|
7498
7698
|
if (movedOi !== void 0) return oldNodes[movedOi];
|
|
7499
7699
|
if (subOldByNew.has(j)) return fresh.get(j);
|
|
7500
7700
|
const f = fresh.get(j);
|
|
7501
|
-
return f &&
|
|
7701
|
+
return f && appliedFresh.has(j) ? f : null;
|
|
7502
7702
|
};
|
|
7503
7703
|
for (const j of pureAdded) {
|
|
7504
7704
|
const el = fresh.get(j);
|
|
@@ -7513,6 +7713,7 @@ var DocxodusEditor = (() => {
|
|
|
7513
7713
|
if (prevW) prevW.after(el);
|
|
7514
7714
|
else if (nextW) nextW.before(el);
|
|
7515
7715
|
else return "insert with no anchored neighbor";
|
|
7716
|
+
appliedFresh.add(j);
|
|
7516
7717
|
this.wireUnit(el, units[j]);
|
|
7517
7718
|
}
|
|
7518
7719
|
for (const i of pureRemoved) {
|
|
@@ -7551,13 +7752,13 @@ var DocxodusEditor = (() => {
|
|
|
7551
7752
|
if (anchorEl) this.wireBlock(anchorEl);
|
|
7552
7753
|
root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
|
|
7553
7754
|
}
|
|
7554
|
-
/** Old-sequence diff state for one notes section. `null` requests remount
|
|
7555
|
-
* stampable
|
|
7755
|
+
/** Old-sequence diff state for one notes section. `null` requests remount when an
|
|
7756
|
+
* existing list is not stampable; an absent list is the valid first-note case. */
|
|
7556
7757
|
notesDiff(sectionClass, units) {
|
|
7557
7758
|
const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
|
|
7558
7759
|
const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
|
|
7559
7760
|
if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
|
|
7560
|
-
if (!ol) return
|
|
7761
|
+
if (!ol) return { lis, diff: diffUnits([], units) };
|
|
7561
7762
|
const tokens = [];
|
|
7562
7763
|
for (const li of lis) {
|
|
7563
7764
|
const unid = li.getAttribute("data-note-anchor");
|
|
@@ -7574,7 +7775,20 @@ var DocxodusEditor = (() => {
|
|
|
7574
7775
|
this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
|
|
7575
7776
|
return;
|
|
7576
7777
|
}
|
|
7577
|
-
|
|
7778
|
+
let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
|
|
7779
|
+
let ol = section?.querySelector(`:scope > ol`) ?? null;
|
|
7780
|
+
if (!ol) {
|
|
7781
|
+
if (!section) {
|
|
7782
|
+
section = document.createElement("section");
|
|
7783
|
+
section.className = sectionClass;
|
|
7784
|
+
const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
|
|
7785
|
+
if (following) following.before(section);
|
|
7786
|
+
else this.editRoot.append(section);
|
|
7787
|
+
}
|
|
7788
|
+
if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
|
|
7789
|
+
ol = document.createElement("ol");
|
|
7790
|
+
section.append(ol);
|
|
7791
|
+
}
|
|
7578
7792
|
const prefix = sectionClass === "footnotes" ? "fn" : "en";
|
|
7579
7793
|
const nodes = [];
|
|
7580
7794
|
for (let j = 0; j < units.length; j++) {
|
package/dist/editor.d.ts
CHANGED
|
@@ -616,8 +616,8 @@ export declare class DocxEditor {
|
|
|
616
616
|
* editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
|
|
617
617
|
*
|
|
618
618
|
* Body blocks only: Word disallows a note reference inside a header/footer story or inside
|
|
619
|
-
* another note, and the session rejects those with `AnchorWrongKind`.
|
|
620
|
-
*
|
|
619
|
+
* another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
|
|
620
|
+
* the first notes section when needed and renumbers every affected citation in place.
|
|
621
621
|
*/
|
|
622
622
|
insertFootnote(markdown?: string): void;
|
|
623
623
|
/** Cite a new endnote from the caret — see {@link insertFootnote}; writes the endnotes part. */
|
|
@@ -725,8 +725,8 @@ export declare class DocxEditor {
|
|
|
725
725
|
* content signature on its `[data-anchor]` element — the element the next
|
|
726
726
|
* reconcile's DOM walk reads tokens from. */
|
|
727
727
|
private wireUnit;
|
|
728
|
-
/** Old-sequence diff state for one notes section. `null` requests remount
|
|
729
|
-
* stampable
|
|
728
|
+
/** Old-sequence diff state for one notes section. `null` requests remount when an
|
|
729
|
+
* existing list is not stampable; an absent list is the valid first-note case. */
|
|
730
730
|
private notesDiff;
|
|
731
731
|
/** Apply a notes-section diff: rebuild the `<ol>`'s li list, preserving kept nodes. */
|
|
732
732
|
private applyNotesDiff;
|