@designcrowd/library.brand-page 5.10.9-data-id-2 → 5.10.9-sanitise-hyperlink-1
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/lib/src/brand-page/models/index.d.ts +5 -0
- package/lib/src/brand-page/models/index.js +4 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.js +1 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.js +2 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +19 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +31 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.d.ts +2 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.js +161 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.js +3 -3
- package/lib/src/brand-page-maker/composables/rich-text-editor/font.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/font.js +0 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.js +0 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.d.ts +19 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.js +87 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.js +0 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/textAlign.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/textAlign.js +0 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +19 -3
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +109 -22
- package/lib/src/brand-page-maker/models/button-group.model.d.ts +2 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +15 -1
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +282 -1
- package/lib/src/composables/useTranslate.js +2 -2
- package/lib/src/css/library.brand-page-brandCrowd.min.css +83 -57
- package/lib/src/css/library.brand-page-brandPage.min.css +83 -57
- package/lib/src/css/library.brand-page-designCom.min.css +83 -57
- package/lib/src/css/library.brand-page-preview.min.css +83 -57
- package/lib/src/index.d.ts +1 -0
- package/lib/src/index.mjs +24533 -22407
- package/lib/src/lib.js +1 -0
- package/lib/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.d.ts +11 -1
- package/lib/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.js +4 -0
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -0
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.js +12 -7
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -0
- package/package.json +6 -3
- package/src/brand-page/components/banner-section/BannerSection.vue +6 -0
- package/src/brand-page/components/text/Text.vue +2 -0
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +26 -8
- package/src/brand-page/models/index.ts +6 -0
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +1 -1
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.ts +1 -0
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.ts +2 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +339 -46
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +42 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.ts +227 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.test.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.ts +4 -15
- package/src/brand-page-maker/composables/rich-text-editor/font.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/formatting.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/hyperlink.ts +114 -0
- package/src/brand-page-maker/composables/rich-text-editor/spacing.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/textAlign.ts +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +160 -24
- package/src/brand-page-maker/models/button-group.model.ts +2 -0
- package/src/brand-page-maker/stores/brandpage-maker/__snapshots__/brandpage-maker-store.test.ts.snap +1 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +359 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +361 -1
- package/src/composables/useTranslate.ts +2 -2
- package/src/lib.ts +1 -0
- package/src/shared/common/SideBarSettingsHeader.vue +1 -1
- package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.mixin.ts +4 -0
- package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +2 -2
- package/src/shared/content-settings/components/text-field/TextField.vue +1 -1
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +2 -2
- package/src/shared/content-settings/components/textarea-field/TextareaField.vue +3 -3
- package/src/shared/content-settings/components/url-field/UrlField.mixin.ts +15 -10
- package/src/shared/content-settings/components/url-field/UrlField.vue +1 -0
|
@@ -207,6 +207,11 @@ export declare const BUTTON_LINK_TYPE: {
|
|
|
207
207
|
readonly tel: "tel";
|
|
208
208
|
};
|
|
209
209
|
export type ButtonLinkType = keyof typeof BUTTON_LINK_TYPE;
|
|
210
|
+
export declare const HYPERLINK_TYPE: {
|
|
211
|
+
readonly url: "url";
|
|
212
|
+
readonly page: "page";
|
|
213
|
+
};
|
|
214
|
+
export type HyperlinkType = keyof typeof HYPERLINK_TYPE;
|
|
210
215
|
export declare const URL_TYPE: {
|
|
211
216
|
readonly url: "url";
|
|
212
217
|
readonly email: "email";
|
package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const brandPageContentsSampleData: (import("../../../../brand-page").ButtonContent | import("../../../../brand-page").ProfileContent | import("../../../../brand-page").LogoContent)[];
|
|
1
|
+
export declare const brandPageContentsSampleData: (import("../../../../brand-page").ButtonContent | import("../../../../brand-page").ProfileContent | import("../../../../brand-page").TextContent | import("../../../../brand-page").LogoContent)[];
|
|
2
2
|
export declare const brandPageContentsSampleDataStarterKit: (import("../../../../brand-page").ButtonContent | import("../../../../brand-page").ProfileContent | import("../../../../brand-page").LogoContent)[];
|
package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/page-content-items.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { logoContent, profileContent, generalLink, facebookLink, websiteLink, emailLink, phoneLink, twitterLink, instagramLink, youtubeLink, linkedinLink, pinterestLink, twitchLink, tiktokLink, } from './content-settings-items.fixture';
|
|
1
|
+
import { logoContent, profileContent, generalLink, facebookLink, websiteLink, emailLink, phoneLink, twitterLink, instagramLink, youtubeLink, linkedinLink, pinterestLink, twitchLink, tiktokLink, textContent, } from './content-settings-items.fixture';
|
|
2
2
|
export const brandPageContentsSampleData = [
|
|
3
3
|
logoContent,
|
|
4
4
|
profileContent,
|
|
5
|
+
textContent,
|
|
5
6
|
generalLink,
|
|
6
7
|
websiteLink,
|
|
7
8
|
emailLink,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/vue-3';
|
|
2
|
+
import type { Ref } from 'vue';
|
|
3
|
+
export declare const useBubbleMenu: (editor: Editor, isHyperlinkPopupOpen?: Ref<boolean>) => {
|
|
4
|
+
shouldShow: ({ editor, state }: {
|
|
5
|
+
editor: any;
|
|
6
|
+
state: any;
|
|
7
|
+
}) => any;
|
|
8
|
+
tippyOptions: {
|
|
9
|
+
duration: number;
|
|
10
|
+
placement: "bottom";
|
|
11
|
+
interactive: boolean;
|
|
12
|
+
appendTo: "parent";
|
|
13
|
+
hideOnClick: boolean;
|
|
14
|
+
arrow: boolean;
|
|
15
|
+
offset: [number, number];
|
|
16
|
+
trigger: "manual";
|
|
17
|
+
zIndex: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const useBubbleMenu = (editor, isHyperlinkPopupOpen) => {
|
|
2
|
+
const shouldShow = ({ editor, state }) => {
|
|
3
|
+
if (!editor || !state)
|
|
4
|
+
return false;
|
|
5
|
+
// Hide preview popup if the hyperlink popup is open
|
|
6
|
+
if (isHyperlinkPopupOpen?.value)
|
|
7
|
+
return false;
|
|
8
|
+
const { selection } = state;
|
|
9
|
+
// Only show bubble menu if there's a selection and it contains a link
|
|
10
|
+
if (!selection.empty) {
|
|
11
|
+
const linkAttrs = editor.getAttributes('link');
|
|
12
|
+
return linkAttrs.href && linkAttrs.href.trim() !== '';
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
const tippyOptions = {
|
|
17
|
+
duration: 100,
|
|
18
|
+
placement: 'bottom',
|
|
19
|
+
interactive: true,
|
|
20
|
+
appendTo: 'parent',
|
|
21
|
+
hideOnClick: false,
|
|
22
|
+
arrow: false,
|
|
23
|
+
offset: [0, 10],
|
|
24
|
+
trigger: 'manual',
|
|
25
|
+
zIndex: 10,
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
shouldShow,
|
|
29
|
+
tippyOptions,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
3
|
+
const LINK_COLOR = '#0097d7';
|
|
4
|
+
const isLinkMark = (mark) => mark.type.name === 'link';
|
|
5
|
+
const isUnderlineMark = (mark) => mark.type.name === 'underline';
|
|
6
|
+
const isTextStyleMark = (mark) => mark.type.name === 'textStyle';
|
|
7
|
+
const isBlueColorMark = (mark) => isTextStyleMark(mark) && mark.attrs.color === LINK_COLOR;
|
|
8
|
+
const hasLinkMark = (marks) => marks.some(isLinkMark);
|
|
9
|
+
const hasUnderlineMark = (marks) => marks.some(isUnderlineMark);
|
|
10
|
+
const createStylesWithoutColor = (schema, textStyleMark) => {
|
|
11
|
+
const { color: _color, ...otherAttrs } = textStyleMark.attrs;
|
|
12
|
+
return Object.keys(otherAttrs).length > 0
|
|
13
|
+
? schema.marks.textStyle?.create(otherAttrs)
|
|
14
|
+
: null;
|
|
15
|
+
};
|
|
16
|
+
const applyDefaultLinkStyling = (tr, schema, node, pos) => {
|
|
17
|
+
const range = { from: pos, to: pos + node.nodeSize };
|
|
18
|
+
let modified = false;
|
|
19
|
+
const textStyleMark = node.marks.find(isTextStyleMark);
|
|
20
|
+
// Always apply blue color to newly created links, regardless of existing colors
|
|
21
|
+
const attrs = { ...textStyleMark?.attrs, color: LINK_COLOR };
|
|
22
|
+
const colorMark = schema.marks.textStyle?.create(attrs);
|
|
23
|
+
if (colorMark) {
|
|
24
|
+
if (textStyleMark) {
|
|
25
|
+
tr.removeMark(range.from, range.to, textStyleMark);
|
|
26
|
+
}
|
|
27
|
+
tr.addMark(range.from, range.to, colorMark);
|
|
28
|
+
modified = true;
|
|
29
|
+
}
|
|
30
|
+
// Add underline to newly detected URLs (auto-links)
|
|
31
|
+
// This ensures typed URLs get the same styling as manually created links
|
|
32
|
+
if (!hasUnderlineMark(node.marks)) {
|
|
33
|
+
const underlineMark = schema.marks.underline?.create();
|
|
34
|
+
if (underlineMark) {
|
|
35
|
+
tr.addMark(range.from, range.to, underlineMark);
|
|
36
|
+
modified = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return modified;
|
|
40
|
+
};
|
|
41
|
+
const removeHyperlinkStyles = (tr, schema, node, pos) => {
|
|
42
|
+
const range = { from: pos, to: pos + node.nodeSize };
|
|
43
|
+
let modified = false;
|
|
44
|
+
// Only remove blue color that was added for link styling
|
|
45
|
+
const textStyleMark = node.marks.find(isBlueColorMark);
|
|
46
|
+
if (textStyleMark) {
|
|
47
|
+
tr.removeMark(range.from, range.to, textStyleMark);
|
|
48
|
+
const newTextStyleMark = createStylesWithoutColor(schema, textStyleMark);
|
|
49
|
+
if (newTextStyleMark) {
|
|
50
|
+
tr.addMark(range.from, range.to, newTextStyleMark);
|
|
51
|
+
}
|
|
52
|
+
modified = true;
|
|
53
|
+
}
|
|
54
|
+
// Don't automatically remove underlines from non-link text
|
|
55
|
+
// Users should be able to have underlined text that's not a link
|
|
56
|
+
return modified;
|
|
57
|
+
};
|
|
58
|
+
export const AutoLink = Extension.create({
|
|
59
|
+
name: 'autoLink',
|
|
60
|
+
addKeyboardShortcuts() {
|
|
61
|
+
return {
|
|
62
|
+
// Handle any character input at the end of links
|
|
63
|
+
Backspace: () => false, // Let default behavior handle backspace
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
addProseMirrorPlugins() {
|
|
67
|
+
return [
|
|
68
|
+
new Plugin({
|
|
69
|
+
key: new PluginKey('autoLinkStyling'),
|
|
70
|
+
props: {
|
|
71
|
+
handleTextInput(view, from, to, text) {
|
|
72
|
+
const { state } = view;
|
|
73
|
+
const { selection } = state;
|
|
74
|
+
const { $anchor } = selection;
|
|
75
|
+
// Only handle when typing at a cursor position (from === to)
|
|
76
|
+
if (from !== to) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Check if we have link marks at cursor position
|
|
80
|
+
const currentMarks = $anchor.marks();
|
|
81
|
+
const linkMark = currentMarks.find(isLinkMark);
|
|
82
|
+
if (linkMark) {
|
|
83
|
+
// Check if we're at the end of the link
|
|
84
|
+
const currentPos = $anchor.pos;
|
|
85
|
+
const nextPos = currentPos + 1;
|
|
86
|
+
const hasLinkAfter = nextPos <= state.doc.content.size &&
|
|
87
|
+
state.doc
|
|
88
|
+
.resolve(Math.min(nextPos, state.doc.content.size))
|
|
89
|
+
.marks()
|
|
90
|
+
.some((mark) => isLinkMark(mark) && mark.eq(linkMark));
|
|
91
|
+
if (!hasLinkAfter) {
|
|
92
|
+
// detach the link after the attached link mark
|
|
93
|
+
const preservedMarks = currentMarks
|
|
94
|
+
.filter((mark) => {
|
|
95
|
+
if (isLinkMark(mark))
|
|
96
|
+
return false;
|
|
97
|
+
// Only preserve underlines if there's no blue color (manually added underlines)
|
|
98
|
+
// Remove underlines that were part of link styling (with blue color)
|
|
99
|
+
if (isUnderlineMark(mark)) {
|
|
100
|
+
const hasBlueColor = currentMarks.some(isBlueColorMark);
|
|
101
|
+
return !hasBlueColor; // Keep underline only if no blue color
|
|
102
|
+
}
|
|
103
|
+
// Remove blue color styling but preserve other text styles
|
|
104
|
+
if (isBlueColorMark(mark)) {
|
|
105
|
+
return createStylesWithoutColor(state.schema, mark);
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
})
|
|
109
|
+
.filter(Boolean); // Insert text with only preserved marks
|
|
110
|
+
let tr = state.tr;
|
|
111
|
+
tr = tr.setStoredMarks(preservedMarks);
|
|
112
|
+
tr = tr.insertText(text, from, to);
|
|
113
|
+
tr = tr.setMeta('autoLinkDetached', true);
|
|
114
|
+
view.dispatch(tr);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
appendTransaction(transactions, oldState, newState) {
|
|
122
|
+
const tr = newState.tr;
|
|
123
|
+
let modified = false;
|
|
124
|
+
const hasDocChanges = transactions.some((transaction) => transaction.docChanged);
|
|
125
|
+
if (!hasDocChanges)
|
|
126
|
+
return null;
|
|
127
|
+
const wasDetached = transactions.some((transaction) => transaction.getMeta('autoLinkDetached'));
|
|
128
|
+
// Apply default styling to links (unless just detached)
|
|
129
|
+
if (!wasDetached) {
|
|
130
|
+
newState.doc.descendants((node, pos) => {
|
|
131
|
+
if (!node.isText || !node.text || !hasLinkMark(node.marks))
|
|
132
|
+
return;
|
|
133
|
+
// Check if this is a newly created link by comparing with old state
|
|
134
|
+
const oldNode = pos < oldState.doc.nodeSize ? oldState.doc.nodeAt(pos) : null;
|
|
135
|
+
const wasAlreadyLink = oldNode && hasLinkMark(oldNode.marks);
|
|
136
|
+
// Only apply full styling (including underline) to newly created links
|
|
137
|
+
if (wasAlreadyLink === false) {
|
|
138
|
+
if (applyDefaultLinkStyling(tr, newState.schema, node, pos)) {
|
|
139
|
+
modified = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// For existing links, preserve their current color and underline choices
|
|
144
|
+
// Don't override user customizations on existing links
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Clean up hyperlink styling from text without link marks
|
|
149
|
+
newState.doc.descendants((node, pos) => {
|
|
150
|
+
if (!node.isText || !node.text || hasLinkMark(node.marks))
|
|
151
|
+
return;
|
|
152
|
+
if (removeHyperlinkStyles(tr, newState.schema, node, pos)) {
|
|
153
|
+
modified = true;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return modified ? tr : null;
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
];
|
|
160
|
+
},
|
|
161
|
+
});
|
|
@@ -42,7 +42,7 @@ export const MaxLength = Extension.create({
|
|
|
42
42
|
const text = node.text ?? '';
|
|
43
43
|
const piece = text.slice(0, remaining);
|
|
44
44
|
if (piece.length > 0) {
|
|
45
|
-
nodes.push(node.type.
|
|
45
|
+
nodes.push(node.type.schema.text(piece, node.marks));
|
|
46
46
|
remaining -= piece.length;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -64,7 +64,7 @@ export const MaxLength = Extension.create({
|
|
|
64
64
|
const piece = (subNode.text ?? '').slice(0, remaining);
|
|
65
65
|
if (piece.length === 0)
|
|
66
66
|
return null;
|
|
67
|
-
const newText = subNode.type.
|
|
67
|
+
const newText = subNode.type.schema.text(piece, subNode.marks);
|
|
68
68
|
remaining -= piece.length;
|
|
69
69
|
return newText;
|
|
70
70
|
}
|
|
@@ -77,7 +77,7 @@ export const MaxLength = Extension.create({
|
|
|
77
77
|
});
|
|
78
78
|
if (children.length === 0)
|
|
79
79
|
return null;
|
|
80
|
-
return subNode.
|
|
80
|
+
return subNode.copy(Fragment.fromArray(children));
|
|
81
81
|
}
|
|
82
82
|
return null;
|
|
83
83
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/vue-3';
|
|
2
|
+
export declare const useHyperlink: (editor: Editor) => {
|
|
3
|
+
hyperlinkValue: import("vue").WritableComputedRef<{
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}, {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}>;
|
|
12
|
+
setHyperlinkUrl: (url: string) => void;
|
|
13
|
+
setHyperlinkUrlValue: (url: string) => void;
|
|
14
|
+
removeHyperlink: (clearUrlValue?: boolean) => void;
|
|
15
|
+
applyHyperlinkToEditor: (url: string, text?: string, selection?: {
|
|
16
|
+
from: number;
|
|
17
|
+
to: number;
|
|
18
|
+
}) => void;
|
|
19
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ref, computed } from 'vue';
|
|
2
|
+
// Store original colors before applying hyperlinks
|
|
3
|
+
const originalColors = new WeakMap();
|
|
4
|
+
export const useHyperlink = (editor) => {
|
|
5
|
+
const hyperlinkUrl = ref('');
|
|
6
|
+
const storeOriginalColor = () => {
|
|
7
|
+
const currentTextStyle = editor.getAttributes('textStyle');
|
|
8
|
+
const originalColor = currentTextStyle.color || null;
|
|
9
|
+
// Store the original color using the editor instance as key
|
|
10
|
+
originalColors.set(editor, originalColor);
|
|
11
|
+
return originalColor;
|
|
12
|
+
};
|
|
13
|
+
const restoreOriginalColor = () => {
|
|
14
|
+
const originalColor = originalColors.get(editor);
|
|
15
|
+
if (originalColor) {
|
|
16
|
+
// Restore the original color
|
|
17
|
+
editor.chain().focus().setColor(originalColor).run();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// No original color, remove color completely
|
|
21
|
+
editor.chain().focus().unsetColor().run();
|
|
22
|
+
}
|
|
23
|
+
// Clean up stored color
|
|
24
|
+
originalColors.delete(editor);
|
|
25
|
+
};
|
|
26
|
+
const setHyperlinkUrl = (url) => {
|
|
27
|
+
const safeUrl = typeof url === 'string' ? url : '';
|
|
28
|
+
hyperlinkUrl.value = safeUrl;
|
|
29
|
+
storeOriginalColor();
|
|
30
|
+
// Set the link, apply blue color, and add underline by default
|
|
31
|
+
editor
|
|
32
|
+
.chain()
|
|
33
|
+
.focus()
|
|
34
|
+
.setLink({ href: safeUrl })
|
|
35
|
+
.setColor('#0097d7')
|
|
36
|
+
.setUnderline()
|
|
37
|
+
.run();
|
|
38
|
+
};
|
|
39
|
+
const setHyperlinkUrlValue = (url) => {
|
|
40
|
+
hyperlinkUrl.value = typeof url === 'string' ? url : '';
|
|
41
|
+
};
|
|
42
|
+
const hyperlinkValue = computed({
|
|
43
|
+
get: () => ({
|
|
44
|
+
id: 'id',
|
|
45
|
+
name: 'url',
|
|
46
|
+
value: hyperlinkUrl.value,
|
|
47
|
+
}),
|
|
48
|
+
set: (v) => {
|
|
49
|
+
setHyperlinkUrl(v?.value || '');
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const applyHyperlinkToEditor = (url, text, selection) => {
|
|
53
|
+
storeOriginalColor();
|
|
54
|
+
const selectedText = editor.chain().focus();
|
|
55
|
+
// Replace content with text if provided
|
|
56
|
+
if (text && text.trim()) {
|
|
57
|
+
const currentPos = selection?.from || editor.state.selection.from;
|
|
58
|
+
selectedText
|
|
59
|
+
.deleteSelection()
|
|
60
|
+
.insertContent(text)
|
|
61
|
+
.setTextSelection({
|
|
62
|
+
from: currentPos,
|
|
63
|
+
to: currentPos + text.length,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
selectedText
|
|
67
|
+
.setLink({ href: url })
|
|
68
|
+
.setColor('#0097d7')
|
|
69
|
+
.setUnderline()
|
|
70
|
+
.run();
|
|
71
|
+
};
|
|
72
|
+
const removeHyperlink = (clearUrlValue = false) => {
|
|
73
|
+
editor.chain().focus().unsetLink().unsetUnderline().run();
|
|
74
|
+
// Restore original color (or remove color if there was none)
|
|
75
|
+
restoreOriginalColor();
|
|
76
|
+
if (clearUrlValue) {
|
|
77
|
+
setHyperlinkUrlValue('');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
hyperlinkValue,
|
|
82
|
+
setHyperlinkUrl,
|
|
83
|
+
setHyperlinkUrlValue,
|
|
84
|
+
removeHyperlink,
|
|
85
|
+
applyHyperlinkToEditor,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/vue-3';
|
|
1
|
+
import type { Editor } from '@tiptap/vue-3';
|
|
2
2
|
export declare const useSpacing: (editor: Editor) => {
|
|
3
3
|
letterSpacingValue: import("vue").WritableComputedRef<string, string>;
|
|
4
4
|
lineHeightValue: import("vue").WritableComputedRef<string, string>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
|
-
import { Editor } from '@tiptap/vue-3';
|
|
3
2
|
import debounce from 'lodash/debounce';
|
|
4
3
|
import { letterSpacingValueConvertor } from '../../../brand-page-maker/convertors';
|
|
5
4
|
import { skipStyleDetection } from '../../../brand-page-maker/utils';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ModelRef, type ComputedRef, type Ref, type WritableComputedRef } from 'vue';
|
|
2
2
|
import { Editor } from '@tiptap/vue-3';
|
|
3
3
|
import type { StyleTextFontFamilyOption } from '../../../shared/style-settings/models';
|
|
4
|
-
import { TextStyles
|
|
4
|
+
import type { TextStyles } from '../../../brand-page';
|
|
5
|
+
import { type TextItem, type TextStyleDeclaration } from '../../../brand-page';
|
|
5
6
|
import type { ButtonGroupOption } from '../../../brand-page-maker/models/button-group.model';
|
|
6
7
|
interface UseRichTextEditorReturn {
|
|
7
8
|
editor: Editor;
|
|
@@ -28,12 +29,27 @@ interface UseRichTextEditorReturn {
|
|
|
28
29
|
setInitialStyleSettings: (selectedTextStyle?: BrandPageCssStyle) => void;
|
|
29
30
|
copyGlobalStyleSettings: (selectedTextStyle?: BrandPageCssStyle) => void;
|
|
30
31
|
unsetAllMarks: () => void;
|
|
32
|
+
handleHyperlink: (action: 'toggle' | 'apply' | 'remove', data?: {
|
|
33
|
+
href?: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
}) => void;
|
|
36
|
+
hyperlinkValue: ComputedRef<{
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
value: string;
|
|
40
|
+
}>;
|
|
41
|
+
setHyperlinkUrlValue: (url: string) => void;
|
|
42
|
+
shouldShow: ({ editor, state }: {
|
|
43
|
+
editor: any;
|
|
44
|
+
state: any;
|
|
45
|
+
}) => boolean;
|
|
46
|
+
tippyOptions: Record<string, any>;
|
|
31
47
|
}
|
|
32
48
|
interface DropdownInstance {
|
|
33
49
|
hideMenu: () => void;
|
|
34
50
|
}
|
|
35
51
|
interface EditorOptionType {
|
|
36
|
-
type: 'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle' | 'textDecorationLine' | 'textDecorationStrike' | 'textColor' | 'backgroundColor' | 'lineHeight' | 'letterSpacing' | 'textAlign';
|
|
52
|
+
type: 'hyperlink' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle' | 'textDecorationLine' | 'textDecorationStrike' | 'textColor' | 'backgroundColor' | 'lineHeight' | 'letterSpacing' | 'textAlign';
|
|
37
53
|
value: string | StyleTextFontFamilyOption;
|
|
38
54
|
}
|
|
39
55
|
export declare const useExternalRichTextEditor: () => {
|
|
@@ -56,5 +72,5 @@ export declare const useRichTextEditor: (model: ModelRef<any>, props: {
|
|
|
56
72
|
selectedFontSizePx: number;
|
|
57
73
|
selectedTextColor: string;
|
|
58
74
|
selectedBackgroundColor: string;
|
|
59
|
-
}, fontStyleDropdownRef: Ref<DropdownInstance | undefined, DropdownInstance | undefined>, selectedGlobalTextStyles: WritableComputedRef<TextStyles, string>, onSelectTextStyleType: (option: ButtonGroupOption) => void, updateRichTextEditor: Ref<boolean>, textStyleTypeOptions: ButtonGroupOption[]) => UseRichTextEditorReturn;
|
|
75
|
+
}, fontStyleDropdownRef: Ref<DropdownInstance | undefined, DropdownInstance | undefined>, selectedGlobalTextStyles: WritableComputedRef<TextStyles, string>, onSelectTextStyleType: (option: ButtonGroupOption) => void, updateRichTextEditor: Ref<boolean>, textStyleTypeOptions: ButtonGroupOption[], markedText: Ref<string>, isHyperlinkPopupOpen: Ref<boolean>) => UseRichTextEditorReturn;
|
|
60
76
|
export {};
|