@tmagic/editor 1.1.6 → 1.2.0-beta.2

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 (45) hide show
  1. package/dist/style.css +177 -0
  2. package/dist/tmagic-editor.mjs +1456 -524
  3. package/dist/tmagic-editor.mjs.map +1 -1
  4. package/dist/tmagic-editor.umd.js +1448 -510
  5. package/dist/tmagic-editor.umd.js.map +1 -1
  6. package/package.json +7 -7
  7. package/src/Editor.vue +18 -1
  8. package/src/fields/Code.vue +4 -1
  9. package/src/fields/CodeLink.vue +19 -11
  10. package/src/fields/CodeSelect.vue +139 -0
  11. package/src/index.ts +6 -2
  12. package/src/layouts/AddPageBox.vue +11 -19
  13. package/src/layouts/CodeEditor.vue +106 -120
  14. package/src/layouts/Layout.vue +1 -1
  15. package/src/layouts/PropsPanel.vue +1 -1
  16. package/src/layouts/sidebar/LayerMenu.vue +86 -91
  17. package/src/layouts/sidebar/Sidebar.vue +30 -24
  18. package/src/layouts/sidebar/TabPane.vue +57 -40
  19. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +164 -0
  20. package/src/layouts/sidebar/code-block/CodeBlockList.vue +267 -0
  21. package/src/services/codeBlock.ts +415 -0
  22. package/src/services/editor.ts +27 -1
  23. package/src/theme/code-block.scss +184 -0
  24. package/src/theme/index.scss +1 -13
  25. package/src/theme/layout.scss +5 -0
  26. package/src/theme/theme.scss +15 -0
  27. package/src/type.ts +87 -1
  28. package/src/utils/props.ts +9 -3
  29. package/types/fields/Code.vue.d.ts +8 -0
  30. package/types/fields/CodeLink.vue.d.ts +4 -0
  31. package/types/fields/CodeSelect.vue.d.ts +96 -0
  32. package/types/index.d.ts +3 -0
  33. package/types/layouts/AddPageBox.vue.d.ts +45 -3
  34. package/types/layouts/CodeEditor.vue.d.ts +160 -45
  35. package/types/layouts/sidebar/LayerMenu.vue.d.ts +16 -64
  36. package/types/layouts/sidebar/LayerPanel.vue.d.ts +8 -196
  37. package/types/layouts/sidebar/Sidebar.vue.d.ts +114 -17
  38. package/types/layouts/sidebar/TabPane.vue.d.ts +80 -12
  39. package/types/layouts/sidebar/code-block/CodeBlockEditor.vue.d.ts +50 -0
  40. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +73 -0
  41. package/types/services/codeBlock.d.ts +155 -0
  42. package/types/services/editor.d.ts +12 -1
  43. package/types/services/props.d.ts +12 -0
  44. package/types/type.d.ts +77 -1
  45. package/types/utils/props.d.ts +12 -0
@@ -2,8 +2,8 @@
2
2
  <content-menu :menu-data="menuData" ref="menu" style="overflow: initial"></content-menu>
3
3
  </template>
4
4
 
5
- <script lang="ts">
6
- import { computed, defineComponent, inject, markRaw, ref } from 'vue';
5
+ <script lang="ts" setup>
6
+ import { computed, inject, markRaw, ref } from 'vue';
7
7
  import { Delete, DocumentCopy, Files, Plus } from '@element-plus/icons-vue';
8
8
 
9
9
  import { NodeType } from '@tmagic/schema';
@@ -11,105 +11,100 @@ import { NodeType } from '@tmagic/schema';
11
11
  import ContentMenu from '../../components/ContentMenu.vue';
12
12
  import type { ComponentGroup, MenuButton, MenuComponent, Services } from '../../type';
13
13
 
14
- export default defineComponent({
15
- components: { ContentMenu },
14
+ const services = inject<Services>('services');
15
+ const menu = ref<InstanceType<typeof ContentMenu>>();
16
+ const node = computed(() => services?.editorService.get('node'));
17
+ const isRoot = computed(() => node.value?.type === NodeType.ROOT);
18
+ const isPage = computed(() => node.value?.type === NodeType.PAGE);
19
+ const componentList = computed(() => services?.componentListService.getList() || []);
16
20
 
17
- setup() {
18
- const services = inject<Services>('services');
19
- const menu = ref<InstanceType<typeof ContentMenu>>();
20
- const node = computed(() => services?.editorService.get('node'));
21
- const isRoot = computed(() => node.value?.type === NodeType.ROOT);
22
- const isPage = computed(() => node.value?.type === NodeType.PAGE);
23
- const componentList = computed(() => services?.componentListService.getList() || []);
21
+ const layerContentMenu = inject<(MenuComponent | MenuButton)[]>('layerContentMenu', []);
24
22
 
25
- const layerContentMenu = inject<(MenuComponent | MenuButton)[]>('layerContentMenu', []);
23
+ const createMenuItems = (group: ComponentGroup): MenuButton[] =>
24
+ group.items.map((component) => ({
25
+ text: component.text,
26
+ type: 'button',
27
+ icon: component.icon,
28
+ handler: () => {
29
+ services?.editorService.add({
30
+ name: component.text,
31
+ type: component.type,
32
+ ...(component.data || {}),
33
+ });
34
+ },
35
+ }));
26
36
 
27
- const createMenuItems = (group: ComponentGroup): MenuButton[] =>
28
- group.items.map((component) => ({
29
- text: component.text,
37
+ const getSubMenuData = computed<MenuButton[]>(() => {
38
+ if (node.value?.type === 'tabs') {
39
+ return [
40
+ {
41
+ text: '标签页',
30
42
  type: 'button',
31
- icon: component.icon,
43
+ icon: Files,
32
44
  handler: () => {
33
45
  services?.editorService.add({
34
- name: component.text,
35
- type: component.type,
36
- ...(component.data || {}),
46
+ type: 'tab-pane',
37
47
  });
38
48
  },
39
- }));
49
+ },
50
+ ];
51
+ }
52
+ if (node.value?.items) {
53
+ return (
54
+ componentList.value.reduce(
55
+ (subMenuData: MenuButton[], group: ComponentGroup, index) =>
56
+ subMenuData.concat(
57
+ createMenuItems(group),
58
+ index < componentList.value.length - 1
59
+ ? [
60
+ {
61
+ type: 'divider',
62
+ direction: 'horizontal',
63
+ },
64
+ ]
65
+ : [],
66
+ ),
67
+ [],
68
+ ) || []
69
+ );
70
+ }
71
+ return [];
72
+ });
40
73
 
41
- const getSubMenuData = computed<MenuButton[]>(() => {
42
- if (node.value?.type === 'tabs') {
43
- return [
44
- {
45
- text: '标签页',
46
- type: 'button',
47
- icon: Files,
48
- handler: () => {
49
- services?.editorService.add({
50
- type: 'tab-pane',
51
- });
52
- },
53
- },
54
- ];
55
- }
56
- if (node.value?.items) {
57
- return (
58
- componentList.value.reduce(
59
- (subMenuData: MenuButton[], group: ComponentGroup, index) =>
60
- subMenuData.concat(
61
- createMenuItems(group),
62
- index < componentList.value.length - 1
63
- ? [
64
- {
65
- type: 'divider',
66
- direction: 'horizontal',
67
- },
68
- ]
69
- : [],
70
- ),
71
- [],
72
- ) || []
73
- );
74
- }
75
- return [];
76
- });
74
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() => [
75
+ {
76
+ type: 'button',
77
+ text: '新增',
78
+ icon: markRaw(Plus),
79
+ display: () => node.value?.items,
80
+ items: getSubMenuData.value,
81
+ },
82
+ {
83
+ type: 'button',
84
+ text: '复制',
85
+ icon: markRaw(DocumentCopy),
86
+ display: () => !isRoot.value,
87
+ handler: () => {
88
+ node.value && services?.editorService.copy(node.value);
89
+ },
90
+ },
91
+ {
92
+ type: 'button',
93
+ text: '删除',
94
+ icon: markRaw(Delete),
95
+ display: () => !isRoot.value && !isPage.value,
96
+ handler: () => {
97
+ node.value && services?.editorService.remove(node.value);
98
+ },
99
+ },
100
+ ...layerContentMenu,
101
+ ]);
77
102
 
78
- return {
79
- menu,
80
- menuData: computed<(MenuButton | MenuComponent)[]>(() => [
81
- {
82
- type: 'button',
83
- text: '新增',
84
- icon: markRaw(Plus),
85
- display: () => node.value?.items,
86
- items: getSubMenuData.value,
87
- },
88
- {
89
- type: 'button',
90
- text: '复制',
91
- icon: markRaw(DocumentCopy),
92
- display: () => !isRoot.value,
93
- handler: () => {
94
- node.value && services?.editorService.copy(node.value);
95
- },
96
- },
97
- {
98
- type: 'button',
99
- text: '删除',
100
- icon: markRaw(Delete),
101
- display: () => !isRoot.value && !isPage.value,
102
- handler: () => {
103
- node.value && services?.editorService.remove(node.value);
104
- },
105
- },
106
- ...layerContentMenu,
107
- ]),
103
+ const show = (e: MouseEvent) => {
104
+ menu.value?.show(e);
105
+ };
108
106
 
109
- show(e: MouseEvent) {
110
- menu.value?.show(e);
111
- },
112
- };
113
- },
107
+ defineExpose({
108
+ show,
114
109
  });
115
110
  </script>
@@ -22,42 +22,48 @@
22
22
  <template #component-list-item="{ component }" v-if="item === 'component-list'">
23
23
  <slot name="component-list-item" :component="component"></slot>
24
24
  </template>
25
+
26
+ <template #code-block-panel-header v-if="item === 'code-block'">
27
+ <slot name="code-block-panel-header"></slot>
28
+ </template>
29
+
30
+ <template #code-block-panel-tool="{ id, data }" v-if="item === 'code-block'">
31
+ <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
32
+ </template>
33
+
34
+ <template #code-block-edit-panel-header="{ id }" v-if="item === 'code-block'">
35
+ <slot name="code-block-edit-panel-header" :id="id"></slot>
36
+ </template>
25
37
  </tab-pane>
26
38
  </el-tabs>
27
39
  </template>
28
40
 
29
- <script lang="ts">
30
- import { defineComponent, PropType, ref, watch } from 'vue';
41
+ <script lang="ts" setup>
42
+ import { ref, watch } from 'vue';
31
43
 
32
44
  import { SideBarData } from '../../type';
33
45
 
34
46
  import TabPane from './TabPane.vue';
35
47
 
36
- export default defineComponent({
37
- components: { TabPane },
38
-
39
- name: 'm-sidebar',
40
-
41
- props: {
42
- data: {
43
- type: Object as PropType<SideBarData>,
44
- default: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer'] }),
45
- },
48
+ const props = withDefaults(
49
+ defineProps<{
50
+ data?: SideBarData;
51
+ }>(),
52
+ {
53
+ data: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer', 'code-block'] }),
46
54
  },
55
+ );
47
56
 
48
- setup(props) {
49
- const activeTabName = ref(props.data?.status);
57
+ const activeTabName = ref(props.data?.status);
50
58
 
51
- watch(
52
- () => props.data?.status,
53
- (status) => {
54
- activeTabName.value = status || '0';
55
- },
56
- );
57
-
58
- return {
59
- activeTabName,
60
- };
59
+ watch(
60
+ () => props.data.status,
61
+ (status) => {
62
+ activeTabName.value = status || '0';
61
63
  },
64
+ );
65
+
66
+ defineExpose({
67
+ activeTabName,
62
68
  });
63
69
  </script>
@@ -30,6 +30,24 @@
30
30
  <component v-else-if="config.slots?.layerPanelHeader" :is="config.slots.layerPanelHeader" />
31
31
  </template>
32
32
 
33
+ <template #code-block-panel-header v-if="data === 'code-block' || config.slots?.codeBlockPanelHeader">
34
+ <slot v-if="data === 'code-block'" name="code-block-panel-header"></slot>
35
+ <component v-else-if="config.slots?.codeBlockPanelHeader" :is="config.slots.codeBlockPanelHeader" />
36
+ </template>
37
+
38
+ <template #code-block-panel-tool="{ id, data }" v-if="data === 'code-block' || config.slots?.codeBlockPanelTool">
39
+ <slot v-if="data === 'code-block'" name="code-block-panel-tool" :id="id" :data="data"></slot>
40
+ <component v-else-if="config.slots?.codeBlockPanelTool" :is="config.slots.codeBlockPanelTool" />
41
+ </template>
42
+
43
+ <template
44
+ #code-block-edit-panel-header="{ id }"
45
+ v-if="data === 'code-block' || config.slots?.codeBlockEditPanelHeader"
46
+ >
47
+ <slot v-if="data === 'code-block'" name="code-block-edit-panel-header" :id="id"></slot>
48
+ <component v-else-if="config.slots?.codeBlockEditPanelHeader" :is="config.slots.codeBlockEditPanelHeader" />
49
+ </template>
50
+
33
51
  <template
34
52
  #layer-node-content="{ data: nodeData, node }"
35
53
  v-if="data === 'layer' || config.slots?.layerNodeContent"
@@ -46,54 +64,53 @@
46
64
  </el-tab-pane>
47
65
  </template>
48
66
 
49
- <script lang="ts">
50
- import { computed, defineComponent, PropType } from 'vue';
51
- import { Coin, Files } from '@element-plus/icons-vue';
67
+ <script lang="ts" setup>
68
+ import { computed } from 'vue';
69
+ import { Coin, EditPen, Files } from '@element-plus/icons-vue';
52
70
 
53
71
  import MIcon from '../../components/Icon.vue';
54
72
  import { SideComponent, SideItem } from '../../type';
55
73
 
74
+ import CodeBlockList from './code-block/CodeBlockList.vue';
56
75
  import ComponentListPanel from './ComponentListPanel.vue';
57
76
  import LayerPanel from './LayerPanel.vue';
58
77
 
59
- export default defineComponent({
60
- components: { MIcon },
61
-
62
- props: {
63
- data: {
64
- type: [Object, String] as PropType<SideItem>,
65
- },
66
- },
78
+ const props = defineProps<{
79
+ data?: SideItem;
80
+ }>();
67
81
 
68
- setup(props) {
69
- return {
70
- config: computed<SideComponent | undefined>(() => {
71
- if (typeof props.data !== 'string') {
72
- return props.data;
73
- }
82
+ const config = computed<SideComponent | undefined>(() => {
83
+ if (typeof props.data !== 'string') {
84
+ return props.data;
85
+ }
74
86
 
75
- switch (props.data) {
76
- case 'component-list':
77
- return {
78
- type: 'component',
79
- icon: Coin,
80
- text: '组件',
81
- component: ComponentListPanel,
82
- slots: {},
83
- };
84
- case 'layer':
85
- return {
86
- type: 'component',
87
- icon: Files,
88
- text: '已选组件',
89
- component: LayerPanel,
90
- slots: {},
91
- };
92
- default:
93
- return undefined;
94
- }
95
- }),
96
- };
97
- },
87
+ switch (props.data) {
88
+ case 'component-list':
89
+ return {
90
+ type: 'component',
91
+ icon: Coin,
92
+ text: '组件',
93
+ component: ComponentListPanel,
94
+ slots: {},
95
+ };
96
+ case 'layer':
97
+ return {
98
+ type: 'component',
99
+ icon: Files,
100
+ text: '已选组件',
101
+ component: LayerPanel,
102
+ slots: {},
103
+ };
104
+ case 'code-block':
105
+ return {
106
+ type: 'component',
107
+ icon: EditPen,
108
+ text: '代码编辑',
109
+ component: CodeBlockList,
110
+ slots: {},
111
+ };
112
+ default:
113
+ return undefined;
114
+ }
98
115
  });
99
116
  </script>
@@ -0,0 +1,164 @@
1
+ <template>
2
+ <el-dialog
3
+ v-model="isShowCodeBlockEditor"
4
+ class="code-editor-dialog"
5
+ :title="currentTitle"
6
+ :fullscreen="true"
7
+ :before-close="saveAndClose"
8
+ :append-to-body="true"
9
+ >
10
+ <layout v-model:left="left" :min-left="45" class="code-editor-layout">
11
+ <!-- 左侧列表 -->
12
+ <template #left v-if="mode === CodeEditorMode.LIST">
13
+ <el-tree
14
+ v-if="!isEmpty(state.codeList)"
15
+ ref="tree"
16
+ node-key="id"
17
+ empty-text="暂无代码块"
18
+ :data="state.codeList"
19
+ :highlight-current="true"
20
+ @node-click="selectHandler"
21
+ :current-node-key="state.codeList[0].id"
22
+ class="side-tree"
23
+ >
24
+ <template #default="{ data }">
25
+ <div :id="data.id" class="list-container">
26
+ <div class="list-item">
27
+ <div class="code-name">{{ data.name }}({{ data.id }})</div>
28
+ </div>
29
+ </div>
30
+ </template>
31
+ </el-tree>
32
+ </template>
33
+ <!-- 右侧区域 -->
34
+ <template #center>
35
+ <div
36
+ v-if="!isEmpty(codeConfig)"
37
+ :class="[
38
+ mode === CodeEditorMode.LIST
39
+ ? 'm-editor-code-block-editor-panel-list-mode'
40
+ : 'm-editor-code-block-editor-panel',
41
+ ]"
42
+ >
43
+ <slot name="code-block-edit-panel-header" :id="id"></slot>
44
+ <el-card shadow="never">
45
+ <template #header>
46
+ <div class="code-name-wrapper">
47
+ <div class="code-name-label">代码块名称</div>
48
+ <el-input v-if="codeConfig" class="code-name-input" v-model="codeConfig.name" :disabled="!editable" />
49
+ </div>
50
+ </template>
51
+ <div class="m-editor-wrapper">
52
+ <magic-code-editor
53
+ v-if="codeConfig"
54
+ ref="codeEditor"
55
+ class="m-editor-container"
56
+ :init-values="`${codeConfig.content}`"
57
+ @save="saveCode"
58
+ :options="{
59
+ tabSize: 2,
60
+ fontSize: 16,
61
+ formatOnPaste: true,
62
+ readOnly: !editable,
63
+ }"
64
+ ></magic-code-editor>
65
+ <div class="m-editor-content-bottom" v-if="editable">
66
+ <el-button type="primary" class="button" @click="saveCode">保存</el-button>
67
+ <el-button type="primary" class="button" @click="saveAndClose">关闭</el-button>
68
+ </div>
69
+ <div class="m-editor-content-bottom" v-else>
70
+ <el-button type="primary" class="button" @click="saveAndClose">关闭</el-button>
71
+ </div>
72
+ </div>
73
+ </el-card>
74
+ </div>
75
+ </template>
76
+ </layout>
77
+ </el-dialog>
78
+ </template>
79
+
80
+ <script lang="ts" setup>
81
+ import { computed, inject, reactive, ref, watchEffect } from 'vue';
82
+ import { ElMessage } from 'element-plus';
83
+ import { forIn, isEmpty } from 'lodash-es';
84
+ import type * as monaco from 'monaco-editor';
85
+
86
+ import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
87
+ import { CodeEditorMode } from '../../../type';
88
+ import MagicCodeEditor from '../../CodeEditor.vue';
89
+ import Layout from '../../Layout.vue';
90
+
91
+ const services = inject<Services>('services');
92
+
93
+ const codeEditor = ref<InstanceType<typeof MagicCodeEditor>>();
94
+ const left = ref(200);
95
+ const currentTitle = ref('');
96
+ // 编辑器当前需展示的代码块内容
97
+ const codeConfig = ref<CodeBlockContent | null>(null);
98
+ // select选择的内容(ListState)
99
+ const state = reactive<ListState>({
100
+ codeList: [],
101
+ });
102
+
103
+ // 是否展示代码编辑区
104
+ const isShowCodeBlockEditor = computed(
105
+ () => (codeConfig.value && services?.codeBlockService.getCodeEditorShowStatus()) || false,
106
+ );
107
+ const mode = computed(() => services?.codeBlockService.getMode());
108
+ const id = computed(() => services?.codeBlockService.getId() || '');
109
+ const editable = computed(() => services?.codeBlockService.getEditStatus());
110
+ // 当前选中组件绑定的代码块id数组
111
+ const selectedIds = computed(() => services?.codeBlockService.getCombineIds() || []);
112
+
113
+ watchEffect(async () => {
114
+ codeConfig.value = (await services?.codeBlockService.getCodeContentById(id.value)) || null;
115
+ });
116
+
117
+ watchEffect(async () => {
118
+ const codeDsl = (await services?.codeBlockService.getCodeDslByIds(selectedIds.value)) || null;
119
+ if (!codeDsl) return;
120
+ state.codeList = [];
121
+ forIn(codeDsl, (value: CodeBlockContent, key: string) => {
122
+ state.codeList.push({
123
+ id: key,
124
+ name: value.name,
125
+ });
126
+ });
127
+ currentTitle.value = state.codeList[0]?.name || '';
128
+ });
129
+
130
+ // 保存代码
131
+ const saveCode = async (): Promise<boolean> => {
132
+ if (!codeEditor.value || !codeConfig.value || !editable.value) return true;
133
+
134
+ try {
135
+ // 代码内容
136
+ const codeContent = (codeEditor.value.getEditor() as monaco.editor.IStandaloneCodeEditor)?.getValue();
137
+ /* eslint no-eval: "off" */
138
+ codeConfig.value.content = eval(codeContent);
139
+ } catch (e: any) {
140
+ ElMessage.error(e.stack);
141
+ return false;
142
+ }
143
+ // 存入dsl
144
+ await services?.codeBlockService.setCodeDslById(id.value, {
145
+ name: codeConfig.value.name,
146
+ content: codeConfig.value.content,
147
+ });
148
+ ElMessage.success('代码保存成功');
149
+ return true;
150
+ };
151
+
152
+ // 保存并关闭
153
+ const saveAndClose = async () => {
154
+ const saveRes = await saveCode();
155
+ if (saveRes) {
156
+ await services?.codeBlockService.setCodeEditorShowStatus(false);
157
+ }
158
+ };
159
+
160
+ const selectHandler = (data: CodeDslList) => {
161
+ services?.codeBlockService.setId(data.id);
162
+ currentTitle.value = data.name;
163
+ };
164
+ </script>