@a3s-lab/office 0.33.0 → 0.35.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.
Files changed (30) hide show
  1. package/README.md +23 -19
  2. package/dist/0~5809.js +123 -0
  3. package/dist/{0~7614.js → 0~7828.js} +376 -5
  4. package/dist/0~pdf-viewer.js +5 -1
  5. package/dist/0~presentation-editor.js +597 -71
  6. package/dist/0~spreadsheet-editor.js +166 -18
  7. package/dist/0~work-pptx-export.js +50 -14
  8. package/dist/0~work-pptx-import.js +21 -9
  9. package/dist/4121.js +122 -1
  10. package/dist/4560.js +33 -0
  11. package/dist/internal/features/work/editors/presentation-animation-panel.d.ts +11 -0
  12. package/dist/internal/features/work/editors/presentation-command-types.d.ts +16 -1
  13. package/dist/internal/features/work/editors/presentation-editor-focus.d.ts +2 -0
  14. package/dist/internal/features/work/editors/presentation-presenter-view.d.ts +2 -1
  15. package/dist/internal/features/work/editors/presentation-slide-canvas.d.ts +11 -3
  16. package/dist/internal/features/work/editors/presentation-text-editor.d.ts +7 -0
  17. package/dist/internal/features/work/editors/spreadsheet-command-controller.d.ts +10 -0
  18. package/dist/internal/features/work/editors/spreadsheet-command-selection.d.ts +22 -1
  19. package/dist/internal/features/work/editors/use-presentation-animation-commands.d.ts +15 -0
  20. package/dist/internal/features/work/editors/use-presentation-selection.d.ts +1 -1
  21. package/dist/internal/features/work/work-pptx-animation.d.ts +13 -0
  22. package/dist/internal/features/work/work-pptx-groups.d.ts +4 -0
  23. package/dist/internal/features/work/work-presentation-animation-constraints.d.ts +4 -0
  24. package/dist/internal/features/work/work-presentation-animation.d.ts +20 -0
  25. package/dist/internal/features/work/work-presentation-clipboard.d.ts +10 -3
  26. package/dist/internal/features/work/work-types.d.ts +17 -0
  27. package/dist/office-kernel.wasm +0 -0
  28. package/dist/styles.css +123 -0
  29. package/package.json +19 -13
  30. package/dist/0~work-presentation-transition.js +0 -38
package/README.md CHANGED
@@ -13,13 +13,13 @@
13
13
 
14
14
  <p align="center">
15
15
  <a href="https://github.com/A3S-Lab/Office/actions/workflows/ci.yml"><img alt="CI status" src="https://img.shields.io/github/actions/workflow/status/A3S-Lab/Office/ci.yml?branch=main&amp;style=flat-square&amp;label=CI"></a>
16
- <a href="https://a3s-lab.github.io/Office/"><img alt="Open the live Playground" src="https://img.shields.io/badge/Live_Playground-open-2f6fed?style=flat-square"></a>
16
+ <a href="https://a3s-lab.github.io/Office/playground/"><img alt="Open the live Playground" src="https://img.shields.io/badge/Live_Playground-open-2f6fed?style=flat-square"></a>
17
17
  <a href="#project-status"><img alt="Project status: pre-1.0" src="https://img.shields.io/badge/status-pre--1.0-7a5bd6?style=flat-square"></a>
18
18
  <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-159469?style=flat-square"></a>
19
19
  </p>
20
20
 
21
21
  <p align="center">
22
- <a href="https://a3s-lab.github.io/Office/">Playground</a> ·
22
+ <a href="https://a3s-lab.github.io/Office/playground/">Playground</a> ·
23
23
  <a href="https://a3s-lab.github.io/Office/docs/">Documentation</a> ·
24
24
  <a href="#quick-start">Quick start</a> ·
25
25
  <a href="#editor-capability-comparison">Editors</a> ·
@@ -56,12 +56,13 @@ workflows, and a separate Rust automation plane.
56
56
 
57
57
  ## Latest on `main`
58
58
 
59
- The `0.33.0` release plus the current `main` branch expose these capabilities
59
+ The `0.35.0` release plus the current `main` branch expose these capabilities
60
60
  as normal user-facing Playground templates, with matching implementation
61
61
  detail in the documentation:
62
62
 
63
63
  | Editor | Latest capability | Public entry |
64
64
  | --- | --- | --- |
65
+ | Presentation | Author, reorder, preview, collaborate on, and play ordered appear, fade, fly-in, and zoom entrance animations with on-click, with-previous, and after-previous triggers; the bounded subset round-trips through native PPTX timing trees | Playground **Latest capabilities → 入场动画** · [Presentation reference](docs/latest/en/components/presentation.mdx#entrance-animations) |
65
66
  | Writer | Compare the current document with an imported DOCX, HTML, or TXT version and generate deterministic insertion, deletion, character-formatting, and paragraph-formatting revisions; combine a reviewed copy only after exact baseline verification | Playground **Latest capabilities → 文档比较** · [Document reference](docs/latest/en/components/document.mdx#document-compare-and-combine) |
66
67
  | Writer | Insert, customize, navigate, and explicitly refresh a typed Table of Contents with heading and native outline levels 1–9, hyperlinks, page numbers, alignment, four leader styles, and native DOCX `TOC` round trips | Playground **Latest capabilities → 可更新目录** · [Document reference](docs/latest/en/components/document.mdx#native-table-of-contents) |
67
68
  | Writer | Mark primary and secondary index terms, cross-references, and bold/italic page numbers; insert, navigate, customize, and refresh a bounded native index with editable DOCX `XE` and `INDEX` round trips | Playground **Latest capabilities → 原生索引** · [Document reference](docs/latest/en/components/document.mdx#native-document-index) |
@@ -70,16 +71,17 @@ detail in the documentation:
70
71
  | Spreadsheet | Complete common Data Validation input, blank/dropdown, and Stop/Warning/Information error settings with native XLSX round trips | Playground **Latest capabilities → 数据验证** · [Spreadsheet reference](docs/latest/en/components/spreadsheet.mdx#data-validation) |
71
72
  | PDF | Insert, delete, rotate, reorder, extract, merge, and split pages through a dedicated Web Worker, with Blob-level Undo/Redo and independent binary reopen verification | Playground **Latest capabilities → 组织 PDF 页面** · [PDF reference](docs/latest/en/components/pdf.mdx#page-organization) |
72
73
 
73
- All seven entries are reachable from the first Playground viewport. The six
74
- content templates also remain under **新建**; **组织 PDF 页面** opens the normal
75
- PDF file workflow because page organization must operate on host-provided source
76
- bytes. The highlighted strip keeps each capability discoverable without a deep
77
- documentation section or an `?e2e=` fixture.
74
+ All eight entries are reachable from the first Playground viewport. The
75
+ filterable capability gallery groups releases by editor, publishes the source
76
+ release on every card, and grows from one shared metadata list instead of a
77
+ fixed-width strip. The seven content templates also remain under **新建**;
78
+ **组织 PDF 页面** opens the normal PDF file workflow because page organization
79
+ must operate on host-provided source bytes.
78
80
 
79
81
  ## See it working
80
82
 
81
83
  The images below are committed visual-regression baselines from the real
82
- [Playground](https://a3s-lab.github.io/Office/), not conceptual mockups.
84
+ [Playground](https://a3s-lab.github.io/Office/playground/), not conceptual mockups.
83
85
 
84
86
  <p align="center">
85
87
  <a href="visual-tests/__snapshots__/linux/desktop-1280/document.png">
@@ -163,7 +165,7 @@ baseline rather than one specific release.
163
165
  | Text, shapes, tables, charts, and images | **Partial** — typed editable objects with native import/export paths | Broader shapes, connectors, effects, SmartArt, and embedded chart editing |
164
166
  | Masters and layouts | **Partial** — import/export inheritance with editable common paths | Full visual master, layout, and placeholder authoring |
165
167
  | Transitions and timings | **Partial** — fade, push, wipe, split, cut, and click/automatic advance | Broader transition catalog and timing controls |
166
- | Animations and media | **Gap** — no production animation timeline, trigger model, audio, video, or recording path | Object animation, motion paths, media editing, and recording |
168
+ | Animations and media | **Partial** — the Work scene model authors, validates, copies, collaborates on, previews, and plays ordered appear/fade/fly-in/zoom entrance cues with click/with-previous/after-previous triggers and bounded timing; this supported entrance subset round-trips through native PPTX timing trees | Exit/emphasis effects, motion paths, audio, video, recording, and broader PPTX preservation |
167
169
  | Slideshow and presenter workflows | **Supported** — current/beginning start, keyboard playback, notes, timer, and responsive presenter view | Rehearsal, recording, ink/laser, and richer multi-display controls |
168
170
  | Review and collaboration | **Partial** — comments, shared presence, remote object locations, and host transport | Threads, assignments, mentions, and integrated cloud review |
169
171
  | PPTX, print, PDF, and video | **Partial** — PPTX round trip plus slide/notes/handout PDF models; no video export | Broader print controls, vector fidelity, media preservation, and video export |
@@ -363,7 +365,7 @@ bun run playground
363
365
  ```
364
366
 
365
367
  Then open the local URL printed by the development server. For a zero-install
366
- tour, use the [live Playground](https://a3s-lab.github.io/Office/).
368
+ tour, use the [live Playground](https://a3s-lab.github.io/Office/playground/).
367
369
  Choose **体验格式修订**, open **审阅**, then **查看修订(2)** to inspect the
368
370
  independent Formatting and Paragraph Formatting cards and exercise accept or
369
371
  reject semantics.
@@ -1612,16 +1614,18 @@ committed visual contracts; see [CONTRIBUTING.md](CONTRIBUTING.md).
1612
1614
 
1613
1615
  ## Documentation
1614
1616
 
1615
- The published website keeps the interactive Playground at its root and builds
1616
- the searchable Rspress documentation center under `/docs/`. Simplified Chinese
1617
- is the stable default, English remains available from the language menu, and
1618
- the version menu switches between `latest` and frozen release documentation.
1619
- Search is scoped to the active language and version. Both surfaces use the
1620
- same deployment base, so preview, Pages, and fork deployments keep working
1621
- without hard-coded return URLs.
1617
+ The published website follows the A3S UI documentation model: the searchable
1618
+ Rspress documentation center owns the website root, while Playground is a
1619
+ primary top-navigation route under `/playground/`. Simplified Chinese is the
1620
+ stable default, English remains available from the language menu, and the
1621
+ version menu switches between `latest` and frozen release documentation.
1622
+ Search is scoped to the active language and version. Both surfaces derive
1623
+ their paths from one deployment base, so preview, Pages, and fork deployments
1624
+ keep working without hard-coded return URLs.
1622
1625
 
1623
- - [Live Playground](https://a3s-lab.github.io/Office/)
1626
+ - [Live Playground](https://a3s-lab.github.io/Office/playground/)
1624
1627
  - [Documentation center](https://a3s-lab.github.io/Office/docs/)
1628
+ - [A3S Office 0.34.0 documentation](https://a3s-lab.github.io/Office/docs/0.34.0/)
1625
1629
  - [A3S Office 0.33.0 documentation](https://a3s-lab.github.io/Office/docs/0.33.0/)
1626
1630
  - [A3S Office 0.32.0 documentation](https://a3s-lab.github.io/Office/docs/0.32.0/)
1627
1631
  - [A3S Office 0.31.0 documentation](https://a3s-lab.github.io/Office/docs/0.31.0/)
package/dist/0~5809.js ADDED
@@ -0,0 +1,123 @@
1
+ import { createOfficeId as createWorkId } from "./4121.js";
2
+ function createWorkSlideAnimation(elementId, effect, previous) {
3
+ return normalizeWorkSlideAnimation({
4
+ id: previous?.id ?? createWorkId('slide-animation'),
5
+ elementId,
6
+ effect,
7
+ trigger: previous?.trigger ?? 'on-click',
8
+ durationMs: previous?.durationMs ?? 500,
9
+ delayMs: previous?.delayMs ?? 0,
10
+ direction: 'fly-in' === effect ? previous?.direction ?? 'left' : void 0
11
+ });
12
+ }
13
+ function normalizeWorkSlideAnimation(animation) {
14
+ return {
15
+ ...animation,
16
+ durationMs: boundedInteger(animation.durationMs, 100, 60000),
17
+ delayMs: boundedInteger(animation.delayMs, 0, 60000),
18
+ direction: 'fly-in' === animation.effect ? normalizeWorkSlideAnimationDirection(animation.direction) : void 0
19
+ };
20
+ }
21
+ function workSlideAnimationForElement(slide, elementId) {
22
+ if (!elementId) return;
23
+ return slide.animations?.find((animation)=>animation.elementId === elementId);
24
+ }
25
+ function workSlideAnimationIndex(slide, animationId) {
26
+ if (!animationId) return -1;
27
+ return slide.animations?.findIndex((animation)=>animation.id === animationId) ?? -1;
28
+ }
29
+ function workSlideAnimationCues(animations) {
30
+ const cues = [];
31
+ let current;
32
+ for (const raw of animations ?? []){
33
+ const animation = normalizeWorkSlideAnimation(raw);
34
+ if (!current || 'on-click' === animation.trigger) {
35
+ current = {
36
+ automatic: 'on-click' !== animation.trigger,
37
+ items: [],
38
+ totalDurationMs: 0
39
+ };
40
+ cues.push(current);
41
+ }
42
+ const previous = current.items.at(-1);
43
+ const baseStart = 'after-previous' === animation.trigger ? previous?.endOffsetMs ?? 0 : 'with-previous' === animation.trigger ? previous?.startOffsetMs ?? 0 : 0;
44
+ const startOffsetMs = baseStart + animation.delayMs;
45
+ const endOffsetMs = startOffsetMs + animation.durationMs;
46
+ current.items.push({
47
+ animation,
48
+ startOffsetMs,
49
+ endOffsetMs
50
+ });
51
+ current.totalDurationMs = Math.max(current.totalDurationMs, endOffsetMs);
52
+ }
53
+ return cues;
54
+ }
55
+ function initialWorkSlideAnimationCueIndex(cues) {
56
+ return cues[0]?.automatic ? 0 : -1;
57
+ }
58
+ function removeWorkSlideAnimationsForElements(slide, elementIds) {
59
+ const animations = slide.animations?.filter((animation)=>!elementIds.has(animation.elementId));
60
+ if (animations?.length === slide.animations?.length) return slide;
61
+ return {
62
+ ...slide,
63
+ animations: animations?.length ? animations : void 0
64
+ };
65
+ }
66
+ function remapWorkSlideAnimations(animations, elementIds) {
67
+ const remapped = (animations ?? []).flatMap((animation)=>{
68
+ const elementId = elementIds.get(animation.elementId);
69
+ return elementId ? [
70
+ {
71
+ ...animation,
72
+ id: createWorkId('slide-animation'),
73
+ elementId
74
+ }
75
+ ] : [];
76
+ });
77
+ return remapped.length ? remapped : void 0;
78
+ }
79
+ function normalizeWorkSlideAnimationDirection(direction) {
80
+ return 'right' === direction || 'up' === direction || 'down' === direction ? direction : 'left';
81
+ }
82
+ function boundedInteger(value, minimum, maximum) {
83
+ const finite = Number.isFinite(value) ? Math.round(value) : minimum;
84
+ return Math.min(maximum, Math.max(minimum, finite));
85
+ }
86
+ const CARDINAL_DIRECTIONS = new Set([
87
+ 'left',
88
+ 'right',
89
+ 'up',
90
+ 'down'
91
+ ]);
92
+ const SPLIT_DIRECTIONS = new Set([
93
+ 'in',
94
+ 'out'
95
+ ]);
96
+ function createWorkSlideTransition(type, previous) {
97
+ const common = {
98
+ type,
99
+ speed: previous?.speed ?? 'medium',
100
+ advanceOnClick: previous?.advanceOnClick ?? true,
101
+ advanceAfterMs: previous?.advanceAfterMs
102
+ };
103
+ if ('push' === type || 'wipe' === type) return {
104
+ ...common,
105
+ direction: previous?.direction && CARDINAL_DIRECTIONS.has(previous.direction) ? previous.direction : 'left'
106
+ };
107
+ if ('split' === type) return {
108
+ ...common,
109
+ direction: previous?.direction && SPLIT_DIRECTIONS.has(previous.direction) ? previous.direction : 'out',
110
+ orientation: previous?.orientation ?? 'horizontal'
111
+ };
112
+ return common;
113
+ }
114
+ function slideTransitionDurationMilliseconds(transition) {
115
+ if (transition?.speed === 'fast') return 500;
116
+ if (transition?.speed === 'slow') return 2000;
117
+ return 1000;
118
+ }
119
+ function workSlideTransitionsEqual(left, right) {
120
+ if (!left || !right) return left === right;
121
+ return left.type === right.type && left.speed === right.speed && left.direction === right.direction && left.orientation === right.orientation && left.advanceOnClick === right.advanceOnClick && left.advanceAfterMs === right.advanceAfterMs;
122
+ }
123
+ export { createWorkSlideAnimation, createWorkSlideTransition, initialWorkSlideAnimationCueIndex, normalizeWorkSlideAnimation, remapWorkSlideAnimations, removeWorkSlideAnimationsForElements, slideTransitionDurationMilliseconds, workSlideAnimationCues, workSlideAnimationForElement, workSlideAnimationIndex, workSlideTransitionsEqual };
@@ -1,9 +1,9 @@
1
1
  import jszip from "jszip";
2
- import { descendants, directChildren, directChild, xmlNamespacePrefix, attribute, firstDescendant, parseXml } from "./4121.js";
2
+ import { directChildren, xmlNamespacePrefix, parseXml, firstDescendant, descendants, directChild, attribute, createOfficeId as createWorkId } from "./4121.js";
3
3
  import { normalizePresentationChartDataLabelPosition, normalizePresentationChartDataLabels, normalizePresentationChartSeriesStyle } from "./0~work-presentation-charts.js";
4
4
  import { parseXlsxChartSeriesStyle, xlsxChartSeriesMarkerXml, parseXlsxTrendline, isSupportedXlsxChartLegend, isSupportedXlsxChartPlotLayout, isSupportedXlsxChartSeriesFormatting, parseXlsxChartPlotLayout, parseXlsxChartLegend, xlsxChartSeriesShapePropertiesXml, parseXlsxErrorBars } from "./4166.js";
5
5
  import { workSpreadsheetChartSupportsErrorBars, workSpreadsheetChartSupportsGrouping, normalizeWorkSpreadsheetChartSmoothLines, workSpreadsheetChartSupportsSeriesAnalysis, normalizeWorkSpreadsheetChartSeriesStyle, normalizeWorkSpreadsheetErrorBars, normalizeWorkSpreadsheetChartOverlap, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetChartSupportsBarSpacing, normalizeWorkSpreadsheetChartLegendOverlay, normalizeWorkSpreadsheetChartGapWidth, normalizeWorkSpreadsheetChartGrouping, workSpreadsheetChartSupportsTrendlines, workSpreadsheetChartSupportsSmoothLines, normalizeWorkSpreadsheetTrendline } from "./8715.js";
6
- import { createWorkSlideTransition } from "./0~work-presentation-transition.js";
6
+ import { normalizeWorkSlideAnimation, createWorkSlideTransition } from "./0~5809.js";
7
7
  const PRESENTATION_NAMESPACE = 'http://schemas.openxmlformats.org/presentationml/2006/main';
8
8
  const DRAWING_NAMESPACE = 'http://schemas.openxmlformats.org/drawingml/2006/main';
9
9
  const OFFICE_RELATIONSHIPS_NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
@@ -904,6 +904,377 @@ function finiteNumber(value) {
904
904
  function work_pptx_chart_series_analysis_chartPartNumber(path) {
905
905
  return Number(/chart(\d+)\.xml$/.exec(path)?.[1] ?? Number.MAX_SAFE_INTEGER);
906
906
  }
907
+ const PRESENTATIONML_NAMESPACE = 'http://schemas.openxmlformats.org/presentationml/2006/main';
908
+ function readPptxAnimations(document, elementIdByPptxShapeId) {
909
+ const timing = directChild(document.documentElement, 'timing');
910
+ if (!timing) return {
911
+ animations: [],
912
+ diagnostics: []
913
+ };
914
+ const diagnostics = [];
915
+ if (elementNamespace(timing) !== PRESENTATIONML_NAMESPACE) return {
916
+ animations: [],
917
+ diagnostics: [
918
+ {
919
+ code: 'pptx.animation.namespace',
920
+ message: 'A namespace-spoofed animation timing tree was ignored instead of being treated as trusted PresentationML.'
921
+ }
922
+ ]
923
+ };
924
+ const animations = [];
925
+ const animatedElements = new Set();
926
+ const effectNodes = pptxDescendants(timing, 'cTn').filter((node)=>[
927
+ 'clickEffect',
928
+ 'withEffect',
929
+ 'afterEffect'
930
+ ].includes(attribute(node, 'nodeType') ?? ''));
931
+ if (effectNodes.length > 256) diagnostics.push({
932
+ code: 'pptx.animation.limit',
933
+ message: "Only the first 256 object animation timing records on a slide are inspected and imported."
934
+ });
935
+ for (const node of effectNodes.slice(0, 256)){
936
+ if ('entr' !== attribute(node, 'presetClass')) {
937
+ diagnostics.push({
938
+ code: 'pptx.animation.class',
939
+ message: 'An emphasis, exit, motion-path, or malformed animation remains in the source PPTX only.'
940
+ });
941
+ continue;
942
+ }
943
+ const targets = Array.from(new Set(pptxDescendants(node, 'spTgt').map((target)=>attribute(target, 'spid')?.trim()).filter((target)=>Boolean(target))));
944
+ if (1 !== targets.length) {
945
+ diagnostics.push({
946
+ code: 'pptx.animation.target',
947
+ message: 'An entrance animation with a missing or ambiguous object target was ignored.'
948
+ });
949
+ continue;
950
+ }
951
+ const elementId = elementIdByPptxShapeId.get(targets[0]);
952
+ if (!elementId) {
953
+ diagnostics.push({
954
+ code: 'pptx.animation.target',
955
+ message: 'An entrance animation points to an unavailable slide object and was ignored.'
956
+ });
957
+ continue;
958
+ }
959
+ if (animatedElements.has(elementId)) {
960
+ diagnostics.push({
961
+ code: 'pptx.animation.duplicate-target',
962
+ message: 'Only the first supported entrance animation for an object is editable; additional effects remain in the source PPTX only.'
963
+ });
964
+ continue;
965
+ }
966
+ const effectResult = readPptxAnimationEffect(node);
967
+ if (!effectResult.effect) {
968
+ diagnostics.push({
969
+ code: 'pptx.animation.effect',
970
+ message: 'An entrance animation uses an unsupported or malformed effect and remains in the source PPTX only.'
971
+ });
972
+ continue;
973
+ }
974
+ const timingResult = readPptxAnimationTiming(node);
975
+ if (timingResult.normalized) diagnostics.push({
976
+ code: 'pptx.animation.timing',
977
+ message: 'An invalid or out-of-range animation duration or delay was normalized to safe editable bounds.'
978
+ });
979
+ const animation = normalizeWorkSlideAnimation({
980
+ id: createWorkId('slide-animation'),
981
+ elementId,
982
+ effect: effectResult.effect,
983
+ trigger: pptxAnimationTrigger(attribute(node, 'nodeType')),
984
+ durationMs: timingResult.durationMs,
985
+ delayMs: timingResult.delayMs,
986
+ direction: effectResult.direction
987
+ });
988
+ animations.push(animation);
989
+ animatedElements.add(elementId);
990
+ }
991
+ if (!effectNodes.length) diagnostics.push({
992
+ code: 'pptx.animation.structure',
993
+ message: 'The slide timing tree does not contain a supported object entrance sequence and remains in the source PPTX only.'
994
+ });
995
+ return {
996
+ animations,
997
+ diagnostics
998
+ };
999
+ }
1000
+ async function patchPptxAnimations(buffer, slides, registry) {
1001
+ if (!slides.some((slide)=>slide.animations?.length)) return buffer;
1002
+ const archive = await jszip.loadAsync(buffer);
1003
+ for (const [index, slide] of slides.entries()){
1004
+ if (!slide.animations?.length) continue;
1005
+ const path = `ppt/slides/slide${index + 1}.xml`;
1006
+ const entry = archive.file(path);
1007
+ if (!entry) throw new Error(`PPTX animation export is missing generated slide ${index + 1}.`);
1008
+ const document = parseXml(await entry.async('text'), path);
1009
+ normalizePptxShapeIds(document);
1010
+ const targetByMarker = pptxTargetIdsByMarker(document);
1011
+ const scope = `slide:${slide.id}`;
1012
+ const targets = slide.animations.map((animation)=>{
1013
+ const marker = registry.markerForElement(scope, animation.elementId);
1014
+ const ids = marker ? targetByMarker.get(marker) : void 0;
1015
+ if (!marker || ids?.length !== 1) throw new Error(`PPTX animation export expected one generated object for animation ${animation.id}, but found ${ids?.length ?? 0}.`);
1016
+ return {
1017
+ animation,
1018
+ spid: ids[0]
1019
+ };
1020
+ });
1021
+ writePptxAnimations(document, targets);
1022
+ archive.file(path, new XMLSerializer().serializeToString(document));
1023
+ }
1024
+ return archive.generateAsync({
1025
+ type: 'arraybuffer',
1026
+ compression: 'DEFLATE'
1027
+ });
1028
+ }
1029
+ function writePptxAnimations(document, targets) {
1030
+ const root = document.documentElement;
1031
+ directChild(root, 'timing')?.remove();
1032
+ if (!targets.length) return;
1033
+ const source = parseXml(pptxAnimationTimingXml(targets), 'PPTX animations');
1034
+ const timing = clonePptxAnimationElement(document, source.documentElement);
1035
+ const extensionList = directChild(root, 'extLst') ?? null;
1036
+ root.insertBefore(timing, extensionList);
1037
+ }
1038
+ function clonePptxAnimationElement(document, source) {
1039
+ const element = document.createElementNS(PRESENTATIONML_NAMESPACE, `p:${source.localName}`);
1040
+ for (const item of Array.from(source.attributes))if (!('xmlns' === item.name || item.name.startsWith('xmlns:'))) element.setAttribute(item.name, item.value);
1041
+ for (const child of Array.from(source.childNodes))element.append(child instanceof Element ? clonePptxAnimationElement(document, child) : document.createTextNode(child.textContent ?? ''));
1042
+ return element;
1043
+ }
1044
+ function readPptxAnimationEffect(node) {
1045
+ const presetId = attribute(node, 'presetID');
1046
+ const animationEffects = pptxDescendants(node, 'animEffect');
1047
+ const filters = animationEffects.map((effect)=>attribute(effect, 'filter')?.trim()).filter((filter)=>Boolean(filter));
1048
+ const attributes = new Set(pptxDescendants(node, 'attrName').map((name)=>name.textContent?.trim()));
1049
+ if (filters.some((filter)=>'fade' === filter)) return {
1050
+ effect: 'fade'
1051
+ };
1052
+ for (const filter of filters){
1053
+ const direction = directionFromPptxSlideFilter(filter);
1054
+ if (direction) return {
1055
+ effect: 'fly-in',
1056
+ direction
1057
+ };
1058
+ }
1059
+ if ('23' === presetId || pptxDescendants(node, 'animScale').length > 0 || attributes.has('ppt_w') && attributes.has('ppt_h')) return {
1060
+ effect: 'zoom'
1061
+ };
1062
+ if ('2' === presetId && (attributes.has('ppt_x') || attributes.has('ppt_y'))) return {
1063
+ effect: 'fly-in',
1064
+ direction: directionFromPptxMotion(node)
1065
+ };
1066
+ if ('1' === presetId && pptxDescendants(node, 'set').length > 0 && 0 === animationEffects.length && 0 === pptxDescendants(node, 'anim').length && 0 === pptxDescendants(node, 'animScale').length) return {
1067
+ effect: 'appear'
1068
+ };
1069
+ return {};
1070
+ }
1071
+ function readPptxAnimationTiming(node) {
1072
+ const directDuration = nonnegativeInteger(attribute(node, 'dur'));
1073
+ const behaviorDurations = pptxDirectChildren(pptxDirectChild(node, 'childTnLst') ?? node).flatMap((behavior)=>pptxDescendants(behavior, 'cTn')).map((time)=>nonnegativeInteger(attribute(time, 'dur'))).filter((duration)=>void 0 !== duration);
1074
+ const behaviorDuration = Math.max(0, ...behaviorDurations);
1075
+ const rawDuration = directDuration ?? (behaviorDuration || 500);
1076
+ const delayValue = attribute(pptxChildPath(node, 'stCondLst', 'cond') ?? node, 'delay');
1077
+ const rawDelay = nonnegativeInteger(delayValue) ?? 0;
1078
+ const normalized = normalizeWorkSlideAnimation({
1079
+ id: 'timing',
1080
+ elementId: 'timing',
1081
+ effect: 'fade',
1082
+ trigger: 'on-click',
1083
+ durationMs: rawDuration,
1084
+ delayMs: rawDelay
1085
+ });
1086
+ return {
1087
+ durationMs: normalized.durationMs,
1088
+ delayMs: normalized.delayMs,
1089
+ normalized: normalized.durationMs !== rawDuration || normalized.delayMs !== rawDelay || null !== attribute(node, 'dur') && void 0 === directDuration || null !== delayValue && void 0 === nonnegativeInteger(delayValue)
1090
+ };
1091
+ }
1092
+ function pptxAnimationTrigger(value) {
1093
+ if ('withEffect' === value) return 'with-previous';
1094
+ if ('afterEffect' === value) return 'after-previous';
1095
+ return 'on-click';
1096
+ }
1097
+ function directionFromPptxSlideFilter(filter) {
1098
+ if ('slide(fromLeft)' === filter) return 'left';
1099
+ if ('slide(fromRight)' === filter) return 'right';
1100
+ if ('slide(fromTop)' === filter) return 'up';
1101
+ if ('slide(fromBottom)' === filter) return 'down';
1102
+ }
1103
+ function directionFromPptxMotion(node) {
1104
+ const values = pptxDescendants(node, 'strVal').map((value)=>attribute(value, 'val') ?? '').join(' ');
1105
+ if (values.includes('-#ppt_w/2')) return 'right';
1106
+ if (values.includes('1+#ppt_h/2')) return 'down';
1107
+ if (values.includes('-#ppt_h/2')) return 'up';
1108
+ return 'left';
1109
+ }
1110
+ function pptxAnimationTimingXml(targets) {
1111
+ let timeNodeId = 1;
1112
+ const nextId = ()=>timeNodeId++;
1113
+ const rootId = nextId();
1114
+ const mainSequenceId = nextId();
1115
+ const targetByAnimationId = new Map(targets.map((target)=>[
1116
+ target.animation.id,
1117
+ target
1118
+ ]));
1119
+ const groups = pptxAnimationGroups(targets.map((target)=>target.animation));
1120
+ const groupXml = groups.map((group)=>{
1121
+ const outerId = nextId();
1122
+ const wrappers = group.wrappers.map((animations)=>{
1123
+ const wrapperId = nextId();
1124
+ const effects = animations.map((animation)=>{
1125
+ const target = targetByAnimationId.get(animation.id);
1126
+ if (!target) return '';
1127
+ return pptxAnimationEffectXml(target, nextId);
1128
+ }).join('');
1129
+ return `<p:par><p:cTn id="${wrapperId}" fill="hold"><p:stCondLst><p:cond delay="0"/></p:stCondLst><p:childTnLst>${effects}</p:childTnLst></p:cTn></p:par>`;
1130
+ }).join('');
1131
+ const delay = group.automatic ? '0' : 'indefinite';
1132
+ return `<p:par><p:cTn id="${outerId}" fill="hold"><p:stCondLst><p:cond delay="${delay}"/></p:stCondLst><p:childTnLst>${wrappers}</p:childTnLst></p:cTn></p:par>`;
1133
+ }).join('');
1134
+ const builds = targets.map(({ spid })=>`<p:bldP spid="${spid}" grpId="0" animBg="1"/>`).join('');
1135
+ return `<p:timing xmlns:p="${PRESENTATIONML_NAMESPACE}"><p:tnLst><p:par><p:cTn id="${rootId}" dur="indefinite" restart="never" nodeType="tmRoot"><p:childTnLst><p:seq concurrent="1" nextAc="seek"><p:cTn id="${mainSequenceId}" dur="indefinite" nodeType="mainSeq"><p:childTnLst>${groupXml}</p:childTnLst></p:cTn><p:prevCondLst><p:cond evt="onPrev" delay="0"><p:tgtEl><p:sldTgt/></p:tgtEl></p:cond></p:prevCondLst><p:nextCondLst><p:cond evt="onNext" delay="0"><p:tgtEl><p:sldTgt/></p:tgtEl></p:cond></p:nextCondLst></p:seq></p:childTnLst></p:cTn></p:par></p:tnLst><p:bldLst>${builds}</p:bldLst></p:timing>`;
1136
+ }
1137
+ function pptxAnimationGroups(animations) {
1138
+ const groups = [];
1139
+ let current;
1140
+ for (const animation of animations)if (current && 'on-click' !== animation.trigger) if ('with-previous' === animation.trigger) current.wrappers.at(-1)?.push(animation);
1141
+ else current.wrappers.push([
1142
+ animation
1143
+ ]);
1144
+ else {
1145
+ current = {
1146
+ automatic: 'on-click' !== animation.trigger,
1147
+ wrappers: [
1148
+ [
1149
+ animation
1150
+ ]
1151
+ ]
1152
+ };
1153
+ groups.push(current);
1154
+ }
1155
+ return groups;
1156
+ }
1157
+ function pptxAnimationEffectXml(target, nextId) {
1158
+ const animation = normalizeWorkSlideAnimation(target.animation);
1159
+ const preset = pptxAnimationPreset(animation);
1160
+ const leafId = nextId();
1161
+ const visibilityId = nextId();
1162
+ const visibility = `<p:set><p:cBhvr><p:cTn id="${visibilityId}" dur="1" fill="hold"><p:stCondLst><p:cond delay="0"/></p:stCondLst></p:cTn><p:tgtEl><p:spTgt spid="${target.spid}"/></p:tgtEl><p:attrNameLst><p:attrName>style.visibility</p:attrName></p:attrNameLst></p:cBhvr><p:to><p:strVal val="visible"/></p:to></p:set>`;
1163
+ const behavior = pptxAnimationBehaviorXml(animation, target.spid, nextId);
1164
+ return `<p:par><p:cTn id="${leafId}" presetID="${preset.id}" presetClass="entr" presetSubtype="${preset.subtype}" fill="hold" nodeType="${pptxAnimationNodeType(animation.trigger)}" dur="${animation.durationMs}"><p:stCondLst><p:cond delay="${animation.delayMs}"/></p:stCondLst><p:childTnLst>${visibility}${behavior}</p:childTnLst></p:cTn></p:par>`;
1165
+ }
1166
+ function pptxAnimationBehaviorXml(animation, spid, nextId) {
1167
+ if ('appear' === animation.effect) return '';
1168
+ if ('zoom' === animation.effect) return [
1169
+ 'ppt_w',
1170
+ 'ppt_h'
1171
+ ].map((attributeName)=>{
1172
+ const id = nextId();
1173
+ return `<p:anim calcMode="lin" valueType="num"><p:cBhvr><p:cTn id="${id}" dur="${animation.durationMs}" fill="hold"/><p:tgtEl><p:spTgt spid="${spid}"/></p:tgtEl><p:attrNameLst><p:attrName>${attributeName}</p:attrName></p:attrNameLst></p:cBhvr><p:tavLst><p:tav tm="0"><p:val><p:fltVal val="0"/></p:val></p:tav><p:tav tm="100000"><p:val><p:strVal val="#${attributeName}"/></p:val></p:tav></p:tavLst></p:anim>`;
1174
+ }).join('');
1175
+ const id = nextId();
1176
+ const filter = 'fade' === animation.effect ? 'fade' : pptxSlideFilter(animation.direction ?? 'left');
1177
+ return `<p:animEffect transition="in" filter="${filter}"><p:cBhvr><p:cTn id="${id}" dur="${animation.durationMs}"/><p:tgtEl><p:spTgt spid="${spid}"/></p:tgtEl></p:cBhvr></p:animEffect>`;
1178
+ }
1179
+ function pptxAnimationPreset(animation) {
1180
+ if ('appear' === animation.effect) return {
1181
+ id: 1,
1182
+ subtype: 0
1183
+ };
1184
+ if ('fade' === animation.effect) return {
1185
+ id: 10,
1186
+ subtype: 0
1187
+ };
1188
+ if ('zoom' === animation.effect) return {
1189
+ id: 23,
1190
+ subtype: 16
1191
+ };
1192
+ const subtypes = {
1193
+ up: 1,
1194
+ right: 2,
1195
+ down: 4,
1196
+ left: 8
1197
+ };
1198
+ return {
1199
+ id: 2,
1200
+ subtype: subtypes[animation.direction ?? 'left']
1201
+ };
1202
+ }
1203
+ function pptxAnimationNodeType(trigger) {
1204
+ if ('with-previous' === trigger) return 'withEffect';
1205
+ if ('after-previous' === trigger) return 'afterEffect';
1206
+ return 'clickEffect';
1207
+ }
1208
+ function pptxSlideFilter(direction) {
1209
+ if ('right' === direction) return 'slide(fromRight)';
1210
+ if ('up' === direction) return 'slide(fromTop)';
1211
+ if ('down' === direction) return 'slide(fromBottom)';
1212
+ return 'slide(fromLeft)';
1213
+ }
1214
+ function normalizePptxShapeIds(document) {
1215
+ for (const shapeTree of descendants(document, 'spTree')){
1216
+ const used = new Set();
1217
+ let candidate = 1;
1218
+ for (const properties of descendants(shapeTree, 'cNvPr')){
1219
+ const current = Number(attribute(properties, 'id'));
1220
+ if (Number.isInteger(current) && current > 0 && !used.has(current)) {
1221
+ used.add(current);
1222
+ continue;
1223
+ }
1224
+ while(used.has(candidate))candidate += 1;
1225
+ properties.setAttribute('id', String(candidate));
1226
+ used.add(candidate);
1227
+ candidate += 1;
1228
+ }
1229
+ }
1230
+ }
1231
+ function pptxTargetIdsByMarker(document) {
1232
+ const targets = new Map();
1233
+ for (const properties of descendants(document, 'cNvPr')){
1234
+ const marker = attribute(properties, 'name');
1235
+ const id = Number(attribute(properties, 'id'));
1236
+ if (!marker || !Number.isInteger(id) || id <= 0) continue;
1237
+ const ids = targets.get(marker) ?? [];
1238
+ ids.push(id);
1239
+ targets.set(marker, ids);
1240
+ }
1241
+ return targets;
1242
+ }
1243
+ function nonnegativeInteger(value) {
1244
+ if (null === value || '' === value.trim()) return;
1245
+ const number = Number(value);
1246
+ return Number.isSafeInteger(number) && number >= 0 ? number : void 0;
1247
+ }
1248
+ function elementNamespace(element) {
1249
+ if (element.namespaceURI) return element.namespaceURI;
1250
+ const separator = element.tagName.indexOf(':');
1251
+ const prefix = element.prefix ?? (separator >= 0 ? element.tagName.slice(0, separator) : void 0);
1252
+ const declaration = prefix ? `xmlns:${prefix}` : 'xmlns';
1253
+ let current = element;
1254
+ while(current){
1255
+ const namespace = current.getAttribute(declaration);
1256
+ if (namespace) return namespace;
1257
+ current = current.parentElement;
1258
+ }
1259
+ return null;
1260
+ }
1261
+ function pptxDescendants(parent, localName) {
1262
+ return descendants(parent, localName).filter((element)=>elementNamespace(element) === PRESENTATIONML_NAMESPACE);
1263
+ }
1264
+ function pptxDirectChildren(parent, localName) {
1265
+ return directChildren(parent, localName).filter((element)=>elementNamespace(element) === PRESENTATIONML_NAMESPACE);
1266
+ }
1267
+ function pptxDirectChild(parent, localName) {
1268
+ return pptxDirectChildren(parent, localName)[0];
1269
+ }
1270
+ function pptxChildPath(parent, ...localNames) {
1271
+ let current = parent;
1272
+ for (const localName of localNames){
1273
+ if (!current) return;
1274
+ current = pptxDirectChild(current, localName);
1275
+ }
1276
+ return current instanceof Element ? current : void 0;
1277
+ }
907
1278
  const EFFECT_TYPES = new Set([
908
1279
  'fade',
909
1280
  'push',
@@ -956,7 +1327,7 @@ function readPptxTransition(document) {
956
1327
  speed: readSpeed(element, diagnostics),
957
1328
  advanceOnClick: readBoolean(attribute(element, 'advClick'), true)
958
1329
  };
959
- const advanceAfterMs = nonnegativeInteger(attribute(element, 'advTm'));
1330
+ const advanceAfterMs = work_pptx_transition_nonnegativeInteger(attribute(element, 'advTm'));
960
1331
  if (void 0 !== advanceAfterMs) transition.advanceAfterMs = advanceAfterMs;
961
1332
  else if (null !== attribute(element, 'advTm')) diagnostics.push({
962
1333
  code: 'pptx.transition.timing',
@@ -1030,9 +1401,9 @@ function readBoolean(value, fallback) {
1030
1401
  if (null === value) return fallback;
1031
1402
  return '1' === value || 'true' === value.toLowerCase();
1032
1403
  }
1033
- function nonnegativeInteger(value) {
1404
+ function work_pptx_transition_nonnegativeInteger(value) {
1034
1405
  if (null === value) return;
1035
1406
  const number = Number(value);
1036
1407
  return Number.isSafeInteger(number) && number >= 0 ? number : void 0;
1037
1408
  }
1038
- export { loadPptxCommentAuthors, patchPptxChartDataLabels, patchPptxChartLayoutAndSeriesStyles, patchPptxChartSeriesAnalysis, patchPptxComments, patchPptxTransitions, readPptxChartDataLabels, readPptxChartLayoutAndSeriesStyles, readPptxChartSeriesAnalysis, readPptxSlideComments, readPptxTransition };
1409
+ export { loadPptxCommentAuthors, patchPptxAnimations, patchPptxChartDataLabels, patchPptxChartLayoutAndSeriesStyles, patchPptxChartSeriesAnalysis, patchPptxComments, patchPptxTransitions, readPptxAnimations, readPptxChartDataLabels, readPptxChartLayoutAndSeriesStyles, readPptxChartSeriesAnalysis, readPptxSlideComments, readPptxTransition };
@@ -2195,13 +2195,17 @@ function PdfToolbar({ annotationState, can, commands, editable, pageOrganization
2195
2195
  className: "work-pdf-page-organizer-trigger",
2196
2196
  "data-pdf-page-organizer-trigger": true,
2197
2197
  "aria-label": "组织 PDF 页面",
2198
+ title: "组织 PDF 页面",
2198
2199
  disabled: !can.openPageOrganizer(),
2199
2200
  onClick: commands.openPageOrganizer,
2200
2201
  children: [
2201
2202
  /*#__PURE__*/ jsx(LayoutGrid, {
2202
2203
  size: 15
2203
2204
  }),
2204
- "组织页面"
2205
+ /*#__PURE__*/ jsx("span", {
2206
+ className: "work-pdf-page-organizer-label",
2207
+ children: "组织页面"
2208
+ })
2205
2209
  ]
2206
2210
  })
2207
2211
  }),