diff --git a/apps/frontend/src/app/global.scss b/apps/frontend/src/app/global.scss
index 2227ab94..fd801a0e 100644
--- a/apps/frontend/src/app/global.scss
+++ b/apps/frontend/src/app/global.scss
@@ -652,6 +652,11 @@ html[dir='rtl'] [dir='ltr'] {
}
}
+.tiptap {
+ a {
+ @apply underline;
+ }
+}
.tiptap {
:first-child {
margin-top: 0;
diff --git a/apps/frontend/src/components/new-launch/a.component.tsx b/apps/frontend/src/components/new-launch/a.component.tsx
new file mode 100644
index 00000000..10e6802f
--- /dev/null
+++ b/apps/frontend/src/components/new-launch/a.component.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import { FC, useCallback } from 'react';
+
+export const AComponent: FC<{
+ editor: any;
+ currentValue: string;
+}> = ({ editor }) => {
+ const mark = () => {
+ const previousUrl = editor?.getAttributes('link')?.href;
+ const url = window.prompt('URL', previousUrl);
+
+ // cancelled
+ if (url === null) {
+ return;
+ }
+
+ // empty
+ if (url === '') {
+ editor?.chain()?.focus()?.extendMarkRange('link')?.unsetLink()?.run();
+
+ return;
+ }
+
+ // update link
+ try {
+ editor?.chain()?.focus()?.extendMarkRange('link')?.setLink({ href: url })?.run();
+ } catch (e) {
+ }
+ editor?.commands?.focus();
+ };
+ return (
+
setEmojiPickerOpen(!emojiPickerOpen)}
@@ -703,6 +709,66 @@ export const OnlyEditor = forwardRef<
InterceptUnderlineShortcut,
BulletList,
ListItem,
+ Link.configure({
+ openOnClick: false,
+ autolink: true,
+ defaultProtocol: 'https',
+ protocols: ['http', 'https'],
+ isAllowedUri: (url, ctx) => {
+ try {
+ // construct URL
+ const parsedUrl = url.includes(':')
+ ? new URL(url)
+ : new URL(`${ctx.defaultProtocol}://${url}`);
+
+ // use default validation
+ if (!ctx.defaultValidate(parsedUrl.href)) {
+ return false;
+ }
+
+ // disallowed protocols
+ const disallowedProtocols = ['ftp', 'file', 'mailto'];
+ const protocol = parsedUrl.protocol.replace(':', '');
+
+ if (disallowedProtocols.includes(protocol)) {
+ return false;
+ }
+
+ // only allow protocols specified in ctx.protocols
+ const allowedProtocols = ctx.protocols.map((p) =>
+ typeof p === 'string' ? p : p.scheme
+ );
+
+ if (!allowedProtocols.includes(protocol)) {
+ return false;
+ }
+
+ // all checks have passed
+ return true;
+ } catch {
+ return false;
+ }
+ },
+ shouldAutoLink: (url) => {
+ try {
+ // construct URL
+ const parsedUrl = url.includes(':')
+ ? new URL(url)
+ : new URL(`https://${url}`);
+
+ // only auto-link if the domain is not in the disallowed list
+ const disallowedDomains = [
+ 'example-no-autolink.com',
+ 'another-no-autolink.com',
+ ];
+ const domain = parsedUrl.hostname;
+
+ return !disallowedDomains.includes(domain);
+ } catch {
+ return false;
+ }
+ },
+ }),
...(internal?.integration?.id
? [
Mention.configure({
diff --git a/libraries/helpers/src/utils/strip.html.validation.ts b/libraries/helpers/src/utils/strip.html.validation.ts
index b21fe255..cddd7346 100644
--- a/libraries/helpers/src/utils/strip.html.validation.ts
+++ b/libraries/helpers/src/utils/strip.html.validation.ts
@@ -178,7 +178,13 @@ export const stripHtmlValidation = (
})
.replace(/
([.\s\S]*?)<\/p>/g, (match, p1) => {
return `
${p1}
\n`;
- }),
+ })
+ .replace(
+ /
([.\s\S]*?)<\/a>/g,
+ (match, p1, p2) => {
+ return `[${p2}](${p1})`;
+ }
+ ),
convertMentionFunction
)
);
@@ -203,6 +209,12 @@ export const stripHtmlValidation = (
const processedHtml = convertMention(
convertToAscii(
html
+ .replace(
+ /([.\s\S]*?)<\/a>/g,
+ (match, p1, p2) => {
+ return `${p1}`;
+ }
+ )
.replace(//, '\n')
.replace(/<\/ul>\n/, '
')
.replace(/([.\s\S]*?)<\/li.*?>/gm, (match, p1) => {
diff --git a/package.json b/package.json
index 13cf704d..ee298420 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"@tiptap/extension-document": "^3.0.6",
"@tiptap/extension-heading": "^3.0.7",
"@tiptap/extension-history": "^3.0.7",
+ "@tiptap/extension-link": "^3.0.9",
"@tiptap/extension-list": "^3.0.7",
"@tiptap/extension-mention": "^3.0.7",
"@tiptap/extension-paragraph": "^3.0.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 790cf36d..03a22762 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -135,6 +135,9 @@ importers:
'@tiptap/extension-history':
specifier: ^3.0.7
version: 3.0.7(@tiptap/extensions@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
+ '@tiptap/extension-link':
+ specifier: ^3.0.9
+ version: 3.0.9(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list':
specifier: ^3.0.7
version: 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
@@ -5914,11 +5917,11 @@ packages:
peerDependencies:
'@tiptap/core': ^3.0.6
- '@tiptap/extension-link@3.0.6':
- resolution: {integrity: sha512-BQZNnXx52jncbWrS60PXCtL7kYewA9j4XuYj6U+V943mjmXE+pJ8KczF1YZRmbU7YRLRLrGOtMrSUC8ioJpq6Q==}
+ '@tiptap/extension-link@3.0.9':
+ resolution: {integrity: sha512-cOsG3vpct7/JuenxCePDj5dlaSUEe2eK/g/jlRixgW4Llx5DvG2yj8+gha4MHdCUp/MrUBR4M+NJk1dOOSKXGw==}
peerDependencies:
- '@tiptap/core': ^3.0.6
- '@tiptap/pm': ^3.0.6
+ '@tiptap/core': ^3.0.9
+ '@tiptap/pm': ^3.0.9
'@tiptap/extension-list-item@3.0.6':
resolution: {integrity: sha512-gu3WJ+7GhIi7gPQuaD59Si1oXjBJHKt9wndLKHjYgzlQZb8pfHvix7MqkdSrF/wY+5ScYm2bZToCZku2baoAJw==}
@@ -10949,8 +10952,8 @@ packages:
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
- linkifyjs@4.3.1:
- resolution: {integrity: sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg==}
+ linkifyjs@4.3.2:
+ resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
lit-element@4.2.0:
resolution: {integrity: sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==}
@@ -22346,11 +22349,11 @@ snapshots:
dependencies:
'@tiptap/core': 3.0.6(@tiptap/pm@3.0.6)
- '@tiptap/extension-link@3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)':
+ '@tiptap/extension-link@3.0.9(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)':
dependencies:
'@tiptap/core': 3.0.6(@tiptap/pm@3.0.6)
'@tiptap/pm': 3.0.6
- linkifyjs: 4.3.1
+ linkifyjs: 4.3.2
'@tiptap/extension-list-item@3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))':
dependencies:
@@ -22447,7 +22450,7 @@ snapshots:
'@tiptap/extension-heading': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
'@tiptap/extension-horizontal-rule': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-italic': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))
- '@tiptap/extension-link': 3.0.6(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
+ '@tiptap/extension-link': 3.0.9(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list': 3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6)
'@tiptap/extension-list-item': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
'@tiptap/extension-list-keymap': 3.0.6(@tiptap/extension-list@3.0.7(@tiptap/core@3.0.6(@tiptap/pm@3.0.6))(@tiptap/pm@3.0.6))
@@ -29153,7 +29156,7 @@ snapshots:
dependencies:
uc.micro: 2.1.0
- linkifyjs@4.3.1: {}
+ linkifyjs@4.3.2: {}
lit-element@4.2.0:
dependencies: