jvs-ppt-ui 0.0.6 → 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.
- package/dist/lib/jvs-ppt-ui.es.js +135084 -112697
- package/dist/lib/jvs-ppt-ui.umd.js +101 -101
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +36 -14
- package/src/components/CanvasArea.vue +51 -13
- package/src/components/Header.vue +38 -21
- package/src/components/PptEditor.vue +1040 -1005
- package/src/components/PresentationGroupChild.vue +1 -1
- package/src/components/PresentationMode.vue +4 -2
- package/src/components/SidebarLeft.vue +15 -8
- package/src/components/Toolbar.vue +6 -4
- package/src/components/sidebar/panels/SidebarDesignPanel.vue +1341 -1336
- package/src/composables/canvas/useCanvasTable.ts +183 -146
- package/src/composables/sidebar/useSidebarTextFormat.ts +1 -1
- package/src/composables/useSlides.ts +239 -38
- package/src/index.ts +2 -1
- package/src/utils/canvas/elementStyles.ts +1 -1
- package/dist/lib/__vite-browser-external-CbV4tiB6.js +0 -7
- package/dist/lib/dist-DkuYVyNR.js +0 -16010
- package/dist/lib/es-CfdgxdYp.js +0 -477
- package/dist/lib/jszip.min-Kba9Mb0l.js +0 -3034
- package/dist/lib/pptxgen.es-D0HrgUe_.js +0 -2352
|
@@ -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
|
-
//
|
|
2499
|
-
//
|
|
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
|
-
|
|
2541
|
-
|
|
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
|
-
|
|
2548
|
-
|
|
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
|
-
|
|
2569
|
-
|
|
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
|
-
|
|
2598
|
-
|
|
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
|
-
//
|
|
2609
|
-
historyStack.value.push(
|
|
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
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -5269,7 +5466,7 @@ function extractPatternFromFill(el: any): string | undefined {
|
|
|
5269
5466
|
if (typeof val === 'string') {
|
|
5270
5467
|
pattern = val
|
|
5271
5468
|
} else if (val && typeof val === 'object') {
|
|
5272
|
-
pattern = val.
|
|
5469
|
+
pattern = val.blob || val.base64 || val.src || val.url || ''
|
|
5273
5470
|
}
|
|
5274
5471
|
}
|
|
5275
5472
|
}
|
|
@@ -8058,9 +8255,13 @@ async function uploadMediaResourcesInSlides(deckOrSlides: any): Promise<void> {
|
|
|
8058
8255
|
const remote = await uploadMediaUrl(targetSrc, 'image', 'svg')
|
|
8059
8256
|
el.src = remote
|
|
8060
8257
|
}
|
|
8061
|
-
} else if (el.type === 'shape'
|
|
8062
|
-
const
|
|
8063
|
-
|
|
8258
|
+
} else if (el.type === 'shape') {
|
|
8259
|
+
const targetPattern = el.pattern || el.style?.pattern || (el.fill?.type === 'image' ? (el.fill.value?.blob || el.fill.value?.base64 || el.fill.value?.src) : undefined)
|
|
8260
|
+
if (targetPattern && shouldUpload(targetPattern)) {
|
|
8261
|
+
const remote = await uploadMediaUrl(targetPattern, 'image', 'png')
|
|
8262
|
+
el.pattern = remote
|
|
8263
|
+
if (el.style) el.style.pattern = remote
|
|
8264
|
+
}
|
|
8064
8265
|
}
|
|
8065
8266
|
}
|
|
8066
8267
|
|
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
|
-
//
|
|
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'
|
|
@@ -186,7 +186,7 @@ export function getElementPattern(el: any): string | undefined {
|
|
|
186
186
|
if (typeof val === 'string') {
|
|
187
187
|
pattern = val
|
|
188
188
|
} else if (val && typeof val === 'object') {
|
|
189
|
-
pattern = val.
|
|
189
|
+
pattern = val.blob || val.base64 || val.src || val.url || ''
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|