@designcrowd/library.brand-page 5.10.20 → 5.10.30

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.
Files changed (95) hide show
  1. package/lib/src/brand-page/models/index.d.ts +5 -0
  2. package/lib/src/brand-page/models/index.js +4 -0
  3. package/lib/src/brand-page/utils/style.util.d.ts +8 -1
  4. package/lib/src/brand-page/utils/style.util.js +58 -5
  5. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +1 -1
  6. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.js +1 -0
  7. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.d.ts +1 -1
  8. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.js +2 -1
  9. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +33 -0
  10. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.js +35 -1
  11. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +1 -0
  12. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +17 -0
  13. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +19 -0
  14. package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +31 -0
  15. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.d.ts +2 -0
  16. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.js +161 -0
  17. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.js +3 -3
  18. package/lib/src/brand-page-maker/composables/rich-text-editor/font.d.ts +1 -1
  19. package/lib/src/brand-page-maker/composables/rich-text-editor/font.js +0 -1
  20. package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.d.ts +1 -1
  21. package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.js +0 -1
  22. package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.d.ts +19 -0
  23. package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.js +87 -0
  24. package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.d.ts +1 -1
  25. package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.js +0 -1
  26. package/lib/src/brand-page-maker/composables/rich-text-editor/textAlign.d.ts +1 -1
  27. package/lib/src/brand-page-maker/composables/rich-text-editor/textAlign.js +0 -1
  28. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +24 -3
  29. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +214 -33
  30. package/lib/src/brand-page-maker/models/button-group.model.d.ts +2 -0
  31. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +19 -2
  32. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +340 -8
  33. package/lib/src/{bundled-translations.de-DE-O4sKkzBX.js → bundled-translations.de-DE-DjQFwukR.js} +1 -1
  34. package/lib/src/{bundled-translations.es-ES-C4lGaf08.js → bundled-translations.es-ES--EbGRI7e.js} +1 -1
  35. package/lib/src/bundled-translations.fr-FR-BS3Kia5y.js +117 -0
  36. package/lib/src/{bundled-translations.fr-FR-CIy8KjAV.js → bundled-translations.fr-FR-VtC2JYUC.js} +1 -1
  37. package/lib/src/{bundled-translations.pt-PT-C8EcSdyX.js → bundled-translations.pt-PT-l9cEldEo.js} +1 -1
  38. package/lib/src/bundles/bundled-translations.fr-FR.json +2 -2
  39. package/lib/src/css/library.brand-page-brandCrowd.min.css +96 -70
  40. package/lib/src/css/library.brand-page-brandPage.min.css +96 -70
  41. package/lib/src/css/library.brand-page-designCom.min.css +96 -70
  42. package/lib/src/css/library.brand-page-preview.min.css +96 -70
  43. package/lib/src/index.d.ts +1 -0
  44. package/lib/src/index.mjs +29673 -27225
  45. package/lib/src/lib.js +1 -0
  46. package/lib/src/mockServiceWorker.js +91 -54
  47. package/lib/src/shared/content-settings/components/color-field/ColorField.mixin.d.ts +11 -0
  48. package/lib/src/shared/content-settings/components/color-field/ColorField.mixin.js +5 -0
  49. package/lib/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.d.ts +11 -1
  50. package/lib/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.js +4 -0
  51. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -0
  52. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.js +12 -7
  53. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -0
  54. package/package.json +6 -3
  55. package/src/brand-page/components/banner-section/BannerSection.vue +6 -0
  56. package/src/brand-page/components/text/Text.vue +2 -0
  57. package/src/brand-page/components/text-wrapper/TextWrapper.vue +31 -11
  58. package/src/brand-page/models/index.ts +6 -0
  59. package/src/brand-page/utils/style.util.ts +105 -5
  60. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +1 -1
  61. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.ts +1 -0
  62. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.ts +2 -0
  63. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.ts +44 -1
  64. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.vue +1 -0
  65. package/src/brand-page-maker/components/brandpage-settings/i18n/brand-page-settings.fr-FR.json +1 -1
  66. package/src/brand-page-maker/components/google-analytics/i18n/google-analytics.fr-FR.json +1 -1
  67. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +539 -50
  68. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +22 -0
  69. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +1 -1
  70. package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +42 -0
  71. package/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.ts +227 -0
  72. package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.test.ts +1 -1
  73. package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.ts +4 -15
  74. package/src/brand-page-maker/composables/rich-text-editor/font.ts +1 -1
  75. package/src/brand-page-maker/composables/rich-text-editor/formatting.ts +1 -1
  76. package/src/brand-page-maker/composables/rich-text-editor/hyperlink.ts +114 -0
  77. package/src/brand-page-maker/composables/rich-text-editor/spacing.ts +1 -1
  78. package/src/brand-page-maker/composables/rich-text-editor/textAlign.ts +1 -1
  79. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +299 -41
  80. package/src/brand-page-maker/models/button-group.model.ts +2 -0
  81. package/src/brand-page-maker/stores/brandpage-maker/__snapshots__/brandpage-maker-store.test.ts.snap +1 -0
  82. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +418 -0
  83. package/src/brand-page-maker/utils/rich-text-editor.util.ts +470 -25
  84. package/src/bundles/bundled-translations.fr-FR.json +2 -2
  85. package/src/lib.ts +1 -0
  86. package/src/shared/content-settings/components/color-field/ColorField.mixin.ts +5 -0
  87. package/src/shared/content-settings/components/color-field/ColorField.vue +1 -1
  88. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.ts +4 -0
  89. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +2 -2
  90. package/src/shared/content-settings/components/text-field/TextField.vue +1 -1
  91. package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +2 -2
  92. package/src/shared/content-settings/components/textarea-field/TextareaField.vue +3 -3
  93. package/src/shared/content-settings/components/url-field/UrlField.mixin.ts +15 -10
  94. package/src/shared/content-settings/components/url-field/UrlField.vue +1 -0
  95. package/lib/src/bundled-translations.fr-FR-CTnDppmf.js +0 -117
@@ -14,6 +14,7 @@ import {
14
14
  applyUserPersonalisationToBrandPageModelAsync,
15
15
  getBrandPageViewModelFromBrandPageModel,
16
16
  } from '../../../brand-page/utils';
17
+ import { STYLE_THEME_KEYS } from '../../../brand-page/constants';
17
18
  import type { TemplateSettingsTemplate } from '../../../shared/template-settings/models';
18
19
  import type { DebounceHandle } from 'lodash.debounce';
19
20
  import debounce from 'lodash.debounce';
@@ -178,6 +179,27 @@ export default defineComponent({
178
179
  isPersonalisable(): boolean {
179
180
  return this.template.isPersonalisable || false;
180
181
  },
182
+
183
+ cssVariablesStyle(): Record<string, string> {
184
+ if (!this.logoTheme) {
185
+ return {};
186
+ }
187
+
188
+ const cssVariables: Record<string, string> = {};
189
+
190
+ // Generate CSS variables for logo theme colors
191
+ Object.entries(STYLE_THEME_KEYS).forEach(([key, value]) => {
192
+ const themeColor = key.includes('Background')
193
+ ? this.logoTheme.logoBackgroundColor
194
+ : this.logoTheme.logoTextColor;
195
+
196
+ if (themeColor?.rgb) {
197
+ cssVariables[`--${value}`] = themeColor.rgb;
198
+ }
199
+ });
200
+
201
+ return cssVariables;
202
+ },
181
203
  },
182
204
  watch: {
183
205
  logoTheme: {
@@ -19,7 +19,7 @@
19
19
  </button>
20
20
  <!-- End Overlay-->
21
21
  <!-- Start Page -->
22
- <div class="template-preview__page">
22
+ <div class="template-preview__page" :style="cssVariablesStyle">
23
23
  <Page
24
24
  v-if="viewModel"
25
25
  :context="context"
@@ -0,0 +1,42 @@
1
+ import type { Editor } from '@tiptap/vue-3';
2
+ import type { Ref } from 'vue';
3
+
4
+ export const useBubbleMenu = (
5
+ editor: Editor,
6
+ isHyperlinkPopupOpen?: Ref<boolean>,
7
+ ) => {
8
+ const shouldShow = ({ editor, state }) => {
9
+ if (!editor || !state) return false;
10
+
11
+ // Hide preview popup if the hyperlink popup is open
12
+ if (isHyperlinkPopupOpen?.value) return false;
13
+
14
+ const { selection } = state;
15
+
16
+ // Only show bubble menu if there's a selection and it contains a link
17
+ if (!selection.empty) {
18
+ const linkAttrs = editor.getAttributes('link');
19
+
20
+ return linkAttrs.href && linkAttrs.href.trim() !== '';
21
+ }
22
+
23
+ return false;
24
+ };
25
+
26
+ const tippyOptions = {
27
+ duration: 100,
28
+ placement: 'bottom' as const,
29
+ interactive: true,
30
+ appendTo: 'parent' as const,
31
+ hideOnClick: false,
32
+ arrow: false,
33
+ offset: [0, 10] as [number, number],
34
+ trigger: 'manual' as const,
35
+ zIndex: 10,
36
+ };
37
+
38
+ return {
39
+ shouldShow,
40
+ tippyOptions,
41
+ };
42
+ };
@@ -0,0 +1,227 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { Plugin, PluginKey } from 'prosemirror-state';
3
+ import type { Transaction } from 'prosemirror-state';
4
+ import type { Node, Mark } from 'prosemirror-model';
5
+
6
+ const LINK_COLOR = '#0097d7';
7
+
8
+ const isLinkMark = (mark: Mark) => mark.type.name === 'link';
9
+ const isUnderlineMark = (mark: Mark) => mark.type.name === 'underline';
10
+ const isTextStyleMark = (mark: Mark) => mark.type.name === 'textStyle';
11
+ const isBlueColorMark = (mark: Mark) =>
12
+ isTextStyleMark(mark) && mark.attrs.color === LINK_COLOR;
13
+
14
+ const hasLinkMark = (marks: readonly Mark[]) => marks.some(isLinkMark);
15
+
16
+ const hasUnderlineMark = (marks: readonly Mark[]) =>
17
+ marks.some(isUnderlineMark);
18
+
19
+ const createStylesWithoutColor = (schema: any, textStyleMark: Mark) => {
20
+ const { color: _color, ...otherAttrs } = textStyleMark.attrs;
21
+
22
+ return Object.keys(otherAttrs).length > 0
23
+ ? schema.marks.textStyle?.create(otherAttrs)
24
+ : null;
25
+ };
26
+
27
+ const applyDefaultLinkStyling = (
28
+ tr: Transaction,
29
+ schema: any,
30
+ node: Node,
31
+ pos: number,
32
+ ) => {
33
+ const range = { from: pos, to: pos + node.nodeSize };
34
+ let modified = false;
35
+
36
+ const textStyleMark = node.marks.find(isTextStyleMark);
37
+
38
+ // Always apply blue color to newly created links, regardless of existing colors
39
+ const attrs = { ...textStyleMark?.attrs, color: LINK_COLOR };
40
+ const colorMark = schema.marks.textStyle?.create(attrs);
41
+
42
+ if (colorMark) {
43
+ if (textStyleMark) {
44
+ tr.removeMark(range.from, range.to, textStyleMark);
45
+ }
46
+
47
+ tr.addMark(range.from, range.to, colorMark);
48
+ modified = true;
49
+ }
50
+
51
+ // Add underline to newly detected URLs (auto-links)
52
+ // This ensures typed URLs get the same styling as manually created links
53
+ if (!hasUnderlineMark(node.marks)) {
54
+ const underlineMark = schema.marks.underline?.create();
55
+
56
+ if (underlineMark) {
57
+ tr.addMark(range.from, range.to, underlineMark);
58
+ modified = true;
59
+ }
60
+ }
61
+
62
+ return modified;
63
+ };
64
+
65
+ const removeHyperlinkStyles = (
66
+ tr: Transaction,
67
+ schema: any,
68
+ node: Node,
69
+ pos: number,
70
+ ) => {
71
+ const range = { from: pos, to: pos + node.nodeSize };
72
+ let modified = false;
73
+
74
+ // Only remove blue color that was added for link styling
75
+ const textStyleMark = node.marks.find(isBlueColorMark);
76
+
77
+ if (textStyleMark) {
78
+ tr.removeMark(range.from, range.to, textStyleMark);
79
+
80
+ const newTextStyleMark = createStylesWithoutColor(schema, textStyleMark);
81
+
82
+ if (newTextStyleMark) {
83
+ tr.addMark(range.from, range.to, newTextStyleMark);
84
+ }
85
+
86
+ modified = true;
87
+ }
88
+
89
+ // Don't automatically remove underlines from non-link text
90
+ // Users should be able to have underlined text that's not a link
91
+
92
+ return modified;
93
+ };
94
+
95
+ export const AutoLink = Extension.create({
96
+ name: 'autoLink',
97
+
98
+ addKeyboardShortcuts() {
99
+ return {
100
+ // Handle any character input at the end of links
101
+ Backspace: () => false, // Let default behavior handle backspace
102
+ };
103
+ },
104
+
105
+ addProseMirrorPlugins() {
106
+ return [
107
+ new Plugin({
108
+ key: new PluginKey('autoLinkStyling'),
109
+
110
+ props: {
111
+ handleTextInput(view, from, to, text) {
112
+ const { state } = view;
113
+ const { selection } = state;
114
+ const { $anchor } = selection;
115
+
116
+ // Only handle when typing at a cursor position (from === to)
117
+ if (from !== to) {
118
+ return false;
119
+ }
120
+
121
+ // Check if we have link marks at cursor position
122
+ const currentMarks = $anchor.marks();
123
+ const linkMark = currentMarks.find(isLinkMark);
124
+
125
+ if (linkMark) {
126
+ // Check if we're at the end of the link
127
+ const currentPos = $anchor.pos;
128
+ const nextPos = currentPos + 1;
129
+
130
+ const hasLinkAfter =
131
+ nextPos <= state.doc.content.size &&
132
+ state.doc
133
+ .resolve(Math.min(nextPos, state.doc.content.size))
134
+ .marks()
135
+ .some((mark) => isLinkMark(mark) && mark.eq(linkMark));
136
+
137
+ if (!hasLinkAfter) {
138
+ // detach the link after the attached link mark
139
+ const preservedMarks = currentMarks
140
+ .filter((mark) => {
141
+ if (isLinkMark(mark)) return false;
142
+
143
+ // Only preserve underlines if there's no blue color (manually added underlines)
144
+ // Remove underlines that were part of link styling (with blue color)
145
+ if (isUnderlineMark(mark)) {
146
+ const hasBlueColor = currentMarks.some(isBlueColorMark);
147
+
148
+ return !hasBlueColor; // Keep underline only if no blue color
149
+ }
150
+
151
+ // Remove blue color styling but preserve other text styles
152
+ if (isBlueColorMark(mark)) {
153
+ return createStylesWithoutColor(state.schema, mark);
154
+ }
155
+
156
+ return true;
157
+ })
158
+ .filter(Boolean); // Insert text with only preserved marks
159
+
160
+ let tr = state.tr;
161
+
162
+ tr = tr.setStoredMarks(preservedMarks);
163
+ tr = tr.insertText(text, from, to);
164
+ tr = tr.setMeta('autoLinkDetached', true);
165
+
166
+ view.dispatch(tr);
167
+
168
+ return true;
169
+ }
170
+ }
171
+
172
+ return false;
173
+ },
174
+ },
175
+
176
+ appendTransaction(transactions, oldState, newState) {
177
+ const tr = newState.tr;
178
+ let modified = false;
179
+
180
+ const hasDocChanges = transactions.some(
181
+ (transaction) => transaction.docChanged,
182
+ );
183
+
184
+ if (!hasDocChanges) return null;
185
+
186
+ const wasDetached = transactions.some((transaction) =>
187
+ transaction.getMeta('autoLinkDetached'),
188
+ );
189
+
190
+ // Apply default styling to links (unless just detached)
191
+ if (!wasDetached) {
192
+ newState.doc.descendants((node, pos) => {
193
+ if (!node.isText || !node.text || !hasLinkMark(node.marks))
194
+ return;
195
+
196
+ // Check if this is a newly created link by comparing with old state
197
+ const oldNode =
198
+ pos < oldState.doc.nodeSize ? oldState.doc.nodeAt(pos) : null;
199
+ const wasAlreadyLink = oldNode && hasLinkMark(oldNode.marks);
200
+
201
+ // Only apply full styling (including underline) to newly created links
202
+ if (wasAlreadyLink === false) {
203
+ if (applyDefaultLinkStyling(tr, newState.schema, node, pos)) {
204
+ modified = true;
205
+ }
206
+ } else {
207
+ // For existing links, preserve their current color and underline choices
208
+ // Don't override user customizations on existing links
209
+ }
210
+ });
211
+ }
212
+
213
+ // Clean up hyperlink styling from text without link marks
214
+ newState.doc.descendants((node, pos) => {
215
+ if (!node.isText || !node.text || hasLinkMark(node.marks)) return;
216
+
217
+ if (removeHyperlinkStyles(tr, newState.schema, node, pos)) {
218
+ modified = true;
219
+ }
220
+ });
221
+
222
+ return modified ? tr : null;
223
+ },
224
+ }),
225
+ ];
226
+ },
227
+ });
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { Editor } from '@tiptap/vue-3';
3
3
  import StarterKit from '@tiptap/starter-kit';
4
4
  import { MaxLength } from './MaxLength';
@@ -61,13 +61,7 @@ export const MaxLength = Extension.create<MaxLengthOptions>({
61
61
  const piece = text.slice(0, remaining);
62
62
 
63
63
  if (piece.length > 0) {
64
- nodes.push(
65
- node.type.create(
66
- node.attrs,
67
- node.type.schema.text(piece),
68
- node.marks,
69
- ),
70
- );
64
+ nodes.push(node.type.schema.text(piece, node.marks));
71
65
  remaining -= piece.length;
72
66
  }
73
67
  } else {
@@ -90,9 +84,8 @@ export const MaxLength = Extension.create<MaxLengthOptions>({
90
84
 
91
85
  if (piece.length === 0) return null;
92
86
 
93
- const newText = subNode.type.create(
94
- subNode.attrs,
95
- subNode.type.schema.text(piece),
87
+ const newText = subNode.type.schema.text(
88
+ piece,
96
89
  subNode.marks,
97
90
  );
98
91
 
@@ -112,11 +105,7 @@ export const MaxLength = Extension.create<MaxLengthOptions>({
112
105
 
113
106
  if (children.length === 0) return null;
114
107
 
115
- return subNode.type.create(
116
- subNode.attrs,
117
- Fragment.fromArray(children),
118
- subNode.marks,
119
- );
108
+ return subNode.copy(Fragment.fromArray(children));
120
109
  }
121
110
 
122
111
  return null;
@@ -1,5 +1,5 @@
1
1
  import { ref, type Ref } from 'vue';
2
- import { Editor } from '@tiptap/vue-3';
2
+ import type { Editor } from '@tiptap/vue-3';
3
3
  import {
4
4
  emToPx,
5
5
  pxToEm,
@@ -1,4 +1,4 @@
1
- import { Editor } from '@tiptap/vue-3';
1
+ import type { Editor } from '@tiptap/vue-3';
2
2
  import { skipStyleDetection } from '../../../brand-page-maker/utils';
3
3
 
4
4
  export const createFormattingActions = (editor: Editor) => {
@@ -0,0 +1,114 @@
1
+ import { ref, computed } from 'vue';
2
+ import type { Editor } from '@tiptap/vue-3';
3
+
4
+ // Store original colors before applying hyperlinks
5
+ const originalColors = new WeakMap<any, string | null>();
6
+
7
+ export const useHyperlink = (editor: Editor) => {
8
+ const hyperlinkUrl = ref('');
9
+
10
+ const storeOriginalColor = () => {
11
+ const currentTextStyle = editor.getAttributes('textStyle');
12
+ const originalColor = currentTextStyle.color || null;
13
+
14
+ // Store the original color using the editor instance as key
15
+ originalColors.set(editor, originalColor);
16
+
17
+ return originalColor;
18
+ };
19
+
20
+ const restoreOriginalColor = () => {
21
+ const originalColor = originalColors.get(editor);
22
+
23
+ if (originalColor) {
24
+ // Restore the original color
25
+ editor.chain().focus().setColor(originalColor).run();
26
+ } else {
27
+ // No original color, remove color completely
28
+ editor.chain().focus().unsetColor().run();
29
+ }
30
+
31
+ // Clean up stored color
32
+ originalColors.delete(editor);
33
+ };
34
+
35
+ const setHyperlinkUrl = (url: string) => {
36
+ const safeUrl = typeof url === 'string' ? url : '';
37
+
38
+ hyperlinkUrl.value = safeUrl;
39
+
40
+ storeOriginalColor();
41
+
42
+ // Set the link, apply blue color, and add underline by default
43
+ editor
44
+ .chain()
45
+ .focus()
46
+ .setLink({ href: safeUrl })
47
+ .setColor('#0097d7')
48
+ .setUnderline()
49
+ .run();
50
+ };
51
+
52
+ const setHyperlinkUrlValue = (url: string) => {
53
+ hyperlinkUrl.value = typeof url === 'string' ? url : '';
54
+ };
55
+
56
+ const hyperlinkValue = computed({
57
+ get: () => ({
58
+ id: 'id',
59
+ name: 'url',
60
+ value: hyperlinkUrl.value,
61
+ }),
62
+ set: (v: { id: string; name: string; value: string }) => {
63
+ setHyperlinkUrl(v?.value || '');
64
+ },
65
+ });
66
+
67
+ const applyHyperlinkToEditor = (
68
+ url: string,
69
+ text?: string,
70
+ selection?: { from: number; to: number },
71
+ ) => {
72
+ storeOriginalColor();
73
+
74
+ const selectedText = editor.chain().focus();
75
+
76
+ // Replace content with text if provided
77
+ if (text && text.trim()) {
78
+ const currentPos = selection?.from || editor.state.selection.from;
79
+
80
+ selectedText
81
+ .deleteSelection()
82
+ .insertContent(text)
83
+ .setTextSelection({
84
+ from: currentPos,
85
+ to: currentPos + text.length,
86
+ });
87
+ }
88
+
89
+ selectedText
90
+ .setLink({ href: url })
91
+ .setColor('#0097d7')
92
+ .setUnderline()
93
+ .run();
94
+ };
95
+
96
+ const removeHyperlink = (clearUrlValue = false) => {
97
+ editor.chain().focus().unsetLink().unsetUnderline().run();
98
+
99
+ // Restore original color (or remove color if there was none)
100
+ restoreOriginalColor();
101
+
102
+ if (clearUrlValue) {
103
+ setHyperlinkUrlValue('');
104
+ }
105
+ };
106
+
107
+ return {
108
+ hyperlinkValue,
109
+ setHyperlinkUrl,
110
+ setHyperlinkUrlValue,
111
+ removeHyperlink,
112
+ applyHyperlinkToEditor,
113
+ };
114
+ };
@@ -1,5 +1,5 @@
1
1
  import { ref, computed } from 'vue';
2
- import { Editor } from '@tiptap/vue-3';
2
+ import type { Editor } from '@tiptap/vue-3';
3
3
  import debounce from 'lodash/debounce';
4
4
  import { letterSpacingValueConvertor } from '../../../brand-page-maker/convertors';
5
5
  import { skipStyleDetection } from '../../../brand-page-maker/utils';
@@ -1,5 +1,5 @@
1
1
  import { ref, computed } from 'vue';
2
- import { Editor } from '@tiptap/vue-3';
2
+ import type { Editor } from '@tiptap/vue-3';
3
3
  import { skipStyleDetection } from '../../../brand-page-maker/utils';
4
4
 
5
5
  export const useTextAlign = (editor: Editor) => {