vue-editify 0.1.27 → 0.1.30

Sign up to get free protection for your applications and to get access to all the features.
package/src/core/tool.ts CHANGED
@@ -198,8 +198,15 @@ export type MenuConfigType = {
198
198
  extends?: MenuExtendType
199
199
  }
200
200
 
201
+ export type PluginMenuConfigType = {
202
+ extraDisabled?: ((name: string) => boolean) | null
203
+ sequence: number
204
+ extend: MenuCustomButtonType
205
+ }
206
+
201
207
  export type PluginResultType = {
202
- menu?: MenuConfigType
208
+ name: string
209
+ menu?: PluginMenuConfigType
203
210
  updateView?: () => void
204
211
  customParseNode?: (element: AlexElement) => AlexElement
205
212
  renderRule?: (el: AlexElement) => void
@@ -209,7 +216,9 @@ export type PluginResultType = {
209
216
 
210
217
  export type PluginType = (editifyInstance: ComponentInternalInstance, color: string | null, editTrans: (key: string) => any) => PluginResultType
211
218
 
212
- //粘贴html时保留的数据
219
+ /**
220
+ * 粘贴html时保留的数据
221
+ */
213
222
  export const pasteKeepData: ObjectType = {
214
223
  //粘贴html时元素保留的样式(全部元素)
215
224
  marks: {
@@ -237,7 +246,12 @@ export const pasteKeepData: ObjectType = {
237
246
  }
238
247
  }
239
248
 
240
- //对象平替值方法
249
+ /**
250
+ * 对象平替值方法
251
+ * @param o1
252
+ * @param o2
253
+ * @returns
254
+ */
241
255
  export const mergeObject = function (o1: ObjectType, o2: ObjectType) {
242
256
  if (!DapCommon.isObject(o1) && DapCommon.isObject(o2)) {
243
257
  return null
@@ -255,7 +269,13 @@ export const mergeObject = function (o1: ObjectType, o2: ObjectType) {
255
269
  return o1
256
270
  }
257
271
 
258
- //判断对象是否含有某个属性或者属性值是否一致
272
+ /**
273
+ * 判断对象是否含有某个属性或者属性值是否一致
274
+ * @param obj
275
+ * @param name
276
+ * @param value
277
+ * @returns
278
+ */
259
279
  export const queryHasValue = function (obj: ObjectType, name: string, value?: string | number) {
260
280
  //如果value不存在则判断是否拥有属性name
261
281
  if (value == null || value == undefined) {
@@ -296,7 +316,11 @@ export const queryHasValue = function (obj: ObjectType, name: string, value?: st
296
316
  return ownValue == value
297
317
  }
298
318
 
299
- //深拷贝函数
319
+ /**
320
+ * 深拷贝函数
321
+ * @param data
322
+ * @returns
323
+ */
300
324
  export const cloneData = function (data: any) {
301
325
  if (DapCommon.isObject(data) || Array.isArray(data)) {
302
326
  return JSON.parse(JSON.stringify(data))
@@ -304,7 +328,11 @@ export const cloneData = function (data: any) {
304
328
  return data
305
329
  }
306
330
 
307
- //根据行元素获取colgroup的col数量
331
+ /**
332
+ * 根据行元素获取colgroup的col数量
333
+ * @param row
334
+ * @returns
335
+ */
308
336
  export const getColNumbers = function (row: AlexElement) {
309
337
  const children = row.children || []
310
338
  let num = 0
@@ -321,7 +349,11 @@ export const getColNumbers = function (row: AlexElement) {
321
349
  return num
322
350
  }
323
351
 
324
- //获取菜单按钮列表数据配置
352
+ /**
353
+ * 获取菜单按钮列表数据配置
354
+ * @param editTrans
355
+ * @returns
356
+ */
325
357
  export const getButtonOptionsConfig = function (editTrans: (key: string) => any): ButtonOptionsConfigType {
326
358
  return {
327
359
  //标题配置
@@ -517,7 +549,12 @@ export const getButtonOptionsConfig = function (editTrans: (key: string) => any)
517
549
  }
518
550
  }
519
551
 
520
- //工具条全量配置
552
+ /**
553
+ * 工具条全量配置
554
+ * @param editTrans
555
+ * @param editLocale
556
+ * @returns
557
+ */
521
558
  export const getToolbarConfig = function (editTrans: (key: string) => any, editLocale: LocaleType): ToolbarConfigType {
522
559
  return {
523
560
  //是否使用工具条
@@ -762,7 +799,12 @@ export const getToolbarConfig = function (editTrans: (key: string) => any, editL
762
799
  }
763
800
  }
764
801
 
765
- //菜单栏全量配置
802
+ /**
803
+ * 菜单栏全量配置
804
+ * @param editTrans
805
+ * @param editLocale
806
+ * @returns
807
+ */
766
808
  export const getMenuConfig = function (editTrans: (key: string) => any, editLocale: LocaleType): MenuConfigType {
767
809
  return {
768
810
  //是否使用菜单栏
@@ -348,7 +348,7 @@
348
348
  color: @font-color-link;
349
349
  transition: all 200ms;
350
350
  position: relative;
351
- padding: 0 2px;
351
+ padding: 0 10px;
352
352
  font-size: 14px;
353
353
  vertical-align: baseline;
354
354
 
@@ -378,6 +378,7 @@
378
378
  &.editify-placeholder::before {
379
379
  cursor: auto;
380
380
  }
381
+
381
382
  :deep(a) {
382
383
  cursor: pointer;
383
384
  }
@@ -45,8 +45,6 @@ const emits = defineEmits(['update:modelValue', 'focus', 'blur', 'change', 'keyd
45
45
 
46
46
  //设置国际化方法
47
47
  const $editTrans = trans(props.locale || 'zh_CN')
48
- //对子孙后代组件提供国际化方法
49
- provide('$editTrans', $editTrans)
50
48
 
51
49
  //是否编辑器内部修改值
52
50
  const isModelChange = ref<boolean>(false)
@@ -135,15 +133,7 @@ const pluginResultList = computed<PluginResultType[]>(() => {
135
133
  })
136
134
  //最终生效的菜单栏配置
137
135
  const menuConfig = computed<MenuConfigType>(() => {
138
- let menu: MenuConfigType = {}
139
- //注册插件:自定义菜单栏
140
- pluginResultList.value.forEach(pluginResult => {
141
- menu = <MenuConfigType>mergeObject(menu, pluginResult.menu || {})
142
- })
143
- //加入自定义menu配置
144
- menu = <MenuConfigType>mergeObject(menu, props.menu || {})
145
- //返回最终配置
146
- return <MenuConfigType>mergeObject(getMenuConfig($editTrans, props.locale), menu)
136
+ return <MenuConfigType>mergeObject(getMenuConfig($editTrans, props.locale), props.menu || {})
147
137
  })
148
138
 
149
139
  //编辑器内部修改值的方法
@@ -841,6 +831,7 @@ onBeforeUnmount(() => {
841
831
  editor.value!.destroy()
842
832
  })
843
833
 
834
+ provide('$editTrans', $editTrans)
844
835
  provide('editify', instance)
845
836
  provide('isSourceView', isSourceView)
846
837
  provide('isFullScreen', isFullScreen)
@@ -848,6 +839,7 @@ provide('canUseMenu', canUseMenu)
848
839
  provide('editor', editor)
849
840
  provide('dataRangeCaches', dataRangeCaches)
850
841
  provide('showBorder', showBorder)
842
+ provide('pluginResultList', pluginResultList)
851
843
 
852
844
  defineExpose({
853
845
  editor,
package/src/hljs/index.ts CHANGED
@@ -74,7 +74,12 @@ export type LanguagesItemType = {
74
74
  value?: string
75
75
  }
76
76
 
77
- //获取经过hljs处理的html元素
77
+ /**
78
+ * 获取经过hljs处理的html元素
79
+ * @param code
80
+ * @param language
81
+ * @returns
82
+ */
78
83
  export const getHljsHtml = function (code: string, language: string) {
79
84
  if (language) {
80
85
  return hljs.highlight(code, {
@@ -84,7 +89,9 @@ export const getHljsHtml = function (code: string, language: string) {
84
89
  }
85
90
  return hljs.highlightAuto(code).value
86
91
  }
87
- //可选择语言列表
92
+ /**
93
+ * 可选择语言列表
94
+ */
88
95
  export const languages: LanguagesItemType[] = [
89
96
  {
90
97
  label: 'Plain Text',
package/src/index.ts CHANGED
@@ -21,7 +21,7 @@ const install: FunctionPlugin = (app: App) => {
21
21
  app.component(Editify.name!, Editify)
22
22
  }
23
23
  //版本号
24
- const version = '0.1.27'
24
+ const version = '0.1.30'
25
25
 
26
26
  //导出AlexElement元素
27
27
  export { AlexElement } from 'alex-editor'
@@ -1,10 +1,13 @@
1
1
  import { en_US } from './en_US'
2
2
  import { zh_CN } from './zh_CN'
3
3
 
4
- //语言类型
5
4
  export type LocaleType = 'zh_CN' | 'en_US'
6
5
 
7
- //翻译方法
6
+ /**
7
+ * 翻译方法
8
+ * @param locale
9
+ * @returns
10
+ */
8
11
  export const trans = (locale: LocaleType) => {
9
12
  return (key: string) => {
10
13
  return { zh_CN, en_US }[locale][key]
@@ -1,5 +1,5 @@
1
1
  import { ComponentInternalInstance, h } from 'vue'
2
- import { AlexEditor, AlexElement } from 'alex-editor'
2
+ import { AlexEditor, AlexElement, AlexElementsRangeType } from 'alex-editor'
3
3
  import { ObjectType, PluginType } from '../../core/tool'
4
4
  import Layer from '../../components/layer/layer.vue'
5
5
  import Button from '../../components/button/button.vue'
@@ -7,7 +7,7 @@ import Icon from '../../components/icon/icon.vue'
7
7
  import InsertAttachment from './insertAttachment/insertAttachment.vue'
8
8
  import { InsertAttachmentUploadErrorType } from './insertAttachment/props'
9
9
  import { event as DapEvent, common as DapCommon } from 'dap-util'
10
- import { hasPreInRange } from '../../core/function'
10
+ import { hasLinkInRange, hasPreInRange, hasQuoteInRange } from '../../core/function'
11
11
 
12
12
  export type AttachmentOptionsType = {
13
13
  //排序
@@ -34,72 +34,116 @@ export type AttachmentOptionsType = {
34
34
  handleError?: (error: InsertAttachmentUploadErrorType, file: File) => void
35
35
  }
36
36
 
37
+ /**
38
+ * 元素是否附件
39
+ * @param element
40
+ * @returns
41
+ */
42
+ export const isAttachment = (element: AlexElement) => {
43
+ if (element.isEmpty()) {
44
+ return false
45
+ }
46
+ return element.parsedom == 'span' && element.type == 'closed' && element.hasMarks() && element.marks!['data-attachment']
47
+ }
48
+
49
+ /**
50
+ * 选区是否含有附件
51
+ * @param editor
52
+ * @param dataRangeCaches
53
+ * @returns
54
+ */
55
+ export const hasAttachmentInRange = (editor: AlexEditor, dataRangeCaches: AlexElementsRangeType) => {
56
+ if (!editor.range) {
57
+ return false
58
+ }
59
+ if (editor.range.anchor.isEqual(editor.range.focus)) {
60
+ return isAttachment(editor.range.anchor.element)
61
+ }
62
+ return dataRangeCaches.flatList.some(item => {
63
+ return isAttachment(item.element)
64
+ })
65
+ }
66
+
67
+ /**
68
+ * 附件插件
69
+ * @param options
70
+ * @returns
71
+ */
37
72
  export const attachment = (options?: AttachmentOptionsType) => {
38
73
  if (!DapCommon.isObject(options)) {
39
74
  options = {}
40
75
  }
41
76
  const plugin: PluginType = (editifyInstance: ComponentInternalInstance, color: string | null, editTrans: (key: string) => any) => {
77
+ let isDisabled = false
78
+ //如果光标范围内有链接、代码块和引用则禁用
79
+ if (editifyInstance.exposed!.editor.value) {
80
+ isDisabled = hasPreInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasLinkInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) || hasQuoteInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
81
+ }
42
82
  return {
83
+ name: 'attachment',
43
84
  //附件菜单项配置
44
85
  menu: {
45
- sequence: {
46
- attachment: options!.sequence || 100
86
+ sequence: options!.sequence || 100,
87
+ extraDisabled: (name: string) => {
88
+ //如果光标选区内有附件则禁用链接菜单、引用菜单、代码块菜单
89
+ if (name == 'link' || name == 'quote' || name == 'codeBlock') {
90
+ return hasAttachmentInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value)
91
+ }
92
+ return false
47
93
  },
48
- extends: {
49
- attachment: {
50
- type: 'select',
51
- title: options!.title || editTrans('insertAttachment'),
52
- leftBorder: options!.leftBorder,
53
- rightBorder: options!.rightBorder,
54
- hideScroll: true,
55
- disabled: editifyInstance.exposed!.editor.value ? hasPreInRange(editifyInstance.exposed!.editor.value, editifyInstance.exposed!.dataRangeCaches.value) : false,
56
- default: () => h(Icon, { value: 'attachment' }),
57
- layer: (_name: string, btnInstance: InstanceType<typeof Button>) =>
58
- h(InsertAttachment, {
59
- color: color,
60
- accept: options!.accept,
61
- allowedFileType: options!.allowedFileType || [],
62
- multiple: !!options!.multiple,
63
- maxSize: options!.maxSize,
64
- minSize: options!.minSize,
65
- customUpload: options!.customUpload,
66
- handleError: options!.handleError,
67
- onChange: () => {
68
- ;(<InstanceType<typeof Layer>>btnInstance.$refs.layerRef).setPosition()
69
- },
70
- onInsert: (name: string, url: string) => {
71
- //如果地址存在
72
- if (url) {
73
- const marks: ObjectType = {
74
- 'data-attachment': url,
75
- 'data-attachment-name': name || editTrans('attachmentDefaultName'),
76
- contenteditable: 'false'
77
- }
78
- //创建元素
79
- const attachmentElement = new AlexElement('closed', 'span', marks, null, null)
80
- //获取editor对象
81
- const editor = <AlexEditor>editifyInstance.exposed!.editor.value
82
- //插入编辑器
83
- editor.insertElement(attachmentElement)
84
- //创建空文本元素
85
- const beforeText = AlexElement.getSpaceElement()
86
- const afterText = AlexElement.getSpaceElement()
87
- //将空白文本元素插入附件两端
88
- editor.addElementAfter(afterText, attachmentElement)
89
- editor.addElementBefore(beforeText, attachmentElement)
90
- //移动光标到新插入的元素
91
- editor.range!.anchor.moveToStart(afterText)
92
- editor.range!.focus.moveToStart(afterText)
93
- //渲染
94
- editor.formatElementStack()
95
- editor.domRender()
96
- editor.rangeRender()
94
+ extend: {
95
+ type: 'select',
96
+ title: options!.title || editTrans('insertAttachment'),
97
+ leftBorder: options!.leftBorder,
98
+ rightBorder: options!.rightBorder,
99
+ hideScroll: true,
100
+ disabled: isDisabled,
101
+ default: () => h(Icon, { value: 'attachment' }),
102
+ layer: (_name: string, btnInstance: InstanceType<typeof Button>) =>
103
+ h(InsertAttachment, {
104
+ color: color,
105
+ accept: options!.accept,
106
+ allowedFileType: options!.allowedFileType || [],
107
+ multiple: !!options!.multiple,
108
+ maxSize: options!.maxSize,
109
+ minSize: options!.minSize,
110
+ customUpload: options!.customUpload,
111
+ handleError: options!.handleError,
112
+ onChange: () => {
113
+ ;(<InstanceType<typeof Layer>>btnInstance.$refs.layerRef).setPosition()
114
+ },
115
+ onInsert: (name: string, url: string) => {
116
+ //如果地址存在
117
+ if (url) {
118
+ const marks: ObjectType = {
119
+ 'data-attachment': url,
120
+ 'data-attachment-name': name || editTrans('attachmentDefaultName'),
121
+ contenteditable: 'false'
97
122
  }
98
- //关闭浮层
99
- btnInstance.show = false
123
+ //创建元素
124
+ const attachmentElement = new AlexElement('closed', 'span', marks, null, null)
125
+ //获取editor对象
126
+ const editor = <AlexEditor>editifyInstance.exposed!.editor.value
127
+ //插入编辑器
128
+ editor.insertElement(attachmentElement)
129
+ //创建空文本元素
130
+ const beforeText = AlexElement.getSpaceElement()
131
+ const afterText = AlexElement.getSpaceElement()
132
+ //将空白文本元素插入附件两端
133
+ editor.addElementAfter(afterText, attachmentElement)
134
+ editor.addElementBefore(beforeText, attachmentElement)
135
+ //移动光标到新插入的元素
136
+ editor.range!.anchor.moveToStart(afterText)
137
+ editor.range!.focus.moveToStart(afterText)
138
+ //渲染
139
+ editor.formatElementStack()
140
+ editor.domRender()
141
+ editor.rangeRender()
100
142
  }
101
- })
102
- }
143
+ //关闭浮层
144
+ btnInstance.show = false
145
+ }
146
+ })
103
147
  }
104
148
  },
105
149
  //找到附件元素点击下载
@@ -1,48 +0,0 @@
1
- // vite.config.ts
2
- import { defineConfig } from "file:///Users/lingkai/Desktop/%E5%89%8D%E7%AB%AF%E5%BA%93/vue-editify/node_modules/vite/dist/node/index.js";
3
- import vue from "file:///Users/lingkai/Desktop/%E5%89%8D%E7%AB%AF%E5%BA%93/vue-editify/node_modules/@vitejs/plugin-vue/dist/index.mjs";
4
- import dts from "file:///Users/lingkai/Desktop/%E5%89%8D%E7%AB%AF%E5%BA%93/vue-editify/node_modules/vite-plugin-dts/dist/index.mjs";
5
- import path from "path";
6
- var __vite_injected_original_dirname = "/Users/lingkai/Desktop/\u524D\u7AEF\u5E93/vue-editify";
7
- var vite_config_default = defineConfig({
8
- plugins: [vue(), dts()],
9
- build: {
10
- //打包后的目录名称
11
- outDir: "lib",
12
- minify: "terser",
13
- lib: {
14
- entry: path.resolve(__vite_injected_original_dirname, "src/index.ts"),
15
- name: "editify",
16
- fileName: (format) => `editify.${format}.js`
17
- },
18
- rollupOptions: {
19
- // 确保外部化处理那些你不想打包进库的依赖
20
- external: ["vue"],
21
- output: {
22
- // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
23
- globals: {
24
- vue: "Vue"
25
- },
26
- exports: "named"
27
- }
28
- },
29
- sourcemap: false
30
- //是否构建source map 文件
31
- },
32
- css: {
33
- preprocessorOptions: {
34
- less: {
35
- // 使用 less 编写样式的 UI 库(如 antd)时建议加入这个设置
36
- javascriptEnabled: true,
37
- additionalData: `@import "src/css/base.less";`
38
- }
39
- }
40
- },
41
- server: {
42
- host: "0.0.0.0"
43
- }
44
- });
45
- export {
46
- vite_config_default as default
47
- };
48
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvbGluZ2thaS9EZXNrdG9wL1x1NTI0RFx1N0FFRlx1NUU5My92dWUtZWRpdGlmeVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL2xpbmdrYWkvRGVza3RvcC9cdTUyNERcdTdBRUZcdTVFOTMvdnVlLWVkaXRpZnkvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL2xpbmdrYWkvRGVza3RvcC8lRTUlODklOEQlRTclQUIlQUYlRTUlQkElOTMvdnVlLWVkaXRpZnkvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuaW1wb3J0IHZ1ZSBmcm9tICdAdml0ZWpzL3BsdWdpbi12dWUnXG5pbXBvcnQgZHRzIGZyb20gJ3ZpdGUtcGx1Z2luLWR0cydcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnXG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG5cdHBsdWdpbnM6IFt2dWUoKSwgZHRzKCldLFxuXHRidWlsZDoge1xuXHRcdC8vXHU2MjUzXHU1MzA1XHU1NDBFXHU3Njg0XHU3NkVFXHU1RjU1XHU1NDBEXHU3OUYwXG5cdFx0b3V0RGlyOiAnbGliJyxcblx0XHRtaW5pZnk6ICd0ZXJzZXInLFxuXHRcdGxpYjoge1xuXHRcdFx0ZW50cnk6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICdzcmMvaW5kZXgudHMnKSxcblx0XHRcdG5hbWU6ICdlZGl0aWZ5Jyxcblx0XHRcdGZpbGVOYW1lOiBmb3JtYXQgPT4gYGVkaXRpZnkuJHtmb3JtYXR9LmpzYFxuXHRcdH0sXG5cdFx0cm9sbHVwT3B0aW9uczoge1xuXHRcdFx0Ly8gXHU3ODZFXHU0RkREXHU1OTE2XHU5MEU4XHU1MzE2XHU1OTA0XHU3NDA2XHU5MEEzXHU0RTlCXHU0RjYwXHU0RTBEXHU2MEYzXHU2MjUzXHU1MzA1XHU4RkRCXHU1RTkzXHU3Njg0XHU0RjlEXHU4RDU2XG5cdFx0XHRleHRlcm5hbDogWyd2dWUnXSxcblx0XHRcdG91dHB1dDoge1xuXHRcdFx0XHQvLyBcdTU3MjggVU1EIFx1Njc4NFx1NUVGQVx1NkEyMVx1NUYwRlx1NEUwQlx1NEUzQVx1OEZEOVx1NEU5Qlx1NTkxNlx1OTBFOFx1NTMxNlx1NzY4NFx1NEY5RFx1OEQ1Nlx1NjNEMFx1NEY5Qlx1NEUwMFx1NEUyQVx1NTE2OFx1NUM0MFx1NTNEOFx1OTFDRlxuXHRcdFx0XHRnbG9iYWxzOiB7XG5cdFx0XHRcdFx0dnVlOiAnVnVlJ1xuXHRcdFx0XHR9LFxuXHRcdFx0XHRleHBvcnRzOiAnbmFtZWQnXG5cdFx0XHR9XG5cdFx0fSxcblx0XHRzb3VyY2VtYXA6IGZhbHNlIC8vXHU2NjJGXHU1NDI2XHU2Nzg0XHU1RUZBc291cmNlIG1hcCBcdTY1ODdcdTRFRjZcblx0fSxcblx0Y3NzOiB7XG5cdFx0cHJlcHJvY2Vzc29yT3B0aW9uczoge1xuXHRcdFx0bGVzczoge1xuXHRcdFx0XHQvLyBcdTRGN0ZcdTc1MjggbGVzcyBcdTdGMTZcdTUxOTlcdTY4MzdcdTVGMEZcdTc2ODQgVUkgXHU1RTkzXHVGRjA4XHU1OTgyIGFudGRcdUZGMDlcdTY1RjZcdTVFRkFcdThCQUVcdTUyQTBcdTUxNjVcdThGRDlcdTRFMkFcdThCQkVcdTdGNkVcblx0XHRcdFx0amF2YXNjcmlwdEVuYWJsZWQ6IHRydWUsXG5cdFx0XHRcdGFkZGl0aW9uYWxEYXRhOiBgQGltcG9ydCBcInNyYy9jc3MvYmFzZS5sZXNzXCI7YFxuXHRcdFx0fVxuXHRcdH1cblx0fSxcblx0c2VydmVyOiB7XG5cdFx0aG9zdDogJzAuMC4wLjAnXG5cdH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQTRULFNBQVMsb0JBQW9CO0FBQ3pWLE9BQU8sU0FBUztBQUNoQixPQUFPLFNBQVM7QUFDaEIsT0FBTyxVQUFVO0FBSGpCLElBQU0sbUNBQW1DO0FBS3pDLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzNCLFNBQVMsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0FBQUEsRUFDdEIsT0FBTztBQUFBO0FBQUEsSUFFTixRQUFRO0FBQUEsSUFDUixRQUFRO0FBQUEsSUFDUixLQUFLO0FBQUEsTUFDSixPQUFPLEtBQUssUUFBUSxrQ0FBVyxjQUFjO0FBQUEsTUFDN0MsTUFBTTtBQUFBLE1BQ04sVUFBVSxZQUFVLFdBQVcsTUFBTTtBQUFBLElBQ3RDO0FBQUEsSUFDQSxlQUFlO0FBQUE7QUFBQSxNQUVkLFVBQVUsQ0FBQyxLQUFLO0FBQUEsTUFDaEIsUUFBUTtBQUFBO0FBQUEsUUFFUCxTQUFTO0FBQUEsVUFDUixLQUFLO0FBQUEsUUFDTjtBQUFBLFFBQ0EsU0FBUztBQUFBLE1BQ1Y7QUFBQSxJQUNEO0FBQUEsSUFDQSxXQUFXO0FBQUE7QUFBQSxFQUNaO0FBQUEsRUFDQSxLQUFLO0FBQUEsSUFDSixxQkFBcUI7QUFBQSxNQUNwQixNQUFNO0FBQUE7QUFBQSxRQUVMLG1CQUFtQjtBQUFBLFFBQ25CLGdCQUFnQjtBQUFBLE1BQ2pCO0FBQUEsSUFDRDtBQUFBLEVBQ0Q7QUFBQSxFQUNBLFFBQVE7QUFBQSxJQUNQLE1BQU07QUFBQSxFQUNQO0FBQ0QsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K