@zipify/wysiwyg 1.0.0-dev.68 → 1.0.0-dev.69

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/dist/wysiwyg.css CHANGED
@@ -620,13 +620,13 @@
620
620
  justify-content: flex-end;
621
621
  }
622
622
 
623
- .zw-link-modal[data-v-0ca87de5] {
623
+ .zw-link-modal[data-v-28e9497e] {
624
624
  width: 266px;
625
625
  }
626
- .zw-link-modal__body[data-v-0ca87de5] {
626
+ .zw-link-modal__body[data-v-28e9497e] {
627
627
  padding: var(--zw-offset-sm);
628
628
  }
629
- [data-v-0ca87de5] .zw-link-modal-dropdown__option {
629
+ [data-v-28e9497e] .zw-link-modal-dropdown__option {
630
630
  width: 234px;
631
631
  }
632
632
 
package/dist/wysiwyg.mjs CHANGED
@@ -20409,30 +20409,35 @@ function __vue2_injectStyles$r(context) {
20409
20409
  const Dropdown = /* @__PURE__ */ function() {
20410
20410
  return __component__$r.exports;
20411
20411
  }();
20412
- function usePickerApi({ pickerRef, initialColorRef, onClosed, onBeforeOpened }) {
20413
- const editingColor = ref(initialColorRef.value);
20412
+ function usePickerApi({ pickerRef, colorRef, onChange, onClosed, onBeforeOpened }) {
20414
20413
  const isOpened = computed(() => {
20415
20414
  var _a, _b;
20416
20415
  return (_b = (_a = pickerRef.value) == null ? void 0 : _a.isVisible) != null ? _b : false;
20417
20416
  });
20417
+ let initialColor;
20418
20418
  function open() {
20419
20419
  onBeforeOpened();
20420
- editingColor.value = initialColorRef.value;
20420
+ initialColor = colorRef.value;
20421
20421
  pickerRef.value.open(pickerRef.value.$el);
20422
20422
  }
20423
20423
  function close2() {
20424
20424
  var _a;
20425
- (_a = pickerRef.value) == null ? void 0 : _a.close(editingColor.value);
20426
- onClosed(editingColor.value);
20425
+ (_a = pickerRef.value) == null ? void 0 : _a.close(colorRef.value);
20426
+ onClosed();
20427
20427
  }
20428
20428
  function toggle() {
20429
20429
  isOpened.value ? close2() : open();
20430
20430
  }
20431
20431
  function cancel() {
20432
20432
  var _a;
20433
- editingColor.value = initialColorRef.value;
20434
- (_a = pickerRef.value) == null ? void 0 : _a.close(editingColor.value);
20433
+ const color = initialColor != null ? initialColor : colorRef.value;
20434
+ onChange(color);
20435
+ (_a = pickerRef.value) == null ? void 0 : _a.close(color);
20435
20436
  }
20437
+ const editingColor = computed({
20438
+ get: () => colorRef.value,
20439
+ set: (color) => colorRef.value !== color && onChange(color)
20440
+ });
20436
20441
  return {
20437
20442
  isOpened,
20438
20443
  editingColor,
@@ -20528,17 +20533,14 @@ const __vue2_script$q = {
20528
20533
  const editor = inject(InjectionTokens$1.EDITOR);
20529
20534
  const pickerRef = ref(null);
20530
20535
  const api = usePickerApi({
20531
- onClosed: (color) => {
20532
- editor.commands.restoreSelection();
20533
- if (color !== props.value)
20534
- emit("change", color);
20535
- },
20536
- onBeforeOpened: () => {
20537
- editor.commands.storeSelection();
20538
- emit("before-opened");
20536
+ onChange: (color) => {
20537
+ emit("change", color);
20538
+ editor.chain().focus().restoreSelection().run();
20539
20539
  },
20540
- pickerRef,
20541
- initialColorRef: toRef(props, "value")
20540
+ onClosed: () => editor.commands.restoreSelection(),
20541
+ onBeforeOpened: () => editor.commands.storeSelection(),
20542
+ colorRef: toRef(props, "value"),
20543
+ pickerRef
20542
20544
  });
20543
20545
  usePickerHotkeys({
20544
20546
  isOpenedRef: api.isOpened,
@@ -22086,12 +22088,27 @@ function useLink() {
22086
22088
  function prepareInitialFields() {
22087
22089
  linkData.value.text = editor.commands.getSelectedText();
22088
22090
  currentDestination.value.id = editor.getAttributes("link").destination || LinkDestinations.URL;
22089
- destinationHrefs.value[currentDestination.value.id] = editor.getAttributes("link").href || "";
22090
22091
  linkData.value.target = editor.getAttributes("link").target || LinkTargets.SELF;
22092
+ if (editor.getAttributes("link").href) {
22093
+ destinationHrefs.value[currentDestination.value.id] = editor.getAttributes("link").href;
22094
+ }
22095
+ }
22096
+ function resetDestinations() {
22097
+ destinationHrefs.value.block = pageBlocks.value[0].id;
22098
+ destinationHrefs.value.url = "";
22099
+ }
22100
+ function getFormattedHref() {
22101
+ if (currentDestination.value.id === LinkDestinations.URL) {
22102
+ const url = destinationHrefs.value.url;
22103
+ const isRelative = !!url.startsWith("/");
22104
+ const hasProtocol2 = /^https?:\/\/.+$/i.test(url);
22105
+ return isRelative || hasProtocol2 ? url : `https://${url}`;
22106
+ }
22107
+ return destinationHrefs.value[currentDestination.value.id];
22091
22108
  }
22092
22109
  function apply2() {
22093
22110
  editor.chain().focus().applyLink({
22094
- href: destinationHrefs.value[currentDestination.value.id],
22111
+ href: getFormattedHref(),
22095
22112
  text: linkData.value.text,
22096
22113
  target: linkData.value.target,
22097
22114
  destination: currentDestination.value.id
@@ -22111,6 +22128,7 @@ function useLink() {
22111
22128
  linkData,
22112
22129
  destinationHrefs,
22113
22130
  currentDestination,
22131
+ resetDestinations,
22114
22132
  prepareInitialFields,
22115
22133
  updateTarget,
22116
22134
  updateLink,
@@ -22333,7 +22351,7 @@ const __vue2_script$5 = {
22333
22351
  },
22334
22352
  setup(props, { emit }) {
22335
22353
  const link = toRef(props, "link");
22336
- const isURLDestination = computed(() => link.value.currentDestination.value.id === "url");
22354
+ const isURLDestination = computed(() => link.value.currentDestination.value.id === LinkDestinations.URL);
22337
22355
  const isTargetBlank = computed(() => link.value.linkData.value.target === LinkTargets.BLANK);
22338
22356
  const changeDestination = (value) => {
22339
22357
  emit("reset-errors");
@@ -22461,14 +22479,16 @@ const __vue2_script$4 = {
22461
22479
  const modalRef = ref(null);
22462
22480
  const editor = inject(InjectionTokens$1.EDITOR);
22463
22481
  const link = useLink();
22464
- const urlRegExp = /(^(https?:\/\/|\/)(?:www\.|(?!www))?[^\s])/;
22482
+ const urlRegExp = /^(https:\/\/www\.|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,100}(:[0-9]{1,5})?(\/.*)?$/i;
22465
22483
  const isEmpty = () => {
22466
22484
  return link.linkData.value.text ? false : "Can't be empty";
22467
22485
  };
22468
22486
  const isUrl = () => {
22469
- if (link.currentDestination.value.id !== "url")
22487
+ if (link.currentDestination.value.id !== LinkDestinations.URL)
22470
22488
  return false;
22471
- return urlRegExp.test(link.destinationHrefs.value.url) ? false : "Please enter a valid URL";
22489
+ const href = link.destinationHrefs.value.url;
22490
+ const isValidUrl = href.startsWith("/") || urlRegExp.test(href);
22491
+ return isValidUrl ? false : "Please enter a valid URL";
22472
22492
  };
22473
22493
  const nameValidator = useValidator({
22474
22494
  validations: [isEmpty]
@@ -22502,6 +22522,7 @@ const __vue2_script$4 = {
22502
22522
  return;
22503
22523
  link.apply();
22504
22524
  toggler.close();
22525
+ link.resetDestinations();
22505
22526
  };
22506
22527
  const removeLink = () => {
22507
22528
  link.removeLink();
@@ -22529,7 +22550,7 @@ var __component__$4 = /* @__PURE__ */ normalizeComponent(
22529
22550
  staticRenderFns$4,
22530
22551
  false,
22531
22552
  __vue2_injectStyles$4,
22532
- "0ca87de5",
22553
+ "28e9497e",
22533
22554
  null,
22534
22555
  null
22535
22556
  );
@@ -53,16 +53,14 @@ export default {
53
53
  const pickerRef = ref(null);
54
54
 
55
55
  const api = usePickerApi({
56
- onClosed: (color) => {
57
- editor.commands.restoreSelection();
58
- if (color !== props.value) emit('change', color);
56
+ onChange: (color) => {
57
+ emit('change', color);
58
+ editor.chain().focus().restoreSelection().run();
59
59
  },
60
- onBeforeOpened: () => {
61
- editor.commands.storeSelection();
62
- emit('before-opened');
63
- },
64
- pickerRef,
65
- initialColorRef: toRef(props, 'value')
60
+ onClosed: () => editor.commands.restoreSelection(),
61
+ onBeforeOpened: () => editor.commands.storeSelection(),
62
+ colorRef: toRef(props, 'value'),
63
+ pickerRef
66
64
  });
67
65
 
68
66
  usePickerHotkeys({
@@ -20,7 +20,7 @@ describe('open/close picker', () => {
20
20
  onClosed: jest.fn(),
21
21
  onBeforeOpened: jest.fn(),
22
22
  pickerRef: createPickerRef({ isOpened: true }),
23
- initialColorRef: ref('red')
23
+ colorRef: ref('red')
24
24
  });
25
25
 
26
26
  expect(toggler.isOpened.value).toBe(true);
@@ -31,7 +31,7 @@ describe('open/close picker', () => {
31
31
  onClosed: jest.fn(),
32
32
  onBeforeOpened: jest.fn(),
33
33
  pickerRef: ref(null),
34
- initialColorRef: ref('red')
34
+ colorRef: ref('red')
35
35
  });
36
36
 
37
37
  expect(toggler.isOpened.value).toBe(false);
@@ -43,7 +43,7 @@ describe('open/close picker', () => {
43
43
  onClosed: jest.fn(),
44
44
  onBeforeOpened: jest.fn(),
45
45
  pickerRef,
46
- initialColorRef: ref('red')
46
+ colorRef: ref('red')
47
47
  });
48
48
 
49
49
  toggler.open();
@@ -57,7 +57,7 @@ describe('open/close picker', () => {
57
57
  onClosed: jest.fn(),
58
58
  onBeforeOpened: jest.fn(),
59
59
  pickerRef,
60
- initialColorRef: ref('red')
60
+ colorRef: ref('red')
61
61
  });
62
62
 
63
63
  toggler.toggle();
@@ -71,7 +71,7 @@ describe('open/close picker', () => {
71
71
  onClosed: jest.fn(),
72
72
  onBeforeOpened: jest.fn(),
73
73
  pickerRef,
74
- initialColorRef: ref('red')
74
+ colorRef: ref('red')
75
75
  });
76
76
 
77
77
  toggler.close();
@@ -1,18 +1,18 @@
1
- import { computed, ref } from 'vue';
1
+ import { computed } from 'vue';
2
2
 
3
- export function usePickerApi({ pickerRef, initialColorRef, onClosed, onBeforeOpened }) {
4
- const editingColor = ref(initialColorRef.value);
3
+ export function usePickerApi({ pickerRef, colorRef, onChange, onClosed, onBeforeOpened }) {
5
4
  const isOpened = computed(() => pickerRef.value?.isVisible ?? false);
5
+ let initialColor;
6
6
 
7
7
  function open() {
8
8
  onBeforeOpened();
9
- editingColor.value = initialColorRef.value;
9
+ initialColor = colorRef.value;
10
10
  pickerRef.value.open(pickerRef.value.$el);
11
11
  }
12
12
 
13
13
  function close() {
14
- pickerRef.value?.close(editingColor.value);
15
- onClosed(editingColor.value);
14
+ pickerRef.value?.close(colorRef.value);
15
+ onClosed();
16
16
  }
17
17
 
18
18
  function toggle() {
@@ -20,10 +20,17 @@ export function usePickerApi({ pickerRef, initialColorRef, onClosed, onBeforeOpe
20
20
  }
21
21
 
22
22
  function cancel() {
23
- editingColor.value = initialColorRef.value;
24
- pickerRef.value?.close(editingColor.value);
23
+ const color = initialColor ?? colorRef.value;
24
+
25
+ onChange(color);
26
+ pickerRef.value?.close(color);
25
27
  }
26
28
 
29
+ const editingColor = computed({
30
+ get: () => colorRef.value,
31
+ set: (color) => colorRef.value !== color && onChange(color)
32
+ });
33
+
27
34
  return {
28
35
  isOpened,
29
36
  editingColor,
@@ -32,6 +32,7 @@
32
32
 
33
33
  <script>
34
34
  import { computed, ref, inject } from 'vue';
35
+ import { LinkDestinations } from '../../../../enums';
35
36
  import { InjectionTokens } from '../../../../injectionTokens';
36
37
  import { tooltip } from '../../../../directives';
37
38
  import { useValidator } from '../../../base/composables';
@@ -65,15 +66,18 @@ export default {
65
66
  const editor = inject(InjectionTokens.EDITOR);
66
67
 
67
68
  const link = useLink();
68
- const urlRegExp = /(^(https?:\/\/|\/)(?:www\.|(?!www))?[^\s])/;
69
+ const urlRegExp = /^(https:\/\/www\.|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,100}(:[0-9]{1,5})?(\/.*)?$/i;
69
70
 
70
71
  const isEmpty = () => {
71
72
  return link.linkData.value.text ? false : 'Can\'t be empty';
72
73
  };
73
74
  const isUrl = () => {
74
- if (link.currentDestination.value.id !== 'url') return false;
75
+ if (link.currentDestination.value.id !== LinkDestinations.URL) return false;
75
76
 
76
- return urlRegExp.test(link.destinationHrefs.value.url) ? false : 'Please enter a valid URL';
77
+ const href = link.destinationHrefs.value.url;
78
+ const isValidUrl = href.startsWith('/') || urlRegExp.test(href);
79
+
80
+ return isValidUrl ? false : 'Please enter a valid URL';
77
81
  };
78
82
 
79
83
  const nameValidator = useValidator({
@@ -116,6 +120,7 @@ export default {
116
120
 
117
121
  link.apply();
118
122
  toggler.close();
123
+ link.resetDestinations();
119
124
  };
120
125
 
121
126
  const removeLink = () => {
@@ -1,5 +1,12 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
+ exports[`actions with link should reset destination 1`] = `
4
+ Object {
5
+ "block": 1,
6
+ "url": "",
7
+ }
8
+ `;
9
+
3
10
  exports[`init link should prepare initial fields 1`] = `
4
11
  Object {
5
12
  "block": 1,
@@ -89,7 +89,7 @@ describe('actions with link', () => {
89
89
  link.apply();
90
90
 
91
91
  expect(link.editor.commands.applyLink).toHaveBeenCalledWith({
92
- destination: 'url',
92
+ destination: LinkDestinations.URL,
93
93
  href: '/world',
94
94
  target: LinkTargets.SELF,
95
95
  text: 'hello'
@@ -111,4 +111,62 @@ describe('actions with link', () => {
111
111
 
112
112
  expect(link.linkData.value.target).toBe(LinkTargets.BLANK);
113
113
  });
114
+
115
+ test('should reset destination', () => {
116
+ const link = useComposable();
117
+
118
+ link.resetDestinations();
119
+
120
+ expect(link.destinationHrefs.value).toMatchSnapshot();
121
+ });
122
+ });
123
+
124
+ describe('format link', () => {
125
+ test('should format absolute link without href', () => {
126
+ const link = useComposable();
127
+
128
+ link.updateText('hello');
129
+ link.updateLink('world.com');
130
+
131
+ link.apply();
132
+
133
+ expect(link.editor.commands.applyLink).toHaveBeenCalledWith({
134
+ destination: LinkDestinations.URL,
135
+ href: 'https://world.com',
136
+ target: LinkTargets.SELF,
137
+ text: 'hello'
138
+ });
139
+ });
140
+
141
+ test('should format absolute link with href', () => {
142
+ const link = useComposable();
143
+
144
+ link.updateText('hello');
145
+ link.updateLink('https://world.com');
146
+
147
+ link.apply();
148
+
149
+ expect(link.editor.commands.applyLink).toHaveBeenCalledWith({
150
+ destination: LinkDestinations.URL,
151
+ href: 'https://world.com',
152
+ target: LinkTargets.SELF,
153
+ text: 'hello'
154
+ });
155
+ });
156
+
157
+ test('should format relative link', () => {
158
+ const link = useComposable();
159
+
160
+ link.updateText('hello');
161
+ link.updateLink('/world/new');
162
+
163
+ link.apply();
164
+
165
+ expect(link.editor.commands.applyLink).toHaveBeenCalledWith({
166
+ destination: LinkDestinations.URL,
167
+ href: '/world/new',
168
+ target: LinkTargets.SELF,
169
+ text: 'hello'
170
+ });
171
+ });
114
172
  });
@@ -17,8 +17,28 @@ export function useLink() {
17
17
  function prepareInitialFields() {
18
18
  linkData.value.text = editor.commands.getSelectedText();
19
19
  currentDestination.value.id = editor.getAttributes('link').destination || LinkDestinations.URL;
20
- destinationHrefs.value[currentDestination.value.id] = editor.getAttributes('link').href || '';
21
20
  linkData.value.target = editor.getAttributes('link').target || LinkTargets.SELF;
21
+
22
+ if (editor.getAttributes('link').href) {
23
+ destinationHrefs.value[currentDestination.value.id] = editor.getAttributes('link').href;
24
+ }
25
+ }
26
+
27
+ function resetDestinations() {
28
+ destinationHrefs.value.block = pageBlocks.value[0].id;
29
+ destinationHrefs.value.url = '';
30
+ }
31
+
32
+ function getFormattedHref() {
33
+ if (currentDestination.value.id === LinkDestinations.URL) {
34
+ const url = destinationHrefs.value.url;
35
+ const isRelative = !!url.startsWith('/');
36
+ const hasProtocol = /^https?:\/\/.+$/i.test(url);
37
+
38
+ return isRelative || hasProtocol ? url : `https://${url}`;
39
+ }
40
+
41
+ return destinationHrefs.value[currentDestination.value.id];
22
42
  }
23
43
 
24
44
  function apply() {
@@ -26,7 +46,7 @@ export function useLink() {
26
46
  .chain()
27
47
  .focus()
28
48
  .applyLink({
29
- href: destinationHrefs.value[currentDestination.value.id],
49
+ href: getFormattedHref(),
30
50
  text: linkData.value.text,
31
51
  target: linkData.value.target,
32
52
  destination: currentDestination.value.id
@@ -51,6 +71,7 @@ export function useLink() {
51
71
  linkData,
52
72
  destinationHrefs,
53
73
  currentDestination,
74
+ resetDestinations,
54
75
  prepareInitialFields,
55
76
  updateTarget,
56
77
  updateLink,
@@ -75,7 +75,7 @@ export default {
75
75
 
76
76
  setup(props, { emit }) {
77
77
  const link = toRef(props, 'link');
78
- const isURLDestination = computed(() => link.value.currentDestination.value.id === 'url');
78
+ const isURLDestination = computed(() => link.value.currentDestination.value.id === LinkDestinations.URL);
79
79
  const isTargetBlank = computed(() => link.value.linkData.value.target === LinkTargets.BLANK);
80
80
 
81
81
  const changeDestination = (value) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zipify/wysiwyg",
3
- "version": "1.0.0-dev.68",
3
+ "version": "1.0.0-dev.69",
4
4
  "description": "Zipify modification of TipTap text editor",
5
5
  "main": "dist/wysiwyg.mjs",
6
6
  "repository": {