feat: clickable entry id in ai daily

This commit is contained in:
DIYgod 2024-08-07 17:38:19 +08:00
parent acfb35abd0
commit 0ffe1e5b6d
No known key found for this signature in database
5 changed files with 108 additions and 8 deletions

View File

@ -114,6 +114,8 @@
"rehype-parse": "9.0.0",
"rehype-sanitize": "6.0.0",
"rehype-stringify": "10.0.0",
"remark-parse": "11.0.0",
"remark-rehype": "11.1.0",
"semver": "7.6.3",
"shiki": "1.12.0",
"sonner": "^1.5.0",

View File

@ -271,6 +271,12 @@ importers:
rehype-stringify:
specifier: 10.0.0
version: 10.0.0
remark-parse:
specifier: 11.0.0
version: 11.0.0
remark-rehype:
specifier: 11.1.0
version: 11.1.0
semver:
specifier: 7.6.3
version: 7.6.3
@ -5897,6 +5903,12 @@ packages:
rehype-stringify@10.0.0:
resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==}
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
remark-rehype@11.1.0:
resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==}
repeat-string@1.6.1:
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
engines: {node: '>=0.10'}
@ -13524,6 +13536,23 @@ snapshots:
hast-util-to-html: 9.0.1
unified: 11.0.5
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.3
mdast-util-from-markdown: 2.0.0
micromark-util-types: 2.0.0
unified: 11.0.5
transitivePeerDependencies:
- supports-color
remark-rehype@11.1.0:
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
mdast-util-to-hast: 13.1.0
unified: 11.0.5
vfile: 6.0.2
repeat-string@1.6.1:
optional: true

View File

@ -1,5 +1,8 @@
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
import { FeedViewType } from "@renderer/lib/enum"
import { useEntryContentContext } from "@renderer/modules/entry-content/hooks"
import { useEntry } from "@renderer/store/entry"
import { useCallback } from "react"
import type { LinkProps } from "../../link"
import {
@ -13,6 +16,19 @@ import { ensureAndRenderTimeStamp } from "../utils"
export const MarkdownLink = (props: LinkProps) => {
const { view } = useEntryContentContext()
const entryId = (/^\w{17}$/.test(props.href)) ? props.href : null
const entry = useEntry(entryId)
const navigate = useNavigateEntry()
const onClick = useCallback((e: React.MouseEvent) => {
if (entryId) {
e.preventDefault()
navigate({
entryId,
})
}
}, [entryId, navigate])
const parseTimeStamp = view === FeedViewType.Audios
if (parseTimeStamp) {
const childrenText = props.children
@ -22,6 +38,7 @@ export const MarkdownLink = (props: LinkProps) => {
if (renderer) return renderer
}
}
return (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
@ -30,6 +47,7 @@ export const MarkdownLink = (props: LinkProps) => {
href={props.href}
title={props.title}
target="_blank"
onClick={onClick}
>
{props.children}
@ -41,7 +59,7 @@ export const MarkdownLink = (props: LinkProps) => {
{!!props.href && (
<TooltipPortal>
<TooltipContent align="start" className="break-all" side="bottom">
{props.href}
{entry?.entries.title || props.href}
</TooltipContent>
</TooltipPortal>
)}

View File

@ -0,0 +1,42 @@
import {
MarkdownLink,
} from "@renderer/components/ui/markdown/renderers"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { createElement } from "react"
import { Fragment, jsx, jsxs } from "react/jsx-runtime"
import rehypeSanitize from "rehype-sanitize"
import rehypeStringify from "rehype-stringify"
import remarkParse from "remark-parse"
import remarkRehype from "remark-rehype"
import { unified } from "unified"
import { VFile } from "vfile"
export const parseMarkdown = async (
content: string,
) => {
const file = new VFile(content)
const pipeline = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeSanitize)
.use(rehypeStringify)
const tree = pipeline.parse(content)
const hastTree = pipeline.runSync(tree, file)
return {
content: toJsxRuntime(hastTree, {
Fragment,
ignoreInvalidStyle: true,
jsx: (type, props, key) => jsx(type as any, props, key),
jsxs: (type, props, key) => jsxs(type as any, props, key),
passNode: true,
components: {
a: ({ node, ...props }) =>
createElement(MarkdownLink, { ...props } as any),
},
}),
}
}

View File

@ -1,7 +1,6 @@
import { Card, CardHeader } from "@renderer/components/ui/card"
import { Collapse, CollapseGroup } from "@renderer/components/ui/collapse"
import { LoadingCircle } from "@renderer/components/ui/loading"
import { Markdown } from "@renderer/components/ui/markdown"
import { ScrollArea } from "@renderer/components/ui/scroll-area"
import {
Tooltip,
@ -12,10 +11,11 @@ import { useAuthQuery } from "@renderer/hooks/common"
import { apiClient } from "@renderer/lib/api-fetch"
import { defineQuery } from "@renderer/lib/defineQuery"
import type { FeedViewType } from "@renderer/lib/enum"
import { parseMarkdown } from "@renderer/lib/parse-markdown"
import { cn } from "@renderer/lib/utils"
import { MarkAllButton } from "@renderer/modules/entry-column/mark-all-button"
import type { FC } from "react"
import { useState } from "react"
import { useEffect, useState } from "react"
type DailyView = Extract<
FeedViewType,
@ -153,6 +153,15 @@ const DailyReportContent: FC<{
const [markAllButtonRef, setMarkAllButtonRef] =
useState<HTMLButtonElement | null>(null)
const [eleContent, setEleContent] = useState<JSX.Element>()
useEffect(() => {
if (content.data) {
parseMarkdown(content.data).then(({ content }) => {
setEleContent(content)
})
}
}, [content.data])
return (
<Card className="border-none bg-transparent">
<CardHeader className="space-y-0 p-0">
@ -164,14 +173,14 @@ const DailyReportContent: FC<{
{content.isLoading ? (
<LoadingCircle size="large" className="mt-8 text-center" />
) : (
content.data && (
<Markdown className="prose-sm mt-4 px-6 prose-p:my-1 prose-ul:my-1">
{content.data}
</Markdown>
eleContent && (
<div className="prose-sm mt-4 px-6 prose-p:my-1 prose-ul:my-1">
{eleContent}
</div>
)
)}
</ScrollArea.ScrollArea>
{content.data && (
{eleContent && (
<button
type="button"
onClick={() => {