@tmagic/editor 1.5.22 → 1.5.24

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.22",
2
+ "version": "1.5.24",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,14 +58,14 @@
58
58
  "moveable": "^0.53.0",
59
59
  "serialize-javascript": "^6.0.2",
60
60
  "sortablejs": "^1.15.6",
61
- "@tmagic/design": "1.5.22",
62
- "@tmagic/form": "1.5.22",
63
- "@tmagic/table": "1.5.22",
64
- "@tmagic/utils": "1.5.22",
65
- "@tmagic/stage": "1.5.22"
61
+ "@tmagic/form": "1.5.24",
62
+ "@tmagic/stage": "1.5.24",
63
+ "@tmagic/utils": "1.5.24",
64
+ "@tmagic/design": "1.5.24",
65
+ "@tmagic/table": "1.5.24"
66
66
  },
67
67
  "devDependencies": {
68
- "@types/events": "^3.0.0",
68
+ "@types/events": "^3.0.3",
69
69
  "@types/lodash-es": "^4.17.4",
70
70
  "@types/serialize-javascript": "^5.0.4",
71
71
  "@types/sortablejs": "^1.15.8",
@@ -74,9 +74,9 @@
74
74
  },
75
75
  "peerDependencies": {
76
76
  "monaco-editor": "^0.48.0",
77
- "typescript": "*",
78
- "vue": ">=3.5.0",
79
- "@tmagic/core": "1.5.22"
77
+ "typescript": "^5.8.3",
78
+ "vue": "^3.5.17",
79
+ "@tmagic/core": "1.5.24"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
package/src/Editor.vue CHANGED
@@ -97,6 +97,7 @@
97
97
  :extend-state="extendFormState"
98
98
  :disabled-show-src="disabledShowSrc"
99
99
  @mounted="propsPanelMountedHandler"
100
+ @unmounted="propsPanelUnmountedHandler"
100
101
  @form-error="propsPanelFormErrorHandler"
101
102
  @submit-error="propsPanelSubmitErrorHandler"
102
103
  >
@@ -163,6 +164,7 @@ defineOptions({
163
164
 
164
165
  const emit = defineEmits<{
165
166
  'props-panel-mounted': [instance: InstanceType<typeof FormPanel>];
167
+ 'props-panel-unmounted': [];
166
168
  'update:modelValue': [value: MApp | null];
167
169
  'props-form-error': [e: any];
168
170
  'props-submit-error': [e: any];
@@ -235,6 +237,9 @@ provide<EventBus>('eventBus', new EventEmitter());
235
237
  const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
236
238
  emit('props-panel-mounted', e);
237
239
  };
240
+ const propsPanelUnmountedHandler = () => {
241
+ emit('props-panel-unmounted');
242
+ };
238
243
 
239
244
  const propsPanelSubmitErrorHandler = (e: any) => {
240
245
  emit('props-submit-error', e);
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="m-editor-data-source-fields">
3
- <MagicTable :data="model[name]" :columns="fieldColumns"></MagicTable>
3
+ <MagicTable :data="model[name]" :columns="fieldColumns" :border="true"></MagicTable>
4
4
 
5
5
  <div class="m-editor-data-source-fields-footer">
6
6
  <TMagicButton size="small" :disabled="disabled" plain @click="newFromJsonHandler()">快速添加</TMagicButton>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="m-editor-data-source-methods">
3
- <MagicTable :data="model[name]" :columns="methodColumns"></MagicTable>
3
+ <MagicTable :data="model[name]" :columns="methodColumns" :border="true"></MagicTable>
4
4
 
5
5
  <div class="m-editor-data-source-methods-footer">
6
6
  <TMagicButton size="small" type="primary" :disabled="disabled" plain @click="createCodeHandler"
@@ -32,10 +32,12 @@ export const useFilter = (
32
32
  const visible = filterIsMatch(text, node);
33
33
  if (visible && parents.length) {
34
34
  parents.forEach((parent) => {
35
- updateStatus(nodeStatusMap.value!, parent.id, {
36
- visible,
37
- expand: true,
38
- });
35
+ if (text || text.length) {
36
+ updateStatus(nodeStatusMap.value!, parent.id, {
37
+ visible,
38
+ expand: true,
39
+ });
40
+ }
39
41
  });
40
42
  }
41
43
 
@@ -45,6 +45,7 @@ export const useStage = (stageOptions: StageOptions) => {
45
45
  updateDragEl: stageOptions.updateDragEl,
46
46
  guidesOptions: stageOptions.guidesOptions,
47
47
  disabledMultiSelect: stageOptions.disabledMultiSelect,
48
+ disabledRule: stageOptions.disabledRule,
48
49
  });
49
50
 
50
51
  watch(
@@ -1,4 +1,4 @@
1
- import { onBeforeUnmount, reactive, toRaw, watch } from 'vue';
1
+ import { nextTick, onBeforeUnmount, reactive, toRaw, watch } from 'vue';
2
2
  import { cloneDeep } from 'lodash-es';
3
3
 
4
4
  import type TMagicCore from '@tmagic/core';
@@ -21,9 +21,11 @@ import {
21
21
  NODE_CONDS_KEY,
22
22
  NodeType,
23
23
  Target,
24
+ updateNode,
24
25
  } from '@tmagic/core';
25
26
  import { ChangeRecord } from '@tmagic/form';
26
- import { getNodes, isPage, isValueIncludeDataSource } from '@tmagic/utils';
27
+ import StageCore from '@tmagic/stage';
28
+ import { getDepNodeIds, getNodes, isPage, isValueIncludeDataSource } from '@tmagic/utils';
27
29
 
28
30
  import PropsPanel from './layouts/PropsPanel.vue';
29
31
  import { isIncludeDataSource } from './utils/editor';
@@ -233,14 +235,15 @@ export const initServiceEvents = (
233
235
  ) => {
234
236
  let getTMagicAppPrimise: Promise<TMagicCore | undefined> | null = null;
235
237
 
236
- const getTMagicApp = (): Promise<TMagicCore | undefined> => {
237
- const renderer = editorService.get('stage')?.renderer;
238
+ const getTMagicApp = async (): Promise<TMagicCore | undefined> => {
239
+ const stage = await getStage();
240
+ const { renderer } = stage;
238
241
  if (!renderer) {
239
- return Promise.resolve(void 0);
242
+ return void 0;
240
243
  }
241
244
 
242
245
  if (renderer.runtime) {
243
- return Promise.resolve(renderer.runtime.getApp?.());
246
+ return renderer.runtime.getApp?.();
244
247
  }
245
248
 
246
249
  if (getTMagicAppPrimise) {
@@ -344,6 +347,61 @@ export const initServiceEvents = (
344
347
  },
345
348
  );
346
349
 
350
+ const getStage = (): Promise<StageCore> => {
351
+ const stage = editorService.get('stage');
352
+ if (stage) {
353
+ return Promise.resolve(stage);
354
+ }
355
+
356
+ return new Promise<StageCore>((resolve) => {
357
+ const unWatch = watch(
358
+ () => editorService.get('stage'),
359
+ (stage) => {
360
+ if (stage) {
361
+ resolve(stage);
362
+ nextTick(() => {
363
+ unWatch();
364
+ });
365
+ }
366
+ },
367
+ );
368
+ });
369
+ };
370
+
371
+ const updateStageDsl = async (value: MApp | null) => {
372
+ const stage = await getStage();
373
+
374
+ const runtime = await stage.renderer?.getRuntime();
375
+ const app = await getTMagicApp();
376
+
377
+ if (!app?.dataSourceManager) {
378
+ runtime?.updateRootConfig?.(cloneDeep(toRaw(value))!);
379
+ }
380
+
381
+ const page = editorService.get('page');
382
+ const node = editorService.get('node');
383
+ page?.id && runtime?.updatePageId?.(page.id);
384
+ setTimeout(() => {
385
+ node && stage?.select(toRaw(node.id));
386
+ });
387
+
388
+ if (value) {
389
+ depService.clearIdleTasks();
390
+
391
+ const dsl = cloneDeep(toRaw(value));
392
+
393
+ await (typeof Worker === 'undefined' ? collectIdle(dsl.items, true) : depService.collectByWorker(dsl));
394
+
395
+ if (dsl.dataSources && dsl.dataSourceDeps && app?.dataSourceManager) {
396
+ for (const node of getNodes(getDepNodeIds(dsl.dataSourceDeps), dsl.items)) {
397
+ updateNode(app.dataSourceManager.compiledNode(node), dsl);
398
+ }
399
+ }
400
+
401
+ runtime?.updateRootConfig?.(dsl);
402
+ }
403
+ };
404
+
347
405
  const initDataSourceDepTarget = (ds: DataSourceSchema) => {
348
406
  depService.addTarget(createDataSourceTarget(ds, reactive({})));
349
407
  depService.addTarget(createDataSourceMethodTarget(ds, reactive({})));
@@ -370,11 +428,7 @@ export const initServiceEvents = (
370
428
  }
371
429
 
372
430
  if (Array.isArray(value.items)) {
373
- depService.clearIdleTasks();
374
-
375
- (typeof Worker === 'undefined' ? collectIdle(value.items, true) : depService.collectByWorker(value)).then(() => {
376
- updateStageNodes(value.items);
377
- });
431
+ updateStageDsl(value);
378
432
  } else {
379
433
  depService.clear();
380
434
  delete value.dataSourceDeps;
@@ -78,6 +78,7 @@ const resizeObserver = new ResizeObserver(() => {
78
78
 
79
79
  onMounted(() => {
80
80
  pageBarEl.value && resizeObserver.observe(pageBarEl.value);
81
+ itemsContainerEl.value && resizeObserver.observe(itemsContainerEl.value);
81
82
  });
82
83
 
83
84
  onBeforeUnmount(() => {
@@ -136,7 +137,7 @@ watch(
136
137
  let beforeDragList: Id[] = [];
137
138
  const options = {
138
139
  ...{
139
- dataIdAttr: 'page-id', // 获取排序后的数据
140
+ dataIdAttr: 'data-page-id', // 获取排序后的数据
140
141
  onStart: async (event: SortableEvent) => {
141
142
  if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
142
143
  await props.pageBarSortOptions.beforeStart(event, sortable);
@@ -42,7 +42,7 @@
42
42
  </template>
43
43
 
44
44
  <script setup lang="ts">
45
- import { computed, getCurrentInstance, inject, onMounted, ref, useTemplateRef, watchEffect } from 'vue';
45
+ import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, useTemplateRef, watchEffect } from 'vue';
46
46
  import { Document as DocumentIcon } from '@element-plus/icons-vue';
47
47
 
48
48
  import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
@@ -78,6 +78,7 @@ const emit = defineEmits<{
78
78
  'submit-error': [e: any];
79
79
  'form-error': [e: any];
80
80
  mounted: [internalInstance: any];
81
+ unmounted: [];
81
82
  }>();
82
83
 
83
84
  const services = useServices();
@@ -104,6 +105,10 @@ onMounted(() => {
104
105
  emit('mounted', internalInstance?.proxy);
105
106
  });
106
107
 
108
+ onUnmounted(() => {
109
+ emit('unmounted');
110
+ });
111
+
107
112
  const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
108
113
  try {
109
114
  const values = await configFormRef.value?.submitForm();
@@ -13,6 +13,7 @@
13
13
  @submit-error="errorHandler"
14
14
  @form-error="errorHandler"
15
15
  @mounted="mountedHandler"
16
+ @unmounted="unmountedHandler"
16
17
  ></FormPanel>
17
18
 
18
19
  <Resizer v-if="showStylePanel" @change="widthChange"></Resizer>
@@ -89,6 +90,7 @@ const emit = defineEmits<{
89
90
  'submit-error': [e: any];
90
91
  'form-error': [e: any];
91
92
  mounted: [internalInstance: InstanceType<typeof FormPanel>];
93
+ unmounted: [];
92
94
  }>();
93
95
 
94
96
  const { editorService, uiService, propsService, storageService } = useServices();
@@ -165,6 +167,10 @@ const mountedHandler = () => {
165
167
  }
166
168
  };
167
169
 
170
+ const unmountedHandler = () => {
171
+ emit('unmounted');
172
+ };
173
+
168
174
  const propsPanelEl = useTemplateRef('propsPanel');
169
175
  const propsPanelWidth = ref(
170
176
  storageService.getItem(PROPS_PANEL_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || 300,
@@ -43,20 +43,9 @@
43
43
  </template>
44
44
 
45
45
  <script lang="ts" setup>
46
- import {
47
- computed,
48
- markRaw,
49
- nextTick,
50
- onBeforeUnmount,
51
- onMounted,
52
- toRaw,
53
- useTemplateRef,
54
- watch,
55
- watchEffect,
56
- } from 'vue';
57
- import { cloneDeep } from 'lodash-es';
58
-
59
- import type { MApp, MContainer } from '@tmagic/core';
46
+ import { computed, markRaw, nextTick, onBeforeUnmount, onMounted, useTemplateRef, watch, watchEffect } from 'vue';
47
+
48
+ import type { MContainer } from '@tmagic/core';
60
49
  import StageCore, { getOffset, Runtime } from '@tmagic/stage';
61
50
  import { calcValueByFontsize, getIdFromEl } from '@tmagic/utils';
62
51
 
@@ -130,12 +119,6 @@ watchEffect(() => {
130
119
 
131
120
  stage.on('runtime-ready', (rt) => {
132
121
  runtime = rt;
133
- // toRaw返回的值是一个引用而非快照,需要cloneDeep
134
- root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));
135
- page.value?.id && runtime?.updatePageId?.(page.value.id);
136
- setTimeout(() => {
137
- node.value && stage?.select(toRaw(node.value.id));
138
- });
139
122
  });
140
123
  });
141
124
 
@@ -184,14 +167,6 @@ watch(page, (page) => {
184
167
  }
185
168
  });
186
169
 
187
- const rootChangeHandler = (root: MApp) => {
188
- if (runtime && root) {
189
- runtime.updateRootConfig?.(cloneDeep(toRaw(root)));
190
- }
191
- };
192
-
193
- editorService.on('root-change', rootChangeHandler);
194
-
195
170
  const resizeObserver = new ResizeObserver((entries) => {
196
171
  for (const { contentRect } of entries) {
197
172
  uiService.set('stageContainerRect', {
@@ -210,10 +185,10 @@ onMounted(() => {
210
185
 
211
186
  onBeforeUnmount(() => {
212
187
  stage?.destroy();
188
+ stage = null;
213
189
  resizeObserver.disconnect();
214
190
  editorService.set('stage', null);
215
191
  keybindingService.unregisterEl('stage');
216
- editorService.off('root-change', rootChangeHandler);
217
192
  });
218
193
 
219
194
  const parseDSL = getEditorConfig('parseDSL');
@@ -1,9 +1,19 @@
1
1
  <template>
2
- <div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlayHandler">
3
- <TMagicIcon class="m-editor-stage-overlay-close" :size="'20'" @click="closeOverlayHandler"
2
+ <div v-if="stageOverlayVisible" class="m-editor-stage-overlay">
3
+ <TMagicIcon class="m-editor-stage-overlay-close" :size="'30'" @click="closeOverlayHandler"
4
4
  ><CloseBold
5
5
  /></TMagicIcon>
6
- <div ref="stageOverlay" class="m-editor-stage-overlay-container" :style="style" @click.stop></div>
6
+
7
+ <ScrollViewer
8
+ class="m-editor-stage"
9
+ :width="wrapWidth"
10
+ :height="wrapHeight"
11
+ :wrap-width="columnWidth.center"
12
+ :wrap-height="frameworkRect.height"
13
+ :zoom="zoom"
14
+ >
15
+ <div ref="stageOverlay" class="m-editor-stage-container" :style="style"></div>
16
+ </ScrollViewer>
7
17
  </div>
8
18
  </template>
9
19
 
@@ -13,10 +23,11 @@ import { CloseBold } from '@element-plus/icons-vue';
13
23
 
14
24
  import { TMagicIcon } from '@tmagic/design';
15
25
 
26
+ import ScrollViewer from '@editor/components/ScrollViewer.vue';
16
27
  import { useServices } from '@editor/hooks/use-services';
17
28
  import type { StageOptions } from '@editor/type';
18
29
 
19
- const { stageOverlayService, editorService } = useServices();
30
+ const { stageOverlayService, editorService, uiService } = useServices();
20
31
 
21
32
  const stageOptions = inject<StageOptions>('stageOptions');
22
33
 
@@ -26,10 +37,12 @@ const stageOverlayVisible = computed(() => stageOverlayService.get('stageOverlay
26
37
  const wrapWidth = computed(() => stageOverlayService.get('wrapWidth'));
27
38
  const wrapHeight = computed(() => stageOverlayService.get('wrapHeight'));
28
39
  const stage = computed(() => editorService.get('stage'));
40
+ const zoom = computed(() => uiService.get('zoom'));
41
+ const columnWidth = computed(() => uiService.get('columnWidth'));
42
+ const frameworkRect = computed(() => uiService.get('frameworkRect'));
29
43
 
30
44
  const style = computed(() => ({
31
- width: `${wrapWidth.value}px`,
32
- height: `${wrapHeight.value}px`,
45
+ transform: `scale(${zoom.value})`,
33
46
  }));
34
47
 
35
48
  watch(stage, (stage) => {
@@ -43,6 +56,12 @@ watch(stage, (stage) => {
43
56
  }
44
57
  });
45
58
 
59
+ watch(zoom, (zoom) => {
60
+ const stage = stageOverlayService.get('stage');
61
+ if (!stage || !zoom) return;
62
+ stage.setZoom(zoom);
63
+ });
64
+
46
65
  watch(stageOverlayEl, (stageOverlay) => {
47
66
  const subStage = stageOverlayService.createStage(stageOptions);
48
67
  stageOverlayService.set('stage', subStage);
@@ -199,24 +199,33 @@ export default class extends EventEmitter {
199
199
  * @deprecated 请使用usePlugin代替
200
200
  */
201
201
  public use(options: Record<string, Function>) {
202
- Object.entries(options).forEach(([methodName, method]: [string, Function]) => {
202
+ for (const [methodName, method] of Object.entries(options)) {
203
203
  if (typeof method === 'function') this.middleware[methodName].push(method);
204
- });
204
+ }
205
205
  }
206
206
 
207
207
  public usePlugin(options: Record<string, Function>) {
208
- Object.entries(options).forEach(([methodName, method]: [string, Function]) => {
208
+ for (const [methodName, method] of Object.entries(options)) {
209
209
  if (typeof method === 'function') this.pluginOptionsList[methodName].push(method);
210
- });
210
+ }
211
+ }
212
+
213
+ public removePlugin(options: Record<string, Function>) {
214
+ for (const [methodName, method] of Object.entries(options)) {
215
+ if (Array.isArray(this.pluginOptionsList[methodName])) {
216
+ this.pluginOptionsList[methodName] = this.pluginOptionsList[methodName].filter((item) => item !== method);
217
+ }
218
+ }
211
219
  }
212
220
 
213
221
  public removeAllPlugins() {
214
- Object.keys(this.pluginOptionsList).forEach((key) => {
222
+ for (const key of Object.keys(this.pluginOptionsList)) {
215
223
  this.pluginOptionsList[key] = [];
216
- });
217
- Object.keys(this.middleware).forEach((key) => {
224
+ }
225
+
226
+ for (const key of Object.keys(this.middleware)) {
218
227
  this.middleware[key] = [];
219
- });
228
+ }
220
229
  }
221
230
 
222
231
  private async doTask() {
@@ -145,6 +145,18 @@ class Keybinding extends BaseService {
145
145
  for (const [type = '', eventType = 'keydown'] of when) {
146
146
  const cacheItem: KeyBindingCacheItem = { type, command, keybinding, eventType, bound: false };
147
147
 
148
+ if (
149
+ this.bindingList.find(
150
+ (item) =>
151
+ item.command === command &&
152
+ item.eventType === eventType &&
153
+ item.type === type &&
154
+ item.keybinding === keybinding,
155
+ )
156
+ ) {
157
+ continue;
158
+ }
159
+
148
160
  this.bindingList.push(cacheItem);
149
161
  }
150
162
  }
@@ -97,9 +97,9 @@ class StageOverlay extends BaseService {
97
97
  public createStage(stageOptions: StageOptions = {}) {
98
98
  return useStage({
99
99
  ...stageOptions,
100
- zoom: 1,
101
100
  runtimeUrl: '',
102
101
  autoScrollIntoView: false,
102
+ disabledRule: true,
103
103
  render: async (stage: StageCore) => {
104
104
  this.copyDocumentElement();
105
105
 
@@ -35,22 +35,15 @@
35
35
  width: 100%;
36
36
  height: 100%;
37
37
  background-color: #fff;
38
- display: flex;
39
38
  z-index: 20;
40
- overflow: auto;
41
- }
42
-
43
- .m-editor-stage-overlay-container {
44
- position: relative;
45
- flex-shrink: 0;
46
- margin: auto;
47
- box-shadow: rgba(0, 0, 0, 0.04) 0px 3px 5px;
48
39
  }
49
40
 
50
41
  .m-editor-stage-overlay-close.tmagic-design-icon {
51
42
  position: fixed;
52
43
  right: 20px;
53
44
  top: 10px;
45
+ cursor: pointer;
46
+ z-index: 1;
54
47
  }
55
48
 
56
49
  .m-editor-stage-float-button {
@@ -66,8 +59,10 @@
66
59
  background-color: #ffffff;
67
60
  transition: background-color 0.2s;
68
61
  color: rgba(0, 0, 0, 0.88);
69
- box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
70
- 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
62
+ box-shadow:
63
+ 0 6px 16px 0 rgba(0, 0, 0, 0.08),
64
+ 0 3px 6px -4px rgba(0, 0, 0, 0.12),
65
+ 0 9px 28px 8px rgba(0, 0, 0, 0.05);
71
66
  }
72
67
 
73
68
  .m-editor-node-list-menu {
package/src/type.ts CHANGED
@@ -164,6 +164,7 @@ export interface StageOptions {
164
164
  renderType?: RenderType;
165
165
  guidesOptions?: Partial<GuidesOptions>;
166
166
  disabledMultiSelect?: boolean;
167
+ disabledRule?: boolean;
167
168
  zoom?: number;
168
169
  }
169
170
 
@@ -17,7 +17,7 @@
17
17
  * limitations under the License.
18
18
  */
19
19
 
20
- import { NODE_CONDS_KEY } from '@tmagic/core';
20
+ import { NODE_CONDS_KEY, NODE_DISABLE_CODE_BLOCK_KEY, NODE_DISABLE_DATA_SOURCE_KEY } from '@tmagic/core';
21
21
  import { tMagicMessage } from '@tmagic/design';
22
22
  import type { FormConfig, FormState, TabConfig, TabPaneConfig } from '@tmagic/form';
23
23
 
@@ -123,6 +123,20 @@ export const advancedTabConfig: TabPaneConfig = {
123
123
  title: '高级',
124
124
  lazy: true,
125
125
  items: [
126
+ {
127
+ name: NODE_DISABLE_CODE_BLOCK_KEY,
128
+ text: '禁用代码块',
129
+ type: 'switch',
130
+ defaultValue: false,
131
+ extra: '开启后,配置的代码块将不会被执行',
132
+ },
133
+ {
134
+ name: NODE_DISABLE_DATA_SOURCE_KEY,
135
+ text: '禁用数据源',
136
+ type: 'switch',
137
+ defaultValue: false,
138
+ extra: '开启后,组件内配置的数据源相关配置将不会被编译,显隐条件将失效',
139
+ },
126
140
  {
127
141
  name: 'created',
128
142
  text: 'created',