@tmagic/editor 1.3.0-alpha.6 → 1.3.0-alpha.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.
Files changed (38) hide show
  1. package/dist/style.css +17 -0
  2. package/dist/tmagic-editor.js +471 -264
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +474 -265
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +12 -12
  7. package/src/Editor.vue +3 -0
  8. package/src/components/ContentMenu.vue +15 -8
  9. package/src/components/FunctionEditor.vue +6 -4
  10. package/src/components/SplitView.vue +2 -2
  11. package/src/fields/CodeLink.vue +4 -2
  12. package/src/fields/DataSourceInput.vue +11 -10
  13. package/src/index.ts +4 -2
  14. package/src/initService.ts +2 -0
  15. package/src/layouts/Framework.vue +4 -4
  16. package/src/layouts/PropsPanel.vue +2 -1
  17. package/src/layouts/sidebar/LayerMenu.vue +4 -3
  18. package/src/layouts/sidebar/LayerPanel.vue +55 -32
  19. package/src/layouts/workspace/Stage.vue +13 -3
  20. package/src/layouts/workspace/Workspace.vue +2 -2
  21. package/src/services/codeBlock.ts +3 -2
  22. package/src/services/keybinding.ts +148 -203
  23. package/src/services/storage.ts +3 -2
  24. package/src/theme/data-source-input.scss +18 -0
  25. package/src/theme/stage.scss +4 -0
  26. package/src/theme/theme.scss +1 -0
  27. package/src/type.ts +56 -0
  28. package/src/utils/config.ts +1 -1
  29. package/src/utils/keybinding-config.ts +120 -0
  30. package/src/utils/props.ts +34 -0
  31. package/types/components/ContentMenu.vue.d.ts +2 -0
  32. package/types/index.d.ts +2 -1
  33. package/types/initService.d.ts +1 -1
  34. package/types/layouts/workspace/Workspace.vue.d.ts +1 -1
  35. package/types/services/keybinding.d.ts +13 -5
  36. package/types/type.d.ts +53 -0
  37. package/types/utils/config.d.ts +1 -1
  38. package/types/utils/keybinding-config.d.ts +3 -0
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.0-alpha.6",
2
+ "version": "1.3.0-alpha.8",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -46,27 +46,27 @@
46
46
  "dependencies": {
47
47
  "@babel/core": "^7.18.0",
48
48
  "@element-plus/icons-vue": "^2.0.9",
49
- "@tmagic/core": "1.3.0-alpha.6",
50
- "@tmagic/design": "1.3.0-alpha.6",
51
- "@tmagic/form": "1.3.0-alpha.6",
52
- "@tmagic/schema": "1.3.0-alpha.6",
53
- "@tmagic/stage": "1.3.0-alpha.6",
54
- "@tmagic/table": "1.3.0-alpha.6",
55
- "@tmagic/utils": "1.3.0-alpha.6",
49
+ "@tmagic/core": "1.3.0-alpha.8",
50
+ "@tmagic/design": "1.3.0-alpha.8",
51
+ "@tmagic/form": "1.3.0-alpha.8",
52
+ "@tmagic/schema": "1.3.0-alpha.8",
53
+ "@tmagic/stage": "1.3.0-alpha.8",
54
+ "@tmagic/table": "1.3.0-alpha.8",
55
+ "@tmagic/utils": "1.3.0-alpha.8",
56
56
  "buffer": "^6.0.3",
57
57
  "color": "^3.1.3",
58
58
  "events": "^3.3.0",
59
59
  "gesto": "^1.7.0",
60
60
  "keycon": "^1.1.2",
61
61
  "lodash-es": "^4.17.21",
62
- "monaco-editor": "^0.34.0",
62
+ "monaco-editor": "^0.39.0",
63
63
  "serialize-javascript": "^6.0.0",
64
64
  "vue": "^3.3.4"
65
65
  },
66
66
  "peerDependencies": {
67
- "@tmagic/design": "1.3.0-alpha.6",
68
- "@tmagic/form": "1.3.0-alpha.6",
69
- "monaco-editor": "^0.34.0",
67
+ "@tmagic/design": "1.3.0-alpha.8",
68
+ "@tmagic/form": "1.3.0-alpha.8",
69
+ "monaco-editor": "^0.39.0",
70
70
  "vue": "^3.3.4"
71
71
  },
72
72
  "devDependencies": {
package/src/Editor.vue CHANGED
@@ -100,6 +100,7 @@ import keybindingService from './services/keybinding';
100
100
  import propsService from './services/props';
101
101
  import storageService from './services/storage';
102
102
  import uiService from './services/ui';
103
+ import keybindingConfig from './utils/keybinding-config';
103
104
  import editorProps from './editorProps';
104
105
  import { initServiceEvents, initServiceState } from './initService';
105
106
  import type { Services } from './type';
@@ -136,6 +137,8 @@ export default defineComponent({
136
137
 
137
138
  initServiceEvents(props, emit, services);
138
139
  initServiceState(props, services);
140
+ keybindingService.registe(keybindingConfig);
141
+ keybindingService.registeEl('global');
139
142
 
140
143
  provide('services', services);
141
144
 
@@ -12,6 +12,7 @@
12
12
  </div>
13
13
  <teleport to="body">
14
14
  <content-menu
15
+ v-if="subMenuData.length"
15
16
  class="sub-menu"
16
17
  ref="subMenu"
17
18
  :menu-data="subMenuData"
@@ -55,6 +56,8 @@ const menuStyle = ref({
55
56
  top: '0',
56
57
  });
57
58
 
59
+ const contains = (el: HTMLElement) => menu.value?.contains(el) || subMenu.value?.contains(el);
60
+
58
61
  const hide = () => {
59
62
  if (!visible.value) return;
60
63
 
@@ -69,7 +72,7 @@ const hideHandler = (e: MouseEvent) => {
69
72
  if (!visible.value || !target) {
70
73
  return;
71
74
  }
72
- if (menu.value?.contains(target) || subMenu.value?.$el?.contains(target)) {
75
+ if (contains(target)) {
73
76
  return;
74
77
  }
75
78
  hide();
@@ -104,13 +107,15 @@ const showSubMenu = (item: MenuButton | MenuComponent) => {
104
107
  return;
105
108
  }
106
109
 
107
- subMenuData.value = menuItem.items;
108
- if (menu.value) {
109
- subMenu.value.show({
110
- clientX: menu.value.offsetLeft + menu.value.clientWidth,
111
- clientY: menu.value.offsetTop,
112
- });
113
- }
110
+ subMenuData.value = menuItem.items || [];
111
+ setTimeout(() => {
112
+ if (menu.value) {
113
+ subMenu.value?.show({
114
+ clientX: menu.value.offsetLeft + menu.value.clientWidth,
115
+ clientY: menu.value.offsetTop,
116
+ });
117
+ }
118
+ }, 0);
114
119
  };
115
120
 
116
121
  onMounted(() => {
@@ -126,7 +131,9 @@ onUnmounted(() => {
126
131
  });
127
132
 
128
133
  defineExpose({
134
+ menu,
129
135
  hide,
130
136
  show,
137
+ contains,
131
138
  });
132
139
  </script>
@@ -33,7 +33,7 @@
33
33
  </TMagicCard>
34
34
  </template>
35
35
  <script lang="ts" setup>
36
- import { inject, ref, watchEffect } from 'vue';
36
+ import { inject, provide, ref, watchEffect } from 'vue';
37
37
  import { cloneDeep } from 'lodash-es';
38
38
 
39
39
  import { TMagicCard, TMagicInput, tMagicMessage } from '@tmagic/design';
@@ -41,6 +41,7 @@ import { ColumnConfig, TableConfig } from '@tmagic/form';
41
41
  import { CodeParam, Id } from '@tmagic/schema';
42
42
 
43
43
  import type { Services } from '@editor/type';
44
+ import { getConfig } from '@editor/utils/config';
44
45
 
45
46
  import CodeDraftEditor from './CodeDraftEditor.vue';
46
47
 
@@ -48,6 +49,8 @@ defineOptions({
48
49
  name: 'MEditorFunctionEditor',
49
50
  });
50
51
 
52
+ provide('mForm', {});
53
+
51
54
  const defaultParamColConfig: ColumnConfig = {
52
55
  type: 'row',
53
56
  label: '参数类型',
@@ -147,9 +150,8 @@ initTableModel();
147
150
  // 保存前钩子
148
151
  const beforeSave = (codeValue: string): boolean => {
149
152
  try {
150
- // eval检测js代码是否存在语法错误
151
- // eslint-disable-next-line no-eval
152
- eval(codeValue);
153
+ // 检测js代码是否存在语法错误
154
+ getConfig('parseDSL')(codeValue);
153
155
  return true;
154
156
  } catch (e: any) {
155
157
  tMagicMessage.error(e.stack);
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div ref="el" class="m-editor-layout">
3
- <template v-if="hasLeft">
3
+ <template v-if="hasLeft && $slots.left">
4
4
  <div class="m-editor-layout-left" :class="leftClass" :style="`width: ${left}px`">
5
5
  <slot name="left"></slot>
6
6
  </div>
@@ -11,7 +11,7 @@
11
11
  <slot name="center"></slot>
12
12
  </div>
13
13
 
14
- <template v-if="hasRight">
14
+ <template v-if="hasRight && $slots.right">
15
15
  <Resizer @change="changeRight"></Resizer>
16
16
  <div class="m-editor-layout-right" :class="rightClass" :style="`width: ${right}px`">
17
17
  <slot name="right"></slot>
@@ -6,6 +6,8 @@
6
6
  import { computed, reactive, watch } from 'vue';
7
7
  import serialize from 'serialize-javascript';
8
8
 
9
+ import { getConfig } from '@editor/utils/config';
10
+
9
11
  defineOptions({
10
12
  name: 'MEditorCodeLink',
11
13
  });
@@ -69,8 +71,8 @@ const changeHandler = (v: Record<string, any>) => {
69
71
  if (!props.name || !props.model) return;
70
72
 
71
73
  try {
72
- // eslint-disable-next-line no-eval
73
- props.model[props.name] = eval(`(${v[props.name]})`);
74
+ const parseDSL = getConfig('parseDSL');
75
+ props.model[props.name] = parseDSL(`(${v[props.name]})`);
74
76
  emit('change', props.model[props.name]);
75
77
  } catch (e) {
76
78
  console.error(e);
@@ -30,13 +30,15 @@
30
30
  </div>
31
31
  </template>
32
32
  </component>
33
- <div :class="`el-input el-input--${size}`" @mouseup="mouseupHandler" v-else>
34
- <div :class="`el-input__wrapper ${isFocused ? ' is-focus' : ''}`">
33
+ <div :class="`tmagic-data-source-input-text el-input el-input--${size}`" @mouseup="mouseupHandler" v-else>
34
+ <div :class="`tmagic-data-source-input-text-wrapper el-input__wrapper ${isFocused ? ' is-focus' : ''}`">
35
35
  <div class="el-input__inner">
36
36
  <template v-for="(item, index) in displayState">
37
37
  <span :key="index" v-if="item.type === 'text'" style="margin-right: 2px">{{ item.value }}</span>
38
38
  <TMagicTag :key="index" :size="size" v-if="item.type === 'var'">{{ item.value }}</TMagicTag>
39
39
  </template>
40
+
41
+ <Icon class="tmagic-data-source-input-icon" :icon="Coin" />
40
42
  </div>
41
43
  </div>
42
44
  </div>
@@ -90,7 +92,7 @@ const dataSources = computed(() => dataSourceService?.get('dataSources') || []);
90
92
  const setDisplayState = () => {
91
93
  displayState.value = [];
92
94
 
93
- const matches = state.value.matchAll(/\{\{([\s\S]+?)\}\}/g);
95
+ const matches = state.value.matchAll(/\$\{([\s\S]+?)\}/g);
94
96
  let index = 0;
95
97
  for (const match of matches) {
96
98
  if (typeof match.index === 'undefined') break;
@@ -195,7 +197,7 @@ const getSelectionStart = () => {
195
197
  * @param leftCurlyBracketIndex {字符索引
196
198
  */
197
199
  const curCharIsLeftCurlyBracket = (leftCurlyBracketIndex: number) =>
198
- leftCurlyBracketIndex > -1 && leftCurlyBracketIndex === getSelectionStart() - 1;
200
+ leftCurlyBracketIndex > 0 && leftCurlyBracketIndex === getSelectionStart() - 1;
199
201
 
200
202
  /**
201
203
  * 当前输入的是.
@@ -213,7 +215,7 @@ const dsQuerySearch = (queryString: string, leftCurlyBracketIndex: number, cb: (
213
215
  if (curCharIsLeftCurlyBracket(leftCurlyBracketIndex)) {
214
216
  // 当前输入的是{
215
217
  result = dataSources.value;
216
- } else if (leftCurlyBracketIndex > -1) {
218
+ } else if (leftCurlyBracketIndex > 0) {
217
219
  // 当前输入的是{xx
218
220
  const queryName = queryString.substring(leftCurlyBracketIndex + 1).toLowerCase();
219
221
  result = dataSources.value.filter((ds) => ds.title?.toLowerCase().includes(queryName) || ds.id.includes(queryName));
@@ -299,7 +301,7 @@ const querySearch = (queryString: string, cb: (data: { value: string }[]) => voi
299
301
  const curQueryString = queryString.substring(0, selectionStart);
300
302
 
301
303
  const fieldKeyStringLastIndex = curQueryString.lastIndexOf('.');
302
- const dsKeyStringLastIndex = curQueryString.lastIndexOf('{');
304
+ const dsKeyStringLastIndex = curQueryString.lastIndexOf('${') + 1;
303
305
 
304
306
  const isFieldTip = fieldKeyStringLastIndex > dsKeyStringLastIndex;
305
307
 
@@ -321,7 +323,7 @@ const selectHandler = async ({ value, type }: { value: string; type: 'dataSource
321
323
  let startText = inputText.substring(0, selectionStart);
322
324
 
323
325
  const dotIndex = startText.lastIndexOf('.');
324
- const leftCurlyBracketIndex = startText.lastIndexOf('{');
326
+ const leftCurlyBracketIndex = startText.lastIndexOf('${') + 1;
325
327
 
326
328
  const endText = inputText.substring(selectionStart);
327
329
 
@@ -336,7 +338,6 @@ const selectHandler = async ({ value, type }: { value: string; type: 'dataSource
336
338
  if (!isRightCurlyBracket(selectionStart + 1)) {
337
339
  suggestText = `${suggestText}}`;
338
340
  }
339
- suggestText = `{${suggestText}}`;
340
341
  } else if (!curCharIsDot(dotIndex)) {
341
342
  startText = startText.substring(0, dotIndex + 1);
342
343
  }
@@ -345,10 +346,10 @@ const selectHandler = async ({ value, type }: { value: string; type: 'dataSource
345
346
 
346
347
  await nextTick();
347
348
 
348
- // 由于选择数据源时会在后面补全}}, 所以光标要前移2位
349
+ // 由于选择数据源时会在后面补全}, 所以光标要前移2位
349
350
  let newSelectionStart = 0;
350
351
  if (isDataSource) {
351
- newSelectionStart = leftCurlyBracketIndex + suggestText.length - 1;
352
+ newSelectionStart = leftCurlyBracketIndex + suggestText.length;
352
353
  } else {
353
354
  newSelectionStart = dotIndex + suggestText.length + 1;
354
355
  }
package/src/index.ts CHANGED
@@ -62,13 +62,15 @@ export { default as ContentMenu } from './components/ContentMenu.vue';
62
62
  export { default as Icon } from './components/Icon.vue';
63
63
  export { default as LayoutContainer } from './components/SplitView.vue';
64
64
  export { default as SplitView } from './components/SplitView.vue';
65
+ export { default as Resizer } from './components/Resizer.vue';
65
66
 
66
67
  const defaultInstallOpt: InstallOptions = {
67
- // @todo, 自定义图片上传方法等编辑器依赖的外部选项
68
+ // eslint-disable-next-line no-eval
69
+ parseDSL: (dsl: string) => eval(dsl),
68
70
  };
69
71
 
70
72
  export default {
71
- install: (app: App, opt?: InstallOptions): void => {
73
+ install: (app: App, opt?: Partial<InstallOptions>): void => {
72
74
  const option = Object.assign(defaultInstallOpt, opt || {});
73
75
 
74
76
  // eslint-disable-next-line no-param-reassign
@@ -25,6 +25,7 @@ export const initServiceState = (
25
25
  eventsService,
26
26
  uiService,
27
27
  codeBlockService,
28
+ keybindingService,
28
29
  }: Services,
29
30
  ) => {
30
31
  // 初始值变化,重新设置节点信息
@@ -104,6 +105,7 @@ export const initServiceState = (
104
105
  uiService.resetState();
105
106
  componentListService.resetState();
106
107
  codeBlockService.resetState();
108
+ keybindingService.reset();
107
109
  });
108
110
  };
109
111
 
@@ -33,7 +33,7 @@
33
33
  </slot>
34
34
  </template>
35
35
 
36
- <template v-if="pageLength > 0 && nodes.length === 1" #right>
36
+ <template v-if="pageLength > 0" #right>
37
37
  <TMagicScrollbar>
38
38
  <slot name="props-panel"></slot>
39
39
  </TMagicScrollbar>
@@ -52,6 +52,7 @@ import { TMagicScrollbar } from '@tmagic/design';
52
52
 
53
53
  import SplitView from '@editor/components/SplitView.vue';
54
54
  import type { GetColumnWidth, Services } from '@editor/type';
55
+ import { getConfig } from '@editor/utils/config';
55
56
 
56
57
  import AddPageBox from './AddPageBox.vue';
57
58
  import CodeEditor from './CodeEditor.vue';
@@ -67,7 +68,6 @@ const codeOptions = inject('codeOptions', {});
67
68
  const { editorService, uiService } = inject<Services>('services') || {};
68
69
 
69
70
  const root = computed(() => editorService?.get('root'));
70
- const nodes = computed(() => editorService?.get('nodes') || []);
71
71
 
72
72
  const pageLength = computed(() => editorService?.get('pageLength') || 0);
73
73
  const showSrc = computed(() => uiService?.get('showSrc'));
@@ -131,8 +131,8 @@ const columnWidthChange = (columnW: GetColumnWidth) => {
131
131
 
132
132
  const saveCode = (value: string) => {
133
133
  try {
134
- // eslint-disable-next-line no-eval
135
- editorService?.set('root', eval(value));
134
+ const parseDSL = getConfig('parseDSL');
135
+ editorService?.set('root', parseDSL(value));
136
136
  } catch (e: any) {
137
137
  console.error(e);
138
138
  }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="m-editor-props-panel">
2
+ <div class="m-editor-props-panel" v-if="nodes.length === 1">
3
3
  <slot name="props-panel-header"></slot>
4
4
  <MForm
5
5
  ref="configForm"
@@ -35,6 +35,7 @@ const configForm = ref<InstanceType<typeof MForm>>();
35
35
  const curFormConfig = ref<any>([]);
36
36
  const services = inject<Services>('services');
37
37
  const node = computed(() => services?.editorService.get('node'));
38
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
38
39
  const propsPanelSize = computed(() => services?.uiService.get('propsPanelSize') || 'small');
39
40
  const stage = computed(() => services?.editorService.get('stage'));
40
41
 
@@ -22,6 +22,7 @@ const props = defineProps<{
22
22
  const services = inject<Services>('services');
23
23
  const menu = ref<InstanceType<typeof ContentMenu>>();
24
24
  const node = computed(() => services?.editorService.get('node'));
25
+ const nodes = computed(() => services?.editorService.get('nodes'));
25
26
  const isRoot = computed(() => node.value?.type === NodeType.ROOT);
26
27
  const isPage = computed(() => node.value?.type === NodeType.PAGE);
27
28
  const componentList = computed(() => services?.componentListService.getList() || []);
@@ -82,7 +83,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
82
83
  type: 'button',
83
84
  text: '新增',
84
85
  icon: markRaw(Plus),
85
- display: () => node.value?.items,
86
+ display: () => node.value?.items && nodes.value?.length === 1,
86
87
  items: getSubMenuData.value,
87
88
  },
88
89
  {
@@ -91,7 +92,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
91
92
  icon: markRaw(CopyDocument),
92
93
  display: () => !isRoot.value,
93
94
  handler: () => {
94
- node.value && services?.editorService.copy(node.value);
95
+ node.value && services?.editorService.copy(nodes.value || []);
95
96
  },
96
97
  },
97
98
  {
@@ -100,7 +101,7 @@ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
100
101
  icon: markRaw(Delete),
101
102
  display: () => !isRoot.value && !isPage.value,
102
103
  handler: () => {
103
- node.value && services?.editorService.remove(node.value);
104
+ node.value && services?.editorService.remove(nodes.value || []);
104
105
  },
105
106
  },
106
107
  ...props.layerContentMenu,
@@ -50,8 +50,7 @@
50
50
  </template>
51
51
 
52
52
  <script lang="ts" setup>
53
- import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue';
54
- import KeyController from 'keycon';
53
+ import { computed, inject, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
55
54
  import { difference, throttle, union } from 'lodash-es';
56
55
 
57
56
  import { TMagicScrollbar, TMagicTree } from '@tmagic/design';
@@ -248,51 +247,71 @@ const windowBlurHandler = () => {
248
247
  isCtrlKeyDown.value = false;
249
248
  };
250
249
 
251
- const globalKeyupHandler = () => {
252
- if (document.activeElement !== tree.value?.$el) {
250
+ keybindingService?.registeCommand('layer-panel-not-ctrl-keydown', (e) => {
251
+ if (e.key !== keybindingService.ctrlKey) {
253
252
  isCtrlKeyDown.value = false;
254
253
  }
255
- };
254
+ });
256
255
 
257
- let keycon: KeyController;
256
+ keybindingService?.registeCommand('layer-panel-ctrl-keydown', () => {
257
+ isCtrlKeyDown.value = true;
258
+ });
258
259
 
259
- onMounted(() => {
260
- editorService?.on('remove', editorServiceRemoveHandler);
260
+ keybindingService?.registeCommand('layer-panel-ctrl-keyup', () => {
261
+ isCtrlKeyDown.value = false;
262
+ });
261
263
 
264
+ keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
265
+ if (!tree.value?.$el.contains(document.activeElement)) {
266
+ isCtrlKeyDown.value = false;
267
+ }
268
+ });
269
+
270
+ keybindingService?.registe([
271
+ {
272
+ command: 'layer-panel-not-ctrl-keydown',
273
+ when: [['layer-panel', 'keydown']],
274
+ },
275
+ {
276
+ command: 'layer-panel-ctrl-keydown',
277
+ keybinding: 'ctrl',
278
+ when: [['layer-panel', 'keydown']],
279
+ },
280
+ {
281
+ command: 'layer-panel-ctrl-keyup',
282
+ keybinding: 'ctrl',
283
+ when: [['layer-panel', 'keyup']],
284
+ },
285
+ {
286
+ command: 'layer-panel-global-keydwon',
287
+ keybinding: 'ctrl',
288
+ when: [['global', 'keydown']],
289
+ },
290
+ ]);
291
+
292
+ watch(tree, () => {
262
293
  if (tree.value?.$el) {
263
- // 如果是在树上按下ctrl,那么在树外松开ctrl时,也要触发松开事件
264
- keybindingService?.on('keyup', globalKeyupHandler);
265
-
266
- keycon = new KeyController(tree.value.$el);
267
- const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
268
- const ctrl = isMac ? 'meta' : 'ctrl';
269
-
270
- keycon
271
- .keydown((e) => {
272
- if (e.key !== ctrl) {
273
- isCtrlKeyDown.value = false;
274
- }
275
- })
276
- .keydown(ctrl, () => {
277
- isCtrlKeyDown.value = true;
278
- })
279
- .keyup(ctrl, () => {
280
- isCtrlKeyDown.value = false;
281
- });
294
+ keybindingService?.registeEl('layer-panel', tree.value.$el);
282
295
 
283
296
  tree.value.$el.addEventListener('blur', windowBlurHandler);
297
+ } else {
298
+ keybindingService?.unregisteEl('layer-panel');
284
299
  }
300
+ });
301
+
302
+ onMounted(() => {
303
+ editorService?.on('remove', editorServiceRemoveHandler);
285
304
 
286
305
  globalThis.addEventListener('blur', windowBlurHandler);
287
306
  });
288
307
 
289
- onUnmounted(() => {
290
- keycon.destroy();
308
+ onBeforeUnmount(() => {
309
+ tree.value?.$el.removeEventListener('blur', windowBlurHandler);
310
+ });
291
311
 
312
+ onUnmounted(() => {
292
313
  editorService?.off('remove', editorServiceRemoveHandler);
293
- keybindingService?.off('keyup', globalKeyupHandler);
294
314
  globalThis.removeEventListener('blur', windowBlurHandler);
295
- tree.value?.$el.removeEventListener('blur', windowBlurHandler);
296
315
  });
297
316
 
298
317
  // 鼠标在组件树移动触发高亮
@@ -331,7 +350,11 @@ const clickHandler = (data: MNode): void => {
331
350
  // 右键菜单
332
351
  const contextmenu = async (event: MouseEvent, data: MNode): Promise<void> => {
333
352
  event.preventDefault();
334
- await select(data);
353
+
354
+ if (nodes.value.length < 2) {
355
+ await select(data);
356
+ }
357
+
335
358
  menu.value?.show(event);
336
359
  };
337
360
  </script>
@@ -2,6 +2,7 @@
2
2
  <ScrollViewer
3
3
  class="m-editor-stage"
4
4
  ref="stageWrap"
5
+ tabindex="-1"
5
6
  :width="stageRect?.width"
6
7
  :height="stageRect?.height"
7
8
  :wrap-width="stageContainerRect?.width"
@@ -35,6 +36,7 @@ import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stag
35
36
 
36
37
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
37
38
  import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
39
+ import { getConfig } from '@editor/utils/config';
38
40
  import { useStage } from '@editor/utils/stage';
39
41
 
40
42
  import ViewerMenu from './ViewerMenu.vue';
@@ -74,6 +76,10 @@ watchEffect(() => {
74
76
 
75
77
  stage = useStage(stageOptions);
76
78
 
79
+ stage.on('select', () => {
80
+ stageWrap.value?.container?.focus();
81
+ });
82
+
77
83
  services?.editorService.set('stage', markRaw(stage));
78
84
 
79
85
  stage?.mount(stageContainer.value);
@@ -120,13 +126,17 @@ const resizeObserver = new ResizeObserver((entries) => {
120
126
  });
121
127
 
122
128
  onMounted(() => {
123
- stageWrap.value?.container && resizeObserver.observe(stageWrap.value.container);
129
+ if (stageWrap.value?.container) {
130
+ resizeObserver.observe(stageWrap.value.container);
131
+ services?.keybindingService.registeEl('stage', stageWrap.value.container);
132
+ }
124
133
  });
125
134
 
126
135
  onUnmounted(() => {
127
136
  stage?.destroy();
128
137
  resizeObserver.disconnect();
129
138
  services?.editorService.set('stage', null);
139
+ services?.keybindingService.unregisteEl('stage');
130
140
  });
131
141
 
132
142
  const contextmenuHandler = (e: MouseEvent) => {
@@ -153,8 +163,8 @@ const dropHandler = async (e: DragEvent) => {
153
163
  }
154
164
 
155
165
  if (e.dataTransfer && parent && stageContainer.value && stage) {
156
- // eslint-disable-next-line no-eval
157
- const config = eval(`(${e.dataTransfer.getData('data')})`);
166
+ const parseDSL = getConfig('parseDSL');
167
+ const config = parseDSL(`(${e.dataTransfer.getData('data')})`);
158
168
  const layout = await services?.editorService.getLayout(parent);
159
169
 
160
170
  const containerRect = stageContainer.value.getBoundingClientRect();
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="m-editor-workspace" tabindex="-1">
2
+ <div class="m-editor-workspace">
3
3
  <Breadcrumb></Breadcrumb>
4
4
 
5
5
  <slot name="stage">
@@ -18,7 +18,7 @@
18
18
  <script lang="ts" setup>
19
19
  import { computed, inject } from 'vue';
20
20
 
21
- import type { MenuButton, MenuComponent, Services } from '@editor/type';
21
+ import { MenuButton, MenuComponent, Services } from '@editor/type';
22
22
 
23
23
  import Breadcrumb from './Breadcrumb.vue';
24
24
  import PageBar from './PageBar.vue';
@@ -23,6 +23,7 @@ import { CodeBlockContent, CodeBlockDSL, Id } from '@tmagic/schema';
23
23
 
24
24
  import type { CodeState } from '@editor/type';
25
25
  import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
26
+ import { getConfig } from '@editor/utils/config';
26
27
 
27
28
  import BaseService from './BaseService';
28
29
 
@@ -95,8 +96,8 @@ class CodeBlock extends BaseService {
95
96
  const codeConfigProcessed = codeConfig;
96
97
  if (codeConfig.content) {
97
98
  // 在保存的时候转换代码内容
98
- // eslint-disable-next-line no-eval
99
- codeConfigProcessed.content = eval(codeConfig.content);
99
+ const parseDSL = getConfig('parseDSL');
100
+ codeConfigProcessed.content = parseDSL(codeConfig.content);
100
101
  }
101
102
 
102
103
  const existContent = codeDsl[id] || {};