@a3s-lab/office 0.29.0 → 0.31.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/README.md +117 -3
- package/dist/{0~7043.js → 0~4808.js} +347 -2
- package/dist/0~5050.js +10 -1
- package/dist/{0~5093.js → 0~7240.js} +465 -55
- package/dist/0~document-editor.js +1694 -187
- package/dist/0~spreadsheet-editor.js +183 -34
- package/dist/0~work-docx-export.js +507 -31
- package/dist/0~work-docx-import.js +41 -7
- package/dist/0~work-office-diagnostics.js +146 -5
- package/dist/1544.js +13 -1
- package/dist/4104.js +29 -6
- package/dist/4121.js +2340 -1
- package/dist/6282.js +1072 -1148
- package/dist/internal/features/work/editors/document-command-catalog.d.ts +45 -0
- package/dist/internal/features/work/editors/document-dom-selection.d.ts +7 -0
- package/dist/internal/features/work/editors/document-editor-support.d.ts +1 -0
- package/dist/internal/features/work/editors/document-font-dialog-model.d.ts +5 -2
- package/dist/internal/features/work/editors/document-font-dialog-run-shading-model.d.ts +23 -0
- package/dist/internal/features/work/editors/document-font-dialog-run-shading-section.d.ts +8 -0
- package/dist/internal/features/work/editors/document-index-dialog.d.ts +10 -0
- package/dist/internal/features/work/editors/document-index-entry-dialog.d.ts +10 -0
- package/dist/internal/features/work/editors/document-page-chrome-ribbon.d.ts +2 -1
- package/dist/internal/features/work/editors/document-proofing-dialog-model.d.ts +22 -0
- package/dist/internal/features/work/editors/document-proofing-dialog.d.ts +7 -0
- package/dist/internal/features/work/editors/document-references-ribbon.d.ts +19 -0
- package/dist/internal/features/work/editors/document-table-of-contents-dialog.d.ts +10 -0
- package/dist/internal/features/work/editors/document-toolbar.d.ts +6 -1
- package/dist/internal/features/work/editors/spreadsheet-data-validation.d.ts +7 -1
- package/dist/internal/features/work/editors/use-document-insert-commands.d.ts +5 -0
- package/dist/internal/features/work/work-document-character-formatting.d.ts +8 -0
- package/dist/internal/features/work/work-document-format-changes.d.ts +6 -0
- package/dist/internal/features/work/work-document-highlight.d.ts +9 -0
- package/dist/internal/features/work/work-document-index-fields.d.ts +19 -0
- package/dist/internal/features/work/work-document-index-nodes.d.ts +32 -0
- package/dist/internal/features/work/work-document-index.d.ts +63 -0
- package/dist/internal/features/work/work-document-outline.d.ts +1 -0
- package/dist/internal/features/work/work-document-paragraph-shading.d.ts +6 -0
- package/dist/internal/features/work/work-document-proofing.d.ts +19 -0
- package/dist/internal/features/work/work-document-run-shading.d.ts +12 -0
- package/dist/internal/features/work/work-document-table-of-contents-node.d.ts +18 -0
- package/dist/internal/features/work/work-document-table-of-contents.d.ts +51 -0
- package/dist/internal/features/work/work-document-word-line-metrics.d.ts +3 -0
- package/dist/internal/features/work/work-docx-field-instructions.d.ts +1 -1
- package/dist/internal/features/work/work-docx-import.d.ts +5 -1
- package/dist/internal/features/work/work-docx-index-export.d.ts +14 -0
- package/dist/internal/features/work/work-docx-index-import.d.ts +23 -0
- package/dist/internal/features/work/work-docx-proofing-diagnostics.d.ts +3 -0
- package/dist/internal/features/work/work-docx-proofing.d.ts +38 -0
- package/dist/internal/features/work/work-docx-run-formatting-import.d.ts +7 -0
- package/dist/internal/features/work/work-docx-run-shading-diagnostics.d.ts +3 -0
- package/dist/internal/features/work/work-docx-run-shading-export.d.ts +17 -0
- package/dist/internal/features/work/work-docx-run-shading.d.ts +20 -0
- package/dist/internal/features/work/work-docx-table-of-contents-export.d.ts +11 -0
- package/dist/internal/features/work/work-docx-table-of-contents-import.d.ts +15 -0
- package/dist/internal/features/work/work-spreadsheet-data-validation.d.ts +7 -0
- package/dist/internal/features/work/work-types.d.ts +9 -0
- package/dist/internal/kernel/office-kernel-protocol.d.ts +1 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +607 -4
- package/docs/latest/en/browser-editor-architecture.md +64 -1
- package/package.json +15 -2
package/dist/4121.js
CHANGED
|
@@ -1,4 +1,73 @@
|
|
|
1
1
|
import jszip from "jszip";
|
|
2
|
+
import { Extension } from "@tiptap/core";
|
|
3
|
+
import { isHistoryTransaction } from "@tiptap/pm/history";
|
|
4
|
+
import { Plugin } from "@tiptap/pm/state";
|
|
5
|
+
import { Mapping } from "@tiptap/pm/transform";
|
|
6
|
+
const DOCUMENT_INTEGRITY_BOOKMARK = 1;
|
|
7
|
+
const DOCUMENT_INTEGRITY_FIELD = 2;
|
|
8
|
+
const DOCUMENT_INTEGRITY_IMAGE = 4;
|
|
9
|
+
const DOCUMENT_INTEGRITY_NOTE = 8;
|
|
10
|
+
const DOCUMENT_INTEGRITY_PARAGRAPH_IDENTITY = 16;
|
|
11
|
+
const DOCUMENT_INTEGRITY_TABLE_ROW_IDENTITY = 32;
|
|
12
|
+
const featureCache = new WeakMap();
|
|
13
|
+
function documentHasIntegrityFeature(document, feature) {
|
|
14
|
+
return (documentIntegrityFeatures(document, true) & feature) !== 0;
|
|
15
|
+
}
|
|
16
|
+
function primeDocumentIntegrityFeatures(document) {
|
|
17
|
+
document.forEach(primeDocumentIntegrityNode);
|
|
18
|
+
}
|
|
19
|
+
function primeDocumentIntegrityNode(node) {
|
|
20
|
+
if ('documentChunk' === node.type.name) {
|
|
21
|
+
const features = nonNegativeInteger(node.attrs.integrityFeatures);
|
|
22
|
+
if (null !== features) featureCache.set(node, features);
|
|
23
|
+
if (true !== node.attrs.windowContainer) return;
|
|
24
|
+
}
|
|
25
|
+
node.forEach(primeDocumentIntegrityNode);
|
|
26
|
+
}
|
|
27
|
+
function documentIntegrityFeatures(node, cacheNode) {
|
|
28
|
+
if (cacheNode) {
|
|
29
|
+
const cached = featureCache.get(node);
|
|
30
|
+
if (void 0 !== cached) return cached;
|
|
31
|
+
}
|
|
32
|
+
let features = nodeIntegrityFeatures(node);
|
|
33
|
+
node.forEach((child)=>{
|
|
34
|
+
features |= documentIntegrityFeatures(child, 'documentChunk' === child.type.name);
|
|
35
|
+
});
|
|
36
|
+
if (cacheNode) featureCache.set(node, features);
|
|
37
|
+
return features;
|
|
38
|
+
}
|
|
39
|
+
function nodeIntegrityFeatures(node) {
|
|
40
|
+
let features = 0;
|
|
41
|
+
switch(node.type.name){
|
|
42
|
+
case 'documentBookmarkBoundary':
|
|
43
|
+
features |= DOCUMENT_INTEGRITY_BOOKMARK;
|
|
44
|
+
break;
|
|
45
|
+
case 'documentCrossReference':
|
|
46
|
+
if ('bookmark' === node.attrs.targetType) features |= DOCUMENT_INTEGRITY_BOOKMARK;
|
|
47
|
+
break;
|
|
48
|
+
case 'documentField':
|
|
49
|
+
features |= DOCUMENT_INTEGRITY_FIELD;
|
|
50
|
+
break;
|
|
51
|
+
case 'image':
|
|
52
|
+
features |= DOCUMENT_INTEGRITY_IMAGE;
|
|
53
|
+
break;
|
|
54
|
+
case 'documentNote':
|
|
55
|
+
case 'documentNoteReference':
|
|
56
|
+
features |= DOCUMENT_INTEGRITY_NOTE;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (node.isText && node.marks.some((mark)=>'link' === mark.type.name && 'string' == typeof mark.attrs.href && mark.attrs.href.startsWith('#'))) features |= DOCUMENT_INTEGRITY_BOOKMARK;
|
|
60
|
+
if (identityComponent(node.attrs.paragraphId) || identityComponent(node.attrs.textId)) features |= DOCUMENT_INTEGRITY_PARAGRAPH_IDENTITY;
|
|
61
|
+
if (identityComponent(node.attrs.rowId) || identityComponent(node.attrs.rowTextId)) features |= DOCUMENT_INTEGRITY_TABLE_ROW_IDENTITY;
|
|
62
|
+
return features;
|
|
63
|
+
}
|
|
64
|
+
function identityComponent(value) {
|
|
65
|
+
return 'string' == typeof value && value.trim().length > 0;
|
|
66
|
+
}
|
|
67
|
+
function nonNegativeInteger(value) {
|
|
68
|
+
const number = Number(value);
|
|
69
|
+
return Number.isSafeInteger(number) && number >= 0 ? number : null;
|
|
70
|
+
}
|
|
2
71
|
function normalizeCssColor(source) {
|
|
3
72
|
const value = source?.trim().toLowerCase();
|
|
4
73
|
if (!value) return null;
|
|
@@ -971,6 +1040,1779 @@ function cssBorderStyle(style) {
|
|
|
971
1040
|
function work_document_run_border_formatPixels(value) {
|
|
972
1041
|
return Number(value.toFixed(3)).toString();
|
|
973
1042
|
}
|
|
1043
|
+
const work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS = new Set([
|
|
1044
|
+
'nil',
|
|
1045
|
+
'clear',
|
|
1046
|
+
'solid',
|
|
1047
|
+
'horzStripe',
|
|
1048
|
+
'vertStripe',
|
|
1049
|
+
'reverseDiagStripe',
|
|
1050
|
+
'diagStripe',
|
|
1051
|
+
'horzCross',
|
|
1052
|
+
'diagCross',
|
|
1053
|
+
'thinHorzStripe',
|
|
1054
|
+
'thinVertStripe',
|
|
1055
|
+
'thinReverseDiagStripe',
|
|
1056
|
+
'thinDiagStripe',
|
|
1057
|
+
'thinHorzCross',
|
|
1058
|
+
'thinDiagCross',
|
|
1059
|
+
'pct5',
|
|
1060
|
+
'pct10',
|
|
1061
|
+
'pct12',
|
|
1062
|
+
'pct15',
|
|
1063
|
+
'pct20',
|
|
1064
|
+
'pct25',
|
|
1065
|
+
'pct30',
|
|
1066
|
+
'pct35',
|
|
1067
|
+
'pct37',
|
|
1068
|
+
'pct40',
|
|
1069
|
+
'pct45',
|
|
1070
|
+
'pct50',
|
|
1071
|
+
'pct55',
|
|
1072
|
+
'pct60',
|
|
1073
|
+
'pct62',
|
|
1074
|
+
'pct65',
|
|
1075
|
+
'pct70',
|
|
1076
|
+
'pct75',
|
|
1077
|
+
'pct80',
|
|
1078
|
+
'pct85',
|
|
1079
|
+
'pct87',
|
|
1080
|
+
'pct90',
|
|
1081
|
+
'pct95'
|
|
1082
|
+
]);
|
|
1083
|
+
function normalizeDocumentParagraphShading(source) {
|
|
1084
|
+
if (!source || 'object' != typeof source) return null;
|
|
1085
|
+
const value = source;
|
|
1086
|
+
const pattern = value.pattern;
|
|
1087
|
+
if ('string' != typeof pattern || !work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS.has(pattern)) return null;
|
|
1088
|
+
const color = normalizeShadingColor(value.color);
|
|
1089
|
+
const fill = normalizeShadingColor(value.fill);
|
|
1090
|
+
if (void 0 !== value.color && !color || void 0 !== value.fill && !fill) return null;
|
|
1091
|
+
return {
|
|
1092
|
+
pattern: pattern,
|
|
1093
|
+
...color ? {
|
|
1094
|
+
color
|
|
1095
|
+
} : {},
|
|
1096
|
+
...fill ? {
|
|
1097
|
+
fill
|
|
1098
|
+
} : {}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function parseDocumentParagraphShading(source) {
|
|
1102
|
+
if ('string' != typeof source) return normalizeDocumentParagraphShading(source);
|
|
1103
|
+
if (!source.trim()) return null;
|
|
1104
|
+
try {
|
|
1105
|
+
return normalizeDocumentParagraphShading(JSON.parse(source));
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function serializeDocumentParagraphShading(source) {
|
|
1111
|
+
const shading = normalizeDocumentParagraphShading(source);
|
|
1112
|
+
if (!shading) return;
|
|
1113
|
+
return JSON.stringify({
|
|
1114
|
+
pattern: shading.pattern,
|
|
1115
|
+
...shading.color ? {
|
|
1116
|
+
color: serializedShadingColor(shading.color)
|
|
1117
|
+
} : {},
|
|
1118
|
+
...shading.fill ? {
|
|
1119
|
+
fill: serializedShadingColor(shading.fill)
|
|
1120
|
+
} : {}
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
function parseDocumentParagraphShadingElement(element) {
|
|
1124
|
+
const semantic = parseDocumentParagraphShading(element.dataset.officeParagraphShading);
|
|
1125
|
+
const background = normalizeCssColor(element.style.backgroundColor);
|
|
1126
|
+
if (semantic) {
|
|
1127
|
+
const presentation = documentParagraphShadingPresentation(semantic);
|
|
1128
|
+
const expected = normalizeCssColor(presentation.backgroundColor);
|
|
1129
|
+
if (background && expected && background !== expected) {
|
|
1130
|
+
if ('transparent' === background) return {
|
|
1131
|
+
pattern: 'nil'
|
|
1132
|
+
};
|
|
1133
|
+
if (paragraphShadingBackgroundUsesForeground(semantic)) return {
|
|
1134
|
+
...semantic,
|
|
1135
|
+
color: {
|
|
1136
|
+
value: background
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
return {
|
|
1140
|
+
...semantic,
|
|
1141
|
+
fill: {
|
|
1142
|
+
value: background
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
return semantic;
|
|
1147
|
+
}
|
|
1148
|
+
return background && 'transparent' !== background ? {
|
|
1149
|
+
pattern: 'clear',
|
|
1150
|
+
fill: {
|
|
1151
|
+
value: background
|
|
1152
|
+
}
|
|
1153
|
+
} : null;
|
|
1154
|
+
}
|
|
1155
|
+
function documentParagraphShadingDomAttributes(source) {
|
|
1156
|
+
const shading = normalizeDocumentParagraphShading(source);
|
|
1157
|
+
const serialized = serializeDocumentParagraphShading(shading);
|
|
1158
|
+
if (!shading || !serialized) return {};
|
|
1159
|
+
const presentation = documentParagraphShadingPresentation(shading);
|
|
1160
|
+
const styles = [
|
|
1161
|
+
`background-color: ${presentation.backgroundColor}`,
|
|
1162
|
+
presentation.backgroundImage ? `background-image: ${presentation.backgroundImage}` : '',
|
|
1163
|
+
presentation.backgroundSize ? `background-size: ${presentation.backgroundSize}` : ''
|
|
1164
|
+
].filter(Boolean);
|
|
1165
|
+
return {
|
|
1166
|
+
'data-office-paragraph-shading': serialized,
|
|
1167
|
+
style: styles.join('; ')
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function normalizeShadingColor(source) {
|
|
1171
|
+
if (!source || 'object' != typeof source) return null;
|
|
1172
|
+
const value = source;
|
|
1173
|
+
const theme = parseDocxThemeReference('string' == typeof value.theme ? value.theme : value.theme ? JSON.stringify(value.theme) : void 0);
|
|
1174
|
+
const direct = 'auto' === value.value ? 'auto' : 'string' == typeof value.value ? normalizeCssColor(value.value) : null;
|
|
1175
|
+
const resolved = direct ?? theme?.resolved ?? null;
|
|
1176
|
+
if (!resolved || 'transparent' === resolved) return null;
|
|
1177
|
+
if (theme && resolved !== theme.resolved) return null;
|
|
1178
|
+
return {
|
|
1179
|
+
value: resolved,
|
|
1180
|
+
...theme ? {
|
|
1181
|
+
theme
|
|
1182
|
+
} : {}
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function serializedShadingColor(color) {
|
|
1186
|
+
const theme = serializeDocxThemeReference(color.theme ?? null);
|
|
1187
|
+
return {
|
|
1188
|
+
value: color.value,
|
|
1189
|
+
...theme ? {
|
|
1190
|
+
theme: JSON.parse(theme)
|
|
1191
|
+
} : {}
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function documentParagraphShadingPresentation(shading) {
|
|
1195
|
+
if ('nil' === shading.pattern) return {
|
|
1196
|
+
backgroundColor: 'transparent'
|
|
1197
|
+
};
|
|
1198
|
+
const foreground = shadingColor(shading.color, '#000000');
|
|
1199
|
+
const background = shadingColor(shading.fill, 'transparent');
|
|
1200
|
+
if ('clear' === shading.pattern) return {
|
|
1201
|
+
backgroundColor: background
|
|
1202
|
+
};
|
|
1203
|
+
if ('solid' === shading.pattern) return {
|
|
1204
|
+
backgroundColor: foreground
|
|
1205
|
+
};
|
|
1206
|
+
const percentage = shadingPercentage(shading.pattern);
|
|
1207
|
+
if (null !== percentage) {
|
|
1208
|
+
const inverted = percentage > 50;
|
|
1209
|
+
const dotColor = inverted ? background : foreground;
|
|
1210
|
+
const baseColor = inverted ? foreground : background;
|
|
1211
|
+
const density = Math.min(50, inverted ? 100 - percentage : percentage);
|
|
1212
|
+
const spacing = Math.max(2, Math.round(9 - density / 7));
|
|
1213
|
+
return {
|
|
1214
|
+
backgroundColor: baseColor,
|
|
1215
|
+
backgroundImage: `radial-gradient(circle, ${dotColor} 0 1px, transparent 1.2px)`,
|
|
1216
|
+
backgroundSize: `${spacing}px ${spacing}px`
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const thin = shading.pattern.startsWith('thin');
|
|
1220
|
+
const width = thin ? 1 : 2;
|
|
1221
|
+
const period = thin ? 7 : 6;
|
|
1222
|
+
const stripe = (angle)=>`repeating-linear-gradient(${angle}deg, ${foreground} 0 ${width}px, transparent ${width}px ${period}px)`;
|
|
1223
|
+
const angles = shadingPatternAngles(shading.pattern);
|
|
1224
|
+
return {
|
|
1225
|
+
backgroundColor: background,
|
|
1226
|
+
backgroundImage: angles.map(stripe).join(', ')
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
function shadingColor(color, fallback) {
|
|
1230
|
+
return color && 'auto' !== color.value ? color.value : fallback;
|
|
1231
|
+
}
|
|
1232
|
+
function shadingPercentage(pattern) {
|
|
1233
|
+
const match = /^pct(\d+)$/.exec(pattern);
|
|
1234
|
+
return match?.[1] ? Number(match[1]) : null;
|
|
1235
|
+
}
|
|
1236
|
+
function paragraphShadingBackgroundUsesForeground(shading) {
|
|
1237
|
+
if ('solid' === shading.pattern) return true;
|
|
1238
|
+
const percentage = shadingPercentage(shading.pattern);
|
|
1239
|
+
return null !== percentage && percentage > 50;
|
|
1240
|
+
}
|
|
1241
|
+
function shadingPatternAngles(pattern) {
|
|
1242
|
+
if (pattern.includes('HorzCross')) return [
|
|
1243
|
+
0,
|
|
1244
|
+
90
|
|
1245
|
+
];
|
|
1246
|
+
if (pattern.includes('DiagCross')) return [
|
|
1247
|
+
45,
|
|
1248
|
+
-45
|
|
1249
|
+
];
|
|
1250
|
+
if (pattern.includes('VertStripe')) return [
|
|
1251
|
+
90
|
|
1252
|
+
];
|
|
1253
|
+
if (pattern.includes('ReverseDiagStripe')) return [
|
|
1254
|
+
-45
|
|
1255
|
+
];
|
|
1256
|
+
if (pattern.includes('DiagStripe')) return [
|
|
1257
|
+
45
|
|
1258
|
+
];
|
|
1259
|
+
return [
|
|
1260
|
+
0
|
|
1261
|
+
];
|
|
1262
|
+
}
|
|
1263
|
+
const DOCUMENT_HIGHLIGHT_ATTRIBUTE = 'data-office-highlight';
|
|
1264
|
+
const DOCUMENT_HIGHLIGHT_VALUES = new Set([
|
|
1265
|
+
'black',
|
|
1266
|
+
'blue',
|
|
1267
|
+
'cyan',
|
|
1268
|
+
'darkBlue',
|
|
1269
|
+
'darkCyan',
|
|
1270
|
+
'darkGray',
|
|
1271
|
+
'darkGreen',
|
|
1272
|
+
'darkMagenta',
|
|
1273
|
+
'darkRed',
|
|
1274
|
+
'darkYellow',
|
|
1275
|
+
'green',
|
|
1276
|
+
'lightGray',
|
|
1277
|
+
'magenta',
|
|
1278
|
+
'none',
|
|
1279
|
+
'red',
|
|
1280
|
+
'white',
|
|
1281
|
+
'yellow'
|
|
1282
|
+
]);
|
|
1283
|
+
const HIGHLIGHT_COLORS = {
|
|
1284
|
+
black: '#000000',
|
|
1285
|
+
blue: '#0000ff',
|
|
1286
|
+
cyan: '#00ffff',
|
|
1287
|
+
darkBlue: '#000080',
|
|
1288
|
+
darkCyan: '#008080',
|
|
1289
|
+
darkGray: '#808080',
|
|
1290
|
+
darkGreen: '#008000',
|
|
1291
|
+
darkMagenta: '#800080',
|
|
1292
|
+
darkRed: '#800000',
|
|
1293
|
+
darkYellow: '#808000',
|
|
1294
|
+
green: '#00ff00',
|
|
1295
|
+
lightGray: '#c0c0c0',
|
|
1296
|
+
magenta: '#ff00ff',
|
|
1297
|
+
none: 'transparent',
|
|
1298
|
+
red: '#ff0000',
|
|
1299
|
+
white: '#ffffff',
|
|
1300
|
+
yellow: '#ffff00'
|
|
1301
|
+
};
|
|
1302
|
+
function normalizeDocumentHighlight(source) {
|
|
1303
|
+
return 'string' == typeof source && DOCUMENT_HIGHLIGHT_VALUES.has(source) ? source : null;
|
|
1304
|
+
}
|
|
1305
|
+
function documentHighlightFromDocxValue(source) {
|
|
1306
|
+
if ('string' != typeof source) return null;
|
|
1307
|
+
const normalized = source.trim().toLowerCase();
|
|
1308
|
+
for (const value of DOCUMENT_HIGHLIGHT_VALUES)if (value.toLowerCase() === normalized) return value;
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
function documentHighlightCssColor(source) {
|
|
1312
|
+
const value = normalizeDocumentHighlight(source);
|
|
1313
|
+
return value ? HIGHLIGHT_COLORS[value] : null;
|
|
1314
|
+
}
|
|
1315
|
+
function documentHighlightForCssColor(source) {
|
|
1316
|
+
const color = normalizeCssColor('string' == typeof source ? source : null);
|
|
1317
|
+
if (!color) return null;
|
|
1318
|
+
for (const [value, candidate] of Object.entries(HIGHLIGHT_COLORS))if (candidate === color) return value;
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
function documentHighlightFromElement(element) {
|
|
1322
|
+
return normalizeDocumentHighlight(element.getAttribute(DOCUMENT_HIGHLIGHT_ATTRIBUTE)) ?? documentHighlightForCssColor(element.style.backgroundColor);
|
|
1323
|
+
}
|
|
1324
|
+
function documentHighlightDomAttributes(source) {
|
|
1325
|
+
const value = normalizeDocumentHighlight(source);
|
|
1326
|
+
const color = documentHighlightCssColor(value);
|
|
1327
|
+
return value && color ? {
|
|
1328
|
+
[DOCUMENT_HIGHLIGHT_ATTRIBUTE]: value,
|
|
1329
|
+
style: `background-color: ${color}`
|
|
1330
|
+
} : {};
|
|
1331
|
+
}
|
|
1332
|
+
const DOCUMENT_RUN_SHADING_ATTRIBUTE = 'data-office-run-shading';
|
|
1333
|
+
const MAX_SERIALIZED_RUN_SHADING_BYTES = 4096;
|
|
1334
|
+
const RUN_SHADING_KEYS = new Set([
|
|
1335
|
+
'pattern',
|
|
1336
|
+
'color',
|
|
1337
|
+
'fill'
|
|
1338
|
+
]);
|
|
1339
|
+
const RUN_SHADING_COLOR_KEYS = new Set([
|
|
1340
|
+
'value',
|
|
1341
|
+
'theme'
|
|
1342
|
+
]);
|
|
1343
|
+
const THEME_REFERENCE_KEYS = new Set([
|
|
1344
|
+
'theme',
|
|
1345
|
+
'resolved',
|
|
1346
|
+
'tint',
|
|
1347
|
+
'shade'
|
|
1348
|
+
]);
|
|
1349
|
+
function normalizeDocumentRunShading(source) {
|
|
1350
|
+
if (!isRecordWithKeys(source, RUN_SHADING_KEYS)) return null;
|
|
1351
|
+
for (const name of [
|
|
1352
|
+
'color',
|
|
1353
|
+
'fill'
|
|
1354
|
+
]){
|
|
1355
|
+
const color = source[name];
|
|
1356
|
+
if (void 0 !== color) {
|
|
1357
|
+
if (!isRecordWithKeys(color, RUN_SHADING_COLOR_KEYS)) return null;
|
|
1358
|
+
if (void 0 !== color.theme && !isRecordWithKeys(color.theme, THEME_REFERENCE_KEYS)) return null;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return normalizeDocumentParagraphShading(source);
|
|
1362
|
+
}
|
|
1363
|
+
function parseDocumentRunShading(source) {
|
|
1364
|
+
if ('string' != typeof source) return normalizeDocumentRunShading(source);
|
|
1365
|
+
if (!source.trim() || source.length > MAX_SERIALIZED_RUN_SHADING_BYTES) return null;
|
|
1366
|
+
try {
|
|
1367
|
+
return normalizeDocumentRunShading(JSON.parse(source));
|
|
1368
|
+
} catch {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
function serializeDocumentRunShading(source) {
|
|
1373
|
+
const shading = normalizeDocumentRunShading(source);
|
|
1374
|
+
const serialized = shading ? serializeDocumentParagraphShading(shading) : void 0;
|
|
1375
|
+
return serialized && serialized.length <= MAX_SERIALIZED_RUN_SHADING_BYTES ? serialized : void 0;
|
|
1376
|
+
}
|
|
1377
|
+
function parseDocumentRunShadingElement(element) {
|
|
1378
|
+
const semantic = parseDocumentRunShading(element.getAttribute(DOCUMENT_RUN_SHADING_ATTRIBUTE));
|
|
1379
|
+
if (!semantic) return null;
|
|
1380
|
+
if (element.hasAttribute(DOCUMENT_HIGHLIGHT_ATTRIBUTE)) return semantic;
|
|
1381
|
+
const background = normalizeCssColor(element.style.backgroundColor);
|
|
1382
|
+
const expected = normalizeCssColor(documentParagraphShadingPresentation(semantic).backgroundColor);
|
|
1383
|
+
if (!background || !expected || background === expected) return semantic;
|
|
1384
|
+
if ('transparent' === background) return {
|
|
1385
|
+
pattern: 'nil'
|
|
1386
|
+
};
|
|
1387
|
+
return paragraphShadingBackgroundUsesForeground(semantic) ? {
|
|
1388
|
+
...semantic,
|
|
1389
|
+
color: {
|
|
1390
|
+
value: background
|
|
1391
|
+
}
|
|
1392
|
+
} : {
|
|
1393
|
+
...semantic,
|
|
1394
|
+
fill: {
|
|
1395
|
+
value: background
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
function documentRunShadingDomAttributes(source) {
|
|
1400
|
+
const shading = normalizeDocumentRunShading(source);
|
|
1401
|
+
const serialized = serializeDocumentRunShading(shading);
|
|
1402
|
+
if (!shading || !serialized) return {};
|
|
1403
|
+
const paragraphAttributes = documentParagraphShadingDomAttributes(shading);
|
|
1404
|
+
const style = [
|
|
1405
|
+
paragraphAttributes.style,
|
|
1406
|
+
'box-decoration-break: clone',
|
|
1407
|
+
'-webkit-box-decoration-break: clone'
|
|
1408
|
+
].filter(Boolean).join('; ');
|
|
1409
|
+
return {
|
|
1410
|
+
[DOCUMENT_RUN_SHADING_ATTRIBUTE]: serialized,
|
|
1411
|
+
style
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function isRecordWithKeys(source, allowed) {
|
|
1415
|
+
return 'object' == typeof source && null !== source && !Array.isArray(source) && Object.keys(source).every((key)=>allowed.has(key));
|
|
1416
|
+
}
|
|
1417
|
+
const DOCUMENT_SCRIPT_FONTS_ATTRIBUTE = "data-office-script-fonts";
|
|
1418
|
+
const DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE = "data-office-script-font-slot";
|
|
1419
|
+
const MAX_FONT_NAME_LENGTH = 127;
|
|
1420
|
+
const MAX_SERIALIZED_SCRIPT_FONTS_LENGTH = 4096;
|
|
1421
|
+
const SCRIPT_FONT_KEYS = new Set([
|
|
1422
|
+
'ascii',
|
|
1423
|
+
'highAnsi',
|
|
1424
|
+
'eastAsia',
|
|
1425
|
+
'complexScript',
|
|
1426
|
+
'hint'
|
|
1427
|
+
]);
|
|
1428
|
+
const SCRIPT_FONT_FACE_KEYS = new Set([
|
|
1429
|
+
'name',
|
|
1430
|
+
'theme',
|
|
1431
|
+
'resolved'
|
|
1432
|
+
]);
|
|
1433
|
+
const SCRIPT_FONT_HINTS = new Set([
|
|
1434
|
+
'default',
|
|
1435
|
+
'eastAsia',
|
|
1436
|
+
'cs'
|
|
1437
|
+
]);
|
|
1438
|
+
const THEME_FONTS = new Set([
|
|
1439
|
+
'majorEastAsia',
|
|
1440
|
+
'majorBidi',
|
|
1441
|
+
'majorAscii',
|
|
1442
|
+
'majorHAnsi',
|
|
1443
|
+
'minorEastAsia',
|
|
1444
|
+
'minorBidi',
|
|
1445
|
+
'minorAscii',
|
|
1446
|
+
'minorHAnsi'
|
|
1447
|
+
]);
|
|
1448
|
+
const SLOT_FALLBACK_ORDER = {
|
|
1449
|
+
ascii: [
|
|
1450
|
+
'ascii',
|
|
1451
|
+
'highAnsi',
|
|
1452
|
+
'eastAsia',
|
|
1453
|
+
'complexScript'
|
|
1454
|
+
],
|
|
1455
|
+
highAnsi: [
|
|
1456
|
+
'highAnsi',
|
|
1457
|
+
'ascii',
|
|
1458
|
+
'eastAsia',
|
|
1459
|
+
'complexScript'
|
|
1460
|
+
],
|
|
1461
|
+
eastAsia: [
|
|
1462
|
+
'eastAsia',
|
|
1463
|
+
'highAnsi',
|
|
1464
|
+
'ascii',
|
|
1465
|
+
'complexScript'
|
|
1466
|
+
],
|
|
1467
|
+
complexScript: [
|
|
1468
|
+
'complexScript',
|
|
1469
|
+
'highAnsi',
|
|
1470
|
+
'ascii',
|
|
1471
|
+
'eastAsia'
|
|
1472
|
+
]
|
|
1473
|
+
};
|
|
1474
|
+
const NEUTRAL_SCRIPT_CHARACTER = /^[\p{Cc}\p{Cf}\p{M}\p{N}\p{P}\p{S}\p{Z}]$/u;
|
|
1475
|
+
function normalizeDocumentScriptFonts(source) {
|
|
1476
|
+
if (!work_document_script_fonts_isRecordWithKeys(source, SCRIPT_FONT_KEYS)) return null;
|
|
1477
|
+
const normalized = {};
|
|
1478
|
+
for (const slot of scriptFontSlots){
|
|
1479
|
+
if (void 0 === source[slot]) continue;
|
|
1480
|
+
const face = normalizeDocumentScriptFontFace(source[slot]);
|
|
1481
|
+
if (!face) return null;
|
|
1482
|
+
normalized[slot] = face;
|
|
1483
|
+
}
|
|
1484
|
+
if (void 0 !== source.hint) {
|
|
1485
|
+
const hint = normalizeDocumentScriptFontHint(source.hint);
|
|
1486
|
+
if (!hint) return null;
|
|
1487
|
+
normalized.hint = hint;
|
|
1488
|
+
}
|
|
1489
|
+
return Object.keys(normalized).length ? normalized : null;
|
|
1490
|
+
}
|
|
1491
|
+
function serializeDocumentScriptFonts(source) {
|
|
1492
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
1493
|
+
return fonts ? JSON.stringify(fonts) : null;
|
|
1494
|
+
}
|
|
1495
|
+
function parseDocumentScriptFonts(source) {
|
|
1496
|
+
if (!source || source.length > MAX_SERIALIZED_SCRIPT_FONTS_LENGTH) return null;
|
|
1497
|
+
try {
|
|
1498
|
+
return normalizeDocumentScriptFonts(JSON.parse(source));
|
|
1499
|
+
} catch {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
function documentScriptFontsFromElement(element) {
|
|
1504
|
+
return parseDocumentScriptFonts(element.getAttribute(DOCUMENT_SCRIPT_FONTS_ATTRIBUTE));
|
|
1505
|
+
}
|
|
1506
|
+
function documentScriptFontSlotFromElement(element) {
|
|
1507
|
+
return normalizeDocumentScriptFontSlot(element.getAttribute(DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE));
|
|
1508
|
+
}
|
|
1509
|
+
function documentScriptFontsDomAttributes(source, slot) {
|
|
1510
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
1511
|
+
const normalizedSlot = normalizeDocumentScriptFontSlot(slot);
|
|
1512
|
+
if (!fonts) return {};
|
|
1513
|
+
const serialized = serializeDocumentScriptFonts(fonts);
|
|
1514
|
+
if (!serialized) return {};
|
|
1515
|
+
const family = documentScriptFontFamily(fonts, normalizedSlot ?? documentScriptFontSlotFromHint(fonts.hint));
|
|
1516
|
+
return {
|
|
1517
|
+
[DOCUMENT_SCRIPT_FONTS_ATTRIBUTE]: serialized,
|
|
1518
|
+
...normalizedSlot ? {
|
|
1519
|
+
[DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE]: normalizedSlot
|
|
1520
|
+
} : {},
|
|
1521
|
+
...family ? {
|
|
1522
|
+
style: `font-family: ${family}`
|
|
1523
|
+
} : {}
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
function documentScriptFontFamily(source, slot) {
|
|
1527
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
1528
|
+
if (!fonts) return;
|
|
1529
|
+
const families = [];
|
|
1530
|
+
const seen = new Set();
|
|
1531
|
+
for (const candidate of documentScriptFontFallbackSlots(slot)){
|
|
1532
|
+
const family = documentScriptFontFaceFamily(fonts[candidate]);
|
|
1533
|
+
const key = family?.toLocaleLowerCase();
|
|
1534
|
+
if (!(!family || !key || seen.has(key))) {
|
|
1535
|
+
seen.add(key);
|
|
1536
|
+
families.push(cssFontFamily(family));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return families.length ? families.join(', ') : void 0;
|
|
1540
|
+
}
|
|
1541
|
+
function documentScriptFontFallbackSlots(slot) {
|
|
1542
|
+
return SLOT_FALLBACK_ORDER[slot];
|
|
1543
|
+
}
|
|
1544
|
+
function documentScriptFontFamilyForRendering(source, slot, currentFontFamily) {
|
|
1545
|
+
const projected = documentScriptFontFamily(source, slot);
|
|
1546
|
+
const safeCurrent = safeCssFontFamilyList(currentFontFamily);
|
|
1547
|
+
if (!safeCurrent || !projected) return projected;
|
|
1548
|
+
const currentPrimary = documentFontNameFromCssFamily(safeCurrent);
|
|
1549
|
+
const projectedPrimary = documentFontNameFromCssFamily(projected);
|
|
1550
|
+
return currentPrimary && projectedPrimary && currentPrimary.toLocaleLowerCase() === projectedPrimary.toLocaleLowerCase() ? safeCurrent : projected;
|
|
1551
|
+
}
|
|
1552
|
+
function documentScriptFontDirectFamily(source, slot) {
|
|
1553
|
+
const fonts = normalizeDocumentScriptFonts(source);
|
|
1554
|
+
return fonts ? documentScriptFontFaceFamily(fonts[slot]) ?? null : null;
|
|
1555
|
+
}
|
|
1556
|
+
function documentScriptFontsForAllText(fontFamily) {
|
|
1557
|
+
const name = documentFontNameFromCssFamily(fontFamily);
|
|
1558
|
+
if (!name) return null;
|
|
1559
|
+
const face = {
|
|
1560
|
+
name,
|
|
1561
|
+
resolved: name
|
|
1562
|
+
};
|
|
1563
|
+
return {
|
|
1564
|
+
ascii: face,
|
|
1565
|
+
highAnsi: face,
|
|
1566
|
+
eastAsia: face,
|
|
1567
|
+
complexScript: face,
|
|
1568
|
+
hint: 'default'
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function patchDocumentScriptFonts(source, patch, fallbackFontFamily) {
|
|
1572
|
+
const current = normalizeDocumentScriptFonts(source) ?? documentScriptFontsForAllText(fallbackFontFamily) ?? {};
|
|
1573
|
+
const next = {
|
|
1574
|
+
...current
|
|
1575
|
+
};
|
|
1576
|
+
if (void 0 !== patch.latin) {
|
|
1577
|
+
const face = directFontFace(patch.latin);
|
|
1578
|
+
if (face) {
|
|
1579
|
+
next.ascii = face;
|
|
1580
|
+
next.highAnsi = face;
|
|
1581
|
+
} else {
|
|
1582
|
+
delete next.ascii;
|
|
1583
|
+
delete next.highAnsi;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (void 0 !== patch.eastAsia) {
|
|
1587
|
+
const face = directFontFace(patch.eastAsia);
|
|
1588
|
+
if (face) next.eastAsia = face;
|
|
1589
|
+
else delete next.eastAsia;
|
|
1590
|
+
}
|
|
1591
|
+
if (void 0 !== patch.complexScript) {
|
|
1592
|
+
const face = directFontFace(patch.complexScript);
|
|
1593
|
+
if (face) next.complexScript = face;
|
|
1594
|
+
else delete next.complexScript;
|
|
1595
|
+
}
|
|
1596
|
+
return normalizeDocumentScriptFonts(next);
|
|
1597
|
+
}
|
|
1598
|
+
function documentScriptFontSegments(text, hint = 'default', forceComplexScript = false) {
|
|
1599
|
+
if (!text) return [];
|
|
1600
|
+
if (forceComplexScript) return [
|
|
1601
|
+
{
|
|
1602
|
+
from: 0,
|
|
1603
|
+
to: text.length,
|
|
1604
|
+
slot: 'complexScript'
|
|
1605
|
+
}
|
|
1606
|
+
];
|
|
1607
|
+
const characters = [];
|
|
1608
|
+
let offset = 0;
|
|
1609
|
+
for (const character of text){
|
|
1610
|
+
const from = offset;
|
|
1611
|
+
offset += character.length;
|
|
1612
|
+
characters.push({
|
|
1613
|
+
from,
|
|
1614
|
+
to: offset,
|
|
1615
|
+
slot: strongDocumentScriptFontSlot(character)
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
const fallback = documentScriptFontSlotFromHint(hint);
|
|
1619
|
+
let previous = null;
|
|
1620
|
+
for(let index = 0; index < characters.length; index += 1){
|
|
1621
|
+
const entry = characters[index];
|
|
1622
|
+
if (!entry) continue;
|
|
1623
|
+
if (entry.slot) {
|
|
1624
|
+
previous = entry.slot;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
let next = previous;
|
|
1628
|
+
if (!next) for(let cursor = index + 1; cursor < characters.length; cursor += 1){
|
|
1629
|
+
const candidate = characters[cursor]?.slot;
|
|
1630
|
+
if (candidate) {
|
|
1631
|
+
next = candidate;
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
entry.slot = next ?? fallback;
|
|
1636
|
+
}
|
|
1637
|
+
const segments = [];
|
|
1638
|
+
for (const entry of characters){
|
|
1639
|
+
const slot = entry.slot ?? fallback;
|
|
1640
|
+
const prior = segments[segments.length - 1];
|
|
1641
|
+
if (prior?.slot === slot && prior.to === entry.from) prior.to = entry.to;
|
|
1642
|
+
else segments.push({
|
|
1643
|
+
from: entry.from,
|
|
1644
|
+
to: entry.to,
|
|
1645
|
+
slot
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
return segments;
|
|
1649
|
+
}
|
|
1650
|
+
function normalizeDocumentScriptFontSlot(value) {
|
|
1651
|
+
return scriptFontSlots.includes(value) ? value : null;
|
|
1652
|
+
}
|
|
1653
|
+
function normalizeDocumentScriptFontHint(value) {
|
|
1654
|
+
return SCRIPT_FONT_HINTS.has(value) ? value : null;
|
|
1655
|
+
}
|
|
1656
|
+
function normalizeDocumentThemeFont(value) {
|
|
1657
|
+
return THEME_FONTS.has(value) ? value : null;
|
|
1658
|
+
}
|
|
1659
|
+
function documentScriptFontSlotFromHint(hint) {
|
|
1660
|
+
if ('eastAsia' === hint) return 'eastAsia';
|
|
1661
|
+
if ('cs' === hint) return 'complexScript';
|
|
1662
|
+
return 'ascii';
|
|
1663
|
+
}
|
|
1664
|
+
function documentFontNameFromCssFamily(value) {
|
|
1665
|
+
if ('string' != typeof value) return null;
|
|
1666
|
+
const source = value.trim();
|
|
1667
|
+
if (!source) return null;
|
|
1668
|
+
let family = '';
|
|
1669
|
+
const quote = source[0];
|
|
1670
|
+
if ('"' === quote || "'" === quote) {
|
|
1671
|
+
let closed = false;
|
|
1672
|
+
for(let index = 1; index < source.length; index += 1){
|
|
1673
|
+
const character = source[index];
|
|
1674
|
+
if (character === quote) {
|
|
1675
|
+
closed = true;
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
if ('\\' !== character) {
|
|
1679
|
+
family += character;
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
const decoded = decodeCssEscape(source, index + 1);
|
|
1683
|
+
if (!decoded) return null;
|
|
1684
|
+
family += decoded.value;
|
|
1685
|
+
index = decoded.end - 1;
|
|
1686
|
+
}
|
|
1687
|
+
if (!closed) return null;
|
|
1688
|
+
} else family = source.split(',')[0] ?? '';
|
|
1689
|
+
return normalizeDocumentFontName(family);
|
|
1690
|
+
}
|
|
1691
|
+
function cssDocumentFontFamily(value) {
|
|
1692
|
+
const family = normalizeDocumentFontName(value);
|
|
1693
|
+
return family ? cssFontFamily(family) : null;
|
|
1694
|
+
}
|
|
1695
|
+
function normalizeDocumentFontName(value) {
|
|
1696
|
+
if ('string' != typeof value) return null;
|
|
1697
|
+
const normalized = value.trim();
|
|
1698
|
+
return normalized && normalized.length <= MAX_FONT_NAME_LENGTH && !/[\p{Cc}\p{Cs}]/u.test(normalized) ? normalized : null;
|
|
1699
|
+
}
|
|
1700
|
+
const scriptFontSlots = [
|
|
1701
|
+
'ascii',
|
|
1702
|
+
'highAnsi',
|
|
1703
|
+
'eastAsia',
|
|
1704
|
+
'complexScript'
|
|
1705
|
+
];
|
|
1706
|
+
function normalizeDocumentScriptFontFace(source) {
|
|
1707
|
+
if (!work_document_script_fonts_isRecordWithKeys(source, SCRIPT_FONT_FACE_KEYS)) return null;
|
|
1708
|
+
const name = void 0 === source.name ? void 0 : normalizeDocumentFontName(source.name);
|
|
1709
|
+
const resolved = void 0 === source.resolved ? void 0 : normalizeDocumentFontName(source.resolved);
|
|
1710
|
+
const theme = void 0 === source.theme ? void 0 : normalizeDocumentThemeFont(source.theme);
|
|
1711
|
+
if (void 0 !== source.name && !name || void 0 !== source.resolved && !resolved || null === theme || !name && !theme && !resolved) return null;
|
|
1712
|
+
return {
|
|
1713
|
+
...name ? {
|
|
1714
|
+
name
|
|
1715
|
+
} : {},
|
|
1716
|
+
...theme ? {
|
|
1717
|
+
theme
|
|
1718
|
+
} : {},
|
|
1719
|
+
...resolved ? {
|
|
1720
|
+
resolved
|
|
1721
|
+
} : {}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function directFontFace(value) {
|
|
1725
|
+
if (null === value) return null;
|
|
1726
|
+
const name = documentFontNameFromCssFamily(value) ?? normalizeDocumentFontName(value);
|
|
1727
|
+
return name ? {
|
|
1728
|
+
name,
|
|
1729
|
+
resolved: name
|
|
1730
|
+
} : null;
|
|
1731
|
+
}
|
|
1732
|
+
function documentScriptFontFaceFamily(face) {
|
|
1733
|
+
return face?.resolved ?? face?.name;
|
|
1734
|
+
}
|
|
1735
|
+
function strongDocumentScriptFontSlot(character) {
|
|
1736
|
+
if (NEUTRAL_SCRIPT_CHARACTER.test(character)) return null;
|
|
1737
|
+
const codePoint = character.codePointAt(0);
|
|
1738
|
+
if (void 0 === codePoint) return null;
|
|
1739
|
+
if (isComplexScriptCodePoint(codePoint)) return 'complexScript';
|
|
1740
|
+
if (isEastAsianCodePoint(codePoint)) return 'eastAsia';
|
|
1741
|
+
return codePoint <= 0x7f ? 'ascii' : 'highAnsi';
|
|
1742
|
+
}
|
|
1743
|
+
function isComplexScriptCodePoint(codePoint) {
|
|
1744
|
+
return codePoint >= 0x0590 && codePoint <= 0x08ff || codePoint >= 0x0900 && codePoint <= 0x109f || codePoint >= 0x1780 && codePoint <= 0x18af || codePoint >= 0x1900 && codePoint <= 0x1cff || codePoint >= 0xa800 && codePoint <= 0xa8ff || codePoint >= 0xa980 && codePoint <= 0xa9df || codePoint >= 0xaa00 && codePoint <= 0xaa7f || codePoint >= 0xabc0 && codePoint <= 0xabff || codePoint >= 0xfb1d && codePoint <= 0xfdff || codePoint >= 0xfe70 && codePoint <= 0xfeff || codePoint >= 0x10a00 && codePoint <= 0x10fff || codePoint >= 0x11000 && codePoint <= 0x11fff || codePoint >= 0x1e900 && codePoint <= 0x1edff || codePoint >= 0x1ee00 && codePoint <= 0x1eeff;
|
|
1745
|
+
}
|
|
1746
|
+
function isEastAsianCodePoint(codePoint) {
|
|
1747
|
+
return codePoint >= 0x1100 && codePoint <= 0x11ff || codePoint >= 0x2e80 && codePoint <= 0xa4cf || codePoint >= 0xac00 && codePoint <= 0xd7af || codePoint >= 0xf900 && codePoint <= 0xfaff || codePoint >= 0xfe10 && codePoint <= 0xfe6f || codePoint >= 0xff00 && codePoint <= 0xffef || codePoint >= 0x20000 && codePoint <= 0x323af;
|
|
1748
|
+
}
|
|
1749
|
+
function cssFontFamily(value) {
|
|
1750
|
+
return /^(?:-?[\p{L}_])[\p{L}\p{N}_-]*$/u.test(value) ? value : `"${Array.from(value, cssStringCharacter).join('')}"`;
|
|
1751
|
+
}
|
|
1752
|
+
function cssStringCharacter(character) {
|
|
1753
|
+
return /[\\":;{}<>]/u.test(character) ? `\\${character.codePointAt(0)?.toString(16)} ` : character;
|
|
1754
|
+
}
|
|
1755
|
+
function safeCssFontFamilyList(value) {
|
|
1756
|
+
if ('string' != typeof value) return null;
|
|
1757
|
+
const source = value.trim();
|
|
1758
|
+
if (!source || source.length > 1024 || /[;{}]/u.test(source)) return null;
|
|
1759
|
+
const tokens = [];
|
|
1760
|
+
let start = 0;
|
|
1761
|
+
let quote = '';
|
|
1762
|
+
for(let index = 0; index < source.length; index += 1){
|
|
1763
|
+
const character = source[index] ?? '';
|
|
1764
|
+
if (quote) {
|
|
1765
|
+
if ('\\' === character) {
|
|
1766
|
+
index += 1;
|
|
1767
|
+
if (index >= source.length) return null;
|
|
1768
|
+
} else if (character === quote) quote = '';
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if ('"' === character || "'" === character) {
|
|
1772
|
+
quote = character;
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
if (',' === character) {
|
|
1776
|
+
tokens.push(source.slice(start, index).trim());
|
|
1777
|
+
start = index + 1;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
if (quote) return null;
|
|
1781
|
+
tokens.push(source.slice(start).trim());
|
|
1782
|
+
return tokens.length && tokens.every(validCssFontFamilyToken) ? source : null;
|
|
1783
|
+
}
|
|
1784
|
+
function validCssFontFamilyToken(value) {
|
|
1785
|
+
if (!value) return false;
|
|
1786
|
+
if (value.startsWith('"')) return /^"(?:[^"\\\r\n\f]|\\(?:[\da-f]{1,6}\s?|[^\r\n\f]))*"$/iu.test(value) && null !== documentFontNameFromCssFamily(value);
|
|
1787
|
+
if (value.startsWith("'")) return /^'(?:[^'\\\r\n\f]|\\(?:[\da-f]{1,6}\s?|[^\r\n\f]))*'$/iu.test(value) && null !== documentFontNameFromCssFamily(value);
|
|
1788
|
+
return /[\p{L}_]/u.test(value) && /^[\p{L}_-][\p{L}\p{N}_ -]*$/u.test(value) && !/^(?:inherit|initial|revert(?:-layer)?|unset)$/iu.test(value);
|
|
1789
|
+
}
|
|
1790
|
+
function decodeCssEscape(source, start) {
|
|
1791
|
+
if (start >= source.length) return null;
|
|
1792
|
+
const hexadecimal = /^[\da-f]{1,6}/i.exec(source.slice(start))?.[0];
|
|
1793
|
+
if (hexadecimal) {
|
|
1794
|
+
const codePoint = Number.parseInt(hexadecimal, 16);
|
|
1795
|
+
if (0 === codePoint || codePoint > 0x10ffff || codePoint >= 0xd800 && codePoint <= 0xdfff) return null;
|
|
1796
|
+
let end = start + hexadecimal.length;
|
|
1797
|
+
if (/\s/u.test(source[end] ?? '')) end += 1;
|
|
1798
|
+
return {
|
|
1799
|
+
value: String.fromCodePoint(codePoint),
|
|
1800
|
+
end
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
const value = source[start];
|
|
1804
|
+
return !value || /[\r\n\f]/u.test(value) ? null : {
|
|
1805
|
+
value,
|
|
1806
|
+
end: start + 1
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function work_document_script_fonts_isRecordWithKeys(value, allowed) {
|
|
1810
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value) && Object.keys(value).every((key)=>allowed.has(key));
|
|
1811
|
+
}
|
|
1812
|
+
const DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE = 'data-office-proofing-languages';
|
|
1813
|
+
const DOCUMENT_NO_PROOF_ATTRIBUTE = 'data-office-no-proof';
|
|
1814
|
+
const PROOFING_LANGUAGE_KEYS = new Set([
|
|
1815
|
+
'latin',
|
|
1816
|
+
'eastAsia',
|
|
1817
|
+
'bidi'
|
|
1818
|
+
]);
|
|
1819
|
+
const PROOFING_LANGUAGE_ORDER = [
|
|
1820
|
+
'latin',
|
|
1821
|
+
'eastAsia',
|
|
1822
|
+
'bidi'
|
|
1823
|
+
];
|
|
1824
|
+
const MAX_LANGUAGE_TAG_LENGTH = 85;
|
|
1825
|
+
const MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES = 384;
|
|
1826
|
+
function normalizeDocumentLanguageTag(source) {
|
|
1827
|
+
if ('string' != typeof source) return null;
|
|
1828
|
+
if (!source || source !== source.trim() || source.length > MAX_LANGUAGE_TAG_LENGTH || /[\p{Cc}\p{Cs}]/u.test(source)) return null;
|
|
1829
|
+
return /^(?:x-none|[a-z0-9]{1,8}(?:-[a-z0-9]{1,8})*)$/iu.test(source) ? source : null;
|
|
1830
|
+
}
|
|
1831
|
+
function normalizeDocumentProofingLanguages(source) {
|
|
1832
|
+
if (!isRecord(source)) return null;
|
|
1833
|
+
const keys = Object.keys(source);
|
|
1834
|
+
if (!keys.length || keys.some((key)=>!PROOFING_LANGUAGE_KEYS.has(key))) return null;
|
|
1835
|
+
const normalized = {};
|
|
1836
|
+
for (const key of PROOFING_LANGUAGE_ORDER){
|
|
1837
|
+
if (void 0 === source[key]) continue;
|
|
1838
|
+
const language = normalizeDocumentLanguageTag(source[key]);
|
|
1839
|
+
if (!language) return null;
|
|
1840
|
+
normalized[key] = language;
|
|
1841
|
+
}
|
|
1842
|
+
return Object.keys(normalized).length ? normalized : null;
|
|
1843
|
+
}
|
|
1844
|
+
function serializeDocumentProofingLanguages(source) {
|
|
1845
|
+
const normalized = normalizeDocumentProofingLanguages(source);
|
|
1846
|
+
if (!normalized) return;
|
|
1847
|
+
const serialized = JSON.stringify(normalized);
|
|
1848
|
+
return serialized.length <= MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES ? serialized : void 0;
|
|
1849
|
+
}
|
|
1850
|
+
function parseDocumentProofingLanguages(source) {
|
|
1851
|
+
if ('string' != typeof source) return normalizeDocumentProofingLanguages(source);
|
|
1852
|
+
if (!source || source.length > MAX_SERIALIZED_PROOFING_LANGUAGES_BYTES) return null;
|
|
1853
|
+
try {
|
|
1854
|
+
const normalized = normalizeDocumentProofingLanguages(JSON.parse(source));
|
|
1855
|
+
return normalized && JSON.stringify(normalized) === source ? normalized : null;
|
|
1856
|
+
} catch {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
function normalizeDocumentNoProof(source) {
|
|
1861
|
+
if (true === source || 'true' === source || '1' === source) return true;
|
|
1862
|
+
if (false === source || 'false' === source || '0' === source) return false;
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function documentProofingLanguagesFromElement(element) {
|
|
1866
|
+
return parseDocumentProofingLanguages(element.getAttribute(DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE));
|
|
1867
|
+
}
|
|
1868
|
+
function documentNoProofFromElement(element) {
|
|
1869
|
+
return normalizeDocumentNoProof(element.getAttribute(DOCUMENT_NO_PROOF_ATTRIBUTE));
|
|
1870
|
+
}
|
|
1871
|
+
function documentProofingLanguageForScript(source, slot) {
|
|
1872
|
+
const languages = normalizeDocumentProofingLanguages(source);
|
|
1873
|
+
if (!languages) return;
|
|
1874
|
+
if ('eastAsia' === slot) return languages.eastAsia ?? languages.latin;
|
|
1875
|
+
if ('complexScript' === slot) return languages.bidi ?? languages.latin;
|
|
1876
|
+
return languages.latin ?? languages.eastAsia ?? languages.bidi;
|
|
1877
|
+
}
|
|
1878
|
+
function documentProofingDomAttributes(languagesSource, noProofSource, slot) {
|
|
1879
|
+
const languages = normalizeDocumentProofingLanguages(languagesSource);
|
|
1880
|
+
const serialized = serializeDocumentProofingLanguages(languages);
|
|
1881
|
+
const noProof = normalizeDocumentNoProof(noProofSource);
|
|
1882
|
+
const language = documentProofingLanguageForScript(languages, slot);
|
|
1883
|
+
return {
|
|
1884
|
+
...serialized ? {
|
|
1885
|
+
[DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE]: serialized
|
|
1886
|
+
} : {},
|
|
1887
|
+
...null === noProof ? {} : {
|
|
1888
|
+
[DOCUMENT_NO_PROOF_ATTRIBUTE]: String(noProof)
|
|
1889
|
+
},
|
|
1890
|
+
...slot ? {
|
|
1891
|
+
[DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE]: slot
|
|
1892
|
+
} : {},
|
|
1893
|
+
...language && 'x-none' !== language ? {
|
|
1894
|
+
lang: language
|
|
1895
|
+
} : {},
|
|
1896
|
+
...true === noProof ? {
|
|
1897
|
+
spellcheck: 'false'
|
|
1898
|
+
} : {}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function patchDocumentProofingLanguages(source, patch) {
|
|
1902
|
+
const current = normalizeDocumentProofingLanguages(source) ?? {};
|
|
1903
|
+
const next = {
|
|
1904
|
+
...current
|
|
1905
|
+
};
|
|
1906
|
+
for (const slot of PROOFING_LANGUAGE_ORDER){
|
|
1907
|
+
const value = patch[slot];
|
|
1908
|
+
if (void 0 === value) continue;
|
|
1909
|
+
if (null === value) {
|
|
1910
|
+
delete next[slot];
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
const language = normalizeDocumentLanguageTag(value);
|
|
1914
|
+
if (!language) return null;
|
|
1915
|
+
next[slot] = language;
|
|
1916
|
+
}
|
|
1917
|
+
return Object.keys(next).length ? next : null;
|
|
1918
|
+
}
|
|
1919
|
+
function isRecord(source) {
|
|
1920
|
+
return 'object' == typeof source && null !== source && !Array.isArray(source);
|
|
1921
|
+
}
|
|
1922
|
+
const DEFAULT_DOCUMENT_INDEX_OPTIONS = {
|
|
1923
|
+
columns: 1,
|
|
1924
|
+
format: 'indented',
|
|
1925
|
+
rightAlignPageNumbers: true,
|
|
1926
|
+
leader: 'dot'
|
|
1927
|
+
};
|
|
1928
|
+
const MAX_DOCUMENT_INDEX_ENTRIES = 512;
|
|
1929
|
+
const MAX_DOCUMENT_INDEX_MARKERS = 2048;
|
|
1930
|
+
const MAX_DOCUMENT_INDEX_TERM_LENGTH = 240;
|
|
1931
|
+
const INDEX_ENTRY_SELECTOR = '[data-document-index-entry]';
|
|
1932
|
+
const INDEX_SELECTOR = '[data-document-index]';
|
|
1933
|
+
const INDEX_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;
|
|
1934
|
+
function normalizeDocumentIndexEntryDraft(source) {
|
|
1935
|
+
const mainEntry = normalizedIndexTerm(source?.mainEntry);
|
|
1936
|
+
if (!mainEntry) return null;
|
|
1937
|
+
const subEntry = normalizedIndexTerm(source?.subEntry);
|
|
1938
|
+
const crossReference = normalizedIndexTerm(source?.crossReference);
|
|
1939
|
+
return {
|
|
1940
|
+
mainEntry,
|
|
1941
|
+
subEntry,
|
|
1942
|
+
crossReference,
|
|
1943
|
+
pageBold: !crossReference && Boolean(source?.pageBold),
|
|
1944
|
+
pageItalic: !crossReference && Boolean(source?.pageItalic)
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function normalizeDocumentIndexEntry(source, fallbackId = 'index-entry') {
|
|
1948
|
+
const value = normalizeDocumentIndexEntryDraft(source);
|
|
1949
|
+
if (!value) return null;
|
|
1950
|
+
return {
|
|
1951
|
+
id: validIndexId(source?.id) ?? fallbackId,
|
|
1952
|
+
...value
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function normalizeDocumentIndexOptions(source) {
|
|
1956
|
+
return {
|
|
1957
|
+
columns: boundedColumns(source?.columns),
|
|
1958
|
+
format: source?.format === 'run-in' ? 'run-in' : 'indented',
|
|
1959
|
+
rightAlignPageNumbers: source?.rightAlignPageNumbers !== false,
|
|
1960
|
+
leader: indexLeader(source?.leader) ?? 'dot'
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
function normalizeDocumentIndexValue(source) {
|
|
1964
|
+
return {
|
|
1965
|
+
id: validIndexId(source.id) ?? 'document-index',
|
|
1966
|
+
options: normalizeDocumentIndexOptions(source.options),
|
|
1967
|
+
entries: normalizeGeneratedEntries(source.entries),
|
|
1968
|
+
truncated: Boolean(source.truncated)
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function buildDocumentIndexEntries(document, options = {}) {
|
|
1972
|
+
const grouped = new Map();
|
|
1973
|
+
let markerCount = 0;
|
|
1974
|
+
document.descendants((node, position)=>{
|
|
1975
|
+
if ('documentIndexEntry' !== node.type.name) return;
|
|
1976
|
+
markerCount += 1;
|
|
1977
|
+
if (markerCount > MAX_DOCUMENT_INDEX_MARKERS) return;
|
|
1978
|
+
const marker = normalizeDocumentIndexEntry(node.attrs, `index-entry-${markerCount}`);
|
|
1979
|
+
if (!marker) return;
|
|
1980
|
+
const key = indexEntryKey(marker);
|
|
1981
|
+
let group = grouped.get(key);
|
|
1982
|
+
if (!group) {
|
|
1983
|
+
group = {
|
|
1984
|
+
entry: {
|
|
1985
|
+
mainEntry: marker.mainEntry,
|
|
1986
|
+
subEntry: marker.subEntry,
|
|
1987
|
+
crossReference: marker.crossReference,
|
|
1988
|
+
pages: []
|
|
1989
|
+
},
|
|
1990
|
+
pages: new Map()
|
|
1991
|
+
};
|
|
1992
|
+
grouped.set(key, group);
|
|
1993
|
+
}
|
|
1994
|
+
if (marker.crossReference) return;
|
|
1995
|
+
const pageNumber = positiveInteger(options.resolveContext?.(position)?.pageNumber) ?? fallbackDocumentPageNumber(document, position);
|
|
1996
|
+
const existing = group.pages.get(pageNumber);
|
|
1997
|
+
if (existing) {
|
|
1998
|
+
existing.pageBold ||= marker.pageBold;
|
|
1999
|
+
existing.pageItalic ||= marker.pageItalic;
|
|
2000
|
+
if (!existing.targetIds.includes(marker.id)) existing.targetIds.push(marker.id);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
group.pages.set(pageNumber, {
|
|
2004
|
+
pageNumber,
|
|
2005
|
+
pageBold: marker.pageBold,
|
|
2006
|
+
pageItalic: marker.pageItalic,
|
|
2007
|
+
targetIds: [
|
|
2008
|
+
marker.id
|
|
2009
|
+
]
|
|
2010
|
+
});
|
|
2011
|
+
});
|
|
2012
|
+
const collator = new Intl.Collator(void 0, {
|
|
2013
|
+
numeric: true,
|
|
2014
|
+
sensitivity: 'base',
|
|
2015
|
+
usage: 'sort'
|
|
2016
|
+
});
|
|
2017
|
+
const allEntries = Array.from(grouped.values()).map(({ entry, pages })=>({
|
|
2018
|
+
...entry,
|
|
2019
|
+
pages: Array.from(pages.values()).sort((left, right)=>left.pageNumber - right.pageNumber)
|
|
2020
|
+
})).sort((left, right)=>collator.compare(left.mainEntry, right.mainEntry) || collator.compare(left.subEntry, right.subEntry) || collator.compare(left.crossReference, right.crossReference));
|
|
2021
|
+
return {
|
|
2022
|
+
entries: allEntries.slice(0, MAX_DOCUMENT_INDEX_ENTRIES),
|
|
2023
|
+
truncated: markerCount > MAX_DOCUMENT_INDEX_MARKERS || allEntries.length > MAX_DOCUMENT_INDEX_ENTRIES
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
function documentIndexEntryHtml(source) {
|
|
2027
|
+
const value = normalizeDocumentIndexEntry(source);
|
|
2028
|
+
if (!value) return '';
|
|
2029
|
+
const detail = indexEntryDisplay(value);
|
|
2030
|
+
return [
|
|
2031
|
+
`<span data-document-index-entry="true" data-index-entry-id="${escapeHtmlAttribute(value.id)}" data-index-main-entry="${escapeHtmlAttribute(value.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(value.subEntry)}" data-index-cross-reference="${escapeHtmlAttribute(value.crossReference)}" data-index-page-bold="${String(value.pageBold)}" data-index-page-italic="${String(value.pageItalic)}" class="work-document-index-entry" contenteditable="false" aria-label="索引项:${escapeHtmlAttribute(detail)}">`,
|
|
2032
|
+
'<span aria-hidden="true">索引项</span>',
|
|
2033
|
+
`<strong>${escapeHtml(detail)}</strong>`,
|
|
2034
|
+
'</span>'
|
|
2035
|
+
].join('');
|
|
2036
|
+
}
|
|
2037
|
+
function documentIndexHtml(source) {
|
|
2038
|
+
const value = normalizeDocumentIndexValue(source);
|
|
2039
|
+
const rows = value.entries.length ? value.entries.map(documentIndexRowHtml) : [
|
|
2040
|
+
'<li class="work-document-index-empty">没有已标记的索引项</li>'
|
|
2041
|
+
];
|
|
2042
|
+
const status = value.truncated ? `仅显示前 ${MAX_DOCUMENT_INDEX_ENTRIES} 项` : `${value.entries.length} 项`;
|
|
2043
|
+
return [
|
|
2044
|
+
`<div data-document-index="true" data-index-id="${escapeHtmlAttribute(value.id)}" data-index-columns="${value.options.columns}" data-index-format="${value.options.format}" data-index-right-align-page-numbers="${String(value.options.rightAlignPageNumbers)}" data-index-leader="${value.options.leader}" data-index-entries="${escapeHtmlAttribute(JSON.stringify(value.entries))}" data-index-truncated="${String(value.truncated)}" class="work-document-index" contenteditable="false" aria-label="索引">`,
|
|
2045
|
+
`<div class="work-document-index-header"><strong>索引</strong><span>${status}</span></div>`,
|
|
2046
|
+
'<ol class="work-document-index-list">',
|
|
2047
|
+
...rows,
|
|
2048
|
+
'</ol></div>'
|
|
2049
|
+
].join('');
|
|
2050
|
+
}
|
|
2051
|
+
function documentIndexEntryFromElement(element, index = 1) {
|
|
2052
|
+
return normalizeDocumentIndexEntry({
|
|
2053
|
+
id: element.dataset.indexEntryId,
|
|
2054
|
+
mainEntry: element.dataset.indexMainEntry,
|
|
2055
|
+
subEntry: element.dataset.indexSubEntry,
|
|
2056
|
+
crossReference: element.dataset.indexCrossReference,
|
|
2057
|
+
pageBold: 'true' === element.dataset.indexPageBold,
|
|
2058
|
+
pageItalic: 'true' === element.dataset.indexPageItalic
|
|
2059
|
+
}, `index-entry-${index}`);
|
|
2060
|
+
}
|
|
2061
|
+
function documentIndexValueFromElement(element, index = 1) {
|
|
2062
|
+
return normalizeDocumentIndexValue({
|
|
2063
|
+
id: validIndexId(element.dataset.indexId) ?? `document-index-${index}`,
|
|
2064
|
+
options: {
|
|
2065
|
+
columns: Number(element.dataset.indexColumns),
|
|
2066
|
+
format: 'run-in' === element.dataset.indexFormat ? 'run-in' : 'indented',
|
|
2067
|
+
rightAlignPageNumbers: 'false' !== element.dataset.indexRightAlignPageNumbers,
|
|
2068
|
+
leader: indexLeader(element.dataset.indexLeader) ?? 'dot'
|
|
2069
|
+
},
|
|
2070
|
+
entries: parseGeneratedEntries(element.dataset.indexEntries),
|
|
2071
|
+
truncated: 'true' === element.dataset.indexTruncated
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
function normalizeDocumentIndexesHtml(source) {
|
|
2075
|
+
const document = new DOMParser().parseFromString(source, 'text/html');
|
|
2076
|
+
const usedEntryIds = new Set();
|
|
2077
|
+
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_ENTRY_SELECTOR)).entries()){
|
|
2078
|
+
const value = documentIndexEntryFromElement(element, index + 1);
|
|
2079
|
+
if (!value) {
|
|
2080
|
+
element.remove();
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
value.id = uniqueIndexId(value.id, 'index-entry', index + 1, usedEntryIds);
|
|
2084
|
+
element.replaceWith(document.createRange().createContextualFragment(documentIndexEntryHtml(value)));
|
|
2085
|
+
}
|
|
2086
|
+
const usedIndexIds = new Set();
|
|
2087
|
+
for (const [index, element] of Array.from(document.body.querySelectorAll(INDEX_SELECTOR)).entries()){
|
|
2088
|
+
const value = documentIndexValueFromElement(element, index + 1);
|
|
2089
|
+
value.id = uniqueIndexId(value.id, 'document-index', index + 1, usedIndexIds);
|
|
2090
|
+
element.replaceWith(document.createRange().createContextualFragment(documentIndexHtml(value)));
|
|
2091
|
+
}
|
|
2092
|
+
return document.body.innerHTML;
|
|
2093
|
+
}
|
|
2094
|
+
function indexLeader(value) {
|
|
2095
|
+
return 'dot' === value || 'dash' === value || 'underline' === value || 'none' === value ? value : null;
|
|
2096
|
+
}
|
|
2097
|
+
function documentIndexRowHtml(entry) {
|
|
2098
|
+
const term = entry.subEntry ? `<span class="work-document-index-main">${escapeHtml(entry.mainEntry)}</span><span class="work-document-index-sub">${escapeHtml(entry.subEntry)}</span>` : `<span class="work-document-index-main">${escapeHtml(entry.mainEntry)}</span>`;
|
|
2099
|
+
const pages = entry.crossReference ? `<span class="work-document-index-cross-reference">参见 ${escapeHtml(entry.crossReference)}</span>` : entry.pages.map((page)=>{
|
|
2100
|
+
const target = page.targetIds[0] ?? '';
|
|
2101
|
+
const classes = [
|
|
2102
|
+
page.pageBold ? 'bold' : '',
|
|
2103
|
+
page.pageItalic ? 'italic' : ''
|
|
2104
|
+
].filter(Boolean).join(' ');
|
|
2105
|
+
return `<a href="#${escapeHtmlAttribute(target)}" data-index-target="${escapeHtmlAttribute(target)}" tabindex="-1"${classes ? ` class="${classes}"` : ''}>${page.pageNumber}</a>`;
|
|
2106
|
+
}).join('<span aria-hidden="true">, </span>');
|
|
2107
|
+
return `<li data-index-main-entry="${escapeHtmlAttribute(entry.mainEntry)}" data-index-sub-entry="${escapeHtmlAttribute(entry.subEntry)}">${term}<i aria-hidden="true"></i><span class="work-document-index-pages">${pages}</span></li>`;
|
|
2108
|
+
}
|
|
2109
|
+
function normalizeGeneratedEntries(source) {
|
|
2110
|
+
const entries = [];
|
|
2111
|
+
for (const item of source.slice(0, MAX_DOCUMENT_INDEX_ENTRIES)){
|
|
2112
|
+
if (!item || 'object' != typeof item) continue;
|
|
2113
|
+
const candidate = item;
|
|
2114
|
+
const draft = normalizeDocumentIndexEntryDraft({
|
|
2115
|
+
mainEntry: normalizedIndexTerm(candidate.mainEntry),
|
|
2116
|
+
subEntry: normalizedIndexTerm(candidate.subEntry),
|
|
2117
|
+
crossReference: normalizedIndexTerm(candidate.crossReference)
|
|
2118
|
+
});
|
|
2119
|
+
if (draft) entries.push({
|
|
2120
|
+
mainEntry: draft.mainEntry,
|
|
2121
|
+
subEntry: draft.subEntry,
|
|
2122
|
+
crossReference: draft.crossReference,
|
|
2123
|
+
pages: draft.crossReference ? [] : normalizeIndexPages(candidate.pages)
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
return entries;
|
|
2127
|
+
}
|
|
2128
|
+
function normalizeIndexPages(source) {
|
|
2129
|
+
if (!Array.isArray(source)) return [];
|
|
2130
|
+
const pages = [];
|
|
2131
|
+
const seen = new Set();
|
|
2132
|
+
for (const item of source.slice(0, MAX_DOCUMENT_INDEX_MARKERS)){
|
|
2133
|
+
if (!item || 'object' != typeof item) continue;
|
|
2134
|
+
const candidate = item;
|
|
2135
|
+
const pageNumber = positiveInteger(candidate.pageNumber);
|
|
2136
|
+
if (!pageNumber || seen.has(pageNumber)) continue;
|
|
2137
|
+
const targetIds = Array.isArray(candidate.targetIds) ? candidate.targetIds.flatMap((value)=>validIndexId(value) ? [
|
|
2138
|
+
String(value)
|
|
2139
|
+
] : []).slice(0, 64) : [];
|
|
2140
|
+
if (targetIds.length) {
|
|
2141
|
+
seen.add(pageNumber);
|
|
2142
|
+
pages.push({
|
|
2143
|
+
pageNumber,
|
|
2144
|
+
pageBold: Boolean(candidate.pageBold),
|
|
2145
|
+
pageItalic: Boolean(candidate.pageItalic),
|
|
2146
|
+
targetIds
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return pages.sort((left, right)=>left.pageNumber - right.pageNumber);
|
|
2151
|
+
}
|
|
2152
|
+
function parseGeneratedEntries(source) {
|
|
2153
|
+
if (!source) return [];
|
|
2154
|
+
try {
|
|
2155
|
+
const parsed = JSON.parse(source);
|
|
2156
|
+
return Array.isArray(parsed) ? normalizeGeneratedEntries(parsed) : [];
|
|
2157
|
+
} catch {
|
|
2158
|
+
return [];
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
function fallbackDocumentPageNumber(document, position) {
|
|
2162
|
+
let pageNumber = 1;
|
|
2163
|
+
document.descendants((node, offset)=>{
|
|
2164
|
+
if (offset >= position) return false;
|
|
2165
|
+
if ('pageBreak' === node.type.name) pageNumber += 1;
|
|
2166
|
+
return true;
|
|
2167
|
+
});
|
|
2168
|
+
return pageNumber;
|
|
2169
|
+
}
|
|
2170
|
+
function indexEntryKey(entry) {
|
|
2171
|
+
return [
|
|
2172
|
+
entry.mainEntry,
|
|
2173
|
+
entry.subEntry,
|
|
2174
|
+
entry.crossReference
|
|
2175
|
+
].map((value)=>value.normalize('NFKC').toLocaleLowerCase()).join('\u0000');
|
|
2176
|
+
}
|
|
2177
|
+
function indexEntryDisplay(entry) {
|
|
2178
|
+
const term = entry.subEntry ? `${entry.mainEntry} › ${entry.subEntry}` : entry.mainEntry;
|
|
2179
|
+
return entry.crossReference ? `${term} · 参见 ${entry.crossReference}` : term;
|
|
2180
|
+
}
|
|
2181
|
+
function normalizedIndexTerm(value) {
|
|
2182
|
+
return 'string' == typeof value ? value.replace(/\s+/g, ' ').trim().slice(0, MAX_DOCUMENT_INDEX_TERM_LENGTH) : '';
|
|
2183
|
+
}
|
|
2184
|
+
function boundedColumns(value) {
|
|
2185
|
+
const number = Number(value);
|
|
2186
|
+
return Number.isInteger(number) && number >= 1 && number <= 4 ? number : 1;
|
|
2187
|
+
}
|
|
2188
|
+
function positiveInteger(value) {
|
|
2189
|
+
const number = Number(value);
|
|
2190
|
+
return Number.isSafeInteger(number) && number > 0 ? Math.min(999999, number) : null;
|
|
2191
|
+
}
|
|
2192
|
+
function validIndexId(value) {
|
|
2193
|
+
return 'string' == typeof value && INDEX_ID_PATTERN.test(value) ? value : null;
|
|
2194
|
+
}
|
|
2195
|
+
function uniqueIndexId(source, prefix, index, used) {
|
|
2196
|
+
if (!used.has(source)) {
|
|
2197
|
+
used.add(source);
|
|
2198
|
+
return source;
|
|
2199
|
+
}
|
|
2200
|
+
let suffix = index;
|
|
2201
|
+
while(used.has(`${prefix}-${suffix}`))suffix += 1;
|
|
2202
|
+
const id = `${prefix}-${suffix}`;
|
|
2203
|
+
used.add(id);
|
|
2204
|
+
return id;
|
|
2205
|
+
}
|
|
2206
|
+
function escapeHtml(value) {
|
|
2207
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
2208
|
+
}
|
|
2209
|
+
function escapeHtmlAttribute(value) {
|
|
2210
|
+
return escapeHtml(value).replaceAll('"', '"').replaceAll("'", ''');
|
|
2211
|
+
}
|
|
2212
|
+
const DOCUMENT_PARAGRAPH_ID_ATTRIBUTE = 'data-office-paragraph-id';
|
|
2213
|
+
const DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE = 'data-office-paragraph-text-id';
|
|
2214
|
+
const PARAGRAPH_ID_PATTERN = /^[0-9A-F]{8}$/;
|
|
2215
|
+
const MAX_PARAGRAPH_ID = 0x7fffffff;
|
|
2216
|
+
let fallbackIdentitySequence = 0;
|
|
2217
|
+
const DocumentParagraphIdentity = Extension.create({
|
|
2218
|
+
name: 'documentParagraphIdentity',
|
|
2219
|
+
addOptions () {
|
|
2220
|
+
return {
|
|
2221
|
+
rotateTextId: ()=>true,
|
|
2222
|
+
types: [
|
|
2223
|
+
'paragraph',
|
|
2224
|
+
'heading',
|
|
2225
|
+
'documentCaption'
|
|
2226
|
+
]
|
|
2227
|
+
};
|
|
2228
|
+
},
|
|
2229
|
+
addGlobalAttributes () {
|
|
2230
|
+
return [
|
|
2231
|
+
{
|
|
2232
|
+
types: this.options.types,
|
|
2233
|
+
attributes: {
|
|
2234
|
+
paragraphId: {
|
|
2235
|
+
default: null,
|
|
2236
|
+
parseHTML: (element)=>normalizeDocumentParagraphId(element.getAttribute(DOCUMENT_PARAGRAPH_ID_ATTRIBUTE)),
|
|
2237
|
+
renderHTML: (attributes)=>{
|
|
2238
|
+
const value = normalizeDocumentParagraphId(attributes.paragraphId);
|
|
2239
|
+
return value ? {
|
|
2240
|
+
[DOCUMENT_PARAGRAPH_ID_ATTRIBUTE]: value
|
|
2241
|
+
} : {};
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
textId: {
|
|
2245
|
+
default: null,
|
|
2246
|
+
parseHTML: (element)=>normalizeDocumentParagraphId(element.getAttribute(DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE)),
|
|
2247
|
+
renderHTML: (attributes)=>{
|
|
2248
|
+
const value = normalizeDocumentParagraphId(attributes.textId);
|
|
2249
|
+
return value ? {
|
|
2250
|
+
[DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE]: value
|
|
2251
|
+
} : {};
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
];
|
|
2257
|
+
},
|
|
2258
|
+
addProseMirrorPlugins () {
|
|
2259
|
+
return [
|
|
2260
|
+
createDocumentParagraphIdentityPlugin(this.options.types, this.options.rotateTextId)
|
|
2261
|
+
];
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
function normalizeDocumentParagraphId(value) {
|
|
2265
|
+
if ('string' != typeof value) return null;
|
|
2266
|
+
const normalized = value.trim().toUpperCase();
|
|
2267
|
+
if (!PARAGRAPH_ID_PATTERN.test(normalized)) return null;
|
|
2268
|
+
const number = Number.parseInt(normalized, 16);
|
|
2269
|
+
return number > 0 && number <= MAX_PARAGRAPH_ID ? normalized : null;
|
|
2270
|
+
}
|
|
2271
|
+
function createDocumentParagraphIdentityRegistry() {
|
|
2272
|
+
return {
|
|
2273
|
+
paragraphIds: new Set()
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
function normalizeDocumentParagraphIdentity(source) {
|
|
2277
|
+
const paragraphId = normalizeDocumentParagraphId(source.paragraphId);
|
|
2278
|
+
const textId = normalizeDocumentParagraphId(source.textId);
|
|
2279
|
+
return paragraphId && textId ? {
|
|
2280
|
+
paragraphId,
|
|
2281
|
+
textId
|
|
2282
|
+
} : null;
|
|
2283
|
+
}
|
|
2284
|
+
function documentParagraphIdentityFromElement(element) {
|
|
2285
|
+
return normalizeDocumentParagraphIdentity({
|
|
2286
|
+
paragraphId: element.getAttribute(DOCUMENT_PARAGRAPH_ID_ATTRIBUTE),
|
|
2287
|
+
textId: element.getAttribute(DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE)
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
function applyDocumentParagraphIdentityToElement(element, source) {
|
|
2291
|
+
const identity = normalizeDocumentParagraphIdentity(source);
|
|
2292
|
+
if (!identity) {
|
|
2293
|
+
element.removeAttribute(DOCUMENT_PARAGRAPH_ID_ATTRIBUTE);
|
|
2294
|
+
element.removeAttribute(DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE);
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
element.setAttribute(DOCUMENT_PARAGRAPH_ID_ATTRIBUTE, identity.paragraphId);
|
|
2298
|
+
element.setAttribute(DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE, identity.textId);
|
|
2299
|
+
return identity;
|
|
2300
|
+
}
|
|
2301
|
+
function createDocumentParagraphIdentityPlugin(types, rotateTextId) {
|
|
2302
|
+
const trackedTypes = new Set(types);
|
|
2303
|
+
return new Plugin({
|
|
2304
|
+
view (view) {
|
|
2305
|
+
const transaction = normalizeDocumentParagraphIdentities(view.state, trackedTypes, rotateTextId);
|
|
2306
|
+
if (transaction) {
|
|
2307
|
+
transaction.setMeta('addToHistory', false);
|
|
2308
|
+
view.dispatch(transaction);
|
|
2309
|
+
}
|
|
2310
|
+
return {};
|
|
2311
|
+
},
|
|
2312
|
+
appendTransaction (transactions, oldState, newState) {
|
|
2313
|
+
if (!transactions.some((transaction)=>transaction.docChanged)) return null;
|
|
2314
|
+
return normalizeDocumentParagraphIdentities(newState, trackedTypes, rotateTextId, oldState, transactions);
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
function normalizeDocumentParagraphIdentities(state, trackedTypes, rotateTextId, oldState, transactions = []) {
|
|
2319
|
+
if (!documentHasIntegrityFeature(state.doc, 16)) return null;
|
|
2320
|
+
const paragraphs = documentParagraphs(state.doc, trackedTypes);
|
|
2321
|
+
if (!paragraphs.length) return null;
|
|
2322
|
+
const retainedPositions = oldState ? retainedDocumentParagraphPositions(oldState, state, transactions, trackedTypes) : new Set();
|
|
2323
|
+
const editedPositions = oldState && rotateTextId() && !transactions.some(isHistoryTransaction) ? editedDocumentParagraphPositions(oldState, state, transactions, trackedTypes) : new Set();
|
|
2324
|
+
const ordered = [
|
|
2325
|
+
...paragraphs.filter((item)=>retainedPositions.has(item.position)),
|
|
2326
|
+
...paragraphs.filter((item)=>!retainedPositions.has(item.position))
|
|
2327
|
+
];
|
|
2328
|
+
const registry = createDocumentParagraphIdentityRegistry();
|
|
2329
|
+
const updates = new Map();
|
|
2330
|
+
for (const paragraph of ordered){
|
|
2331
|
+
const current = paragraphIdentitySourceFromNode(paragraph.node);
|
|
2332
|
+
const source = editedPositions.has(paragraph.position) ? {
|
|
2333
|
+
...current,
|
|
2334
|
+
textId: nextDocumentParagraphTextId(current.textId)
|
|
2335
|
+
} : current;
|
|
2336
|
+
const identity = uniqueDocumentParagraphIdentity(source, registry);
|
|
2337
|
+
if (!sameDocumentParagraphIdentity(current, identity)) updates.set(paragraph.position, identity);
|
|
2338
|
+
}
|
|
2339
|
+
if (!updates.size) return null;
|
|
2340
|
+
const transaction = state.tr;
|
|
2341
|
+
for (const paragraph of paragraphs){
|
|
2342
|
+
const identity = updates.get(paragraph.position);
|
|
2343
|
+
if (identity) transaction.setNodeMarkup(paragraph.position, void 0, {
|
|
2344
|
+
...paragraph.node.attrs,
|
|
2345
|
+
...identity
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
if (!transaction.docChanged) return null;
|
|
2349
|
+
transaction.setMeta('addToHistory', false);
|
|
2350
|
+
return transaction;
|
|
2351
|
+
}
|
|
2352
|
+
function hasDocumentParagraphIdentityComponent(node) {
|
|
2353
|
+
return Boolean(normalizeDocumentParagraphId(node.attrs.paragraphId) || normalizeDocumentParagraphId(node.attrs.textId));
|
|
2354
|
+
}
|
|
2355
|
+
function retainedDocumentParagraphPositions(oldState, newState, transactions, trackedTypes) {
|
|
2356
|
+
const mapping = transactionMapping(transactions);
|
|
2357
|
+
const retained = new Set();
|
|
2358
|
+
for (const previous of documentParagraphs(oldState.doc, trackedTypes)){
|
|
2359
|
+
const paragraphId = normalizeDocumentParagraphId(previous.node.attrs.paragraphId);
|
|
2360
|
+
if (!paragraphId) continue;
|
|
2361
|
+
const mapped = mapping.mapResult(previous.position, 1);
|
|
2362
|
+
const current = newState.doc.nodeAt(mapped.pos);
|
|
2363
|
+
if (current && trackedTypes.has(current.type.name) && normalizeDocumentParagraphId(current.attrs.paragraphId) === paragraphId) retained.add(mapped.pos);
|
|
2364
|
+
}
|
|
2365
|
+
return retained;
|
|
2366
|
+
}
|
|
2367
|
+
function editedDocumentParagraphPositions(oldState, newState, transactions, trackedTypes) {
|
|
2368
|
+
const mapping = transactionMapping(transactions);
|
|
2369
|
+
const previousParagraphs = documentParagraphs(oldState.doc, trackedTypes);
|
|
2370
|
+
const currentParagraphs = documentParagraphs(newState.doc, trackedTypes);
|
|
2371
|
+
const previousById = paragraphsById(previousParagraphs);
|
|
2372
|
+
const currentById = paragraphsById(currentParagraphs);
|
|
2373
|
+
const edited = new Set();
|
|
2374
|
+
for (const previous of previousParagraphs){
|
|
2375
|
+
const identity = normalizeDocumentParagraphIdentity(paragraphIdentitySourceFromNode(previous.node));
|
|
2376
|
+
if (!identity) continue;
|
|
2377
|
+
const mapped = mapping.mapResult(previous.position, 1);
|
|
2378
|
+
const current = newState.doc.nodeAt(mapped.pos);
|
|
2379
|
+
if (current && trackedTypes.has(current.type.name) && normalizeDocumentParagraphId(current.attrs.paragraphId) === identity.paragraphId) {
|
|
2380
|
+
if (paragraphTextChanged(previous.node, current, identity.textId)) edited.add(mapped.pos);
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
const previousMatches = previousById.get(identity.paragraphId) ?? [];
|
|
2384
|
+
const currentMatches = currentById.get(identity.paragraphId) ?? [];
|
|
2385
|
+
if (1 === previousMatches.length && 1 === currentMatches.length && paragraphTextChanged(previous.node, currentMatches[0].node, identity.textId)) edited.add(currentMatches[0].position);
|
|
2386
|
+
}
|
|
2387
|
+
return edited;
|
|
2388
|
+
}
|
|
2389
|
+
function paragraphTextChanged(previous, current, previousTextId) {
|
|
2390
|
+
return previous.textContent !== current.textContent && normalizeDocumentParagraphId(current.attrs.textId) === previousTextId;
|
|
2391
|
+
}
|
|
2392
|
+
function paragraphsById(paragraphs) {
|
|
2393
|
+
const result = new Map();
|
|
2394
|
+
for (const paragraph of paragraphs){
|
|
2395
|
+
const id = normalizeDocumentParagraphId(paragraph.node.attrs.paragraphId);
|
|
2396
|
+
if (!id) continue;
|
|
2397
|
+
const matches = result.get(id) ?? [];
|
|
2398
|
+
matches.push(paragraph);
|
|
2399
|
+
result.set(id, matches);
|
|
2400
|
+
}
|
|
2401
|
+
return result;
|
|
2402
|
+
}
|
|
2403
|
+
function documentParagraphs(document, trackedTypes) {
|
|
2404
|
+
const paragraphs = [];
|
|
2405
|
+
document.descendants((node, position)=>{
|
|
2406
|
+
if (trackedTypes.has(node.type.name) && hasDocumentParagraphIdentityComponent(node)) paragraphs.push({
|
|
2407
|
+
node,
|
|
2408
|
+
position
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
return paragraphs;
|
|
2412
|
+
}
|
|
2413
|
+
function paragraphIdentitySourceFromNode(node) {
|
|
2414
|
+
return {
|
|
2415
|
+
paragraphId: node.attrs.paragraphId,
|
|
2416
|
+
textId: node.attrs.textId
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
function uniqueDocumentParagraphIdentity(source, registry) {
|
|
2420
|
+
const preferred = normalizeDocumentParagraphIdentity(source);
|
|
2421
|
+
if (preferred && !registry.paragraphIds.has(preferred.paragraphId)) {
|
|
2422
|
+
registry.paragraphIds.add(preferred.paragraphId);
|
|
2423
|
+
return preferred;
|
|
2424
|
+
}
|
|
2425
|
+
for(let attempt = 0; attempt < 8; attempt += 1){
|
|
2426
|
+
const identity = createDocumentParagraphIdentity();
|
|
2427
|
+
if (!registry.paragraphIds.has(identity.paragraphId)) {
|
|
2428
|
+
registry.paragraphIds.add(identity.paragraphId);
|
|
2429
|
+
return identity;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
return sequentialDocumentParagraphIdentity(registry);
|
|
2433
|
+
}
|
|
2434
|
+
function createDocumentParagraphIdentity() {
|
|
2435
|
+
return {
|
|
2436
|
+
paragraphId: randomDocumentParagraphId(),
|
|
2437
|
+
textId: randomDocumentParagraphId()
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
function sequentialDocumentParagraphIdentity(registry) {
|
|
2441
|
+
for(let value = 1; value <= MAX_PARAGRAPH_ID; value += 1){
|
|
2442
|
+
const paragraphId = formatDocumentParagraphId(value);
|
|
2443
|
+
if (!registry.paragraphIds.has(paragraphId)) {
|
|
2444
|
+
registry.paragraphIds.add(paragraphId);
|
|
2445
|
+
return {
|
|
2446
|
+
paragraphId,
|
|
2447
|
+
textId: randomDocumentParagraphId()
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
throw new Error('No unique Word paragraph identity is available.');
|
|
2452
|
+
}
|
|
2453
|
+
function nextDocumentParagraphTextId(value) {
|
|
2454
|
+
const current = normalizeDocumentParagraphId(value);
|
|
2455
|
+
if (!current) return randomDocumentParagraphId();
|
|
2456
|
+
const number = Number.parseInt(current, 16);
|
|
2457
|
+
return formatDocumentParagraphId(number >= MAX_PARAGRAPH_ID ? 1 : number + 1);
|
|
2458
|
+
}
|
|
2459
|
+
function randomDocumentParagraphId() {
|
|
2460
|
+
let value = 0;
|
|
2461
|
+
const cryptoApi = globalThis.crypto;
|
|
2462
|
+
if ('function' == typeof cryptoApi?.getRandomValues) {
|
|
2463
|
+
const values = new Uint32Array(1);
|
|
2464
|
+
cryptoApi.getRandomValues(values);
|
|
2465
|
+
value = values[0] ?? 0;
|
|
2466
|
+
} else if ('function' == typeof cryptoApi?.randomUUID) value = Number.parseInt(cryptoApi.randomUUID().replaceAll('-', '').slice(0, 8), 16);
|
|
2467
|
+
if (!value) {
|
|
2468
|
+
fallbackIdentitySequence += 1;
|
|
2469
|
+
value = stableIdentityHash(`${Date.now()}:${Math.random()}:${fallbackIdentitySequence}`);
|
|
2470
|
+
}
|
|
2471
|
+
return formatDocumentParagraphId(value || fallbackIdentitySequence || 1);
|
|
2472
|
+
}
|
|
2473
|
+
function stableIdentityHash(value) {
|
|
2474
|
+
let hash = 0x811c9dc5;
|
|
2475
|
+
for(let index = 0; index < value.length; index += 1){
|
|
2476
|
+
hash ^= value.charCodeAt(index);
|
|
2477
|
+
hash = Math.imul(hash, 0x01000193);
|
|
2478
|
+
}
|
|
2479
|
+
return hash >>> 0 || 1;
|
|
2480
|
+
}
|
|
2481
|
+
function formatDocumentParagraphId(value) {
|
|
2482
|
+
return (value >>> 0 & MAX_PARAGRAPH_ID || 1).toString(16).toUpperCase().padStart(8, '0');
|
|
2483
|
+
}
|
|
2484
|
+
function sameDocumentParagraphIdentity(source, identity) {
|
|
2485
|
+
return normalizeDocumentParagraphId(source.paragraphId) === identity.paragraphId && normalizeDocumentParagraphId(source.textId) === identity.textId;
|
|
2486
|
+
}
|
|
2487
|
+
function transactionMapping(transactions) {
|
|
2488
|
+
const mapping = new Mapping();
|
|
2489
|
+
for (const transaction of transactions)mapping.appendMapping(transaction.mapping);
|
|
2490
|
+
return mapping;
|
|
2491
|
+
}
|
|
2492
|
+
function collectWorkDocumentOutline(document) {
|
|
2493
|
+
const items = [];
|
|
2494
|
+
const hierarchy = [];
|
|
2495
|
+
document.descendants((node, position)=>{
|
|
2496
|
+
const level = workDocumentOutlineLevel(node);
|
|
2497
|
+
if (null === level) return;
|
|
2498
|
+
while((hierarchy.at(-1)?.level ?? 0) >= level)hierarchy.pop();
|
|
2499
|
+
const parent = hierarchy.at(-1);
|
|
2500
|
+
const from = position + 1;
|
|
2501
|
+
const paragraphId = normalizeDocumentParagraphId(node.attrs.paragraphId);
|
|
2502
|
+
const item = {
|
|
2503
|
+
id: paragraphId ? `heading-${paragraphId.toLowerCase()}` : `heading-${position}`,
|
|
2504
|
+
text: normalizedDocumentHeadingText(node.textContent),
|
|
2505
|
+
level,
|
|
2506
|
+
depth: hierarchy.length,
|
|
2507
|
+
from,
|
|
2508
|
+
to: from + node.content.size,
|
|
2509
|
+
...parent ? {
|
|
2510
|
+
parentId: parent.id
|
|
2511
|
+
} : {}
|
|
2512
|
+
};
|
|
2513
|
+
items.push(item);
|
|
2514
|
+
hierarchy.push(item);
|
|
2515
|
+
});
|
|
2516
|
+
return items.map((item, index)=>({
|
|
2517
|
+
...item,
|
|
2518
|
+
hasChildren: (items[index + 1]?.depth ?? -1) > item.depth
|
|
2519
|
+
}));
|
|
2520
|
+
}
|
|
2521
|
+
function workDocumentOutlineLevel(node) {
|
|
2522
|
+
if ('heading' === node.type.name) return documentHeadingLevel(node.attrs.level);
|
|
2523
|
+
if ('paragraph' !== node.type.name) return null;
|
|
2524
|
+
const value = node.attrs.outlineLevel;
|
|
2525
|
+
if (null == value || '' === value) return null;
|
|
2526
|
+
const outlineLevel = Number(value);
|
|
2527
|
+
return Number.isInteger(outlineLevel) && outlineLevel >= 0 && outlineLevel <= 8 ? outlineLevel + 1 : null;
|
|
2528
|
+
}
|
|
2529
|
+
function currentWorkDocumentOutlineItem(items, position) {
|
|
2530
|
+
let current = null;
|
|
2531
|
+
for (const item of items){
|
|
2532
|
+
if (item.from > position) break;
|
|
2533
|
+
current = item;
|
|
2534
|
+
}
|
|
2535
|
+
return current;
|
|
2536
|
+
}
|
|
2537
|
+
function visibleWorkDocumentOutlineItems(items, collapsedIds, rawQuery) {
|
|
2538
|
+
const query = normalizedDocumentOutlineQuery(rawQuery);
|
|
2539
|
+
if (query) return items.filter((item)=>normalizedDocumentOutlineQuery(item.text).includes(query));
|
|
2540
|
+
const visible = [];
|
|
2541
|
+
let hiddenBelowDepth = null;
|
|
2542
|
+
for (const item of items){
|
|
2543
|
+
if (null !== hiddenBelowDepth && item.depth <= hiddenBelowDepth) hiddenBelowDepth = null;
|
|
2544
|
+
if (null === hiddenBelowDepth) {
|
|
2545
|
+
visible.push(item);
|
|
2546
|
+
if (item.hasChildren && collapsedIds.has(item.id)) hiddenBelowDepth = item.depth;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return visible;
|
|
2550
|
+
}
|
|
2551
|
+
function documentHeadingLevel(value) {
|
|
2552
|
+
return 'number' == typeof value && Number.isInteger(value) && value >= 1 && value <= 6 ? value : 1;
|
|
2553
|
+
}
|
|
2554
|
+
function normalizedDocumentHeadingText(value) {
|
|
2555
|
+
return value.replace(/\s+/g, ' ').trim() || '未命名标题';
|
|
2556
|
+
}
|
|
2557
|
+
function normalizedDocumentOutlineQuery(value) {
|
|
2558
|
+
return value.normalize('NFKC').trim().toLocaleLowerCase();
|
|
2559
|
+
}
|
|
2560
|
+
const DEFAULT_DOCUMENT_TABLE_OF_CONTENTS_OPTIONS = {
|
|
2561
|
+
minLevel: 1,
|
|
2562
|
+
maxLevel: 3,
|
|
2563
|
+
hyperlinks: true,
|
|
2564
|
+
showPageNumbers: true,
|
|
2565
|
+
rightAlignPageNumbers: true,
|
|
2566
|
+
leader: 'dot'
|
|
2567
|
+
};
|
|
2568
|
+
const MAX_DOCUMENT_TABLE_OF_CONTENTS_ENTRIES = 512;
|
|
2569
|
+
const TABLE_OF_CONTENTS_SELECTOR = '[data-document-table-of-contents]';
|
|
2570
|
+
const MAX_TABLE_OF_CONTENTS_TITLE_LENGTH = 512;
|
|
2571
|
+
const TABLE_OF_CONTENTS_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;
|
|
2572
|
+
const TABLE_OF_CONTENTS_TARGET_PATTERN = /^heading-[a-z0-9._:-]{1,128}$/i;
|
|
2573
|
+
function normalizeDocumentTableOfContentsOptions(source) {
|
|
2574
|
+
const minLevel = boundedLevel(source?.minLevel, 1);
|
|
2575
|
+
const maxLevel = Math.max(minLevel, boundedLevel(source?.maxLevel, Math.max(3, minLevel)));
|
|
2576
|
+
const showPageNumbers = source?.showPageNumbers !== false;
|
|
2577
|
+
return {
|
|
2578
|
+
minLevel,
|
|
2579
|
+
maxLevel,
|
|
2580
|
+
hyperlinks: source?.hyperlinks !== false,
|
|
2581
|
+
showPageNumbers,
|
|
2582
|
+
rightAlignPageNumbers: showPageNumbers && source?.rightAlignPageNumbers !== false,
|
|
2583
|
+
leader: tableOfContentsLeader(source?.leader) ?? 'dot'
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
function buildDocumentTableOfContentsEntries(document, options, buildOptions = {}) {
|
|
2587
|
+
const normalized = normalizeDocumentTableOfContentsOptions(options);
|
|
2588
|
+
const outline = collectWorkDocumentOutline(document).filter((item)=>item.text.length > 0 && item.level >= normalized.minLevel && item.level <= normalized.maxLevel);
|
|
2589
|
+
const entries = outline.slice(0, MAX_DOCUMENT_TABLE_OF_CONTENTS_ENTRIES).map((item)=>tableOfContentsEntryFromOutline(document, item, buildOptions.resolveContext));
|
|
2590
|
+
return {
|
|
2591
|
+
entries,
|
|
2592
|
+
truncated: outline.length > MAX_DOCUMENT_TABLE_OF_CONTENTS_ENTRIES
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function documentTableOfContentsHtml(source) {
|
|
2596
|
+
const value = normalizeDocumentTableOfContentsValue(source);
|
|
2597
|
+
const options = value.options;
|
|
2598
|
+
const rows = value.entries.length ? value.entries.map((entry)=>tableOfContentsEntryHtml(entry, options)) : [
|
|
2599
|
+
'<li class="work-document-table-of-contents-empty">没有符合级别范围的标题</li>'
|
|
2600
|
+
];
|
|
2601
|
+
const status = value.truncated ? `仅显示前 ${MAX_DOCUMENT_TABLE_OF_CONTENTS_ENTRIES} 项` : `${value.entries.length} 项`;
|
|
2602
|
+
return [
|
|
2603
|
+
`<div data-document-table-of-contents="true" data-toc-id="${work_document_table_of_contents_escapeHtmlAttribute(value.id)}" data-toc-min-level="${options.minLevel}" data-toc-max-level="${options.maxLevel}" data-toc-hyperlinks="${String(options.hyperlinks)}" data-toc-show-page-numbers="${String(options.showPageNumbers)}" data-toc-right-align-page-numbers="${String(options.rightAlignPageNumbers)}" data-toc-leader="${options.leader}" data-toc-entries="${work_document_table_of_contents_escapeHtmlAttribute(JSON.stringify(value.entries))}" data-toc-truncated="${String(value.truncated)}" class="work-document-table-of-contents" contenteditable="false" aria-label="目录">`,
|
|
2604
|
+
'<div class="work-document-table-of-contents-header"><strong>目录</strong>',
|
|
2605
|
+
`<span>${status}</span></div>`,
|
|
2606
|
+
'<ol class="work-document-table-of-contents-list">',
|
|
2607
|
+
...rows,
|
|
2608
|
+
'</ol></div>'
|
|
2609
|
+
].join('');
|
|
2610
|
+
}
|
|
2611
|
+
function normalizeDocumentTableOfContentsHtml(source) {
|
|
2612
|
+
const document = new DOMParser().parseFromString(source, 'text/html');
|
|
2613
|
+
const usedIds = new Set();
|
|
2614
|
+
for (const [index, element] of Array.from(document.body.querySelectorAll(TABLE_OF_CONTENTS_SELECTOR)).entries()){
|
|
2615
|
+
const value = documentTableOfContentsValueFromElement(element, index + 1);
|
|
2616
|
+
value.id = uniqueTableOfContentsId(value.id, index + 1, usedIds);
|
|
2617
|
+
const replacement = document.createRange().createContextualFragment(documentTableOfContentsHtml(value));
|
|
2618
|
+
element.replaceWith(replacement);
|
|
2619
|
+
}
|
|
2620
|
+
return document.body.innerHTML;
|
|
2621
|
+
}
|
|
2622
|
+
function documentTableOfContentsValueFromElement(element, index = 1) {
|
|
2623
|
+
const entries = parseTableOfContentsEntries(element.dataset.tocEntries);
|
|
2624
|
+
return normalizeDocumentTableOfContentsValue({
|
|
2625
|
+
id: validTableOfContentsId(element.dataset.tocId) ?? `toc-${index}`,
|
|
2626
|
+
options: {
|
|
2627
|
+
minLevel: Number(element.dataset.tocMinLevel),
|
|
2628
|
+
maxLevel: Number(element.dataset.tocMaxLevel),
|
|
2629
|
+
hyperlinks: 'false' !== element.dataset.tocHyperlinks,
|
|
2630
|
+
showPageNumbers: 'false' !== element.dataset.tocShowPageNumbers,
|
|
2631
|
+
rightAlignPageNumbers: 'false' !== element.dataset.tocRightAlignPageNumbers,
|
|
2632
|
+
leader: tableOfContentsLeader(element.dataset.tocLeader) ?? 'dot'
|
|
2633
|
+
},
|
|
2634
|
+
entries,
|
|
2635
|
+
truncated: 'true' === element.dataset.tocTruncated
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
function parseDocumentTableOfContentsInstruction(instruction) {
|
|
2639
|
+
const command = /^\s*TOC\b/i.exec(instruction);
|
|
2640
|
+
if (!command) return {
|
|
2641
|
+
supported: false,
|
|
2642
|
+
reason: 'not-table-of-contents'
|
|
2643
|
+
};
|
|
2644
|
+
const source = instruction.slice(command[0].length);
|
|
2645
|
+
const switches = new Map();
|
|
2646
|
+
const matcher = /\\([a-z])(?:\s+("([^"]*)"|([^\\\s]+)))?/gi;
|
|
2647
|
+
let cursor = 0;
|
|
2648
|
+
for(let match = matcher.exec(source); match; match = matcher.exec(source)){
|
|
2649
|
+
if (source.slice(cursor, match.index).trim()) return {
|
|
2650
|
+
supported: false,
|
|
2651
|
+
reason: 'invalid-instruction'
|
|
2652
|
+
};
|
|
2653
|
+
const name = match[1]?.toLowerCase() ?? '';
|
|
2654
|
+
if (!name || switches.has(name)) return {
|
|
2655
|
+
supported: false,
|
|
2656
|
+
reason: 'invalid-instruction'
|
|
2657
|
+
};
|
|
2658
|
+
switches.set(name, match[3] ?? match[4] ?? null);
|
|
2659
|
+
cursor = matcher.lastIndex;
|
|
2660
|
+
}
|
|
2661
|
+
if (source.slice(cursor).trim()) return {
|
|
2662
|
+
supported: false,
|
|
2663
|
+
reason: 'invalid-instruction'
|
|
2664
|
+
};
|
|
2665
|
+
for (const name of switches.keys())if (![
|
|
2666
|
+
'o',
|
|
2667
|
+
'h',
|
|
2668
|
+
'z',
|
|
2669
|
+
'u',
|
|
2670
|
+
'n',
|
|
2671
|
+
'p'
|
|
2672
|
+
].includes(name)) return {
|
|
2673
|
+
supported: false,
|
|
2674
|
+
reason: 'unsupported-switch'
|
|
2675
|
+
};
|
|
2676
|
+
const range = switches.has('o') ? parseTableOfContentsRange(switches.get('o')) : {
|
|
2677
|
+
minLevel: 1,
|
|
2678
|
+
maxLevel: 3
|
|
2679
|
+
};
|
|
2680
|
+
if (!range) return {
|
|
2681
|
+
supported: false,
|
|
2682
|
+
reason: 'invalid-level-range'
|
|
2683
|
+
};
|
|
2684
|
+
const pageRange = switches.has('n') ? parseOptionalTableOfContentsRange(switches.get('n')) : null;
|
|
2685
|
+
if (switches.has('n') && void 0 === pageRange) return {
|
|
2686
|
+
supported: false,
|
|
2687
|
+
reason: 'invalid-level-range'
|
|
2688
|
+
};
|
|
2689
|
+
if (pageRange && (pageRange.minLevel !== range.minLevel || pageRange.maxLevel !== range.maxLevel)) return {
|
|
2690
|
+
supported: false,
|
|
2691
|
+
reason: 'unsupported-page-range'
|
|
2692
|
+
};
|
|
2693
|
+
const separator = switches.get('p');
|
|
2694
|
+
if (switches.has('p') && (null == separator || separator.trim())) return {
|
|
2695
|
+
supported: false,
|
|
2696
|
+
reason: 'unsupported-separator'
|
|
2697
|
+
};
|
|
2698
|
+
const showPageNumbers = !switches.has('n');
|
|
2699
|
+
const rightAlignPageNumbers = showPageNumbers && !switches.has('p');
|
|
2700
|
+
return {
|
|
2701
|
+
supported: true,
|
|
2702
|
+
options: {
|
|
2703
|
+
...range,
|
|
2704
|
+
hyperlinks: switches.has('h'),
|
|
2705
|
+
showPageNumbers,
|
|
2706
|
+
rightAlignPageNumbers,
|
|
2707
|
+
leader: rightAlignPageNumbers ? 'dot' : 'none'
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function tableOfContentsLeader(value) {
|
|
2712
|
+
return 'dot' === value || 'dash' === value || 'underline' === value || 'none' === value ? value : null;
|
|
2713
|
+
}
|
|
2714
|
+
function normalizeDocumentTableOfContentsValue(source) {
|
|
2715
|
+
return {
|
|
2716
|
+
id: validTableOfContentsId(source.id) ?? 'document-table-of-contents',
|
|
2717
|
+
options: normalizeDocumentTableOfContentsOptions(source.options),
|
|
2718
|
+
entries: normalizeTableOfContentsEntries(source.entries),
|
|
2719
|
+
truncated: Boolean(source.truncated)
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
function tableOfContentsEntryFromOutline(document, item, resolveContext) {
|
|
2723
|
+
return {
|
|
2724
|
+
targetId: item.id,
|
|
2725
|
+
title: item.text.slice(0, MAX_TABLE_OF_CONTENTS_TITLE_LENGTH),
|
|
2726
|
+
level: item.level,
|
|
2727
|
+
pageNumber: work_document_table_of_contents_positiveInteger(resolveContext?.(item.from)?.pageNumber) ?? work_document_table_of_contents_fallbackDocumentPageNumber(document, item.from)
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
function work_document_table_of_contents_fallbackDocumentPageNumber(document, position) {
|
|
2731
|
+
let pageNumber = 1;
|
|
2732
|
+
document.descendants((node, offset)=>{
|
|
2733
|
+
if (offset >= position) return false;
|
|
2734
|
+
if ('pageBreak' === node.type.name) pageNumber += 1;
|
|
2735
|
+
return true;
|
|
2736
|
+
});
|
|
2737
|
+
return pageNumber;
|
|
2738
|
+
}
|
|
2739
|
+
function tableOfContentsEntryHtml(entry, options) {
|
|
2740
|
+
const title = work_document_table_of_contents_escapeHtml(entry.title);
|
|
2741
|
+
const targetId = work_document_table_of_contents_escapeHtmlAttribute(entry.targetId);
|
|
2742
|
+
const label = options.hyperlinks ? `<a href="#${targetId}" data-toc-target="${targetId}" tabindex="-1">${title}</a>` : `<span>${title}</span>`;
|
|
2743
|
+
const page = options.showPageNumbers ? `<span class="work-document-table-of-contents-page">${entry.pageNumber}</span>` : '';
|
|
2744
|
+
return `<li data-toc-target="${targetId}" data-toc-level="${entry.level}" style="--work-toc-level:${entry.level}">${label}<i aria-hidden="true"></i>${page}</li>`;
|
|
2745
|
+
}
|
|
2746
|
+
function parseTableOfContentsEntries(source) {
|
|
2747
|
+
if (!source) return [];
|
|
2748
|
+
try {
|
|
2749
|
+
const parsed = JSON.parse(source);
|
|
2750
|
+
return Array.isArray(parsed) ? normalizeTableOfContentsEntries(parsed) : [];
|
|
2751
|
+
} catch {
|
|
2752
|
+
return [];
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function normalizeTableOfContentsEntries(source) {
|
|
2756
|
+
const entries = [];
|
|
2757
|
+
for (const value of source.slice(0, MAX_DOCUMENT_TABLE_OF_CONTENTS_ENTRIES)){
|
|
2758
|
+
if (!value || 'object' != typeof value) continue;
|
|
2759
|
+
const candidate = value;
|
|
2760
|
+
const targetId = 'string' == typeof candidate.targetId ? candidate.targetId.trim() : '';
|
|
2761
|
+
const title = 'string' == typeof candidate.title ? candidate.title.replace(/\s+/g, ' ').trim() : '';
|
|
2762
|
+
const level = boundedLevel(candidate.level, 0);
|
|
2763
|
+
const pageNumber = work_document_table_of_contents_positiveInteger(candidate.pageNumber);
|
|
2764
|
+
if (TABLE_OF_CONTENTS_TARGET_PATTERN.test(targetId) && title && level && pageNumber) entries.push({
|
|
2765
|
+
targetId,
|
|
2766
|
+
title: title.slice(0, MAX_TABLE_OF_CONTENTS_TITLE_LENGTH),
|
|
2767
|
+
level,
|
|
2768
|
+
pageNumber
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
return entries;
|
|
2772
|
+
}
|
|
2773
|
+
function parseTableOfContentsRange(value) {
|
|
2774
|
+
if (null == value) return null;
|
|
2775
|
+
const match = /^([1-9])-([1-9])$/.exec(value.trim());
|
|
2776
|
+
if (!match) return null;
|
|
2777
|
+
const minLevel = Number(match[1]);
|
|
2778
|
+
const maxLevel = Number(match[2]);
|
|
2779
|
+
return minLevel <= maxLevel ? {
|
|
2780
|
+
minLevel,
|
|
2781
|
+
maxLevel
|
|
2782
|
+
} : null;
|
|
2783
|
+
}
|
|
2784
|
+
function parseOptionalTableOfContentsRange(value) {
|
|
2785
|
+
if (null === value) return null;
|
|
2786
|
+
return parseTableOfContentsRange(value) ?? void 0;
|
|
2787
|
+
}
|
|
2788
|
+
function boundedLevel(value, fallback) {
|
|
2789
|
+
const number = Number(value);
|
|
2790
|
+
return Number.isInteger(number) && number >= 1 && number <= 9 ? number : fallback;
|
|
2791
|
+
}
|
|
2792
|
+
function work_document_table_of_contents_positiveInteger(value) {
|
|
2793
|
+
const number = Number(value);
|
|
2794
|
+
return Number.isSafeInteger(number) && number > 0 ? Math.min(999999, number) : null;
|
|
2795
|
+
}
|
|
2796
|
+
function validTableOfContentsId(value) {
|
|
2797
|
+
return 'string' == typeof value && TABLE_OF_CONTENTS_ID_PATTERN.test(value) ? value : null;
|
|
2798
|
+
}
|
|
2799
|
+
function uniqueTableOfContentsId(source, index, usedIds) {
|
|
2800
|
+
if (!usedIds.has(source)) {
|
|
2801
|
+
usedIds.add(source);
|
|
2802
|
+
return source;
|
|
2803
|
+
}
|
|
2804
|
+
let suffix = index;
|
|
2805
|
+
while(usedIds.has(`document-table-of-contents-${suffix}`))suffix += 1;
|
|
2806
|
+
const id = `document-table-of-contents-${suffix}`;
|
|
2807
|
+
usedIds.add(id);
|
|
2808
|
+
return id;
|
|
2809
|
+
}
|
|
2810
|
+
function work_document_table_of_contents_escapeHtml(value) {
|
|
2811
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
2812
|
+
}
|
|
2813
|
+
function work_document_table_of_contents_escapeHtmlAttribute(value) {
|
|
2814
|
+
return work_document_table_of_contents_escapeHtml(value).replaceAll('"', '"').replaceAll("'", ''');
|
|
2815
|
+
}
|
|
974
2816
|
const WORK_TEMPLATES = [
|
|
975
2817
|
{
|
|
976
2818
|
id: 'blank-document',
|
|
@@ -986,6 +2828,20 @@ const WORK_TEMPLATES = [
|
|
|
986
2828
|
description: '目标、范围、里程碑与风险',
|
|
987
2829
|
accent: '#536de2'
|
|
988
2830
|
},
|
|
2831
|
+
{
|
|
2832
|
+
id: 'table-of-contents',
|
|
2833
|
+
kind: 'document',
|
|
2834
|
+
name: '可更新目录',
|
|
2835
|
+
description: '标题级别、超链接、页码、前导符与原生 DOCX 往返',
|
|
2836
|
+
accent: '#315f9f'
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
id: 'document-index',
|
|
2840
|
+
kind: 'document',
|
|
2841
|
+
name: '原生索引',
|
|
2842
|
+
description: '主次索引项、交叉引用、页码样式与原生 DOCX 往返',
|
|
2843
|
+
accent: '#4d6398'
|
|
2844
|
+
},
|
|
989
2845
|
{
|
|
990
2846
|
id: 'text-effects',
|
|
991
2847
|
kind: 'document',
|
|
@@ -1000,6 +2856,20 @@ const WORK_TEMPLATES = [
|
|
|
1000
2856
|
description: '原生线型、颜色、宽度、间距与阴影',
|
|
1001
2857
|
accent: '#4472c4'
|
|
1002
2858
|
},
|
|
2859
|
+
{
|
|
2860
|
+
id: 'run-shading',
|
|
2861
|
+
kind: 'document',
|
|
2862
|
+
name: '字符底纹',
|
|
2863
|
+
description: '原生图案、前景色、背景色与显式重置',
|
|
2864
|
+
accent: '#70ad47'
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
id: 'proofing-languages',
|
|
2868
|
+
kind: 'document',
|
|
2869
|
+
name: '校对语言',
|
|
2870
|
+
description: '拉丁、东亚、双向文字与校对排除',
|
|
2871
|
+
accent: '#2f6fed'
|
|
2872
|
+
},
|
|
1003
2873
|
{
|
|
1004
2874
|
id: 'blank-markdown',
|
|
1005
2875
|
kind: 'markdown',
|
|
@@ -1021,6 +2891,13 @@ const WORK_TEMPLATES = [
|
|
|
1021
2891
|
description: '目标进度与预算跟踪',
|
|
1022
2892
|
accent: '#168f72'
|
|
1023
2893
|
},
|
|
2894
|
+
{
|
|
2895
|
+
id: 'data-validation',
|
|
2896
|
+
kind: 'spreadsheet',
|
|
2897
|
+
name: '数据验证',
|
|
2898
|
+
description: '下拉列表、输入提示与错误警告',
|
|
2899
|
+
accent: '#13795b'
|
|
2900
|
+
},
|
|
1024
2901
|
{
|
|
1025
2902
|
id: 'blank-presentation',
|
|
1026
2903
|
kind: 'presentation',
|
|
@@ -1058,9 +2935,14 @@ function createWorkId(prefix) {
|
|
|
1058
2935
|
function initialTitle(templateId, kind) {
|
|
1059
2936
|
const titles = {
|
|
1060
2937
|
'project-brief': '新项目方案',
|
|
2938
|
+
'table-of-contents': '可更新目录示例',
|
|
2939
|
+
'document-index': '原生索引示例',
|
|
1061
2940
|
'text-effects': '文字效果示例',
|
|
1062
2941
|
'run-borders': '字符边框示例',
|
|
2942
|
+
'run-shading': '字符底纹示例',
|
|
2943
|
+
'proofing-languages': '校对语言示例',
|
|
1063
2944
|
'quarterly-plan': '季度执行计划',
|
|
2945
|
+
'data-validation': '数据验证示例',
|
|
1064
2946
|
'strategy-deck': '业务策略汇报'
|
|
1065
2947
|
};
|
|
1066
2948
|
if (titles[templateId]) return titles[templateId];
|
|
@@ -1075,6 +2957,153 @@ function contentForTemplate(templateId) {
|
|
|
1075
2957
|
pageSize: 'a4',
|
|
1076
2958
|
html: "<h1>新项目方案</h1><p><strong>负责人:</strong>项目团队 <strong>更新日期:</strong>今天</p><blockquote><p>用一句话说明这项工作的目标,以及完成后会带来什么变化。</p></blockquote><h2>背景与目标</h2><p>描述当前情况、核心问题和可衡量的成功标准。</p><h2>工作范围</h2><ul><li><p>需要完成的关键交付物</p></li><li><p>明确不在本期范围内的事项</p></li></ul><h2>里程碑</h2><ol><li><p>方案确认</p></li><li><p>执行与评审</p></li><li><p>交付与复盘</p></li></ol><h2>风险与决策</h2><p>记录尚未解决的问题、依赖和决策负责人。</p>"
|
|
1077
2959
|
};
|
|
2960
|
+
if ('table-of-contents' === templateId) return {
|
|
2961
|
+
type: 'document',
|
|
2962
|
+
pageSize: 'a4',
|
|
2963
|
+
html: [
|
|
2964
|
+
documentTableOfContentsHtml({
|
|
2965
|
+
id: 'playground-table-of-contents',
|
|
2966
|
+
options: {
|
|
2967
|
+
minLevel: 1,
|
|
2968
|
+
maxLevel: 3,
|
|
2969
|
+
hyperlinks: true,
|
|
2970
|
+
showPageNumbers: true,
|
|
2971
|
+
rightAlignPageNumbers: true,
|
|
2972
|
+
leader: 'dot'
|
|
2973
|
+
},
|
|
2974
|
+
entries: [
|
|
2975
|
+
{
|
|
2976
|
+
targetId: 'heading-00001001',
|
|
2977
|
+
title: '项目概述',
|
|
2978
|
+
level: 1,
|
|
2979
|
+
pageNumber: 1
|
|
2980
|
+
},
|
|
2981
|
+
{
|
|
2982
|
+
targetId: 'heading-00001002',
|
|
2983
|
+
title: '目标与范围',
|
|
2984
|
+
level: 2,
|
|
2985
|
+
pageNumber: 1
|
|
2986
|
+
},
|
|
2987
|
+
{
|
|
2988
|
+
targetId: 'heading-00001003',
|
|
2989
|
+
title: '实施计划',
|
|
2990
|
+
level: 1,
|
|
2991
|
+
pageNumber: 2
|
|
2992
|
+
}
|
|
2993
|
+
]
|
|
2994
|
+
}),
|
|
2995
|
+
'<h1 data-office-paragraph-id="00001001" data-office-paragraph-text-id="00002001">项目概述</h1>',
|
|
2996
|
+
'<p>目录是一个可选择、不可直接改写的结构化块。请从“引用”选项卡自定义或更新它。</p>',
|
|
2997
|
+
'<h2 data-office-paragraph-id="00001002" data-office-paragraph-text-id="00002002">目标与范围</h2>',
|
|
2998
|
+
'<p>修改标题文字后,使用“更新目录”同步标题与当前分页页码。</p>',
|
|
2999
|
+
'<hr class="work-page-break" data-page-break="true">',
|
|
3000
|
+
'<h1 data-office-paragraph-id="00001003" data-office-paragraph-text-id="00002003">实施计划</h1>',
|
|
3001
|
+
'<p>导出 DOCX 后仍保留原生 TOC 域、缓存目录项、超链接和前导符。</p>'
|
|
3002
|
+
].join('')
|
|
3003
|
+
};
|
|
3004
|
+
if ('document-index' === templateId) return {
|
|
3005
|
+
type: 'document',
|
|
3006
|
+
pageSize: 'a4',
|
|
3007
|
+
html: [
|
|
3008
|
+
documentIndexHtml({
|
|
3009
|
+
id: 'playground-document-index',
|
|
3010
|
+
options: {
|
|
3011
|
+
columns: 2,
|
|
3012
|
+
format: 'indented',
|
|
3013
|
+
rightAlignPageNumbers: true,
|
|
3014
|
+
leader: 'dot'
|
|
3015
|
+
},
|
|
3016
|
+
entries: [
|
|
3017
|
+
{
|
|
3018
|
+
mainEntry: 'Architecture',
|
|
3019
|
+
subEntry: '',
|
|
3020
|
+
crossReference: '',
|
|
3021
|
+
pages: [
|
|
3022
|
+
{
|
|
3023
|
+
pageNumber: 1,
|
|
3024
|
+
pageBold: false,
|
|
3025
|
+
pageItalic: false,
|
|
3026
|
+
targetIds: [
|
|
3027
|
+
'index-entry-architecture'
|
|
3028
|
+
]
|
|
3029
|
+
}
|
|
3030
|
+
]
|
|
3031
|
+
},
|
|
3032
|
+
{
|
|
3033
|
+
mainEntry: 'Architecture',
|
|
3034
|
+
subEntry: 'Runtime',
|
|
3035
|
+
crossReference: '',
|
|
3036
|
+
pages: [
|
|
3037
|
+
{
|
|
3038
|
+
pageNumber: 1,
|
|
3039
|
+
pageBold: true,
|
|
3040
|
+
pageItalic: false,
|
|
3041
|
+
targetIds: [
|
|
3042
|
+
'index-entry-runtime'
|
|
3043
|
+
]
|
|
3044
|
+
}
|
|
3045
|
+
]
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
mainEntry: 'Collaboration',
|
|
3049
|
+
subEntry: '',
|
|
3050
|
+
crossReference: 'Architecture',
|
|
3051
|
+
pages: []
|
|
3052
|
+
},
|
|
3053
|
+
{
|
|
3054
|
+
mainEntry: 'Performance',
|
|
3055
|
+
subEntry: '',
|
|
3056
|
+
crossReference: '',
|
|
3057
|
+
pages: [
|
|
3058
|
+
{
|
|
3059
|
+
pageNumber: 2,
|
|
3060
|
+
pageBold: false,
|
|
3061
|
+
pageItalic: true,
|
|
3062
|
+
targetIds: [
|
|
3063
|
+
'index-entry-performance'
|
|
3064
|
+
]
|
|
3065
|
+
}
|
|
3066
|
+
]
|
|
3067
|
+
}
|
|
3068
|
+
]
|
|
3069
|
+
}),
|
|
3070
|
+
'<h1>原生索引</h1>',
|
|
3071
|
+
`<p>选择正文后,从“引用 → 标记索引项”创建主索引项、次索引项或交叉引用。${documentIndexEntryHtml({
|
|
3072
|
+
id: 'index-entry-architecture',
|
|
3073
|
+
mainEntry: 'Architecture',
|
|
3074
|
+
subEntry: '',
|
|
3075
|
+
crossReference: '',
|
|
3076
|
+
pageBold: false,
|
|
3077
|
+
pageItalic: false
|
|
3078
|
+
})}</p>`,
|
|
3079
|
+
`<p>Runtime 项的页码使用粗体,并作为 Architecture 的次索引项。${documentIndexEntryHtml({
|
|
3080
|
+
id: 'index-entry-runtime',
|
|
3081
|
+
mainEntry: 'Architecture',
|
|
3082
|
+
subEntry: 'Runtime',
|
|
3083
|
+
crossReference: '',
|
|
3084
|
+
pageBold: true,
|
|
3085
|
+
pageItalic: false
|
|
3086
|
+
})}</p>`,
|
|
3087
|
+
`<p>Collaboration 使用“参见 Architecture”交叉引用,不生成当前页码。${documentIndexEntryHtml({
|
|
3088
|
+
id: 'index-entry-collaboration',
|
|
3089
|
+
mainEntry: 'Collaboration',
|
|
3090
|
+
subEntry: '',
|
|
3091
|
+
crossReference: 'Architecture',
|
|
3092
|
+
pageBold: false,
|
|
3093
|
+
pageItalic: false
|
|
3094
|
+
})}</p>`,
|
|
3095
|
+
'<hr class="work-page-break" data-page-break="true">',
|
|
3096
|
+
`<p>Performance 位于第二页,页码使用斜体。${documentIndexEntryHtml({
|
|
3097
|
+
id: 'index-entry-performance',
|
|
3098
|
+
mainEntry: 'Performance',
|
|
3099
|
+
subEntry: '',
|
|
3100
|
+
crossReference: '',
|
|
3101
|
+
pageBold: false,
|
|
3102
|
+
pageItalic: true
|
|
3103
|
+
})}</p>`,
|
|
3104
|
+
'<p>修改索引项后选择“更新索引”,再导出 DOCX 验证原生 XE 与 INDEX 域。</p>'
|
|
3105
|
+
].join('')
|
|
3106
|
+
};
|
|
1078
3107
|
if ('text-effects' === templateId) return {
|
|
1079
3108
|
type: 'document',
|
|
1080
3109
|
pageSize: 'a4',
|
|
@@ -1124,10 +3153,89 @@ function contentForTemplate(templateId) {
|
|
|
1124
3153
|
}, '阴影与框架字符边框')}</p>`
|
|
1125
3154
|
].join('')
|
|
1126
3155
|
};
|
|
3156
|
+
if ('run-shading' === templateId) return {
|
|
3157
|
+
type: 'document',
|
|
3158
|
+
pageSize: 'a4',
|
|
3159
|
+
html: [
|
|
3160
|
+
'<h1>原生字符底纹</h1>',
|
|
3161
|
+
'<p>选择任一示例并打开字体高级设置,可以编辑完整的原生图案、前景色、背景色,或写入显式无底纹重置。</p>',
|
|
3162
|
+
'<h2>基本填充</h2>',
|
|
3163
|
+
`<p>${runShadingTemplateSpan({
|
|
3164
|
+
pattern: 'clear',
|
|
3165
|
+
fill: {
|
|
3166
|
+
value: '#fff2cc'
|
|
3167
|
+
}
|
|
3168
|
+
}, '清除图案使用背景色')}</p>`,
|
|
3169
|
+
`<p>${runShadingTemplateSpan({
|
|
3170
|
+
pattern: 'solid',
|
|
3171
|
+
color: {
|
|
3172
|
+
value: '#4472c4'
|
|
3173
|
+
}
|
|
3174
|
+
}, '实心图案使用前景色')}</p>`,
|
|
3175
|
+
'<h2>图案与密度</h2>',
|
|
3176
|
+
`<p>${runShadingTemplateSpan({
|
|
3177
|
+
pattern: 'diagCross',
|
|
3178
|
+
color: {
|
|
3179
|
+
value: '#c00000'
|
|
3180
|
+
},
|
|
3181
|
+
fill: {
|
|
3182
|
+
value: '#fce4d6'
|
|
3183
|
+
}
|
|
3184
|
+
}, '对角交叉字符底纹')}</p>`,
|
|
3185
|
+
`<p>${runShadingTemplateSpan({
|
|
3186
|
+
pattern: 'pct25',
|
|
3187
|
+
color: {
|
|
3188
|
+
value: '#4472c4'
|
|
3189
|
+
},
|
|
3190
|
+
fill: {
|
|
3191
|
+
value: '#ddebf7'
|
|
3192
|
+
}
|
|
3193
|
+
}, '25% 字符底纹')}</p>`,
|
|
3194
|
+
`<p>${runShadingTemplateSpan({
|
|
3195
|
+
pattern: 'thinHorzStripe',
|
|
3196
|
+
color: {
|
|
3197
|
+
value: '#70ad47'
|
|
3198
|
+
},
|
|
3199
|
+
fill: {
|
|
3200
|
+
value: '#e2f0d9'
|
|
3201
|
+
}
|
|
3202
|
+
}, '细水平条纹字符底纹')}</p>`
|
|
3203
|
+
].join('')
|
|
3204
|
+
};
|
|
3205
|
+
if ('proofing-languages' === templateId) {
|
|
3206
|
+
const languages = {
|
|
3207
|
+
latin: 'en-US',
|
|
3208
|
+
eastAsia: 'zh-CN',
|
|
3209
|
+
bidi: 'ar-SA'
|
|
3210
|
+
};
|
|
3211
|
+
return {
|
|
3212
|
+
type: 'document',
|
|
3213
|
+
pageSize: 'a4',
|
|
3214
|
+
html: [
|
|
3215
|
+
'<h1>原生校对语言</h1>',
|
|
3216
|
+
'<p>在“审阅”选项卡中打开“设置校对语言”,可以独立编辑拉丁、东亚和双向文字语言,并决定文字是否参与拼写与语法检查。</p>',
|
|
3217
|
+
'<h2>按文字系统设置</h2>',
|
|
3218
|
+
`<p>${proofingLanguageTemplateSpan(languages, false, 'ascii', 'English proofing language')}</p>`,
|
|
3219
|
+
`<p>${proofingLanguageTemplateSpan(languages, false, 'eastAsia', '简体中文校对语言')}</p>`,
|
|
3220
|
+
`<p dir="rtl">${proofingLanguageTemplateSpan(languages, false, 'complexScript', 'لغة التدقيق العربية')}</p>`,
|
|
3221
|
+
'<h2>校对行为</h2>',
|
|
3222
|
+
`<p>${proofingLanguageTemplateSpan({
|
|
3223
|
+
latin: 'en-GB'
|
|
3224
|
+
}, false, 'ascii', 'This text participates in proofing.')}</p>`,
|
|
3225
|
+
`<p>${proofingLanguageTemplateSpan({
|
|
3226
|
+
latin: 'x-none'
|
|
3227
|
+
}, true, 'ascii', 'A3S-API-v2: this product identifier is excluded from proofing.')}</p>`
|
|
3228
|
+
].join('')
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
1127
3231
|
if ('quarterly-plan' === templateId) return {
|
|
1128
3232
|
type: 'spreadsheet',
|
|
1129
3233
|
sheets: quarterlyPlanSheets()
|
|
1130
3234
|
};
|
|
3235
|
+
if ('data-validation' === templateId) return {
|
|
3236
|
+
type: 'spreadsheet',
|
|
3237
|
+
sheets: dataValidationTemplateSheets()
|
|
3238
|
+
};
|
|
1131
3239
|
if ('strategy-deck' === templateId) return strategyPresentation();
|
|
1132
3240
|
if ('blank-spreadsheet' === templateId) return {
|
|
1133
3241
|
type: 'spreadsheet',
|
|
@@ -1155,6 +3263,14 @@ function runBorderTemplateSpan(border, text) {
|
|
|
1155
3263
|
const attributes = documentRunBorderDomAttributes(border);
|
|
1156
3264
|
return `<span ${DOCUMENT_RUN_BORDER_ATTRIBUTE}='${attributes[DOCUMENT_RUN_BORDER_ATTRIBUTE]}' style="${attributes.style}">${text}</span>`;
|
|
1157
3265
|
}
|
|
3266
|
+
function runShadingTemplateSpan(shading, text) {
|
|
3267
|
+
const attributes = documentRunShadingDomAttributes(shading);
|
|
3268
|
+
return `<span ${DOCUMENT_RUN_SHADING_ATTRIBUTE}='${attributes[DOCUMENT_RUN_SHADING_ATTRIBUTE]}' style="${attributes.style}">${text}</span>`;
|
|
3269
|
+
}
|
|
3270
|
+
function proofingLanguageTemplateSpan(languages, noProof, slot, text) {
|
|
3271
|
+
const attributes = documentProofingDomAttributes(languages, noProof, slot);
|
|
3272
|
+
return `<span ${Object.entries(attributes).map(([name, value])=>`${name}='${value}'`).join(' ')}>${text}</span>`;
|
|
3273
|
+
}
|
|
1158
3274
|
function blankSheet() {
|
|
1159
3275
|
return {
|
|
1160
3276
|
id: createWorkId('sheet'),
|
|
@@ -1270,6 +3386,229 @@ function quarterlyPlanSheets() {
|
|
|
1270
3386
|
}
|
|
1271
3387
|
];
|
|
1272
3388
|
}
|
|
3389
|
+
function dataValidationTemplateSheets() {
|
|
3390
|
+
const inputs = emptyMatrix(24, 8);
|
|
3391
|
+
[
|
|
3392
|
+
'Task',
|
|
3393
|
+
'State',
|
|
3394
|
+
'Due date',
|
|
3395
|
+
'Priority',
|
|
3396
|
+
'Owner'
|
|
3397
|
+
].forEach((value, column)=>{
|
|
3398
|
+
inputs[0][column] = headerCell(value);
|
|
3399
|
+
});
|
|
3400
|
+
const rows = [
|
|
3401
|
+
[
|
|
3402
|
+
'Confirm requirements',
|
|
3403
|
+
'Ready',
|
|
3404
|
+
'2026-09-05',
|
|
3405
|
+
2,
|
|
3406
|
+
'Avery'
|
|
3407
|
+
],
|
|
3408
|
+
[
|
|
3409
|
+
'Review integration',
|
|
3410
|
+
'In review',
|
|
3411
|
+
'2026-09-12',
|
|
3412
|
+
3,
|
|
3413
|
+
'Morgan'
|
|
3414
|
+
],
|
|
3415
|
+
[
|
|
3416
|
+
'Resolve blockers',
|
|
3417
|
+
'Blocked',
|
|
3418
|
+
'2026-09-18',
|
|
3419
|
+
5,
|
|
3420
|
+
'Riley'
|
|
3421
|
+
],
|
|
3422
|
+
[
|
|
3423
|
+
'Publish preview',
|
|
3424
|
+
'Ready',
|
|
3425
|
+
'2026-09-24',
|
|
3426
|
+
1,
|
|
3427
|
+
'Jordan'
|
|
3428
|
+
],
|
|
3429
|
+
[
|
|
3430
|
+
'Ship release',
|
|
3431
|
+
'In review',
|
|
3432
|
+
'2026-09-30',
|
|
3433
|
+
4,
|
|
3434
|
+
'Taylor'
|
|
3435
|
+
]
|
|
3436
|
+
];
|
|
3437
|
+
rows.forEach((row, rowIndex)=>{
|
|
3438
|
+
row.forEach((value, columnIndex)=>{
|
|
3439
|
+
inputs[rowIndex + 1][columnIndex] = styledCell(value, {
|
|
3440
|
+
bg: rowIndex % 2 ? '#f4faf7' : '#ffffff'
|
|
3441
|
+
});
|
|
3442
|
+
});
|
|
3443
|
+
});
|
|
3444
|
+
return [
|
|
3445
|
+
{
|
|
3446
|
+
id: createWorkId('sheet'),
|
|
3447
|
+
name: 'Inputs',
|
|
3448
|
+
status: 1,
|
|
3449
|
+
order: 0,
|
|
3450
|
+
row: 24,
|
|
3451
|
+
column: 8,
|
|
3452
|
+
data: inputs,
|
|
3453
|
+
dataValidationRanges: [
|
|
3454
|
+
{
|
|
3455
|
+
ranges: [
|
|
3456
|
+
{
|
|
3457
|
+
row: [
|
|
3458
|
+
1,
|
|
3459
|
+
5
|
|
3460
|
+
],
|
|
3461
|
+
column: [
|
|
3462
|
+
1,
|
|
3463
|
+
1
|
|
3464
|
+
]
|
|
3465
|
+
}
|
|
3466
|
+
],
|
|
3467
|
+
item: dataValidationTemplateItem({
|
|
3468
|
+
type: 'dropdown',
|
|
3469
|
+
rangeTxt: 'B2:B6',
|
|
3470
|
+
value1: "'Lists'!A1:A3",
|
|
3471
|
+
allowBlank: false,
|
|
3472
|
+
showDropdownArrow: true,
|
|
3473
|
+
errorStyle: 'stop',
|
|
3474
|
+
errorTitle: 'Invalid state',
|
|
3475
|
+
errorMessage: 'Choose a state from the list.',
|
|
3476
|
+
hintShow: true,
|
|
3477
|
+
hintTitle: 'Workflow state',
|
|
3478
|
+
hintValue: 'Choose Ready, Blocked, or In review.'
|
|
3479
|
+
})
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
ranges: [
|
|
3483
|
+
{
|
|
3484
|
+
row: [
|
|
3485
|
+
1,
|
|
3486
|
+
5
|
|
3487
|
+
],
|
|
3488
|
+
column: [
|
|
3489
|
+
2,
|
|
3490
|
+
2
|
|
3491
|
+
]
|
|
3492
|
+
}
|
|
3493
|
+
],
|
|
3494
|
+
item: dataValidationTemplateItem({
|
|
3495
|
+
type: 'date',
|
|
3496
|
+
type2: 'between',
|
|
3497
|
+
rangeTxt: 'C2:C6',
|
|
3498
|
+
value1: '2026-01-01',
|
|
3499
|
+
value2: '2026-12-31',
|
|
3500
|
+
errorStyle: 'information',
|
|
3501
|
+
errorTitle: 'Date outside 2026',
|
|
3502
|
+
errorMessage: 'Enter a date in calendar year 2026.',
|
|
3503
|
+
hintShow: true,
|
|
3504
|
+
hintTitle: 'Due date',
|
|
3505
|
+
hintValue: 'Use a date between 2026-01-01 and 2026-12-31.'
|
|
3506
|
+
})
|
|
3507
|
+
},
|
|
3508
|
+
{
|
|
3509
|
+
ranges: [
|
|
3510
|
+
{
|
|
3511
|
+
row: [
|
|
3512
|
+
1,
|
|
3513
|
+
5
|
|
3514
|
+
],
|
|
3515
|
+
column: [
|
|
3516
|
+
3,
|
|
3517
|
+
3
|
|
3518
|
+
]
|
|
3519
|
+
}
|
|
3520
|
+
],
|
|
3521
|
+
item: dataValidationTemplateItem({
|
|
3522
|
+
type: 'number_integer',
|
|
3523
|
+
type2: 'between',
|
|
3524
|
+
rangeTxt: 'D2:D6',
|
|
3525
|
+
value1: '1',
|
|
3526
|
+
value2: '5',
|
|
3527
|
+
allowBlank: false,
|
|
3528
|
+
errorStyle: 'warning',
|
|
3529
|
+
errorTitle: 'Priority outside range',
|
|
3530
|
+
errorMessage: 'Enter a whole number from 1 through 5.',
|
|
3531
|
+
hintShow: true,
|
|
3532
|
+
hintTitle: 'Priority',
|
|
3533
|
+
hintValue: '1 is highest priority; 5 is lowest.'
|
|
3534
|
+
})
|
|
3535
|
+
}
|
|
3536
|
+
],
|
|
3537
|
+
luckysheet_select_save: [
|
|
3538
|
+
{
|
|
3539
|
+
row: [
|
|
3540
|
+
1,
|
|
3541
|
+
5
|
|
3542
|
+
],
|
|
3543
|
+
column: [
|
|
3544
|
+
1,
|
|
3545
|
+
1
|
|
3546
|
+
],
|
|
3547
|
+
row_focus: 1,
|
|
3548
|
+
column_focus: 1
|
|
3549
|
+
}
|
|
3550
|
+
],
|
|
3551
|
+
config: {
|
|
3552
|
+
columnlen: {
|
|
3553
|
+
0: 190,
|
|
3554
|
+
1: 110,
|
|
3555
|
+
2: 118,
|
|
3556
|
+
3: 84,
|
|
3557
|
+
4: 104
|
|
3558
|
+
},
|
|
3559
|
+
rowlen: {
|
|
3560
|
+
0: 30
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
},
|
|
3564
|
+
{
|
|
3565
|
+
id: createWorkId('sheet'),
|
|
3566
|
+
name: 'Lists',
|
|
3567
|
+
status: 0,
|
|
3568
|
+
order: 1,
|
|
3569
|
+
row: 12,
|
|
3570
|
+
column: 3,
|
|
3571
|
+
data: [
|
|
3572
|
+
[
|
|
3573
|
+
styledCell('Ready')
|
|
3574
|
+
],
|
|
3575
|
+
[
|
|
3576
|
+
styledCell('Blocked')
|
|
3577
|
+
],
|
|
3578
|
+
[
|
|
3579
|
+
styledCell('In review')
|
|
3580
|
+
]
|
|
3581
|
+
],
|
|
3582
|
+
config: {
|
|
3583
|
+
columnlen: {
|
|
3584
|
+
0: 120
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
];
|
|
3589
|
+
}
|
|
3590
|
+
function dataValidationTemplateItem(overrides) {
|
|
3591
|
+
return {
|
|
3592
|
+
type: 'dropdown',
|
|
3593
|
+
type2: '',
|
|
3594
|
+
rangeTxt: '',
|
|
3595
|
+
value1: '',
|
|
3596
|
+
value2: '',
|
|
3597
|
+
validity: '',
|
|
3598
|
+
remote: false,
|
|
3599
|
+
allowBlank: true,
|
|
3600
|
+
showDropdownArrow: true,
|
|
3601
|
+
prohibitInput: true,
|
|
3602
|
+
errorStyle: 'stop',
|
|
3603
|
+
errorTitle: '',
|
|
3604
|
+
errorMessage: '',
|
|
3605
|
+
hintShow: false,
|
|
3606
|
+
hintTitle: '',
|
|
3607
|
+
hintValue: '',
|
|
3608
|
+
checked: false,
|
|
3609
|
+
...overrides
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
1273
3612
|
function emptyMatrix(rows, columns) {
|
|
1274
3613
|
return Array.from({
|
|
1275
3614
|
length: rows
|
|
@@ -1507,4 +3846,4 @@ new TextEncoder();
|
|
|
1507
3846
|
function isOfficeKernelSpreadsheetError(value) {
|
|
1508
3847
|
return 'string' == typeof value && spreadsheetErrors.has(value);
|
|
1509
3848
|
}
|
|
1510
|
-
export { DOCUMENT_PARAGRAPH_BORDERS_ATTRIBUTE, DOCUMENT_PARAGRAPH_BORDER_EDGES, DOCUMENT_PARAGRAPH_BORDER_STYLES, DOCUMENT_RUN_BORDER_ATTRIBUTE, DOCUMENT_RUN_BORDER_STYLES, DocxThemePatchCollector, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, OoxmlPackage, WORK_TEMPLATES as officeTemplates, attribute, bytesToDataUrl, childPath, contentTypeForPart, createWorkArtifact as createArtifact, createWorkId as createOfficeId, decodeXmlBytes, descendants, directChild, directChildren, documentBorderPresentation, documentParagraphBordersDomAttributes, documentRunBorderDomAttributes, documentRunBorderIsVisible, firstDescendant, isDocumentParagraphArtBorderStyle, isOfficeKernelSpreadsheetError, normalizeCssColor, normalizeDocumentParagraphBorder, normalizeDocumentParagraphBorders, normalizeDocumentRunBorder, parseDocumentParagraphBorders, parseDocumentParagraphBordersElement, parseDocumentRunBorder, parseDocumentRunBorderElement, parseDocxThemeReference, parseXml, patchDocxThemeReferences, resolvePartTarget, serializeDocumentParagraphBorders, serializeDocumentRunBorder, serializeDocxThemeReference, serializeUtf8Xml, xmlContainsAnyElement, xmlNamespacePrefix };
|
|
3849
|
+
export { DEFAULT_DOCUMENT_INDEX_OPTIONS, DEFAULT_DOCUMENT_TABLE_OF_CONTENTS_OPTIONS, DOCUMENT_HIGHLIGHT_ATTRIBUTE, DOCUMENT_NO_PROOF_ATTRIBUTE, DOCUMENT_PARAGRAPH_BORDERS_ATTRIBUTE, DOCUMENT_PARAGRAPH_BORDER_EDGES, DOCUMENT_PARAGRAPH_BORDER_STYLES, DOCUMENT_PARAGRAPH_ID_ATTRIBUTE, DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE, DOCUMENT_PROOFING_LANGUAGES_ATTRIBUTE, DOCUMENT_RUN_BORDER_ATTRIBUTE, DOCUMENT_RUN_BORDER_STYLES, DOCUMENT_RUN_SHADING_ATTRIBUTE, DOCUMENT_SCRIPT_FONTS_ATTRIBUTE, DOCUMENT_SCRIPT_FONT_SLOT_ATTRIBUTE, DocumentParagraphIdentity, DocxThemePatchCollector, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, OoxmlPackage, WORK_TEMPLATES as officeTemplates, applyDocumentParagraphIdentityToElement, attribute, buildDocumentIndexEntries, buildDocumentTableOfContentsEntries, bytesToDataUrl, childPath, collectWorkDocumentOutline, contentTypeForPart, createDocumentParagraphIdentity, createDocumentParagraphIdentityRegistry, createWorkArtifact as createArtifact, createWorkId as createOfficeId, cssDocumentFontFamily, currentWorkDocumentOutlineItem, decodeXmlBytes, descendants, directChild, directChildren, documentBorderPresentation, documentFontNameFromCssFamily, documentHasIntegrityFeature, documentHighlightCssColor, documentHighlightDomAttributes, documentHighlightForCssColor, documentHighlightFromDocxValue, documentHighlightFromElement, documentIndexEntryFromElement, documentIndexEntryHtml, documentIndexHtml, documentIndexValueFromElement, documentNoProofFromElement, documentParagraphBordersDomAttributes, documentParagraphIdentityFromElement, documentParagraphShadingDomAttributes, documentProofingDomAttributes, documentProofingLanguagesFromElement, documentRunBorderDomAttributes, documentRunBorderIsVisible, documentRunShadingDomAttributes, documentScriptFontDirectFamily, documentScriptFontFallbackSlots, documentScriptFontFamily, documentScriptFontFamilyForRendering, documentScriptFontSegments, documentScriptFontSlotFromElement, documentScriptFontSlotFromHint, documentScriptFontsDomAttributes, documentScriptFontsForAllText, documentScriptFontsFromElement, documentTableOfContentsHtml, documentTableOfContentsValueFromElement, firstDescendant, isDocumentParagraphArtBorderStyle, isOfficeKernelSpreadsheetError, normalizeCssColor, normalizeDocumentFontName, normalizeDocumentHighlight, normalizeDocumentIndexEntry, normalizeDocumentIndexEntryDraft, normalizeDocumentIndexOptions, normalizeDocumentIndexValue, normalizeDocumentIndexesHtml, normalizeDocumentLanguageTag, normalizeDocumentNoProof, normalizeDocumentParagraphBorder, normalizeDocumentParagraphBorders, normalizeDocumentParagraphId, normalizeDocumentParagraphIdentity, normalizeDocumentProofingLanguages, normalizeDocumentRunBorder, normalizeDocumentRunShading, normalizeDocumentScriptFontHint, normalizeDocumentScriptFontSlot, normalizeDocumentScriptFonts, normalizeDocumentTableOfContentsHtml, normalizeDocumentTableOfContentsOptions, normalizeDocumentTableOfContentsValue, normalizeDocumentThemeFont, parseDocumentParagraphBorders, parseDocumentParagraphBordersElement, parseDocumentParagraphShading, parseDocumentParagraphShadingElement, parseDocumentProofingLanguages, parseDocumentRunBorder, parseDocumentRunBorderElement, parseDocumentRunShading, parseDocumentRunShadingElement, parseDocumentScriptFonts, parseDocumentTableOfContentsInstruction, parseDocxThemeReference, parseXml, patchDocumentProofingLanguages, patchDocumentScriptFonts, patchDocxThemeReferences, primeDocumentIntegrityFeatures, resolvePartTarget, serializeDocumentParagraphBorders, serializeDocumentParagraphShading, serializeDocumentProofingLanguages, serializeDocumentRunBorder, serializeDocumentRunShading, serializeDocumentScriptFonts, serializeDocxThemeReference, serializeUtf8Xml, uniqueDocumentParagraphIdentity, visibleWorkDocumentOutlineItems, workDocumentOutlineLevel, work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS, xmlContainsAnyElement, xmlNamespacePrefix };
|