Skip to content

Commit 921786f

Browse files
committed
Add default protocol, basic validation
1 parent dca6d61 commit 921786f

File tree

3 files changed

+102
-76
lines changed

3 files changed

+102
-76
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import Link from "@tiptap/extension-link";
2+
3+
const LinkWithTitle = Link.extend({
4+
addAttributes() {
5+
return {
6+
...this.parent?.(),
7+
title: {
8+
default: null,
9+
renderHTML: (attributes) => {
10+
if (!attributes.href) {
11+
return {};
12+
}
13+
return {
14+
title: `Right-click to open ${attributes.href}`,
15+
};
16+
},
17+
},
18+
};
19+
},
20+
});
21+
22+
export const ConfiguredLink = LinkWithTitle.configure({
23+
openOnClick: false,
24+
autolink: true,
25+
defaultProtocol: "https",
26+
protocols: ["http", "https"],
27+
isAllowedUri: (url, ctx) => {
28+
try {
29+
// construct URL
30+
const parsedUrl = url.includes(":")
31+
? new URL(url)
32+
: new URL(`${ctx.defaultProtocol}://${url}`);
33+
34+
// use default validation
35+
if (!ctx.defaultValidate(parsedUrl.href)) {
36+
return false;
37+
}
38+
39+
// Placeholder for future disallowed domains if we want to add any
40+
// const disallowedProtocols = ["ftp", "file", "mailto"];
41+
// const protocol = parsedUrl.protocol.replace(":", "");
42+
43+
// if (disallowedProtocols.includes(protocol)) {
44+
// return false;
45+
// }
46+
47+
// // only allow protocols specified in ctx.protocols
48+
// const allowedProtocols = ctx.protocols.map((p) =>
49+
// typeof p === "string" ? p : p.scheme
50+
// );
51+
52+
// if (!allowedProtocols.includes(protocol)) {
53+
// return false;
54+
// }
55+
56+
// Placeholder for future disallowed domains if we want to add any
57+
// const disallowedDomains = [
58+
// "example-phishing.com",
59+
// "malicious-site.net",
60+
// ];
61+
// const domain = parsedUrl.hostname;
62+
63+
// if (disallowedDomains.includes(domain)) {
64+
// return false;
65+
// }
66+
67+
// all checks have passed
68+
return true;
69+
} catch {
70+
return false;
71+
}
72+
},
73+
shouldAutoLink: (url) => {
74+
try {
75+
// construct URL
76+
const parsedUrl = url.includes(":")
77+
? new URL(url)
78+
: new URL(`https://${url}`);
79+
80+
// only auto-link if the domain is not in the disallowed list
81+
const disallowedDomains = [
82+
"example-no-autolink.com",
83+
"another-no-autolink.com",
84+
];
85+
const domain = parsedUrl.hostname;
86+
87+
return !disallowedDomains.includes(domain);
88+
} catch {
89+
return false;
90+
}
91+
},
92+
});

src/components/ui/coaching-sessions/coaching-notes/extensions.tsx

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { createLowlight } from "lowlight";
1919
import { all } from "lowlight";
2020
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
2121
import { TiptapCollabProvider } from "@hocuspocus/provider";
22+
import { ConfiguredLink } from "./extended-link-extension";
2223
// Initialize lowlight with all languages
2324
const lowlight = createLowlight(all);
2425

@@ -44,77 +45,7 @@ export const Extensions = (
4445
Strike,
4546
Text,
4647
Underline,
47-
Link.configure({
48-
openOnClick: false,
49-
autolink: true,
50-
defaultProtocol: "https",
51-
protocols: ["http", "https"],
52-
isAllowedUri: (url, ctx) => {
53-
try {
54-
// construct URL
55-
const parsedUrl = url.includes(":")
56-
? new URL(url)
57-
: new URL(`${ctx.defaultProtocol}://${url}`);
58-
59-
// use default validation
60-
if (!ctx.defaultValidate(parsedUrl.href)) {
61-
return false;
62-
}
63-
64-
// Placeholder for future disallowed domains if we want to add any
65-
// const disallowedProtocols = ["ftp", "file", "mailto"];
66-
// const protocol = parsedUrl.protocol.replace(":", "");
67-
68-
// if (disallowedProtocols.includes(protocol)) {
69-
// return false;
70-
// }
71-
72-
// // only allow protocols specified in ctx.protocols
73-
// const allowedProtocols = ctx.protocols.map((p) =>
74-
// typeof p === "string" ? p : p.scheme
75-
// );
76-
77-
// if (!allowedProtocols.includes(protocol)) {
78-
// return false;
79-
// }
80-
81-
// Placeholder for future disallowed domains if we want to add any
82-
// const disallowedDomains = [
83-
// "example-phishing.com",
84-
// "malicious-site.net",
85-
// ];
86-
// const domain = parsedUrl.hostname;
87-
88-
// if (disallowedDomains.includes(domain)) {
89-
// return false;
90-
// }
91-
92-
// all checks have passed
93-
return true;
94-
} catch {
95-
return false;
96-
}
97-
},
98-
shouldAutoLink: (url) => {
99-
try {
100-
// construct URL
101-
const parsedUrl = url.includes(":")
102-
? new URL(url)
103-
: new URL(`https://${url}`);
104-
105-
// only auto-link if the domain is not in the disallowed list
106-
const disallowedDomains = [
107-
"example-no-autolink.com",
108-
"another-no-autolink.com",
109-
];
110-
const domain = parsedUrl.hostname;
111-
112-
return !disallowedDomains.includes(domain);
113-
} catch {
114-
return false;
115-
}
116-
},
117-
}),
48+
ConfiguredLink,
11849
Collaboration.configure({
11950
document: doc,
12051
}),

src/components/ui/coaching-sessions/coaching-notes/link-dialog.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,17 @@ export const LinkDialog = ({
4646

4747
// update link
4848
try {
49+
const linkWithProtocol = /^https?:\/\//.test(linkUrl)
50+
? linkUrl
51+
: `https://${linkUrl}`;
52+
53+
// basic validation for a real url
54+
const urlHref = new URL(linkWithProtocol).toString();
4955
editor
5056
.chain()
5157
.focus()
5258
.extendMarkRange("link")
53-
.setLink({ href: linkUrl })
54-
.updateAttributes("link", { title: linkUrl })
59+
.setLink({ href: urlHref })
5560
.run();
5661
setLinkUrl("");
5762
onOpenChange(false);
@@ -65,9 +70,7 @@ export const LinkDialog = ({
6570
<DialogContent>
6671
<DialogHeader>
6772
<DialogTitle>Insert Link</DialogTitle>
68-
<DialogDescription>
69-
Insert a url for a link in your document.
70-
</DialogDescription>
73+
<DialogDescription>Insert URL for your link</DialogDescription>
7174
</DialogHeader>
7275
<div className="grid grid-cols-6 gap-4 items-center">
7376
<Label htmlFor="url" className="text-right">

0 commit comments

Comments
 (0)