goodteditor-ui 1.0.25 → 1.0.26

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,6 +1,6 @@
1
1
  {
2
2
  "name": "goodteditor-ui",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "main": "index.js",
5
5
  "homepage": "https://goodt-ui.netlify.app/",
6
6
  "scripts": {
@@ -1,4 +1,13 @@
1
- import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse, Link } from './renders';
1
+ import {
2
+ Button,
3
+ Select,
4
+ InputUnits,
5
+ ColorPicker,
6
+ ToolbarPopover,
7
+ InputAuto,
8
+ Link,
9
+ Image
10
+ } from './renders';
2
11
 
3
12
  export type Render = Readonly<{
4
13
  BUTTON: Button;
@@ -7,8 +16,8 @@ export type Render = Readonly<{
7
16
  SELECT: Select;
8
17
  TOOLBAR_POPOVER: ToolbarPopover;
9
18
  INPUT_AUTO: InputAuto;
10
- INPUT_BROWSE: InputBrowse;
11
19
  LINK: Link;
20
+ IMAGE: Image;
12
21
  }>;
13
22
 
14
23
  export type CommandDef = {
@@ -1,4 +1,13 @@
1
- import { Button, Select, InputUnits, ColorPicker, ToolbarPopover, InputAuto, InputBrowse, Link } from './renders';
1
+ import {
2
+ Button,
3
+ Select,
4
+ InputUnits,
5
+ ColorPicker,
6
+ ToolbarPopover,
7
+ InputAuto,
8
+ Link,
9
+ Image
10
+ } from './renders';
2
11
 
3
12
  /**
4
13
  * @typedef {import('./WysiwygEditor').NodeType} NodeType
@@ -147,8 +156,8 @@ export const Render = Object.freeze({
147
156
  SELECT: Select,
148
157
  TOOLBAR_POPOVER: ToolbarPopover,
149
158
  INPUT_AUTO: InputAuto,
150
- INPUT_BROWSE: InputBrowse,
151
- LINK: Link
159
+ LINK: Link,
160
+ IMAGE: Image
152
161
  });
153
162
 
154
163
  /**
@@ -231,8 +240,8 @@ export const createInputAutoTool = ({ getValue = () => null, options = [], ...to
231
240
  * @param {Tool} tool
232
241
  * @return Tool
233
242
  */
234
- export const createInputBrowseTool = ({ getValue = () => null, ...toolOptions }) => ({
235
- ...createBaseTool({ render: Render.INPUT_BROWSE, ...toolOptions }),
243
+ export const createImageTool = ({ getValue = () => null, ...toolOptions }) => ({
244
+ ...createBaseTool({ render: Render.IMAGE, ...toolOptions }),
236
245
  getValue
237
246
  });
238
247
 
@@ -24,7 +24,7 @@ export const FontSize = Extension.create({
24
24
  },
25
25
  addCommands() {
26
26
  return {
27
- setFontSize: fontSize => ({ chain }) => chain().setMark(MarkType.TEXT_STYLE, { fontSize }).run(),
27
+ setFontSize: fontSize => ({ commands }) => commands.setMark(MarkType.TEXT_STYLE, { fontSize }),
28
28
  unsetFontSize: () => ({ chain }) => chain()
29
29
  .setMark(MarkType.TEXT_STYLE, { fontSize: null })
30
30
  .removeEmptyTextStyle()
@@ -1,4 +1,5 @@
1
1
  import { Image as ImageToExtend } from '@tiptap/extension-image';
2
+ import { NodeType } from '../constants';
2
3
 
3
4
  export const Image = ImageToExtend
4
5
  .extend({
@@ -13,7 +14,20 @@ export const Image = ImageToExtend
13
14
  },
14
15
  };
15
16
  },
17
+ addCommands() {
18
+ return {
19
+ ...this.parent?.(),
20
+ toggleResponsive: (value = false) => ({ commands }) => {
21
+ value === true
22
+ ? commands.updateAttributes(NodeType.IMAGE, { class: 'responsive' })
23
+ : commands.updateAttributes(NodeType.IMAGE, { class: null });
24
+ },
25
+ setStyles: (styles) => ({ commands }) => {
26
+ const styleObjToString = () => Object
27
+ .entries(styles)
28
+ .reduce((acc, [styleName, style]) => `${acc}${styleName}:${style};`, '');
29
+ commands.updateAttributes(NodeType.IMAGE, { style: styleObjToString() });
30
+ }
31
+ };
32
+ }
16
33
  })
17
- .configure({
18
- inline: true,
19
- });
@@ -6,9 +6,6 @@ export const TextStyle = TextStyleToExtend.extend({
6
6
  ...this.parent?.(),
7
7
  class: {
8
8
  default: null
9
- },
10
- style: {
11
- default: null
12
9
  }
13
10
  }
14
11
  }
@@ -0,0 +1,162 @@
1
+ <template>
2
+ <with-popover :show.sync="isPopoverShown" auto-width>
3
+ <template #button="{ togglePopover }">
4
+ <button :title="title" class="btn btn-small btn-outline btn-icon" @click="togglePopover">
5
+ <div class="icon">
6
+ <i class="mdi" :class="icon"></i>
7
+ </div>
8
+ </button>
9
+ </template>
10
+ <div class="w-f2">
11
+ <div class="row row-hgap-3 mar-bot-l1">
12
+ <div class="col col-vmid text-truncate">
13
+ <div class="form-label form-label-xsmall text-truncate">Url</div>
14
+ </div>
15
+ <div class="col col-vmid col-12-12">
16
+ <div class="form-control form-control-icon-right w-12-12">
17
+ <input-autocomplete v-model.trim.lazy="image.url" size="small" @change="onChange" />
18
+ <div class="icon">
19
+ <i class="mdi mdi-folder cursor-pointer color-grey" @click="browse" />
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="row row-hgap-3 mar-bot-l1">
26
+ <div class="col col-vmid text-truncate">
27
+ <label for="resp" class="form-label form-label-small text-truncate">
28
+ Отзывчивое изображение
29
+ </label>
30
+ </div>
31
+ <div class="col col-vmid col-auto">
32
+ <input
33
+ id="resp"
34
+ v-model="image.isResponsive"
35
+ type="checkbox"
36
+ class="switch switch-small pull-right">
37
+ </div>
38
+ </div>
39
+
40
+ <div class="row row-hgap-l1 mar-bot-l1">
41
+ <div class="col">
42
+ <div class="row row-hgap-3">
43
+ <div class="col col-vmid text-truncate">
44
+ <div class="form-label form-label-xsmall text-truncate">Ширина</div>
45
+ </div>
46
+ <div class="col col-vmid col-12-12">
47
+ <input-units
48
+ v-model="image.width"
49
+ :units="$options.static.SizeUnits"
50
+ :disabled="image.isResponsive"
51
+ size="small"
52
+ @change="onChange">
53
+ </input-units>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="col">
58
+ <div class="row row-hgap-3">
59
+ <div class="col col-vmid text-truncate">
60
+ <div class="form-label form-label-xsmall text-truncate">Высота</div>
61
+ </div>
62
+ <div class="col col-vmid col-12-12">
63
+ <input-units
64
+ v-model="image.height"
65
+ :units="$options.static.SizeUnits"
66
+ :disabled="image.isResponsive"
67
+ size="small"
68
+ @change="onChange">
69
+ </input-units>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </with-popover>
76
+ </template>
77
+
78
+ <script>
79
+ import InputAutocomplete from '../../InputAutocomplete.vue';
80
+ import InputUnits from '../../InputUnits.vue';
81
+ import WithPopover from './components/WithPopover.vue';
82
+ import { useRender } from './mixins';
83
+
84
+ const SizeUnits = ["rem", "em", "%", "px", "vh", "vw"];
85
+
86
+ const defaultImgSettings = {
87
+ url: '',
88
+ isResponsive: true,
89
+ height: '100%',
90
+ width: '100%'
91
+ };
92
+
93
+ const cssStrToObj = (str) => {
94
+ if (str == null) {
95
+ return {};
96
+ }
97
+ return str.split(';').reduce((obj, style) => {
98
+ const [name, val] = style.split(':').map((el) => el.trim());
99
+ if (name != null && val != null) {
100
+ obj[name] = val;
101
+ }
102
+ return obj;
103
+ }, {});
104
+ }
105
+
106
+ export default {
107
+ components: {
108
+ InputAutocomplete,
109
+ InputUnits,
110
+ WithPopover
111
+ },
112
+ mixins: [useRender()],
113
+ data: () => ({
114
+ isPopoverShown: false,
115
+ image: { ...defaultImgSettings }
116
+ }),
117
+ watch: {
118
+ isPopoverShown(isShown) {
119
+ if (isShown === false) {
120
+ this.image = { ...defaultImgSettings };
121
+ return;
122
+ }
123
+
124
+ this.setImageSettings();
125
+ }
126
+ },
127
+ static: {
128
+ SizeUnits
129
+ },
130
+ methods: {
131
+ setImageSettings() {
132
+ const attrs = this.tool.getValue();
133
+
134
+ if (JSON.stringify(attrs) === '{}') {
135
+ return;
136
+ }
137
+
138
+ const { src: url, class: className, style } = attrs;
139
+ const { width, height } = cssStrToObj(style);
140
+
141
+ this.image = {
142
+ ...defaultImgSettings,
143
+ url,
144
+ isResponsive: className?.includes?.('responsive') ?? false,
145
+ ...(width && { width }),
146
+ ...(height && { height })
147
+ };
148
+ },
149
+ async browse() {
150
+ await this.tool.exec(this.image);
151
+ const { src: url } = this.tool.getValue();
152
+
153
+ this.image = { ...this.image, url };
154
+ this.emitExecuted();
155
+ },
156
+ onChange() {
157
+ this.tool.exec(this.image);
158
+ this.emitExecuted();
159
+ }
160
+ }
161
+ };
162
+ </script>
@@ -8,7 +8,6 @@
8
8
 
9
9
  <script>
10
10
  import InputUnits from '../../InputUnits.vue';
11
-
12
11
  import { useRender } from './mixins';
13
12
 
14
13
  export default {
@@ -1,63 +1,58 @@
1
1
  <template>
2
- <div :data-popover="popoverTargetId" :title="title">
3
- <button class="btn btn-small btn-outline btn-icon" @click="togglePopover">
4
- <div class="icon">
5
- <i class="mdi" :class="icon"></i>
6
- </div>
7
- </button>
8
- <popover :show="popoverShow" v-bind="popoverOptions">
9
- <div class="dropdown pad-l1 pad-top-l2">
10
- <div class="close pos-abs pos-top-right mar-2" @click="togglePopover">
11
- <i class="mdi mdi-close color-grey" />
12
- </div>
13
- <div class="row row-hgap-3 mar-bot-l1">
14
- <div class="col col-vmid text-truncate">
15
- <div class="form-label form-label-xsmall text-truncate">Url</div>
16
- </div>
17
- <div class="col col-vmid col-12-12">
18
- <input-autocomplete v-model="url" class="w-100" size="small" />
19
- </div>
2
+ <with-popover :show.sync="isPopoverShown">
3
+ <template #button="{ togglePopover }">
4
+ <button :title="title" class="btn btn-small btn-outline btn-icon" @click="togglePopover">
5
+ <div class="icon">
6
+ <i class="mdi" :class="icon"></i>
20
7
  </div>
21
- <div class="row row-hgap-3 mar-bot-l1">
22
- <div class="col col-vmid text-truncate">
23
- <div class="form-label form-label-xsmall text-truncate">Target</div>
24
- </div>
25
- <div class="col col-vmid col-12-12">
26
- <ui-select
27
- v-model="target"
28
- :options="$options.static.TargetTypeOptions"
29
- class="w-100"
30
- size="small" />
31
- </div>
32
- </div>
33
- <button class="btn btn-outline btn-small w-100" @click="execute">Применить</button>
8
+ </button>
9
+ </template>
10
+ <div class="row row-hgap-3 mar-bot-l1">
11
+ <div class="col col-vmid text-truncate">
12
+ <div class="form-label form-label-xsmall text-truncate">Url</div>
13
+ </div>
14
+ <div class="col col-vmid col-12-12">
15
+ <input-autocomplete v-model="url" class="w-100" size="small" />
34
16
  </div>
35
- </popover>
36
- </div>
17
+ </div>
18
+ <div class="row row-hgap-3 mar-bot-l1">
19
+ <div class="col col-vmid text-truncate">
20
+ <div class="form-label form-label-xsmall text-truncate">Target</div>
21
+ </div>
22
+ <div class="col col-vmid col-12-12">
23
+ <ui-select
24
+ v-model="target"
25
+ :options="$options.static.TargetTypeOptions"
26
+ class="w-100"
27
+ size="small" />
28
+ </div>
29
+ </div>
30
+ <button class="btn btn-outline btn-small w-100" @click="execute">Применить</button>
31
+ </with-popover>
37
32
  </template>
38
33
 
39
34
  <script>
40
- import Popover from '../../Popover.vue';
41
35
  import InputAutocomplete from '../../InputAutocomplete.vue';
42
36
  import UiSelect from '../../Select.vue';
43
- import WithPopover from '../../utils/WithPopover';
37
+ import WithPopover from './components/WithPopover.vue';
44
38
  import { useRender } from './mixins';
45
39
 
46
40
  const TargetType = {
47
41
  BLANK: '_blank',
48
42
  SELF: '_self'
49
- }
43
+ };
50
44
 
51
45
  export default {
52
46
  components: {
53
- Popover,
47
+ WithPopover,
54
48
  InputAutocomplete,
55
49
  UiSelect
56
50
  },
57
- mixins: [useRender(), WithPopover],
51
+ mixins: [useRender()],
58
52
  data: () => ({
59
53
  url: '',
60
- target: null
54
+ target: null,
55
+ isPopoverShown: false
61
56
  }),
62
57
  static: {
63
58
  TargetTypeOptions: [
@@ -66,7 +61,7 @@ export default {
66
61
  ]
67
62
  },
68
63
  watch: {
69
- popoverShow(isShown) {
64
+ isPopoverShown(isShown) {
70
65
  if (isShown) {
71
66
  const { url = '', target = TargetType.BLANK } = this.tool.getValue();
72
67
  this.url = url;
@@ -79,7 +74,7 @@ export default {
79
74
  const { tool, url, target } = this;
80
75
  tool.exec({ url, target });
81
76
 
82
- this.popoverShow = false;
77
+ this.isPopoverShown = false;
83
78
  this.emitExecuted();
84
79
  }
85
80
  }
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div :data-popover="popoverTargetId">
3
+ <slot name="button" v-bind="{ togglePopover }"></slot>
4
+
5
+ <popover :show.sync="popoverShow" v-bind="popoverOptions">
6
+ <div class="dropdown pad-l1 pad-top-l2">
7
+ <div class="close pos-abs pos-top-right mar-2" @click="togglePopover">
8
+ <i class="mdi mdi-close color-grey" />
9
+ </div>
10
+ <slot></slot>
11
+ </div>
12
+ </popover>
13
+ </div>
14
+ </template>
15
+
16
+ <script>
17
+ import Popover from '../../../Popover.vue';
18
+ import WithPopover from '../../../utils/WithPopover';
19
+
20
+ export default {
21
+ components: { Popover },
22
+ mixins: [WithPopover],
23
+ props: {
24
+ show: {
25
+ type: Boolean,
26
+ default: false
27
+ }
28
+ },
29
+ watch: {
30
+ popoverShow(val) {
31
+ this.$nextTick(() => this.$emit('update:show', val));
32
+ }
33
+ }
34
+ };
35
+ </script>
@@ -4,5 +4,5 @@ export { default as InputUnits } from './InputUnits.vue';
4
4
  export { default as ColorPicker } from './ColorPicker.vue';
5
5
  export { default as ToolbarPopover } from './ToolbarPopover.vue';
6
6
  export { default as InputAuto } from './InputAuto.vue';
7
- export { default as InputBrowse } from './InputBrowse.vue';
8
7
  export { default as Link } from './Link.vue';
8
+ export { default as Image } from './Image.vue';
@@ -4,5 +4,5 @@ export { default as InputUnits } from './InputUnits.vue';
4
4
  export { default as ColorPicker } from './ColorPicker.vue';
5
5
  export { default as ToolbarPopover } from './ToolbarPopover.vue';
6
6
  export { default as InputAuto } from './InputAuto.vue';
7
- export { default as InputBrowse } from './InputBrowse.vue';
8
7
  export { default as Link } from './Link.vue';
8
+ export { default as Image } from './Image.vue';
@@ -10,8 +10,8 @@ import {
10
10
  createSelectTool,
11
11
  createToolbarPopoverTool,
12
12
  createInputAutoTool,
13
- createInputBrowseTool,
14
- createLinkTool
13
+ createLinkTool,
14
+ createImageTool,
15
15
  } from './constants';
16
16
 
17
17
  export const CommandMap = {
@@ -382,32 +382,6 @@ export const TableOptionsMap = {
382
382
  })
383
383
  };
384
384
 
385
- export const ImageOptionsMap = {
386
- [ToolType.INSERT_IMAGE]: createInputBrowseTool({
387
- name: ToolType.INSERT_IMAGE,
388
- title: 'Вставить изображение',
389
- async exec(fromInputUrl) {
390
- const setImage = (url) => {
391
- this.editor.chain().focus().setImage({ src: url }).run();
392
- };
393
-
394
- if (fromInputUrl != null) {
395
- setImage(fromInputUrl);
396
- return;
397
- }
398
-
399
- const imageUrl = await this.getImageUrl();
400
-
401
- if (imageUrl != null) {
402
- setImage(imageUrl);
403
- }
404
- },
405
- getValue() {
406
- return this.editor.getAttributes(NodeType.IMAGE).src;
407
- }
408
- })
409
- };
410
-
411
385
  export const ToolsMap = {
412
386
  [ToolType.UNDO]: createButtonTool({
413
387
  name: ToolType.UNDO,
@@ -537,11 +511,35 @@ export const ToolsMap = {
537
511
  return this.editor.isActive(MarkType.CODE);
538
512
  }
539
513
  }),
540
- [ToolType.IMAGE]: createToolbarPopoverTool({
514
+ [ToolType.IMAGE]: createImageTool({
541
515
  name: ToolType.IMAGE,
542
516
  icon: 'image-plus',
543
517
  title: 'Изображение',
544
- options: Object.values(ImageOptionsMap)
518
+ async exec({ url = null, isResponsive, width, height }) {
519
+ const setImage = (src) => {
520
+ const editor = this.editor.chain().setImage({ src });
521
+
522
+ if (isResponsive === false) {
523
+ editor.setStyles({ width, height }).toggleResponsive().run();
524
+ return;
525
+ }
526
+ editor.run();
527
+ };
528
+
529
+ if ([null, ''].includes(url) === false) {
530
+ setImage(url);
531
+ return;
532
+ }
533
+
534
+ const imageUrl = await this.getImageUrl();
535
+
536
+ if (imageUrl != null) {
537
+ setImage(imageUrl);
538
+ }
539
+ },
540
+ getValue() {
541
+ return this.editor.getAttributes(NodeType.IMAGE);
542
+ }
545
543
  }),
546
544
  [ToolType.ORDERED_LIST]: createButtonTool({
547
545
  name: ToolType.ORDERED_LIST,
@@ -69,4 +69,4 @@ export const bindContext = (value, context) => {
69
69
 
70
70
  return { ...acc, [propName]: propValue };
71
71
  }, {});
72
- }
72
+ };
@@ -1,35 +0,0 @@
1
- <template>
2
- <div :title="title" class="form-control form-control-icon-right w-12-12">
3
- <input-autocomplete
4
- v-bind="{ value, disabled: !isEnabled, size: 'small' }"
5
- @change="onChange" />
6
- <div class="icon">
7
- <i class="mdi mdi-folder cursor-pointer color-grey" @click="browse" />
8
- </div>
9
- </div>
10
- </template>
11
-
12
- <script>
13
- import InputAutocomplete from '../../InputAutocomplete.vue';
14
- import { useRender } from './mixins';
15
-
16
- export default {
17
- components: { InputAutocomplete },
18
- mixins: [useRender()],
19
- computed: {
20
- value() {
21
- return this.tool.getValue();
22
- }
23
- },
24
- methods: {
25
- onChange(value) {
26
- this.tool.exec(value);
27
- this.emitExecuted();
28
- },
29
- async browse() {
30
- await this.tool.exec();
31
- this.emitExecuted();
32
- }
33
- }
34
- };
35
- </script>