@templatical/core 0.9.1 → 0.10.1
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/cloud/index.d.ts +397 -375
- package/dist/cloud/index.js +2179 -2698
- package/dist/cloud/index.js.map +1 -1
- package/dist/editor-zvlM4bZ7.d.ts +46 -0
- package/dist/index.d.ts +68 -62
- package/dist/index.js +484 -600
- package/dist/index.js.map +1 -1
- package/package.json +5 -6
- package/dist/editor-D7sbEq2_.d.ts +0 -44
package/dist/index.js
CHANGED
|
@@ -1,619 +1,503 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
computed,
|
|
5
|
-
reactive,
|
|
6
|
-
readonly
|
|
7
|
-
} from "@vue/reactivity";
|
|
1
|
+
import { createBlock, createDefaultTemplateContent, generateId } from "@templatical/types";
|
|
2
|
+
import { computed, reactive, readonly, ref, watch } from "@vue/reactivity";
|
|
3
|
+
//#region src/editor.ts
|
|
8
4
|
function getColumnCount(layout) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
if (layout === "1") return 1;
|
|
6
|
+
if (layout === "3") return 3;
|
|
7
|
+
return 2;
|
|
12
8
|
}
|
|
13
9
|
function useEditor(options) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const parent = findBlockParent(state.content.blocks, blockId);
|
|
186
|
-
if (!parent) return;
|
|
187
|
-
const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
|
|
188
|
-
if (oldIndex === -1) return;
|
|
189
|
-
let targetArray;
|
|
190
|
-
if (targetSectionId) {
|
|
191
|
-
const section = findBlockById(state.content.blocks, targetSectionId);
|
|
192
|
-
if (!section || section.type !== "section") return;
|
|
193
|
-
if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
section.children[columnIndex] = section.children[columnIndex] || [];
|
|
197
|
-
targetArray = section.children[columnIndex];
|
|
198
|
-
} else {
|
|
199
|
-
targetArray = state.content.blocks;
|
|
200
|
-
}
|
|
201
|
-
const [block] = parent.blocks.splice(oldIndex, 1);
|
|
202
|
-
targetArray.splice(newIndex, 0, block);
|
|
203
|
-
state.isDirty = true;
|
|
204
|
-
}
|
|
205
|
-
function markDirty() {
|
|
206
|
-
state.isDirty = true;
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
state: readonly(state),
|
|
210
|
-
content,
|
|
211
|
-
selectedBlock,
|
|
212
|
-
isBlockLocked,
|
|
213
|
-
setContent,
|
|
214
|
-
selectBlock,
|
|
215
|
-
setViewport,
|
|
216
|
-
setDarkMode,
|
|
217
|
-
setUiTheme,
|
|
218
|
-
setPreviewMode,
|
|
219
|
-
updateBlock,
|
|
220
|
-
updateSettings,
|
|
221
|
-
addBlock,
|
|
222
|
-
removeBlock,
|
|
223
|
-
moveBlock,
|
|
224
|
-
markDirty,
|
|
225
|
-
findBlockLocation
|
|
226
|
-
};
|
|
10
|
+
const state = reactive({
|
|
11
|
+
content: options.content ?? createDefaultTemplateContent(options.defaultFontFamily, options.templateDefaults),
|
|
12
|
+
selectedBlockId: null,
|
|
13
|
+
viewport: "desktop",
|
|
14
|
+
darkMode: false,
|
|
15
|
+
previewMode: false,
|
|
16
|
+
isDirty: false,
|
|
17
|
+
uiTheme: "auto"
|
|
18
|
+
});
|
|
19
|
+
const content = computed({
|
|
20
|
+
get: () => state.content,
|
|
21
|
+
set: (value) => {
|
|
22
|
+
state.content = value;
|
|
23
|
+
state.isDirty = true;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const selectedBlock = computed(() => {
|
|
27
|
+
if (!state.selectedBlockId) return null;
|
|
28
|
+
return findBlockById(state.content.blocks, state.selectedBlockId);
|
|
29
|
+
});
|
|
30
|
+
function findBlockById(blocks, id) {
|
|
31
|
+
for (const block of blocks) {
|
|
32
|
+
if (block.id === id) return block;
|
|
33
|
+
if (block.type === "section") for (const column of block.children) {
|
|
34
|
+
const found = findBlockById(column, id);
|
|
35
|
+
if (found) return found;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function collectBlockIds(block, ids) {
|
|
41
|
+
ids.add(block.id);
|
|
42
|
+
if (block.type === "section") for (const column of block.children) for (const child of column) collectBlockIds(child, ids);
|
|
43
|
+
}
|
|
44
|
+
function findBlockParent(blocks, id, parent = { blocks }) {
|
|
45
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
46
|
+
const block = blocks[i];
|
|
47
|
+
if (block.id === id) return parent;
|
|
48
|
+
if (block.type === "section") for (let colIdx = 0; colIdx < block.children.length; colIdx++) {
|
|
49
|
+
const result = findBlockParent(block.children[colIdx], id, {
|
|
50
|
+
blocks: block.children[colIdx],
|
|
51
|
+
sectionId: block.id,
|
|
52
|
+
columnIndex: colIdx
|
|
53
|
+
});
|
|
54
|
+
if (result) return result;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function isBlockLocked(blockId) {
|
|
60
|
+
return options.lockedBlocks?.value.has(blockId) ?? false;
|
|
61
|
+
}
|
|
62
|
+
function findBlockLocation(blockId) {
|
|
63
|
+
const parent = findBlockParent(state.content.blocks, blockId);
|
|
64
|
+
if (!parent) return null;
|
|
65
|
+
const index = parent.blocks.findIndex((b) => b.id === blockId);
|
|
66
|
+
if (index === -1) return null;
|
|
67
|
+
return {
|
|
68
|
+
targetSectionId: parent.sectionId,
|
|
69
|
+
columnIndex: parent.columnIndex,
|
|
70
|
+
index
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function setContent(newContent, markDirty = true) {
|
|
74
|
+
state.content = newContent;
|
|
75
|
+
if (markDirty) state.isDirty = true;
|
|
76
|
+
}
|
|
77
|
+
function selectBlock(blockId) {
|
|
78
|
+
if (blockId && isBlockLocked(blockId)) return;
|
|
79
|
+
state.selectedBlockId = blockId;
|
|
80
|
+
}
|
|
81
|
+
function setViewport(viewport) {
|
|
82
|
+
state.viewport = viewport;
|
|
83
|
+
}
|
|
84
|
+
function setDarkMode(darkMode) {
|
|
85
|
+
state.darkMode = darkMode;
|
|
86
|
+
}
|
|
87
|
+
function setUiTheme(theme) {
|
|
88
|
+
state.uiTheme = theme;
|
|
89
|
+
}
|
|
90
|
+
function setPreviewMode(previewMode) {
|
|
91
|
+
state.previewMode = previewMode;
|
|
92
|
+
if (previewMode) state.selectedBlockId = null;
|
|
93
|
+
}
|
|
94
|
+
function updateBlock(blockId, updates) {
|
|
95
|
+
if (isBlockLocked(blockId)) return;
|
|
96
|
+
const block = findBlockById(state.content.blocks, blockId);
|
|
97
|
+
if (block) {
|
|
98
|
+
Object.assign(block, updates);
|
|
99
|
+
state.isDirty = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function updateSettings(updates) {
|
|
103
|
+
state.content.settings = {
|
|
104
|
+
...state.content.settings,
|
|
105
|
+
...updates
|
|
106
|
+
};
|
|
107
|
+
state.isDirty = true;
|
|
108
|
+
}
|
|
109
|
+
function addBlock(block, targetSectionId, columnIndex = 0, index) {
|
|
110
|
+
if (targetSectionId) {
|
|
111
|
+
if (isBlockLocked(targetSectionId)) return;
|
|
112
|
+
const section = findBlockById(state.content.blocks, targetSectionId);
|
|
113
|
+
if (section && section.type === "section") {
|
|
114
|
+
if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) return;
|
|
115
|
+
section.children[columnIndex] = section.children[columnIndex] || [];
|
|
116
|
+
const targetArray = section.children[columnIndex];
|
|
117
|
+
if (index !== void 0 && index < targetArray.length) targetArray.splice(index, 0, block);
|
|
118
|
+
else targetArray.push(block);
|
|
119
|
+
}
|
|
120
|
+
} else if (index !== void 0 && index < state.content.blocks.length) state.content.blocks.splice(index, 0, block);
|
|
121
|
+
else state.content.blocks.push(block);
|
|
122
|
+
state.isDirty = true;
|
|
123
|
+
}
|
|
124
|
+
function removeBlock(blockId) {
|
|
125
|
+
if (isBlockLocked(blockId)) return;
|
|
126
|
+
const parent = findBlockParent(state.content.blocks, blockId);
|
|
127
|
+
if (parent) {
|
|
128
|
+
const index = parent.blocks.findIndex((b) => b.id === blockId);
|
|
129
|
+
if (index !== -1) {
|
|
130
|
+
const [removed] = parent.blocks.splice(index, 1);
|
|
131
|
+
if (state.selectedBlockId) {
|
|
132
|
+
const removedIds = /* @__PURE__ */ new Set();
|
|
133
|
+
collectBlockIds(removed, removedIds);
|
|
134
|
+
if (removedIds.has(state.selectedBlockId)) state.selectedBlockId = null;
|
|
135
|
+
}
|
|
136
|
+
state.isDirty = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function moveBlock(blockId, newIndex, targetSectionId, columnIndex = 0) {
|
|
141
|
+
if (isBlockLocked(blockId)) return;
|
|
142
|
+
if (targetSectionId && isBlockLocked(targetSectionId)) return;
|
|
143
|
+
const parent = findBlockParent(state.content.blocks, blockId);
|
|
144
|
+
if (!parent) return;
|
|
145
|
+
const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
|
|
146
|
+
if (oldIndex === -1) return;
|
|
147
|
+
let targetArray;
|
|
148
|
+
if (targetSectionId) {
|
|
149
|
+
const section = findBlockById(state.content.blocks, targetSectionId);
|
|
150
|
+
if (!section || section.type !== "section") return;
|
|
151
|
+
if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) return;
|
|
152
|
+
section.children[columnIndex] = section.children[columnIndex] || [];
|
|
153
|
+
targetArray = section.children[columnIndex];
|
|
154
|
+
} else targetArray = state.content.blocks;
|
|
155
|
+
const [block] = parent.blocks.splice(oldIndex, 1);
|
|
156
|
+
targetArray.splice(newIndex, 0, block);
|
|
157
|
+
state.isDirty = true;
|
|
158
|
+
}
|
|
159
|
+
function markDirty() {
|
|
160
|
+
state.isDirty = true;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
state: readonly(state),
|
|
164
|
+
content,
|
|
165
|
+
selectedBlock,
|
|
166
|
+
isBlockLocked,
|
|
167
|
+
setContent,
|
|
168
|
+
selectBlock,
|
|
169
|
+
setViewport,
|
|
170
|
+
setDarkMode,
|
|
171
|
+
setUiTheme,
|
|
172
|
+
setPreviewMode,
|
|
173
|
+
updateBlock,
|
|
174
|
+
updateSettings,
|
|
175
|
+
addBlock,
|
|
176
|
+
removeBlock,
|
|
177
|
+
moveBlock,
|
|
178
|
+
markDirty,
|
|
179
|
+
findBlockLocation
|
|
180
|
+
};
|
|
227
181
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
var NAVIGATE_IDLE_MS = 1500;
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/history.ts
|
|
184
|
+
const MAX_STACK_SIZE = 50;
|
|
185
|
+
const DEBOUNCE_MS = 300;
|
|
186
|
+
const NAVIGATE_IDLE_MS = 1500;
|
|
234
187
|
function useHistory(options) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
function destroy() {
|
|
337
|
-
clear();
|
|
338
|
-
if (navigatingTimeoutId) {
|
|
339
|
-
clearTimeout(navigatingTimeoutId);
|
|
340
|
-
navigatingTimeoutId = null;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
canUndo,
|
|
345
|
-
canRedo,
|
|
346
|
-
isNavigating,
|
|
347
|
-
undo,
|
|
348
|
-
redo,
|
|
349
|
-
record,
|
|
350
|
-
recordDebounced,
|
|
351
|
-
clear,
|
|
352
|
-
destroy
|
|
353
|
-
};
|
|
188
|
+
const { content, setContent, isRemoteOperation, maxSize = MAX_STACK_SIZE } = options;
|
|
189
|
+
const undoStack = ref([]);
|
|
190
|
+
const redoStack = ref([]);
|
|
191
|
+
const isNavigating = ref(false);
|
|
192
|
+
let navigatingTimeoutId = null;
|
|
193
|
+
let pendingDebounce = null;
|
|
194
|
+
const canUndo = computed(() => undoStack.value.length > 0);
|
|
195
|
+
const canRedo = computed(() => redoStack.value.length > 0);
|
|
196
|
+
function cloneContent() {
|
|
197
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
198
|
+
return JSON.parse(JSON.stringify(content.value, (_key, value) => {
|
|
199
|
+
if (typeof value === "object" && value !== null) {
|
|
200
|
+
if (seen.has(value)) return void 0;
|
|
201
|
+
seen.add(value);
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
function pushToUndoStack(snapshot) {
|
|
207
|
+
undoStack.value.push(snapshot);
|
|
208
|
+
if (undoStack.value.length > maxSize) undoStack.value.splice(0, undoStack.value.length - maxSize);
|
|
209
|
+
}
|
|
210
|
+
function flushPendingDebounce() {
|
|
211
|
+
if (pendingDebounce) {
|
|
212
|
+
clearTimeout(pendingDebounce.timeoutId);
|
|
213
|
+
pendingDebounce = null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function record() {
|
|
217
|
+
if (isRemoteOperation?.()) return;
|
|
218
|
+
flushPendingDebounce();
|
|
219
|
+
pushToUndoStack(cloneContent());
|
|
220
|
+
redoStack.value = [];
|
|
221
|
+
}
|
|
222
|
+
function recordDebounced(blockId) {
|
|
223
|
+
if (isRemoteOperation?.()) return;
|
|
224
|
+
if (pendingDebounce && pendingDebounce.blockId === blockId) {
|
|
225
|
+
clearTimeout(pendingDebounce.timeoutId);
|
|
226
|
+
pendingDebounce.timeoutId = setTimeout(() => {
|
|
227
|
+
pendingDebounce = null;
|
|
228
|
+
}, DEBOUNCE_MS);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
flushPendingDebounce();
|
|
232
|
+
pushToUndoStack(cloneContent());
|
|
233
|
+
redoStack.value = [];
|
|
234
|
+
pendingDebounce = {
|
|
235
|
+
blockId,
|
|
236
|
+
timeoutId: setTimeout(() => {
|
|
237
|
+
pendingDebounce = null;
|
|
238
|
+
}, DEBOUNCE_MS)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function setNavigating() {
|
|
242
|
+
isNavigating.value = true;
|
|
243
|
+
if (navigatingTimeoutId) clearTimeout(navigatingTimeoutId);
|
|
244
|
+
navigatingTimeoutId = setTimeout(() => {
|
|
245
|
+
isNavigating.value = false;
|
|
246
|
+
navigatingTimeoutId = null;
|
|
247
|
+
}, NAVIGATE_IDLE_MS);
|
|
248
|
+
}
|
|
249
|
+
function undo() {
|
|
250
|
+
if (undoStack.value.length === 0) return;
|
|
251
|
+
flushPendingDebounce();
|
|
252
|
+
const snapshot = undoStack.value.pop();
|
|
253
|
+
redoStack.value.push(cloneContent());
|
|
254
|
+
setContent(snapshot, true);
|
|
255
|
+
setNavigating();
|
|
256
|
+
}
|
|
257
|
+
function redo() {
|
|
258
|
+
if (redoStack.value.length === 0) return;
|
|
259
|
+
flushPendingDebounce();
|
|
260
|
+
const snapshot = redoStack.value.pop();
|
|
261
|
+
undoStack.value.push(cloneContent());
|
|
262
|
+
setContent(snapshot, true);
|
|
263
|
+
setNavigating();
|
|
264
|
+
}
|
|
265
|
+
function clear() {
|
|
266
|
+
undoStack.value = [];
|
|
267
|
+
redoStack.value = [];
|
|
268
|
+
flushPendingDebounce();
|
|
269
|
+
}
|
|
270
|
+
function destroy() {
|
|
271
|
+
clear();
|
|
272
|
+
if (navigatingTimeoutId) {
|
|
273
|
+
clearTimeout(navigatingTimeoutId);
|
|
274
|
+
navigatingTimeoutId = null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
canUndo,
|
|
279
|
+
canRedo,
|
|
280
|
+
isNavigating,
|
|
281
|
+
undo,
|
|
282
|
+
redo,
|
|
283
|
+
record,
|
|
284
|
+
recordDebounced,
|
|
285
|
+
clear,
|
|
286
|
+
destroy
|
|
287
|
+
};
|
|
354
288
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
import { createBlock, generateId } from "@templatical/types";
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/block-actions.ts
|
|
358
291
|
function regenerateNestedIds(block) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
292
|
+
if (block.type === "table") block.rows = block.rows.map((row) => ({
|
|
293
|
+
...row,
|
|
294
|
+
id: generateId(),
|
|
295
|
+
cells: row.cells.map((cell) => ({
|
|
296
|
+
...cell,
|
|
297
|
+
id: generateId()
|
|
298
|
+
}))
|
|
299
|
+
}));
|
|
300
|
+
else if (block.type === "social") block.icons = block.icons.map((icon) => ({
|
|
301
|
+
...icon,
|
|
302
|
+
id: generateId()
|
|
303
|
+
}));
|
|
304
|
+
else if (block.type === "menu") block.items = block.items.map((item) => ({
|
|
305
|
+
...item,
|
|
306
|
+
id: generateId()
|
|
307
|
+
}));
|
|
370
308
|
}
|
|
371
309
|
function useBlockActions(options) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
function deleteBlock(blockId) {
|
|
412
|
-
removeBlock(blockId);
|
|
413
|
-
}
|
|
414
|
-
function updateBlockProperty(blockId, key, value) {
|
|
415
|
-
updateBlock(blockId, { [key]: value });
|
|
416
|
-
}
|
|
417
|
-
return {
|
|
418
|
-
createAndAddBlock,
|
|
419
|
-
duplicateBlock,
|
|
420
|
-
deleteBlock,
|
|
421
|
-
updateBlockProperty
|
|
422
|
-
};
|
|
310
|
+
const { addBlock, removeBlock, updateBlock, selectBlock, findBlockLocation } = options;
|
|
311
|
+
function createAndAddBlock(type, targetSectionId, columnIndex) {
|
|
312
|
+
const block = createBlock(type, options.blockDefaults);
|
|
313
|
+
addBlock(block, targetSectionId, columnIndex);
|
|
314
|
+
selectBlock(block.id);
|
|
315
|
+
return block;
|
|
316
|
+
}
|
|
317
|
+
function duplicateBlock(block, targetSectionId, columnIndex) {
|
|
318
|
+
const cloned = JSON.parse(JSON.stringify(block));
|
|
319
|
+
cloned.id = generateId();
|
|
320
|
+
regenerateNestedIds(cloned);
|
|
321
|
+
if (cloned.type === "section") cloned.children = cloned.children.map((column) => column.map((child) => {
|
|
322
|
+
const clonedChild = JSON.parse(JSON.stringify(child));
|
|
323
|
+
clonedChild.id = generateId();
|
|
324
|
+
regenerateNestedIds(clonedChild);
|
|
325
|
+
return clonedChild;
|
|
326
|
+
}));
|
|
327
|
+
if (targetSectionId !== void 0 || columnIndex !== void 0) addBlock(cloned, targetSectionId, columnIndex);
|
|
328
|
+
else {
|
|
329
|
+
const sourceLocation = findBlockLocation?.(block.id) ?? null;
|
|
330
|
+
if (sourceLocation) addBlock(cloned, sourceLocation.targetSectionId, sourceLocation.columnIndex, sourceLocation.index + 1);
|
|
331
|
+
else addBlock(cloned, targetSectionId, columnIndex);
|
|
332
|
+
}
|
|
333
|
+
selectBlock(cloned.id);
|
|
334
|
+
return cloned;
|
|
335
|
+
}
|
|
336
|
+
function deleteBlock(blockId) {
|
|
337
|
+
removeBlock(blockId);
|
|
338
|
+
}
|
|
339
|
+
function updateBlockProperty(blockId, key, value) {
|
|
340
|
+
updateBlock(blockId, { [key]: value });
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
createAndAddBlock,
|
|
344
|
+
duplicateBlock,
|
|
345
|
+
deleteBlock,
|
|
346
|
+
updateBlockProperty
|
|
347
|
+
};
|
|
423
348
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
import { watch } from "@vue/reactivity";
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/auto-save.ts
|
|
427
351
|
function useAutoSave(options) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
scheduleOnChange();
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
{ deep: true }
|
|
477
|
-
);
|
|
478
|
-
function destroy() {
|
|
479
|
-
stopWatch();
|
|
480
|
-
cancel();
|
|
481
|
-
}
|
|
482
|
-
return {
|
|
483
|
-
flush,
|
|
484
|
-
cancel,
|
|
485
|
-
pause,
|
|
486
|
-
resume,
|
|
487
|
-
destroy
|
|
488
|
-
};
|
|
352
|
+
const { content, isDirty, onChange, debounce = 1e3, enabled = true } = options;
|
|
353
|
+
let timeoutId = null;
|
|
354
|
+
let paused = false;
|
|
355
|
+
function isEnabled() {
|
|
356
|
+
return typeof enabled === "function" ? enabled() : enabled;
|
|
357
|
+
}
|
|
358
|
+
function pause() {
|
|
359
|
+
paused = true;
|
|
360
|
+
cancel();
|
|
361
|
+
}
|
|
362
|
+
function resume() {
|
|
363
|
+
paused = false;
|
|
364
|
+
}
|
|
365
|
+
function cancel() {
|
|
366
|
+
if (timeoutId) {
|
|
367
|
+
clearTimeout(timeoutId);
|
|
368
|
+
timeoutId = null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function flush() {
|
|
372
|
+
cancel();
|
|
373
|
+
if (isDirty()) onChange(JSON.parse(JSON.stringify(content.value)));
|
|
374
|
+
}
|
|
375
|
+
function scheduleOnChange() {
|
|
376
|
+
if (!isEnabled() || paused) return;
|
|
377
|
+
cancel();
|
|
378
|
+
timeoutId = setTimeout(() => {
|
|
379
|
+
timeoutId = null;
|
|
380
|
+
if (isEnabled() && !paused && isDirty()) onChange(JSON.parse(JSON.stringify(content.value)));
|
|
381
|
+
}, debounce);
|
|
382
|
+
}
|
|
383
|
+
const stopWatch = watch(content, () => {
|
|
384
|
+
if (isEnabled() && !paused && isDirty()) scheduleOnChange();
|
|
385
|
+
}, { deep: true });
|
|
386
|
+
function destroy() {
|
|
387
|
+
stopWatch();
|
|
388
|
+
cancel();
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
flush,
|
|
392
|
+
cancel,
|
|
393
|
+
pause,
|
|
394
|
+
resume,
|
|
395
|
+
destroy
|
|
396
|
+
};
|
|
489
397
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
import { computed as computed3, reactive as reactive2 } from "@vue/reactivity";
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/condition-preview.ts
|
|
493
400
|
function useConditionPreview(editor) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
reset,
|
|
516
|
-
hasHiddenBlocks
|
|
517
|
-
};
|
|
401
|
+
const hiddenBlockIds = reactive(/* @__PURE__ */ new Set());
|
|
402
|
+
const hasHiddenBlocks = computed(() => hiddenBlockIds.size > 0);
|
|
403
|
+
function isHidden(blockId) {
|
|
404
|
+
return hiddenBlockIds.has(blockId);
|
|
405
|
+
}
|
|
406
|
+
function toggleBlock(blockId) {
|
|
407
|
+
if (hiddenBlockIds.has(blockId)) hiddenBlockIds.delete(blockId);
|
|
408
|
+
else {
|
|
409
|
+
hiddenBlockIds.add(blockId);
|
|
410
|
+
if (editor.state.selectedBlockId === blockId) editor.selectBlock(null);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function reset() {
|
|
414
|
+
hiddenBlockIds.clear();
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
isHidden,
|
|
418
|
+
toggleBlock,
|
|
419
|
+
reset,
|
|
420
|
+
hasHiddenBlocks
|
|
421
|
+
};
|
|
518
422
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
import { computed as computed4, ref as ref2 } from "@vue/reactivity";
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/data-source-fetch.ts
|
|
522
425
|
function useDataSourceFetch(options) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
isFetching.value = false;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
return {
|
|
559
|
-
isFetching,
|
|
560
|
-
fetchError,
|
|
561
|
-
fetch,
|
|
562
|
-
hasDataSource,
|
|
563
|
-
needsFetch
|
|
564
|
-
};
|
|
426
|
+
const isFetching = ref(false);
|
|
427
|
+
const fetchError = ref(false);
|
|
428
|
+
const hasDataSource = computed(() => !!options.definition.value?.dataSource);
|
|
429
|
+
const needsFetch = computed(() => hasDataSource.value && !options.block.value.dataSourceFetched);
|
|
430
|
+
async function fetch() {
|
|
431
|
+
const def = options.definition.value;
|
|
432
|
+
if (!def?.dataSource) return;
|
|
433
|
+
isFetching.value = true;
|
|
434
|
+
fetchError.value = false;
|
|
435
|
+
try {
|
|
436
|
+
const result = await def.dataSource.onFetch({
|
|
437
|
+
fieldValues: { ...options.block.value.fieldValues },
|
|
438
|
+
blockId: options.block.value.id
|
|
439
|
+
});
|
|
440
|
+
if (result == null) return;
|
|
441
|
+
const merged = { ...options.block.value.fieldValues };
|
|
442
|
+
for (const key of Object.keys(merged)) if (key in result) merged[key] = result[key];
|
|
443
|
+
options.onUpdate(merged, true);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.warn("[Templatical] Data source fetch error:", error);
|
|
446
|
+
fetchError.value = true;
|
|
447
|
+
} finally {
|
|
448
|
+
isFetching.value = false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
isFetching,
|
|
453
|
+
fetchError,
|
|
454
|
+
fetch,
|
|
455
|
+
hasDataSource,
|
|
456
|
+
needsFetch
|
|
457
|
+
};
|
|
565
458
|
}
|
|
566
|
-
|
|
567
|
-
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/history-interceptor.ts
|
|
461
|
+
/**
|
|
462
|
+
* Wraps editor mutation methods to record history snapshots before each
|
|
463
|
+
* operation. Mutates the editor object in place.
|
|
464
|
+
*
|
|
465
|
+
* Must be applied **after** any collaboration broadcast wrapping so the
|
|
466
|
+
* call chain is: history.record() → broadcast → original mutation.
|
|
467
|
+
*/
|
|
568
468
|
function useHistoryInterceptor(editor, history) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (editor.isBlockLocked(blockId)) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
history.recordDebounced(blockId);
|
|
603
|
-
originalUpdateBlock(blockId, updates);
|
|
604
|
-
};
|
|
605
|
-
editor.updateSettings = (updates) => {
|
|
606
|
-
history.record();
|
|
607
|
-
originalUpdateSettings(updates);
|
|
608
|
-
};
|
|
469
|
+
const originalAddBlock = editor.addBlock;
|
|
470
|
+
const originalRemoveBlock = editor.removeBlock;
|
|
471
|
+
const originalMoveBlock = editor.moveBlock;
|
|
472
|
+
const originalUpdateBlock = editor.updateBlock;
|
|
473
|
+
const originalUpdateSettings = editor.updateSettings;
|
|
474
|
+
editor.addBlock = (block, targetSectionId, columnIndex, index) => {
|
|
475
|
+
if (targetSectionId && editor.isBlockLocked(targetSectionId)) return;
|
|
476
|
+
history.record();
|
|
477
|
+
originalAddBlock(block, targetSectionId, columnIndex, index);
|
|
478
|
+
};
|
|
479
|
+
editor.removeBlock = (blockId) => {
|
|
480
|
+
if (editor.isBlockLocked(blockId)) return;
|
|
481
|
+
history.record();
|
|
482
|
+
originalRemoveBlock(blockId);
|
|
483
|
+
};
|
|
484
|
+
editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
|
|
485
|
+
if (editor.isBlockLocked(blockId)) return;
|
|
486
|
+
if (targetSectionId && editor.isBlockLocked(targetSectionId)) return;
|
|
487
|
+
history.record();
|
|
488
|
+
originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
|
|
489
|
+
};
|
|
490
|
+
editor.updateBlock = (blockId, updates) => {
|
|
491
|
+
if (editor.isBlockLocked(blockId)) return;
|
|
492
|
+
history.recordDebounced(blockId);
|
|
493
|
+
originalUpdateBlock(blockId, updates);
|
|
494
|
+
};
|
|
495
|
+
editor.updateSettings = (updates) => {
|
|
496
|
+
history.record();
|
|
497
|
+
originalUpdateSettings(updates);
|
|
498
|
+
};
|
|
609
499
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
useConditionPreview,
|
|
614
|
-
useDataSourceFetch,
|
|
615
|
-
useEditor,
|
|
616
|
-
useHistory,
|
|
617
|
-
useHistoryInterceptor
|
|
618
|
-
};
|
|
500
|
+
//#endregion
|
|
501
|
+
export { useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor };
|
|
502
|
+
|
|
619
503
|
//# sourceMappingURL=index.js.map
|