From 47945f4d1288cc2334e1225ad070f87c1007b473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicole=20Miko=C5=82ajczyk?= Date: Sun, 13 Apr 2025 19:08:47 +0200 Subject: [PATCH] pl-fe: allow to always display links target domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicole Mikołajczyk --- .../pl-fe/src/components/parsed-content.tsx | 32 ++++++++++++++++--- .../pl-fe/src/components/status-content.tsx | 1 + .../pl-fe/src/features/url-privacy/index.tsx | 8 +++++ packages/pl-fe/src/locales/en.json | 1 + packages/pl-fe/src/schemas/pl-fe/settings.ts | 1 + 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/pl-fe/src/components/parsed-content.tsx b/packages/pl-fe/src/components/parsed-content.tsx index 381a81de7..970464a48 100644 --- a/packages/pl-fe/src/components/parsed-content.tsx +++ b/packages/pl-fe/src/components/parsed-content.tsx @@ -23,6 +23,21 @@ const GREENTEXT_CLASS = 'dark:text-accent-green text-lime-600'; const nodesToText = (nodes: Array): string => nodes.map(node => node.type === 'text' ? node.data : node.type === 'tag' ? nodesToText(node.children as Array) : '').join(''); +const isHostNotVisible = (href: string, nodes: Array): false | string => { + try { + const { host } = new URL(href); + const text = nodesToText(nodes).trim(); + + if (new RegExp(`^(https?://)?(www\.)?${host}(/|$)`).test(text)) { + return false; + } else { + return host; + } + } catch (e) { + return false; + } +}; + interface IParsedContent { /** HTML content to display. */ html: string; @@ -32,7 +47,10 @@ interface IParsedContent { hasQuote?: boolean; /** Related custom emojis. */ emojis?: Array; + /** Whether to call a function to remove tracking parameters from URLs. */ cleanUrls?: boolean; + /** Whether to display link target domain when it's not part of the text. */ + displayTargetHost?: boolean; greentext?: boolean; speakAsCat?: boolean; } @@ -81,6 +99,7 @@ function parseContent({ hasQuote, emojis, cleanUrls = false, + displayTargetHost = true, greentext = false, speakAsCat = false, }: IParsedContent, extractHashtags = false) { @@ -158,6 +177,8 @@ function parseContent({ } } + const host = displayTargetHost && isHostNotVisible(href, domNode.children as Array); + const fallback = ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions - {domToReact(domNode.children as DOMNode[], options)} + {domToReact(domNode.children as Array, options)} + {host && ( + {' '}[{host}] + )} ); @@ -196,7 +220,7 @@ function parseContent({ } } - if (classes?.includes('hashtag')) { + if (classes?.includes('hashtag') || domNode.attribs.rel === 'tag') { const hashtag = nodesToText(domNode.children as Array); if (hashtag) { return ; @@ -248,10 +272,10 @@ function parseContent({ } const ParsedContent: React.FC = React.memo((props) => { - const settings = useSettings(); + const { urlPrivacy } = useSettings(); if (props.cleanUrls === undefined) { - props = { ...props, cleanUrls: settings.urlPrivacy.clearLinksInContent }; + props = { ...props, cleanUrls: urlPrivacy.clearLinksInContent, displayTargetHost: urlPrivacy.displayTargetHost }; } return parseContent(props, false); diff --git a/packages/pl-fe/src/components/status-content.tsx b/packages/pl-fe/src/components/status-content.tsx index c2f5b961b..05257e7fc 100644 --- a/packages/pl-fe/src/components/status-content.tsx +++ b/packages/pl-fe/src/components/status-content.tsx @@ -147,6 +147,7 @@ const StatusContent: React.FC = React.memo(({ hasQuote: !!status.quote_id, emojis: status.emojis, cleanUrls: urlPrivacy.clearLinksInContent, + displayTargetHost: urlPrivacy.displayTargetHost, greentext, speakAsCat: status.account.speak_as_cat, }, true), [content]); diff --git a/packages/pl-fe/src/features/url-privacy/index.tsx b/packages/pl-fe/src/features/url-privacy/index.tsx index 30dd18dac..282fa858d 100644 --- a/packages/pl-fe/src/features/url-privacy/index.tsx +++ b/packages/pl-fe/src/features/url-privacy/index.tsx @@ -26,6 +26,7 @@ const UrlPrivacy = () => { const { urlPrivacy } = useSettings(); + const [displayTargetHost, setDisplayTargetHost] = useState(urlPrivacy.displayTargetHost); const [clearLinksInCompose, setClearLinksInCompose] = useState(urlPrivacy.clearLinksInCompose); const [clearLinksInContent, setClearLinksInContent] = useState(urlPrivacy.clearLinksInContent); // const [allowReferralMarketing, setAllowReferralMarketing] = useState(urlPrivacy.allowReferralMarketing); @@ -35,6 +36,7 @@ const UrlPrivacy = () => { const onSubmit = () => { dispatch(changeSetting(['urlPrivacy'], { ...urlPrivacy, + displayTargetHost, clearLinksInCompose, clearLinksInContent, // allowReferralMarketing, @@ -58,6 +60,12 @@ const UrlPrivacy = () => {
+ + }> + setDisplayTargetHost(target.checked)} /> + + + }> setClearLinksInCompose(target.checked)} /> diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index e08e84620..6effe2898 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1670,6 +1670,7 @@ "upload_progress.label": "Uploading…", "url_privacy.clear_links_in_compose": "Suggest removing tracking parameters when composing a post", "url_privacy.clear_links_in_content": "Remove tracking parameters from displayed posts", + "url_privacy.display_target_host": "Always display the domain external links lead to", "url_privacy.hash_url.hint": "SHA256 hash of rules database, used to avoid unnecessary fetches, eg. {url}", "url_privacy.hash_url.label": "URL cleaning rules hash address (optional)", "url_privacy.hash_url.placeholder": "Hash URL", diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index c8f53a30e..629ab5d24 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -43,6 +43,7 @@ const settingsSchema = v.object({ allowReferralMarketing: v.fallback(v.boolean(), false), rulesUrl: v.fallback(v.string(), ''), hashUrl: v.fallback(v.string(), ''), + displayTargetHost: v.fallback(v.boolean(), true), }), theme: v.fallback(v.optional(v.object({