vue-editify 0.1.27 → 0.1.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/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.29'
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') {
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