feat: add custom body link component
This commit is contained in:
parent
f22f380b96
commit
47abc44506
8 changed files with 314 additions and 247 deletions
42
package-lock.json
generated
42
package-lock.json
generated
|
|
@ -10,7 +10,7 @@
|
|||
"dependencies": {
|
||||
"@radix-ui/react-collapsible": "^1.1.7",
|
||||
"@radix-ui/react-dialog": "^1.1.10",
|
||||
"@radix-ui/react-hover-card": "^1.1.14",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
|
|
@ -1300,18 +1300,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.14.tgz",
|
||||
"integrity": "sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz",
|
||||
"integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.10",
|
||||
"@radix-ui/react-popper": "1.2.7",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-popper": "1.2.8",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.4",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
|
|
@ -1330,6 +1330,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
||||
|
|
@ -1354,12 +1360,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
|
||||
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
|
||||
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
|
|
@ -1381,9 +1387,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
||||
"integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||
"integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
|
|
@ -1437,9 +1443,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
|
||||
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@radix-ui/react-collapsible": "^1.1.7",
|
||||
"@radix-ui/react-dialog": "^1.1.10",
|
||||
"@radix-ui/react-hover-card": "^1.1.14",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
|
|
|
|||
31
src/App.css
31
src/App.css
|
|
@ -1,25 +1,34 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap");
|
||||
|
||||
code {
|
||||
font-family: "JetBrains Mono";
|
||||
font-family: "JetBrains Mono";
|
||||
}
|
||||
|
||||
pre > code {
|
||||
padding: 0.5rem;
|
||||
font-size: 14px;
|
||||
pre>code {
|
||||
padding: 0.5rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h2 > code {
|
||||
font-size: 1rem;
|
||||
h2>code {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn[data-state="active"] {
|
||||
box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
button[data-state="active"] {
|
||||
--tw-shadow: none;
|
||||
--tw-shadow-colored: none;
|
||||
box-shadow:
|
||||
var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
--tw-shadow: none;
|
||||
--tw-shadow-colored: none;
|
||||
box-shadow:
|
||||
var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
/* .HoverCardContent { */
|
||||
/* width: 300px; */
|
||||
/* max-height: 500px; */
|
||||
/* } */
|
||||
|
||||
/* .HoverCardContent { */
|
||||
/* transform-origin: 10px; */
|
||||
/* } */
|
||||
|
|
|
|||
50
src/components/BodyLink.tsx
Normal file
50
src/components/BodyLink.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import api from "@/api/eolas-api"
|
||||
|
||||
export default function BodyLink({ link, children }) {
|
||||
const [entryExists, setEntryExists] = useState(false)
|
||||
const path = link.split("/").pop().split(".")[0]
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEntryPreview = async () => {
|
||||
const cachedEntry = queryClient.getQueryData([`entry_${path}`])
|
||||
if (cachedEntry) {
|
||||
setEntryExists(true)
|
||||
console.info("INFO: Entry exists in cache.")
|
||||
} else {
|
||||
try {
|
||||
const remoteEntry = await queryClient.fetchQuery({
|
||||
queryKey: [`entry_${path}`],
|
||||
queryFn: () => api.get(`/entries/${path}`).then((res) => res.data),
|
||||
})
|
||||
|
||||
setEntryExists(true)
|
||||
console.info("INFO: Entry exists on remote.")
|
||||
} catch (error) {
|
||||
console.log(`INFO: Could not fetch entry ${path} ${error}`)
|
||||
setEntryExists(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchEntryPreview()
|
||||
}, [path, queryClient])
|
||||
|
||||
if (entryExists) {
|
||||
return (
|
||||
<a
|
||||
className="text-foreground underline-offset-4 underline hover:text-gray-700 dark:hover:text-green-300"
|
||||
href={`/entries/${path}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
} else
|
||||
return (
|
||||
<a className="text-red-500 line-through" href={`/entries/${path}`}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,68 +10,68 @@ import { Link } from "react-router"
|
|||
import { useState, useRef, useEffect } from "react"
|
||||
|
||||
export default function EntriesListSidebar() {
|
||||
const scrollRef = useRef(null)
|
||||
const [isOpen, setIsOpen] = useState(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const saved = sessionStorage.getItem("entries_list_sidebar_open")
|
||||
return saved ? JSON.parse(saved) : false
|
||||
}
|
||||
return false
|
||||
})
|
||||
const scrollRef = useRef(null)
|
||||
const [isOpen, setIsOpen] = useState(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const saved = sessionStorage.getItem("entries_list_sidebar_open")
|
||||
return saved ? JSON.parse(saved) : false
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const { data: entries, isLoading } = useQuery({
|
||||
queryKey: ["entries_list"],
|
||||
queryFn: () => api.get("/entries").then((res) => res.data),
|
||||
})
|
||||
const { data: entries, isLoading } = useQuery({
|
||||
queryKey: ["entries_list"],
|
||||
queryFn: () => api.get("/entries").then((res) => res.data),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem("entries_list_sidebar_open", JSON.stringify(isOpen))
|
||||
}, [isOpen])
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem("entries_list_sidebar_open", JSON.stringify(isOpen))
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
const savedScroll = sessionStorage.getItem("entries_list_sidebar_scroll_position")
|
||||
if (savedScroll && scrollRef.current) {
|
||||
scrollRef.current.scrollTop = parseInt(savedScroll)
|
||||
}
|
||||
}, [entries])
|
||||
useEffect(() => {
|
||||
const savedScroll = sessionStorage.getItem("entries_list_sidebar_scroll_position")
|
||||
if (savedScroll && scrollRef.current) {
|
||||
scrollRef.current.scrollTop = parseInt(savedScroll)
|
||||
}
|
||||
}, [entries])
|
||||
|
||||
const handleScroll = () => {
|
||||
if (scrollRef.current) {
|
||||
sessionStorage.setItem("entries_list_sidebar_scroll_position", scrollRef.current.scrollTop)
|
||||
}
|
||||
}
|
||||
const handleScroll = () => {
|
||||
if (scrollRef.current) {
|
||||
sessionStorage.setItem("entries_list_sidebar_scroll_position", scrollRef.current.scrollTop)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="group/collapsible">
|
||||
<SidebarMenuItem key="entries">
|
||||
<CollapsibleTrigger asChild className="rounded-none">
|
||||
<SidebarMenuButton asChild className="rounded-none">
|
||||
<a href="#">
|
||||
<FileText />
|
||||
<span>Entries</span>
|
||||
<Badge className="ml-0" variant="secondary">
|
||||
{entries?.count}
|
||||
</Badge>
|
||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div ref={scrollRef} onScroll={handleScroll} className="max-h-100 overflow-y-auto">
|
||||
<SidebarMenuSub>
|
||||
{entries?.data.map((item, i) => (
|
||||
<SidebarMenuItem key={i}>
|
||||
<Link to={`/entries/${item.title}`}>
|
||||
<span className="text-xs text-foreground dark:hover:text-green-300 dark:active:text-green-400 hover:text-gray-600 active:text-gray-700 focus:text-gray-900 dark:focus:text-green-900">
|
||||
{item.title.replace(/_/g, " ")}
|
||||
</span>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
)
|
||||
return (
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="group/collapsible">
|
||||
<SidebarMenuItem key="entries">
|
||||
<CollapsibleTrigger asChild className="rounded-none">
|
||||
<SidebarMenuButton asChild className="rounded-none">
|
||||
<a href="#">
|
||||
<FileText />
|
||||
<span>Entries</span>
|
||||
<Badge className="ml-0" variant="secondary">
|
||||
{entries?.count}
|
||||
</Badge>
|
||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div ref={scrollRef} onScroll={handleScroll} className="max-h-100 overflow-y-auto">
|
||||
<SidebarMenuSub>
|
||||
{entries?.data.map((item, i) => (
|
||||
<SidebarMenuItem key={i}>
|
||||
<Link to={`/entries/${item.title}`}>
|
||||
<span className="text-xs text-foreground dark:hover:text-green-300 dark:active:text-green-400 hover:text-gray-600 active:text-gray-700 focus:text-gray-900 dark:focus:text-green-900">
|
||||
{item.title.replace(/_/g, " ")}
|
||||
</span>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,100 +5,99 @@ import remarkMath from "remark-math"
|
|||
import rehypeKatex from "rehype-katex"
|
||||
import "katex/dist/katex.min.css"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import BodyLink from "./BodyLink"
|
||||
|
||||
const EntryLoadingSkeleton = () => {
|
||||
return (
|
||||
<div className="space-y-2 max-w-2xl p-4 lg:p-6">
|
||||
{/*
|
||||
return (
|
||||
<div className="space-y-2 max-w-2xl p-4 lg:p-6">
|
||||
{/*
|
||||
|
||||
<Skeleton className="h-[400px] md:h-[800px] max-w-2xl rounded-none" />
|
||||
*/}
|
||||
|
||||
<Skeleton className="h-4 max-w-full" />
|
||||
<Skeleton className="h-4 md:max-w-xl max-w-[300px]" />
|
||||
<Skeleton className="h-4 md:max-w-[400px] max-w-[250px]" />
|
||||
<Skeleton className="h-4 md:max-w-[300px] max-w-[200px]" />
|
||||
<Skeleton className="h-4 md:max-w-[200px] max-w-[150px]" />
|
||||
<Skeleton className="h-4 md:max-w-[100px] max-w-[100px]" />
|
||||
</div>
|
||||
)
|
||||
<Skeleton className="h-4 max-w-full" />
|
||||
<Skeleton className="h-4 md:max-w-xl max-w-[300px]" />
|
||||
<Skeleton className="h-4 md:max-w-[400px] max-w-[250px]" />
|
||||
<Skeleton className="h-4 md:max-w-[300px] max-w-[200px]" />
|
||||
<Skeleton className="h-4 md:max-w-[200px] max-w-[150px]" />
|
||||
<Skeleton className="h-4 md:max-w-[100px] max-w-[100px]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ImagePreprocessor = (src) => {
|
||||
const filename = src.src.split("/").pop()
|
||||
const s3RootUrl = "https://eolas.s3.systemsobscure.net/"
|
||||
return <img src={s3RootUrl + filename} />
|
||||
const filename = src.src.split("/").pop()
|
||||
const s3RootUrl = "https://eolas.s3.systemsobscure.net/"
|
||||
return <img src={s3RootUrl + filename} />
|
||||
}
|
||||
|
||||
export default function EntryBody({ body, isLoading }) {
|
||||
if (isLoading) {
|
||||
return <EntryLoadingSkeleton />
|
||||
} else
|
||||
return (
|
||||
<div className="max-w-2xl p-4 lg:p-6">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
h1: () => null,
|
||||
h2: ({ children }) => (
|
||||
<h2 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h4>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="leading-[1.5] mb-4 not-first:mt-4">{children}</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc ml-10 mb-4 space-y-1">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal ml-10 mb-4 space-y-1">{children}</ol>
|
||||
),
|
||||
a: ({ href, children }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<table className="w-full mb-4 text-sm">{children}</table>
|
||||
),
|
||||
tr: ({ children }) => (
|
||||
<tr className="even:bg-muted m-0 border-t p-0">{children}</tr>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="mt-4 border-l-2 pl-6 text-muted-foreground">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
pre: ({ children }) => {
|
||||
const child = children.props
|
||||
return <CodeBlock className={child.className}>{child.children}</CodeBlock>
|
||||
},
|
||||
code: ({ children }) => (
|
||||
<code className="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
img: ({ children, src }) => <ImagePreprocessor src={src} />,
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
if (isLoading) {
|
||||
return <EntryLoadingSkeleton />
|
||||
} else
|
||||
return (
|
||||
<div className="max-w-2xl p-4 lg:p-6">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
h1: () => null,
|
||||
h2: ({ children }) => (
|
||||
<h2 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">{children}</h4>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="leading-[1.5] mb-4 not-first:mt-4">{children}</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc ml-10 mb-4 space-y-1">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal ml-10 mb-4 space-y-1">{children}</ol>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<table className="w-full mb-4 text-sm">{children}</table>
|
||||
),
|
||||
tr: ({ children }) => (
|
||||
<tr className="even:bg-muted m-0 border-t p-0">{children}</tr>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="mt-4 border-l-2 pl-6 text-muted-foreground">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
pre: ({ children }) => {
|
||||
const child = children.props
|
||||
return <CodeBlock className={child.className}>{child.children}</CodeBlock>
|
||||
},
|
||||
code: ({ children }) => (
|
||||
<code className="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
img: ({ src }) => <ImagePreprocessor src={src} />,
|
||||
a: ({ href, children }) => {
|
||||
return <BodyLink link={href} children={children} />
|
||||
},
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,85 +4,84 @@ import { Link } from "react-router"
|
|||
import { Badge } from "./ui/badge"
|
||||
|
||||
export default function EntryReferences({ entryTitle }) {
|
||||
const { data: tags, isLoading: tagsLoading } = useQuery({
|
||||
queryKey: [`tags_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/tags/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
const { data: tags, isLoading: tagsLoading } = useQuery({
|
||||
queryKey: [`tags_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/tags/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
|
||||
const { data: backlinks, isLoading: backlinksLoading } = useQuery({
|
||||
queryKey: [`backlinks_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/entries/backlinks/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
const { data: backlinks, isLoading: backlinksLoading } = useQuery({
|
||||
queryKey: [`backlinks_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/entries/backlinks/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
|
||||
const { data: outlinks, isLoading: outlinksLoading } = useQuery({
|
||||
queryKey: [`outlinks_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/entries/outlinks/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
const { data: outlinks, isLoading: outlinksLoading } = useQuery({
|
||||
queryKey: [`outlinks_for_${entryTitle}`],
|
||||
queryFn: () => api.get(`/entries/outlinks/${entryTitle}`).then((res) => res.data),
|
||||
})
|
||||
|
||||
console.log(backlinks)
|
||||
return (
|
||||
<div className="w-full flex flex-row justify-between gap-3">
|
||||
<div className="w-full">
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Tags</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{tags?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{tags?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/tags/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2"
|
||||
>
|
||||
{item}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<div className="w-full flex flex-row justify-between gap-3">
|
||||
<div className="w-full">
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Tags</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{tags?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{tags?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/tags/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2"
|
||||
>
|
||||
{item}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Incoming links</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{backlinks?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Incoming links</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{backlinks?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{backlinks &&
|
||||
backlinks?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/entries/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2 block mb-2"
|
||||
>
|
||||
{item.replace(/_/g, " ")}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{backlinks &&
|
||||
backlinks?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/entries/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2 block mb-2"
|
||||
>
|
||||
{item.replace(/_/g, " ")}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Outgoing links</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{outlinks?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex flex row justify-between bg-sidebar">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Outgoing links</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{outlinks?.count}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{outlinks &&
|
||||
outlinks?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/entries/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2 block mb-2"
|
||||
>
|
||||
{item.replace(/_/g, " ")}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
<div className="mt-2 mb-3 pl-1">
|
||||
{outlinks &&
|
||||
outlinks?.data.map((item, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/entries/${item}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 pr-2 block mb-2"
|
||||
>
|
||||
{item.replace(/_/g, " ")}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
html {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue