jvs-ppt-ui 0.0.7 → 0.0.8

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.
@@ -1,4 +1,4 @@
1
- import { ref, computed, watch, nextTick } from 'vue'
1
+ import { ref, computed, watch, nextTick, type WatchStopHandle } from 'vue'
2
2
  import { extractAnimationsFromPptxArrayBuffer } from '../utils/pptxAnimationExtractor'
3
3
  import { syncSlideConnections, scaleSvgPath, extractPathStartEnd, isEllipseShape, isStandardShapeType } from '../utils/line'
4
4
  import { getSvgPathRange } from '../utils/svgPathParser'
@@ -1737,6 +1737,11 @@ function applyStyleRecipeToMaster(
1737
1737
  globalBgColor.value = recipe.backgroundColor
1738
1738
  }
1739
1739
 
1740
+ // 同 tick 多母版变更 + 急切快照:pre-flush watcher 尚未排空,显式标脏目标母版
1741
+ targetMasters.forEach(m => {
1742
+ const i = slideMasters.value.indexOf(m)
1743
+ if (i >= 0) dirtyMasterIndexes.add(i)
1744
+ })
1740
1745
  addMasterHistorySnapshot()
1741
1746
  notify(isAll ? `已应用主题「${recipe.name}」到全部母版` : `已应用主题「${recipe.name}」到当前母版`)
1742
1747
  }
@@ -1848,6 +1853,8 @@ function applyStyleRecipeToSlides(
1848
1853
  globalThemeColor.value = [...recipe.styleColors]
1849
1854
  }
1850
1855
 
1856
+ // 同 tick 多页变更 + 急切快照:非 single 模式涉及多页,显式全量标脏(字符串比对会自动去重)
1857
+ if (effectiveMode !== 'single') markAllPagesDirty()
1851
1858
  addHistorySnapshot()
1852
1859
  if (effectiveMode === 'all') {
1853
1860
  notify(`已应用主题「${recipe.name}」到全部幻灯片与母版`)
@@ -2495,20 +2502,72 @@ function notify(msg: string) {
2495
2502
  }, 2500)
2496
2503
  }
2497
2504
 
2498
- // 历史栈只存 slides 的 JSON 字符串;每条快照对应的激活页记录在平行数组中,
2499
- // 翻页时只更新一个数字,不再对整册快照做 parse + stringify
2500
- const historyStack = ref<string[]>([])
2505
+ // 历史栈按页存 JSON 字符串:每条快照是 string[](每页一条),未变页的字符串引用跨快照共享,
2506
+ // 编辑只序列化脏页、撤销只解析变更页;每条快照对应的激活页仍记在平行数组中,翻页只改一个数字
2507
+ const historyStack = ref<string[][]>([])
2501
2508
  const historyIndex = ref(-1)
2502
2509
  const isUndoingRedoing = ref(false)
2503
2510
  let historyActiveIndexes: number[] = []
2504
2511
 
2505
- const masterHistoryStack = ref<string[]>([])
2512
+ const masterHistoryStack = ref<string[][]>([])
2506
2513
  const masterHistoryIndex = ref(-1)
2507
- let lastSnapshotMastersStr = ''
2508
2514
  // 母版栈同理:激活的母版/版式 id 记在平行数组,切换母版不触发整册重序列化
2509
2515
  let masterHistoryActiveMids: string[] = []
2510
2516
  let masterHistoryActiveLids: string[] = []
2511
2517
 
2518
+ // --- 按页脏跟踪(slides 与 slideMasters 各一套) ---
2519
+ let pageWatchers: WatchStopHandle[] = []
2520
+ const dirtyPageIndexes = new Set<number>()
2521
+ let currentPageStrs: string[] = [] // 最近一次落盘的每页字符串(与栈内条目按引用共享)
2522
+ let currentPageIds: (string | undefined)[] = [] // 最近一次落盘的每页 id,用于重排漏检兜底
2523
+
2524
+ let masterWatchers: WatchStopHandle[] = []
2525
+ const dirtyMasterIndexes = new Set<number>()
2526
+ let currentMasterStrs: string[] = []
2527
+ let currentMasterIds: (string | undefined)[] = []
2528
+
2529
+ // --- 资源池:data URL 大字符串 intern 成短 id,避免随每条快照重复拷贝 ---
2530
+ // 池只在 initHistory 清空(届时双栈同时重置,不会留下悬空 id);slides 与 masters 共用
2531
+ const assetPool = new Map<string, string>() // id -> 原始 data URL
2532
+ const assetIntern = new Map<string, string>() // 原始 data URL -> id
2533
+ let assetSeq = 0
2534
+
2535
+ const ASSET_MIN_LEN = 1024
2536
+
2537
+ function assetReplacer(_k: string, v: any): any {
2538
+ if (typeof v === 'string' && v.length >= ASSET_MIN_LEN && v.charCodeAt(0) === 100 && v.startsWith('data:')) {
2539
+ let id = assetIntern.get(v)
2540
+ if (id === undefined) {
2541
+ assetSeq += 1
2542
+ id = `@@JVSAsset:${assetSeq}@@`
2543
+ assetIntern.set(v, id)
2544
+ assetPool.set(id, v)
2545
+ }
2546
+ return id
2547
+ }
2548
+ return v
2549
+ }
2550
+
2551
+ function assetReviver(_k: string, v: any): any {
2552
+ if (typeof v === 'string' && v.length <= 32 && v.charCodeAt(0) === 64) {
2553
+ const hit = assetPool.get(v)
2554
+ if (hit !== undefined) return hit
2555
+ }
2556
+ return v
2557
+ }
2558
+
2559
+ function serializePage(page: any): string {
2560
+ return JSON.stringify(page, assetReplacer)
2561
+ }
2562
+
2563
+ function parsePage<T>(s: string): T {
2564
+ return JSON.parse(s, assetReviver) as T
2565
+ }
2566
+
2567
+ function markAllPagesDirty() {
2568
+ for (let i = 0; i < slides.value.length; i++) dirtyPageIndexes.add(i)
2569
+ }
2570
+
2512
2571
  function revokeSlideBlobUrls(slideList: any[]) {
2513
2572
  if (!slideList || !Array.isArray(slideList)) return
2514
2573
  for (const slide of slideList) {
@@ -2537,24 +2596,33 @@ function revokeSlideBlobUrls(slideList: any[]) {
2537
2596
  }
2538
2597
 
2539
2598
  function initHistory() {
2540
- const slidesStr = JSON.stringify(slides.value)
2541
- historyStack.value = [slidesStr]
2599
+ ensurePageWatchers()
2600
+ ensureMasterWatchers()
2601
+ // 双栈即将整体重置,此时清资源池是安全的(不会有存活条目引用池内 id)
2602
+ assetPool.clear()
2603
+ assetIntern.clear()
2604
+ assetSeq = 0
2605
+ dirtyPageIndexes.clear()
2606
+ dirtyMasterIndexes.clear()
2607
+
2608
+ if (import.meta.env.DEV) console.debug(`[perf:initHistory] pages=${slides.value.length} masters=${slideMasters.value.length}`)
2609
+
2610
+ currentPageStrs = slides.value.map(serializePage)
2611
+ currentPageIds = slides.value.map(s => s && s.id)
2612
+ historyStack.value = [currentPageStrs.slice()]
2542
2613
  historyActiveIndexes = [activeIndex.value]
2543
2614
  historyIndex.value = 0
2544
- lastSnapshotSlidesStr = slidesStr
2545
2615
 
2546
2616
  // 母版历史独立初始化(导入/重置演示文稿时同样清空)
2547
- const mastersStr = JSON.stringify(slideMasters.value)
2548
- masterHistoryStack.value = [mastersStr]
2617
+ currentMasterStrs = slideMasters.value.map(serializePage)
2618
+ currentMasterIds = slideMasters.value.map(m => m && m.id)
2619
+ masterHistoryStack.value = [currentMasterStrs.slice()]
2549
2620
  masterHistoryActiveMids = [activeMasterId.value || '']
2550
2621
  masterHistoryActiveLids = [activeLayoutId.value || '']
2551
2622
  masterHistoryIndex.value = 0
2552
- lastSnapshotMastersStr = mastersStr
2553
2623
  activeSlideVersion.value++
2554
2624
  }
2555
2625
 
2556
- let lastSnapshotSlidesStr = ''
2557
-
2558
2626
  function syncActiveEditingTextToElement() {
2559
2627
  if (typeof document === 'undefined' || !slides.value) return
2560
2628
  const curSlide = slides.value[activeIndex.value]
@@ -2565,8 +2633,11 @@ function syncActiveEditingTextToElement() {
2565
2633
  if (container && (!el.content || !el.content.includes('<table'))) {
2566
2634
  const htmlVal = container.innerHTML
2567
2635
  if (el.type === 'shape') {
2568
- el.text = htmlVal
2569
- } else {
2636
+ // 空 DOM 抓到 '' 时不能给无文本 shape 制造 text:"" 新键:
2637
+ // 语义等价但字符串不等,会被当作变更推入幽灵快照,导致 undo 永远撤不到底
2638
+ const isEmptyNoop = htmlVal === '' && !el.text
2639
+ if (!isEmptyNoop && el.text !== htmlVal) el.text = htmlVal
2640
+ } else if (el.content !== htmlVal) {
2570
2641
  el.content = htmlVal
2571
2642
  }
2572
2643
  }
@@ -2594,19 +2665,47 @@ function syncDOMWithRestoredSlides() {
2594
2665
  function addHistorySnapshot() {
2595
2666
  if (isUndoingRedoing.value || pauseHistory.value) return
2596
2667
  syncActiveEditingTextToElement()
2597
- const currentSlidesStr = JSON.stringify(slides.value)
2598
- if (currentSlidesStr === lastSnapshotSlidesStr) {
2668
+ ensurePageWatchers()
2669
+
2670
+ const __perfT0 = performance.now()
2671
+ const n = slides.value.length
2672
+ // 结构性变化(增删页/watcher 数漂移)时全量比对;否则只比对候选脏页
2673
+ const structural = n !== currentPageStrs.length || pageWatchers.length !== n
2674
+ const candidates: Set<number> = new Set(structural ? undefined : dirtyPageIndexes)
2675
+ if (!structural) {
2676
+ // 同 tick 内先改数据再急切调用 addHistorySnapshot 的调用点全部只动当前页,标当前页兜底
2677
+ candidates.add(activeIndex.value)
2678
+ // id 错位说明发生过 watcher 覆盖不到的重排/对调,把这些页也纳入比对
2679
+ for (let i = 0; i < n; i++) {
2680
+ if (slides.value[i]?.id !== currentPageIds[i]) candidates.add(i)
2681
+ }
2682
+ } else {
2683
+ for (let i = 0; i < n; i++) candidates.add(i)
2684
+ }
2685
+
2686
+ let changed = false
2687
+ for (const i of candidates) {
2688
+ const s = serializePage(slides.value[i])
2689
+ if (s !== currentPageStrs[i]) {
2690
+ currentPageStrs[i] = s
2691
+ changed = true
2692
+ }
2693
+ }
2694
+ currentPageStrs.length = n
2695
+ dirtyPageIndexes.clear()
2696
+
2697
+ if (!changed) {
2698
+ if (import.meta.env.DEV) console.debug(`[perf:addSnapshot-dedup] ${(performance.now() - __perfT0).toFixed(1)}ms pages=${slides.value.length} dirty=${candidates.size}`)
2599
2699
  return
2600
2700
  }
2601
- lastSnapshotSlidesStr = currentSlidesStr
2602
2701
 
2603
2702
  if (historyIndex.value >= 0) {
2604
2703
  historyStack.value = historyStack.value.slice(0, historyIndex.value + 1)
2605
2704
  historyActiveIndexes = historyActiveIndexes.slice(0, historyIndex.value + 1)
2606
2705
  }
2607
2706
 
2608
- // 零拷贝优化:直接合成快照字符串,避免反序列化为海量对象后再重新序列化的巨大内存与CPU开销
2609
- historyStack.value.push(currentSlidesStr)
2707
+ // 未变页的字符串引用跨快照共享(string 不可变,零拷贝)
2708
+ historyStack.value.push(currentPageStrs.slice())
2610
2709
  historyActiveIndexes.push(activeIndex.value)
2611
2710
 
2612
2711
  if (historyStack.value.length > 15) {
@@ -2615,20 +2714,48 @@ function addHistorySnapshot() {
2615
2714
  }
2616
2715
 
2617
2716
  historyIndex.value = historyStack.value.length - 1
2717
+ currentPageIds = slides.value.map(s => s && s.id)
2718
+ if (import.meta.env.DEV) {
2719
+ console.debug(`[perf:addSnapshot] ${(performance.now() - __perfT0).toFixed(1)}ms pages=${slides.value.length} dirty=${candidates.size} stack=${historyStack.value.length}`)
2720
+ // 漏标脏页保险丝:未纳入候选集的页应与落盘字符串完全一致,不一致说明脏标漏页(仅 DEV)
2721
+ const __bad = slides.value.findIndex((s: any, i: number) => !candidates.has(i) && serializePage(s) !== currentPageStrs[i])
2722
+ if (__bad >= 0) console.error(`[history-parity] 第 ${__bad} 页未被标脏但内容已变化:存在漏标脏页!`)
2723
+ }
2618
2724
  }
2619
2725
 
2620
2726
  function addMasterHistorySnapshot() {
2621
2727
  if (isUndoingRedoing.value || pauseHistory.value) return
2622
- const currentMastersStr = JSON.stringify(slideMasters.value)
2623
- if (currentMastersStr === lastSnapshotMastersStr) {
2624
- return
2728
+ ensureMasterWatchers()
2729
+
2730
+ const n = slideMasters.value.length
2731
+ const structural = n !== currentMasterStrs.length || masterWatchers.length !== n
2732
+ const candidates: Set<number> = new Set(structural ? undefined : dirtyMasterIndexes)
2733
+ if (!structural) {
2734
+ const activeIdx = slideMasters.value.findIndex(m => m.id === activeMasterId.value)
2735
+ if (activeIdx >= 0) candidates.add(activeIdx)
2736
+ for (let i = 0; i < n; i++) {
2737
+ if (slideMasters.value[i]?.id !== currentMasterIds[i]) candidates.add(i)
2738
+ }
2739
+ } else {
2740
+ for (let i = 0; i < n; i++) candidates.add(i)
2741
+ }
2742
+
2743
+ let changed = false
2744
+ for (const i of candidates) {
2745
+ const s = serializePage(slideMasters.value[i])
2746
+ if (s !== currentMasterStrs[i]) {
2747
+ currentMasterStrs[i] = s
2748
+ changed = true
2749
+ }
2625
2750
  }
2626
- lastSnapshotMastersStr = currentMastersStr
2751
+ currentMasterStrs.length = n
2752
+ dirtyMasterIndexes.clear()
2753
+ if (!changed) return
2627
2754
 
2628
2755
  masterHistoryStack.value = masterHistoryStack.value.slice(0, masterHistoryIndex.value + 1)
2629
2756
  masterHistoryActiveMids = masterHistoryActiveMids.slice(0, masterHistoryIndex.value + 1)
2630
2757
  masterHistoryActiveLids = masterHistoryActiveLids.slice(0, masterHistoryIndex.value + 1)
2631
- masterHistoryStack.value.push(currentMastersStr)
2758
+ masterHistoryStack.value.push(currentMasterStrs.slice())
2632
2759
  masterHistoryActiveMids.push(activeMasterId.value || '')
2633
2760
  masterHistoryActiveLids.push(activeLayoutId.value || '')
2634
2761
 
@@ -2639,6 +2766,7 @@ function addMasterHistorySnapshot() {
2639
2766
  }
2640
2767
 
2641
2768
  masterHistoryIndex.value = masterHistoryStack.value.length - 1
2769
+ currentMasterIds = slideMasters.value.map(m => m && m.id)
2642
2770
  }
2643
2771
 
2644
2772
  let historySnapshotTimer: any = null
@@ -2664,9 +2792,28 @@ function scheduleHistorySnapshot() {
2664
2792
  }, 350)
2665
2793
  }
2666
2794
 
2667
- watch(slides, () => {
2795
+ // 每页一个 deep watcher:任何页的任何深层变更只标脏自己那一页(替代原整册 deep watch)。
2796
+ // 标脏不受守卫限制(守卫只作用于 flush),pauseHistory/撤销期间产生的标记靠下次 flush 的字符串比对自愈
2797
+ function ensurePageWatchers() {
2798
+ const n = slides.value.length
2799
+ while (pageWatchers.length > n) pageWatchers.pop()!()
2800
+ while (pageWatchers.length < n) {
2801
+ const i = pageWatchers.length
2802
+ pageWatchers.push(watch(() => slides.value[i], () => {
2803
+ // undo/redo 整体替换数组会触发全部页 watcher(deep 回调不比较新旧值),全是假阳性;
2804
+ // 此窗口内还原路径已同步 currentPageStrs,无需标脏。watcher 回调先于 nextTick 复位执行,守卫可靠
2805
+ if (isUndoingRedoing.value) return
2806
+ dirtyPageIndexes.add(i)
2807
+ scheduleHistorySnapshot()
2808
+ }, { deep: true }))
2809
+ }
2810
+ }
2811
+
2812
+ // 长度哨兵:纯尾部追加(末页后 addSlide)不触发任何 index getter,必须靠它补注册 watcher
2813
+ watch(() => slides.value.length, () => {
2814
+ ensurePageWatchers()
2668
2815
  scheduleHistorySnapshot()
2669
- }, { deep: true })
2816
+ })
2670
2817
 
2671
2818
  watch(activeIndex, (newIdx) => {
2672
2819
  if (isUndoingRedoing.value) return
@@ -2698,9 +2845,23 @@ function scheduleMasterHistorySnapshot() {
2698
2845
  }, 350)
2699
2846
  }
2700
2847
 
2701
- watch(slideMasters, () => {
2848
+ function ensureMasterWatchers() {
2849
+ const n = slideMasters.value.length
2850
+ while (masterWatchers.length > n) masterWatchers.pop()!()
2851
+ while (masterWatchers.length < n) {
2852
+ const i = masterWatchers.length
2853
+ masterWatchers.push(watch(() => slideMasters.value[i], () => {
2854
+ if (isUndoingRedoing.value) return
2855
+ dirtyMasterIndexes.add(i)
2856
+ scheduleMasterHistorySnapshot()
2857
+ }, { deep: true }))
2858
+ }
2859
+ }
2860
+
2861
+ watch(() => slideMasters.value.length, () => {
2862
+ ensureMasterWatchers()
2702
2863
  scheduleMasterHistorySnapshot()
2703
- }, { deep: true })
2864
+ })
2704
2865
 
2705
2866
  // 切换母版/版式只更新栈顶快照的激活状态(与 activeIndex 同理),不产生新历史记录
2706
2867
  watch([activeMasterId, activeLayoutId], ([mid, lid]) => {
@@ -2746,18 +2907,39 @@ const canRedo = computed(() => isMasterEditingView.value
2746
2907
  ? masterHistoryIndex.value < masterHistoryStack.value.length - 1
2747
2908
  : historyIndex.value < historyStack.value.length - 1)
2748
2909
 
2910
+ // 按页还原:只解析与当前状态字符串不同的页,未变页对象引用复用(缩略图/组件 vnode 稳定)。
2911
+ // 始终返回新数组:依赖 slides.value 数组 identity 的 watcher(缩略图测量/滚动重置等)照常触发
2912
+ function applyHistoryEntry<T>(entryStrs: string[], currentStrs: string[], list: T[]): { list: T[]; changed: boolean } {
2913
+ if (entryStrs.length !== list.length || entryStrs.length !== currentStrs.length) {
2914
+ // 结构性差异(增删页):与旧行为一致,整册逐页解析
2915
+ return { list: entryStrs.map(s => parsePage<T>(s)), changed: true }
2916
+ }
2917
+ let changed = false
2918
+ const next = list.map((page, i) => {
2919
+ if (entryStrs[i] === currentStrs[i]) return page
2920
+ changed = true
2921
+ return parsePage<T>(entryStrs[i])
2922
+ })
2923
+ return { list: next, changed }
2924
+ }
2925
+
2749
2926
  function undo() {
2750
2927
  if (isMasterEditingView.value) {
2751
2928
  // 先落盘防抖中的快照:编辑后 350ms 内撤销不再丢最后一步编辑
2752
2929
  flushPendingMasterHistorySnapshot()
2753
2930
  if (masterHistoryIndex.value > 0) {
2931
+ const __perfT0 = performance.now()
2754
2932
  isUndoingRedoing.value = true
2755
2933
  masterHistoryIndex.value--
2756
- slideMasters.value = JSON.parse(masterHistoryStack.value[masterHistoryIndex.value]) as SlideMaster[]
2934
+ const entry = masterHistoryStack.value[masterHistoryIndex.value]
2935
+ slideMasters.value = applyHistoryEntry<SlideMaster>(entry, currentMasterStrs, slideMasters.value).list
2936
+ currentMasterStrs = entry.slice()
2937
+ currentMasterIds = slideMasters.value.map(m => m && m.id)
2757
2938
  activeMasterId.value = masterHistoryActiveMids[masterHistoryIndex.value]
2758
2939
  activeLayoutId.value = masterHistoryActiveLids[masterHistoryIndex.value]
2759
2940
  selectedElementId.value = null
2760
2941
  selectedElementIds.value = []
2942
+ if (import.meta.env.DEV) console.debug(`[perf:undo-master] patch ${(performance.now() - __perfT0).toFixed(1)}ms masters=${slideMasters.value.length}`)
2761
2943
  notify('已撤销')
2762
2944
  nextTick(() => {
2763
2945
  isUndoingRedoing.value = false
@@ -2770,14 +2952,19 @@ function undo() {
2770
2952
  // 先落盘防抖中的快照:编辑后 350ms 内撤销不再丢最后一步编辑
2771
2953
  flushPendingHistorySnapshot()
2772
2954
  if (canUndo.value) {
2955
+ const __perfT0 = performance.now()
2773
2956
  isUndoingRedoing.value = true
2774
2957
  historyIndex.value--
2775
- slides.value = JSON.parse(historyStack.value[historyIndex.value]) as Slide[]
2958
+ const entry = historyStack.value[historyIndex.value]
2959
+ slides.value = applyHistoryEntry<Slide>(entry, currentPageStrs, slides.value).list
2960
+ currentPageStrs = entry.slice()
2961
+ currentPageIds = slides.value.map(s => s && s.id)
2776
2962
  activeIndex.value = historyActiveIndexes[historyIndex.value]
2777
2963
  selectedElementId.value = null
2778
2964
  selectedElementIds.value = []
2779
2965
  syncDOMWithRestoredSlides()
2780
2966
  activeSlideVersion.value++
2967
+ if (import.meta.env.DEV) console.debug(`[perf:undo] patch ${(performance.now() - __perfT0).toFixed(1)}ms pages=${slides.value.length}`)
2781
2968
  notify('已撤销')
2782
2969
  nextTick(() => {
2783
2970
  isUndoingRedoing.value = false
@@ -2791,15 +2978,20 @@ function redo() {
2791
2978
  if (isMasterEditingView.value) {
2792
2979
  flushPendingMasterHistorySnapshot()
2793
2980
  if (masterHistoryIndex.value < masterHistoryStack.value.length - 1) {
2981
+ const __perfT0 = performance.now()
2794
2982
  isUndoingRedoing.value = true
2795
2983
  masterHistoryIndex.value++
2796
- slideMasters.value = JSON.parse(masterHistoryStack.value[masterHistoryIndex.value]) as SlideMaster[]
2984
+ const entry = masterHistoryStack.value[masterHistoryIndex.value]
2985
+ slideMasters.value = applyHistoryEntry<SlideMaster>(entry, currentMasterStrs, slideMasters.value).list
2986
+ currentMasterStrs = entry.slice()
2987
+ currentMasterIds = slideMasters.value.map(m => m && m.id)
2797
2988
  activeMasterId.value = masterHistoryActiveMids[masterHistoryIndex.value]
2798
2989
  activeLayoutId.value = masterHistoryActiveLids[masterHistoryIndex.value]
2799
2990
  selectedElementId.value = null
2800
2991
  selectedElementIds.value = []
2801
2992
  syncDOMWithRestoredSlides()
2802
2993
  activeSlideVersion.value++
2994
+ if (import.meta.env.DEV) console.debug(`[perf:redo-master] patch ${(performance.now() - __perfT0).toFixed(1)}ms masters=${slideMasters.value.length}`)
2803
2995
  notify('已重做')
2804
2996
  nextTick(() => {
2805
2997
  isUndoingRedoing.value = false
@@ -2811,14 +3003,19 @@ function redo() {
2811
3003
  }
2812
3004
  flushPendingHistorySnapshot()
2813
3005
  if (canRedo.value) {
3006
+ const __perfT0 = performance.now()
2814
3007
  isUndoingRedoing.value = true
2815
3008
  historyIndex.value++
2816
- slides.value = JSON.parse(historyStack.value[historyIndex.value]) as Slide[]
3009
+ const entry = historyStack.value[historyIndex.value]
3010
+ slides.value = applyHistoryEntry<Slide>(entry, currentPageStrs, slides.value).list
3011
+ currentPageStrs = entry.slice()
3012
+ currentPageIds = slides.value.map(s => s && s.id)
2817
3013
  activeIndex.value = historyActiveIndexes[historyIndex.value]
2818
3014
  selectedElementId.value = null
2819
3015
  selectedElementIds.value = []
2820
3016
  syncDOMWithRestoredSlides()
2821
3017
  activeSlideVersion.value++
3018
+ if (import.meta.env.DEV) console.debug(`[perf:redo] patch ${(performance.now() - __perfT0).toFixed(1)}ms pages=${slides.value.length}`)
2822
3019
  notify('已重做')
2823
3020
  nextTick(() => {
2824
3021
  isUndoingRedoing.value = false
package/src/index.ts CHANGED
@@ -4,7 +4,8 @@ import jvsPickerColorV3 from 'jvs-picker-color-v3'
4
4
  import PptEditor from './components/PptEditor.vue'
5
5
  import { useSlides } from './composables/useSlides'
6
6
 
7
- // 自动打入所有依赖样式,合并至 dist/lib/style.css,宿主无需额外引入 element-plus 样式
7
+ // 自动打入所有依赖样式与图标库,宿主无需在 index.html 额外引入 iconfont 或 element-plus 样式
8
+ import './assets/iconfont/iconfont.js'
8
9
  import 'element-plus/dist/index.css'
9
10
  import 'jvs-picker-color-v3/lib/jvs-picker-color-v3.css'
10
11
  import 'animate.css'
@@ -1,7 +0,0 @@
1
- import { t as e } from "./jvs-ppt-ui.es.js";
2
- //#region __vite-browser-external
3
- var t = /* @__PURE__ */ e(((e, t) => {
4
- t.exports = {};
5
- }));
6
- //#endregion
7
- export default t();