From f03e81b8fc51bf355864f84b1bf8ffc8c02f41a7 Mon Sep 17 00:00:00 2001 From: XLor Date: Fri, 16 Jan 2026 21:35:54 +0800 Subject: [PATCH] feat: markdown link supports open share feed link directly (#4811) * feat: markdown link supports open share feed link directly * docs: update changelog * fix: revert stopPropagation * fix: revert stopPropagation * fix: revert stopPropagation --- apps/desktop/changelog/next.md | 2 + .../ui/markdown/renderers/MarkdownLink.tsx | 71 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/apps/desktop/changelog/next.md b/apps/desktop/changelog/next.md index 8f5eac449..8282349c9 100644 --- a/apps/desktop/changelog/next.md +++ b/apps/desktop/changelog/next.md @@ -2,6 +2,8 @@ ## Shiny new things +- markdown link supports open share feed link directly in the app + ## Improvements ## No longer broken diff --git a/apps/desktop/layer/renderer/src/components/ui/markdown/renderers/MarkdownLink.tsx b/apps/desktop/layer/renderer/src/components/ui/markdown/renderers/MarkdownLink.tsx index a762df69a..dfb140c22 100644 --- a/apps/desktop/layer/renderer/src/components/ui/markdown/renderers/MarkdownLink.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/markdown/renderers/MarkdownLink.tsx @@ -7,11 +7,15 @@ import { TooltipTrigger, } from "@follow/components/ui/tooltip/index.jsx" import { useCorrectZIndex } from "@follow/components/ui/z-index/ctx.js" -import { cn, stopPropagation } from "@follow/utils" +import { env } from "@follow/shared/env.desktop" +import { feedSyncServices } from "@follow/store/feed/store" +import { cn, parseSafeUrl, stopPropagation } from "@follow/utils" +import type { MouseEvent } from "react" import { use, useCallback } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" +import { navigateEntry } from "~/hooks/biz/useNavigateEntry" import { copyToClipboard } from "~/lib/clipboard" import { MarkdownRenderActionContext } from "../context" @@ -21,6 +25,7 @@ export const MarkdownLink: Component = (props) => { const { t } = useTranslation() const populatedFullHref = transformUrl(props.href) + const shareFeedInfo = parseShareFeedInfo(populatedFullHref) const handleCopyLink = useCallback(async () => { try { @@ -34,6 +39,25 @@ export const MarkdownLink: Component = (props) => { } }, [populatedFullHref, t]) + const handleClickLink = useCallback( + async (event: MouseEvent) => { + stopPropagation(event) + + if (!shareFeedInfo) { + return + } + event.preventDefault() + + const view = await resolveShareFeedView(shareFeedInfo) + navigateEntry({ + feedId: shareFeedInfo.id, + entryId: null, + view, + }) + }, + [shareFeedInfo], + ) + const parseTimeStamp = isAudio(populatedFullHref) const zIndex = useCorrectZIndex(0) if (parseTimeStamp) { @@ -58,7 +82,7 @@ export const MarkdownLink: Component = (props) => { title={props.title} target="_blank" rel="noreferrer" - onClick={stopPropagation} + onClick={handleClickLink} > {props.children} @@ -93,3 +117,46 @@ export const MarkdownLink: Component = (props) => { ) } + +const parseShareFeedInfo = (href?: string) => { + if (!href) return null + + const baseUrl = parseSafeUrl(env.VITE_WEB_URL) + if (!baseUrl) return null + + let parsedUrl: URL + try { + parsedUrl = new URL(href, baseUrl) + } catch { + return null + } + + if (parsedUrl.host !== baseUrl.host) return null + + const pathParts = parsedUrl.pathname.split("/").filter(Boolean) + if (pathParts.length !== 3 || pathParts[0] !== "share" || pathParts[1] !== "feeds") { + return null + } + + const viewParam = parsedUrl.searchParams.get("view") + const view = viewParam ? Number.parseInt(viewParam, 10) : undefined + + return { + id: pathParts[2]!, + view: Number.isNaN(view) ? undefined : view, + } +} + +const resolveShareFeedView = async (info: { id: string; view?: number }) => { + if (typeof info.view === "number") { + return info.view + } + + const data = await feedSyncServices.fetchFeedById({ id: info.id }).catch(() => {}) + const analyticsView = data?.analytics?.view + if (typeof analyticsView === "number") { + return analyticsView + } + + return 0 +}