@a3s-lab/office 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/4174.js CHANGED
@@ -215,1617 +215,1898 @@ function normalizedReferenceText(value) {
215
215
  function stringAttribute(value) {
216
216
  return 'string' == typeof value ? value.trim() : '';
217
217
  }
218
- const BOOKMARK_NAME_PATTERN = /^[\p{L}_][\p{L}\p{N}_]*$/u;
219
- const BOOKMARK_UI_NAME_PATTERN = /^[\p{L}][\p{L}\p{N}_]*$/u;
220
- const MAX_BOOKMARK_NAME_LENGTH = 40;
221
- const MAX_BOOKMARK_NATIVE_ID = 0x7fffffff;
222
- const MISSING_LINK_CLASS = 'work-document-link-missing';
223
- const DOCUMENT_BOOKMARK_VALIDATION_MESSAGE = '书签名称需以字母开头,只能包含字母、数字和下划线,且不超过 40 个字符。';
224
- const DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE = '文档中已存在同名书签。';
225
- const DocumentBookmarkBoundary = core_Node.create({
226
- name: 'documentBookmarkBoundary',
227
- inline: true,
228
- group: 'inline',
229
- atom: true,
230
- selectable: false,
231
- addCommands () {
232
- return {
233
- insertDocumentBookmark: (name)=>(props)=>insertDocumentBookmarkCommand(props, name),
234
- deleteDocumentBookmark: (id)=>(props)=>deleteDocumentBookmarkCommand(props, id)
235
- };
236
- },
237
- addProseMirrorPlugins () {
238
- return [
239
- createDocumentBookmarkPlugin(this.name)
240
- ];
241
- },
242
- addAttributes () {
243
- return {
244
- id: {
245
- default: '',
246
- parseHTML: (element)=>element.dataset.bookmarkId ?? '',
247
- renderHTML: ()=>({})
248
- },
249
- name: {
250
- default: '',
251
- parseHTML: (element)=>element.dataset.bookmarkName ?? '',
252
- renderHTML: ()=>({})
253
- },
254
- nativeId: {
255
- default: null,
256
- parseHTML: (element)=>normalizeDocumentBookmarkNativeId(element.dataset.officeBookmarkId),
257
- renderHTML: ()=>({})
258
- },
259
- kind: {
260
- default: 'start',
261
- parseHTML: (element)=>documentBookmarkBoundaryKind(element.dataset.bookmarkKind),
262
- renderHTML: ()=>({})
263
- }
264
- };
265
- },
266
- parseHTML () {
267
- return [
268
- {
269
- tag: 'span[data-document-bookmark-boundary]',
270
- getAttrs: (node)=>{
271
- if (!(node instanceof HTMLElement)) return false;
272
- return {
273
- id: node.dataset.bookmarkId ?? '',
274
- name: node.dataset.bookmarkName ?? '',
275
- nativeId: normalizeDocumentBookmarkNativeId(node.dataset.officeBookmarkId),
276
- kind: documentBookmarkBoundaryKind(node.dataset.bookmarkKind)
277
- };
278
- }
279
- }
280
- ];
281
- },
282
- renderHTML ({ node, HTMLAttributes }) {
283
- const name = normalizeDocumentBookmarkName(node.attrs.name) ?? '';
284
- const nativeId = normalizeDocumentBookmarkNativeId(node.attrs.nativeId);
285
- const kind = documentBookmarkBoundaryKind(node.attrs.kind);
286
- return [
287
- 'span',
288
- mergeAttributes(HTMLAttributes, {
289
- ...'start' === kind && name ? {
290
- id: name
291
- } : {},
292
- 'data-document-bookmark-boundary': 'true',
293
- 'data-bookmark-kind': kind,
294
- 'data-bookmark-id': 'string' == typeof node.attrs.id ? node.attrs.id : '',
295
- 'data-bookmark-name': name,
296
- 'data-office-bookmark-id': null === nativeId ? void 0 : String(nativeId),
297
- class: `work-document-bookmark-boundary ${kind}`,
298
- contenteditable: 'false',
299
- 'aria-hidden': 'true'
300
- })
301
- ];
302
- },
303
- renderText () {
304
- return '';
305
- }
306
- });
307
- function validateDocumentBookmarkName(value) {
308
- const name = value.trim();
309
- return BOOKMARK_UI_NAME_PATTERN.test(name) && Array.from(name).length <= MAX_BOOKMARK_NAME_LENGTH ? null : DOCUMENT_BOOKMARK_VALIDATION_MESSAGE;
310
- }
311
- function normalizeDocumentBookmarkName(value) {
312
- if ('string' != typeof value) return null;
313
- const name = value.trim();
314
- return BOOKMARK_NAME_PATTERN.test(name) && Array.from(name).length <= MAX_BOOKMARK_NAME_LENGTH ? name : null;
315
- }
316
- function normalizeDocumentBookmarkNativeId(value) {
317
- if (null == value || '' === value || 'string' == typeof value && !value.trim()) return null;
318
- const number = 'number' == typeof value ? value : Number(value);
319
- return Number.isSafeInteger(number) && number >= 0 && number <= MAX_BOOKMARK_NATIVE_ID ? number : null;
218
+ const DOCUMENT_HTML_FINGERPRINT_VERSION = 'p1';
219
+ const DOCUMENT_HTML_HASH_BASE = 0x01000193;
220
+ const LEGACY_FNV_OFFSET = 0x811c9dc5;
221
+ function createDocumentHtmlFingerprintSegment(source, from = 0, to = source.length) {
222
+ if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from < 0 || to < from || to > source.length) throw new RangeError('The document HTML fingerprint range is invalid.');
223
+ let hash = 0;
224
+ for(let index = from; index < to; index += 1)hash = Math.imul(hash, DOCUMENT_HTML_HASH_BASE) + source.charCodeAt(index) >>> 0;
225
+ const length = to - from;
226
+ return {
227
+ hash,
228
+ length,
229
+ power: documentHtmlHashPower(length)
230
+ };
320
231
  }
321
- function editorDocumentBookmarks(editor) {
322
- return collectDocumentBookmarkPairs(editor.state.doc, 'documentBookmarkBoundary').pairs.map(({ id, name, nativeId, from, to })=>({
323
- id,
324
- name,
325
- nativeId,
326
- from,
327
- to
328
- }));
232
+ function combineDocumentHtmlFingerprintSegments(segments) {
233
+ let combined = {
234
+ hash: 0,
235
+ length: 0,
236
+ power: 1
237
+ };
238
+ for (const segment of segments)combined = {
239
+ hash: Math.imul(combined.hash, segment.power) + segment.hash >>> 0,
240
+ length: combined.length + segment.length,
241
+ power: Math.imul(combined.power, segment.power) >>> 0
242
+ };
243
+ return combined;
329
244
  }
330
- function editorDocumentBookmarkReferenceTargets(editor) {
331
- return collectDocumentBookmarkPairs(editor.state.doc, 'documentBookmarkBoundary').pairs.map((bookmark)=>{
332
- const display = documentBookmarkReferenceDisplay(editor.state.doc, bookmark);
333
- return {
334
- type: 'bookmark',
335
- id: bookmark.id,
336
- name: bookmark.name,
337
- title: display,
338
- display,
339
- instruction: documentBookmarkReferenceInstruction(bookmark.name)
340
- };
341
- });
245
+ function documentHtmlFingerprintForSegment(segment) {
246
+ return `${DOCUMENT_HTML_FINGERPRINT_VERSION}:${segment.length.toString(36)}:${segment.hash.toString(36)}`;
342
247
  }
343
- function activeDocumentBookmark(editor) {
344
- const { from, to } = editor.state.selection;
345
- const matches = editorDocumentBookmarks(editor).filter((bookmark)=>from >= bookmark.from + 1 && to <= bookmark.to);
346
- return matches.sort((left, right)=>left.to - left.from - (right.to - right.from))[0] ?? null;
248
+ function documentHtmlFingerprint(html) {
249
+ return documentHtmlFingerprintForSegment(createDocumentHtmlFingerprintSegment(html));
347
250
  }
348
- function documentBookmarkNameExists(editor, value, exceptId) {
349
- const name = value.trim().toLowerCase();
350
- return editorDocumentBookmarks(editor).some((bookmark)=>bookmark.id !== exceptId && bookmark.name.toLowerCase() === name);
251
+ function documentHtmlFingerprintMatches(html, candidate) {
252
+ return candidate.startsWith(`${DOCUMENT_HTML_FINGERPRINT_VERSION}:`) ? candidate === documentHtmlFingerprint(html) : candidate === legacyDocumentHtmlFingerprint(html);
351
253
  }
352
- function normalizeDocumentBookmarksHtml(source) {
353
- const document1 = new DOMParser().parseFromString(source, 'text/html');
354
- const boundaries = Array.from(document1.body.querySelectorAll('span[data-document-bookmark-boundary]'));
355
- const collection = collectDomBookmarkPairs(boundaries);
356
- for (const orphan of collection.orphans)orphan.remove();
357
- const registry = createBookmarkRegistry();
358
- for (const pair of collection.pairs){
359
- const previous = {
360
- id: bookmarkInternalId(pair.id),
361
- name: normalizeDocumentBookmarkName(pair.name) ?? ''
362
- };
363
- const identity = uniqueDocumentBookmarkIdentity(pair, registry);
364
- applyBookmarkIdentityToElement(pair.start, identity, 'start');
365
- applyBookmarkIdentityToElement(pair.end, identity, 'end');
366
- if (previous.id && previous.name && (previous.id !== identity.id || previous.name !== identity.name)) retargetDomBookmarkReferences(document1.body, pair.start, pair.end, previous, identity);
254
+ function legacyDocumentHtmlFingerprint(html) {
255
+ let hash = LEGACY_FNV_OFFSET;
256
+ for(let index = 0; index < html.length; index += 1){
257
+ hash ^= html.charCodeAt(index);
258
+ hash = Math.imul(hash, DOCUMENT_HTML_HASH_BASE);
367
259
  }
368
- synchronizeDomInternalLinks(document1.body, registry.names);
369
- return document1.body.innerHTML;
370
- }
371
- function insertDocumentBookmarkCommand({ dispatch, editor, state, tr }, value) {
372
- const name = value.trim();
373
- const boundaryType = editor.schema.nodes.documentBookmarkBoundary;
374
- const selection = state.selection;
375
- if (!boundaryType || validateDocumentBookmarkName(name) || documentBookmarkNameExists(editor, name) || !selection.$from.parent.inlineContent || !selection.$to.parent.inlineContent) return false;
376
- if (!dispatch) return true;
377
- const registry = bookmarkRegistryForDocument(state.doc, boundaryType.name);
378
- const identity = uniqueDocumentBookmarkIdentity({
379
- id: createUniqueBookmarkId(registry.ids),
380
- name,
381
- nativeId: nextBookmarkNativeId(registry.nativeIds)
382
- }, registry);
383
- const start = boundaryType.create({
384
- ...identity,
385
- kind: 'start'
386
- });
387
- const end = boundaryType.create({
388
- ...identity,
389
- kind: 'end'
390
- });
391
- const from = selection.from;
392
- const to = selection.to;
393
- tr.insert(to, end);
394
- tr.insert(from, start);
395
- tr.setSelection(TextSelection.create(tr.doc, from + 1, to + 1));
396
- dispatch(tr.scrollIntoView());
397
- return true;
260
+ return `${html.length.toString(36)}:${(hash >>> 0).toString(36)}`;
398
261
  }
399
- function deleteDocumentBookmarkCommand({ dispatch, editor, state, tr }, id) {
400
- const bookmark = collectDocumentBookmarkPairs(state.doc, editor.schema.nodes.documentBookmarkBoundary?.name ?? '').pairs.find((candidate)=>candidate.id === id);
401
- if (!bookmark) return false;
402
- if (!dispatch) return true;
403
- tr.delete(bookmark.to, bookmark.to + bookmark.end.node.nodeSize);
404
- tr.delete(bookmark.from, bookmark.from + bookmark.start.node.nodeSize);
405
- tr.setSelection(TextSelection.near(tr.doc.resolve(bookmark.from)));
406
- dispatch(tr.scrollIntoView());
407
- return true;
262
+ function documentHtmlHashPower(length) {
263
+ let exponent = length;
264
+ let factor = DOCUMENT_HTML_HASH_BASE;
265
+ let power = 1;
266
+ while(exponent > 0){
267
+ if (exponent % 2 === 1) power = Math.imul(power, factor) >>> 0;
268
+ factor = Math.imul(factor, factor) >>> 0;
269
+ exponent = Math.floor(exponent / 2);
270
+ }
271
+ return power;
408
272
  }
409
- function createDocumentBookmarkPlugin(boundaryNodeName) {
410
- return new Plugin({
411
- view (view) {
412
- const transaction = normalizeDocumentBookmarks(view.state, boundaryNodeName);
413
- if (transaction) {
414
- transaction.setMeta('addToHistory', false);
415
- view.dispatch(transaction);
273
+ const SECTION_CLOSE = '</section>';
274
+ const TABLE_BODY_OPEN = '<tbody>';
275
+ const TABLE_CLOSE = '</tbody></table>';
276
+ const projectionFingerprints = new WeakMap();
277
+ function createDocumentLazyHtmlProjection(html, root) {
278
+ if (!html.startsWith('<section') || !html.endsWith(SECTION_CLOSE)) return null;
279
+ const sectionOpenEnd = html.indexOf('>') + 1;
280
+ if (sectionOpenEnd <= 0) return null;
281
+ let cursor = sectionOpenEnd;
282
+ const orderedRanges = [];
283
+ const ranges = new Map();
284
+ for (const chunk of lazyDocumentLeafChunks(root)){
285
+ const id = 'string' == typeof chunk.attrs?.id ? chunk.attrs.id : '';
286
+ if (!id || ranges.has(id)) return null;
287
+ const from = cursor;
288
+ let tablePart = null;
289
+ const content = chunk.content ?? [];
290
+ for (const node of content){
291
+ const scanned = scanSimpleDocumentNodeHtml(html, cursor, node);
292
+ if (!scanned) return null;
293
+ cursor = scanned.to;
294
+ if (scanned.tablePart) {
295
+ if (1 !== content.length || tablePart) return null;
296
+ tablePart = scanned.tablePart;
416
297
  }
417
- return {};
418
- },
419
- appendTransaction (transactions, oldState, newState) {
420
- if (!transactions.some((transaction)=>transaction.docChanged)) return null;
421
- return normalizeDocumentBookmarks(newState, boundaryNodeName, oldState, transactions);
422
- }
423
- });
424
- }
425
- function normalizeDocumentBookmarks(state, boundaryNodeName, oldState, transactions = []) {
426
- if ('documentBookmarkBoundary' === boundaryNodeName && !documentHasIntegrityFeature(state.doc, 1)) return null;
427
- const collection = collectDocumentBookmarkPairs(state.doc, boundaryNodeName);
428
- const retained = oldState ? retainedDocumentBookmarkPairs(oldState, state, transactions, boundaryNodeName) : new Set();
429
- const ordered = [
430
- ...collection.pairs.filter((pair)=>retained.has(pairPositionKey(pair))),
431
- ...collection.pairs.filter((pair)=>!retained.has(pairPositionKey(pair)))
432
- ];
433
- const registry = createBookmarkRegistry();
434
- const updates = new Map();
435
- const renames = [];
436
- const effectivePairs = [];
437
- for (const pair of ordered){
438
- const identity = uniqueDocumentBookmarkIdentity(pair, registry);
439
- if (!sameBookmarkIdentity(pair.start.node.attrs, identity)) updates.set(pair.from, identity);
440
- if (!sameBookmarkIdentity(pair.end.node.attrs, identity)) updates.set(pair.to, identity);
441
- if (!sameBookmarkIdentity(pair, identity)) {
442
- if (pair.id && pair.name && (pair.id !== identity.id || pair.name !== identity.name)) renames.push({
443
- from: pair.from,
444
- to: pair.to,
445
- previousId: pair.id,
446
- nextId: identity.id,
447
- previousName: pair.name,
448
- nextName: identity.name
449
- });
450
- }
451
- effectivePairs.push({
452
- ...pair,
453
- ...identity
454
- });
455
- }
456
- const tr = state.tr;
457
- for (const boundary of documentBookmarkBoundaries(state.doc, boundaryNodeName)){
458
- const identity = updates.get(boundary.position);
459
- if (identity) tr.setNodeMarkup(boundary.position, void 0, {
460
- ...boundary.node.attrs,
461
- ...identity
462
- });
298
+ }
299
+ const range = {
300
+ from,
301
+ id,
302
+ tablePart,
303
+ to: cursor
304
+ };
305
+ orderedRanges.push(range);
306
+ ranges.set(id, range);
463
307
  }
464
- synchronizeInternalLinkMarks(state, tr, effectivePairs, renames);
465
- synchronizeDocumentBookmarkReferenceNodes(state, tr, effectivePairs, renames);
466
- for (const orphan of [
467
- ...collection.orphans
468
- ].sort((left, right)=>right.position - left.position))tr.delete(orphan.position, orphan.position + orphan.node.nodeSize);
469
- return tr.docChanged ? tr : null;
308
+ if (cursor !== html.length - SECTION_CLOSE.length) return null;
309
+ return {
310
+ html,
311
+ orderedRanges,
312
+ ranges
313
+ };
470
314
  }
471
- function retainedDocumentBookmarkPairs(oldState, newState, transactions, boundaryNodeName) {
472
- const mapping = transactionMapping(transactions);
473
- const retained = new Set();
474
- const current = collectDocumentBookmarkPairs(newState.doc, boundaryNodeName).pairs;
475
- const currentById = new Map();
476
- for (const pair of current){
477
- const matches = currentById.get(pair.id) ?? [];
478
- matches.push(pair);
479
- currentById.set(pair.id, matches);
480
- }
481
- for (const previous of collectDocumentBookmarkPairs(oldState.doc, boundaryNodeName).pairs){
482
- const mappedFrom = mapping.mapResult(previous.from, 1);
483
- const mappedTo = mapping.mapResult(previous.to, 1);
484
- const exact = current.find((pair)=>pair.from === mappedFrom.pos && pair.to === mappedTo.pos && pair.id === previous.id);
485
- if (exact) {
486
- retained.add(pairPositionKey(exact));
487
- continue;
315
+ function patchDocumentLazyHtmlProjection(projection, replacements) {
316
+ if (!replacements.size) return projection.html;
317
+ for (const id of replacements.keys())if (!projection.ranges.has(id)) return null;
318
+ const fingerprintState = documentLazyHtmlFingerprintState(projection);
319
+ const parts = [];
320
+ let cursor = 0;
321
+ for (const range of projection.orderedRanges){
322
+ const replacement = replacements.get(range.id);
323
+ if (void 0 !== replacement) {
324
+ parts.push(projection.html.slice(cursor, range.from), replacement);
325
+ fingerprintState.ranges.set(range.id, createDocumentHtmlFingerprintSegment(replacement));
326
+ cursor = range.to;
488
327
  }
489
- const sameIdentity = currentById.get(previous.id);
490
- if (sameIdentity?.length === 1) retained.add(pairPositionKey(sameIdentity[0]));
491
328
  }
492
- return retained;
329
+ parts.push(projection.html.slice(cursor));
330
+ const html = parts.join('');
331
+ let offset = 0;
332
+ for (const range of projection.orderedRanges){
333
+ const previousFrom = range.from;
334
+ const previousLength = range.to - previousFrom;
335
+ const replacement = replacements.get(range.id);
336
+ const nextLength = replacement?.length ?? previousLength;
337
+ range.from = previousFrom + offset;
338
+ range.to = range.from + nextLength;
339
+ offset += nextLength - previousLength;
340
+ }
341
+ projection.html = html;
342
+ fingerprintState.fingerprint = documentHtmlFingerprintForSegment(combineDocumentHtmlFingerprintSegments([
343
+ fingerprintState.prefix,
344
+ ...projection.orderedRanges.map((range)=>{
345
+ const segment = fingerprintState.ranges.get(range.id);
346
+ if (!segment) throw new Error('The lazy HTML fingerprint segment is missing.');
347
+ return segment;
348
+ }),
349
+ fingerprintState.suffix
350
+ ]));
351
+ return html;
493
352
  }
494
- function synchronizeInternalLinkMarks(state, tr, bookmarks, renames) {
495
- const linkType = state.schema.marks.link;
496
- if (!linkType) return;
497
- const names = new Set(bookmarks.map((bookmark)=>bookmark.name.toLowerCase()));
498
- state.doc.descendants((node, position)=>{
499
- if (!node.isText) return;
500
- const link = node.marks.find((mark)=>mark.type === linkType);
501
- if (!link) return;
502
- const href = 'string' == typeof link.attrs.href ? link.attrs.href : '';
503
- if (!href.startsWith('#')) return;
504
- const target = href.slice(1);
505
- const rename = renames.filter((candidate)=>position > candidate.from && position < candidate.to && target.toLowerCase() === candidate.previousName.toLowerCase()).sort((left, right)=>left.to - left.from - (right.to - right.from))[0];
506
- const nextHref = rename ? `#${rename.nextName}` : href;
507
- const nextTarget = nextHref.slice(1).toLowerCase();
508
- const nextClass = toggleClassToken(link.attrs.class, MISSING_LINK_CLASS, !names.has(nextTarget));
509
- if (nextHref === href && nextClass === normalizedClass(link.attrs.class)) return;
510
- replaceLinkMark(state, tr, node, position, link, {
511
- ...link.attrs,
512
- href: nextHref,
513
- class: nextClass || null
514
- });
515
- });
353
+ function documentLazyHtmlProjectionFingerprint(projection) {
354
+ return projectionFingerprints.get(projection)?.fingerprint ?? null;
516
355
  }
517
- function replaceLinkMark(_state, tr, node, position, link, attributes) {
518
- tr.removeMark(position, position + node.nodeSize, link.type);
519
- tr.addMark(position, position + node.nodeSize, link.type.create(attributes));
356
+ function documentLazyHtmlChunkFragment(html, tablePart) {
357
+ if (!tablePart || 'complete' === tablePart) return html;
358
+ const bodyOpen = html.indexOf(TABLE_BODY_OPEN);
359
+ const bodyClose = html.lastIndexOf('</tbody>');
360
+ if (bodyOpen < 0 || bodyClose < bodyOpen) return null;
361
+ const rowsFrom = bodyOpen + TABLE_BODY_OPEN.length;
362
+ if ('first' === tablePart) return html.slice(0, bodyClose);
363
+ if ('middle' === tablePart) return html.slice(rowsFrom, bodyClose);
364
+ return html.slice(rowsFrom);
520
365
  }
521
- function collectDocumentBookmarkPairs(document1, boundaryNodeName) {
522
- const boundaries = documentBookmarkBoundaries(document1, boundaryNodeName);
523
- const open = new Map();
524
- const pairs = [];
525
- const orphans = [];
526
- for (const boundary of boundaries){
527
- const key = bookmarkBoundaryPairKey(boundary.node.attrs);
528
- if ('start' === boundary.kind) {
529
- const stack = open.get(key) ?? [];
530
- stack.push(boundary);
531
- open.set(key, stack);
366
+ function lazyDocumentLeafChunks(root) {
367
+ const chunks = [];
368
+ const pending = [
369
+ root
370
+ ];
371
+ while(pending.length){
372
+ const node = pending.pop();
373
+ if (!node) continue;
374
+ if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
375
+ chunks.push(node);
532
376
  continue;
533
377
  }
534
- const stack = open.get(key);
535
- const start = stack?.pop();
536
- if (!start) {
537
- orphans.push(boundary);
538
- continue;
378
+ for(let index = (node.content?.length ?? 0) - 1; index >= 0; index -= 1){
379
+ const child = node.content?.[index];
380
+ if (child) pending.push(child);
539
381
  }
540
- pairs.push(bookmarkPair(start, boundary));
541
382
  }
542
- for (const stack of open.values())orphans.push(...stack);
543
- pairs.sort((left, right)=>left.from - right.from || left.to - right.to);
544
- return {
545
- pairs,
546
- orphans
547
- };
548
- }
549
- function documentBookmarkBoundaries(document1, boundaryNodeName) {
550
- if (!boundaryNodeName) return [];
551
- const boundaries = [];
552
- document1.descendants((node, position)=>{
553
- if (node.type.name !== boundaryNodeName) return;
554
- boundaries.push({
555
- node,
556
- position,
557
- kind: documentBookmarkBoundaryKind(node.attrs.kind)
558
- });
559
- });
560
- return boundaries;
383
+ return chunks;
561
384
  }
562
- function bookmarkPair(start, end) {
563
- return {
564
- id: bookmarkInternalId(start.node.attrs.id),
565
- name: normalizeDocumentBookmarkName(start.node.attrs.name) ?? '',
566
- nativeId: normalizeDocumentBookmarkNativeId(start.node.attrs.nativeId) ?? -1,
567
- from: start.position,
568
- to: end.position,
569
- start,
570
- end
385
+ function documentLazyHtmlFingerprintState(projection) {
386
+ const existing = projectionFingerprints.get(projection);
387
+ if (existing) return existing;
388
+ const first = projection.orderedRanges[0];
389
+ const last = projection.orderedRanges.at(-1);
390
+ const ranges = new Map();
391
+ for (const range of projection.orderedRanges)ranges.set(range.id, createDocumentHtmlFingerprintSegment(projection.html, range.from, range.to));
392
+ const prefix = createDocumentHtmlFingerprintSegment(projection.html, 0, first?.from ?? projection.html.length);
393
+ const suffix = createDocumentHtmlFingerprintSegment(projection.html, last?.to ?? projection.html.length, projection.html.length);
394
+ const state = {
395
+ fingerprint: documentHtmlFingerprintForSegment(combineDocumentHtmlFingerprintSegments([
396
+ prefix,
397
+ ...projection.orderedRanges.map((range)=>{
398
+ const segment = ranges.get(range.id);
399
+ if (!segment) throw new Error('The lazy HTML fingerprint segment is missing.');
400
+ return segment;
401
+ }),
402
+ suffix
403
+ ])),
404
+ prefix,
405
+ ranges,
406
+ suffix
571
407
  };
408
+ projectionFingerprints.set(projection, state);
409
+ return state;
572
410
  }
573
- function bookmarkRegistryForDocument(document1, boundaryNodeName) {
574
- const registry = createBookmarkRegistry();
575
- for (const pair of collectDocumentBookmarkPairs(document1, boundaryNodeName).pairs){
576
- const identity = bookmarkIdentity(pair);
577
- if (identity) reserveBookmarkIdentity(identity, registry);
411
+ function scanSimpleDocumentNodeHtml(html, from, node) {
412
+ if ('paragraph' === node.type) {
413
+ if (!html.startsWith('<p>', from)) return null;
414
+ const close = html.indexOf('</p>', from + 3);
415
+ return close < 0 ? null : {
416
+ tablePart: null,
417
+ to: close + 4
418
+ };
578
419
  }
579
- return registry;
420
+ if ('table' !== node.type) return null;
421
+ const virtualIndex = Number(node.attrs?.virtualTableIndex);
422
+ const virtualCount = Number(node.attrs?.virtualTableCount);
423
+ const virtual = Boolean('string' == typeof node.attrs?.virtualTableId && node.attrs.virtualTableId && Number.isSafeInteger(virtualIndex) && Number.isSafeInteger(virtualCount) && virtualIndex >= 0 && virtualCount > 0 && virtualIndex < virtualCount);
424
+ let cursor = from;
425
+ if (!virtual || 0 === virtualIndex) {
426
+ if (!html.startsWith('<table', cursor)) return null;
427
+ const body = html.indexOf(TABLE_BODY_OPEN, cursor);
428
+ if (body < 0) return null;
429
+ cursor = body + TABLE_BODY_OPEN.length;
430
+ }
431
+ for (const row of node.content ?? []){
432
+ if ('tableRow' !== row.type || !html.startsWith('<tr>', cursor)) return null;
433
+ const close = html.indexOf('</tr>', cursor + 4);
434
+ if (close < 0) return null;
435
+ cursor = close + 5;
436
+ }
437
+ if (!virtual || virtualIndex === virtualCount - 1) {
438
+ if (!html.startsWith(TABLE_CLOSE, cursor)) return null;
439
+ cursor += TABLE_CLOSE.length;
440
+ }
441
+ const tablePart = virtual ? 1 === virtualCount ? 'complete' : 0 === virtualIndex ? 'first' : virtualIndex === virtualCount - 1 ? 'last' : 'middle' : 'complete';
442
+ return {
443
+ tablePart,
444
+ to: cursor
445
+ };
580
446
  }
581
- function uniqueDocumentBookmarkIdentity(source, registry) {
582
- const preferred = bookmarkIdentity(source);
583
- if (preferred && !bookmarkIdentityConflicts(preferred, registry)) {
584
- reserveBookmarkIdentity(preferred, registry);
585
- return preferred;
447
+ const DOCUMENT_LAZY_BLOCK_NODE = 'documentLazyBlock';
448
+ const DOCUMENT_LAZY_INITIAL_CHUNK_COUNT = 2;
449
+ const DOCUMENT_LAZY_POSITION_BOUNDARY = '\ufffc';
450
+ const SIMPLE_LAZY_CONTAINER_TYPES = new Set([
451
+ 'paragraph',
452
+ 'table',
453
+ 'tableCell',
454
+ 'tableRow'
455
+ ]);
456
+ const preparedModels = new WeakMap();
457
+ function prepareLazyDocumentEditorSource(model, allowCreate, html) {
458
+ const startedAt = lazyDocumentNow();
459
+ const cacheHit = preparedModels.has(model);
460
+ let prepared = preparedModels.get(model);
461
+ if (prepared) {
462
+ if (!prepared.root) {
463
+ const htmlProjection = prepared.htmlProjection;
464
+ const rebuilt = createPreparedLazyDocumentEditorSource(model.root, prepared.payloads);
465
+ if (!rebuilt) return null;
466
+ rebuilt.htmlProjection = htmlProjection;
467
+ prepared = rebuilt;
468
+ preparedModels.set(model, rebuilt);
469
+ }
470
+ } else {
471
+ if (!allowCreate) return null;
472
+ const created = createPreparedLazyDocumentEditorSource(model.root);
473
+ if (!created) return null;
474
+ prepared = created;
475
+ preparedModels.set(model, created);
586
476
  }
587
- const baseName = normalizeDocumentBookmarkName(source.name) ?? 'Bookmark';
588
- const identity = {
589
- id: createUniqueBookmarkId(registry.ids),
590
- name: uniqueBookmarkName(baseName, registry.names),
591
- nativeId: nextBookmarkNativeId(registry.nativeIds)
477
+ if (!prepared.htmlProjection && html) prepared.htmlProjection = createDocumentLazyHtmlProjection(html, model.root);
478
+ if (!prepared.root) return null;
479
+ const result = {
480
+ lazyChunkCount: prepared.lazyChunkCount,
481
+ payloads: prepared.payloads,
482
+ root: prepared.root
592
483
  };
593
- reserveBookmarkIdentity(identity, registry);
594
- return identity;
484
+ recordLazyDocumentMeasure('a3s-office.document.lazy-editor-source', startedAt, lazyDocumentNow(), {
485
+ cacheHit
486
+ });
487
+ return result;
595
488
  }
596
- function bookmarkIdentity(source) {
597
- const id = bookmarkInternalId(source.id);
598
- const name = normalizeDocumentBookmarkName(source.name);
599
- const nativeId = normalizeDocumentBookmarkNativeId(source.nativeId);
600
- return id && name && null !== nativeId ? {
601
- id,
602
- name,
603
- nativeId
604
- } : null;
489
+ function documentLazyHtmlProjection(model) {
490
+ return model ? preparedModels.get(model)?.htmlProjection ?? null : null;
605
491
  }
606
- function bookmarkIdentityConflicts(identity, registry) {
607
- return registry.ids.has(identity.id) || registry.names.has(identity.name.toLowerCase()) || registry.nativeIds.has(identity.nativeId);
492
+ function invalidateDocumentLazyHtmlProjection(model) {
493
+ if (model) {
494
+ const prepared = preparedModels.get(model);
495
+ if (prepared) prepared.htmlProjection = null;
496
+ }
608
497
  }
609
- function reserveBookmarkIdentity(identity, registry) {
610
- registry.ids.add(identity.id);
611
- registry.names.add(identity.name.toLowerCase());
612
- registry.nativeIds.add(identity.nativeId);
498
+ function documentLazyChunkContent(model, chunkId) {
499
+ if (!model || !chunkId) return null;
500
+ return preparedModels.get(model)?.payloads.get(chunkId) ?? null;
613
501
  }
614
- function createBookmarkRegistry() {
615
- return {
616
- ids: new Set(),
617
- names: new Set(),
618
- nativeIds: new Set()
502
+ function materializeLazyDocumentEditorRoot(root, model) {
503
+ const prepared = model ? preparedModels.get(model) : null;
504
+ if (!prepared) return root;
505
+ const visit = (node)=>{
506
+ if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
507
+ const id = documentChunkId(node);
508
+ if (!id) return node;
509
+ if (documentChunkIsLazy(node)) {
510
+ const payload = prepared.payloads.get(id);
511
+ if (!payload) throw new Error(`The lazy document chunk payload "${id}" is missing.`);
512
+ return {
513
+ ...node,
514
+ content: [
515
+ ...payload
516
+ ]
517
+ };
518
+ }
519
+ if (node.content?.length) prepared.payloads.set(id, node.content);
520
+ return node;
521
+ }
522
+ if (!node.content?.length) return node;
523
+ let changed = false;
524
+ const content = node.content.map((child)=>{
525
+ const next = visit(child);
526
+ if (next !== child) changed = true;
527
+ return next;
528
+ });
529
+ return changed ? {
530
+ ...node,
531
+ content
532
+ } : node;
619
533
  };
534
+ return visit(root);
620
535
  }
621
- function createUniqueBookmarkId(ids) {
622
- for(let attempt = 0; attempt < 8; attempt += 1){
623
- const id = createWorkId('bookmark');
624
- if (!ids.has(id)) return id;
625
- }
626
- let suffix = 1;
627
- while(ids.has(`bookmark-${suffix}`))suffix += 1;
628
- return `bookmark-${suffix}`;
536
+ function transferLazyDocumentModelState(previous, next) {
537
+ if (!previous) return;
538
+ const prepared = preparedModels.get(previous);
539
+ if (!prepared) return;
540
+ preparedModels.set(next, {
541
+ htmlProjection: prepared.htmlProjection,
542
+ lazyChunkCount: prepared.lazyChunkCount,
543
+ payloads: prepared.payloads,
544
+ root: null
545
+ });
629
546
  }
630
- function uniqueBookmarkName(base, names) {
631
- if (!names.has(base.toLowerCase())) return base;
632
- let suffix = 2;
633
- while(suffix <= MAX_BOOKMARK_NATIVE_ID){
634
- const ending = `_${suffix}`;
635
- const prefix = Array.from(base).slice(0, MAX_BOOKMARK_NAME_LENGTH - ending.length).join('');
636
- const candidate = `${prefix}${ending}`;
637
- if (!names.has(candidate.toLowerCase())) return candidate;
638
- suffix += 1;
547
+ function simpleDocumentNodeSize(node) {
548
+ if ('text' === node.type) return 'string' == typeof node.text && node.text ? node.text.length : null;
549
+ if (!SIMPLE_LAZY_CONTAINER_TYPES.has(node.type)) return null;
550
+ let size = 2;
551
+ for (const child of node.content ?? []){
552
+ const childSize = simpleDocumentNodeSize(child);
553
+ if (null === childSize) return null;
554
+ size += childSize;
639
555
  }
640
- throw new Error('No unique Word bookmark name is available.');
556
+ return size;
641
557
  }
642
- function nextBookmarkNativeId(ids) {
643
- for(let id = 0; id <= MAX_BOOKMARK_NATIVE_ID; id += 1)if (!ids.has(id)) return id;
644
- throw new Error('No unique Word bookmark identifier is available.');
558
+ function createPreparedLazyDocumentEditorSource(root, previousPayloads) {
559
+ const payloads = new Map();
560
+ let leafIndex = 0;
561
+ let lazyChunkCount = 0;
562
+ let unsupported = false;
563
+ const visit = (node)=>{
564
+ if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
565
+ const id = documentChunkId(node);
566
+ const payload = id ? previousPayloads?.get(id) ?? node.content ?? [] : [];
567
+ if (!id || !payload.length) {
568
+ unsupported = true;
569
+ return node;
570
+ }
571
+ payloads.set(id, payload);
572
+ const currentIndex = leafIndex;
573
+ leafIndex += 1;
574
+ if (currentIndex < DOCUMENT_LAZY_INITIAL_CHUNK_COUNT) return node;
575
+ const placeholder = lazyPlaceholderForContent(id, payload);
576
+ if (!placeholder) {
577
+ unsupported = true;
578
+ return node;
579
+ }
580
+ lazyChunkCount += 1;
581
+ return {
582
+ ...node,
583
+ content: [
584
+ placeholder
585
+ ]
586
+ };
587
+ }
588
+ if (!node.content?.length) return node;
589
+ let changed = false;
590
+ const content = node.content.map((child)=>{
591
+ const next = visit(child);
592
+ if (next !== child) changed = true;
593
+ return next;
594
+ });
595
+ return changed ? {
596
+ ...node,
597
+ content
598
+ } : node;
599
+ };
600
+ const compactRoot = visit(root);
601
+ if (unsupported || leafIndex <= DOCUMENT_LAZY_INITIAL_CHUNK_COUNT || 0 === lazyChunkCount) return null;
602
+ return {
603
+ htmlProjection: null,
604
+ lazyChunkCount,
605
+ payloads,
606
+ root: compactRoot
607
+ };
645
608
  }
646
- function sameBookmarkIdentity(source, identity) {
647
- return bookmarkInternalId(source.id) === identity.id && normalizeDocumentBookmarkName(source.name) === identity.name && normalizeDocumentBookmarkNativeId(source.nativeId) === identity.nativeId;
609
+ function lazyPlaceholderForContent(chunkId, content) {
610
+ const tapeParts = [];
611
+ const statistics = {
612
+ contentSize: 0,
613
+ paragraphCount: 0
614
+ };
615
+ for (const node of content)if (!appendDocumentNodePositionTape(node, tapeParts, statistics)) return null;
616
+ if (statistics.contentSize < 2 || tapeParts[0] !== DOCUMENT_LAZY_POSITION_BOUNDARY || tapeParts.at(-1) !== DOCUMENT_LAZY_POSITION_BOUNDARY) return null;
617
+ tapeParts[0] = '';
618
+ tapeParts[tapeParts.length - 1] = '';
619
+ const filler = tapeParts.join('');
620
+ return {
621
+ type: DOCUMENT_LAZY_BLOCK_NODE,
622
+ attrs: {
623
+ chunkId,
624
+ contentSize: statistics.contentSize,
625
+ paragraphCount: statistics.paragraphCount
626
+ },
627
+ ...filler ? {
628
+ content: [
629
+ {
630
+ type: 'text',
631
+ text: filler
632
+ }
633
+ ]
634
+ } : {
635
+ content: []
636
+ }
637
+ };
648
638
  }
649
- function bookmarkInternalId(value) {
650
- return 'string' == typeof value ? value.trim() : '';
639
+ function appendDocumentNodePositionTape(node, parts, statistics) {
640
+ if ('text' === node.type) {
641
+ if ('string' != typeof node.text || !node.text) return false;
642
+ parts.push(node.text);
643
+ statistics.contentSize += node.text.length;
644
+ return true;
645
+ }
646
+ if (!SIMPLE_LAZY_CONTAINER_TYPES.has(node.type)) return false;
647
+ if ('paragraph' === node.type) statistics.paragraphCount += 1;
648
+ statistics.contentSize += 2;
649
+ parts.push(DOCUMENT_LAZY_POSITION_BOUNDARY);
650
+ for (const child of node.content ?? [])if (!appendDocumentNodePositionTape(child, parts, statistics)) return false;
651
+ parts.push(DOCUMENT_LAZY_POSITION_BOUNDARY);
652
+ return true;
651
653
  }
652
- function bookmarkBoundaryPairKey(attributes) {
653
- const id = bookmarkInternalId(attributes.id);
654
- if (id) return `id:${id}`;
655
- return `legacy:${String(attributes.name ?? '')}:${String(attributes.nativeId ?? '')}`;
654
+ function documentChunkIsLazy(node) {
655
+ return 'documentChunk' === node.type && node.attrs?.windowContainer !== true && node.content?.length === 1 && node.content[0]?.type === DOCUMENT_LAZY_BLOCK_NODE;
656
656
  }
657
- function documentBookmarkBoundaryKind(value) {
658
- return 'end' === value ? 'end' : 'start';
657
+ function documentChunkId(node) {
658
+ const id = node.attrs?.id;
659
+ return 'string' == typeof id && id ? id : null;
659
660
  }
660
- function transactionMapping(transactions) {
661
- const mapping = new Mapping();
662
- for (const transaction of transactions)mapping.appendMapping(transaction.mapping);
663
- return mapping;
661
+ function lazyDocumentNow() {
662
+ return globalThis.performance?.now?.() ?? Date.now();
664
663
  }
665
- function pairPositionKey(pair) {
666
- return `${pair.from}:${pair.to}`;
664
+ function recordLazyDocumentMeasure(name, start, end, detail) {
665
+ try {
666
+ globalThis.performance?.measure(name, {
667
+ detail,
668
+ end,
669
+ start
670
+ });
671
+ } catch {}
667
672
  }
668
- function collectDomBookmarkPairs(boundaries) {
669
- const open = new Map();
670
- const pairs = [];
671
- const orphans = [];
672
- for (const boundary of boundaries){
673
- const key = domBookmarkPairKey(boundary);
674
- if ('start' === documentBookmarkBoundaryKind(boundary.dataset.bookmarkKind)) {
675
- const stack = open.get(key) ?? [];
676
- stack.push(boundary);
677
- open.set(key, stack);
673
+ const statisticsByDocument = new WeakMap();
674
+ function documentWordCount(value) {
675
+ let asciiCount = 0;
676
+ let inAsciiWord = false;
677
+ for(let index = 0; index < value.length; index += 1){
678
+ const code = value.charCodeAt(index);
679
+ const asciiLetterOrNumber = code >= 0x30 && code <= 0x39 || code >= 0x41 && code <= 0x5a || code >= 0x61 && code <= 0x7a;
680
+ if (asciiLetterOrNumber) {
681
+ if (!inAsciiWord) asciiCount += 1;
682
+ inAsciiWord = true;
678
683
  continue;
679
684
  }
680
- const start = open.get(key)?.pop();
681
- if (!start) {
682
- orphans.push(boundary);
683
- continue;
685
+ inAsciiWord = false;
686
+ if (!(code <= 0x7f) && 0xfffc !== code) return unicodeDocumentWordCount(value);
687
+ }
688
+ return asciiCount;
689
+ }
690
+ function unicodeDocumentWordCount(value) {
691
+ let count = 0;
692
+ for (const _match of value.matchAll(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]|[\p{L}\p{N}]+/gu))count += 1;
693
+ return count;
694
+ }
695
+ function documentTextStatistics(editor) {
696
+ const document1 = editor.state.doc;
697
+ const cached = statisticsByDocument.get(document1);
698
+ if (cached) return cached;
699
+ const source = editor.getText({
700
+ blockSeparator: '\n'
701
+ });
702
+ let characterCountWithSpaces = 0;
703
+ let characterCountWithoutSpaces = 0;
704
+ for(let index = 0; index < source.length; index += 1){
705
+ const codePoint = source.codePointAt(index);
706
+ if (void 0 !== codePoint) {
707
+ if (codePoint > 0xffff) index += 1;
708
+ if (codePoint !== "".codePointAt(0) && 0x0a !== codePoint && 0x0d !== codePoint) {
709
+ characterCountWithSpaces += 1;
710
+ if (!isEcmaScriptWhitespace(codePoint)) characterCountWithoutSpaces += 1;
711
+ }
684
712
  }
685
- pairs.push({
686
- id: start.dataset.bookmarkId,
687
- name: start.dataset.bookmarkName,
688
- nativeId: normalizeDocumentBookmarkNativeId(start.dataset.officeBookmarkId) ?? void 0,
689
- start,
690
- end: boundary
691
- });
692
713
  }
693
- for (const stack of open.values())orphans.push(...stack);
694
- return {
695
- pairs,
696
- orphans
714
+ let paragraphCount = 0;
715
+ document1.descendants((node)=>{
716
+ if (node.type.name === DOCUMENT_LAZY_BLOCK_NODE) {
717
+ const logicalParagraphs = Number(node.attrs.paragraphCount);
718
+ paragraphCount += Number.isSafeInteger(logicalParagraphs) && logicalParagraphs >= 0 ? logicalParagraphs : 0;
719
+ return false;
720
+ }
721
+ if (node.isTextblock) paragraphCount += 1;
722
+ return true;
723
+ });
724
+ const statistics = {
725
+ characterCountWithSpaces,
726
+ characterCountWithoutSpaces,
727
+ paragraphCount,
728
+ wordCount: documentWordCount(source)
697
729
  };
730
+ statisticsByDocument.set(document1, statistics);
731
+ return statistics;
698
732
  }
699
- function domBookmarkPairKey(element) {
700
- const id = element.dataset.bookmarkId?.trim();
701
- return id ? `id:${id}` : `legacy:${element.dataset.bookmarkName ?? ''}:${element.dataset.officeBookmarkId ?? ''}`;
733
+ function transferDocumentTextStatistics(previous, next) {
734
+ const statistics = statisticsByDocument.get(previous);
735
+ if (statistics) statisticsByDocument.set(next, statistics);
702
736
  }
703
- function applyBookmarkIdentityToElement(element, identity, kind) {
704
- element.dataset.documentBookmarkBoundary = 'true';
705
- element.dataset.bookmarkKind = kind;
706
- element.dataset.bookmarkId = identity.id;
707
- element.dataset.bookmarkName = identity.name;
708
- element.dataset.officeBookmarkId = String(identity.nativeId);
709
- element.classList.add('work-document-bookmark-boundary', kind);
710
- element.contentEditable = 'false';
711
- element.setAttribute('aria-hidden', 'true');
712
- if ('start' === kind) element.id = identity.name;
713
- else element.removeAttribute('id');
737
+ function transferChangedDocumentTextStatistics(previous, next, changes) {
738
+ const cached = statisticsByDocument.get(previous);
739
+ if (!cached || !changes.length) return false;
740
+ const statistics = {
741
+ ...cached
742
+ };
743
+ for (const { after, before } of changes){
744
+ applyDocumentTextStatisticsDelta(statistics, simpleDocumentSubtreeStatistics(before), -1);
745
+ applyDocumentTextStatisticsDelta(statistics, simpleDocumentSubtreeStatistics(after), 1);
746
+ }
747
+ if (Object.values(statistics).some((value)=>value < 0)) return false;
748
+ statisticsByDocument.set(next, statistics);
749
+ return true;
714
750
  }
715
- function synchronizeDomInternalLinks(root, names) {
716
- for (const link of root.querySelectorAll('a[href^="#"]')){
717
- const target = (link.getAttribute('href') ?? '').slice(1).toLowerCase();
718
- link.classList.toggle(MISSING_LINK_CLASS, !names.has(target));
719
- if (!link.className) link.removeAttribute('class');
751
+ function simpleDocumentSubtreeStatistics(root) {
752
+ const statistics = {
753
+ characterCountWithSpaces: 0,
754
+ characterCountWithoutSpaces: 0,
755
+ paragraphCount: 0,
756
+ wordCount: 0
757
+ };
758
+ const visit = (node)=>{
759
+ if (node.type.name === DOCUMENT_LAZY_BLOCK_NODE) {
760
+ accumulateDocumentTextStatistics(statistics, node.textContent);
761
+ const logicalParagraphs = Number(node.attrs.paragraphCount);
762
+ statistics.paragraphCount += Number.isSafeInteger(logicalParagraphs) && logicalParagraphs >= 0 ? logicalParagraphs : 0;
763
+ return;
764
+ }
765
+ if (node.isTextblock) {
766
+ accumulateDocumentTextStatistics(statistics, node.textContent);
767
+ statistics.paragraphCount += 1;
768
+ return;
769
+ }
770
+ node.forEach(visit);
771
+ };
772
+ visit(root);
773
+ return statistics;
774
+ }
775
+ function accumulateDocumentTextStatistics(statistics, source) {
776
+ for(let index = 0; index < source.length; index += 1){
777
+ const codePoint = source.codePointAt(index);
778
+ if (void 0 !== codePoint) {
779
+ if (codePoint > 0xffff) index += 1;
780
+ if (codePoint !== "".codePointAt(0) && 0x0a !== codePoint && 0x0d !== codePoint) {
781
+ statistics.characterCountWithSpaces += 1;
782
+ if (!isEcmaScriptWhitespace(codePoint)) statistics.characterCountWithoutSpaces += 1;
783
+ }
784
+ }
720
785
  }
786
+ statistics.wordCount += documentWordCount(source);
721
787
  }
722
- function normalizedClass(value) {
723
- return 'string' == typeof value ? value.trim().split(/\s+/).filter(Boolean).join(' ') : '';
788
+ function applyDocumentTextStatisticsDelta(target, value, direction) {
789
+ target.characterCountWithSpaces += direction * value.characterCountWithSpaces;
790
+ target.characterCountWithoutSpaces += direction * value.characterCountWithoutSpaces;
791
+ target.paragraphCount += direction * value.paragraphCount;
792
+ target.wordCount += direction * value.wordCount;
724
793
  }
725
- function toggleClassToken(value, token, enabled) {
726
- const tokens = new Set(normalizedClass(value).split(' ').filter(Boolean));
727
- if (enabled) tokens.add(token);
728
- else tokens.delete(token);
729
- return Array.from(tokens).join(' ');
794
+ function isEcmaScriptWhitespace(codePoint) {
795
+ return codePoint >= 0x09 && codePoint <= 0x0d || 0x20 === codePoint || 0xa0 === codePoint || 0x1680 === codePoint || codePoint >= 0x2000 && codePoint <= 0x200a || 0x2028 === codePoint || 0x2029 === codePoint || 0x202f === codePoint || 0x205f === codePoint || 0x3000 === codePoint || 0xfeff === codePoint;
730
796
  }
731
- const CAPTION_SELECTOR = 'figcaption[data-document-caption]';
732
- const REFERENCE_SELECTOR = 'span[data-document-cross-reference]';
733
- function normalizeDocumentCaptionsHtml(source) {
734
- const document1 = new DOMParser().parseFromString(source, 'text/html');
735
- const targets = normalizeCaptions(document1);
736
- normalizeReferences(document1, new Map(targets.map((target)=>[
737
- target.id,
738
- target
739
- ])));
740
- return document1.body.innerHTML;
797
+ const FIELD_SELECTOR = 'span[data-document-field]';
798
+ const FIELD_TEXT_BOUNDARY = '\uFFFC';
799
+ const FIELD_COMMANDS = {
800
+ page: 'PAGE',
801
+ numPages: 'NUMPAGES',
802
+ section: 'SECTION',
803
+ sectionPages: 'SECTIONPAGES',
804
+ date: 'DATE \\@ "yyyy年M月d日"',
805
+ time: 'TIME \\@ "HH:mm"',
806
+ wordCount: 'NUMWORDS',
807
+ characterCount: 'NUMCHARS',
808
+ pageReference: 'PAGEREF'
809
+ };
810
+ const FIELD_LABELS = {
811
+ page: '当前页码',
812
+ numPages: '总页数',
813
+ section: '当前节号',
814
+ sectionPages: '本节页数',
815
+ date: '当前日期',
816
+ time: '当前时间',
817
+ wordCount: '字数',
818
+ characterCount: '字符数',
819
+ pageReference: '目标页码'
820
+ };
821
+ function documentFieldKind(value) {
822
+ if ('page' === value || 'numPages' === value || 'section' === value || 'sectionPages' === value || 'date' === value || 'time' === value || 'wordCount' === value || 'characterCount' === value || 'pageReference' === value) return value;
823
+ return null;
741
824
  }
742
- function documentCaptionKind(value) {
743
- return 'figure' === value || 'table' === value ? value : null;
825
+ function docxDocumentFieldKind(instruction) {
826
+ const command = /^\s*([a-z][a-z0-9]*)\b/i.exec(instruction)?.[1]?.toUpperCase();
827
+ if ('PAGE' === command) return 'page';
828
+ if ('NUMPAGES' === command) return 'numPages';
829
+ if ('SECTION' === command) return 'section';
830
+ if ('SECTIONPAGES' === command) return 'sectionPages';
831
+ if ('DATE' === command) return 'date';
832
+ if ('TIME' === command) return 'time';
833
+ if ('NUMWORDS' === command) return 'wordCount';
834
+ if ('NUMCHARS' === command) return 'characterCount';
835
+ if ('PAGEREF' === command) return 'pageReference';
836
+ return null;
744
837
  }
745
- function documentCaptionLabel(kind) {
746
- return 'table' === kind ? '' : '图';
838
+ function documentFieldInstruction(kind, options = {}) {
839
+ if ('pageReference' === kind) return documentPageReferenceInstruction(options.targetName, '', true);
840
+ return FIELD_COMMANDS[kind];
747
841
  }
748
- function documentCaptionDisplay(kind, number) {
749
- return `${documentCaptionLabel(kind)} ${positiveInteger(number)}`;
842
+ function documentPageReferenceInstruction(targetName, source = '', defaultHyperlink = false) {
843
+ const target = normalizeFieldTarget(targetName);
844
+ if (!target) return FIELD_COMMANDS.pageReference;
845
+ const switches = [];
846
+ if (/(?:^|\s)\\h(?:\s|$)/i.test(source)) switches.push('\\h');
847
+ if (/(?:^|\s)\\\*\s+MERGEFORMAT(?:\s|$)/i.test(source)) switches.push('\\* MERGEFORMAT');
848
+ if (!switches.length && defaultHyperlink) switches.push('\\h');
849
+ return `PAGEREF ${target}${switches.length ? ` ${switches.join(' ')}` : ''}`;
750
850
  }
751
- function normalizeCaptions(document1) {
752
- const counters = {
753
- figure: 0,
754
- table: 0
755
- };
756
- const usedIds = new Set();
757
- return Array.from(document1.body.querySelectorAll(CAPTION_SELECTOR)).map((element, index)=>{
758
- const kind = documentCaptionKind(element.dataset.captionKind) ?? 'figure';
759
- counters[kind] += 1;
760
- const number = counters[kind];
761
- const id = uniqueCaptionId(element.dataset.captionId, kind, index + 1, usedIds);
762
- const label = documentCaptionLabel(kind);
763
- element.dataset.documentCaption = 'true';
764
- element.dataset.captionId = id;
765
- element.dataset.captionKind = kind;
766
- element.dataset.captionNumber = String(number);
767
- element.dataset.captionLabel = label;
768
- element.classList.add('work-document-caption');
769
- const title = element.textContent?.replace(/\s+/g, ' ').trim() ?? '';
770
- const display = documentCaptionDisplay(kind, number);
771
- element.setAttribute('aria-label', title ? `${display} ${title}` : display);
772
- return {
773
- id,
774
- kind,
775
- number,
776
- label,
777
- title,
778
- display
779
- };
780
- });
851
+ function documentFieldLabel(kind) {
852
+ return FIELD_LABELS[kind];
781
853
  }
782
- function normalizeReferences(document1, targets) {
783
- for (const element of Array.from(document1.body.querySelectorAll(REFERENCE_SELECTOR))){
784
- if ('bookmark' === element.dataset.referenceTargetType) continue;
785
- const id = element.dataset.referenceTargetId?.trim() ?? '';
786
- const target = targets.get(id);
787
- element.dataset.documentCrossReference = 'true';
788
- element.dataset.referenceTargetId = id;
789
- element.classList.add('work-document-cross-reference');
790
- if (!target) {
791
- element.dataset.referenceOrphaned = 'true';
792
- element.textContent = '引用缺失';
854
+ function documentFieldDisplay(kind, context, instruction = documentFieldInstruction(kind), cachedValue = '') {
855
+ if ('page' === kind) return String(positiveInteger(context.pageNumber));
856
+ if ('numPages' === kind) return String(positiveInteger(context.totalPages));
857
+ if ('section' === kind) return String(positiveInteger(context.sectionNumber));
858
+ if ('sectionPages' === kind) return String(positiveInteger(context.sectionPages));
859
+ if ('wordCount' === kind) return String(nonNegativeInteger(context.wordCount, cachedValue));
860
+ if ('characterCount' === kind) return String(nonNegativeInteger(context.characterCount, cachedValue));
861
+ if ('pageReference' === kind) {
862
+ const target = docxDocumentFieldTarget(instruction);
863
+ const hasResolutionContext = void 0 !== context.referencePageNumber || void 0 !== context.bookmarkPageNumbers;
864
+ const page = context.referencePageNumber ?? (target ? context.bookmarkPageNumbers?.get(`name:${target.toLowerCase()}`) : void 0);
865
+ return page && Number.isSafeInteger(page) && page > 0 ? String(page) : hasResolutionContext ? '引用缺失' : cachedValue.trim() || '引用缺失';
866
+ }
867
+ const now = validDate(context.now) ?? new Date();
868
+ const format = dateFormatSwitch(instruction) ?? ('date' === kind ? 'yyyy年M月d日' : 'HH:mm');
869
+ const display = formatWordDate(now, format);
870
+ return display || cachedValue || documentFieldLabel(kind);
871
+ }
872
+ function normalizeDocumentFieldsHtml(source) {
873
+ const document1 = new DOMParser().parseFromString(source, 'text/html');
874
+ const usedIds = new Set();
875
+ for (const [index, element] of Array.from(document1.body.querySelectorAll(FIELD_SELECTOR)).entries()){
876
+ const instruction = element.dataset.fieldInstruction?.trim() ?? '';
877
+ const kind = documentFieldKind(element.dataset.fieldKind) ?? docxDocumentFieldKind(instruction);
878
+ if (!kind) {
879
+ element.replaceWith(document1.createTextNode(element.textContent ?? ''));
793
880
  continue;
794
881
  }
795
- delete element.dataset.referenceOrphaned;
796
- element.dataset.captionKind = target.kind;
797
- element.dataset.captionNumber = String(target.number);
798
- element.dataset.captionLabel = target.label;
799
- element.textContent = target.display;
882
+ const display = element.dataset.fieldDisplay?.trim() || element.textContent?.trim() || documentFieldLabel(kind);
883
+ element.dataset.documentField = 'true';
884
+ element.dataset.fieldId = uniqueFieldId(element.dataset.fieldId, index + 1, usedIds);
885
+ element.dataset.fieldKind = kind;
886
+ element.dataset.fieldInstruction = instruction || documentFieldInstruction(kind);
887
+ if ('pageReference' === kind) {
888
+ const targetName = normalizeFieldTarget(element.dataset.fieldTargetName) ?? docxDocumentFieldTarget(instruction);
889
+ if (targetName) {
890
+ element.dataset.fieldTargetName = targetName;
891
+ element.dataset.fieldInstruction = documentPageReferenceInstruction(targetName, instruction);
892
+ delete element.dataset.fieldOrphaned;
893
+ } else {
894
+ delete element.dataset.fieldTargetName;
895
+ element.dataset.fieldOrphaned = 'true';
896
+ }
897
+ }
898
+ element.dataset.fieldDisplay = display;
899
+ element.classList.add('work-document-field');
900
+ element.textContent = display;
800
901
  }
902
+ return document1.body.innerHTML;
801
903
  }
802
- function uniqueCaptionId(source, kind, index, usedIds) {
904
+ function resolveDocumentFieldsHtml(source, context) {
905
+ const document1 = new DOMParser().parseFromString(normalizeDocumentFieldsHtml(source), 'text/html');
906
+ for (const element of Array.from(document1.body.querySelectorAll(FIELD_SELECTOR))){
907
+ const kind = documentFieldKind(element.dataset.fieldKind);
908
+ if (!kind) continue;
909
+ const referencePageNumber = 'pageReference' === kind ? context.referencePageNumber ?? (element.dataset.fieldTargetId ? context.bookmarkPageNumbers?.get(`id:${element.dataset.fieldTargetId.trim()}`) : void 0) : context.referencePageNumber;
910
+ const display = documentFieldDisplay(kind, {
911
+ ...context,
912
+ referencePageNumber
913
+ }, element.dataset.fieldInstruction, element.dataset.fieldDisplay);
914
+ element.dataset.fieldDisplay = display;
915
+ element.textContent = display;
916
+ }
917
+ return document1.body.innerHTML;
918
+ }
919
+ function uniqueFieldId(source, index, usedIds) {
803
920
  const candidate = source?.trim();
804
921
  if (candidate && !usedIds.has(candidate)) {
805
922
  usedIds.add(candidate);
806
923
  return candidate;
807
924
  }
808
925
  let suffix = index;
809
- while(usedIds.has(`document-${kind}-caption-${suffix}`))suffix += 1;
810
- const id = `document-${kind}-caption-${suffix}`;
926
+ while(usedIds.has(`document-field-${suffix}`))suffix += 1;
927
+ const id = `document-field-${suffix}`;
811
928
  usedIds.add(id);
812
929
  return id;
813
930
  }
931
+ function dateFormatSwitch(instruction) {
932
+ return /\\@\s+"([^"]+)"/i.exec(instruction)?.[1] ?? null;
933
+ }
934
+ function docxDocumentFieldTarget(instruction) {
935
+ const kind = docxDocumentFieldKind(instruction);
936
+ if ('pageReference' !== kind) return null;
937
+ return normalizeFieldTarget(/^\s*PAGEREF\s+([^\s\\]+)/i.exec(instruction)?.[1]);
938
+ }
939
+ function supportedDocxDocumentFieldInstruction(instruction) {
940
+ const kind = docxDocumentFieldKind(instruction);
941
+ if (!kind) return false;
942
+ const source = instruction.trim();
943
+ if ('pageReference' === kind) {
944
+ const match = /^PAGEREF\s+([^\s\\]+)([\s\S]*)$/i.exec(source);
945
+ const target = normalizeFieldTarget(match?.[1]);
946
+ if (!target || !match) return false;
947
+ let rest = match[2] ?? '';
948
+ const hyperlink = /^\s+\\h\b/i.exec(rest);
949
+ if (hyperlink) rest = rest.slice(hyperlink[0].length);
950
+ return onlyMergeFormatSwitch(rest);
951
+ }
952
+ if ('date' === kind || 'time' === kind) {
953
+ const command = 'date' === kind ? 'DATE' : 'TIME';
954
+ const match = new RegExp(`^${command}\\b([\\s\\S]*)$`, 'i').exec(source);
955
+ if (!match) return false;
956
+ let rest = match[1] ?? '';
957
+ const format = /^\s+\\@\s+"[^"\r\n]{1,128}"/i.exec(rest);
958
+ if (format) rest = rest.slice(format[0].length);
959
+ return onlyMergeFormatSwitch(rest);
960
+ }
961
+ const command = FIELD_COMMANDS[kind];
962
+ const match = new RegExp(`^${command}\\b([\\s\\S]*)$`, 'i').exec(source);
963
+ return Boolean(match && onlyMergeFormatSwitch(match[1] ?? ''));
964
+ }
965
+ function onlyMergeFormatSwitch(source) {
966
+ return /^(?:\s+\\\*\s+MERGEFORMAT)?\s*$/i.test(source);
967
+ }
968
+ function documentFieldStatisticsFromText(source) {
969
+ const normalized = source.replace(/\r\n?/g, '\n');
970
+ let characterCount = 0;
971
+ for(let index = 0; index < normalized.length; index += 1){
972
+ const codePoint = normalized.codePointAt(index);
973
+ if (void 0 !== codePoint) {
974
+ if (codePoint > 0xffff) index += 1;
975
+ if (0x0a !== codePoint && 0xfffc !== codePoint) characterCount += 1;
976
+ }
977
+ }
978
+ return {
979
+ wordCount: documentWordCount(normalized),
980
+ characterCount
981
+ };
982
+ }
983
+ function documentFieldStatisticsFromHtml(source) {
984
+ const document1 = new DOMParser().parseFromString(source, 'text/html');
985
+ for (const field of Array.from(document1.body.querySelectorAll('[data-document-field]')))field.replaceWith(document1.createTextNode(FIELD_TEXT_BOUNDARY));
986
+ return documentFieldStatisticsFromText(document1.body.textContent ?? '');
987
+ }
988
+ function formatWordDate(date, format) {
989
+ const hour12 = date.getHours() % 12 || 12;
990
+ const replacements = {
991
+ 'AM/PM': date.getHours() < 12 ? 'AM' : 'PM',
992
+ 'am/pm': date.getHours() < 12 ? 'am' : 'pm',
993
+ yyyy: String(date.getFullYear()).padStart(4, '0'),
994
+ yy: String(date.getFullYear() % 100).padStart(2, '0'),
995
+ MMMM: new Intl.DateTimeFormat('zh-CN', {
996
+ month: 'long'
997
+ }).format(date),
998
+ MMM: new Intl.DateTimeFormat('zh-CN', {
999
+ month: 'short'
1000
+ }).format(date),
1001
+ MM: String(date.getMonth() + 1).padStart(2, '0'),
1002
+ M: String(date.getMonth() + 1),
1003
+ dddd: new Intl.DateTimeFormat('zh-CN', {
1004
+ weekday: 'long'
1005
+ }).format(date),
1006
+ ddd: new Intl.DateTimeFormat('zh-CN', {
1007
+ weekday: 'short'
1008
+ }).format(date),
1009
+ dd: String(date.getDate()).padStart(2, '0'),
1010
+ d: String(date.getDate()),
1011
+ HH: String(date.getHours()).padStart(2, '0'),
1012
+ H: String(date.getHours()),
1013
+ hh: String(hour12).padStart(2, '0'),
1014
+ h: String(hour12),
1015
+ mm: String(date.getMinutes()).padStart(2, '0'),
1016
+ m: String(date.getMinutes()),
1017
+ ss: String(date.getSeconds()).padStart(2, '0'),
1018
+ s: String(date.getSeconds())
1019
+ };
1020
+ return format.replace(/AM\/PM|am\/pm|yyyy|MMMM|dddd|MMM|ddd|yy|MM|dd|HH|hh|mm|ss|M|d|H|h|m|s/g, (token)=>replacements[token] ?? token);
1021
+ }
814
1022
  function positiveInteger(value) {
815
- const number = Number(value);
816
- return Number.isSafeInteger(number) && number > 0 ? number : 1;
1023
+ return Number.isSafeInteger(value) && value > 0 ? value : 1;
817
1024
  }
818
- const CITATION_SELECTOR = 'span[data-document-citation]';
819
- const BIBLIOGRAPHY_SELECTOR = 'section[data-document-bibliography]';
820
- const STYLE_DETAILS = {
821
- apa: {
822
- name: 'APA',
823
- selectedStyle: '\\APASixthEditionOfficeOnline.xsl'
1025
+ function nonNegativeInteger(value, cachedValue) {
1026
+ if (void 0 !== value && Number.isSafeInteger(value) && value >= 0) return value;
1027
+ const cached = Number(cachedValue.trim());
1028
+ return Number.isSafeInteger(cached) && cached >= 0 ? cached : 0;
1029
+ }
1030
+ function normalizeFieldTarget(value) {
1031
+ if ('string' != typeof value) return null;
1032
+ const target = value.trim();
1033
+ return /^[\p{L}_][\p{L}\p{N}_]*$/u.test(target) ? target : null;
1034
+ }
1035
+ function validDate(value) {
1036
+ return value && Number.isFinite(value.getTime()) ? value : null;
1037
+ }
1038
+ const BOOKMARK_NAME_PATTERN = /^[\p{L}_][\p{L}\p{N}_]*$/u;
1039
+ const BOOKMARK_UI_NAME_PATTERN = /^[\p{L}][\p{L}\p{N}_]*$/u;
1040
+ const MAX_BOOKMARK_NAME_LENGTH = 40;
1041
+ const MAX_BOOKMARK_NATIVE_ID = 0x7fffffff;
1042
+ const MISSING_LINK_CLASS = 'work-document-link-missing';
1043
+ const DOCUMENT_BOOKMARK_VALIDATION_MESSAGE = '书签名称需以字母开头,只能包含字母、数字和下划线,且不超过 40 个字符。';
1044
+ const DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE = '文档中已存在同名书签。';
1045
+ const DocumentBookmarkBoundary = core_Node.create({
1046
+ name: 'documentBookmarkBoundary',
1047
+ inline: true,
1048
+ group: 'inline',
1049
+ atom: true,
1050
+ selectable: false,
1051
+ addCommands () {
1052
+ return {
1053
+ insertDocumentBookmark: (name)=>(props)=>insertDocumentBookmarkCommand(props, name),
1054
+ deleteDocumentBookmark: (id)=>(props)=>deleteDocumentBookmarkCommand(props, id)
1055
+ };
824
1056
  },
825
- mla: {
826
- name: 'MLA',
827
- selectedStyle: '\\MLASeventhEditionOfficeOnline.xsl'
1057
+ addProseMirrorPlugins () {
1058
+ return [
1059
+ createDocumentBookmarkPlugin(this.name)
1060
+ ];
828
1061
  },
829
- chicago: {
830
- name: 'Chicago',
831
- selectedStyle: '\\CHICAGO.XSL'
1062
+ addAttributes () {
1063
+ return {
1064
+ id: {
1065
+ default: '',
1066
+ parseHTML: (element)=>element.dataset.bookmarkId ?? '',
1067
+ renderHTML: ()=>({})
1068
+ },
1069
+ name: {
1070
+ default: '',
1071
+ parseHTML: (element)=>element.dataset.bookmarkName ?? '',
1072
+ renderHTML: ()=>({})
1073
+ },
1074
+ nativeId: {
1075
+ default: null,
1076
+ parseHTML: (element)=>normalizeDocumentBookmarkNativeId(element.dataset.officeBookmarkId),
1077
+ renderHTML: ()=>({})
1078
+ },
1079
+ kind: {
1080
+ default: 'start',
1081
+ parseHTML: (element)=>documentBookmarkBoundaryKind(element.dataset.bookmarkKind),
1082
+ renderHTML: ()=>({})
1083
+ }
1084
+ };
832
1085
  },
833
- ieee: {
834
- name: 'IEEE',
835
- selectedStyle: '\\IEEE.XSL'
1086
+ parseHTML () {
1087
+ return [
1088
+ {
1089
+ tag: 'span[data-document-bookmark-boundary]',
1090
+ getAttrs: (node)=>{
1091
+ if (!(node instanceof HTMLElement)) return false;
1092
+ return {
1093
+ id: node.dataset.bookmarkId ?? '',
1094
+ name: node.dataset.bookmarkName ?? '',
1095
+ nativeId: normalizeDocumentBookmarkNativeId(node.dataset.officeBookmarkId),
1096
+ kind: documentBookmarkBoundaryKind(node.dataset.bookmarkKind)
1097
+ };
1098
+ }
1099
+ }
1100
+ ];
1101
+ },
1102
+ renderHTML ({ node, HTMLAttributes }) {
1103
+ const name = normalizeDocumentBookmarkName(node.attrs.name) ?? '';
1104
+ const nativeId = normalizeDocumentBookmarkNativeId(node.attrs.nativeId);
1105
+ const kind = documentBookmarkBoundaryKind(node.attrs.kind);
1106
+ return [
1107
+ 'span',
1108
+ mergeAttributes(HTMLAttributes, {
1109
+ ...'start' === kind && name ? {
1110
+ id: name
1111
+ } : {},
1112
+ 'data-document-bookmark-boundary': 'true',
1113
+ 'data-bookmark-kind': kind,
1114
+ 'data-bookmark-id': 'string' == typeof node.attrs.id ? node.attrs.id : '',
1115
+ 'data-bookmark-name': name,
1116
+ 'data-office-bookmark-id': null === nativeId ? void 0 : String(nativeId),
1117
+ class: `work-document-bookmark-boundary ${kind}`,
1118
+ contenteditable: 'false',
1119
+ 'aria-hidden': 'true'
1120
+ })
1121
+ ];
1122
+ },
1123
+ renderText () {
1124
+ return '';
836
1125
  }
837
- };
838
- function createDocumentBibliography(style = 'apa') {
839
- return {
840
- style,
841
- styleName: STYLE_DETAILS[style].name,
842
- selectedStyle: STYLE_DETAILS[style].selectedStyle,
843
- sources: []
844
- };
845
- }
846
- function documentCitationStyle(value) {
847
- if ('mla' === value || 'chicago' === value || 'ieee' === value) return value;
848
- return 'apa';
1126
+ });
1127
+ function validateDocumentBookmarkName(value) {
1128
+ const name = value.trim();
1129
+ return BOOKMARK_UI_NAME_PATTERN.test(name) && Array.from(name).length <= MAX_BOOKMARK_NAME_LENGTH ? null : DOCUMENT_BOOKMARK_VALIDATION_MESSAGE;
849
1130
  }
850
- function documentCitationStyleDetails(style) {
851
- return STYLE_DETAILS[style];
1131
+ function normalizeDocumentBookmarkName(value) {
1132
+ if ('string' != typeof value) return null;
1133
+ const name = value.trim();
1134
+ return BOOKMARK_NAME_PATTERN.test(name) && Array.from(name).length <= MAX_BOOKMARK_NAME_LENGTH ? name : null;
852
1135
  }
853
- function documentCitationTagsFromInstruction(instruction) {
854
- const primary = /^\s*CITATION\s+(?:"([^"]+)"|([^\s\\]+))/i.exec(instruction);
855
- if (!primary) return [];
856
- const tags = [
857
- primary[1] || primary[2]
858
- ];
859
- const additional = /\\m\s+(?:"([^"]+)"|([^\s\\]+))/gi;
860
- for (const match of instruction.matchAll(additional))tags.push(match[1] || match[2]);
861
- return uniqueCitationTags(tags);
1136
+ function normalizeDocumentBookmarkNativeId(value) {
1137
+ if (null == value || '' === value || 'string' == typeof value && !value.trim()) return null;
1138
+ const number = 'number' == typeof value ? value : Number(value);
1139
+ return Number.isSafeInteger(number) && number >= 0 && number <= MAX_BOOKMARK_NATIVE_ID ? number : null;
862
1140
  }
863
- function documentCitationInstruction(tags) {
864
- const normalized = uniqueCitationTags(tags);
865
- if (!normalized.length) return '';
866
- return [
867
- 'CITATION',
868
- citationTagInstructionValue(normalized[0]),
869
- ...normalized.slice(1).flatMap((tag)=>[
870
- '\\m',
871
- citationTagInstructionValue(tag)
872
- ]),
873
- '\\l',
874
- '2052'
875
- ].join(' ');
1141
+ function editorDocumentBookmarks(editor) {
1142
+ return collectDocumentBookmarkPairs(editor.state.doc, 'documentBookmarkBoundary').pairs.map(({ id, name, nativeId, from, to })=>({
1143
+ id,
1144
+ name,
1145
+ nativeId,
1146
+ from,
1147
+ to
1148
+ }));
876
1149
  }
877
- function renameDocumentCitationTagInInstruction(instruction, previousTag, nextTag) {
878
- return instruction.replace(/(^\s*CITATION\s+|\\m\s+)("[^"]+"|[^\s\\]+)/gi, (match, prefix, source)=>{
879
- const tag = source.startsWith('"') ? source.slice(1, -1) : source;
880
- return tag === previousTag ? `${prefix}${citationTagInstructionValue(nextTag)}` : match;
1150
+ function editorDocumentBookmarkReferenceTargets(editor) {
1151
+ return collectDocumentBookmarkPairs(editor.state.doc, 'documentBookmarkBoundary').pairs.map((bookmark)=>{
1152
+ const display = documentBookmarkReferenceDisplay(editor.state.doc, bookmark);
1153
+ return {
1154
+ type: 'bookmark',
1155
+ id: bookmark.id,
1156
+ name: bookmark.name,
1157
+ title: display,
1158
+ display,
1159
+ instruction: documentBookmarkReferenceInstruction(bookmark.name)
1160
+ };
881
1161
  });
882
1162
  }
883
- function documentCitationTags(value) {
884
- return uniqueCitationTags((value ?? '').split(/\s+/));
885
- }
886
- function isValidDocumentCitationTag(value) {
887
- return /^[A-Za-z0-9_:.+-]{1,80}$/.test(value);
1163
+ function activeDocumentBookmark(editor) {
1164
+ const { from, to } = editor.state.selection;
1165
+ const matches = editorDocumentBookmarks(editor).filter((bookmark)=>from >= bookmark.from + 1 && to <= bookmark.to);
1166
+ return matches.sort((left, right)=>left.to - left.from - (right.to - right.from))[0] ?? null;
888
1167
  }
889
- function resolveDocumentCitation(tags, bibliography, instruction = '', cachedValue = '') {
890
- const normalized = uniqueCitationTags(tags);
891
- if (!normalized.length) return {
892
- text: cachedValue || '缺失引文',
893
- orphaned: true
894
- };
895
- const sources = new Map((bibliography?.sources ?? []).map((source)=>[
896
- source.tag,
897
- source
898
- ]));
899
- const missing = normalized.filter((tag)=>!sources.has(tag));
900
- if (missing.length) return {
901
- text: 1 === missing.length ? `缺失引文:${missing[0]}` : `缺失引文:${missing.join('、')}`,
902
- orphaned: true
903
- };
904
- const selected = normalized.flatMap((tag)=>{
905
- const source = sources.get(tag);
906
- return source ? [
907
- source
908
- ] : [];
909
- });
910
- const style = bibliography?.style ?? 'apa';
911
- const suppressAuthor = /(?:^|\s)\\n(?:\s|$)/i.test(instruction);
912
- const suppressYear = /(?:^|\s)\\v(?:\s|$)/i.test(instruction);
913
- const prefix = citationSwitch(instruction, 'f');
914
- const suffix = citationSwitch(instruction, 'p');
915
- const text = 'ieee' === style ? ieeeCitation(selected, bibliography?.sources ?? []) : 'mla' === style ? mlaCitation(selected, suppressAuthor) : 'chicago' === style ? chicagoCitation(selected, suppressAuthor, suppressYear) : apaCitation(selected, suppressAuthor, suppressYear);
916
- return {
917
- text: `${prefix}${text}${suffix}`.trim() || cachedValue || normalized.join('; '),
918
- orphaned: false
919
- };
1168
+ function documentBookmarkNameExists(editor, value, exceptId) {
1169
+ const name = value.trim().toLowerCase();
1170
+ return editorDocumentBookmarks(editor).some((bookmark)=>bookmark.id !== exceptId && bookmark.name.toLowerCase() === name);
920
1171
  }
921
- function normalizeDocumentCitationsHtml(source, bibliography) {
1172
+ function normalizeDocumentBookmarksHtml(source) {
922
1173
  const document1 = new DOMParser().parseFromString(source, 'text/html');
923
- const usedIds = new Set();
924
- for (const [index, element] of Array.from(document1.body.querySelectorAll(CITATION_SELECTOR)).entries()){
925
- const instruction = element.dataset.citationInstruction?.trim() ?? '';
926
- const tags = documentCitationTags(element.dataset.citationTags);
927
- const normalizedTags = tags.length ? tags : documentCitationTagsFromInstruction(instruction);
928
- if (!normalizedTags.length) {
929
- element.replaceWith(document1.createTextNode(element.textContent ?? ''));
930
- continue;
1174
+ const boundaries = Array.from(document1.body.querySelectorAll('span[data-document-bookmark-boundary]'));
1175
+ const collection = collectDomBookmarkPairs(boundaries);
1176
+ for (const orphan of collection.orphans)orphan.remove();
1177
+ const registry = createBookmarkRegistry();
1178
+ for (const pair of collection.pairs){
1179
+ const previous = {
1180
+ id: bookmarkInternalId(pair.id),
1181
+ name: normalizeDocumentBookmarkName(pair.name) ?? ''
1182
+ };
1183
+ const identity = uniqueDocumentBookmarkIdentity(pair, registry);
1184
+ applyBookmarkIdentityToElement(pair.start, identity, 'start');
1185
+ applyBookmarkIdentityToElement(pair.end, identity, 'end');
1186
+ if (previous.id && previous.name && (previous.id !== identity.id || previous.name !== identity.name)) {
1187
+ retargetDomBookmarkReferences(document1.body, pair.start, pair.end, previous, identity);
1188
+ retargetDomPageReferences(document1.body, pair.start, pair.end, previous, identity);
931
1189
  }
932
- const cached = element.dataset.citationDisplay?.trim() || element.textContent?.trim() || '';
933
- const resolved = resolveDocumentCitation(normalizedTags, bibliography, instruction, cached);
934
- element.dataset.documentCitation = 'true';
935
- element.dataset.citationId = uniqueCitationId(element.dataset.citationId, index + 1, usedIds);
936
- element.dataset.citationTags = normalizedTags.join(' ');
937
- element.dataset.citationInstruction = instruction || documentCitationInstruction(normalizedTags);
938
- element.dataset.citationDisplay = resolved.text;
939
- if (resolved.orphaned) element.dataset.citationOrphaned = 'true';
940
- else delete element.dataset.citationOrphaned;
941
- element.classList.add('work-document-citation');
942
- element.textContent = resolved.text;
943
- }
944
- for (const [index, element] of Array.from(document1.body.querySelectorAll(BIBLIOGRAPHY_SELECTOR)).entries()){
945
- if (!bibliography) continue;
946
- const replacement = createBibliographyElement(document1, bibliography, element.dataset.bibliographyId || `document-bibliography-${index + 1}`);
947
- element.replaceWith(replacement);
948
1190
  }
1191
+ synchronizeDomInternalLinks(document1.body, registry.names);
949
1192
  return document1.body.innerHTML;
950
1193
  }
951
- function renderDocumentBibliographyHtml(bibliography, id = 'document-bibliography-1') {
952
- const document1 = new DOMParser().parseFromString('', 'text/html');
953
- document1.body.append(createBibliographyElement(document1, bibliography, id));
954
- return document1.body.innerHTML;
955
- }
956
- function documentBibliographyEntry(source, bibliography, index) {
957
- const people = primaryCitationContributor(source);
958
- const authors = people?.corporate?.trim() || people?.people?.map((person)=>bibliographyPersonName(person, bibliography.style)).join(', ') || '未知作者';
959
- const title = source.title.trim() || 'Untitled';
960
- const year = source.year?.trim() || 'n.d.';
961
- const container = citationContainer(source);
962
- const url = source.url?.trim();
963
- if ('ieee' === bibliography.style) return [
964
- `[${index + 1}] ${authors}, “${title}.”`,
965
- container ? `${container},` : '',
966
- year ? `${year}.` : '',
967
- url ?? ''
968
- ].filter(Boolean).join(' ');
969
- if ('mla' === bibliography.style) return [
970
- `${authors}.`,
971
- `“${title}.”`,
972
- container ? `${container},` : '',
973
- `${year}.`,
974
- url ?? ''
975
- ].filter(Boolean).join(' ');
976
- if ('chicago' === bibliography.style) return [
977
- `${authors}.`,
978
- `${year}.`,
979
- `“${title}.”`,
980
- container ? `${container}.` : '',
981
- url ?? ''
982
- ].filter(Boolean).join(' ');
983
- return [
984
- `${authors}.`,
985
- `(${year}).`,
986
- `${title}.`,
987
- container ? `${container}.` : '',
988
- url ?? ''
989
- ].filter(Boolean).join(' ');
990
- }
991
- function primaryCitationContributor(source) {
992
- return source.contributors?.Author ?? Object.values(source.contributors ?? {})[0];
993
- }
994
- function createBibliographyElement(document1, bibliography, id) {
995
- const section = document1.createElement('section');
996
- section.dataset.documentBibliography = 'true';
997
- section.dataset.bibliographyId = id;
998
- section.dataset.bibliographyStyle = bibliography.style;
999
- section.className = 'work-document-bibliography';
1000
- const heading = document1.createElement('h2');
1001
- heading.textContent = '参考文献';
1002
- section.append(heading);
1003
- if (!bibliography.sources.length) {
1004
- const empty = document1.createElement('p');
1005
- empty.dataset.bibliographyEmpty = 'true';
1006
- empty.textContent = '尚无文献源';
1007
- section.append(empty);
1008
- return section;
1009
- }
1010
- bibliography.sources.forEach((source, index)=>{
1011
- const paragraph = document1.createElement('p');
1012
- paragraph.dataset.bibliographyEntry = source.tag;
1013
- paragraph.textContent = documentBibliographyEntry(source, bibliography, index);
1014
- section.append(paragraph);
1194
+ function insertDocumentBookmarkCommand({ dispatch, editor, state, tr }, value) {
1195
+ const name = value.trim();
1196
+ const boundaryType = editor.schema.nodes.documentBookmarkBoundary;
1197
+ const selection = state.selection;
1198
+ if (!boundaryType || validateDocumentBookmarkName(name) || documentBookmarkNameExists(editor, name) || !selection.$from.parent.inlineContent || !selection.$to.parent.inlineContent) return false;
1199
+ if (!dispatch) return true;
1200
+ const registry = bookmarkRegistryForDocument(state.doc, boundaryType.name);
1201
+ const identity = uniqueDocumentBookmarkIdentity({
1202
+ id: createUniqueBookmarkId(registry.ids),
1203
+ name,
1204
+ nativeId: nextBookmarkNativeId(registry.nativeIds)
1205
+ }, registry);
1206
+ const start = boundaryType.create({
1207
+ ...identity,
1208
+ kind: 'start'
1015
1209
  });
1016
- return section;
1017
- }
1018
- function apaCitation(sources, suppressAuthor, suppressYear) {
1019
- const items = sources.map((source)=>{
1020
- const author = suppressAuthor ? '' : citationAuthor(source, '&');
1021
- const year = suppressYear ? '' : source.year?.trim() || 'n.d.';
1022
- return [
1023
- author,
1024
- year
1025
- ].filter(Boolean).join(', ');
1210
+ const end = boundaryType.create({
1211
+ ...identity,
1212
+ kind: 'end'
1026
1213
  });
1027
- return `(${items.join('; ')})`;
1028
- }
1029
- function mlaCitation(sources, suppressAuthor) {
1030
- return `(${sources.map((source)=>suppressAuthor ? source.year?.trim() || 'n.d.' : citationAuthor(source, 'and')).join('; ')})`;
1031
- }
1032
- function chicagoCitation(sources, suppressAuthor, suppressYear) {
1033
- return `(${sources.map((source)=>[
1034
- suppressAuthor ? '' : citationAuthor(source, 'and'),
1035
- suppressYear ? '' : source.year?.trim() || 'n.d.'
1036
- ].filter(Boolean).join(' ')).join('; ')})`;
1214
+ const from = selection.from;
1215
+ const to = selection.to;
1216
+ tr.insert(to, end);
1217
+ tr.insert(from, start);
1218
+ tr.setSelection(TextSelection.create(tr.doc, from + 1, to + 1));
1219
+ dispatch(tr.scrollIntoView());
1220
+ return true;
1037
1221
  }
1038
- function ieeeCitation(sources, allSources) {
1039
- const indexes = sources.map((source)=>allSources.findIndex((candidate)=>candidate.id === source.id || candidate.tag === source.tag) + 1).filter((index)=>index > 0);
1040
- return `[${indexes.join(', ')}]`;
1222
+ function deleteDocumentBookmarkCommand({ dispatch, editor, state, tr }, id) {
1223
+ const bookmark = collectDocumentBookmarkPairs(state.doc, editor.schema.nodes.documentBookmarkBoundary?.name ?? '').pairs.find((candidate)=>candidate.id === id);
1224
+ if (!bookmark) return false;
1225
+ if (!dispatch) return true;
1226
+ tr.delete(bookmark.to, bookmark.to + bookmark.end.node.nodeSize);
1227
+ tr.delete(bookmark.from, bookmark.from + bookmark.start.node.nodeSize);
1228
+ tr.setSelection(TextSelection.near(tr.doc.resolve(bookmark.from)));
1229
+ dispatch(tr.scrollIntoView());
1230
+ return true;
1041
1231
  }
1042
- function citationAuthor(source, conjunction) {
1043
- const contributor = primaryCitationContributor(source);
1044
- if (contributor?.corporate?.trim()) return contributor.corporate.trim();
1045
- const names = (contributor?.people ?? []).map((person)=>person.last.trim() || person.first.trim()).filter(Boolean);
1046
- if (!names.length) return source.title.trim() || source.tag;
1047
- if (1 === names.length) return names[0];
1048
- if (2 === names.length) return `${names[0]} ${conjunction} ${names[1]}`;
1049
- return `${names[0]} et al.`;
1232
+ function createDocumentBookmarkPlugin(boundaryNodeName) {
1233
+ return new Plugin({
1234
+ view (view) {
1235
+ const transaction = normalizeDocumentBookmarks(view.state, boundaryNodeName);
1236
+ if (transaction) {
1237
+ transaction.setMeta('addToHistory', false);
1238
+ view.dispatch(transaction);
1239
+ }
1240
+ return {};
1241
+ },
1242
+ appendTransaction (transactions, oldState, newState) {
1243
+ if (!transactions.some((transaction)=>transaction.docChanged)) return null;
1244
+ return normalizeDocumentBookmarks(newState, boundaryNodeName, oldState, transactions);
1245
+ }
1246
+ });
1050
1247
  }
1051
- function bibliographyPersonName(person, style) {
1052
- const first = [
1053
- person.first,
1054
- person.middle
1055
- ].filter(Boolean).join(' ').trim();
1056
- const suffix = person.suffix?.trim();
1057
- if ('ieee' === style) {
1058
- const initials = [
1059
- person.first,
1060
- person.middle
1061
- ].filter(Boolean).map((value)=>`${Array.from(value ?? '')[0] ?? ''}.`).join(' ');
1062
- return [
1063
- initials,
1064
- person.last,
1065
- suffix
1066
- ].filter(Boolean).join(' ');
1248
+ function normalizeDocumentBookmarks(state, boundaryNodeName, oldState, transactions = []) {
1249
+ if ('documentBookmarkBoundary' === boundaryNodeName && !documentHasIntegrityFeature(state.doc, 1)) return null;
1250
+ const collection = collectDocumentBookmarkPairs(state.doc, boundaryNodeName);
1251
+ const retained = oldState ? retainedDocumentBookmarkPairs(oldState, state, transactions, boundaryNodeName) : new Set();
1252
+ const ordered = [
1253
+ ...collection.pairs.filter((pair)=>retained.has(pairPositionKey(pair))),
1254
+ ...collection.pairs.filter((pair)=>!retained.has(pairPositionKey(pair)))
1255
+ ];
1256
+ const registry = createBookmarkRegistry();
1257
+ const updates = new Map();
1258
+ const renames = [];
1259
+ const effectivePairs = [];
1260
+ for (const pair of ordered){
1261
+ const identity = uniqueDocumentBookmarkIdentity(pair, registry);
1262
+ if (!sameBookmarkIdentity(pair.start.node.attrs, identity)) updates.set(pair.from, identity);
1263
+ if (!sameBookmarkIdentity(pair.end.node.attrs, identity)) updates.set(pair.to, identity);
1264
+ if (!sameBookmarkIdentity(pair, identity)) {
1265
+ if (pair.id && pair.name && (pair.id !== identity.id || pair.name !== identity.name)) renames.push({
1266
+ from: pair.from,
1267
+ to: pair.to,
1268
+ previousId: pair.id,
1269
+ nextId: identity.id,
1270
+ previousName: pair.name,
1271
+ nextName: identity.name
1272
+ });
1273
+ }
1274
+ effectivePairs.push({
1275
+ ...pair,
1276
+ ...identity
1277
+ });
1067
1278
  }
1068
- return [
1069
- person.last,
1070
- first ? `, ${first}` : '',
1071
- suffix ? `, ${suffix}` : ''
1072
- ].join('');
1279
+ const tr = state.tr;
1280
+ for (const boundary of documentBookmarkBoundaries(state.doc, boundaryNodeName)){
1281
+ const identity = updates.get(boundary.position);
1282
+ if (identity) tr.setNodeMarkup(boundary.position, void 0, {
1283
+ ...boundary.node.attrs,
1284
+ ...identity
1285
+ });
1286
+ }
1287
+ synchronizeInternalLinkMarks(state, tr, effectivePairs, renames);
1288
+ synchronizeDocumentBookmarkReferenceNodes(state, tr, effectivePairs, renames);
1289
+ synchronizeDocumentPageReferenceNodes(state, tr, effectivePairs, renames);
1290
+ for (const orphan of [
1291
+ ...collection.orphans
1292
+ ].sort((left, right)=>right.position - left.position))tr.delete(orphan.position, orphan.position + orphan.node.nodeSize);
1293
+ return tr.docChanged ? tr : null;
1073
1294
  }
1074
- function citationContainer(source) {
1075
- return source.journalName?.trim() || source.publisher?.trim() || source.conferenceName?.trim() || source.institution?.trim() || '';
1295
+ function retainedDocumentBookmarkPairs(oldState, newState, transactions, boundaryNodeName) {
1296
+ const mapping = transactionMapping(transactions);
1297
+ const retained = new Set();
1298
+ const current = collectDocumentBookmarkPairs(newState.doc, boundaryNodeName).pairs;
1299
+ const currentById = new Map();
1300
+ for (const pair of current){
1301
+ const matches = currentById.get(pair.id) ?? [];
1302
+ matches.push(pair);
1303
+ currentById.set(pair.id, matches);
1304
+ }
1305
+ for (const previous of collectDocumentBookmarkPairs(oldState.doc, boundaryNodeName).pairs){
1306
+ const mappedFrom = mapping.mapResult(previous.from, 1);
1307
+ const mappedTo = mapping.mapResult(previous.to, 1);
1308
+ const exact = current.find((pair)=>pair.from === mappedFrom.pos && pair.to === mappedTo.pos && pair.id === previous.id);
1309
+ if (exact) {
1310
+ retained.add(pairPositionKey(exact));
1311
+ continue;
1312
+ }
1313
+ const sameIdentity = currentById.get(previous.id);
1314
+ if (sameIdentity?.length === 1) retained.add(pairPositionKey(sameIdentity[0]));
1315
+ }
1316
+ return retained;
1076
1317
  }
1077
- function citationSwitch(instruction, name) {
1078
- const expression = new RegExp(`\\\\${name}\\s+"([^"]*)"`, 'i');
1079
- return expression.exec(instruction)?.[1] ?? '';
1318
+ function synchronizeInternalLinkMarks(state, tr, bookmarks, renames) {
1319
+ const linkType = state.schema.marks.link;
1320
+ if (!linkType) return;
1321
+ const names = new Set(bookmarks.map((bookmark)=>bookmark.name.toLowerCase()));
1322
+ state.doc.descendants((node, position)=>{
1323
+ if (!node.isText) return;
1324
+ const link = node.marks.find((mark)=>mark.type === linkType);
1325
+ if (!link) return;
1326
+ const href = 'string' == typeof link.attrs.href ? link.attrs.href : '';
1327
+ if (!href.startsWith('#')) return;
1328
+ const target = href.slice(1);
1329
+ const rename = renames.filter((candidate)=>position > candidate.from && position < candidate.to && target.toLowerCase() === candidate.previousName.toLowerCase()).sort((left, right)=>left.to - left.from - (right.to - right.from))[0];
1330
+ const nextHref = rename ? `#${rename.nextName}` : href;
1331
+ const nextTarget = nextHref.slice(1).toLowerCase();
1332
+ const nextClass = toggleClassToken(link.attrs.class, MISSING_LINK_CLASS, !names.has(nextTarget));
1333
+ if (nextHref === href && nextClass === normalizedClass(link.attrs.class)) return;
1334
+ replaceLinkMark(state, tr, node, position, link, {
1335
+ ...link.attrs,
1336
+ href: nextHref,
1337
+ class: nextClass || null
1338
+ });
1339
+ });
1080
1340
  }
1081
- function citationTagInstructionValue(tag) {
1082
- return /^[A-Za-z0-9_:.+-]+$/.test(tag) ? tag : `"${tag.replaceAll('"', '')}"`;
1341
+ function replaceLinkMark(_state, tr, node, position, link, attributes) {
1342
+ tr.removeMark(position, position + node.nodeSize, link.type);
1343
+ tr.addMark(position, position + node.nodeSize, link.type.create(attributes));
1083
1344
  }
1084
- function uniqueCitationTags(tags) {
1085
- const result = [];
1086
- const seen = new Set();
1087
- for (const source of tags){
1088
- const tag = source.trim();
1089
- if (!(!tag || seen.has(tag))) {
1090
- seen.add(tag);
1091
- result.push(tag);
1345
+ function collectDocumentBookmarkPairs(document1, boundaryNodeName) {
1346
+ const boundaries = documentBookmarkBoundaries(document1, boundaryNodeName);
1347
+ const open = new Map();
1348
+ const pairs = [];
1349
+ const orphans = [];
1350
+ for (const boundary of boundaries){
1351
+ const key = bookmarkBoundaryPairKey(boundary.node.attrs);
1352
+ if ('start' === boundary.kind) {
1353
+ const stack = open.get(key) ?? [];
1354
+ stack.push(boundary);
1355
+ open.set(key, stack);
1356
+ continue;
1357
+ }
1358
+ const stack = open.get(key);
1359
+ const start = stack?.pop();
1360
+ if (!start) {
1361
+ orphans.push(boundary);
1362
+ continue;
1092
1363
  }
1364
+ pairs.push(bookmarkPair(start, boundary));
1093
1365
  }
1094
- return result;
1366
+ for (const stack of open.values())orphans.push(...stack);
1367
+ pairs.sort((left, right)=>left.from - right.from || left.to - right.to);
1368
+ return {
1369
+ pairs,
1370
+ orphans
1371
+ };
1095
1372
  }
1096
- function uniqueCitationId(source, index, usedIds) {
1097
- const candidate = source?.trim();
1098
- if (candidate && !usedIds.has(candidate)) {
1099
- usedIds.add(candidate);
1100
- return candidate;
1101
- }
1102
- let suffix = index;
1103
- while(usedIds.has(`document-citation-${suffix}`))suffix += 1;
1104
- const id = `document-citation-${suffix}`;
1105
- usedIds.add(id);
1106
- return id;
1373
+ function documentBookmarkBoundaries(document1, boundaryNodeName) {
1374
+ if (!boundaryNodeName) return [];
1375
+ const boundaries = [];
1376
+ document1.descendants((node, position)=>{
1377
+ if (node.type.name !== boundaryNodeName) return;
1378
+ boundaries.push({
1379
+ node,
1380
+ position,
1381
+ kind: documentBookmarkBoundaryKind(node.attrs.kind)
1382
+ });
1383
+ });
1384
+ return boundaries;
1107
1385
  }
1108
- const DEFAULT_DOCUMENT_COLUMNS = {
1109
- count: 1,
1110
- spacing: 12,
1111
- separator: false
1112
- };
1113
- const MAX_COLUMNS = 6;
1114
- const MIN_COLUMN_PERCENT = 5;
1115
- function normalizeDocumentColumns(columns) {
1116
- const customCount = Array.isArray(columns?.custom) ? columns.custom.length : 0;
1117
- const count = clampInteger(columns?.count, customCount || DEFAULT_DOCUMENT_COLUMNS.count, 1, MAX_COLUMNS);
1118
- const spacing = clampNumber(columns?.spacing, DEFAULT_DOCUMENT_COLUMNS.spacing, 0, 30);
1119
- const normalized = {
1120
- count,
1121
- spacing: roundOne(spacing),
1122
- separator: Boolean(columns?.separator)
1386
+ function bookmarkPair(start, end) {
1387
+ return {
1388
+ id: bookmarkInternalId(start.node.attrs.id),
1389
+ name: normalizeDocumentBookmarkName(start.node.attrs.name) ?? '',
1390
+ nativeId: normalizeDocumentBookmarkNativeId(start.node.attrs.nativeId) ?? -1,
1391
+ from: start.position,
1392
+ to: end.position,
1393
+ start,
1394
+ end
1123
1395
  };
1124
- if (count < 2 || !customCount) return normalized;
1125
- const source = columns?.custom ?? [];
1126
- const widths = normalizedPercentages(Array.from({
1127
- length: count
1128
- }, (_, index)=>finiteNumber(source[index]?.widthPercent, 100 / count)), 100, MIN_COLUMN_PERCENT);
1129
- normalized.custom = Array.from({
1130
- length: count
1131
- }, (_, index)=>({
1132
- widthPercent: widths[index],
1133
- spacing: index === count - 1 ? 0 : roundOne(clampNumber(source[index]?.spacing, normalized.spacing, 0, 30))
1134
- }));
1135
- return normalized;
1136
- }
1137
- function serializeDocumentColumns(columns) {
1138
- return JSON.stringify(normalizeDocumentColumns(columns));
1139
1396
  }
1140
- function parseDocumentColumns(source, legacy = {}, fallback) {
1141
- if (source?.trim()) try {
1142
- return normalizeDocumentColumns(JSON.parse(source));
1143
- } catch {}
1144
- if (void 0 !== legacy.count || void 0 !== legacy.spacing || void 0 !== legacy.separator) return normalizeDocumentColumns(legacy);
1145
- return normalizeDocumentColumns(fallback);
1397
+ function bookmarkRegistryForDocument(document1, boundaryNodeName) {
1398
+ const registry = createBookmarkRegistry();
1399
+ for (const pair of collectDocumentBookmarkPairs(document1, boundaryNodeName).pairs){
1400
+ const identity = bookmarkIdentity(pair);
1401
+ if (identity) reserveBookmarkIdentity(identity, registry);
1402
+ }
1403
+ return registry;
1146
1404
  }
1147
- function setCustomDocumentColumns(columns, enabled) {
1148
- const normalized = normalizeDocumentColumns(columns);
1149
- if (!enabled || normalized.count < 2) return {
1150
- ...normalized,
1151
- custom: void 0
1405
+ function uniqueDocumentBookmarkIdentity(source, registry) {
1406
+ const preferred = bookmarkIdentity(source);
1407
+ if (preferred && !bookmarkIdentityConflicts(preferred, registry)) {
1408
+ reserveBookmarkIdentity(preferred, registry);
1409
+ return preferred;
1410
+ }
1411
+ const baseName = normalizeDocumentBookmarkName(source.name) ?? 'Bookmark';
1412
+ const identity = {
1413
+ id: createUniqueBookmarkId(registry.ids),
1414
+ name: uniqueBookmarkName(baseName, registry.names),
1415
+ nativeId: nextBookmarkNativeId(registry.nativeIds)
1152
1416
  };
1153
- if (normalized.custom) return normalized;
1154
- return normalizeDocumentColumns({
1155
- ...normalized,
1156
- custom: Array.from({
1157
- length: normalized.count
1158
- }, (_, index)=>({
1159
- widthPercent: 100 / normalized.count,
1160
- spacing: index === normalized.count - 1 ? 0 : normalized.spacing
1161
- }))
1162
- });
1417
+ reserveBookmarkIdentity(identity, registry);
1418
+ return identity;
1163
1419
  }
1164
- function updateDocumentColumnWidth(columns, index, widthPercent) {
1165
- const normalized = setCustomDocumentColumns(columns, true);
1166
- if (!normalized.custom?.[index]) return normalized;
1167
- const maximum = 100 - MIN_COLUMN_PERCENT * (normalized.custom.length - 1);
1168
- const target = Math.min(maximum, Math.max(MIN_COLUMN_PERCENT, finiteNumber(widthPercent, 100 / normalized.count)));
1169
- const otherIndexes = normalized.custom.map((_, itemIndex)=>itemIndex).filter((itemIndex)=>itemIndex !== index);
1170
- const otherWidths = normalizedPercentages(otherIndexes.map((itemIndex)=>normalized.custom?.[itemIndex].widthPercent ?? 1), 100 - target, MIN_COLUMN_PERCENT);
1171
- const custom = normalized.custom.map((column, itemIndex)=>{
1172
- const otherIndex = otherIndexes.indexOf(itemIndex);
1173
- return {
1174
- ...column,
1175
- widthPercent: itemIndex === index ? roundOne(target) : otherWidths[otherIndex]
1176
- };
1177
- });
1420
+ function bookmarkIdentity(source) {
1421
+ const id = bookmarkInternalId(source.id);
1422
+ const name = normalizeDocumentBookmarkName(source.name);
1423
+ const nativeId = normalizeDocumentBookmarkNativeId(source.nativeId);
1424
+ return id && name && null !== nativeId ? {
1425
+ id,
1426
+ name,
1427
+ nativeId
1428
+ } : null;
1429
+ }
1430
+ function bookmarkIdentityConflicts(identity, registry) {
1431
+ return registry.ids.has(identity.id) || registry.names.has(identity.name.toLowerCase()) || registry.nativeIds.has(identity.nativeId);
1432
+ }
1433
+ function reserveBookmarkIdentity(identity, registry) {
1434
+ registry.ids.add(identity.id);
1435
+ registry.names.add(identity.name.toLowerCase());
1436
+ registry.nativeIds.add(identity.nativeId);
1437
+ }
1438
+ function createBookmarkRegistry() {
1178
1439
  return {
1179
- ...normalized,
1180
- custom
1440
+ ids: new Set(),
1441
+ names: new Set(),
1442
+ nativeIds: new Set()
1181
1443
  };
1182
1444
  }
1183
- function normalizedPercentages(values, total, minimum) {
1184
- if (!values.length) return [];
1185
- const result = Array(values.length).fill(0);
1186
- const remaining = new Set(values.map((_, index)=>index));
1187
- let remainingTotal = total;
1188
- while(remaining.size){
1189
- const sourceTotal = Array.from(remaining).reduce((sum, index)=>sum + Math.max(0.001, finiteNumber(values[index], 1)), 0);
1190
- const belowMinimum = Array.from(remaining).filter((index)=>Math.max(0.001, finiteNumber(values[index], 1)) / sourceTotal * remainingTotal < minimum);
1191
- if (!belowMinimum.length) {
1192
- for (const index of remaining)result[index] = Math.max(0.001, finiteNumber(values[index], 1)) / sourceTotal * remainingTotal;
1193
- break;
1194
- }
1195
- for (const index of belowMinimum){
1196
- result[index] = minimum;
1197
- remaining.delete(index);
1198
- remainingTotal -= minimum;
1199
- }
1445
+ function createUniqueBookmarkId(ids) {
1446
+ for(let attempt = 0; attempt < 8; attempt += 1){
1447
+ const id = createWorkId('bookmark');
1448
+ if (!ids.has(id)) return id;
1200
1449
  }
1201
- const rounded = result.map(roundOne);
1202
- const adjustmentIndex = rounded.indexOf(Math.max(...rounded));
1203
- rounded[adjustmentIndex] = roundOne(rounded[adjustmentIndex] + total - rounded.reduce((sum, value)=>sum + value, 0));
1204
- return rounded;
1205
- }
1206
- function clampInteger(value, fallback, minimum, maximum) {
1207
- return Math.min(maximum, Math.max(minimum, Math.round(finiteNumber(value, fallback))));
1450
+ let suffix = 1;
1451
+ while(ids.has(`bookmark-${suffix}`))suffix += 1;
1452
+ return `bookmark-${suffix}`;
1208
1453
  }
1209
- function clampNumber(value, fallback, minimum, maximum) {
1210
- return Math.min(maximum, Math.max(minimum, finiteNumber(value, fallback)));
1454
+ function uniqueBookmarkName(base, names) {
1455
+ if (!names.has(base.toLowerCase())) return base;
1456
+ let suffix = 2;
1457
+ while(suffix <= MAX_BOOKMARK_NATIVE_ID){
1458
+ const ending = `_${suffix}`;
1459
+ const prefix = Array.from(base).slice(0, MAX_BOOKMARK_NAME_LENGTH - ending.length).join('');
1460
+ const candidate = `${prefix}${ending}`;
1461
+ if (!names.has(candidate.toLowerCase())) return candidate;
1462
+ suffix += 1;
1463
+ }
1464
+ throw new Error('No unique Word bookmark name is available.');
1211
1465
  }
1212
- function finiteNumber(value, fallback) {
1213
- return Number.isFinite(value) ? Number(value) : fallback;
1466
+ function nextBookmarkNativeId(ids) {
1467
+ for(let id = 0; id <= MAX_BOOKMARK_NATIVE_ID; id += 1)if (!ids.has(id)) return id;
1468
+ throw new Error('No unique Word bookmark identifier is available.');
1214
1469
  }
1215
- function roundOne(value) {
1216
- return Math.round(10 * value) / 10;
1470
+ function sameBookmarkIdentity(source, identity) {
1471
+ return bookmarkInternalId(source.id) === identity.id && normalizeDocumentBookmarkName(source.name) === identity.name && normalizeDocumentBookmarkNativeId(source.nativeId) === identity.nativeId;
1217
1472
  }
1218
- const FIELD_SELECTOR = 'span[data-document-field]';
1219
- const FIELD_COMMANDS = {
1220
- page: 'PAGE',
1221
- numPages: 'NUMPAGES',
1222
- section: 'SECTION',
1223
- sectionPages: 'SECTIONPAGES',
1224
- date: 'DATE \\@ "yyyy年M月d日"',
1225
- time: 'TIME \\@ "HH:mm"'
1226
- };
1227
- const FIELD_LABELS = {
1228
- page: '当前页码',
1229
- numPages: '总页数',
1230
- section: '当前节号',
1231
- sectionPages: '本节页数',
1232
- date: '当前日期',
1233
- time: '当前时间'
1234
- };
1235
- function documentFieldKind(value) {
1236
- if ('page' === value || 'numPages' === value || 'section' === value || 'sectionPages' === value || 'date' === value || 'time' === value) return value;
1237
- return null;
1473
+ function bookmarkInternalId(value) {
1474
+ return 'string' == typeof value ? value.trim() : '';
1238
1475
  }
1239
- function docxDocumentFieldKind(instruction) {
1240
- const command = /^\s*([a-z][a-z0-9]*)\b/i.exec(instruction)?.[1]?.toUpperCase();
1241
- if ('PAGE' === command) return 'page';
1242
- if ('NUMPAGES' === command) return 'numPages';
1243
- if ('SECTION' === command) return 'section';
1244
- if ('SECTIONPAGES' === command) return 'sectionPages';
1245
- if ('DATE' === command) return 'date';
1246
- if ('TIME' === command) return 'time';
1247
- return null;
1476
+ function bookmarkBoundaryPairKey(attributes) {
1477
+ const id = bookmarkInternalId(attributes.id);
1478
+ if (id) return `id:${id}`;
1479
+ return `legacy:${String(attributes.name ?? '')}:${String(attributes.nativeId ?? '')}`;
1248
1480
  }
1249
- function documentFieldInstruction(kind) {
1250
- return FIELD_COMMANDS[kind];
1481
+ function documentBookmarkBoundaryKind(value) {
1482
+ return 'end' === value ? 'end' : 'start';
1251
1483
  }
1252
- function documentFieldLabel(kind) {
1253
- return FIELD_LABELS[kind];
1484
+ function transactionMapping(transactions) {
1485
+ const mapping = new Mapping();
1486
+ for (const transaction of transactions)mapping.appendMapping(transaction.mapping);
1487
+ return mapping;
1254
1488
  }
1255
- function documentFieldDisplay(kind, context, instruction = documentFieldInstruction(kind), cachedValue = '') {
1256
- if ('page' === kind) return String(work_document_fields_positiveInteger(context.pageNumber));
1257
- if ('numPages' === kind) return String(work_document_fields_positiveInteger(context.totalPages));
1258
- if ('section' === kind) return String(work_document_fields_positiveInteger(context.sectionNumber));
1259
- if ('sectionPages' === kind) return String(work_document_fields_positiveInteger(context.sectionPages));
1260
- const now = validDate(context.now) ?? new Date();
1261
- const format = dateFormatSwitch(instruction) ?? ('date' === kind ? 'yyyy年M月d日' : 'HH:mm');
1262
- const display = formatWordDate(now, format);
1263
- return display || cachedValue || documentFieldLabel(kind);
1489
+ function pairPositionKey(pair) {
1490
+ return `${pair.from}:${pair.to}`;
1264
1491
  }
1265
- function normalizeDocumentFieldsHtml(source) {
1266
- const document1 = new DOMParser().parseFromString(source, 'text/html');
1267
- const usedIds = new Set();
1268
- for (const [index, element] of Array.from(document1.body.querySelectorAll(FIELD_SELECTOR)).entries()){
1269
- const instruction = element.dataset.fieldInstruction?.trim() ?? '';
1270
- const kind = documentFieldKind(element.dataset.fieldKind) ?? docxDocumentFieldKind(instruction);
1271
- if (!kind) {
1272
- element.replaceWith(document1.createTextNode(element.textContent ?? ''));
1492
+ function collectDomBookmarkPairs(boundaries) {
1493
+ const open = new Map();
1494
+ const pairs = [];
1495
+ const orphans = [];
1496
+ for (const boundary of boundaries){
1497
+ const key = domBookmarkPairKey(boundary);
1498
+ if ('start' === documentBookmarkBoundaryKind(boundary.dataset.bookmarkKind)) {
1499
+ const stack = open.get(key) ?? [];
1500
+ stack.push(boundary);
1501
+ open.set(key, stack);
1273
1502
  continue;
1274
1503
  }
1275
- const display = element.dataset.fieldDisplay?.trim() || element.textContent?.trim() || documentFieldLabel(kind);
1276
- element.dataset.documentField = 'true';
1277
- element.dataset.fieldId = uniqueFieldId(element.dataset.fieldId, index + 1, usedIds);
1278
- element.dataset.fieldKind = kind;
1279
- element.dataset.fieldInstruction = instruction || documentFieldInstruction(kind);
1280
- element.dataset.fieldDisplay = display;
1281
- element.classList.add('work-document-field');
1282
- element.textContent = display;
1283
- }
1284
- return document1.body.innerHTML;
1285
- }
1286
- function resolveDocumentFieldsHtml(source, context) {
1287
- const document1 = new DOMParser().parseFromString(normalizeDocumentFieldsHtml(source), 'text/html');
1288
- for (const element of Array.from(document1.body.querySelectorAll(FIELD_SELECTOR))){
1289
- const kind = documentFieldKind(element.dataset.fieldKind);
1290
- if (!kind) continue;
1291
- const display = documentFieldDisplay(kind, context, element.dataset.fieldInstruction, element.dataset.fieldDisplay);
1292
- element.dataset.fieldDisplay = display;
1293
- element.textContent = display;
1294
- }
1295
- return document1.body.innerHTML;
1296
- }
1297
- function uniqueFieldId(source, index, usedIds) {
1298
- const candidate = source?.trim();
1299
- if (candidate && !usedIds.has(candidate)) {
1300
- usedIds.add(candidate);
1301
- return candidate;
1504
+ const start = open.get(key)?.pop();
1505
+ if (!start) {
1506
+ orphans.push(boundary);
1507
+ continue;
1508
+ }
1509
+ pairs.push({
1510
+ id: start.dataset.bookmarkId,
1511
+ name: start.dataset.bookmarkName,
1512
+ nativeId: normalizeDocumentBookmarkNativeId(start.dataset.officeBookmarkId) ?? void 0,
1513
+ start,
1514
+ end: boundary
1515
+ });
1302
1516
  }
1303
- let suffix = index;
1304
- while(usedIds.has(`document-field-${suffix}`))suffix += 1;
1305
- const id = `document-field-${suffix}`;
1306
- usedIds.add(id);
1307
- return id;
1308
- }
1309
- function dateFormatSwitch(instruction) {
1310
- return /\\@\s+"([^"]+)"/i.exec(instruction)?.[1] ?? null;
1311
- }
1312
- function formatWordDate(date, format) {
1313
- const hour12 = date.getHours() % 12 || 12;
1314
- const replacements = {
1315
- 'AM/PM': date.getHours() < 12 ? 'AM' : 'PM',
1316
- 'am/pm': date.getHours() < 12 ? 'am' : 'pm',
1317
- yyyy: String(date.getFullYear()).padStart(4, '0'),
1318
- yy: String(date.getFullYear() % 100).padStart(2, '0'),
1319
- MMMM: new Intl.DateTimeFormat('zh-CN', {
1320
- month: 'long'
1321
- }).format(date),
1322
- MMM: new Intl.DateTimeFormat('zh-CN', {
1323
- month: 'short'
1324
- }).format(date),
1325
- MM: String(date.getMonth() + 1).padStart(2, '0'),
1326
- M: String(date.getMonth() + 1),
1327
- dddd: new Intl.DateTimeFormat('zh-CN', {
1328
- weekday: 'long'
1329
- }).format(date),
1330
- ddd: new Intl.DateTimeFormat('zh-CN', {
1331
- weekday: 'short'
1332
- }).format(date),
1333
- dd: String(date.getDate()).padStart(2, '0'),
1334
- d: String(date.getDate()),
1335
- HH: String(date.getHours()).padStart(2, '0'),
1336
- H: String(date.getHours()),
1337
- hh: String(hour12).padStart(2, '0'),
1338
- h: String(hour12),
1339
- mm: String(date.getMinutes()).padStart(2, '0'),
1340
- m: String(date.getMinutes()),
1341
- ss: String(date.getSeconds()).padStart(2, '0'),
1342
- s: String(date.getSeconds())
1517
+ for (const stack of open.values())orphans.push(...stack);
1518
+ return {
1519
+ pairs,
1520
+ orphans
1343
1521
  };
1344
- return format.replace(/AM\/PM|am\/pm|yyyy|MMMM|dddd|MMM|ddd|yy|MM|dd|HH|hh|mm|ss|M|d|H|h|m|s/g, (token)=>replacements[token] ?? token);
1345
- }
1346
- function work_document_fields_positiveInteger(value) {
1347
- return Number.isSafeInteger(value) && value > 0 ? value : 1;
1348
1522
  }
1349
- function validDate(value) {
1350
- return value && Number.isFinite(value.getTime()) ? value : null;
1523
+ function domBookmarkPairKey(element) {
1524
+ const id = element.dataset.bookmarkId?.trim();
1525
+ return id ? `id:${id}` : `legacy:${element.dataset.bookmarkName ?? ''}:${element.dataset.officeBookmarkId ?? ''}`;
1351
1526
  }
1352
- const DEFAULT_DOCUMENT_MARGINS = {
1353
- top: 25,
1354
- right: 23,
1355
- bottom: 25,
1356
- left: 23
1357
- };
1358
- function documentMargins(content) {
1359
- return {
1360
- top: validMargin(content.margins?.top, DEFAULT_DOCUMENT_MARGINS.top),
1361
- right: validMargin(content.margins?.right, DEFAULT_DOCUMENT_MARGINS.right),
1362
- bottom: validMargin(content.margins?.bottom, DEFAULT_DOCUMENT_MARGINS.bottom),
1363
- left: validMargin(content.margins?.left, DEFAULT_DOCUMENT_MARGINS.left)
1364
- };
1527
+ function applyBookmarkIdentityToElement(element, identity, kind) {
1528
+ element.dataset.documentBookmarkBoundary = 'true';
1529
+ element.dataset.bookmarkKind = kind;
1530
+ element.dataset.bookmarkId = identity.id;
1531
+ element.dataset.bookmarkName = identity.name;
1532
+ element.dataset.officeBookmarkId = String(identity.nativeId);
1533
+ element.classList.add('work-document-bookmark-boundary', kind);
1534
+ element.contentEditable = 'false';
1535
+ element.setAttribute('aria-hidden', 'true');
1536
+ if ('start' === kind) element.id = identity.name;
1537
+ else element.removeAttribute('id');
1365
1538
  }
1366
- function clampDocumentMargin(value) {
1367
- return Math.min(60, Math.max(5, Math.round(10 * value) / 10));
1539
+ function synchronizeDomInternalLinks(root, names) {
1540
+ for (const link of root.querySelectorAll('a[href^="#"]')){
1541
+ const target = (link.getAttribute('href') ?? '').slice(1).toLowerCase();
1542
+ link.classList.toggle(MISSING_LINK_CLASS, !names.has(target));
1543
+ if (!link.className) link.removeAttribute('class');
1544
+ }
1368
1545
  }
1369
- function millimetersToPixels(value) {
1370
- return 96 * value / 25.4;
1546
+ function synchronizeDocumentPageReferenceNodes(state, tr, bookmarks, renames) {
1547
+ state.doc.descendants((node, position)=>{
1548
+ if ('documentField' !== node.type.name) return;
1549
+ const kind = 'pageReference' === node.attrs.kind ? 'pageReference' : docxDocumentFieldKind(work_document_bookmarks_stringAttribute(node.attrs.instruction));
1550
+ if ('pageReference' !== kind) return;
1551
+ let targetId = work_document_bookmarks_stringAttribute(node.attrs.targetId);
1552
+ let targetName = work_document_bookmarks_stringAttribute(node.attrs.targetName) || docxDocumentFieldTarget(work_document_bookmarks_stringAttribute(node.attrs.instruction)) || '';
1553
+ const rename = renames.filter((candidate)=>position > candidate.from && position < candidate.to && (targetId && targetId === candidate.previousId || targetName && targetName.toLowerCase() === candidate.previousName.toLowerCase())).sort((left, right)=>left.to - left.from - (right.to - right.from))[0];
1554
+ if (rename) {
1555
+ targetId = rename.nextId;
1556
+ targetName = rename.nextName;
1557
+ }
1558
+ const target = bookmarks.find((bookmark)=>targetId && bookmark.id === targetId || targetName && bookmark.name.toLowerCase() === targetName.toLowerCase()) ?? null;
1559
+ const nextTargetId = target?.id ?? targetId;
1560
+ const nextTargetName = target?.name ?? targetName;
1561
+ const instruction = target ? documentPageReferenceInstruction(nextTargetName, work_document_bookmarks_stringAttribute(node.attrs.instruction)) : work_document_bookmarks_stringAttribute(node.attrs.instruction);
1562
+ const orphaned = !target;
1563
+ if (node.attrs.targetId === nextTargetId && node.attrs.targetName === nextTargetName && node.attrs.instruction === instruction && node.attrs.orphaned === orphaned) return;
1564
+ tr.setNodeMarkup(position, void 0, {
1565
+ ...node.attrs,
1566
+ targetId: nextTargetId,
1567
+ targetName: nextTargetName,
1568
+ instruction,
1569
+ orphaned
1570
+ });
1571
+ });
1371
1572
  }
1372
- function validMargin(value, fallback) {
1373
- return Number.isFinite(value) ? clampDocumentMargin(value) : fallback;
1573
+ function retargetDomPageReferences(root, start, end, previous, next) {
1574
+ const range = root.ownerDocument.createRange();
1575
+ range.setStartAfter(start);
1576
+ range.setEndBefore(end);
1577
+ for (const field of root.querySelectorAll('span[data-document-field][data-field-kind="pageReference"]')){
1578
+ if (!range.intersectsNode(field)) continue;
1579
+ const id = field.dataset.fieldTargetId?.trim() ?? '';
1580
+ const name = field.dataset.fieldTargetName?.trim() ?? '';
1581
+ if (id === previous.id || name.toLowerCase() === previous.name.toLowerCase()) {
1582
+ field.dataset.fieldTargetId = next.id;
1583
+ field.dataset.fieldTargetName = next.name;
1584
+ field.dataset.fieldInstruction = documentPageReferenceInstruction(next.name, field.dataset.fieldInstruction);
1585
+ delete field.dataset.fieldOrphaned;
1586
+ }
1587
+ }
1374
1588
  }
1375
- const DOCUMENT_HTML_FINGERPRINT_VERSION = 'p1';
1376
- const DOCUMENT_HTML_HASH_BASE = 0x01000193;
1377
- const LEGACY_FNV_OFFSET = 0x811c9dc5;
1378
- function createDocumentHtmlFingerprintSegment(source, from = 0, to = source.length) {
1379
- if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from < 0 || to < from || to > source.length) throw new RangeError('The document HTML fingerprint range is invalid.');
1380
- let hash = 0;
1381
- for(let index = from; index < to; index += 1)hash = Math.imul(hash, DOCUMENT_HTML_HASH_BASE) + source.charCodeAt(index) >>> 0;
1382
- const length = to - from;
1383
- return {
1384
- hash,
1385
- length,
1386
- power: documentHtmlHashPower(length)
1387
- };
1589
+ function normalizedClass(value) {
1590
+ return 'string' == typeof value ? value.trim().split(/\s+/).filter(Boolean).join(' ') : '';
1388
1591
  }
1389
- function combineDocumentHtmlFingerprintSegments(segments) {
1390
- let combined = {
1391
- hash: 0,
1392
- length: 0,
1393
- power: 1
1394
- };
1395
- for (const segment of segments)combined = {
1396
- hash: Math.imul(combined.hash, segment.power) + segment.hash >>> 0,
1397
- length: combined.length + segment.length,
1398
- power: Math.imul(combined.power, segment.power) >>> 0
1399
- };
1400
- return combined;
1592
+ function toggleClassToken(value, token, enabled) {
1593
+ const tokens = new Set(normalizedClass(value).split(' ').filter(Boolean));
1594
+ if (enabled) tokens.add(token);
1595
+ else tokens.delete(token);
1596
+ return Array.from(tokens).join(' ');
1401
1597
  }
1402
- function documentHtmlFingerprintForSegment(segment) {
1403
- return `${DOCUMENT_HTML_FINGERPRINT_VERSION}:${segment.length.toString(36)}:${segment.hash.toString(36)}`;
1598
+ function work_document_bookmarks_stringAttribute(value) {
1599
+ return 'string' == typeof value ? value.trim() : '';
1404
1600
  }
1405
- function documentHtmlFingerprint(html) {
1406
- return documentHtmlFingerprintForSegment(createDocumentHtmlFingerprintSegment(html));
1601
+ const CAPTION_SELECTOR = 'figcaption[data-document-caption]';
1602
+ const REFERENCE_SELECTOR = 'span[data-document-cross-reference]';
1603
+ function normalizeDocumentCaptionsHtml(source) {
1604
+ const document1 = new DOMParser().parseFromString(source, 'text/html');
1605
+ const targets = normalizeCaptions(document1);
1606
+ normalizeReferences(document1, new Map(targets.map((target)=>[
1607
+ target.id,
1608
+ target
1609
+ ])));
1610
+ return document1.body.innerHTML;
1407
1611
  }
1408
- function documentHtmlFingerprintMatches(html, candidate) {
1409
- return candidate.startsWith(`${DOCUMENT_HTML_FINGERPRINT_VERSION}:`) ? candidate === documentHtmlFingerprint(html) : candidate === legacyDocumentHtmlFingerprint(html);
1612
+ function documentCaptionKind(value) {
1613
+ return 'figure' === value || 'table' === value ? value : null;
1410
1614
  }
1411
- function legacyDocumentHtmlFingerprint(html) {
1412
- let hash = LEGACY_FNV_OFFSET;
1413
- for(let index = 0; index < html.length; index += 1){
1414
- hash ^= html.charCodeAt(index);
1415
- hash = Math.imul(hash, DOCUMENT_HTML_HASH_BASE);
1416
- }
1417
- return `${html.length.toString(36)}:${(hash >>> 0).toString(36)}`;
1615
+ function documentCaptionLabel(kind) {
1616
+ return 'table' === kind ? '表' : '图';
1418
1617
  }
1419
- function documentHtmlHashPower(length) {
1420
- let exponent = length;
1421
- let factor = DOCUMENT_HTML_HASH_BASE;
1422
- let power = 1;
1423
- while(exponent > 0){
1424
- if (exponent % 2 === 1) power = Math.imul(power, factor) >>> 0;
1425
- factor = Math.imul(factor, factor) >>> 0;
1426
- exponent = Math.floor(exponent / 2);
1427
- }
1428
- return power;
1618
+ function documentCaptionDisplay(kind, number) {
1619
+ return `${documentCaptionLabel(kind)} ${work_document_captions_positiveInteger(number)}`;
1429
1620
  }
1430
- const SECTION_CLOSE = '</section>';
1431
- const TABLE_BODY_OPEN = '<tbody>';
1432
- const TABLE_CLOSE = '</tbody></table>';
1433
- const projectionFingerprints = new WeakMap();
1434
- function createDocumentLazyHtmlProjection(html, root) {
1435
- if (!html.startsWith('<section') || !html.endsWith(SECTION_CLOSE)) return null;
1436
- const sectionOpenEnd = html.indexOf('>') + 1;
1437
- if (sectionOpenEnd <= 0) return null;
1438
- let cursor = sectionOpenEnd;
1439
- const orderedRanges = [];
1440
- const ranges = new Map();
1441
- for (const chunk of lazyDocumentLeafChunks(root)){
1442
- const id = 'string' == typeof chunk.attrs?.id ? chunk.attrs.id : '';
1443
- if (!id || ranges.has(id)) return null;
1444
- const from = cursor;
1445
- let tablePart = null;
1446
- const content = chunk.content ?? [];
1447
- for (const node of content){
1448
- const scanned = scanSimpleDocumentNodeHtml(html, cursor, node);
1449
- if (!scanned) return null;
1450
- cursor = scanned.to;
1451
- if (scanned.tablePart) {
1452
- if (1 !== content.length || tablePart) return null;
1453
- tablePart = scanned.tablePart;
1454
- }
1455
- }
1456
- const range = {
1457
- from,
1621
+ function normalizeCaptions(document1) {
1622
+ const counters = {
1623
+ figure: 0,
1624
+ table: 0
1625
+ };
1626
+ const usedIds = new Set();
1627
+ return Array.from(document1.body.querySelectorAll(CAPTION_SELECTOR)).map((element, index)=>{
1628
+ const kind = documentCaptionKind(element.dataset.captionKind) ?? 'figure';
1629
+ counters[kind] += 1;
1630
+ const number = counters[kind];
1631
+ const id = uniqueCaptionId(element.dataset.captionId, kind, index + 1, usedIds);
1632
+ const label = documentCaptionLabel(kind);
1633
+ element.dataset.documentCaption = 'true';
1634
+ element.dataset.captionId = id;
1635
+ element.dataset.captionKind = kind;
1636
+ element.dataset.captionNumber = String(number);
1637
+ element.dataset.captionLabel = label;
1638
+ element.classList.add('work-document-caption');
1639
+ const title = element.textContent?.replace(/\s+/g, ' ').trim() ?? '';
1640
+ const display = documentCaptionDisplay(kind, number);
1641
+ element.setAttribute('aria-label', title ? `${display} ${title}` : display);
1642
+ return {
1458
1643
  id,
1459
- tablePart,
1460
- to: cursor
1644
+ kind,
1645
+ number,
1646
+ label,
1647
+ title,
1648
+ display
1461
1649
  };
1462
- orderedRanges.push(range);
1463
- ranges.set(id, range);
1464
- }
1465
- if (cursor !== html.length - SECTION_CLOSE.length) return null;
1466
- return {
1467
- html,
1468
- orderedRanges,
1469
- ranges
1470
- };
1650
+ });
1471
1651
  }
1472
- function patchDocumentLazyHtmlProjection(projection, replacements) {
1473
- if (!replacements.size) return projection.html;
1474
- for (const id of replacements.keys())if (!projection.ranges.has(id)) return null;
1475
- const fingerprintState = documentLazyHtmlFingerprintState(projection);
1476
- const parts = [];
1477
- let cursor = 0;
1478
- for (const range of projection.orderedRanges){
1479
- const replacement = replacements.get(range.id);
1480
- if (void 0 !== replacement) {
1481
- parts.push(projection.html.slice(cursor, range.from), replacement);
1482
- fingerprintState.ranges.set(range.id, createDocumentHtmlFingerprintSegment(replacement));
1483
- cursor = range.to;
1652
+ function normalizeReferences(document1, targets) {
1653
+ for (const element of Array.from(document1.body.querySelectorAll(REFERENCE_SELECTOR))){
1654
+ if ('bookmark' === element.dataset.referenceTargetType) continue;
1655
+ const id = element.dataset.referenceTargetId?.trim() ?? '';
1656
+ const target = targets.get(id);
1657
+ element.dataset.documentCrossReference = 'true';
1658
+ element.dataset.referenceTargetId = id;
1659
+ element.classList.add('work-document-cross-reference');
1660
+ if (!target) {
1661
+ element.dataset.referenceOrphaned = 'true';
1662
+ element.textContent = '引用缺失';
1663
+ continue;
1484
1664
  }
1665
+ delete element.dataset.referenceOrphaned;
1666
+ element.dataset.captionKind = target.kind;
1667
+ element.dataset.captionNumber = String(target.number);
1668
+ element.dataset.captionLabel = target.label;
1669
+ element.textContent = target.display;
1485
1670
  }
1486
- parts.push(projection.html.slice(cursor));
1487
- const html = parts.join('');
1488
- let offset = 0;
1489
- for (const range of projection.orderedRanges){
1490
- const previousFrom = range.from;
1491
- const previousLength = range.to - previousFrom;
1492
- const replacement = replacements.get(range.id);
1493
- const nextLength = replacement?.length ?? previousLength;
1494
- range.from = previousFrom + offset;
1495
- range.to = range.from + nextLength;
1496
- offset += nextLength - previousLength;
1671
+ }
1672
+ function uniqueCaptionId(source, kind, index, usedIds) {
1673
+ const candidate = source?.trim();
1674
+ if (candidate && !usedIds.has(candidate)) {
1675
+ usedIds.add(candidate);
1676
+ return candidate;
1497
1677
  }
1498
- projection.html = html;
1499
- fingerprintState.fingerprint = documentHtmlFingerprintForSegment(combineDocumentHtmlFingerprintSegments([
1500
- fingerprintState.prefix,
1501
- ...projection.orderedRanges.map((range)=>{
1502
- const segment = fingerprintState.ranges.get(range.id);
1503
- if (!segment) throw new Error('The lazy HTML fingerprint segment is missing.');
1504
- return segment;
1505
- }),
1506
- fingerprintState.suffix
1507
- ]));
1508
- return html;
1678
+ let suffix = index;
1679
+ while(usedIds.has(`document-${kind}-caption-${suffix}`))suffix += 1;
1680
+ const id = `document-${kind}-caption-${suffix}`;
1681
+ usedIds.add(id);
1682
+ return id;
1683
+ }
1684
+ function work_document_captions_positiveInteger(value) {
1685
+ const number = Number(value);
1686
+ return Number.isSafeInteger(number) && number > 0 ? number : 1;
1687
+ }
1688
+ const CITATION_SELECTOR = 'span[data-document-citation]';
1689
+ const BIBLIOGRAPHY_SELECTOR = 'section[data-document-bibliography]';
1690
+ const STYLE_DETAILS = {
1691
+ apa: {
1692
+ name: 'APA',
1693
+ selectedStyle: '\\APASixthEditionOfficeOnline.xsl'
1694
+ },
1695
+ mla: {
1696
+ name: 'MLA',
1697
+ selectedStyle: '\\MLASeventhEditionOfficeOnline.xsl'
1698
+ },
1699
+ chicago: {
1700
+ name: 'Chicago',
1701
+ selectedStyle: '\\CHICAGO.XSL'
1702
+ },
1703
+ ieee: {
1704
+ name: 'IEEE',
1705
+ selectedStyle: '\\IEEE.XSL'
1706
+ }
1707
+ };
1708
+ function createDocumentBibliography(style = 'apa') {
1709
+ return {
1710
+ style,
1711
+ styleName: STYLE_DETAILS[style].name,
1712
+ selectedStyle: STYLE_DETAILS[style].selectedStyle,
1713
+ sources: []
1714
+ };
1509
1715
  }
1510
- function documentLazyHtmlProjectionFingerprint(projection) {
1511
- return projectionFingerprints.get(projection)?.fingerprint ?? null;
1716
+ function documentCitationStyle(value) {
1717
+ if ('mla' === value || 'chicago' === value || 'ieee' === value) return value;
1718
+ return 'apa';
1512
1719
  }
1513
- function documentLazyHtmlChunkFragment(html, tablePart) {
1514
- if (!tablePart || 'complete' === tablePart) return html;
1515
- const bodyOpen = html.indexOf(TABLE_BODY_OPEN);
1516
- const bodyClose = html.lastIndexOf('</tbody>');
1517
- if (bodyOpen < 0 || bodyClose < bodyOpen) return null;
1518
- const rowsFrom = bodyOpen + TABLE_BODY_OPEN.length;
1519
- if ('first' === tablePart) return html.slice(0, bodyClose);
1520
- if ('middle' === tablePart) return html.slice(rowsFrom, bodyClose);
1521
- return html.slice(rowsFrom);
1720
+ function documentCitationStyleDetails(style) {
1721
+ return STYLE_DETAILS[style];
1522
1722
  }
1523
- function lazyDocumentLeafChunks(root) {
1524
- const chunks = [];
1525
- const pending = [
1526
- root
1723
+ function documentCitationTagsFromInstruction(instruction) {
1724
+ const primary = /^\s*CITATION\s+(?:"([^"]+)"|([^\s\\]+))/i.exec(instruction);
1725
+ if (!primary) return [];
1726
+ const tags = [
1727
+ primary[1] || primary[2]
1527
1728
  ];
1528
- while(pending.length){
1529
- const node = pending.pop();
1530
- if (!node) continue;
1531
- if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
1532
- chunks.push(node);
1533
- continue;
1534
- }
1535
- for(let index = (node.content?.length ?? 0) - 1; index >= 0; index -= 1){
1536
- const child = node.content?.[index];
1537
- if (child) pending.push(child);
1538
- }
1539
- }
1540
- return chunks;
1729
+ const additional = /\\m\s+(?:"([^"]+)"|([^\s\\]+))/gi;
1730
+ for (const match of instruction.matchAll(additional))tags.push(match[1] || match[2]);
1731
+ return uniqueCitationTags(tags);
1541
1732
  }
1542
- function documentLazyHtmlFingerprintState(projection) {
1543
- const existing = projectionFingerprints.get(projection);
1544
- if (existing) return existing;
1545
- const first = projection.orderedRanges[0];
1546
- const last = projection.orderedRanges.at(-1);
1547
- const ranges = new Map();
1548
- for (const range of projection.orderedRanges)ranges.set(range.id, createDocumentHtmlFingerprintSegment(projection.html, range.from, range.to));
1549
- const prefix = createDocumentHtmlFingerprintSegment(projection.html, 0, first?.from ?? projection.html.length);
1550
- const suffix = createDocumentHtmlFingerprintSegment(projection.html, last?.to ?? projection.html.length, projection.html.length);
1551
- const state = {
1552
- fingerprint: documentHtmlFingerprintForSegment(combineDocumentHtmlFingerprintSegments([
1553
- prefix,
1554
- ...projection.orderedRanges.map((range)=>{
1555
- const segment = ranges.get(range.id);
1556
- if (!segment) throw new Error('The lazy HTML fingerprint segment is missing.');
1557
- return segment;
1558
- }),
1559
- suffix
1560
- ])),
1561
- prefix,
1562
- ranges,
1563
- suffix
1564
- };
1565
- projectionFingerprints.set(projection, state);
1566
- return state;
1733
+ function documentCitationInstruction(tags) {
1734
+ const normalized = uniqueCitationTags(tags);
1735
+ if (!normalized.length) return '';
1736
+ return [
1737
+ 'CITATION',
1738
+ citationTagInstructionValue(normalized[0]),
1739
+ ...normalized.slice(1).flatMap((tag)=>[
1740
+ '\\m',
1741
+ citationTagInstructionValue(tag)
1742
+ ]),
1743
+ '\\l',
1744
+ '2052'
1745
+ ].join(' ');
1567
1746
  }
1568
- function scanSimpleDocumentNodeHtml(html, from, node) {
1569
- if ('paragraph' === node.type) {
1570
- if (!html.startsWith('<p>', from)) return null;
1571
- const close = html.indexOf('</p>', from + 3);
1572
- return close < 0 ? null : {
1573
- tablePart: null,
1574
- to: close + 4
1575
- };
1576
- }
1577
- if ('table' !== node.type) return null;
1578
- const virtualIndex = Number(node.attrs?.virtualTableIndex);
1579
- const virtualCount = Number(node.attrs?.virtualTableCount);
1580
- const virtual = Boolean('string' == typeof node.attrs?.virtualTableId && node.attrs.virtualTableId && Number.isSafeInteger(virtualIndex) && Number.isSafeInteger(virtualCount) && virtualIndex >= 0 && virtualCount > 0 && virtualIndex < virtualCount);
1581
- let cursor = from;
1582
- if (!virtual || 0 === virtualIndex) {
1583
- if (!html.startsWith('<table', cursor)) return null;
1584
- const body = html.indexOf(TABLE_BODY_OPEN, cursor);
1585
- if (body < 0) return null;
1586
- cursor = body + TABLE_BODY_OPEN.length;
1587
- }
1588
- for (const row of node.content ?? []){
1589
- if ('tableRow' !== row.type || !html.startsWith('<tr>', cursor)) return null;
1590
- const close = html.indexOf('</tr>', cursor + 4);
1591
- if (close < 0) return null;
1592
- cursor = close + 5;
1593
- }
1594
- if (!virtual || virtualIndex === virtualCount - 1) {
1595
- if (!html.startsWith(TABLE_CLOSE, cursor)) return null;
1596
- cursor += TABLE_CLOSE.length;
1597
- }
1598
- const tablePart = virtual ? 1 === virtualCount ? 'complete' : 0 === virtualIndex ? 'first' : virtualIndex === virtualCount - 1 ? 'last' : 'middle' : 'complete';
1747
+ function renameDocumentCitationTagInInstruction(instruction, previousTag, nextTag) {
1748
+ return instruction.replace(/(^\s*CITATION\s+|\\m\s+)("[^"]+"|[^\s\\]+)/gi, (match, prefix, source)=>{
1749
+ const tag = source.startsWith('"') ? source.slice(1, -1) : source;
1750
+ return tag === previousTag ? `${prefix}${citationTagInstructionValue(nextTag)}` : match;
1751
+ });
1752
+ }
1753
+ function documentCitationTags(value) {
1754
+ return uniqueCitationTags((value ?? '').split(/\s+/));
1755
+ }
1756
+ function isValidDocumentCitationTag(value) {
1757
+ return /^[A-Za-z0-9_:.+-]{1,80}$/.test(value);
1758
+ }
1759
+ function resolveDocumentCitation(tags, bibliography, instruction = '', cachedValue = '') {
1760
+ const normalized = uniqueCitationTags(tags);
1761
+ if (!normalized.length) return {
1762
+ text: cachedValue || '缺失引文',
1763
+ orphaned: true
1764
+ };
1765
+ const sources = new Map((bibliography?.sources ?? []).map((source)=>[
1766
+ source.tag,
1767
+ source
1768
+ ]));
1769
+ const missing = normalized.filter((tag)=>!sources.has(tag));
1770
+ if (missing.length) return {
1771
+ text: 1 === missing.length ? `缺失引文:${missing[0]}` : `缺失引文:${missing.join('、')}`,
1772
+ orphaned: true
1773
+ };
1774
+ const selected = normalized.flatMap((tag)=>{
1775
+ const source = sources.get(tag);
1776
+ return source ? [
1777
+ source
1778
+ ] : [];
1779
+ });
1780
+ const style = bibliography?.style ?? 'apa';
1781
+ const suppressAuthor = /(?:^|\s)\\n(?:\s|$)/i.test(instruction);
1782
+ const suppressYear = /(?:^|\s)\\v(?:\s|$)/i.test(instruction);
1783
+ const prefix = citationSwitch(instruction, 'f');
1784
+ const suffix = citationSwitch(instruction, 'p');
1785
+ const text = 'ieee' === style ? ieeeCitation(selected, bibliography?.sources ?? []) : 'mla' === style ? mlaCitation(selected, suppressAuthor) : 'chicago' === style ? chicagoCitation(selected, suppressAuthor, suppressYear) : apaCitation(selected, suppressAuthor, suppressYear);
1599
1786
  return {
1600
- tablePart,
1601
- to: cursor
1787
+ text: `${prefix}${text}${suffix}`.trim() || cachedValue || normalized.join('; '),
1788
+ orphaned: false
1602
1789
  };
1603
1790
  }
1604
- const DOCUMENT_LAZY_BLOCK_NODE = 'documentLazyBlock';
1605
- const DOCUMENT_LAZY_INITIAL_CHUNK_COUNT = 2;
1606
- const DOCUMENT_LAZY_POSITION_BOUNDARY = '\ufffc';
1607
- const SIMPLE_LAZY_CONTAINER_TYPES = new Set([
1608
- 'paragraph',
1609
- 'table',
1610
- 'tableCell',
1611
- 'tableRow'
1612
- ]);
1613
- const preparedModels = new WeakMap();
1614
- function prepareLazyDocumentEditorSource(model, allowCreate, html) {
1615
- const startedAt = lazyDocumentNow();
1616
- const cacheHit = preparedModels.has(model);
1617
- let prepared = preparedModels.get(model);
1618
- if (prepared) {
1619
- if (!prepared.root) {
1620
- const htmlProjection = prepared.htmlProjection;
1621
- const rebuilt = createPreparedLazyDocumentEditorSource(model.root, prepared.payloads);
1622
- if (!rebuilt) return null;
1623
- rebuilt.htmlProjection = htmlProjection;
1624
- prepared = rebuilt;
1625
- preparedModels.set(model, rebuilt);
1791
+ function normalizeDocumentCitationsHtml(source, bibliography) {
1792
+ const document1 = new DOMParser().parseFromString(source, 'text/html');
1793
+ const usedIds = new Set();
1794
+ for (const [index, element] of Array.from(document1.body.querySelectorAll(CITATION_SELECTOR)).entries()){
1795
+ const instruction = element.dataset.citationInstruction?.trim() ?? '';
1796
+ const tags = documentCitationTags(element.dataset.citationTags);
1797
+ const normalizedTags = tags.length ? tags : documentCitationTagsFromInstruction(instruction);
1798
+ if (!normalizedTags.length) {
1799
+ element.replaceWith(document1.createTextNode(element.textContent ?? ''));
1800
+ continue;
1626
1801
  }
1627
- } else {
1628
- if (!allowCreate) return null;
1629
- const created = createPreparedLazyDocumentEditorSource(model.root);
1630
- if (!created) return null;
1631
- prepared = created;
1632
- preparedModels.set(model, created);
1802
+ const cached = element.dataset.citationDisplay?.trim() || element.textContent?.trim() || '';
1803
+ const resolved = resolveDocumentCitation(normalizedTags, bibliography, instruction, cached);
1804
+ element.dataset.documentCitation = 'true';
1805
+ element.dataset.citationId = uniqueCitationId(element.dataset.citationId, index + 1, usedIds);
1806
+ element.dataset.citationTags = normalizedTags.join(' ');
1807
+ element.dataset.citationInstruction = instruction || documentCitationInstruction(normalizedTags);
1808
+ element.dataset.citationDisplay = resolved.text;
1809
+ if (resolved.orphaned) element.dataset.citationOrphaned = 'true';
1810
+ else delete element.dataset.citationOrphaned;
1811
+ element.classList.add('work-document-citation');
1812
+ element.textContent = resolved.text;
1813
+ }
1814
+ for (const [index, element] of Array.from(document1.body.querySelectorAll(BIBLIOGRAPHY_SELECTOR)).entries()){
1815
+ if (!bibliography) continue;
1816
+ const replacement = createBibliographyElement(document1, bibliography, element.dataset.bibliographyId || `document-bibliography-${index + 1}`);
1817
+ element.replaceWith(replacement);
1818
+ }
1819
+ return document1.body.innerHTML;
1820
+ }
1821
+ function renderDocumentBibliographyHtml(bibliography, id = 'document-bibliography-1') {
1822
+ const document1 = new DOMParser().parseFromString('', 'text/html');
1823
+ document1.body.append(createBibliographyElement(document1, bibliography, id));
1824
+ return document1.body.innerHTML;
1825
+ }
1826
+ function documentBibliographyEntry(source, bibliography, index) {
1827
+ const people = primaryCitationContributor(source);
1828
+ const authors = people?.corporate?.trim() || people?.people?.map((person)=>bibliographyPersonName(person, bibliography.style)).join(', ') || '未知作者';
1829
+ const title = source.title.trim() || 'Untitled';
1830
+ const year = source.year?.trim() || 'n.d.';
1831
+ const container = citationContainer(source);
1832
+ const url = source.url?.trim();
1833
+ if ('ieee' === bibliography.style) return [
1834
+ `[${index + 1}] ${authors}, “${title}.”`,
1835
+ container ? `${container},` : '',
1836
+ year ? `${year}.` : '',
1837
+ url ?? ''
1838
+ ].filter(Boolean).join(' ');
1839
+ if ('mla' === bibliography.style) return [
1840
+ `${authors}.`,
1841
+ `“${title}.”`,
1842
+ container ? `${container},` : '',
1843
+ `${year}.`,
1844
+ url ?? ''
1845
+ ].filter(Boolean).join(' ');
1846
+ if ('chicago' === bibliography.style) return [
1847
+ `${authors}.`,
1848
+ `${year}.`,
1849
+ `“${title}.”`,
1850
+ container ? `${container}.` : '',
1851
+ url ?? ''
1852
+ ].filter(Boolean).join(' ');
1853
+ return [
1854
+ `${authors}.`,
1855
+ `(${year}).`,
1856
+ `${title}.`,
1857
+ container ? `${container}.` : '',
1858
+ url ?? ''
1859
+ ].filter(Boolean).join(' ');
1860
+ }
1861
+ function primaryCitationContributor(source) {
1862
+ return source.contributors?.Author ?? Object.values(source.contributors ?? {})[0];
1863
+ }
1864
+ function createBibliographyElement(document1, bibliography, id) {
1865
+ const section = document1.createElement('section');
1866
+ section.dataset.documentBibliography = 'true';
1867
+ section.dataset.bibliographyId = id;
1868
+ section.dataset.bibliographyStyle = bibliography.style;
1869
+ section.className = 'work-document-bibliography';
1870
+ const heading = document1.createElement('h2');
1871
+ heading.textContent = '参考文献';
1872
+ section.append(heading);
1873
+ if (!bibliography.sources.length) {
1874
+ const empty = document1.createElement('p');
1875
+ empty.dataset.bibliographyEmpty = 'true';
1876
+ empty.textContent = '尚无文献源';
1877
+ section.append(empty);
1878
+ return section;
1633
1879
  }
1634
- if (!prepared.htmlProjection && html) prepared.htmlProjection = createDocumentLazyHtmlProjection(html, model.root);
1635
- if (!prepared.root) return null;
1636
- const result = {
1637
- lazyChunkCount: prepared.lazyChunkCount,
1638
- payloads: prepared.payloads,
1639
- root: prepared.root
1640
- };
1641
- recordLazyDocumentMeasure('a3s-office.document.lazy-editor-source', startedAt, lazyDocumentNow(), {
1642
- cacheHit
1880
+ bibliography.sources.forEach((source, index)=>{
1881
+ const paragraph = document1.createElement('p');
1882
+ paragraph.dataset.bibliographyEntry = source.tag;
1883
+ paragraph.textContent = documentBibliographyEntry(source, bibliography, index);
1884
+ section.append(paragraph);
1643
1885
  });
1644
- return result;
1886
+ return section;
1645
1887
  }
1646
- function documentLazyHtmlProjection(model) {
1647
- return model ? preparedModels.get(model)?.htmlProjection ?? null : null;
1888
+ function apaCitation(sources, suppressAuthor, suppressYear) {
1889
+ const items = sources.map((source)=>{
1890
+ const author = suppressAuthor ? '' : citationAuthor(source, '&');
1891
+ const year = suppressYear ? '' : source.year?.trim() || 'n.d.';
1892
+ return [
1893
+ author,
1894
+ year
1895
+ ].filter(Boolean).join(', ');
1896
+ });
1897
+ return `(${items.join('; ')})`;
1648
1898
  }
1649
- function invalidateDocumentLazyHtmlProjection(model) {
1650
- if (model) {
1651
- const prepared = preparedModels.get(model);
1652
- if (prepared) prepared.htmlProjection = null;
1653
- }
1899
+ function mlaCitation(sources, suppressAuthor) {
1900
+ return `(${sources.map((source)=>suppressAuthor ? source.year?.trim() || 'n.d.' : citationAuthor(source, 'and')).join('; ')})`;
1654
1901
  }
1655
- function documentLazyChunkContent(model, chunkId) {
1656
- if (!model || !chunkId) return null;
1657
- return preparedModels.get(model)?.payloads.get(chunkId) ?? null;
1902
+ function chicagoCitation(sources, suppressAuthor, suppressYear) {
1903
+ return `(${sources.map((source)=>[
1904
+ suppressAuthor ? '' : citationAuthor(source, 'and'),
1905
+ suppressYear ? '' : source.year?.trim() || 'n.d.'
1906
+ ].filter(Boolean).join(' ')).join('; ')})`;
1658
1907
  }
1659
- function materializeLazyDocumentEditorRoot(root, model) {
1660
- const prepared = model ? preparedModels.get(model) : null;
1661
- if (!prepared) return root;
1662
- const visit = (node)=>{
1663
- if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
1664
- const id = documentChunkId(node);
1665
- if (!id) return node;
1666
- if (documentChunkIsLazy(node)) {
1667
- const payload = prepared.payloads.get(id);
1668
- if (!payload) throw new Error(`The lazy document chunk payload "${id}" is missing.`);
1669
- return {
1670
- ...node,
1671
- content: [
1672
- ...payload
1673
- ]
1674
- };
1675
- }
1676
- if (node.content?.length) prepared.payloads.set(id, node.content);
1677
- return node;
1678
- }
1679
- if (!node.content?.length) return node;
1680
- let changed = false;
1681
- const content = node.content.map((child)=>{
1682
- const next = visit(child);
1683
- if (next !== child) changed = true;
1684
- return next;
1685
- });
1686
- return changed ? {
1687
- ...node,
1688
- content
1689
- } : node;
1690
- };
1691
- return visit(root);
1908
+ function ieeeCitation(sources, allSources) {
1909
+ const indexes = sources.map((source)=>allSources.findIndex((candidate)=>candidate.id === source.id || candidate.tag === source.tag) + 1).filter((index)=>index > 0);
1910
+ return `[${indexes.join(', ')}]`;
1692
1911
  }
1693
- function transferLazyDocumentModelState(previous, next) {
1694
- if (!previous) return;
1695
- const prepared = preparedModels.get(previous);
1696
- if (!prepared) return;
1697
- preparedModels.set(next, {
1698
- htmlProjection: prepared.htmlProjection,
1699
- lazyChunkCount: prepared.lazyChunkCount,
1700
- payloads: prepared.payloads,
1701
- root: null
1702
- });
1912
+ function citationAuthor(source, conjunction) {
1913
+ const contributor = primaryCitationContributor(source);
1914
+ if (contributor?.corporate?.trim()) return contributor.corporate.trim();
1915
+ const names = (contributor?.people ?? []).map((person)=>person.last.trim() || person.first.trim()).filter(Boolean);
1916
+ if (!names.length) return source.title.trim() || source.tag;
1917
+ if (1 === names.length) return names[0];
1918
+ if (2 === names.length) return `${names[0]} ${conjunction} ${names[1]}`;
1919
+ return `${names[0]} et al.`;
1703
1920
  }
1704
- function simpleDocumentNodeSize(node) {
1705
- if ('text' === node.type) return 'string' == typeof node.text && node.text ? node.text.length : null;
1706
- if (!SIMPLE_LAZY_CONTAINER_TYPES.has(node.type)) return null;
1707
- let size = 2;
1708
- for (const child of node.content ?? []){
1709
- const childSize = simpleDocumentNodeSize(child);
1710
- if (null === childSize) return null;
1711
- size += childSize;
1921
+ function bibliographyPersonName(person, style) {
1922
+ const first = [
1923
+ person.first,
1924
+ person.middle
1925
+ ].filter(Boolean).join(' ').trim();
1926
+ const suffix = person.suffix?.trim();
1927
+ if ('ieee' === style) {
1928
+ const initials = [
1929
+ person.first,
1930
+ person.middle
1931
+ ].filter(Boolean).map((value)=>`${Array.from(value ?? '')[0] ?? ''}.`).join(' ');
1932
+ return [
1933
+ initials,
1934
+ person.last,
1935
+ suffix
1936
+ ].filter(Boolean).join(' ');
1712
1937
  }
1713
- return size;
1938
+ return [
1939
+ person.last,
1940
+ first ? `, ${first}` : '',
1941
+ suffix ? `, ${suffix}` : ''
1942
+ ].join('');
1714
1943
  }
1715
- function createPreparedLazyDocumentEditorSource(root, previousPayloads) {
1716
- const payloads = new Map();
1717
- let leafIndex = 0;
1718
- let lazyChunkCount = 0;
1719
- let unsupported = false;
1720
- const visit = (node)=>{
1721
- if ('documentChunk' === node.type && node.attrs?.windowContainer !== true) {
1722
- const id = documentChunkId(node);
1723
- const payload = id ? previousPayloads?.get(id) ?? node.content ?? [] : [];
1724
- if (!id || !payload.length) {
1725
- unsupported = true;
1726
- return node;
1727
- }
1728
- payloads.set(id, payload);
1729
- const currentIndex = leafIndex;
1730
- leafIndex += 1;
1731
- if (currentIndex < DOCUMENT_LAZY_INITIAL_CHUNK_COUNT) return node;
1732
- const placeholder = lazyPlaceholderForContent(id, payload);
1733
- if (!placeholder) {
1734
- unsupported = true;
1735
- return node;
1736
- }
1737
- lazyChunkCount += 1;
1738
- return {
1739
- ...node,
1740
- content: [
1741
- placeholder
1742
- ]
1743
- };
1944
+ function citationContainer(source) {
1945
+ return source.journalName?.trim() || source.publisher?.trim() || source.conferenceName?.trim() || source.institution?.trim() || '';
1946
+ }
1947
+ function citationSwitch(instruction, name) {
1948
+ const expression = new RegExp(`\\\\${name}\\s+"([^"]*)"`, 'i');
1949
+ return expression.exec(instruction)?.[1] ?? '';
1950
+ }
1951
+ function citationTagInstructionValue(tag) {
1952
+ return /^[A-Za-z0-9_:.+-]+$/.test(tag) ? tag : `"${tag.replaceAll('"', '')}"`;
1953
+ }
1954
+ function uniqueCitationTags(tags) {
1955
+ const result = [];
1956
+ const seen = new Set();
1957
+ for (const source of tags){
1958
+ const tag = source.trim();
1959
+ if (!(!tag || seen.has(tag))) {
1960
+ seen.add(tag);
1961
+ result.push(tag);
1744
1962
  }
1745
- if (!node.content?.length) return node;
1746
- let changed = false;
1747
- const content = node.content.map((child)=>{
1748
- const next = visit(child);
1749
- if (next !== child) changed = true;
1750
- return next;
1751
- });
1752
- return changed ? {
1753
- ...node,
1754
- content
1755
- } : node;
1756
- };
1757
- const compactRoot = visit(root);
1758
- if (unsupported || leafIndex <= DOCUMENT_LAZY_INITIAL_CHUNK_COUNT || 0 === lazyChunkCount) return null;
1759
- return {
1760
- htmlProjection: null,
1761
- lazyChunkCount,
1762
- payloads,
1763
- root: compactRoot
1963
+ }
1964
+ return result;
1965
+ }
1966
+ function uniqueCitationId(source, index, usedIds) {
1967
+ const candidate = source?.trim();
1968
+ if (candidate && !usedIds.has(candidate)) {
1969
+ usedIds.add(candidate);
1970
+ return candidate;
1971
+ }
1972
+ let suffix = index;
1973
+ while(usedIds.has(`document-citation-${suffix}`))suffix += 1;
1974
+ const id = `document-citation-${suffix}`;
1975
+ usedIds.add(id);
1976
+ return id;
1977
+ }
1978
+ const DEFAULT_DOCUMENT_COLUMNS = {
1979
+ count: 1,
1980
+ spacing: 12,
1981
+ separator: false
1982
+ };
1983
+ const MAX_COLUMNS = 6;
1984
+ const MIN_COLUMN_PERCENT = 5;
1985
+ function normalizeDocumentColumns(columns) {
1986
+ const customCount = Array.isArray(columns?.custom) ? columns.custom.length : 0;
1987
+ const count = clampInteger(columns?.count, customCount || DEFAULT_DOCUMENT_COLUMNS.count, 1, MAX_COLUMNS);
1988
+ const spacing = clampNumber(columns?.spacing, DEFAULT_DOCUMENT_COLUMNS.spacing, 0, 30);
1989
+ const normalized = {
1990
+ count,
1991
+ spacing: roundOne(spacing),
1992
+ separator: Boolean(columns?.separator)
1764
1993
  };
1994
+ if (count < 2 || !customCount) return normalized;
1995
+ const source = columns?.custom ?? [];
1996
+ const widths = normalizedPercentages(Array.from({
1997
+ length: count
1998
+ }, (_, index)=>finiteNumber(source[index]?.widthPercent, 100 / count)), 100, MIN_COLUMN_PERCENT);
1999
+ normalized.custom = Array.from({
2000
+ length: count
2001
+ }, (_, index)=>({
2002
+ widthPercent: widths[index],
2003
+ spacing: index === count - 1 ? 0 : roundOne(clampNumber(source[index]?.spacing, normalized.spacing, 0, 30))
2004
+ }));
2005
+ return normalized;
2006
+ }
2007
+ function serializeDocumentColumns(columns) {
2008
+ return JSON.stringify(normalizeDocumentColumns(columns));
2009
+ }
2010
+ function parseDocumentColumns(source, legacy = {}, fallback) {
2011
+ if (source?.trim()) try {
2012
+ return normalizeDocumentColumns(JSON.parse(source));
2013
+ } catch {}
2014
+ if (void 0 !== legacy.count || void 0 !== legacy.spacing || void 0 !== legacy.separator) return normalizeDocumentColumns(legacy);
2015
+ return normalizeDocumentColumns(fallback);
1765
2016
  }
1766
- function lazyPlaceholderForContent(chunkId, content) {
1767
- const tapeParts = [];
1768
- const statistics = {
1769
- contentSize: 0,
1770
- paragraphCount: 0
2017
+ function setCustomDocumentColumns(columns, enabled) {
2018
+ const normalized = normalizeDocumentColumns(columns);
2019
+ if (!enabled || normalized.count < 2) return {
2020
+ ...normalized,
2021
+ custom: void 0
1771
2022
  };
1772
- for (const node of content)if (!appendDocumentNodePositionTape(node, tapeParts, statistics)) return null;
1773
- if (statistics.contentSize < 2 || tapeParts[0] !== DOCUMENT_LAZY_POSITION_BOUNDARY || tapeParts.at(-1) !== DOCUMENT_LAZY_POSITION_BOUNDARY) return null;
1774
- tapeParts[0] = '';
1775
- tapeParts[tapeParts.length - 1] = '';
1776
- const filler = tapeParts.join('');
2023
+ if (normalized.custom) return normalized;
2024
+ return normalizeDocumentColumns({
2025
+ ...normalized,
2026
+ custom: Array.from({
2027
+ length: normalized.count
2028
+ }, (_, index)=>({
2029
+ widthPercent: 100 / normalized.count,
2030
+ spacing: index === normalized.count - 1 ? 0 : normalized.spacing
2031
+ }))
2032
+ });
2033
+ }
2034
+ function updateDocumentColumnWidth(columns, index, widthPercent) {
2035
+ const normalized = setCustomDocumentColumns(columns, true);
2036
+ if (!normalized.custom?.[index]) return normalized;
2037
+ const maximum = 100 - MIN_COLUMN_PERCENT * (normalized.custom.length - 1);
2038
+ const target = Math.min(maximum, Math.max(MIN_COLUMN_PERCENT, finiteNumber(widthPercent, 100 / normalized.count)));
2039
+ const otherIndexes = normalized.custom.map((_, itemIndex)=>itemIndex).filter((itemIndex)=>itemIndex !== index);
2040
+ const otherWidths = normalizedPercentages(otherIndexes.map((itemIndex)=>normalized.custom?.[itemIndex].widthPercent ?? 1), 100 - target, MIN_COLUMN_PERCENT);
2041
+ const custom = normalized.custom.map((column, itemIndex)=>{
2042
+ const otherIndex = otherIndexes.indexOf(itemIndex);
2043
+ return {
2044
+ ...column,
2045
+ widthPercent: itemIndex === index ? roundOne(target) : otherWidths[otherIndex]
2046
+ };
2047
+ });
1777
2048
  return {
1778
- type: DOCUMENT_LAZY_BLOCK_NODE,
1779
- attrs: {
1780
- chunkId,
1781
- contentSize: statistics.contentSize,
1782
- paragraphCount: statistics.paragraphCount
1783
- },
1784
- ...filler ? {
1785
- content: [
1786
- {
1787
- type: 'text',
1788
- text: filler
1789
- }
1790
- ]
1791
- } : {
1792
- content: []
1793
- }
2049
+ ...normalized,
2050
+ custom
1794
2051
  };
1795
2052
  }
1796
- function appendDocumentNodePositionTape(node, parts, statistics) {
1797
- if ('text' === node.type) {
1798
- if ('string' != typeof node.text || !node.text) return false;
1799
- parts.push(node.text);
1800
- statistics.contentSize += node.text.length;
1801
- return true;
2053
+ function normalizedPercentages(values, total, minimum) {
2054
+ if (!values.length) return [];
2055
+ const result = Array(values.length).fill(0);
2056
+ const remaining = new Set(values.map((_, index)=>index));
2057
+ let remainingTotal = total;
2058
+ while(remaining.size){
2059
+ const sourceTotal = Array.from(remaining).reduce((sum, index)=>sum + Math.max(0.001, finiteNumber(values[index], 1)), 0);
2060
+ const belowMinimum = Array.from(remaining).filter((index)=>Math.max(0.001, finiteNumber(values[index], 1)) / sourceTotal * remainingTotal < minimum);
2061
+ if (!belowMinimum.length) {
2062
+ for (const index of remaining)result[index] = Math.max(0.001, finiteNumber(values[index], 1)) / sourceTotal * remainingTotal;
2063
+ break;
2064
+ }
2065
+ for (const index of belowMinimum){
2066
+ result[index] = minimum;
2067
+ remaining.delete(index);
2068
+ remainingTotal -= minimum;
2069
+ }
1802
2070
  }
1803
- if (!SIMPLE_LAZY_CONTAINER_TYPES.has(node.type)) return false;
1804
- if ('paragraph' === node.type) statistics.paragraphCount += 1;
1805
- statistics.contentSize += 2;
1806
- parts.push(DOCUMENT_LAZY_POSITION_BOUNDARY);
1807
- for (const child of node.content ?? [])if (!appendDocumentNodePositionTape(child, parts, statistics)) return false;
1808
- parts.push(DOCUMENT_LAZY_POSITION_BOUNDARY);
1809
- return true;
2071
+ const rounded = result.map(roundOne);
2072
+ const adjustmentIndex = rounded.indexOf(Math.max(...rounded));
2073
+ rounded[adjustmentIndex] = roundOne(rounded[adjustmentIndex] + total - rounded.reduce((sum, value)=>sum + value, 0));
2074
+ return rounded;
1810
2075
  }
1811
- function documentChunkIsLazy(node) {
1812
- return 'documentChunk' === node.type && node.attrs?.windowContainer !== true && node.content?.length === 1 && node.content[0]?.type === DOCUMENT_LAZY_BLOCK_NODE;
2076
+ function clampInteger(value, fallback, minimum, maximum) {
2077
+ return Math.min(maximum, Math.max(minimum, Math.round(finiteNumber(value, fallback))));
1813
2078
  }
1814
- function documentChunkId(node) {
1815
- const id = node.attrs?.id;
1816
- return 'string' == typeof id && id ? id : null;
2079
+ function clampNumber(value, fallback, minimum, maximum) {
2080
+ return Math.min(maximum, Math.max(minimum, finiteNumber(value, fallback)));
1817
2081
  }
1818
- function lazyDocumentNow() {
1819
- return globalThis.performance?.now?.() ?? Date.now();
2082
+ function finiteNumber(value, fallback) {
2083
+ return Number.isFinite(value) ? Number(value) : fallback;
1820
2084
  }
1821
- function recordLazyDocumentMeasure(name, start, end, detail) {
1822
- try {
1823
- globalThis.performance?.measure(name, {
1824
- detail,
1825
- end,
1826
- start
1827
- });
1828
- } catch {}
2085
+ function roundOne(value) {
2086
+ return Math.round(10 * value) / 10;
2087
+ }
2088
+ const DEFAULT_DOCUMENT_MARGINS = {
2089
+ top: 25,
2090
+ right: 23,
2091
+ bottom: 25,
2092
+ left: 23
2093
+ };
2094
+ function documentMargins(content) {
2095
+ return {
2096
+ top: validMargin(content.margins?.top, DEFAULT_DOCUMENT_MARGINS.top),
2097
+ right: validMargin(content.margins?.right, DEFAULT_DOCUMENT_MARGINS.right),
2098
+ bottom: validMargin(content.margins?.bottom, DEFAULT_DOCUMENT_MARGINS.bottom),
2099
+ left: validMargin(content.margins?.left, DEFAULT_DOCUMENT_MARGINS.left)
2100
+ };
2101
+ }
2102
+ function clampDocumentMargin(value) {
2103
+ return Math.min(60, Math.max(5, Math.round(10 * value) / 10));
2104
+ }
2105
+ function millimetersToPixels(value) {
2106
+ return 96 * value / 25.4;
2107
+ }
2108
+ function validMargin(value, fallback) {
2109
+ return Number.isFinite(value) ? clampDocumentMargin(value) : fallback;
1829
2110
  }
1830
2111
  const DOCUMENT_MODEL_SCHEMA = 'a3s.office.document';
1831
2112
  const DOCUMENT_MODEL_VERSION = 1;
@@ -1853,7 +2134,7 @@ function work_document_model_createSchemaValidatedWorkDocumentModel(html, root,
1853
2134
  schemaValidatedDocumentModels.set(model, {
1854
2135
  html,
1855
2136
  htmlFingerprint: model.htmlFingerprint,
1856
- initialIntegrityFeatures: nonNegativeInteger(options.initialIntegrityFeatures),
2137
+ initialIntegrityFeatures: work_document_model_nonNegativeInteger(options.initialIntegrityFeatures),
1857
2138
  root
1858
2139
  });
1859
2140
  return model;
@@ -1902,7 +2183,7 @@ function nextRevision(previous) {
1902
2183
  const revision = previous?.schema === DOCUMENT_MODEL_SCHEMA && previous.version === DOCUMENT_MODEL_VERSION && Number.isSafeInteger(previous.revision) && previous.revision > 0 ? previous.revision : 0;
1903
2184
  return revision >= Number.MAX_SAFE_INTEGER ? 1 : revision + 1;
1904
2185
  }
1905
- function nonNegativeInteger(value) {
2186
+ function work_document_model_nonNegativeInteger(value) {
1906
2187
  const number = Number(value);
1907
2188
  return Number.isSafeInteger(number) && number >= 0 ? number : null;
1908
2189
  }
@@ -10843,168 +11124,44 @@ function bindLazyDocumentChildren(node, parent, firstPosition) {
10843
11124
  if (parent.children.length !== children.length) return null;
10844
11125
  let size = 0;
10845
11126
  for(let index = 0; index < children.length; index += 1){
10846
- const child = children[index];
10847
- const element = parent.children.item(index);
10848
- if (!child || !(element instanceof HTMLElement)) return null;
10849
- const childSize = bindLazyDocumentNode(child, element, firstPosition + size);
10850
- if (null === childSize) return null;
10851
- size += childSize;
10852
- }
10853
- return size;
10854
- }
10855
- function bindLazyDocumentTableAttributes(table, node) {
10856
- setBooleanDataset(table, 'officeTableImported', node.attrs?.officeImported === true);
10857
- setStringDataset(table, 'documentVirtualTableId', node.attrs?.virtualTableId);
10858
- setNumberDataset(table, 'documentVirtualTableIndex', node.attrs?.virtualTableIndex);
10859
- setNumberDataset(table, 'documentVirtualTableCount', node.attrs?.virtualTableCount);
10860
- }
10861
- function lazyDocumentPreviewShape(content) {
10862
- const parts = [];
10863
- for (const node of content)appendLazyDocumentPreviewShape(node, parts);
10864
- return parts.join('');
10865
- }
10866
- function appendLazyDocumentPreviewShape(node, parts) {
10867
- const children = node.content ?? [];
10868
- parts.push(node.type, '[', String(children.length), '](');
10869
- for (const child of children)appendLazyDocumentPreviewShape(child, parts);
10870
- parts.push(')');
10871
- }
10872
- function setBooleanDataset(element, name, value) {
10873
- if (value) element.dataset[name] = 'true';
10874
- else delete element.dataset[name];
10875
- }
10876
- function setStringDataset(element, name, value) {
10877
- if ('string' == typeof value && value) element.dataset[name] = value;
10878
- else delete element.dataset[name];
10879
- }
10880
- function setNumberDataset(element, name, value) {
10881
- const number = Number(value);
10882
- if (Number.isSafeInteger(number) && number >= 0) element.dataset[name] = String(number);
10883
- else delete element.dataset[name];
10884
- }
10885
- const statisticsByDocument = new WeakMap();
10886
- function documentWordCount(value) {
10887
- let asciiCount = 0;
10888
- let inAsciiWord = false;
10889
- for(let index = 0; index < value.length; index += 1){
10890
- const code = value.charCodeAt(index);
10891
- const asciiLetterOrNumber = code >= 0x30 && code <= 0x39 || code >= 0x41 && code <= 0x5a || code >= 0x61 && code <= 0x7a;
10892
- if (asciiLetterOrNumber) {
10893
- if (!inAsciiWord) asciiCount += 1;
10894
- inAsciiWord = true;
10895
- continue;
10896
- }
10897
- inAsciiWord = false;
10898
- if (!(code <= 0x7f) && 0xfffc !== code) return unicodeDocumentWordCount(value);
10899
- }
10900
- return asciiCount;
10901
- }
10902
- function unicodeDocumentWordCount(value) {
10903
- let count = 0;
10904
- for (const _match of value.matchAll(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]|[\p{L}\p{N}]+/gu))count += 1;
10905
- return count;
10906
- }
10907
- function documentTextStatistics(editor) {
10908
- const document1 = editor.state.doc;
10909
- const cached = statisticsByDocument.get(document1);
10910
- if (cached) return cached;
10911
- const source = editor.getText({
10912
- blockSeparator: '\n'
10913
- });
10914
- let characterCountWithSpaces = 0;
10915
- let characterCountWithoutSpaces = 0;
10916
- for(let index = 0; index < source.length; index += 1){
10917
- const codePoint = source.codePointAt(index);
10918
- if (void 0 !== codePoint) {
10919
- if (codePoint > 0xffff) index += 1;
10920
- if (codePoint !== "".codePointAt(0) && 0x0a !== codePoint && 0x0d !== codePoint) {
10921
- characterCountWithSpaces += 1;
10922
- if (!isEcmaScriptWhitespace(codePoint)) characterCountWithoutSpaces += 1;
10923
- }
10924
- }
10925
- }
10926
- let paragraphCount = 0;
10927
- document1.descendants((node)=>{
10928
- if (node.type.name === DOCUMENT_LAZY_BLOCK_NODE) {
10929
- const logicalParagraphs = Number(node.attrs.paragraphCount);
10930
- paragraphCount += Number.isSafeInteger(logicalParagraphs) && logicalParagraphs >= 0 ? logicalParagraphs : 0;
10931
- return false;
10932
- }
10933
- if (node.isTextblock) paragraphCount += 1;
10934
- return true;
10935
- });
10936
- const statistics = {
10937
- characterCountWithSpaces,
10938
- characterCountWithoutSpaces,
10939
- paragraphCount,
10940
- wordCount: documentWordCount(source)
10941
- };
10942
- statisticsByDocument.set(document1, statistics);
10943
- return statistics;
10944
- }
10945
- function transferDocumentTextStatistics(previous, next) {
10946
- const statistics = statisticsByDocument.get(previous);
10947
- if (statistics) statisticsByDocument.set(next, statistics);
10948
- }
10949
- function transferChangedDocumentTextStatistics(previous, next, changes) {
10950
- const cached = statisticsByDocument.get(previous);
10951
- if (!cached || !changes.length) return false;
10952
- const statistics = {
10953
- ...cached
10954
- };
10955
- for (const { after, before } of changes){
10956
- applyDocumentTextStatisticsDelta(statistics, simpleDocumentSubtreeStatistics(before), -1);
10957
- applyDocumentTextStatisticsDelta(statistics, simpleDocumentSubtreeStatistics(after), 1);
11127
+ const child = children[index];
11128
+ const element = parent.children.item(index);
11129
+ if (!child || !(element instanceof HTMLElement)) return null;
11130
+ const childSize = bindLazyDocumentNode(child, element, firstPosition + size);
11131
+ if (null === childSize) return null;
11132
+ size += childSize;
10958
11133
  }
10959
- if (Object.values(statistics).some((value)=>value < 0)) return false;
10960
- statisticsByDocument.set(next, statistics);
10961
- return true;
11134
+ return size;
10962
11135
  }
10963
- function simpleDocumentSubtreeStatistics(root) {
10964
- const statistics = {
10965
- characterCountWithSpaces: 0,
10966
- characterCountWithoutSpaces: 0,
10967
- paragraphCount: 0,
10968
- wordCount: 0
10969
- };
10970
- const visit = (node)=>{
10971
- if (node.type.name === DOCUMENT_LAZY_BLOCK_NODE) {
10972
- accumulateDocumentTextStatistics(statistics, node.textContent);
10973
- const logicalParagraphs = Number(node.attrs.paragraphCount);
10974
- statistics.paragraphCount += Number.isSafeInteger(logicalParagraphs) && logicalParagraphs >= 0 ? logicalParagraphs : 0;
10975
- return;
10976
- }
10977
- if (node.isTextblock) {
10978
- accumulateDocumentTextStatistics(statistics, node.textContent);
10979
- statistics.paragraphCount += 1;
10980
- return;
10981
- }
10982
- node.forEach(visit);
10983
- };
10984
- visit(root);
10985
- return statistics;
11136
+ function bindLazyDocumentTableAttributes(table, node) {
11137
+ setBooleanDataset(table, 'officeTableImported', node.attrs?.officeImported === true);
11138
+ setStringDataset(table, 'documentVirtualTableId', node.attrs?.virtualTableId);
11139
+ setNumberDataset(table, 'documentVirtualTableIndex', node.attrs?.virtualTableIndex);
11140
+ setNumberDataset(table, 'documentVirtualTableCount', node.attrs?.virtualTableCount);
10986
11141
  }
10987
- function accumulateDocumentTextStatistics(statistics, source) {
10988
- for(let index = 0; index < source.length; index += 1){
10989
- const codePoint = source.codePointAt(index);
10990
- if (void 0 !== codePoint) {
10991
- if (codePoint > 0xffff) index += 1;
10992
- if (codePoint !== "".codePointAt(0) && 0x0a !== codePoint && 0x0d !== codePoint) {
10993
- statistics.characterCountWithSpaces += 1;
10994
- if (!isEcmaScriptWhitespace(codePoint)) statistics.characterCountWithoutSpaces += 1;
10995
- }
10996
- }
10997
- }
10998
- statistics.wordCount += documentWordCount(source);
11142
+ function lazyDocumentPreviewShape(content) {
11143
+ const parts = [];
11144
+ for (const node of content)appendLazyDocumentPreviewShape(node, parts);
11145
+ return parts.join('');
10999
11146
  }
11000
- function applyDocumentTextStatisticsDelta(target, value, direction) {
11001
- target.characterCountWithSpaces += direction * value.characterCountWithSpaces;
11002
- target.characterCountWithoutSpaces += direction * value.characterCountWithoutSpaces;
11003
- target.paragraphCount += direction * value.paragraphCount;
11004
- target.wordCount += direction * value.wordCount;
11147
+ function appendLazyDocumentPreviewShape(node, parts) {
11148
+ const children = node.content ?? [];
11149
+ parts.push(node.type, '[', String(children.length), '](');
11150
+ for (const child of children)appendLazyDocumentPreviewShape(child, parts);
11151
+ parts.push(')');
11005
11152
  }
11006
- function isEcmaScriptWhitespace(codePoint) {
11007
- return codePoint >= 0x09 && codePoint <= 0x0d || 0x20 === codePoint || 0xa0 === codePoint || 0x1680 === codePoint || codePoint >= 0x2000 && codePoint <= 0x200a || 0x2028 === codePoint || 0x2029 === codePoint || 0x202f === codePoint || 0x205f === codePoint || 0x3000 === codePoint || 0xfeff === codePoint;
11153
+ function setBooleanDataset(element, name, value) {
11154
+ if (value) element.dataset[name] = 'true';
11155
+ else delete element.dataset[name];
11156
+ }
11157
+ function setStringDataset(element, name, value) {
11158
+ if ('string' == typeof value && value) element.dataset[name] = value;
11159
+ else delete element.dataset[name];
11160
+ }
11161
+ function setNumberDataset(element, name, value) {
11162
+ const number = Number(value);
11163
+ if (Number.isSafeInteger(number) && number >= 0) element.dataset[name] = String(number);
11164
+ else delete element.dataset[name];
11008
11165
  }
11009
11166
  const INITIAL_DOCUMENT_CHUNK_WINDOW_SIZE = 2;
11010
11167
  const DOCUMENT_CHUNK_WINDOW_ROOT_MARGIN = '0px';
@@ -12348,15 +12505,33 @@ function resolveDocumentPageFields(pages, sections) {
12348
12505
  ]));
12349
12506
  const sectionPages = new Map();
12350
12507
  for (const page of pages)for (const sectionId of new Set(page.segments.map((segment)=>segment.sectionId)))sectionPages.set(sectionId, (sectionPages.get(sectionId) ?? 0) + 1);
12508
+ const statistics = documentFieldStatisticsFromHtml(sections.map((section)=>section.html).join('\n'));
12509
+ const bookmarkPages = documentBookmarkPageNumbers(pages);
12351
12510
  const now = new Date();
12352
12511
  for (const page of pages)for (const segment of page.segments)segment.html = resolveDocumentFieldsHtml(segment.html, {
12353
12512
  pageNumber: page.pageNumber,
12354
12513
  totalPages: pages.length,
12355
12514
  sectionNumber: sectionNumbers.get(segment.sectionId) ?? 1,
12356
12515
  sectionPages: sectionPages.get(segment.sectionId) ?? 1,
12516
+ wordCount: statistics.wordCount,
12517
+ characterCount: statistics.characterCount,
12518
+ bookmarkPageNumbers: bookmarkPages,
12357
12519
  now
12358
12520
  });
12359
12521
  }
12522
+ function documentBookmarkPageNumbers(pages) {
12523
+ const values = new Map();
12524
+ for (const page of pages)for (const segment of page.segments){
12525
+ const document1 = new DOMParser().parseFromString(segment.html, 'text/html');
12526
+ for (const boundary of Array.from(document1.body.querySelectorAll('[data-document-bookmark-boundary][data-bookmark-kind="start"]'))){
12527
+ const id = boundary.dataset.bookmarkId?.trim();
12528
+ const name = boundary.dataset.bookmarkName?.trim();
12529
+ if (id && !values.has(`id:${id}`)) values.set(`id:${id}`, page.pageNumber);
12530
+ if (name && !values.has(`name:${name.toLowerCase()}`)) values.set(`name:${name.toLowerCase()}`, page.pageNumber);
12531
+ }
12532
+ }
12533
+ return values;
12534
+ }
12360
12535
  function samePhysicalPageLayout(current, next) {
12361
12536
  return current.pageSize === next.pageSize && current.orientation === next.orientation && JSON.stringify(current.pageGeometry) === JSON.stringify(next.pageGeometry) && current.margins.top === next.margins.top && current.margins.right === next.margins.right && current.margins.bottom === next.margins.bottom && current.margins.left === next.margins.left && JSON.stringify(current.pageMargins) === JSON.stringify(next.pageMargins);
12362
12537
  }
@@ -12447,7 +12622,7 @@ const DocumentField = core_Node.create({
12447
12622
  },
12448
12623
  addCommands () {
12449
12624
  return {
12450
- insertDocumentField: (kind)=>(props)=>insertDocumentFieldCommand(props, kind),
12625
+ insertDocumentField: (kind, options = {})=>(props)=>insertDocumentFieldCommand(props, kind, options),
12451
12626
  refreshDocumentFields: (content, options)=>(props)=>refreshDocumentFieldsCommand(props, content, options)
12452
12627
  };
12453
12628
  },
@@ -12456,7 +12631,10 @@ const DocumentField = core_Node.create({
12456
12631
  id: work_document_field_node_hiddenAttribute(''),
12457
12632
  kind: work_document_field_node_hiddenAttribute('page'),
12458
12633
  instruction: work_document_field_node_hiddenAttribute('PAGE'),
12459
- display: work_document_field_node_hiddenAttribute('1')
12634
+ display: work_document_field_node_hiddenAttribute('1'),
12635
+ targetId: work_document_field_node_hiddenAttribute(''),
12636
+ targetName: work_document_field_node_hiddenAttribute(''),
12637
+ orphaned: work_document_field_node_hiddenAttribute(false)
12460
12638
  };
12461
12639
  },
12462
12640
  parseHTML () {
@@ -12467,11 +12645,15 @@ const DocumentField = core_Node.create({
12467
12645
  if (!(node instanceof HTMLElement)) return false;
12468
12646
  const instruction = node.dataset.fieldInstruction?.trim() ?? '';
12469
12647
  const kind = documentFieldKind(node.dataset.fieldKind) ?? docxDocumentFieldKind(instruction) ?? 'page';
12648
+ const targetName = node.dataset.fieldTargetName ?? docxDocumentFieldTarget(instruction) ?? '';
12470
12649
  return {
12471
12650
  id: node.dataset.fieldId ?? '',
12472
12651
  kind,
12473
- instruction: instruction || documentFieldInstruction(kind),
12474
- display: node.dataset.fieldDisplay?.trim() || node.textContent?.trim() || documentFieldLabel(kind)
12652
+ instruction: 'pageReference' === kind && targetName ? documentPageReferenceInstruction(targetName, instruction) : instruction || documentFieldInstruction(kind),
12653
+ display: node.dataset.fieldDisplay?.trim() || node.textContent?.trim() || documentFieldLabel(kind),
12654
+ targetId: node.dataset.fieldTargetId ?? '',
12655
+ targetName,
12656
+ orphaned: 'true' === node.dataset.fieldOrphaned
12475
12657
  };
12476
12658
  }
12477
12659
  }
@@ -12481,14 +12663,23 @@ const DocumentField = core_Node.create({
12481
12663
  const instruction = 'string' == typeof node.attrs.instruction ? node.attrs.instruction.trim() : '';
12482
12664
  const kind = documentFieldKind(node.attrs.kind) ?? docxDocumentFieldKind(instruction) ?? 'page';
12483
12665
  const display = 'string' == typeof node.attrs.display && node.attrs.display.trim() ? node.attrs.display.trim() : documentFieldLabel(kind);
12666
+ const targetName = 'string' == typeof node.attrs.targetName ? node.attrs.targetName : '';
12667
+ const normalizedInstruction = 'pageReference' === kind && targetName ? documentPageReferenceInstruction(targetName, instruction) : instruction || documentFieldInstruction(kind);
12484
12668
  return [
12485
12669
  'span',
12486
12670
  mergeAttributes(HTMLAttributes, {
12487
12671
  'data-document-field': 'true',
12488
12672
  'data-field-id': 'string' == typeof node.attrs.id ? node.attrs.id : '',
12489
12673
  'data-field-kind': kind,
12490
- 'data-field-instruction': instruction || documentFieldInstruction(kind),
12674
+ 'data-field-instruction': normalizedInstruction,
12491
12675
  'data-field-display': display,
12676
+ ...'pageReference' === kind && 'string' == typeof node.attrs.targetId && node.attrs.targetId ? {
12677
+ 'data-field-target-id': node.attrs.targetId
12678
+ } : {},
12679
+ ...'pageReference' === kind && 'string' == typeof node.attrs.targetName && node.attrs.targetName ? {
12680
+ 'data-field-target-name': node.attrs.targetName
12681
+ } : {},
12682
+ 'data-field-orphaned': node.attrs.orphaned ? 'true' : void 0,
12492
12683
  class: 'work-document-field',
12493
12684
  title: documentFieldLabel(kind)
12494
12685
  }),
@@ -12500,16 +12691,26 @@ const DocumentField = core_Node.create({
12500
12691
  return 'string' == typeof node.attrs.display && node.attrs.display.trim() ? node.attrs.display.trim() : documentFieldLabel(kind);
12501
12692
  }
12502
12693
  });
12503
- function insertDocumentFieldCommand({ dispatch, editor, state, tr }, kind) {
12694
+ function insertDocumentFieldCommand({ dispatch, editor, state, tr }, kind, options) {
12504
12695
  const fieldType = editor.schema.nodes.documentField;
12505
12696
  if (!fieldType) return false;
12697
+ const targetName = 'string' == typeof options.targetName ? options.targetName.trim() : '';
12698
+ if ('pageReference' === kind && !targetName) return false;
12506
12699
  if (!dispatch) return true;
12507
- const instruction = documentFieldInstruction(kind);
12700
+ const instruction = documentFieldInstruction(kind, options);
12701
+ const statistics = documentFieldStatisticsFromText(state.doc.textBetween(0, state.doc.content.size, '\n', '\uFFFC'));
12702
+ const context = {
12703
+ ...fallbackContext(state),
12704
+ ...statistics
12705
+ };
12508
12706
  tr.replaceSelectionWith(fieldType.create({
12509
12707
  id: createWorkId('field'),
12510
12708
  kind,
12511
12709
  instruction,
12512
- display: documentFieldDisplay(kind, fallbackContext(state), instruction)
12710
+ display: documentFieldDisplay(kind, context, instruction),
12711
+ targetId: options.targetId?.trim() ?? '',
12712
+ targetName,
12713
+ orphaned: 'pageReference' === kind && !documentBookmarkExists(state, options)
12513
12714
  }), false);
12514
12715
  tr.scrollIntoView();
12515
12716
  return true;
@@ -12529,6 +12730,7 @@ function refreshDocumentFieldsCommand({ editor, state, tr }, content, options =
12529
12730
  ]
12530
12731
  ] : []));
12531
12732
  const now = work_document_field_node_validDate(options.now) ?? new Date();
12733
+ const statistics = documentFieldStatisticsFromText(state.doc.textBetween(0, state.doc.content.size, '\n', '\uFFFC'));
12532
12734
  let fieldIndex = 0;
12533
12735
  state.doc.descendants((node, position)=>{
12534
12736
  if ('documentField' !== node.type.name) return;
@@ -12540,12 +12742,20 @@ function refreshDocumentFieldsCommand({ editor, state, tr }, content, options =
12540
12742
  const context = options.resolveContext?.(position);
12541
12743
  const display = context ? documentFieldDisplay(kind, {
12542
12744
  ...context,
12745
+ wordCount: context.wordCount ?? statistics.wordCount,
12746
+ characterCount: context.characterCount ?? statistics.characterCount,
12747
+ referencePageNumber: 'pageReference' === kind ? resolveBookmarkPageNumber(state, node, options.resolveContext) : context.referencePageNumber,
12543
12748
  now: work_document_field_node_validDate(context.now) ?? now
12544
12749
  }, work_document_field_node_stringAttribute(node.attrs.instruction), work_document_field_node_stringAttribute(node.attrs.display)) : fallbackById.get(work_document_field_node_stringAttribute(node.attrs.id)) ?? fallback;
12545
- if (!display || node.attrs.display === display) return;
12750
+ if (!display) return;
12751
+ const targetName = work_document_field_node_stringAttribute(node.attrs.targetName) || docxDocumentFieldTarget(work_document_field_node_stringAttribute(node.attrs.instruction)) || '';
12752
+ const orphaned = 'pageReference' === kind ? !bookmarkBoundaryForField(state, node, targetName) : false;
12753
+ if (node.attrs.display === display && node.attrs.targetName === targetName && node.attrs.orphaned === orphaned) return;
12546
12754
  tr.setNodeMarkup(position, void 0, {
12547
12755
  ...node.attrs,
12548
- display
12756
+ display,
12757
+ targetName,
12758
+ orphaned
12549
12759
  });
12550
12760
  });
12551
12761
  if (tr.docChanged && false === options.addToHistory) tr.setMeta('addToHistory', false);
@@ -12605,6 +12815,48 @@ function work_document_field_node_validDate(value) {
12605
12815
  function work_document_field_node_stringAttribute(value) {
12606
12816
  return 'string' == typeof value ? value.trim() : '';
12607
12817
  }
12818
+ function documentBookmarkExists(state, options) {
12819
+ const targetId = work_document_field_node_stringAttribute(options.targetId);
12820
+ const targetName = work_document_field_node_stringAttribute(options.targetName).toLowerCase();
12821
+ let found = false;
12822
+ state.doc.descendants((node)=>{
12823
+ if ('documentBookmarkBoundary' === node.type.name && 'start' === node.attrs.kind && (targetId && work_document_field_node_stringAttribute(node.attrs.id) === targetId || targetName && work_document_field_node_stringAttribute(node.attrs.name).toLowerCase() === targetName)) {
12824
+ found = true;
12825
+ return false;
12826
+ }
12827
+ return !found;
12828
+ });
12829
+ return found;
12830
+ }
12831
+ function bookmarkBoundaryForField(state, node, targetName) {
12832
+ const targetId = work_document_field_node_stringAttribute(node.attrs.targetId);
12833
+ const normalizedName = targetName.toLowerCase();
12834
+ let found = null;
12835
+ state.doc.descendants((candidate)=>{
12836
+ if (found || 'documentBookmarkBoundary' !== candidate.type.name || 'start' !== candidate.attrs.kind) return null === found;
12837
+ if (targetId && work_document_field_node_stringAttribute(candidate.attrs.id) === targetId || normalizedName && work_document_field_node_stringAttribute(candidate.attrs.name).toLowerCase() === normalizedName) {
12838
+ found = candidate;
12839
+ return false;
12840
+ }
12841
+ return true;
12842
+ });
12843
+ return found;
12844
+ }
12845
+ function resolveBookmarkPageNumber(state, node, resolveContext) {
12846
+ if (!resolveContext) return null;
12847
+ const targetId = work_document_field_node_stringAttribute(node.attrs.targetId);
12848
+ const targetName = work_document_field_node_stringAttribute(node.attrs.targetName).toLowerCase() || (docxDocumentFieldTarget(work_document_field_node_stringAttribute(node.attrs.instruction)) ?? '').toLowerCase();
12849
+ let page = null;
12850
+ state.doc.descendants((candidate, position)=>{
12851
+ if (null !== page || 'documentBookmarkBoundary' !== candidate.type.name || 'start' !== candidate.attrs.kind) return null === page;
12852
+ if (targetId && work_document_field_node_stringAttribute(candidate.attrs.id) === targetId || targetName && work_document_field_node_stringAttribute(candidate.attrs.name).toLowerCase() === targetName) {
12853
+ page = resolveContext(position)?.pageNumber ?? null;
12854
+ return false;
12855
+ }
12856
+ return true;
12857
+ });
12858
+ return page;
12859
+ }
12608
12860
  const DEFAULT_WRAP_SIDE = 'bothSides';
12609
12861
  const WRAP_COORDINATE_SIZE = 21600;
12610
12862
  const MAXIMUM_WRAP_COORDINATE = 2147483647;
@@ -14956,7 +15208,7 @@ function measuredDocumentBlock({ block, element, from, to }) {
14956
15208
  };
14957
15209
  }
14958
15210
  function shouldKeepDocumentBlockTogether(node) {
14959
- return 'table' === node.type.name || 'blockquote' === node.type.name || 'codeBlock' === node.type.name || 'image' === node.type.name || 'documentNote' === node.type.name;
15211
+ return 'table' === node.type.name || 'blockquote' === node.type.name || 'codeBlock' === node.type.name || 'image' === node.type.name || 'documentTextBox' === node.type.name || 'documentNote' === node.type.name;
14960
15212
  }
14961
15213
  function documentBlockId(sectionPosition, index, position) {
14962
15214
  return `section-${sectionPosition}-block-${index}-${position}`;
@@ -18093,6 +18345,402 @@ function work_document_table_of_contents_node_hiddenAttribute(defaultValue) {
18093
18345
  function work_document_table_of_contents_node_stringAttribute(value) {
18094
18346
  return 'string' == typeof value ? value.trim() : '';
18095
18347
  }
18348
+ const DOCUMENT_TEXT_BOX_DEFAULTS = {
18349
+ id: '',
18350
+ width: 120,
18351
+ height: 45,
18352
+ layout: 'inline',
18353
+ horizontalOffset: null,
18354
+ verticalOffset: null,
18355
+ horizontalReference: 'column',
18356
+ verticalReference: 'paragraph',
18357
+ fill: '#fff2cc',
18358
+ borderColor: '#4472c4',
18359
+ borderWidth: 0.35,
18360
+ padding: 3,
18361
+ verticalAlign: 'top',
18362
+ docPropertiesId: null
18363
+ };
18364
+ const DOCUMENT_TEXT_BOX_LIMITS = {
18365
+ width: {
18366
+ min: 20,
18367
+ max: 558.7
18368
+ },
18369
+ height: {
18370
+ min: 10,
18371
+ max: 558.7
18372
+ },
18373
+ offset: {
18374
+ min: -558.7,
18375
+ max: 558.7
18376
+ },
18377
+ borderWidth: {
18378
+ min: 0,
18379
+ max: 10
18380
+ },
18381
+ padding: {
18382
+ min: 0,
18383
+ max: 25
18384
+ }
18385
+ };
18386
+ const TEXT_BOX_ID_MAX_LENGTH = 160;
18387
+ const TEXT_BOX_COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
18388
+ const TEXT_BOX_MARKER_ATTRIBUTES = [
18389
+ 'id',
18390
+ 'width',
18391
+ 'height',
18392
+ 'layout',
18393
+ 'horizontalOffset',
18394
+ 'verticalOffset',
18395
+ 'horizontalReference',
18396
+ 'verticalReference',
18397
+ 'fill',
18398
+ 'borderColor',
18399
+ 'borderWidth',
18400
+ 'padding',
18401
+ 'verticalAlign',
18402
+ 'docPropertiesId'
18403
+ ];
18404
+ const DocumentTextBox = core_Node.create({
18405
+ name: 'documentTextBox',
18406
+ group: 'block',
18407
+ content: 'inline*',
18408
+ defining: true,
18409
+ isolating: true,
18410
+ selectable: true,
18411
+ addAttributes () {
18412
+ return {
18413
+ id: dataAttribute(''),
18414
+ width: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.width),
18415
+ height: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.height),
18416
+ layout: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.layout),
18417
+ horizontalOffset: nullableDataAttribute(),
18418
+ verticalOffset: nullableDataAttribute(),
18419
+ horizontalReference: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.horizontalReference),
18420
+ verticalReference: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.verticalReference),
18421
+ fill: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.fill),
18422
+ borderColor: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.borderColor),
18423
+ borderWidth: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.borderWidth),
18424
+ padding: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.padding),
18425
+ verticalAlign: dataAttribute(DOCUMENT_TEXT_BOX_DEFAULTS.verticalAlign),
18426
+ docPropertiesId: nullableDataAttribute()
18427
+ };
18428
+ },
18429
+ parseHTML () {
18430
+ return [
18431
+ {
18432
+ tag: 'div[data-document-text-box]',
18433
+ getAttrs: (element)=>{
18434
+ if (!(element instanceof HTMLElement)) return false;
18435
+ return textBoxAttributesFromElement(element);
18436
+ }
18437
+ }
18438
+ ];
18439
+ },
18440
+ renderHTML ({ node, HTMLAttributes }) {
18441
+ const properties = normalizeDocumentTextBoxProperties(node.attrs);
18442
+ return [
18443
+ 'div',
18444
+ mergeAttributes(HTMLAttributes, textBoxDomAttributes(properties), {
18445
+ class: 'work-document-text-box',
18446
+ contenteditable: void 0,
18447
+ style: textBoxCss(properties),
18448
+ role: 'textbox',
18449
+ 'aria-label': '文本框'
18450
+ }),
18451
+ 0
18452
+ ];
18453
+ },
18454
+ renderText ({ node }) {
18455
+ return node.textContent;
18456
+ },
18457
+ addCommands () {
18458
+ return {
18459
+ insertDocumentTextBox: (text = '', options = {})=>({ dispatch, editor, state, tr })=>insertDocumentTextBoxCommand({
18460
+ dispatch,
18461
+ editor,
18462
+ state,
18463
+ tr
18464
+ }, text, options),
18465
+ setDocumentTextBoxProperties: (value, options = {})=>({ chain, state, tr })=>{
18466
+ if (!selectedDocumentTextBox(state)) return false;
18467
+ const attributes = textBoxAttributesForChanges(value);
18468
+ if (!Object.keys(attributes).length) return false;
18469
+ closeHistory(tr);
18470
+ let commandChain = chain();
18471
+ if (false !== options.restoreFocus) commandChain = commandChain.focus();
18472
+ return commandChain.updateAttributes(this.name, attributes).run();
18473
+ },
18474
+ deleteDocumentTextBox: (options = {})=>({ dispatch, editor, state, tr })=>{
18475
+ const selected = selectedDocumentTextBox(state);
18476
+ if (!selected) return false;
18477
+ if (!dispatch) return true;
18478
+ closeHistory(tr);
18479
+ tr.delete(selected.position, selected.position + selected.node.nodeSize);
18480
+ tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(selected.position, tr.doc.content.size)), -1));
18481
+ dispatch(tr.scrollIntoView());
18482
+ if (false !== options.restoreFocus) editor.view.focus();
18483
+ return true;
18484
+ }
18485
+ };
18486
+ },
18487
+ addKeyboardShortcuts () {
18488
+ return {
18489
+ Backspace: ()=>deleteEmptyDocumentTextBox(this.editor),
18490
+ Delete: ()=>deleteEmptyDocumentTextBox(this.editor)
18491
+ };
18492
+ },
18493
+ addProseMirrorPlugins () {
18494
+ return [
18495
+ new Plugin({
18496
+ appendTransaction: (transactions, _oldState, state)=>{
18497
+ if (!transactions.some((transaction)=>transaction.docChanged)) return null;
18498
+ const seen = new Set();
18499
+ const updates = [];
18500
+ state.doc.descendants((node, position)=>{
18501
+ if (node.type.name !== this.name) return;
18502
+ const current = normalizeDocumentTextBoxId(node.attrs.id);
18503
+ const id = current && !seen.has(current) ? current : createWorkId('text-box');
18504
+ seen.add(id);
18505
+ if (id !== node.attrs.id) updates.push({
18506
+ position,
18507
+ id
18508
+ });
18509
+ });
18510
+ if (!updates.length) return null;
18511
+ const transaction = state.tr;
18512
+ for (const update of updates){
18513
+ const node = state.doc.nodeAt(update.position);
18514
+ if (node) transaction.setNodeMarkup(update.position, void 0, {
18515
+ ...node.attrs,
18516
+ id: update.id
18517
+ });
18518
+ }
18519
+ transaction.setMeta('addToHistory', false);
18520
+ return transaction;
18521
+ }
18522
+ })
18523
+ ];
18524
+ }
18525
+ });
18526
+ function documentTextBoxProperties(editor) {
18527
+ return normalizeDocumentTextBoxProperties(editor.getAttributes('documentTextBox'));
18528
+ }
18529
+ function normalizeDocumentTextBoxProperties(value) {
18530
+ return {
18531
+ id: normalizeDocumentTextBoxId(value.id),
18532
+ width: boundedNumber(value.width, DOCUMENT_TEXT_BOX_DEFAULTS.width, DOCUMENT_TEXT_BOX_LIMITS.width.min, DOCUMENT_TEXT_BOX_LIMITS.width.max),
18533
+ height: boundedNumber(value.height, DOCUMENT_TEXT_BOX_DEFAULTS.height, DOCUMENT_TEXT_BOX_LIMITS.height.min, DOCUMENT_TEXT_BOX_LIMITS.height.max),
18534
+ layout: 'floating' === value.layout ? 'floating' : 'inline',
18535
+ horizontalOffset: nullableBoundedNumber(value.horizontalOffset, DOCUMENT_TEXT_BOX_LIMITS.offset.min, DOCUMENT_TEXT_BOX_LIMITS.offset.max),
18536
+ verticalOffset: nullableBoundedNumber(value.verticalOffset, DOCUMENT_TEXT_BOX_LIMITS.offset.min, DOCUMENT_TEXT_BOX_LIMITS.offset.max),
18537
+ horizontalReference: textBoxHorizontalReference(value.horizontalReference),
18538
+ verticalReference: textBoxVerticalReference(value.verticalReference),
18539
+ fill: normalizeTextBoxFill(value.fill),
18540
+ borderColor: normalizeTextBoxBorderColor(value.borderColor),
18541
+ borderWidth: boundedNumber(value.borderWidth, DOCUMENT_TEXT_BOX_DEFAULTS.borderWidth, DOCUMENT_TEXT_BOX_LIMITS.borderWidth.min, DOCUMENT_TEXT_BOX_LIMITS.borderWidth.max),
18542
+ padding: boundedNumber(value.padding, DOCUMENT_TEXT_BOX_DEFAULTS.padding, DOCUMENT_TEXT_BOX_LIMITS.padding.min, DOCUMENT_TEXT_BOX_LIMITS.padding.max),
18543
+ verticalAlign: textBoxVerticalAlign(value.verticalAlign),
18544
+ docPropertiesId: nullableInteger(value.docPropertiesId, 0, 0xffffffff)
18545
+ };
18546
+ }
18547
+ function textBoxCss(value) {
18548
+ const properties = normalizeDocumentTextBoxProperties(value);
18549
+ return [
18550
+ `--work-document-text-box-width:${work_document_text_box_formatNumber(properties.width)}mm`,
18551
+ `--work-document-text-box-height:${work_document_text_box_formatNumber(properties.height)}mm`,
18552
+ `--work-document-text-box-padding:${work_document_text_box_formatNumber(properties.padding)}mm`,
18553
+ `--work-document-text-box-fill:${properties.fill}`,
18554
+ `--work-document-text-box-border-color:${'none' === properties.borderColor ? 'transparent' : properties.borderColor}`,
18555
+ `--work-document-text-box-border-width:${work_document_text_box_formatNumber(properties.borderWidth)}mm`,
18556
+ `--work-document-text-box-vertical-align:${properties.verticalAlign}`,
18557
+ ...'floating' === properties.layout ? [
18558
+ `--work-document-text-box-horizontal-offset:${formatNullableNumber(properties.horizontalOffset)}mm`,
18559
+ `--work-document-text-box-vertical-offset:${formatNullableNumber(properties.verticalOffset)}mm`
18560
+ ] : []
18561
+ ].join(';');
18562
+ }
18563
+ function textBoxDomAttributes(value) {
18564
+ const properties = normalizeDocumentTextBoxProperties(value);
18565
+ return {
18566
+ 'data-document-text-box': 'true',
18567
+ 'data-text-box-id': properties.id || void 0,
18568
+ 'data-text-box-width': work_document_text_box_formatNumber(properties.width),
18569
+ 'data-text-box-height': work_document_text_box_formatNumber(properties.height),
18570
+ 'data-text-box-layout': properties.layout,
18571
+ 'data-text-box-horizontal-offset': null === properties.horizontalOffset ? void 0 : work_document_text_box_formatNumber(properties.horizontalOffset),
18572
+ 'data-text-box-vertical-offset': null === properties.verticalOffset ? void 0 : work_document_text_box_formatNumber(properties.verticalOffset),
18573
+ 'data-text-box-horizontal-reference': properties.horizontalReference,
18574
+ 'data-text-box-vertical-reference': properties.verticalReference,
18575
+ 'data-text-box-fill': properties.fill,
18576
+ 'data-text-box-border-color': properties.borderColor,
18577
+ 'data-text-box-border-width': work_document_text_box_formatNumber(properties.borderWidth),
18578
+ 'data-text-box-padding': work_document_text_box_formatNumber(properties.padding),
18579
+ 'data-text-box-vertical-align': properties.verticalAlign,
18580
+ 'data-text-box-doc-properties-id': null === properties.docPropertiesId ? void 0 : String(properties.docPropertiesId)
18581
+ };
18582
+ }
18583
+ function insertDocumentTextBoxCommand({ dispatch, editor, state, tr }, text, options) {
18584
+ const section = activeDocumentSectionFromState(state);
18585
+ const textBoxType = editor.schema.nodes.documentTextBox;
18586
+ const paragraphType = editor.schema.nodes.paragraph;
18587
+ if (!section || !textBoxType || !paragraphType) return false;
18588
+ const child = work_document_text_box_activeSectionChild(section, state.selection.from);
18589
+ if (!child) return false;
18590
+ if (!dispatch) return true;
18591
+ const properties = normalizeDocumentTextBoxProperties({
18592
+ ...DOCUMENT_TEXT_BOX_DEFAULTS,
18593
+ ...options,
18594
+ id: options.id || createWorkId('text-box')
18595
+ });
18596
+ const content = text ? editor.schema.text(text) : void 0;
18597
+ const textBox = textBoxType.create(properties, content);
18598
+ const insertPosition = section.position + 1 + child.offset + child.nodeSize;
18599
+ tr.insert(insertPosition, textBox);
18600
+ const selectionPosition = insertPosition + 1;
18601
+ if (child.index === section.node.childCount - 1) {
18602
+ const paragraphPosition = insertPosition + textBox.nodeSize;
18603
+ tr.insert(paragraphPosition, paragraphType.create());
18604
+ }
18605
+ tr.setSelection(TextSelection.near(tr.doc.resolve(selectionPosition)));
18606
+ tr.scrollIntoView();
18607
+ return true;
18608
+ }
18609
+ function selectedDocumentTextBox(state) {
18610
+ const { $from } = state.selection;
18611
+ for(let depth = $from.depth; depth > 0; depth -= 1){
18612
+ const node = $from.node(depth);
18613
+ if ('documentTextBox' === node.type.name) return {
18614
+ node,
18615
+ position: $from.before(depth)
18616
+ };
18617
+ }
18618
+ if (state.selection instanceof NodeSelection && 'documentTextBox' === state.selection.node.type.name) return {
18619
+ node: state.selection.node,
18620
+ position: state.selection.from
18621
+ };
18622
+ return null;
18623
+ }
18624
+ function deleteEmptyDocumentTextBox(editor) {
18625
+ const selected = selectedDocumentTextBox(editor.state);
18626
+ if (!selected || selected.node.content.size > 0) return false;
18627
+ return editor.commands.deleteDocumentTextBox();
18628
+ }
18629
+ function textBoxAttributesForChanges(value) {
18630
+ const attributes = {};
18631
+ for (const name of TEXT_BOX_MARKER_ATTRIBUTES)if (name in value) attributes[name] = normalizeDocumentTextBoxProperties({
18632
+ ...DOCUMENT_TEXT_BOX_DEFAULTS,
18633
+ ...value
18634
+ })[name];
18635
+ return attributes;
18636
+ }
18637
+ function textBoxAttributesFromElement(element) {
18638
+ return {
18639
+ id: element.dataset.textBoxId ?? '',
18640
+ width: element.dataset.textBoxWidth,
18641
+ height: element.dataset.textBoxHeight,
18642
+ layout: element.dataset.textBoxLayout,
18643
+ horizontalOffset: element.dataset.textBoxHorizontalOffset,
18644
+ verticalOffset: element.dataset.textBoxVerticalOffset,
18645
+ horizontalReference: element.dataset.textBoxHorizontalReference,
18646
+ verticalReference: element.dataset.textBoxVerticalReference,
18647
+ fill: element.dataset.textBoxFill,
18648
+ borderColor: element.dataset.textBoxBorderColor,
18649
+ borderWidth: element.dataset.textBoxBorderWidth,
18650
+ padding: element.dataset.textBoxPadding,
18651
+ verticalAlign: element.dataset.textBoxVerticalAlign,
18652
+ docPropertiesId: element.dataset.textBoxDocPropertiesId
18653
+ };
18654
+ }
18655
+ function documentTextBoxPropertiesFromElement(element) {
18656
+ return normalizeDocumentTextBoxProperties({
18657
+ id: element.getAttribute('data-text-box-id'),
18658
+ width: element.getAttribute('data-text-box-width'),
18659
+ height: element.getAttribute('data-text-box-height'),
18660
+ layout: element.getAttribute('data-text-box-layout'),
18661
+ horizontalOffset: element.getAttribute('data-text-box-horizontal-offset'),
18662
+ verticalOffset: element.getAttribute('data-text-box-vertical-offset'),
18663
+ horizontalReference: element.getAttribute('data-text-box-horizontal-reference'),
18664
+ verticalReference: element.getAttribute('data-text-box-vertical-reference'),
18665
+ fill: element.getAttribute('data-text-box-fill'),
18666
+ borderColor: element.getAttribute('data-text-box-border-color'),
18667
+ borderWidth: element.getAttribute('data-text-box-border-width'),
18668
+ padding: element.getAttribute('data-text-box-padding'),
18669
+ verticalAlign: element.getAttribute('data-text-box-vertical-align'),
18670
+ docPropertiesId: element.getAttribute('data-text-box-doc-properties-id')
18671
+ });
18672
+ }
18673
+ function dataAttribute(defaultValue) {
18674
+ return {
18675
+ default: defaultValue,
18676
+ parseHTML: ()=>defaultValue,
18677
+ rendered: false
18678
+ };
18679
+ }
18680
+ function nullableDataAttribute() {
18681
+ return {
18682
+ default: null,
18683
+ parseHTML: ()=>null,
18684
+ rendered: false
18685
+ };
18686
+ }
18687
+ function normalizeDocumentTextBoxId(value) {
18688
+ return 'string' == typeof value ? value.trim().slice(0, TEXT_BOX_ID_MAX_LENGTH) : '';
18689
+ }
18690
+ function normalizeTextBoxFill(value) {
18691
+ if ('transparent' === value) return 'transparent';
18692
+ return normalizeTextBoxColor(value, DOCUMENT_TEXT_BOX_DEFAULTS.fill);
18693
+ }
18694
+ function normalizeTextBoxBorderColor(value) {
18695
+ if ('none' === value) return 'none';
18696
+ return normalizeTextBoxColor(value, DOCUMENT_TEXT_BOX_DEFAULTS.borderColor);
18697
+ }
18698
+ function normalizeTextBoxColor(value, fallback) {
18699
+ if ('string' != typeof value) return fallback;
18700
+ const normalized = value.trim().toLowerCase();
18701
+ return TEXT_BOX_COLOR_PATTERN.test(normalized) ? normalized : fallback;
18702
+ }
18703
+ function textBoxHorizontalReference(value) {
18704
+ return 'margin' === value || 'page' === value ? value : 'column';
18705
+ }
18706
+ function textBoxVerticalReference(value) {
18707
+ return 'margin' === value || 'page' === value ? value : 'paragraph';
18708
+ }
18709
+ function textBoxVerticalAlign(value) {
18710
+ return 'center' === value || 'bottom' === value ? value : 'top';
18711
+ }
18712
+ function boundedNumber(value, fallback, min, max) {
18713
+ const number = 'number' == typeof value ? value : Number(value);
18714
+ if (!Number.isFinite(number)) return fallback;
18715
+ return Number(Math.min(max, Math.max(min, number)).toFixed(2));
18716
+ }
18717
+ function nullableBoundedNumber(value, min, max) {
18718
+ if (null == value || '' === value) return null;
18719
+ return boundedNumber(value, 0, min, max);
18720
+ }
18721
+ function nullableInteger(value, min, max) {
18722
+ if (null == value || '' === value) return null;
18723
+ const number = 'number' == typeof value ? value : Number(value);
18724
+ return Number.isSafeInteger(number) ? Math.min(max, Math.max(min, number)) : null;
18725
+ }
18726
+ function work_document_text_box_formatNumber(value) {
18727
+ return Number(value.toFixed(2)).toString();
18728
+ }
18729
+ function formatNullableNumber(value) {
18730
+ return null === value ? '0' : work_document_text_box_formatNumber(value);
18731
+ }
18732
+ function work_document_text_box_activeSectionChild(section, selectionPosition) {
18733
+ const relativePosition = Math.max(0, selectionPosition - section.position - 1);
18734
+ let active = null;
18735
+ section.node.forEach((node, offset, index)=>{
18736
+ if (relativePosition >= offset) active = {
18737
+ index,
18738
+ offset,
18739
+ nodeSize: node.nodeSize
18740
+ };
18741
+ });
18742
+ return active;
18743
+ }
18096
18744
  const BORDER_EDGES = [
18097
18745
  'top',
18098
18746
  'right',
@@ -20021,6 +20669,7 @@ function createWorkDocumentExtensions(options = {}) {
20021
20669
  minHeight: 40
20022
20670
  }
20023
20671
  }),
20672
+ DocumentTextBox,
20024
20673
  TableKit.configure({
20025
20674
  table: false,
20026
20675
  tableCell: false,
@@ -21913,7 +22562,7 @@ async function importWorkDocumentFile(file, extension, context) {
21913
22562
  recordDocumentImportMeasure('a3s-office.document.mammoth', mammothStartedAt, documentImportNow());
21914
22563
  context?.controller.report('parsing', 0.8);
21915
22564
  const markersStartedAt = documentImportNow();
21916
- html = prepared ? applyDocxSectionsToHtml(result.value, prepared.sections, prepared.captionMarkers, prepared.bookmarkMarkers, prepared.changeMarkers, prepared.commentMarkers, prepared.fieldMarkers, prepared.tableOfContentsMarkers, prepared.indexMarkers, prepared.equationMarkers, prepared.citationMarkers, prepared.listMarkers, prepared.numberingChangeMarkers, prepared.imageLayoutMarkers, prepared.paragraphIdentityMarkers, prepared.paragraphFormattingChangeMarkers, prepared.paragraphAlignmentMarkers, prepared.runFormattingMarkers, prepared.paragraphDirectionMarkers, prepared.paragraphIndentMarkers, prepared.paragraphSpacingMarkers, prepared.paragraphBorderMarkers, prepared.paragraphShadingMarkers, prepared.paragraphPaginationMarkers, prepared.bibliography, prepared.tabStopMarkers, prepared.tableCellMarkers, prepared.tableRowMarkers, prepared.tableSizingMarkers) : result.value;
22565
+ html = prepared ? applyDocxSectionsToHtml(result.value, prepared.sections, prepared.captionMarkers, prepared.bookmarkMarkers, prepared.changeMarkers, prepared.commentMarkers, prepared.fieldMarkers, prepared.tableOfContentsMarkers, prepared.indexMarkers, prepared.equationMarkers, prepared.citationMarkers, prepared.listMarkers, prepared.numberingChangeMarkers, prepared.imageLayoutMarkers, prepared.paragraphIdentityMarkers, prepared.paragraphFormattingChangeMarkers, prepared.paragraphAlignmentMarkers, prepared.runFormattingMarkers, prepared.paragraphDirectionMarkers, prepared.paragraphIndentMarkers, prepared.paragraphSpacingMarkers, prepared.paragraphBorderMarkers, prepared.paragraphShadingMarkers, prepared.paragraphPaginationMarkers, prepared.bibliography, prepared.tabStopMarkers, prepared.tableCellMarkers, prepared.tableRowMarkers, prepared.tableSizingMarkers, prepared.textBoxMarkers) : result.value;
21917
22566
  recordDocumentImportMeasure('a3s-office.document.markers', markersStartedAt, documentImportNow());
21918
22567
  const layout = prepared ? {
21919
22568
  ...documentContentLayoutProperties(prepared.sections[0].layout),
@@ -22111,4 +22760,4 @@ function registerDocumentPageSurfaceGeometry(element, provider) {
22111
22760
  function documentPageSurfaceGeometryForElement(element) {
22112
22761
  return documentPageSurfaceProviders.get(element)?.() ?? null;
22113
22762
  }
22114
- export { DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DEFAULT_DOCUMENT_TABLE_GEOMETRY, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, DOCUMENT_CHUNK_HYDRATION_META, DOCUMENT_CHUNK_PAGINATION_META, DOCUMENT_CHUNK_VISIBLE_IDS_META, DOCUMENT_LEGACY_TEXT_EFFECT_NAMES, DOCUMENT_LEGACY_TEXT_EMBOSS_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_IMPRINT_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_OUTLINE_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_SHADOW_ATTRIBUTE, DOCUMENT_OPEN_TYPE_ATTRIBUTE, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PAGE_MARGIN_KEYS, DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES, DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES, DOCUMENT_STRIKE_STYLE_ATTRIBUTE, DOCUMENT_TABLE_ROW_ID_ATTRIBUTE, DOCUMENT_TABLE_ROW_TEXT_ID_ATTRIBUTE, DOCUMENT_TABLE_STYLE_OPTIONS, DOCUMENT_UNDERLINE_STYLE_ATTRIBUTE, DocumentCharacterFormatting, DocumentEquation, DocumentFontFamily, DocumentHighlight, DocumentImage, DocumentParagraphFormatting, DocumentScriptFontFormatting, DocumentStrike, DocumentSubscript, DocumentSuperscript, DocumentTableRowIdentity, DocumentTextStyle, DocumentUnderline, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, MAX_DOCUMENT_NUMBERING_START, activeDocumentBookmark, activeDocumentSection, activeDocumentTableStyle, applyDocumentImageCropToElement, applyDocumentImageIdentityToElement, applyDocumentImageLayerToElement, applyDocumentImageTransformToElement, applyDocumentImageWrapContourToElement, applyDocumentPageGeometry, applyDocumentTableGeometryToElement, applyDocumentTableRowIdentityToElement, applyDocumentTextCaseStyle, canChangeDocumentIndent, canInsertDocumentComment, canSetDocumentTableRowRepeatHeader, clampDocumentMargin, collectDocumentChanges, collectDocumentCommentAnchors, collectDocumentNotes, collectDocumentTextLayoutParagraphs, createDocumentBibliography, createDocumentEquationElement, createDocumentImageIdentityRegistry, createDocumentNoteElement, createSchemaDerivedWorkDocumentModel, createWorkDocumentBlob, createWorkDocumentExtensions, createWorkDocumentModel, createWorkOfficeDocumentCollaborationBinding as createOfficeDocumentCollaborationBinding, defaultDocumentImageTransform, defaultDocumentImageWrapContour, documentAutoLineHeight, documentBookmarkNameExists, documentBookmarkReferenceInstruction, documentBulletListStyle, documentCaptionKind, documentCaptionLabel, documentCharacterPositionDomAttributes, documentCharacterPositionHalfPointsFromElement, documentCharacterPositionPoints, documentCharacterScaleDomAttributes, documentCharacterScalePercentFromElement, documentCharacterSpacingDomAttributes, documentCharacterSpacingPoints, documentCharacterSpacingTwipsFromElement, documentChunkMountedIds, documentCitationCount, documentCitationInstruction, documentCitationStyle, documentCitationStyleDetails, documentCitationTags, documentCitationTagsFromInstruction, documentCommentDraftRange, documentCommentViews, documentContentLayoutProperties, documentEmphasisMarkDomAttributes, documentEmphasisMarkFromElement, documentEquationFromElement, documentEquationText, documentHasIndex, documentHasTableOfContents, documentHiddenTextDomAttributes, documentHiddenTextFromElement, documentHiddenTextKeyboardShortcut, documentImageCropFromElement, documentImageIdentityFromElement, documentImageLayerFromElement, documentImageLayoutFromElement, documentImageLayoutOptions, documentImagePositionFromElement, documentImageProperties, documentImageTransformFromElement, documentImageWrapContourFromElement, documentInitialSectionLayout, documentKerningDomAttributes, documentKerningIsEffective, documentKerningThresholdHalfPointsFromElement, documentKerningThresholdPoints, documentLazyHtmlChunkFragment, documentLazyHtmlProjection, documentLazyHtmlProjectionFingerprint, documentLegacyTextEffectsConflict, documentLegacyTextEffectsCss, documentLegacyTextEffectsDomAttributes, documentLegacyTextEffectsFromElement, documentLegacyTextEffectsFromTextStyleAttributes, documentModelForContent, documentModelForHtml, documentModelHasTrustedInitialIntegrityFeatures, documentModelUsesWindowing, documentNoteKey, documentNoteKind, documentOpenTypeCssProperties, documentOpenTypeDomAttributes, documentOpenTypeFeaturesFromElement, documentOrderedListState, documentPageBordersVisible, documentPageChromeLegacyFields, documentPageGeometryForLayout, documentPageHorizontalMarginTwips, documentPageMarginBody, documentPageMarginsForLayout, documentPageMetrics, documentPageSurfaceGeometryForElement, documentPaperSizeForGeometry, documentParagraphDirection, documentParagraphIndent, documentParagraphPagination, documentParagraphSpacing, documentParagraphTabStops, documentSectionById, documentSectionDomAttributes, documentSections, documentStrikeDomAttributes, documentStrikeFormattingFromElement, documentStrikeStyle, documentTabLeaderLabel, documentTableBordersFromElement, documentTableCellFormat, documentTableCellMarginOverridesFromElement, documentTableColumnPercentagesFromElement, documentTableGeometryFromElement, documentTableHorizontalAlignment, documentTableRowIdentityFromElement, documentTableRowOptions, documentTableSizing, documentTextCaseFromWordFlags, documentTextCaseKeyboardShortcuts, documentTextLayoutBatches, documentTextStatistics, documentTransactionsOnlyHydrateChunks, documentUnderlineColor, documentUnderlineDomAttributes, documentUnderlineFormattingFromElement, documentUnderlineKeyboardShortcuts, documentUnderlineStyle, documentWordLineHeightFactor, docxBookmarkReferenceTarget, docxDocumentFieldKind, editorDocumentBookmarkReferenceTargets, editorDocumentCaptionTargets, fileNameWithoutExtension, forgetWorkSourceBlob as forgetSourceBlob, importWorkDocumentFile, importedDocumentCharacterFormatting, initializeWorkOfficeDocumentCollaboration as initializeOfficeDocumentCollaboration, invalidateDocumentLazyHtmlProjection, isContourImageLayout, isDocumentCharacterFormatMark, isDocumentOpenTypeFeaturePatch, isValidDocumentCitationTag, materializeLazyDocumentEditorRoot, measureDocumentLayoutBlocksIncrementally, millimetersToPixels, mountWorkLiveDocumentCapture, moveWorkSourceBlob, nextDocumentTabAlignment, normalizeDocumentBookmarkName, normalizeDocumentBookmarkNativeId, normalizeDocumentBookmarkReferencesHtml, normalizeDocumentBookmarksHtml, normalizeDocumentCaptionsHtml, normalizeDocumentCharacterPositionHalfPoints, normalizeDocumentCharacterScalePercent, normalizeDocumentCharacterSpacingTwips, normalizeDocumentCitationsHtml, normalizeDocumentColumns, normalizeDocumentEmphasisMark, normalizeDocumentEquation, normalizeDocumentFieldsHtml, normalizeDocumentHiddenText, normalizeDocumentHtml, normalizeDocumentImageAlignment, normalizeDocumentImageCrop, normalizeDocumentImageIdentity, normalizeDocumentImageLayer, normalizeDocumentImageLayoutOptions, normalizeDocumentImagePosition, normalizeDocumentImageTransform, normalizeDocumentImageWrapContour, normalizeDocumentImageWrapSide, normalizeDocumentKerningThresholdHalfPoints, normalizeDocumentLegacyTextEffect, normalizeDocumentLegacyTextEffects, normalizeDocumentNotesHtml, normalizeDocumentOpenTypeFeatures, normalizeDocumentOpenTypeLigatures, normalizeDocumentOpenTypeNumberForm, normalizeDocumentOpenTypeNumberSpacing, normalizeDocumentOpenTypeStylisticSets, normalizeDocumentPageBorders, normalizeDocumentPageChrome, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, normalizeDocumentPaperSource, normalizeDocumentParagraphIndent, normalizeDocumentStrikeStyle, normalizeDocumentTabStops, normalizeDocumentTableBorderStyle, normalizeDocumentTableBorderWidth, normalizeDocumentTableRowHeightRule, normalizeDocumentTableRowIdentity, normalizeDocumentTableVerticalAlign, normalizeDocumentTextCase, normalizeDocumentUnderlineColor, normalizeDocumentUnderlineStyle, normalizeTableColor, normalizedTabPosition, numberingTypeFromFormat, pageTwipsToMillimeters, parseDocumentCharacterFormatting, parseDocumentNumberingChange, parseDocumentOpenTypeFeatures, parseDocumentParagraphFormatting, patchDocumentLazyHtmlProjection, patchDocumentOpenTypeFeatures, positionWorkLiveDocumentCapture, readWorkOfficeDocumentCollaboration as readOfficeDocumentCollaboration, readWorkSourceBlob as readSourceBlob, registerDocumentPageSurfaceGeometry, rememberWorkSourceBlob as registerSourceBlob, renderDocumentAutoLineHeight, renderDocumentTableBorders, renderDocumentTableCellMarginOverrides, resolveAllDocumentChanges, resolveDocumentPageBorders, resolveDocumentPageChrome, resolveDocumentPageMargins, resolveDocumentPageSize, resolveWorkDocumentEditorInput, retainAnchoredDocumentComments, sanitizeDocumentPageChromeHtml, selectedDocumentChunkId, selectedDocumentIndexDraft, selectedDocumentIndexEntry, selectedDocumentIndexOptions, selectedDocumentTableOfContentsOptions, serializeDocumentCharacterFormatting, serializeDocumentNumberingChange, serializeDocumentOpenTypeFeatures, serializeDocumentParagraphFormatting, serializeDocumentTabStops, serializeWorkDocumentNode, setCustomDocumentColumns, supportedDocxBookmarkReferenceInstruction, syncDocumentContentFromHtml, transferChangedDocumentTextStatistics, uniqueDocumentImageIdentity, updateDocumentColumnWidth, updateDocumentCustomPageMillimeters, updateDocumentGutterPosition, updateDocumentMirrorMargins, updateDocumentPageChromeVariant, updateDocumentPageMarginMillimeters, updateDocumentPageMarginMode, updateDocumentPageOrientation, updateDocumentPaperSizePreset, validateDocumentBookmarkName, windowDocumentModel, workDocumentSchema, workOfficeDocumentCollaborationFragment as officeDocumentCollaborationFragment, work_document_model_codec_createWorkDocumentModelFromContent as createWorkDocumentModelFromContent, work_document_model_codec_materializeWorkDocumentContent as materializeWorkDocumentContent, work_document_model_createSchemaValidatedWorkDocumentModel as createSchemaValidatedWorkDocumentModel, work_document_page_margins_twipsToMillimeters, work_file_download_downloadBlob, work_file_download_safeFileName, wrapsBesideImage };
22763
+ export { DEFAULT_DOCUMENT_TABLE_CELL_FORMAT, DEFAULT_DOCUMENT_TABLE_CELL_MARGINS, DEFAULT_DOCUMENT_TABLE_GEOMETRY, DOCUMENT_BOOKMARK_DUPLICATE_MESSAGE, DOCUMENT_CHUNK_HYDRATION_META, DOCUMENT_CHUNK_PAGINATION_META, DOCUMENT_CHUNK_VISIBLE_IDS_META, DOCUMENT_LEGACY_TEXT_EFFECT_NAMES, DOCUMENT_LEGACY_TEXT_EMBOSS_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_IMPRINT_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_OUTLINE_ATTRIBUTE, DOCUMENT_LEGACY_TEXT_SHADOW_ATTRIBUTE, DOCUMENT_OPEN_TYPE_ATTRIBUTE, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PAGE_MARGIN_KEYS, DOCUMENT_PARAGRAPH_CHANGE_ATTRIBUTES, DOCUMENT_PARAGRAPH_FORMAT_ATTRIBUTES, DOCUMENT_STRIKE_STYLE_ATTRIBUTE, DOCUMENT_TABLE_ROW_ID_ATTRIBUTE, DOCUMENT_TABLE_ROW_TEXT_ID_ATTRIBUTE, DOCUMENT_TABLE_STYLE_OPTIONS, DOCUMENT_TEXT_BOX_DEFAULTS, DOCUMENT_TEXT_BOX_LIMITS, DOCUMENT_UNDERLINE_STYLE_ATTRIBUTE, DocumentCharacterFormatting, DocumentEquation, DocumentFontFamily, DocumentHighlight, DocumentImage, DocumentParagraphFormatting, DocumentScriptFontFormatting, DocumentStrike, DocumentSubscript, DocumentSuperscript, DocumentTableRowIdentity, DocumentTextStyle, DocumentUnderline, MAX_DOCUMENT_IMAGE_RELATIVE_HEIGHT, MAX_DOCUMENT_NUMBERING_START, activeDocumentBookmark, activeDocumentSection, activeDocumentTableStyle, applyDocumentImageCropToElement, applyDocumentImageIdentityToElement, applyDocumentImageLayerToElement, applyDocumentImageTransformToElement, applyDocumentImageWrapContourToElement, applyDocumentPageGeometry, applyDocumentTableGeometryToElement, applyDocumentTableRowIdentityToElement, applyDocumentTextCaseStyle, canChangeDocumentIndent, canInsertDocumentComment, canSetDocumentTableRowRepeatHeader, clampDocumentMargin, collectDocumentChanges, collectDocumentCommentAnchors, collectDocumentNotes, collectDocumentTextLayoutParagraphs, createDocumentBibliography, createDocumentEquationElement, createDocumentImageIdentityRegistry, createDocumentNoteElement, createSchemaDerivedWorkDocumentModel, createWorkDocumentBlob, createWorkDocumentExtensions, createWorkDocumentModel, createWorkOfficeDocumentCollaborationBinding as createOfficeDocumentCollaborationBinding, defaultDocumentImageTransform, defaultDocumentImageWrapContour, documentAutoLineHeight, documentBookmarkNameExists, documentBookmarkReferenceInstruction, documentBulletListStyle, documentCaptionKind, documentCaptionLabel, documentCharacterPositionDomAttributes, documentCharacterPositionHalfPointsFromElement, documentCharacterPositionPoints, documentCharacterScaleDomAttributes, documentCharacterScalePercentFromElement, documentCharacterSpacingDomAttributes, documentCharacterSpacingPoints, documentCharacterSpacingTwipsFromElement, documentChunkMountedIds, documentCitationCount, documentCitationInstruction, documentCitationStyle, documentCitationStyleDetails, documentCitationTags, documentCitationTagsFromInstruction, documentCommentDraftRange, documentCommentViews, documentContentLayoutProperties, documentEmphasisMarkDomAttributes, documentEmphasisMarkFromElement, documentEquationFromElement, documentEquationText, documentHasIndex, documentHasTableOfContents, documentHiddenTextDomAttributes, documentHiddenTextFromElement, documentHiddenTextKeyboardShortcut, documentImageCropFromElement, documentImageIdentityFromElement, documentImageLayerFromElement, documentImageLayoutFromElement, documentImageLayoutOptions, documentImagePositionFromElement, documentImageProperties, documentImageTransformFromElement, documentImageWrapContourFromElement, documentInitialSectionLayout, documentKerningDomAttributes, documentKerningIsEffective, documentKerningThresholdHalfPointsFromElement, documentKerningThresholdPoints, documentLazyHtmlChunkFragment, documentLazyHtmlProjection, documentLazyHtmlProjectionFingerprint, documentLegacyTextEffectsConflict, documentLegacyTextEffectsCss, documentLegacyTextEffectsDomAttributes, documentLegacyTextEffectsFromElement, documentLegacyTextEffectsFromTextStyleAttributes, documentModelForContent, documentModelForHtml, documentModelHasTrustedInitialIntegrityFeatures, documentModelUsesWindowing, documentNoteKey, documentNoteKind, documentOpenTypeCssProperties, documentOpenTypeDomAttributes, documentOpenTypeFeaturesFromElement, documentOrderedListState, documentPageBordersVisible, documentPageChromeLegacyFields, documentPageGeometryForLayout, documentPageHorizontalMarginTwips, documentPageMarginBody, documentPageMarginsForLayout, documentPageMetrics, documentPageSurfaceGeometryForElement, documentPaperSizeForGeometry, documentParagraphDirection, documentParagraphIndent, documentParagraphPagination, documentParagraphSpacing, documentParagraphTabStops, documentSectionById, documentSectionDomAttributes, documentSections, documentStrikeDomAttributes, documentStrikeFormattingFromElement, documentStrikeStyle, documentTabLeaderLabel, documentTableBordersFromElement, documentTableCellFormat, documentTableCellMarginOverridesFromElement, documentTableColumnPercentagesFromElement, documentTableGeometryFromElement, documentTableHorizontalAlignment, documentTableRowIdentityFromElement, documentTableRowOptions, documentTableSizing, documentTextBoxProperties, documentTextBoxPropertiesFromElement, documentTextCaseFromWordFlags, documentTextCaseKeyboardShortcuts, documentTextLayoutBatches, documentTextStatistics, documentTransactionsOnlyHydrateChunks, documentUnderlineColor, documentUnderlineDomAttributes, documentUnderlineFormattingFromElement, documentUnderlineKeyboardShortcuts, documentUnderlineStyle, documentWordLineHeightFactor, docxBookmarkReferenceTarget, docxDocumentFieldKind, docxDocumentFieldTarget, editorDocumentBookmarkReferenceTargets, editorDocumentCaptionTargets, fileNameWithoutExtension, forgetWorkSourceBlob as forgetSourceBlob, importWorkDocumentFile, importedDocumentCharacterFormatting, initializeWorkOfficeDocumentCollaboration as initializeOfficeDocumentCollaboration, invalidateDocumentLazyHtmlProjection, isContourImageLayout, isDocumentCharacterFormatMark, isDocumentOpenTypeFeaturePatch, isValidDocumentCitationTag, materializeLazyDocumentEditorRoot, measureDocumentLayoutBlocksIncrementally, millimetersToPixels, mountWorkLiveDocumentCapture, moveWorkSourceBlob, nextDocumentTabAlignment, normalizeDocumentBookmarkName, normalizeDocumentBookmarkNativeId, normalizeDocumentBookmarkReferencesHtml, normalizeDocumentBookmarksHtml, normalizeDocumentCaptionsHtml, normalizeDocumentCharacterPositionHalfPoints, normalizeDocumentCharacterScalePercent, normalizeDocumentCharacterSpacingTwips, normalizeDocumentCitationsHtml, normalizeDocumentColumns, normalizeDocumentEmphasisMark, normalizeDocumentEquation, normalizeDocumentFieldsHtml, normalizeDocumentHiddenText, normalizeDocumentHtml, normalizeDocumentImageAlignment, normalizeDocumentImageCrop, normalizeDocumentImageIdentity, normalizeDocumentImageLayer, normalizeDocumentImageLayoutOptions, normalizeDocumentImagePosition, normalizeDocumentImageTransform, normalizeDocumentImageWrapContour, normalizeDocumentImageWrapSide, normalizeDocumentKerningThresholdHalfPoints, normalizeDocumentLegacyTextEffect, normalizeDocumentLegacyTextEffects, normalizeDocumentNotesHtml, normalizeDocumentOpenTypeFeatures, normalizeDocumentOpenTypeLigatures, normalizeDocumentOpenTypeNumberForm, normalizeDocumentOpenTypeNumberSpacing, normalizeDocumentOpenTypeStylisticSets, normalizeDocumentPageBorders, normalizeDocumentPageChrome, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, normalizeDocumentPaperSource, normalizeDocumentParagraphIndent, normalizeDocumentStrikeStyle, normalizeDocumentTabStops, normalizeDocumentTableBorderStyle, normalizeDocumentTableBorderWidth, normalizeDocumentTableRowHeightRule, normalizeDocumentTableRowIdentity, normalizeDocumentTableVerticalAlign, normalizeDocumentTextBoxProperties, normalizeDocumentTextCase, normalizeDocumentUnderlineColor, normalizeDocumentUnderlineStyle, normalizeTableColor, normalizedTabPosition, numberingTypeFromFormat, pageTwipsToMillimeters, parseDocumentCharacterFormatting, parseDocumentNumberingChange, parseDocumentOpenTypeFeatures, parseDocumentParagraphFormatting, patchDocumentLazyHtmlProjection, patchDocumentOpenTypeFeatures, positionWorkLiveDocumentCapture, readWorkOfficeDocumentCollaboration as readOfficeDocumentCollaboration, readWorkSourceBlob as readSourceBlob, registerDocumentPageSurfaceGeometry, rememberWorkSourceBlob as registerSourceBlob, renderDocumentAutoLineHeight, renderDocumentTableBorders, renderDocumentTableCellMarginOverrides, resolveAllDocumentChanges, resolveDocumentPageBorders, resolveDocumentPageChrome, resolveDocumentPageMargins, resolveDocumentPageSize, resolveWorkDocumentEditorInput, retainAnchoredDocumentComments, sanitizeDocumentPageChromeHtml, selectedDocumentChunkId, selectedDocumentIndexDraft, selectedDocumentIndexEntry, selectedDocumentIndexOptions, selectedDocumentTableOfContentsOptions, serializeDocumentCharacterFormatting, serializeDocumentNumberingChange, serializeDocumentOpenTypeFeatures, serializeDocumentParagraphFormatting, serializeDocumentTabStops, serializeWorkDocumentNode, setCustomDocumentColumns, supportedDocxBookmarkReferenceInstruction, supportedDocxDocumentFieldInstruction, syncDocumentContentFromHtml, textBoxCss, textBoxDomAttributes, transferChangedDocumentTextStatistics, uniqueDocumentImageIdentity, updateDocumentColumnWidth, updateDocumentCustomPageMillimeters, updateDocumentGutterPosition, updateDocumentMirrorMargins, updateDocumentPageChromeVariant, updateDocumentPageMarginMillimeters, updateDocumentPageMarginMode, updateDocumentPageOrientation, updateDocumentPaperSizePreset, validateDocumentBookmarkName, windowDocumentModel, workDocumentSchema, workOfficeDocumentCollaborationFragment as officeDocumentCollaborationFragment, work_document_model_codec_createWorkDocumentModelFromContent as createWorkDocumentModelFromContent, work_document_model_codec_materializeWorkDocumentContent as materializeWorkDocumentContent, work_document_model_createSchemaValidatedWorkDocumentModel as createSchemaValidatedWorkDocumentModel, work_document_page_margins_twipsToMillimeters, work_file_download_downloadBlob, work_file_download_safeFileName, wrapsBesideImage };