@docs.plus/extension-hyperlink 1.5.2 → 2.1.0
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/CHANGELOG.md +572 -0
- package/LICENSE +21 -0
- package/README.md +135 -385
- package/dist/index.cjs +22 -712
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +392 -0
- package/dist/index.d.ts +392 -9
- package/dist/index.js +22 -707
- package/dist/index.js.map +1 -1
- package/dist/styles.css +362 -0
- package/package.json +85 -33
- package/dist/helpers/autoHyperlink.d.ts +0 -8
- package/dist/helpers/clickHandler.d.ts +0 -11
- package/dist/helpers/editHyperlink.d.ts +0 -9
- package/dist/helpers/pasteHandler.d.ts +0 -9
- package/dist/helpers/tippyHelper.d.ts +0 -29
- package/dist/hyperlink.d.ts +0 -77
- package/dist/index.umd.js +0 -713
- package/dist/index.umd.js.map +0 -1
- package/dist/modals/editeHyperlink.d.ts +0 -11
- package/dist/modals/icons.d.ts +0 -9
- package/dist/modals/previewHyperlink.d.ts +0 -11
- package/dist/modals/setHyperlink.d.ts +0 -14
- package/src/helpers/autoHyperlink.ts +0 -167
- package/src/helpers/clickHandler.ts +0 -80
- package/src/helpers/editHyperlink.ts +0 -59
- package/src/helpers/pasteHandler.ts +0 -46
- package/src/helpers/tippyHelper.ts +0 -136
- package/src/hyperlink.ts +0 -277
- package/src/index.ts +0 -17
- package/src/modals/editeHyperlink.ts +0 -99
- package/src/modals/icons.ts +0 -46
- package/src/modals/previewHyperlink.ts +0 -85
- package/src/modals/setHyperlink.ts +0 -97
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import tippy, { Instance, Props, roundArrow } from "tippy.js";
|
|
2
|
-
import { Editor } from "@tiptap/core";
|
|
3
|
-
import { EditorView } from "@tiptap/pm/view";
|
|
4
|
-
import { posToDOMRect } from "@tiptap/core";
|
|
5
|
-
|
|
6
|
-
export interface TippyInitOptions {
|
|
7
|
-
editor: Editor;
|
|
8
|
-
validate?: (url: string) => boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class Tooltip {
|
|
12
|
-
private tippyInstance?: Instance;
|
|
13
|
-
private preventHide: boolean = false;
|
|
14
|
-
private tippyWrapper: HTMLDivElement;
|
|
15
|
-
private editor: Editor;
|
|
16
|
-
private view: EditorView;
|
|
17
|
-
|
|
18
|
-
constructor(options: TippyInitOptions) {
|
|
19
|
-
this.editor = options.editor;
|
|
20
|
-
this.view = this.editor.view;
|
|
21
|
-
this.tippyWrapper = document.createElement("div");
|
|
22
|
-
this.tippyWrapper.addEventListener("mousedown", this.mousedownHandler, {
|
|
23
|
-
capture: true,
|
|
24
|
-
});
|
|
25
|
-
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
|
26
|
-
this.editor.on("blur", this.blurHandler);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
init() {
|
|
30
|
-
this.tippyWrapper.innerHTML = "";
|
|
31
|
-
|
|
32
|
-
return { tippyModal: this.tippyWrapper, tippyInstance: this.tippyInstance };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
show() {
|
|
36
|
-
this.tippyInstance?.show();
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
hide() {
|
|
41
|
-
setTimeout(() => this.tippyInstance?.hide());
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private mousedownHandler = () => {
|
|
46
|
-
this.preventHide = true;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
private dragstartHandler = () => {
|
|
50
|
-
this.hide();
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
private blurHandler = ({ event }: { event: FocusEvent }) => {
|
|
54
|
-
if (this.preventHide) {
|
|
55
|
-
this.preventHide = false;
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (
|
|
59
|
-
event?.relatedTarget &&
|
|
60
|
-
this.tippyWrapper.parentNode?.contains(event.relatedTarget as Node)
|
|
61
|
-
) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this.hide();
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
private tippyBlurHandler = (event: FocusEvent) => {
|
|
68
|
-
this.blurHandler({ event });
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
private createTooltip() {
|
|
72
|
-
if (!this.editor || !this.editor.options) return;
|
|
73
|
-
const { element: editorElement } = this.editor.options;
|
|
74
|
-
const editorIsAttached = !!editorElement.parentElement;
|
|
75
|
-
|
|
76
|
-
if (this.tippyInstance || !editorIsAttached) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.tippyInstance = tippy(editorElement, {
|
|
81
|
-
duration: 0,
|
|
82
|
-
getReferenceClientRect: null,
|
|
83
|
-
content: this.tippyWrapper,
|
|
84
|
-
interactive: true,
|
|
85
|
-
trigger: "manual",
|
|
86
|
-
placement: "bottom",
|
|
87
|
-
hideOnClick: true,
|
|
88
|
-
onClickOutside: (instance, event) => {
|
|
89
|
-
this.hide();
|
|
90
|
-
},
|
|
91
|
-
onAfterUpdate: (instance, partialProps) => {
|
|
92
|
-
this.show();
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (this.tippyInstance.popper.firstChild) {
|
|
97
|
-
(this.tippyInstance.popper.firstChild as HTMLElement).addEventListener(
|
|
98
|
-
"blur",
|
|
99
|
-
this.tippyBlurHandler
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
update(view: EditorView, option: any = {}) {
|
|
105
|
-
this.createTooltip();
|
|
106
|
-
|
|
107
|
-
option.arrow = option?.arrow ?? false;
|
|
108
|
-
|
|
109
|
-
if (this.tippyInstance) {
|
|
110
|
-
this.tippyInstance.setProps({
|
|
111
|
-
...option,
|
|
112
|
-
getReferenceClientRect: () => {
|
|
113
|
-
const pos = view.state.selection.from;
|
|
114
|
-
// width: 0 is a hack to prevent tippy display in the wrong position
|
|
115
|
-
return { ...posToDOMRect(view, pos, pos), width: 0 };
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
destroyTooltip() {
|
|
124
|
-
if (this.tippyInstance) {
|
|
125
|
-
this.tippyInstance.destroy();
|
|
126
|
-
this.tippyInstance = undefined;
|
|
127
|
-
this.tippyWrapper.removeEventListener("mousedown", this.mousedownHandler, {
|
|
128
|
-
capture: true,
|
|
129
|
-
});
|
|
130
|
-
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
|
131
|
-
this.editor.off("blur", this.blurHandler);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export default Tooltip;
|
package/src/hyperlink.ts
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import { Mark, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
2
|
-
import { Plugin } from "@tiptap/pm/state";
|
|
3
|
-
import { find, registerCustomProtocol, reset } from "linkifyjs";
|
|
4
|
-
import autoHyperlink from "./helpers/autoHyperlink";
|
|
5
|
-
import clickHandler from "./helpers/clickHandler";
|
|
6
|
-
import { pasteHandler } from "./helpers/pasteHandler";
|
|
7
|
-
import editHyperlinkHelper from "./helpers/editHyperlink";
|
|
8
|
-
import Tooltip from "./helpers/tippyHelper";
|
|
9
|
-
import { roundArrow } from "tippy.js";
|
|
10
|
-
import { TSetHyperlinkModalOptions } from "./modals/setHyperlink";
|
|
11
|
-
import { THyperlinkPreviewModalOptions } from "./modals/previewHyperlink";
|
|
12
|
-
|
|
13
|
-
export interface LinkProtocolOptions {
|
|
14
|
-
scheme: string;
|
|
15
|
-
optionalSlashes?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface HyperlinkOptions {
|
|
19
|
-
/**
|
|
20
|
-
* If enabled, it adds links as you type.
|
|
21
|
-
*/
|
|
22
|
-
autoHyperlink: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* An array of custom protocols to be registered with linkifyjs.
|
|
25
|
-
*/
|
|
26
|
-
protocols: Array<LinkProtocolOptions | string>;
|
|
27
|
-
/**
|
|
28
|
-
* If enabled, links will be opened on click.
|
|
29
|
-
*/
|
|
30
|
-
openOnClick: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Adds a link to the current selection if the pasted content only contains an url.
|
|
33
|
-
*/
|
|
34
|
-
hyperlinkOnPaste: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* A list of HTML attributes to be rendered.
|
|
37
|
-
*/
|
|
38
|
-
HTMLAttributes: Record<string, any>;
|
|
39
|
-
/**
|
|
40
|
-
* A list of modals to be rendered.
|
|
41
|
-
* @default null
|
|
42
|
-
* @example
|
|
43
|
-
*/
|
|
44
|
-
modals: {
|
|
45
|
-
previewHyperlink?: ((options: THyperlinkPreviewModalOptions) => void | HTMLElement) | null;
|
|
46
|
-
setHyperlink?: ((options: TSetHyperlinkModalOptions) => void | HTMLElement) | null;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* A validation function that modifies link verification for the auto linker.
|
|
50
|
-
* @param url - The url to be validated.
|
|
51
|
-
* @returns - True if the url is valid, false otherwise.
|
|
52
|
-
*/
|
|
53
|
-
validate?: (url: string) => boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
declare module "@tiptap/core" {
|
|
57
|
-
interface Commands<ReturnType> {
|
|
58
|
-
link: {
|
|
59
|
-
/**
|
|
60
|
-
* Edit the hyperlink's text
|
|
61
|
-
*/
|
|
62
|
-
editHyperLinkText: (text: string) => ReturnType;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Edit the hyperlink's href value
|
|
66
|
-
*/
|
|
67
|
-
editHyperLinkHref: (href: string) => ReturnType;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Edit the hyperlink's
|
|
71
|
-
*/
|
|
72
|
-
editHyperlink: (attributes?: { newText?: string; newURL?: string }) => ReturnType;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Set a hyperlink
|
|
76
|
-
*/
|
|
77
|
-
setHyperlink: (attributes?: { href: string; target?: string | null }) => ReturnType;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Unset a link mark
|
|
81
|
-
*/
|
|
82
|
-
unsetHyperlink: () => ReturnType;
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export const Hyperlink = Mark.create<HyperlinkOptions>({
|
|
88
|
-
name: "hyperlink",
|
|
89
|
-
|
|
90
|
-
priority: 1000,
|
|
91
|
-
|
|
92
|
-
keepOnSplit: false,
|
|
93
|
-
|
|
94
|
-
onCreate() {
|
|
95
|
-
this.options.protocols.forEach((protocol) => {
|
|
96
|
-
if (typeof protocol === "string") {
|
|
97
|
-
registerCustomProtocol(protocol);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
registerCustomProtocol(protocol.scheme, protocol.optionalSlashes);
|
|
101
|
-
});
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
onDestroy() {
|
|
105
|
-
reset();
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
inclusive() {
|
|
109
|
-
return this.options.autoHyperlink;
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
addOptions() {
|
|
113
|
-
return {
|
|
114
|
-
openOnClick: true,
|
|
115
|
-
hyperlinkOnPaste: true,
|
|
116
|
-
autoHyperlink: true,
|
|
117
|
-
protocols: [],
|
|
118
|
-
HTMLAttributes: {
|
|
119
|
-
target: "_blank",
|
|
120
|
-
rel: "noopener noreferrer nofollow",
|
|
121
|
-
class: null,
|
|
122
|
-
},
|
|
123
|
-
modals: {
|
|
124
|
-
previewHyperlink: null,
|
|
125
|
-
setHyperlink: null,
|
|
126
|
-
},
|
|
127
|
-
validate: undefined,
|
|
128
|
-
};
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
addAttributes() {
|
|
132
|
-
return {
|
|
133
|
-
href: {
|
|
134
|
-
default: null,
|
|
135
|
-
},
|
|
136
|
-
target: {
|
|
137
|
-
default: this.options.HTMLAttributes.target,
|
|
138
|
-
},
|
|
139
|
-
class: {
|
|
140
|
-
default: this.options.HTMLAttributes.class,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
parseHTML() {
|
|
146
|
-
return [{ tag: 'a[href]:not([href *= "javascript:" i])' }];
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
renderHTML({ HTMLAttributes }) {
|
|
150
|
-
return ["a", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
addCommands() {
|
|
154
|
-
return {
|
|
155
|
-
setHyperlink:
|
|
156
|
-
(attributes) =>
|
|
157
|
-
({ editor, chain }) => {
|
|
158
|
-
if (!this.options.modals.setHyperlink) {
|
|
159
|
-
return chain()
|
|
160
|
-
.setMark(this.name, attributes)
|
|
161
|
-
.setMeta("preventAutohyperlink", true)
|
|
162
|
-
.run();
|
|
163
|
-
} else {
|
|
164
|
-
this.options.modals.setHyperlink({
|
|
165
|
-
editor,
|
|
166
|
-
validate: this.options.validate,
|
|
167
|
-
extentionName: this.name,
|
|
168
|
-
attributes,
|
|
169
|
-
Tooltip,
|
|
170
|
-
roundArrow,
|
|
171
|
-
});
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
editHyperlink:
|
|
177
|
-
(attributes) =>
|
|
178
|
-
({ editor }) => {
|
|
179
|
-
return editHyperlinkHelper({
|
|
180
|
-
...attributes,
|
|
181
|
-
editor,
|
|
182
|
-
validate: this.options.validate,
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
editHyperLinkText:
|
|
187
|
-
(newText) =>
|
|
188
|
-
({ editor }) => {
|
|
189
|
-
return editHyperlinkHelper({
|
|
190
|
-
editor,
|
|
191
|
-
newText,
|
|
192
|
-
validate: this.options.validate,
|
|
193
|
-
});
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
editHyperLinkHref:
|
|
197
|
-
(newURL) =>
|
|
198
|
-
({ editor }) => {
|
|
199
|
-
return editHyperlinkHelper({
|
|
200
|
-
editor,
|
|
201
|
-
validate: this.options.validate,
|
|
202
|
-
newURL,
|
|
203
|
-
});
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
unsetHyperlink:
|
|
207
|
-
() =>
|
|
208
|
-
({ chain }) => {
|
|
209
|
-
return chain()
|
|
210
|
-
.unsetMark(this.name, { extendEmptyMarkRange: true })
|
|
211
|
-
.setMeta("preventAutohyperlink", true)
|
|
212
|
-
.run();
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
addPasteRules() {
|
|
218
|
-
return [
|
|
219
|
-
markPasteRule({
|
|
220
|
-
find: (text) =>
|
|
221
|
-
find(text)
|
|
222
|
-
.filter((link) => {
|
|
223
|
-
if (this.options.validate) {
|
|
224
|
-
return this.options.validate(link.value);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return true;
|
|
228
|
-
})
|
|
229
|
-
.filter((link) => link.isLink)
|
|
230
|
-
.map((link) => ({
|
|
231
|
-
text: link.value,
|
|
232
|
-
index: link.start,
|
|
233
|
-
data: link,
|
|
234
|
-
})),
|
|
235
|
-
type: this.type,
|
|
236
|
-
getAttributes: (match) => ({
|
|
237
|
-
href: match.data?.href,
|
|
238
|
-
}),
|
|
239
|
-
}),
|
|
240
|
-
];
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
addProseMirrorPlugins() {
|
|
244
|
-
const plugins: Plugin[] = [];
|
|
245
|
-
|
|
246
|
-
if (this.options.autoHyperlink) {
|
|
247
|
-
plugins.push(
|
|
248
|
-
autoHyperlink({
|
|
249
|
-
type: this.type,
|
|
250
|
-
validate: this.options.validate,
|
|
251
|
-
})
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (this.options.openOnClick) {
|
|
256
|
-
plugins.push(
|
|
257
|
-
clickHandler({
|
|
258
|
-
type: this.type,
|
|
259
|
-
editor: this.editor,
|
|
260
|
-
validate: this.options.validate,
|
|
261
|
-
modal: this.options.modals.previewHyperlink,
|
|
262
|
-
})
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (this.options.hyperlinkOnPaste) {
|
|
267
|
-
plugins.push(
|
|
268
|
-
pasteHandler({
|
|
269
|
-
editor: this.editor,
|
|
270
|
-
type: this.type,
|
|
271
|
-
})
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return plugins;
|
|
276
|
-
},
|
|
277
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export * from "./hyperlink";
|
|
2
|
-
|
|
3
|
-
export * from "./helpers/tippyHelper";
|
|
4
|
-
|
|
5
|
-
export * from "./modals/previewHyperlink";
|
|
6
|
-
|
|
7
|
-
export * from "./modals/setHyperlink";
|
|
8
|
-
|
|
9
|
-
import { previewHyperlinkModal } from "./modals/previewHyperlink";
|
|
10
|
-
|
|
11
|
-
import { setHyperlinkModal } from "./modals/setHyperlink";
|
|
12
|
-
|
|
13
|
-
import Tooltip from "./helpers/tippyHelper";
|
|
14
|
-
|
|
15
|
-
import { Hyperlink } from "./hyperlink";
|
|
16
|
-
|
|
17
|
-
export { Hyperlink, Tooltip, previewHyperlinkModal, setHyperlinkModal };
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { Editor } from "@tiptap/core";
|
|
2
|
-
import { roundArrow } from "tippy.js";
|
|
3
|
-
import { find } from "linkifyjs";
|
|
4
|
-
import Tooltip from "../helpers/tippyHelper";
|
|
5
|
-
|
|
6
|
-
type EditHyperlinkModalOptions = {
|
|
7
|
-
editor: Editor;
|
|
8
|
-
validate?: (url: string) => boolean;
|
|
9
|
-
link: HTMLAnchorElement;
|
|
10
|
-
hyperlinkModal: HTMLElement;
|
|
11
|
-
tippy: Tooltip;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const editHyperlinkHandler = (options: EditHyperlinkModalOptions) => {
|
|
15
|
-
// Create the form
|
|
16
|
-
const form = document.createElement("form");
|
|
17
|
-
|
|
18
|
-
const buttonsWrapper = document.createElement("div");
|
|
19
|
-
const inputsWrapper = document.createElement("div");
|
|
20
|
-
|
|
21
|
-
const linkTextInput = document.createElement("input");
|
|
22
|
-
const hrefInput = document.createElement("input");
|
|
23
|
-
const applyButton = document.createElement("button");
|
|
24
|
-
|
|
25
|
-
buttonsWrapper.classList.add("hyperlink-edit-modal__buttons-wrapper");
|
|
26
|
-
inputsWrapper.classList.add("hyperlink-edit-modal__inputs-wrapper");
|
|
27
|
-
|
|
28
|
-
linkTextInput.type = "text";
|
|
29
|
-
linkTextInput.value = options.link?.innerText || "";
|
|
30
|
-
linkTextInput.placeholder = "Enter link text";
|
|
31
|
-
|
|
32
|
-
hrefInput.type = "text";
|
|
33
|
-
hrefInput.value = options.link.href;
|
|
34
|
-
hrefInput.placeholder = "Enter href";
|
|
35
|
-
|
|
36
|
-
applyButton.type = "submit";
|
|
37
|
-
applyButton.classList.add("hyperlink-edit-modal__apply-button");
|
|
38
|
-
applyButton.innerText = "Apply";
|
|
39
|
-
|
|
40
|
-
buttonsWrapper.append(applyButton);
|
|
41
|
-
inputsWrapper.append(linkTextInput, hrefInput);
|
|
42
|
-
|
|
43
|
-
// Append to form
|
|
44
|
-
form.append(inputsWrapper, buttonsWrapper);
|
|
45
|
-
|
|
46
|
-
options.hyperlinkModal.innerHTML = "";
|
|
47
|
-
options.hyperlinkModal.appendChild(form);
|
|
48
|
-
|
|
49
|
-
hrefInput.addEventListener("keydown", () => {
|
|
50
|
-
hrefInput.style.outlineColor = " #dadce0";
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
hrefInput.addEventListener("keydown", () => {
|
|
54
|
-
linkTextInput.style.outlineColor = " #dadce0";
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Handle form submission
|
|
58
|
-
form.addEventListener("submit", (e) => {
|
|
59
|
-
e.preventDefault();
|
|
60
|
-
|
|
61
|
-
const newLinkText = linkTextInput.value;
|
|
62
|
-
const newHref = hrefInput.value;
|
|
63
|
-
|
|
64
|
-
if (!newHref || !newLinkText) {
|
|
65
|
-
if (!newHref) hrefInput.style.outlineColor = "red";
|
|
66
|
-
if (!newLinkText) linkTextInput.style.outlineColor = "red";
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const sanitizeURL = find(newHref)
|
|
71
|
-
.filter((link) => link.isLink)
|
|
72
|
-
.filter((link) => {
|
|
73
|
-
if (options.validate) {
|
|
74
|
-
return options.validate(link.value);
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
77
|
-
})
|
|
78
|
-
.at(0);
|
|
79
|
-
|
|
80
|
-
if (!sanitizeURL?.href) {
|
|
81
|
-
hrefInput.style.outlineColor = "red";
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
options.editor.chain().focus().extendMarkRange("hyperlink").editHyperlink({
|
|
86
|
-
newURL: newHref,
|
|
87
|
-
newText: newLinkText,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
options.tippy.hide();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Show tooltip
|
|
94
|
-
options.tippy.update(options.editor.view, {
|
|
95
|
-
arrow: roundArrow,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
setTimeout(() => linkTextInput.focus());
|
|
99
|
-
};
|
package/src/modals/icons.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
interface IconProps {
|
|
2
|
-
size?: number;
|
|
3
|
-
fill?: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// prettier-ignore
|
|
7
|
-
export const Copy = ({ size = 24, fill = 'rgb(104, 81, 255)' }: IconProps = {}) => {
|
|
8
|
-
return `
|
|
9
|
-
<svg fill=${fill} viewBox="0 0 24 24" width=${size} xmlns="http://www.w3.org/2000/svg">
|
|
10
|
-
<path d="m13 20a5.006 5.006 0 0 0 5-5v-8.757a3.972 3.972 0 0 0 -1.172-2.829l-2.242-2.242a3.972 3.972 0 0 0 -2.829-1.172h-4.757a5.006 5.006 0 0 0 -5 5v10a5.006 5.006 0 0 0 5 5zm-9-5v-10a3 3 0 0 1 3-3s4.919.014 5 .024v1.976a2 2 0 0 0 2 2h1.976c.01.081.024 9 .024 9a3 3 0 0 1 -3 3h-6a3 3 0 0 1 -3-3zm18-7v11a5.006 5.006 0 0 1 -5 5h-9a1 1 0 0 1 0-2h9a3 3 0 0 0 3-3v-11a1 1 0 0 1 2 0z"/>
|
|
11
|
-
</svg>
|
|
12
|
-
`
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const LinkSlash = ({
|
|
16
|
-
size = 24,
|
|
17
|
-
fill = "rgb(104, 81, 255)",
|
|
18
|
-
}: IconProps = {}) => {
|
|
19
|
-
return `
|
|
20
|
-
<svg fill=${fill} viewBox="0 0 24 24" width=${size} xmlns="http://www.w3.org/2000/svg">
|
|
21
|
-
<path d="M11.953,17.993c-1.557-.074-3.021-.722-4.121-1.823-.23-.23-.446-.482-.641-.748-.325-.446-.227-1.072,.22-1.397,.446-.325,1.071-.227,1.397,.219,.129,.178,.274,.349,.437,.511,.749,.75,1.744,1.19,2.802,1.24,.552,.026,.979,.495,.952,1.046-.025,.536-.468,.953-.998,.953h-.048ZM1.831,13.331C.65,14.511,0,16.081,0,17.75c0,1.669,.65,3.239,1.831,4.419,1.18,1.181,2.749,1.831,4.419,1.831s3.239-.65,4.419-1.831l1.058-1.058c.391-.391,.391-1.023,0-1.414s-1.023-.391-1.414,0l-1.058,1.058c-.803,.803-1.87,1.245-3.005,1.245s-2.202-.442-3.005-1.245-1.245-1.87-1.245-3.005,.442-2.203,1.245-3.005l3.037-3.038c.391-.391,.391-1.024,0-1.414-.391-.391-1.023-.391-1.414,0l-3.037,3.038ZM.293,1.707L22.293,23.707c.195,.195,.451,.293,.707,.293s.512-.098,.707-.293c.391-.391,.391-1.023,0-1.414l-6.581-6.581,5.043-5.043c1.181-1.18,1.831-2.75,1.831-4.419,0-1.669-.65-3.239-1.831-4.419C20.989,.65,19.42,0,17.75,0s-3.239,.65-4.419,1.831l-1.058,1.058c-.391,.391-.391,1.023,0,1.414s1.023,.391,1.414,0l1.058-1.058c.803-.803,1.87-1.245,3.005-1.245s2.202,.442,3.005,1.245,1.245,1.87,1.245,3.005-.442,2.203-1.245,3.005l-5.043,5.043-5.852-5.852c1.612-.808,3.575-.52,4.894,.798,.162,.162,.308,.333,.437,.511,.196,.269,.501,.411,.81,.411,.204,0,.41-.063,.588-.192,.446-.325,.545-.951,.22-1.397-.194-.266-.41-.518-.641-.748-2.095-2.095-5.297-2.408-7.76-.835L1.707,.293c-.391-.391-1.023-.391-1.414,0s-.391,1.023,0,1.414Z"/>
|
|
22
|
-
</svg>
|
|
23
|
-
`;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const Pencil = ({
|
|
27
|
-
size = 24,
|
|
28
|
-
fill = "rgb(104, 81, 255)",
|
|
29
|
-
}: IconProps = {}) => {
|
|
30
|
-
return `
|
|
31
|
-
<svg fill=${fill} viewBox="0 0 24 24" width=${size} xmlns="http://www.w3.org/2000/svg">
|
|
32
|
-
<path d="M22.853,1.148a3.626,3.626,0,0,0-5.124,0L1.465,17.412A4.968,4.968,0,0,0,0,20.947V23a1,1,0,0,0,1,1H3.053a4.966,4.966,0,0,0,3.535-1.464L22.853,6.271A3.626,3.626,0,0,0,22.853,1.148ZM5.174,21.122A3.022,3.022,0,0,1,3.053,22H2V20.947a2.98,2.98,0,0,1,.879-2.121L15.222,6.483l2.3,2.3ZM21.438,4.857,18.932,7.364l-2.3-2.295,2.507-2.507a1.623,1.623,0,1,1,2.295,2.3Z"/>
|
|
33
|
-
</svg>
|
|
34
|
-
`;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const AngleUp = ({
|
|
38
|
-
size = 32,
|
|
39
|
-
fill = "rgb(104, 81, 255)",
|
|
40
|
-
}: IconProps = {}) => {
|
|
41
|
-
return `
|
|
42
|
-
<svg fill=${fill} viewBox="0 0 24 24" width=${size} xmlns="http://www.w3.org/2000/svg">
|
|
43
|
-
<path d="M17.293,15.207,12,9.914,6.707,15.207,5.293,13.793,10.586,8.5a2,2,0,0,1,2.828,0l5.293,5.293Z"/>
|
|
44
|
-
</svg>
|
|
45
|
-
`;
|
|
46
|
-
};
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { Editor } from "@tiptap/core";
|
|
2
|
-
import { editHyperlinkHandler } from "./editeHyperlink";
|
|
3
|
-
import { Copy, LinkSlash, Pencil } from "./icons";
|
|
4
|
-
import Tooltip from "../helpers/tippyHelper";
|
|
5
|
-
|
|
6
|
-
export type THyperlinkPreviewModalOptions = {
|
|
7
|
-
editor: Editor;
|
|
8
|
-
validate?: (url: string) => boolean;
|
|
9
|
-
link: HTMLAnchorElement;
|
|
10
|
-
node?: any;
|
|
11
|
-
nodePos: number;
|
|
12
|
-
tippy: Tooltip;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function previewHyperlinkModal(options: THyperlinkPreviewModalOptions): HTMLElement {
|
|
16
|
-
const href = options.link.href;
|
|
17
|
-
|
|
18
|
-
const hyperlinkModal = document.createElement("div");
|
|
19
|
-
const removeButton = document.createElement("button");
|
|
20
|
-
const copyButton = document.createElement("button");
|
|
21
|
-
const editButton = document.createElement("button");
|
|
22
|
-
|
|
23
|
-
const newBubble = document.createElement("div");
|
|
24
|
-
newBubble.classList.add("hyperlink-preview-modal__metadata");
|
|
25
|
-
|
|
26
|
-
const hrefTitle = document.createElement("a");
|
|
27
|
-
hrefTitle.setAttribute("target", "_blank");
|
|
28
|
-
hrefTitle.setAttribute("rel", "noreferrer");
|
|
29
|
-
hrefTitle.setAttribute("href", href);
|
|
30
|
-
hrefTitle.innerText = href;
|
|
31
|
-
|
|
32
|
-
newBubble.append(hrefTitle);
|
|
33
|
-
|
|
34
|
-
fetch("/api/metadata", {
|
|
35
|
-
method: "POST",
|
|
36
|
-
headers: { "Content-Type": "application/json" },
|
|
37
|
-
body: JSON.stringify({ url: href }),
|
|
38
|
-
})
|
|
39
|
-
.then((response) => response.json())
|
|
40
|
-
.then((data) => {
|
|
41
|
-
// Create a new bubble with the title
|
|
42
|
-
hrefTitle.setAttribute("href", href);
|
|
43
|
-
|
|
44
|
-
hrefTitle.innerText = data.title || data["og:title"] || href;
|
|
45
|
-
newBubble.replaceChildren(hrefTitle);
|
|
46
|
-
|
|
47
|
-
// Create an image element if image exists in metadata
|
|
48
|
-
if (data.icon || data.image || data["og:image"]) {
|
|
49
|
-
const img = document.createElement("img");
|
|
50
|
-
img.src = data.icon || data.image || data["og:image"];
|
|
51
|
-
img.alt = data.title || data["og:title"] || "hyperlink image";
|
|
52
|
-
newBubble.appendChild(img);
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
.catch((error) => {
|
|
56
|
-
console.error("Error fetching metadata:", error);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
hyperlinkModal.classList.add("hyperlink-preview-modal");
|
|
60
|
-
|
|
61
|
-
removeButton.classList.add("hyperlink-preview-modal__remove-button");
|
|
62
|
-
removeButton.innerHTML = LinkSlash();
|
|
63
|
-
|
|
64
|
-
editButton.classList.add("hyperlink-preview-modal__edit-button");
|
|
65
|
-
editButton.innerHTML = Pencil();
|
|
66
|
-
|
|
67
|
-
copyButton.classList.add("hyperlink-preview-modal__copy-button");
|
|
68
|
-
copyButton.innerHTML = Copy();
|
|
69
|
-
|
|
70
|
-
removeButton.addEventListener("click", () => {
|
|
71
|
-
options.tippy.hide();
|
|
72
|
-
return options.editor.chain().focus().unsetHyperlink().run();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
editButton.addEventListener("click", () => editHyperlinkHandler({ ...options, hyperlinkModal }));
|
|
76
|
-
|
|
77
|
-
copyButton.addEventListener("click", () => {
|
|
78
|
-
options.tippy.hide();
|
|
79
|
-
navigator.clipboard.writeText(href);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
hyperlinkModal.append(newBubble, copyButton, editButton, removeButton);
|
|
83
|
-
|
|
84
|
-
return hyperlinkModal;
|
|
85
|
-
}
|