@@ -4,6 +4,7 @@ import {Constants} from "../../constants";
44import { hideElements } from "../ui/hideElements" ;
55import { scrollCenter } from "../../util/highlightById" ;
66import { matchHotKey } from "../util/hotKey" ;
7+ import { fetchSyncPost } from "../../util/fetch" ;
78import { ipcRenderer } from "electron" ;
89import { markMirror , refreshUndoButtons , requestRedo , requestUndo } from "./globalUndo" ;
910
@@ -26,6 +27,96 @@ export class Undo {
2627 requestRedo ( protyle ) ;
2728 }
2829
30+
31+ // 重放 insert 操作前检查块 ID 是否已被占用(例如剪切后粘贴到其他编辑器时保留了原 ID,
32+ // 此时撤销剪切会插入重复 ID 的块),冲突时在两个栈中统一替换为新 ID
33+ private async resolveDuplicateIds ( operations : IOperation [ ] ) {
34+ const ids = new Set < string > ( ) ;
35+ operations . forEach ( op => {
36+ if ( op . action === "insert" && typeof op . data === "string" ) {
37+ if ( op . id ) {
38+ ids . add ( op . id ) ;
39+ }
40+ op . data . match ( / d a t a - n o d e - i d = " [ ^ " ] + " / g) ?. forEach ( ( match : string ) => {
41+ ids . add ( match . substring ( 14 , match . length - 1 ) ) ;
42+ } ) ;
43+ }
44+ } ) ;
45+ if ( ids . size === 0 ) {
46+ return ;
47+ }
48+ let existResponse : IWebSocketData ;
49+ try {
50+ existResponse = await fetchSyncPost ( "/api/block/checkBlocksExist" , { ids : Array . from ( ids ) } ) ;
51+ } catch ( e ) {
52+ return ;
53+ }
54+ if ( ! existResponse ?. data ) {
55+ return ;
56+ }
57+ const replacements : [ string , string ] [ ] = [ ] ;
58+ ids . forEach ( id => {
59+ if ( existResponse . data [ id ] === true ) {
60+ replacements . push ( [ id , Lute . NewNodeID ( ) ] ) ;
61+ }
62+ } ) ;
63+ if ( replacements . length === 0 ) {
64+ return ;
65+ }
66+ [ this . undoStack , this . redoStack ] . forEach ( stack => {
67+ stack . forEach ( item => {
68+ [ item . doOperations , item . undoOperations ] . forEach ( ops => {
69+ ops . forEach ( op => {
70+ replacements . forEach ( ( [ oldId , newId ] ) => {
71+ if ( op . id === oldId ) {
72+ op . id = newId ;
73+ }
74+ if ( op . parentID === oldId ) {
75+ op . parentID = newId ;
76+ }
77+ if ( op . previousID === oldId ) {
78+ op . previousID = newId ;
79+ }
80+ if ( op . nextID === oldId ) {
81+ op . nextID = newId ;
82+ }
83+ if ( typeof op . data === "string" ) {
84+ op . data = op . data . split ( `data-node-id="${ oldId } "` ) . join ( `data-node-id="${ newId } "` ) ;
85+ }
86+ } ) ;
87+ } ) ;
88+ } ) ;
89+ } ) ;
90+ } ) ;
91+ }
92+
93+ private async render ( protyle : IProtyle , state : IOperations , redo : boolean ) {
94+ hideElements ( [ "hint" , "gutter" ] , protyle ) ;
95+ protyle . wysiwyg . lastHTMLs = { } ;
96+ await this . resolveDuplicateIds ( redo ? state . doOperations : state . undoOperations ) ;
97+ if ( ! redo ) {
98+ for ( let i = state . undoOperations . length - 1 ; i >= 0 ; i -- ) {
99+ if ( state . undoOperations [ i ] . action === "insert" ) {
100+ if ( state . undoOperations [ i ] . context ) {
101+ state . undoOperations [ i ] . context . setRange = "true" ;
102+ } else {
103+ state . undoOperations [ i ] . context = { setRange : "true" } ;
104+ }
105+ break ;
106+ }
107+ }
108+ onTransaction ( protyle , state . undoOperations , true ) ;
109+ transaction ( protyle , state . undoOperations , undefined , { skipSync : true } ) ;
110+ } else {
111+ for ( let i = state . doOperations . length - 1 ; i >= 0 ; i -- ) {
112+ if ( state . doOperations [ i ] . action === "insert" ) {
113+ if ( state . doOperations [ i ] . context ) {
114+ state . doOperations [ i ] . context . setRange = "true" ;
115+ } else {
116+ state . doOperations [ i ] . context = { setRange : "true" } ;
117+ }
118+ break ;
119+
29120 // renderLocal 仅在发起窗口本地应用操作(isUndo=true),不 POST 到 kernel
30121 // (kernel 的 undo/redo 接口已执行事务并广播)。保留光标恢复/折叠/zoom/lastHTMLs 行为。
31122 public renderLocal ( protyle : IProtyle , operations : IOperation [ ] , isRedo : boolean ) {
@@ -37,6 +128,7 @@ export class Undo {
37128 operations [ i ] . context . setRange = "true" ;
38129 } else {
39130 operations [ i ] . context = { setRange : "true" } ;
131+
40132 }
41133 break ;
42134 }
0 commit comments