From 4ced8392e7f3274a52d1ae0826a4041d2c47eb05 Mon Sep 17 00:00:00 2001 From: Vladislav Mihov Date: Tue, 30 Jan 2024 19:25:44 +0200 Subject: [PATCH 1/3] feat(app/client): implement support for markdown links. --- app/client/lib/textUtils.ts | 52 ++++++++++++++++++++++++++++++++----- app/client/ui2018/links.ts | 4 +-- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/app/client/lib/textUtils.ts b/app/client/lib/textUtils.ts index 28a65c75..537c9a58 100644 --- a/app/client/lib/textUtils.ts +++ b/app/client/lib/textUtils.ts @@ -32,18 +32,56 @@ For 'Link (in http://www.uk?)' 'url-regex' [ 'http://www.uk?)' ] */ -// Match http or https then domain name (with optional port) then any text that ends with letter or number. -export const urlRegex = /(https?:\/\/[A-Za-z\d][A-Za-z\d-.]*(?!\.)(?::\d+)?(?:\/[^\s]*)?[\w\d/])/; + +// Match http or https then domain name or capture markdown link text and URL separately +const urlRegex = /(?:\[(.*?)\]\()?https?:\/\/[A-Za-z\d][A-Za-z\d-.]*\.[A-Za-z]{2,}(?::\d+)?(?:\/[^\s\)]*)?(?:\))?/; /** - * Detects URLs in a text and returns list of tokens { value, isLink } + * Detects URLs in a text and returns list of tokens { value, link, isLink } */ -export function findLinks(text: string): Array<{value: string, isLink: boolean}> { +export function findLinks(text: string): Array<{value: string, link: string, isLink: boolean}> { if (!text) { - return [{ value: text, isLink: false }]; + return [{ value: text, link: text, isLink: false }]; + } + + let tokens = []; + let lastIndex = 0; + text.replace(urlRegex, (match: string, markdownText: string, offset: number) => { + // Add text before the URL + if (offset > lastIndex) { + const currentValue = text.substring(lastIndex, offset) + tokens.push({ value: currentValue, link: currentValue, isLink: false }); + } + + // Extracting the actual URL and link text + let actualUrl, displayText; + if (markdownText) { + const markdownMatch = match.match(/\[(.*?)\]\((.*?)\)/); + if (markdownMatch && markdownMatch.length === 3) { + displayText = markdownMatch[1]; + actualUrl = markdownMatch[2]; + } + } else { + displayText = actualUrl = match; + } + + // Add the URL + tokens.push({ + value: displayText, + link: actualUrl, + isLink: true + }); + + lastIndex = offset + match.length; + }); + + // Add any remaining text after the last URL + if (lastIndex < text.length) { + const currentValue = text.substring(lastIndex) + tokens.push({ value: currentValue, link: currentValue, isLink: false }); } - // urls will be at odd-number indices - return text.split(urlRegex).map((value, i) => ({ value, isLink : (i % 2) === 1})); + + return tokens; } /** diff --git a/app/client/ui2018/links.ts b/app/client/ui2018/links.ts index e46bc21c..3a87bb18 100644 --- a/app/client/ui2018/links.ts +++ b/app/client/ui2018/links.ts @@ -58,11 +58,11 @@ export async function onClickHyperLink(ev: MouseEvent, url: CellValue) { export function makeLinks(text: string) { try { const domElements: DomArg[] = []; - for (const {value, isLink} of findLinks(text)) { + for (const {value, link, isLink} of findLinks(text)) { if (isLink) { // Wrap link with a span to provide hover on and to override wrapping. domElements.push(cssMaybeWrap( - gristLink(value, + gristLink(link, cssIconBackground( icon("FieldLink", testId('tb-link-icon')), dom.cls(cssHoverInText.className), From ab9881488b2d0f5c64bdca969301e8480cc21cb7 Mon Sep 17 00:00:00 2001 From: Vladislav Mihov Date: Tue, 30 Jan 2024 20:04:06 +0200 Subject: [PATCH 2/3] fix: fix lint ci errors. --- app/client/lib/textUtils.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/client/lib/textUtils.ts b/app/client/lib/textUtils.ts index 537c9a58..9c0f972e 100644 --- a/app/client/lib/textUtils.ts +++ b/app/client/lib/textUtils.ts @@ -32,9 +32,8 @@ For 'Link (in http://www.uk?)' 'url-regex' [ 'http://www.uk?)' ] */ - -// Match http or https then domain name or capture markdown link text and URL separately -const urlRegex = /(?:\[(.*?)\]\()?https?:\/\/[A-Za-z\d][A-Za-z\d-.]*\.[A-Za-z]{2,}(?::\d+)?(?:\/[^\s\)]*)?(?:\))?/; +// Match http or https then domain name and capture markdown link text and URL separately +const urlRegex = /(?:\[(.*?)\]\()?https?:\/\/[A-Za-z\d][A-Za-z\d-.]*\.[A-Za-z]{2,}(?::\d+)?(?:\/[^\s)]*)?(?:\))?/; /** * Detects URLs in a text and returns list of tokens { value, link, isLink } @@ -44,12 +43,12 @@ export function findLinks(text: string): Array<{value: string, link: string, is return [{ value: text, link: text, isLink: false }]; } - let tokens = []; + const tokens = []; let lastIndex = 0; text.replace(urlRegex, (match: string, markdownText: string, offset: number) => { // Add text before the URL if (offset > lastIndex) { - const currentValue = text.substring(lastIndex, offset) + const currentValue = text.substring(lastIndex, offset); tokens.push({ value: currentValue, link: currentValue, isLink: false }); } @@ -77,7 +76,7 @@ export function findLinks(text: string): Array<{value: string, link: string, is // Add any remaining text after the last URL if (lastIndex < text.length) { - const currentValue = text.substring(lastIndex) + const currentValue = text.substring(lastIndex); tokens.push({ value: currentValue, link: currentValue, isLink: false }); } From 1500147a58bf92848c3245931ef42bd7666d9101 Mon Sep 17 00:00:00 2001 From: Vladislav Mihov Date: Tue, 30 Jan 2024 20:12:07 +0200 Subject: [PATCH 3/3] fix: production build error. --- app/client/lib/textUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/client/lib/textUtils.ts b/app/client/lib/textUtils.ts index 9c0f972e..a63054bd 100644 --- a/app/client/lib/textUtils.ts +++ b/app/client/lib/textUtils.ts @@ -72,6 +72,7 @@ export function findLinks(text: string): Array<{value: string, link: string, is }); lastIndex = offset + match.length; + return match; }); // Add any remaining text after the last URL