@tmagic/editor 1.8.0-beta.27 → 1.8.0-beta.29
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/es/components/ScrollBar.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/fields/configs/stickyAddButton.js +3 -2
- package/dist/es/hooks/use-code-block-edit.js +4 -2
- package/dist/es/initService.js +14 -2
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/es/layouts/history-list/useHistoryRevert.js +1 -1
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +8 -7
- package/dist/es/layouts/sidebar/layer/use-drag.js +11 -10
- package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +54 -8
- package/dist/es/services/editor.js +31 -25
- package/dist/es/style.css +41 -15
- package/dist/es/utils/editor.js +4 -2
- package/dist/style.css +41 -15
- package/dist/themes/magic-admin.css +41 -15
- package/dist/tmagic-editor-headless.umd.cjs +60 -47
- package/dist/tmagic-editor.umd.cjs +165 -86
- package/package.json +10 -10
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +1 -1
- package/src/fields/configs/stickyAddButton.ts +3 -2
- package/src/initService.ts +24 -2
- package/src/layouts/history-list/useHistoryRevert.ts +1 -4
- package/src/layouts/workspace/viewer/Stage.vue +80 -10
- package/types/headless.d.ts +2 -3
- package/types/index.d.ts +244 -244
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.29",
|
|
3
3
|
"name": "@tmagic/editor",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -61,27 +61,27 @@
|
|
|
61
61
|
"keycon": "^1.4.0",
|
|
62
62
|
"lodash-es": "^4.18.1",
|
|
63
63
|
"moveable": "^0.53.0",
|
|
64
|
-
"serialize-javascript": "^7.
|
|
64
|
+
"serialize-javascript": "^7.1.1",
|
|
65
65
|
"sortablejs": "^1.15.7",
|
|
66
|
-
"@tmagic/design": "1.8.0-beta.
|
|
67
|
-
"@tmagic/
|
|
68
|
-
"@tmagic/
|
|
69
|
-
"@tmagic/
|
|
70
|
-
"@tmagic/utils": "1.8.0-beta.
|
|
66
|
+
"@tmagic/design": "1.8.0-beta.29",
|
|
67
|
+
"@tmagic/form": "1.8.0-beta.29",
|
|
68
|
+
"@tmagic/stage": "1.8.0-beta.29",
|
|
69
|
+
"@tmagic/table": "1.8.0-beta.29",
|
|
70
|
+
"@tmagic/utils": "1.8.0-beta.29"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/events": "^3.0.3",
|
|
74
74
|
"@types/lodash-es": "^4.17.12",
|
|
75
75
|
"@types/serialize-javascript": "^5.0.4",
|
|
76
76
|
"@types/sortablejs": "^1.15.9",
|
|
77
|
-
"@vue/test-utils": "^2.
|
|
77
|
+
"@vue/test-utils": "^2.5.1"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"monaco-editor": "^0.55.1 ",
|
|
81
81
|
"type-fest": "^5.2.0",
|
|
82
82
|
"typescript": "^6.0.3",
|
|
83
|
-
"vue": "^3.5.
|
|
84
|
-
"@tmagic/core": "1.8.0-beta.
|
|
83
|
+
"vue": "^3.5.43",
|
|
84
|
+
"@tmagic/core": "1.8.0-beta.29"
|
|
85
85
|
},
|
|
86
86
|
"peerDependenciesMeta": {
|
|
87
87
|
"typescript": {
|
|
@@ -118,7 +118,7 @@ const emit = defineEmits<{
|
|
|
118
118
|
change: [v: string[]];
|
|
119
119
|
}>();
|
|
120
120
|
|
|
121
|
-
const modelValue = defineModel<string[] | any>('modelValue', { default: [] });
|
|
121
|
+
const modelValue = defineModel<string[] | any>('modelValue', { default: () => [] });
|
|
122
122
|
|
|
123
123
|
const optionComponent = getDesignConfig('components')?.option;
|
|
124
124
|
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* 属性面板列表字段共用的吸底全宽「添加」按钮与吸顶标题,展开到 group-list 配置上。
|
|
21
21
|
*
|
|
22
|
-
*
|
|
22
|
+
* 吸底按钮会盖住列表底部,吸顶标题会盖住列表顶部,新增的项必须同时滚进视口才看得见。
|
|
23
23
|
*/
|
|
24
24
|
export const stickyAddButton = (text: string) => ({
|
|
25
25
|
scrollLastItemIntoView: true as const,
|
|
26
|
+
header: { sticky: true as const },
|
|
26
27
|
addButtonConfig: {
|
|
27
28
|
sticky: true as const,
|
|
28
29
|
text,
|
package/src/initService.ts
CHANGED
|
@@ -427,12 +427,26 @@ export const initServiceEvents = (
|
|
|
427
427
|
});
|
|
428
428
|
};
|
|
429
429
|
|
|
430
|
+
/**
|
|
431
|
+
* root 是否已经不是编辑器当前的 root。
|
|
432
|
+
*
|
|
433
|
+
* `editorService.set('root', v)` 是同步赋值后再派发 `root-change`,因此派发那一刻 `v` 一定
|
|
434
|
+
* 就是当前 root;但处理 `root-change` 要等 stage / runtime / 依赖收集等异步过程,期间 root
|
|
435
|
+
* 可能被新的一次整体替换(外部重设 DSL、更新 root 节点、源码保存等)顶掉,此时手上的 `v`
|
|
436
|
+
* 已是过期快照,继续用它刷画布或回写给外部都会与新 root 互相覆盖。
|
|
437
|
+
*/
|
|
438
|
+
const isStaleRoot = (value: MApp | null) => toRaw(editorService.get('root')) !== toRaw(value);
|
|
439
|
+
|
|
430
440
|
const updateStageDsl = async (value: MApp | null) => {
|
|
431
441
|
const stage = await getStage();
|
|
432
442
|
|
|
433
443
|
const runtime = await stage.renderer?.getRuntime();
|
|
434
444
|
const app = await getTMagicApp();
|
|
435
445
|
|
|
446
|
+
// 等 stage / runtime 就绪期间 root 已被替换:新 root 的刷新可能已经完成,
|
|
447
|
+
// 再把旧 dsl 推给 runtime 会让画布回退到旧内容
|
|
448
|
+
if (isStaleRoot(value)) return;
|
|
449
|
+
|
|
436
450
|
if (!app?.dataSourceManager) {
|
|
437
451
|
runtime?.updateRootConfig?.(cloneDeep(toRaw(value))!);
|
|
438
452
|
}
|
|
@@ -449,6 +463,8 @@ export const initServiceEvents = (
|
|
|
449
463
|
|
|
450
464
|
await (typeof Worker === 'undefined' ? collectIdle(value.items, true) : depService.collectByWorker(value));
|
|
451
465
|
|
|
466
|
+
if (isStaleRoot(value)) return;
|
|
467
|
+
|
|
452
468
|
const dsl = cloneDeep(toRaw(value));
|
|
453
469
|
if (dsl.dataSources && dsl.dataSourceDeps && app?.dataSourceManager) {
|
|
454
470
|
for (const node of getNodes(getDepNodeIds(dsl.dataSourceDeps), dsl.items)) {
|
|
@@ -466,7 +482,7 @@ export const initServiceEvents = (
|
|
|
466
482
|
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
|
467
483
|
};
|
|
468
484
|
|
|
469
|
-
const rootChangeHandler = (value: MApp | null
|
|
485
|
+
const rootChangeHandler = (value: MApp | null) => {
|
|
470
486
|
if (!value) return;
|
|
471
487
|
|
|
472
488
|
value.codeBlocks = value.codeBlocks || {};
|
|
@@ -509,7 +525,13 @@ export const initServiceEvents = (
|
|
|
509
525
|
editorService.set('page', null);
|
|
510
526
|
}
|
|
511
527
|
|
|
512
|
-
|
|
528
|
+
// 上面的 select 是异步的,期间 root 可能已被替换,过期快照不能再回写给外部:
|
|
529
|
+
// 两次整体替换各自持有一个快照时,回写会把对方的 root 顶掉,外部 modelValue 变化又会
|
|
530
|
+
// 重新 set root,两条链路无休止地交替下去(表现为编辑器卡死)
|
|
531
|
+
if (isStaleRoot(value)) return;
|
|
532
|
+
|
|
533
|
+
// 外部已经持有这个 root(如本次变化就是 modelValue 传进来的)时无需回写
|
|
534
|
+
if (toRaw(props.modelValue) !== toRaw(value)) {
|
|
513
535
|
emit('update:modelValue', value);
|
|
514
536
|
}
|
|
515
537
|
})();
|
|
@@ -204,10 +204,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
|
|
|
204
204
|
* 不弹差异弹窗,改用一个普通确认框替代「确定回滚」按钮,避免点击后无任何提示直接执行。
|
|
205
205
|
* 用户取消时返回 false,调用方据此中止回滚。
|
|
206
206
|
*/
|
|
207
|
-
const confirmRevert = (): Promise<boolean> =>
|
|
208
|
-
confirmHistoryAction(
|
|
209
|
-
'确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。',
|
|
210
|
-
);
|
|
207
|
+
const confirmRevert = (): Promise<boolean> => confirmHistoryAction('确定回滚该步骤吗?');
|
|
211
208
|
|
|
212
209
|
/**
|
|
213
210
|
* 「回滚」统一确认入口:可差异对比的步骤动态挂载 HistoryDiffDialog 走差异确认弹窗,
|
|
@@ -56,7 +56,14 @@ import { calcValueByFontsize, getIdFromEl } from '@tmagic/utils';
|
|
|
56
56
|
import ScrollViewer from '@editor/components/ScrollViewer.vue';
|
|
57
57
|
import { useServices } from '@editor/hooks';
|
|
58
58
|
import { useStage } from '@editor/hooks/use-stage';
|
|
59
|
-
import type {
|
|
59
|
+
import type {
|
|
60
|
+
AddMNode,
|
|
61
|
+
CustomContentMenuFunction,
|
|
62
|
+
MenuButton,
|
|
63
|
+
MenuComponent,
|
|
64
|
+
StageOptions,
|
|
65
|
+
StageSlots,
|
|
66
|
+
} from '@editor/type';
|
|
60
67
|
import { DragType, Layout } from '@editor/type';
|
|
61
68
|
import { getEditorConfig } from '@editor/utils/config';
|
|
62
69
|
import { KeyBindingContainerKey } from '@editor/utils/keybinding-config';
|
|
@@ -270,11 +277,56 @@ const resizeObserver = new globalThis.ResizeObserver((entries) => {
|
|
|
270
277
|
}
|
|
271
278
|
});
|
|
272
279
|
|
|
280
|
+
const parseDSL = getEditorConfig('parseDSL');
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 本次拖拽是否由编辑器文档内部发起
|
|
284
|
+
*
|
|
285
|
+
* drop 的 text/json 里可能带函数(组件配置的事件、钩子等),还原只能交给 parseDSL,
|
|
286
|
+
* 而 parseDSL 的默认实现是 eval;HTML 拖放又允许其他源的页面在 DataTransfer 中投递
|
|
287
|
+
* 自定义 MIME 数据,跨源页面只要诱导用户拖拽一次,就能让 eval 执行任意脚本。
|
|
288
|
+
*
|
|
289
|
+
* 跨源页面既不会在本文档触发 dragstart,也无法往本文档创建的 DataTransfer 中写数据,
|
|
290
|
+
* 因此只有起源于本文档的拖拽才交给 parseDSL 还原。
|
|
291
|
+
* 同源拖拽源写入的 text/json 由业务保证可信。
|
|
292
|
+
*/
|
|
293
|
+
let isInternalDrag = false;
|
|
294
|
+
let internalDragSession = 0;
|
|
295
|
+
let clearInternalDragTimer: ReturnType<typeof setTimeout> | undefined;
|
|
296
|
+
|
|
297
|
+
const documentDragStartHandler = () => {
|
|
298
|
+
internalDragSession += 1;
|
|
299
|
+
isInternalDrag = true;
|
|
300
|
+
if (clearInternalDragTimer !== undefined) {
|
|
301
|
+
globalThis.clearTimeout(clearInternalDragTimer);
|
|
302
|
+
clearInternalDragTimer = undefined;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const documentDragEndHandler = () => {
|
|
307
|
+
// WebKit 可能先 dragend 再 drop;推迟到下一个宏任务再清标志,
|
|
308
|
+
// 让同一次拖拽的 drop 仍能消费。session 防止误清掉下一次 dragstart。
|
|
309
|
+
const session = internalDragSession;
|
|
310
|
+
if (clearInternalDragTimer !== undefined) {
|
|
311
|
+
globalThis.clearTimeout(clearInternalDragTimer);
|
|
312
|
+
}
|
|
313
|
+
clearInternalDragTimer = globalThis.setTimeout(() => {
|
|
314
|
+
clearInternalDragTimer = undefined;
|
|
315
|
+
if (session === internalDragSession) {
|
|
316
|
+
isInternalDrag = false;
|
|
317
|
+
}
|
|
318
|
+
}, 0);
|
|
319
|
+
};
|
|
320
|
+
|
|
273
321
|
onMounted(() => {
|
|
274
322
|
if (stageWrapRef.value?.container) {
|
|
275
323
|
resizeObserver.observe(stageWrapRef.value.container);
|
|
276
324
|
keybindingService.registerEl(KeyBindingContainerKey.STAGE, stageWrapRef.value.container);
|
|
277
325
|
}
|
|
326
|
+
|
|
327
|
+
// 用捕获阶段监听,确保拖拽源自身的 dragstart 无论是否阻止冒泡都能被记录
|
|
328
|
+
globalThis.document.addEventListener('dragstart', documentDragStartHandler, true);
|
|
329
|
+
globalThis.document.addEventListener('dragend', documentDragEndHandler, true);
|
|
278
330
|
});
|
|
279
331
|
|
|
280
332
|
onBeforeUnmount(() => {
|
|
@@ -283,9 +335,15 @@ onBeforeUnmount(() => {
|
|
|
283
335
|
resizeObserver.disconnect();
|
|
284
336
|
editorService.set('stage', null);
|
|
285
337
|
keybindingService.unregisterEl('stage');
|
|
286
|
-
});
|
|
287
338
|
|
|
288
|
-
|
|
339
|
+
if (clearInternalDragTimer !== undefined) {
|
|
340
|
+
globalThis.clearTimeout(clearInternalDragTimer);
|
|
341
|
+
clearInternalDragTimer = undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
globalThis.document.removeEventListener('dragstart', documentDragStartHandler, true);
|
|
345
|
+
globalThis.document.removeEventListener('dragend', documentDragEndHandler, true);
|
|
346
|
+
});
|
|
289
347
|
|
|
290
348
|
const contextmenuHandler = (e: MouseEvent) => {
|
|
291
349
|
e.preventDefault();
|
|
@@ -299,15 +357,27 @@ const dragoverHandler = (e: DragEvent) => {
|
|
|
299
357
|
};
|
|
300
358
|
|
|
301
359
|
const dropHandler = async (e: DragEvent) => {
|
|
360
|
+
// 画布上的任意 drop 都消费本次内部拖拽标记(含空 text/json),避免残留到下一次 drop
|
|
361
|
+
const allowed = isInternalDrag;
|
|
362
|
+
isInternalDrag = false;
|
|
363
|
+
|
|
302
364
|
if (!e.dataTransfer) return;
|
|
303
365
|
|
|
304
366
|
const data = e.dataTransfer.getData('text/json');
|
|
305
367
|
|
|
306
|
-
|
|
368
|
+
// 外部源投递的拖拽数据不可信,不能交给 parseDSL(默认实现为 eval)
|
|
369
|
+
if (!data || !allowed) return;
|
|
370
|
+
|
|
371
|
+
let config: { dragType?: string; data?: AddMNode } | undefined;
|
|
372
|
+
try {
|
|
373
|
+
config = parseDSL(`(${data})`);
|
|
374
|
+
} catch {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
307
377
|
|
|
308
|
-
|
|
378
|
+
if (!config || config.dragType !== DragType.COMPONENT_LIST || !config.data) return;
|
|
309
379
|
|
|
310
|
-
|
|
380
|
+
const dragData = config.data;
|
|
311
381
|
|
|
312
382
|
e.preventDefault();
|
|
313
383
|
|
|
@@ -349,7 +419,7 @@ const dropHandler = async (e: DragEvent) => {
|
|
|
349
419
|
|
|
350
420
|
const containerRect = stageContainerEl.value.getBoundingClientRect();
|
|
351
421
|
const { scrollTop, scrollLeft } = stage.mask!;
|
|
352
|
-
const { style = {} } =
|
|
422
|
+
const { style = {} } = dragData;
|
|
353
423
|
|
|
354
424
|
let top = 0;
|
|
355
425
|
let left = 0;
|
|
@@ -371,16 +441,16 @@ const dropHandler = async (e: DragEvent) => {
|
|
|
371
441
|
}
|
|
372
442
|
}
|
|
373
443
|
|
|
374
|
-
|
|
444
|
+
dragData.style = {
|
|
375
445
|
...style,
|
|
376
446
|
position,
|
|
377
447
|
top: calcValueByFontsize(doc, top / zoom.value),
|
|
378
448
|
left: calcValueByFontsize(doc, left / zoom.value),
|
|
379
449
|
};
|
|
380
450
|
|
|
381
|
-
|
|
451
|
+
dragData.inputEvent = e;
|
|
382
452
|
|
|
383
|
-
editorService.add(
|
|
453
|
+
editorService.add(dragData, parent, { historySource: 'component-panel' });
|
|
384
454
|
}
|
|
385
455
|
};
|
|
386
456
|
</script>
|
package/types/headless.d.ts
CHANGED
|
@@ -13,6 +13,5 @@ import { HeadlessFieldOptions } from "@tmagic/form/headless";
|
|
|
13
13
|
* innerConfig 返回的 config / model / prop 与组件模板里传给内部容器的那一组保持一一对应,
|
|
14
14
|
* 且配置本身与组件共用同一份工厂(`fields/configs/`)。
|
|
15
15
|
*/
|
|
16
|
-
declare const editorFields: Record<string, HeadlessFieldOptions>;
|
|
17
|
-
//#endregion
|
|
18
|
-
export { editorFields };
|
|
16
|
+
export declare const editorFields: Record<string, HeadlessFieldOptions>;
|
|
17
|
+
//#endregion
|