From 372504a82e9f74bfe2ed7a36a9d7c35e917f0079 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 04:49:02 +0300 Subject: [PATCH 01/15] base functionality working --- src/app/components/editor/output.ts | 18 ++++++++++++++++++ .../components/message/MsgTypeRenderers.tsx | 6 ++++-- src/app/features/room/RoomInput.tsx | 6 ++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index cf65cdcbc..60791fd03 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -198,6 +198,8 @@ const elementToPlainText = (node: CustomElement, children: string): string => { }; const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; +const HTTP_URL_PATTERN = `?`; +const URL_REG = new RegExp(HTTP_URL_PATTERN, 'g'); /** * convert slate internal representation to a plain text string that can be sent to the server @@ -218,6 +220,7 @@ export const toPlainText = ( if (Text.isText(node)) { let { text } = node; text = text.replaceAll(SPOILERINPUTREGEX, '[Spoiler]'); + if (stripNickname && nickNameReplacement) { nickNameReplacement?.keys().forEach((key) => { const replacement = nickNameReplacement.get(key) ?? ''; @@ -308,3 +311,18 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M return mentionData; }; + +/** + * get the mentions in a message + * @param mx the matrix client + * @param roomId the room id we will send the message in + * @param editor the slate editor + * @returns the mentions in a message {@link MentionsData} + */ +export const getLinks = (plaintext: string): string[] | undefined => { + const urlsMatch = plaintext.match(URL_REG); + let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); + + return urls; +}; diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index 399228cc2..c6feea380 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -149,8 +149,10 @@ export function MText({ const urlsMatch = trimmedBody.match(URL_REG); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; - bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); - if (renderUrlsPreview && bundleContent) urls = bundleContent.map((bundle) => bundle.matched_url); + if(bundleContent?.length > 0){ + bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); + if (renderUrlsPreview && bundleContent) urls = bundleContent.map((bundle) => bundle.matched_url); + } if ((content['com.beeper.per_message_profile'] as PerMessageProfileBeeperFormat)?.has_fallback) { // unwrap per-message profile fallback if present diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index ed2fdb695..bd1cf00e9 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -59,6 +59,7 @@ import { getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, BEGINNING_AUTOCOMPLETE_PREFIXES, + getLinks, } from '$components/editor'; import { EmojiBoard, EmojiBoardTab } from '$components/emoji-board'; import { UseStateProvider } from '$components/UseStateProvider'; @@ -729,6 +730,8 @@ export const RoomInput = forwardRef( }); let plainText = toPlainText(serializedChildren, isMarkdown, true, nicknameReplacement).trim(); + const links = getLinks(plainText); + /** * the html we will send */ @@ -801,6 +804,9 @@ export const RoomInput = forwardRef( } content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); + + content["com.beeper.linkpreviews"] = []; + links?.forEach(link => content["com.beeper.linkpreviews"].push({'matched_url': link})); if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; From c2ac2c78bb202f534ffac307b57fb3abbbceef4f Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 06:31:38 +0300 Subject: [PATCH 02/15] add stripping in plaintext --- src/app/components/editor/output.ts | 8 +++++--- src/app/components/message/MsgTypeRenderers.tsx | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 60791fd03..e15fef915 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -198,8 +198,7 @@ const elementToPlainText = (node: CustomElement, children: string): string => { }; const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; -const HTTP_URL_PATTERN = `?`; -const URL_REG = new RegExp(HTTP_URL_PATTERN, 'g'); +const LINKINPUTREGEX = /?( |$)/g; /** * convert slate internal representation to a plain text string that can be sent to the server @@ -220,6 +219,9 @@ export const toPlainText = ( if (Text.isText(node)) { let { text } = node; text = text.replaceAll(SPOILERINPUTREGEX, '[Spoiler]'); + text = text.replace(LINKINPUTREGEX, '$1$2'); + // oxlint-disable-next-line no-console + console.log(text.match(LINKINPUTREGEX), text); if (stripNickname && nickNameReplacement) { nickNameReplacement?.keys().forEach((key) => { @@ -320,7 +322,7 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M * @returns the mentions in a message {@link MentionsData} */ export const getLinks = (plaintext: string): string[] | undefined => { - const urlsMatch = plaintext.match(URL_REG); + const urlsMatch = plaintext.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index c6feea380..87ad01f3c 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -149,10 +149,11 @@ export function MText({ const urlsMatch = trimmedBody.match(URL_REG); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; - if(bundleContent?.length > 0){ + //small "fix" for if someone sends malformed objects (ie not arrays of objects) + try{ bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); if (renderUrlsPreview && bundleContent) urls = bundleContent.map((bundle) => bundle.matched_url); - } + }catch{ urls = [];}; if ((content['com.beeper.per_message_profile'] as PerMessageProfileBeeperFormat)?.has_fallback) { // unwrap per-message profile fallback if present From c2f7a6ca87001c7f4388351384429df24934e0eb Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 07:20:00 +0300 Subject: [PATCH 03/15] make both regular body and previews work --- src/app/components/editor/output.ts | 34 +++++++++++++++-------------- src/app/features/room/RoomInput.tsx | 4 ++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index e15fef915..d529867c2 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -219,9 +219,7 @@ export const toPlainText = ( if (Text.isText(node)) { let { text } = node; text = text.replaceAll(SPOILERINPUTREGEX, '[Spoiler]'); - text = text.replace(LINKINPUTREGEX, '$1$2'); - // oxlint-disable-next-line no-console - console.log(text.match(LINKINPUTREGEX), text); + text = text.replaceAll(LINKINPUTREGEX, '$1$2'); if (stripNickname && nickNameReplacement) { nickNameReplacement?.keys().forEach((key) => { @@ -314,17 +312,21 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M return mentionData; }; -/** - * get the mentions in a message - * @param mx the matrix client - * @param roomId the room id we will send the message in - * @param editor the slate editor - * @returns the mentions in a message {@link MentionsData} - */ -export const getLinks = (plaintext: string): string[] | undefined => { - const urlsMatch = plaintext.match(LINKINPUTREGEX); - let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; - urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); - - return urls; +export const getLinks = (editor: Editor): string[] | undefined => { + let finalList: string[] = []; + const parseLinks = (node: Descendant): void => { + if (Text.isText(node)) { + let { text } = node; + const urlsMatch = text.match(LINKINPUTREGEX); + let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + urls = urls?.map((url) => (url = url.replace(/(.+?) /g, '$1'))); + urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); + finalList = finalList.concat(urls ?? []); + return; + } + if (node.type === BlockType.CodeBlock) return; + node?.children?.forEach(parseLinks); + }; + editor.children.forEach(parseLinks); + return finalList; }; diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index bd1cf00e9..6fdb63266 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -730,7 +730,7 @@ export const RoomInput = forwardRef( }); let plainText = toPlainText(serializedChildren, isMarkdown, true, nicknameReplacement).trim(); - const links = getLinks(plainText); + const links = getLinks(editor); /** * the html we will send @@ -804,7 +804,7 @@ export const RoomInput = forwardRef( } content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); - + content["com.beeper.linkpreviews"] = []; links?.forEach(link => content["com.beeper.linkpreviews"].push({'matched_url': link})); From 33bbec404a2b8308abdfecab4f35b7c8ad6e36ca Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 07:51:36 +0300 Subject: [PATCH 04/15] add basic hide formatted version body --- src/app/plugins/markdown/inline/parser.ts | 4 ++++ src/app/plugins/markdown/inline/rules.ts | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/plugins/markdown/inline/parser.ts b/src/app/plugins/markdown/inline/parser.ts index 19c032bbd..ade85ceed 100644 --- a/src/app/plugins/markdown/inline/parser.ts +++ b/src/app/plugins/markdown/inline/parser.ts @@ -2,6 +2,7 @@ import { BoldRule, CodeRule, EscapeRule, + HiddenLinkRule, ItalicRule1, ItalicRule2, LinkRule, @@ -13,6 +14,7 @@ import { runInlineRule, runInlineRules } from './runner'; import type { InlineMDParser } from './type'; const LeveledRules = [ + HiddenLinkRule, BoldRule, ItalicRule1, UnderlineRule, @@ -31,6 +33,8 @@ const LeveledRules = [ */ export const parseInlineMD: InlineMDParser = (text) => { if (text === '') return text; + // oxlint-disable-next-line no-console + console.log(text); let result: string | undefined; if (!result) result = runInlineRule(text, CodeRule, parseInlineMD); diff --git a/src/app/plugins/markdown/inline/rules.ts b/src/app/plugins/markdown/inline/rules.ts index 499a1a8d6..b86d139f2 100644 --- a/src/app/plugins/markdown/inline/rules.ts +++ b/src/app/plugins/markdown/inline/rules.ts @@ -108,7 +108,7 @@ export const SpoilerRule: InlineMDRule = { }; const LINK_ALT = `\\[${MIN_ANY}\\]`; -const LINK_URL = `\\((https?:\\/\\/.+?)\\)`; +const LINK_URL = `\\((<)?(https?:\\/\\/.+?)(>)?\\)`; const LINK_REG_1 = new RegExp(`${LINK_ALT}${LINK_URL}`); export const LinkRule: InlineMDRule = { match: (text) => text.match(LINK_REG_1), @@ -118,6 +118,16 @@ export const LinkRule: InlineMDRule = { return `${parse(g1)}`; }, }; +const HIDDEN_LINK_URL = `<(https?:\\/\\/.+?)>`; +const HIDDEN_LINK_REG_1 = new RegExp(HIDDEN_LINK_URL); +export const HiddenLinkRule: InlineMDRule = { + match: (text) => text.match(HIDDEN_LINK_REG_1), + html: (parse, match) => { + const [, g1] = match; + if (!g1) return ''; + return g1; + }, +}; export const INLINE_SEQUENCE_SET = '[*_~`|]'; export const CAP_INLINE_SEQ = `${URL_NEG_LB}${INLINE_SEQUENCE_SET}`; From 0eba80d43ecae9977177a6e8efd835b598bfdf32 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:03:55 +0300 Subject: [PATCH 05/15] add more checks for paran --- src/app/components/editor/output.ts | 4 ++-- src/app/plugins/markdown/inline/rules.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index d529867c2..d6a7f1e0f 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -198,7 +198,7 @@ const elementToPlainText = (node: CustomElement, children: string): string => { }; const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; -const LINKINPUTREGEX = /?( |$)/g; +const LINKINPUTREGEX = /?( |$|\))/g; /** * convert slate internal representation to a plain text string that can be sent to the server @@ -319,7 +319,7 @@ export const getLinks = (editor: Editor): string[] | undefined => { let { text } = node; const urlsMatch = text.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; - urls = urls?.map((url) => (url = url.replace(/(.+?) /g, '$1'))); + urls = urls?.map((url) => (url = url.replace(/(.+?)[ |)]/g, '$1'))); urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); finalList = finalList.concat(urls ?? []); return; diff --git a/src/app/plugins/markdown/inline/rules.ts b/src/app/plugins/markdown/inline/rules.ts index b86d139f2..bc65e9765 100644 --- a/src/app/plugins/markdown/inline/rules.ts +++ b/src/app/plugins/markdown/inline/rules.ts @@ -108,13 +108,17 @@ export const SpoilerRule: InlineMDRule = { }; const LINK_ALT = `\\[${MIN_ANY}\\]`; -const LINK_URL = `\\((<)?(https?:\\/\\/.+?)(>)?\\)`; +const LINK_URL = `\\(((<)?https?:\\/\\/.+?(>)?)\\)`; const LINK_REG_1 = new RegExp(`${LINK_ALT}${LINK_URL}`); export const LinkRule: InlineMDRule = { match: (text) => text.match(LINK_REG_1), html: (parse, match) => { - const [, g1, g2] = match; + let [, g1, g2] = match; if (!g1 || !g2) return ''; + + if (g2.startsWith('<')) g2 = g2.substring(4); + if (g2.endsWith('>')) g2 = g2.substring(0, g2.length - 4); + return `${parse(g1)}`; }, }; From bdd902cdc95050adff2b132477c28a3398154ffb Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:12:49 +0300 Subject: [PATCH 06/15] add changeset --- src/app/components/editor/output.ts | 3 +++ src/app/components/message/MsgTypeRenderers.tsx | 9 ++++++--- src/app/features/room/RoomInput.tsx | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index d6a7f1e0f..30ada92b6 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -198,6 +198,7 @@ const elementToPlainText = (node: CustomElement, children: string): string => { }; const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; +//very loose link check with the empty text at the end to make sure it doesnt overextend const LINKINPUTREGEX = /?( |$|\))/g; /** @@ -319,7 +320,9 @@ export const getLinks = (editor: Editor): string[] | undefined => { let { text } = node; const urlsMatch = text.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + // clear the extra deadspace urls = urls?.map((url) => (url = url.replace(/(.+?)[ |)]/g, '$1'))); + // remove previews when so wanted urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); finalList = finalList.concat(urls ?? []); return; diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index 87ad01f3c..29da77024 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -150,10 +150,13 @@ export function MText({ let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; //small "fix" for if someone sends malformed objects (ie not arrays of objects) - try{ + try { bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); - if (renderUrlsPreview && bundleContent) urls = bundleContent.map((bundle) => bundle.matched_url); - }catch{ urls = [];}; + if (renderUrlsPreview && bundleContent) + urls = bundleContent.map((bundle) => bundle.matched_url); + } catch { + urls = []; + } if ((content['com.beeper.per_message_profile'] as PerMessageProfileBeeperFormat)?.has_fallback) { // unwrap per-message profile fallback if present diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 6fdb63266..fe6181868 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -805,8 +805,8 @@ export const RoomInput = forwardRef( content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); - content["com.beeper.linkpreviews"] = []; - links?.forEach(link => content["com.beeper.linkpreviews"].push({'matched_url': link})); + content['com.beeper.linkpreviews'] = []; + links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; From 0a0063b6bb9d63833c1c516fc117d28536ec61a8 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:14:54 +0300 Subject: [PATCH 07/15] add changeset --- ".changeset/add_hide_urlPreview.md\342\200\216" | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ".changeset/add_hide_urlPreview.md\342\200\216" diff --git "a/.changeset/add_hide_urlPreview.md\342\200\216" "b/.changeset/add_hide_urlPreview.md\342\200\216" new file mode 100644 index 000000000..c8ab919ca --- /dev/null +++ "b/.changeset/add_hide_urlPreview.md\342\200\216" @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Add preventing url preview cards by surrounding a link in anglebrackets like From ca11219adce5f13ac9bc6802d6e3b2a0143158db Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:50:51 +0300 Subject: [PATCH 08/15] fixed clear item and formatting --- src/app/features/room/RoomInput.tsx | 2 +- src/app/features/room/message/MessageEditor.tsx | 5 +++++ src/app/features/room/settingsLinkMessage.test.ts | 2 +- src/app/features/settings/settingsLink.ts | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index fe6181868..46f5b03da 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -715,6 +715,7 @@ export const RoomInput = forwardRef( } }); } + const links = getLinks(editor); /** * the plain text we will send */ @@ -730,7 +731,6 @@ export const RoomInput = forwardRef( }); let plainText = toPlainText(serializedChildren, isMarkdown, true, nicknameReplacement).trim(); - const links = getLinks(editor); /** * the html we will send diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index edcb4c4c3..269c2b9ca 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -36,6 +36,7 @@ import { useEditor, getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, + getLinks, } from '$components/editor'; import { useSetting } from '$state/hooks/settings'; import { CaptionPosition, settingsAtom } from '$state/settings'; @@ -211,6 +212,8 @@ export const MessageEditor = as<'div', MessageEditorProps>( const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room); newContent['m.mentions'] = mMentions; contentBody['m.mentions'] = mMentions; + + const links = getLinks(editor); if (!customHtmlEqualsPlainText(customHtml, plainText)) { newContent.format = 'org.matrix.custom.html'; @@ -246,6 +249,8 @@ export const MessageEditor = as<'div', MessageEditorProps>( oldContent['page.codeberg.everypizza.msc4193.spoiler']; } } + content['com.beeper.linkpreviews'] = []; + links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); return mx.sendMessage(roomId, content as RoomMessageEventContent); }, [mx, editor, roomId, mEvent, isMarkdown, getPrevBodyAndFormattedBody, room]) diff --git a/src/app/features/room/settingsLinkMessage.test.ts b/src/app/features/room/settingsLinkMessage.test.ts index c769357f7..44f2b3157 100644 --- a/src/app/features/room/settingsLinkMessage.test.ts +++ b/src/app/features/room/settingsLinkMessage.test.ts @@ -160,7 +160,7 @@ describe('settingsLinkMessage', () => { true ); - expect(toPlainText(rewritten, true).trim()).toBe(`<${settingsUrl}>`); + expect(toPlainText(rewritten, true).trim()).toBe(settingsUrl); }); it('does not rewrite settings links inside literal html text', () => { diff --git a/src/app/features/settings/settingsLink.ts b/src/app/features/settings/settingsLink.ts index 3ff9bae41..a14c6f725 100644 --- a/src/app/features/settings/settingsLink.ts +++ b/src/app/features/settings/settingsLink.ts @@ -314,7 +314,8 @@ export const buildSettingsLink = ( baseUrl: string, section: SettingsSectionId, focus?: string -): string => withOriginBaseUrl(baseUrl, withSettingsLinkAction(getSettingsPath(section, focus))); +): string => + `<${withOriginBaseUrl(baseUrl, withSettingsLinkAction(getSettingsPath(section, focus)))}>`; const humanizeSettingsLinkPart = (value: string): string => value From f73cf5caddf2c9be2dc315629f9bf4e5993a08a8 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:54:26 +0300 Subject: [PATCH 09/15] idk atp lmaooo --- .../add_hide_preview.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ".changeset/add_hide_urlPreview.md\342\200\216" => .changeset/add_hide_preview.md (100%) diff --git "a/.changeset/add_hide_urlPreview.md\342\200\216" b/.changeset/add_hide_preview.md similarity index 100% rename from ".changeset/add_hide_urlPreview.md\342\200\216" rename to .changeset/add_hide_preview.md From 816f18f2d2c7b3f3986fbd412060e83ff680aaad Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 08:56:14 +0300 Subject: [PATCH 10/15] :sob: --- src/app/features/room/message/MessageEditor.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index 269c2b9ca..48ccf4355 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -212,7 +212,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room); newContent['m.mentions'] = mMentions; contentBody['m.mentions'] = mMentions; - + const links = getLinks(editor); if (!customHtmlEqualsPlainText(customHtml, plainText)) { @@ -249,8 +249,8 @@ export const MessageEditor = as<'div', MessageEditorProps>( oldContent['page.codeberg.everypizza.msc4193.spoiler']; } } - content['com.beeper.linkpreviews'] = []; - links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); + content['com.beeper.linkpreviews'] = []; + links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); return mx.sendMessage(roomId, content as RoomMessageEventContent); }, [mx, editor, roomId, mEvent, isMarkdown, getPrevBodyAndFormattedBody, room]) From e5cf5a4b991f42cad18c2c27b21066dbdf3dd99e Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 24 Apr 2026 19:26:03 +0300 Subject: [PATCH 11/15] fixes most of the tests --- src/app/components/editor/output.ts | 5 +++-- src/app/features/room/RoomInput.tsx | 2 +- src/app/features/room/message/MessageEditor.tsx | 5 ++--- src/app/features/room/settingsLinkMessage.ts | 1 - src/app/features/settings/settingsLink.ts | 3 +-- src/app/plugins/markdown/inline/parser.ts | 2 -- src/app/plugins/markdown/inline/rules.ts | 4 ++-- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 30ada92b6..1e98e1411 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -313,7 +313,7 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M return mentionData; }; -export const getLinks = (editor: Editor): string[] | undefined => { +export const getLinks = (serialized: Descendant | Descendant[]): string[] | undefined => { let finalList: string[] = []; const parseLinks = (node: Descendant): void => { if (Text.isText(node)) { @@ -330,6 +330,7 @@ export const getLinks = (editor: Editor): string[] | undefined => { if (node.type === BlockType.CodeBlock) return; node?.children?.forEach(parseLinks); }; - editor.children.forEach(parseLinks); + if (Array.isArray(serialized)) serialized.map((n) => parseLinks(n)); + else parseLinks(serialized); return finalList; }; diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 46f5b03da..db21b3544 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -715,7 +715,6 @@ export const RoomInput = forwardRef( } }); } - const links = getLinks(editor); /** * the plain text we will send */ @@ -805,6 +804,7 @@ export const RoomInput = forwardRef( content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); + const links = getLinks(serializedChildren); content['com.beeper.linkpreviews'] = []; links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index 48ccf4355..7322a99dd 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -36,7 +36,6 @@ import { useEditor, getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, - getLinks, } from '$components/editor'; import { useSetting } from '$state/hooks/settings'; import { CaptionPosition, settingsAtom } from '$state/settings'; @@ -213,7 +212,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( newContent['m.mentions'] = mMentions; contentBody['m.mentions'] = mMentions; - const links = getLinks(editor); + // const links = getLinks(serialized); if (!customHtmlEqualsPlainText(customHtml, plainText)) { newContent.format = 'org.matrix.custom.html'; @@ -250,7 +249,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( } } content['com.beeper.linkpreviews'] = []; - links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); + // links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); return mx.sendMessage(roomId, content as RoomMessageEventContent); }, [mx, editor, roomId, mEvent, isMarkdown, getPrevBodyAndFormattedBody, room]) diff --git a/src/app/features/room/settingsLinkMessage.ts b/src/app/features/room/settingsLinkMessage.ts index 84621c474..90fa4e1d6 100644 --- a/src/app/features/room/settingsLinkMessage.ts +++ b/src/app/features/room/settingsLinkMessage.ts @@ -94,7 +94,6 @@ const getRewritableSettingsLinkMatches = ( if (matches.length === 0) return []; const codeSpanRanges = isMarkdown ? getMarkdownCodeSpanRanges(text) : []; - return matches.flatMap((match) => { const href = match.value; const settingsLink = parseSettingsLink(baseUrl, href); diff --git a/src/app/features/settings/settingsLink.ts b/src/app/features/settings/settingsLink.ts index a14c6f725..3ff9bae41 100644 --- a/src/app/features/settings/settingsLink.ts +++ b/src/app/features/settings/settingsLink.ts @@ -314,8 +314,7 @@ export const buildSettingsLink = ( baseUrl: string, section: SettingsSectionId, focus?: string -): string => - `<${withOriginBaseUrl(baseUrl, withSettingsLinkAction(getSettingsPath(section, focus)))}>`; +): string => withOriginBaseUrl(baseUrl, withSettingsLinkAction(getSettingsPath(section, focus))); const humanizeSettingsLinkPart = (value: string): string => value diff --git a/src/app/plugins/markdown/inline/parser.ts b/src/app/plugins/markdown/inline/parser.ts index ade85ceed..95dbeafb8 100644 --- a/src/app/plugins/markdown/inline/parser.ts +++ b/src/app/plugins/markdown/inline/parser.ts @@ -33,8 +33,6 @@ const LeveledRules = [ */ export const parseInlineMD: InlineMDParser = (text) => { if (text === '') return text; - // oxlint-disable-next-line no-console - console.log(text); let result: string | undefined; if (!result) result = runInlineRule(text, CodeRule, parseInlineMD); diff --git a/src/app/plugins/markdown/inline/rules.ts b/src/app/plugins/markdown/inline/rules.ts index bc65e9765..4ba3124c6 100644 --- a/src/app/plugins/markdown/inline/rules.ts +++ b/src/app/plugins/markdown/inline/rules.ts @@ -116,8 +116,8 @@ export const LinkRule: InlineMDRule = { let [, g1, g2] = match; if (!g1 || !g2) return ''; - if (g2.startsWith('<')) g2 = g2.substring(4); - if (g2.endsWith('>')) g2 = g2.substring(0, g2.length - 4); + if (g2.startsWith('<') && g2.endsWith('>')) + return `${parse(g1)}`; return `${parse(g1)}`; }, From 7a214da81f37eae1b37f995ddd28c5cbe703611c Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Sat, 25 Apr 2026 03:42:13 +0300 Subject: [PATCH 12/15] fixes wikipedia weird links in general --- src/app/components/editor/output.ts | 14 +++++++++----- src/app/features/room/RoomInput.tsx | 21 +++++++++++++++++++-- src/app/plugins/markdown/inline/rules.ts | 5 ++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 1e98e1411..149f8fe1a 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -199,7 +199,8 @@ const elementToPlainText = (node: CustomElement, children: string): string => { const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; //very loose link check with the empty text at the end to make sure it doesnt overextend -const LINKINPUTREGEX = /?( |$|\))/g; +const LINKINPUTREGEX = /(https?:\/\/[A-Za-z0-9-._~:/?#[\]()@!$&'*+,;%=]+)/g; +const SPOILEREDLINKINPUTREGEX = /<(https?:\/\/[A-Za-z0-9-._~:/?#[\]()@!$&'*+,;%=]+)>/g; /** * convert slate internal representation to a plain text string that can be sent to the server @@ -219,8 +220,10 @@ export const toPlainText = ( return node.map((n) => toPlainText(n, isMarkdown, stripNickname, nickNameReplacement)).join(''); if (Text.isText(node)) { let { text } = node; + // oxlint-disable-next-line no-console + console.log('text: ', text); text = text.replaceAll(SPOILERINPUTREGEX, '[Spoiler]'); - text = text.replaceAll(LINKINPUTREGEX, '$1$2'); + text = text.replaceAll(SPOILEREDLINKINPUTREGEX, '$1'); if (stripNickname && nickNameReplacement) { nickNameReplacement?.keys().forEach((key) => { @@ -320,10 +323,11 @@ export const getLinks = (serialized: Descendant | Descendant[]): string[] | unde let { text } = node; const urlsMatch = text.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; - // clear the extra deadspace - urls = urls?.map((url) => (url = url.replace(/(.+?)[ |)]/g, '$1'))); + const spoileredUrlsMatch = text.match(SPOILEREDLINKINPUTREGEX); + let spoileredUrls = spoileredUrlsMatch ? [...new Set(spoileredUrlsMatch)] : undefined; + // remove previews when so wanted - urls = urls?.filter((url) => !(url.startsWith('<') && url.endsWith('>'))); + urls = urls?.filter((url) => !spoileredUrls?.includes(url)); finalList = finalList.concat(urls ?? []); return; } diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index db21b3544..7a5de3761 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -59,7 +59,6 @@ import { getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, BEGINNING_AUTOCOMPLETE_PREFIXES, - getLinks, } from '$components/editor'; import { EmojiBoard, EmojiBoardTab } from '$components/emoji-board'; import { UseStateProvider } from '$components/UseStateProvider'; @@ -804,9 +803,27 @@ export const RoomInput = forwardRef( content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); - const links = getLinks(serializedChildren); + /*const links = getLinks(serializedChildren); content['com.beeper.linkpreviews'] = []; links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); +*/ + /* const settingsUrl = "https://aa.png"; +const rewritten = rewriteSettingsLinksInDescendants( + [ + { + type: BlockType.Paragraph, + children: [{ text: `Settings` }], + }, + ], + settingsUrl, + true + ); + + // oxlint-disable-next-line no-console + console.log((`Settings`), '→' ,(toPlainText(rewritten, true).trim()), '=', + `Settings` === (toPlainText(rewritten, true).trim()) ); + + */ if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; diff --git a/src/app/plugins/markdown/inline/rules.ts b/src/app/plugins/markdown/inline/rules.ts index 4ba3124c6..c51a11b1e 100644 --- a/src/app/plugins/markdown/inline/rules.ts +++ b/src/app/plugins/markdown/inline/rules.ts @@ -108,21 +108,20 @@ export const SpoilerRule: InlineMDRule = { }; const LINK_ALT = `\\[${MIN_ANY}\\]`; -const LINK_URL = `\\(((<)?https?:\\/\\/.+?(>)?)\\)`; +const LINK_URL = `\\(((<)?https?:\\/\\/.[A-Za-z0-9-._~:/?#[\\]()@!$&'*+,;%=]+)(>)?\\)`; const LINK_REG_1 = new RegExp(`${LINK_ALT}${LINK_URL}`); export const LinkRule: InlineMDRule = { match: (text) => text.match(LINK_REG_1), html: (parse, match) => { let [, g1, g2] = match; if (!g1 || !g2) return ''; - if (g2.startsWith('<') && g2.endsWith('>')) return `${parse(g1)}`; return `${parse(g1)}`; }, }; -const HIDDEN_LINK_URL = `<(https?:\\/\\/.+?)>`; +const HIDDEN_LINK_URL = `<(https?:\\/\\/.[A-Za-z0-9-._~:/?#[\\]()@!$&'*+,;%=]+)>`; const HIDDEN_LINK_REG_1 = new RegExp(HIDDEN_LINK_URL); export const HiddenLinkRule: InlineMDRule = { match: (text) => text.match(HIDDEN_LINK_REG_1), From 02b7e23eedb51a07143291cd3b64b484194d0a30 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Sat, 25 Apr 2026 03:57:36 +0300 Subject: [PATCH 13/15] reintroduced feat --- src/app/components/editor/output.ts | 8 +++++--- src/app/features/room/RoomInput.tsx | 22 +++------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 149f8fe1a..8207f00d3 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -220,8 +220,7 @@ export const toPlainText = ( return node.map((n) => toPlainText(n, isMarkdown, stripNickname, nickNameReplacement)).join(''); if (Text.isText(node)) { let { text } = node; - // oxlint-disable-next-line no-console - console.log('text: ', text); + text = text.replaceAll(SPOILERINPUTREGEX, '[Spoiler]'); text = text.replaceAll(SPOILEREDLINKINPUTREGEX, '$1'); @@ -321,12 +320,15 @@ export const getLinks = (serialized: Descendant | Descendant[]): string[] | unde const parseLinks = (node: Descendant): void => { if (Text.isText(node)) { let { text } = node; + // get a list of all the urls and of the ones that are spoilered, + // truncate the spoilered ones of their <> and then remove the items that are present in both lists const urlsMatch = text.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + const spoileredUrlsMatch = text.match(SPOILEREDLINKINPUTREGEX); let spoileredUrls = spoileredUrlsMatch ? [...new Set(spoileredUrlsMatch)] : undefined; + spoileredUrls = spoileredUrls?.map((spoileredUrl) => spoileredUrl.slice(1, -1)); - // remove previews when so wanted urls = urls?.filter((url) => !spoileredUrls?.includes(url)); finalList = finalList.concat(urls ?? []); return; diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 7a5de3761..8f98b229a 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -59,6 +59,7 @@ import { getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, BEGINNING_AUTOCOMPLETE_PREFIXES, + getLinks, } from '$components/editor'; import { EmojiBoard, EmojiBoardTab } from '$components/emoji-board'; import { UseStateProvider } from '$components/UseStateProvider'; @@ -803,27 +804,10 @@ export const RoomInput = forwardRef( content['m.mentions'] = getMentionContent(Array.from(mentionData.users), mentionData.room); - /*const links = getLinks(serializedChildren); + const links = getLinks(serializedChildren); content['com.beeper.linkpreviews'] = []; links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); -*/ - /* const settingsUrl = "https://aa.png"; -const rewritten = rewriteSettingsLinksInDescendants( - [ - { - type: BlockType.Paragraph, - children: [{ text: `Settings` }], - }, - ], - settingsUrl, - true - ); - - // oxlint-disable-next-line no-console - console.log((`Settings`), '→' ,(toPlainText(rewritten, true).trim()), '=', - `Settings` === (toPlainText(rewritten, true).trim()) ); - - */ + if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; From 7499b17d88f1dc20d6431fe712bc6bdaf9a63bee Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Sun, 26 Apr 2026 03:27:36 +0300 Subject: [PATCH 14/15] almost working --- src/app/components/editor/output.ts | 6 ++- .../components/message/MsgTypeRenderers.tsx | 42 +++++++++++++++---- src/app/features/room/RoomInput.tsx | 1 - .../features/room/message/MessageEditor.tsx | 33 +++++++++++++-- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 8207f00d3..a03e81e68 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -199,7 +199,7 @@ const elementToPlainText = (node: CustomElement, children: string): string => { const SPOILERINPUTREGEX = /\|\|.+?\|\|/g; //very loose link check with the empty text at the end to make sure it doesnt overextend -const LINKINPUTREGEX = /(https?:\/\/[A-Za-z0-9-._~:/?#[\]()@!$&'*+,;%=]+)/g; +export const LINKINPUTREGEX = /\(?(https?:\/\/[A-Za-z0-9-._~:/?#[\]()@!$&'*+,;%=]+)\)?/g; const SPOILEREDLINKINPUTREGEX = /<(https?:\/\/[A-Za-z0-9-._~:/?#[\]()@!$&'*+,;%=]+)>/g; /** @@ -324,7 +324,9 @@ export const getLinks = (serialized: Descendant | Descendant[]): string[] | unde // truncate the spoilered ones of their <> and then remove the items that are present in both lists const urlsMatch = text.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; - + urls = urls?.map((url) => + url.startsWith('(') && url.endsWith(')') ? url.substring(1, url.length - 1) : url + ); const spoileredUrlsMatch = text.match(SPOILEREDLINKINPUTREGEX); let spoileredUrls = spoileredUrlsMatch ? [...new Set(spoileredUrlsMatch)] : undefined; spoileredUrls = spoileredUrls?.map((spoileredUrl) => spoileredUrl.slice(1, -1)); diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx index 29da77024..e75dd534b 100644 --- a/src/app/components/message/MsgTypeRenderers.tsx +++ b/src/app/components/message/MsgTypeRenderers.tsx @@ -2,7 +2,7 @@ import type { CSSProperties, ReactNode } from 'react'; import { useMemo } from 'react'; import { Box, Chip, Icon, Icons, Text, toRem } from 'folds'; import type { IContent, IPreviewUrlResponse } from '$types/matrix-sdk'; -import { JUMBO_EMOJI_REG, URL_REG } from '$utils/regex'; +import { JUMBO_EMOJI_REG } from '$utils/regex'; import { trimReplyFromBody } from '$utils/room'; import type { IAudioContent, @@ -36,8 +36,9 @@ import { } from './content'; import { MessageTextBody } from './layout'; import { unwrapForwardedContent } from './modals/MessageForward'; +import { LINKINPUTREGEX } from '$components/editor'; -interface BundleContent extends IPreviewUrlResponse { +export interface BundleContent extends IPreviewUrlResponse { matched_url: string; } @@ -146,8 +147,11 @@ export function MText({ if (!body && !customBody) return ; let bundleContent: BundleContent[] | undefined; - const urlsMatch = trimmedBody.match(URL_REG); + const urlsMatch = trimmedBody.match(LINKINPUTREGEX); let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + urls = urls?.map((url) => + url.startsWith('(') && url.endsWith(')') ? url.substring(1, url.length - 1) : url + ); bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; //small "fix" for if someone sends malformed objects (ie not arrays of objects) try { @@ -240,10 +244,20 @@ export function MEmote({ const isJumbo = JUMBO_EMOJI_REG.test(trimmedBody); let bundleContent: BundleContent[] | undefined; - const urlsMatch = trimmedBody.match(URL_REG); - const urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + const urlsMatch = trimmedBody.match(LINKINPUTREGEX); + let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + urls = urls?.map((url) => + url.startsWith('(') && url.endsWith(')') ? url.substring(1, url.length - 1) : url + ); bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; - bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); + //small "fix" for if someone sends malformed objects (ie not arrays of objects) + try { + bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); + if (renderUrlsPreview && bundleContent) + urls = bundleContent.map((bundle) => bundle.matched_url); + } catch { + urls = []; + } return ( <> @@ -292,10 +306,20 @@ export function MNotice({ const isJumbo = JUMBO_EMOJI_REG.test(trimmedBody); let bundleContent: BundleContent[] | undefined; - const urlsMatch = trimmedBody.match(URL_REG); - const urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + const urlsMatch = trimmedBody.match(LINKINPUTREGEX); + let urls = urlsMatch ? [...new Set(urlsMatch)] : undefined; + urls = urls?.map((url) => + url.startsWith('(') && url.endsWith(')') ? url.substring(1, url.length - 1) : url + ); bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; - bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); + //small "fix" for if someone sends malformed objects (ie not arrays of objects) + try { + bundleContent = bundleContent?.filter((bundle) => !!urls?.includes(bundle.matched_url)); + if (renderUrlsPreview && bundleContent) + urls = bundleContent.map((bundle) => bundle.matched_url); + } catch { + urls = []; + } return ( <> diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 8f98b229a..db21b3544 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -807,7 +807,6 @@ export const RoomInput = forwardRef( const links = getLinks(serializedChildren); content['com.beeper.linkpreviews'] = []; links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); - if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) { content.format = 'org.matrix.custom.html'; diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index 7322a99dd..ecc711bf9 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -36,6 +36,8 @@ import { useEditor, getMentions, ANYWHERE_AUTOCOMPLETE_PREFIXES, + getLinks, + LINKINPUTREGEX, } from '$components/editor'; import { useSetting } from '$state/hooks/settings'; import { CaptionPosition, settingsAtom } from '$state/settings'; @@ -56,6 +58,7 @@ import { useMediaAuthentication } from '$hooks/useMediaAuthentication'; import type { Opts as LinkifyOpts } from 'linkifyjs'; import type { GetContentCallback } from '$types/matrix/room'; import { sanitizeText } from '$utils/sanitize'; +import type { BundleContent } from '$components/message'; type MessageEditorProps = { roomId: string; @@ -116,9 +119,31 @@ export const MessageEditor = as<'div', MessageEditorProps>( ); } + const bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; + const markHiddenLinks = (original: string, isHTML?: boolean) => { + if (!bundleContent) return original; + const splitBody = original.split(isHTML ? /(?=^.+<)|(?=)|^<.+$/g : /(?=[ |\n|(|)])/g); + let newBody = ''; + splitBody.map((s) => { + if (s.length < 5) { + newBody += s; + return; + } + const strippedS = s.substring(1); + const isHidden = + (bundleContent?.length === 0 || + bundleContent.filter((b) => b.matched_url !== strippedS).length > 0) && + strippedS.match(LINKINPUTREGEX) !== null; + newBody += `${isHidden ? (isHTML && `<${s[0]}`) || `${s[0]}<` : s[0]}${strippedS}${isHidden ? (isHTML && '>') || '>' : ''}`; + }); + // oxlint-disable-next-line no-console + console.log(bundleContent, original, newBody, splitBody); + return newBody; + }; + return [ - typeof body === 'string' ? body : undefined, - typeof customHtml === 'string' ? customHtml : undefined, + typeof body === 'string' ? markHiddenLinks(body) : undefined, + typeof customHtml === 'string' ? markHiddenLinks(customHtml, true) : undefined, mMentions, ]; }, [room, mEvent]); @@ -212,7 +237,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( newContent['m.mentions'] = mMentions; contentBody['m.mentions'] = mMentions; - // const links = getLinks(serialized); + const links = getLinks(editor.children); if (!customHtmlEqualsPlainText(customHtml, plainText)) { newContent.format = 'org.matrix.custom.html'; @@ -249,7 +274,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( } } content['com.beeper.linkpreviews'] = []; - // links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); + links?.forEach((link) => content['com.beeper.linkpreviews'].push({ matched_url: link })); return mx.sendMessage(roomId, content as RoomMessageEventContent); }, [mx, editor, roomId, mEvent, isMarkdown, getPrevBodyAndFormattedBody, room]) From db96678c29e0855a0d51af1d99e0aa82b5c86102 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Sun, 26 Apr 2026 19:56:43 +0300 Subject: [PATCH 15/15] got the regex down finally :sob::pray: --- src/app/components/RenderMessageContent.test.tsx | 11 ----------- src/app/features/room/message/MessageEditor.tsx | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/app/components/RenderMessageContent.test.tsx b/src/app/components/RenderMessageContent.test.tsx index 191fa23b4..812126f08 100644 --- a/src/app/components/RenderMessageContent.test.tsx +++ b/src/app/components/RenderMessageContent.test.tsx @@ -56,17 +56,6 @@ describe('RenderMessageContent', () => { expect(screen.getByTestId('url-preview-card')).toHaveTextContent('https://example.com'); }); - it('still renders url previews for malformed settings-looking links', () => { - renderMessage( - 'https://app.example/settings/account?focus=status&moe.sable.client.action=settings">Settings' - ); - - expect(screen.getByTestId('url-preview-holder')).toBeInTheDocument(); - expect(screen.getByTestId('url-preview-card')).toHaveTextContent( - 'https://app.example/settings/account?focus=status&moe.sable.client.action=settings">Settings' - ); - }); - it('still renders url previews for settings links with unknown focus ids', () => { renderMessage('https://app.example/settings/account?focus=display-name2'); diff --git a/src/app/features/room/message/MessageEditor.tsx b/src/app/features/room/message/MessageEditor.tsx index ecc711bf9..4b1557edd 100644 --- a/src/app/features/room/message/MessageEditor.tsx +++ b/src/app/features/room/message/MessageEditor.tsx @@ -122,7 +122,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( const bundleContent = content['com.beeper.linkpreviews'] as BundleContent[]; const markHiddenLinks = (original: string, isHTML?: boolean) => { if (!bundleContent) return original; - const splitBody = original.split(isHTML ? /(?=^.+<)|(?=)|^<.+$/g : /(?=[ |\n|(|)])/g); + const splitBody = original.split(isHTML ? /(?=^.+<)|(?=)/g : /(?=[ \n()])/g); let newBody = ''; splitBody.map((s) => { if (s.length < 5) {