livemark 0.0.0-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +3 -0
  3. package/build/actions/config/define.d.ts +6 -0
  4. package/build/actions/config/define.js +8 -0
  5. package/build/actions/config/define.unit.d.ts +1 -0
  6. package/build/actions/config/define.unit.js +34 -0
  7. package/build/actions/config/load.d.ts +5 -0
  8. package/build/actions/config/load.js +22 -0
  9. package/build/actions/config/load.unit.d.ts +1 -0
  10. package/build/actions/config/load.unit.js +23 -0
  11. package/build/commands/build.d.ts +5 -0
  12. package/build/commands/build.js +13 -0
  13. package/build/commands/preview.d.ts +5 -0
  14. package/build/commands/preview.js +18 -0
  15. package/build/commands/start.d.ts +5 -0
  16. package/build/commands/start.js +19 -0
  17. package/build/docs/markdown/includes/example.d.ts +9 -0
  18. package/build/docs/markdown/includes/example.js +5 -0
  19. package/build/entrypoints/main.d.ts +2 -0
  20. package/build/entrypoints/main.js +12 -0
  21. package/build/helpers/program.d.ts +2 -0
  22. package/build/helpers/program.js +5 -0
  23. package/build/index.d.ts +1 -0
  24. package/build/index.js +2 -0
  25. package/build/livemark.config.d.ts +9 -0
  26. package/build/livemark.config.js +5 -0
  27. package/build/models/config.d.ts +33 -0
  28. package/build/models/config.js +14 -0
  29. package/build/program.d.ts +2 -0
  30. package/build/program.js +14 -0
  31. package/build/vite.config.d.ts +2 -0
  32. package/build/vite.config.js +48 -0
  33. package/build/website/client.tsx +10 -0
  34. package/build/website/components/Abbr.tsx +17 -0
  35. package/build/website/components/AnsiCode.tsx +15 -0
  36. package/build/website/components/Banner.tsx +32 -0
  37. package/build/website/components/Breadcrumbs.tsx +19 -0
  38. package/build/website/components/Callout.tsx +72 -0
  39. package/build/website/components/Cards.tsx +66 -0
  40. package/build/website/components/CodeBlock.tsx +118 -0
  41. package/build/website/components/CodeTabs.tsx +83 -0
  42. package/build/website/components/Columns.tsx +17 -0
  43. package/build/website/components/Content.tsx +7 -0
  44. package/build/website/components/ContentTab.tsx +7 -0
  45. package/build/website/components/ContentTabs.tsx +82 -0
  46. package/build/website/components/DefaultCatchBoundary.tsx +54 -0
  47. package/build/website/components/DefinitionList.tsx +21 -0
  48. package/build/website/components/Details.tsx +30 -0
  49. package/build/website/components/FileTree.tsx +52 -0
  50. package/build/website/components/Footer.tsx +9 -0
  51. package/build/website/components/FootnoteRef.tsx +52 -0
  52. package/build/website/components/Heading.tsx +20 -0
  53. package/build/website/components/InlineBadge.tsx +9 -0
  54. package/build/website/components/InlineIcon.tsx +21 -0
  55. package/build/website/components/InlineToc.tsx +31 -0
  56. package/build/website/components/Layout.tsx +220 -0
  57. package/build/website/components/LinkButton.tsx +31 -0
  58. package/build/website/components/Mermaid.tsx +37 -0
  59. package/build/website/components/NotFound.tsx +27 -0
  60. package/build/website/components/PackageTabs.tsx +74 -0
  61. package/build/website/components/PageToolbar.tsx +65 -0
  62. package/build/website/components/PrevNext.tsx +52 -0
  63. package/build/website/components/SearchDialog.tsx +245 -0
  64. package/build/website/components/Section.tsx +24 -0
  65. package/build/website/components/SectionHeader.tsx +35 -0
  66. package/build/website/components/SiteTitle.tsx +16 -0
  67. package/build/website/components/SortableTable.tsx +81 -0
  68. package/build/website/components/SoundCloud.tsx +16 -0
  69. package/build/website/components/Theme.tsx +40 -0
  70. package/build/website/components/Toc.tsx +99 -0
  71. package/build/website/components/YouTube.tsx +16 -0
  72. package/build/website/components/ZoomImage.tsx +30 -0
  73. package/build/website/content-collections.ts +181 -0
  74. package/build/website/elements/accordion.tsx +78 -0
  75. package/build/website/elements/alert-dialog.tsx +185 -0
  76. package/build/website/elements/alert.tsx +74 -0
  77. package/build/website/elements/aspect-ratio.tsx +22 -0
  78. package/build/website/elements/avatar.tsx +107 -0
  79. package/build/website/elements/badge.tsx +51 -0
  80. package/build/website/elements/breadcrumb.tsx +120 -0
  81. package/build/website/elements/button-group.tsx +87 -0
  82. package/build/website/elements/button.tsx +57 -0
  83. package/build/website/elements/calendar.tsx +229 -0
  84. package/build/website/elements/card.tsx +102 -0
  85. package/build/website/elements/carousel.tsx +240 -0
  86. package/build/website/elements/chart.tsx +372 -0
  87. package/build/website/elements/checkbox.tsx +26 -0
  88. package/build/website/elements/collapsible.tsx +19 -0
  89. package/build/website/elements/command.tsx +210 -0
  90. package/build/website/elements/context-menu.tsx +270 -0
  91. package/build/website/elements/dialog.tsx +152 -0
  92. package/build/website/elements/direction.tsx +4 -0
  93. package/build/website/elements/drawer.tsx +127 -0
  94. package/build/website/elements/dropdown-menu.tsx +260 -0
  95. package/build/website/elements/empty.tsx +104 -0
  96. package/build/website/elements/field.tsx +235 -0
  97. package/build/website/elements/hover-card.tsx +49 -0
  98. package/build/website/elements/input-group.tsx +171 -0
  99. package/build/website/elements/input-otp.tsx +84 -0
  100. package/build/website/elements/input.tsx +18 -0
  101. package/build/website/elements/item.tsx +199 -0
  102. package/build/website/elements/kbd.tsx +26 -0
  103. package/build/website/elements/label.tsx +17 -0
  104. package/build/website/elements/menubar.tsx +282 -0
  105. package/build/website/elements/native-select.tsx +56 -0
  106. package/build/website/elements/navigation-menu.tsx +171 -0
  107. package/build/website/elements/pagination.tsx +133 -0
  108. package/build/website/elements/popover.tsx +88 -0
  109. package/build/website/elements/progress.tsx +81 -0
  110. package/build/website/elements/radio-group.tsx +36 -0
  111. package/build/website/elements/resizable.tsx +48 -0
  112. package/build/website/elements/scroll-area.tsx +51 -0
  113. package/build/website/elements/select.tsx +200 -0
  114. package/build/website/elements/separator.tsx +22 -0
  115. package/build/website/elements/sheet.tsx +130 -0
  116. package/build/website/elements/sidebar.tsx +717 -0
  117. package/build/website/elements/skeleton.tsx +13 -0
  118. package/build/website/elements/slider.tsx +57 -0
  119. package/build/website/elements/sonner.tsx +40 -0
  120. package/build/website/elements/spinner.tsx +16 -0
  121. package/build/website/elements/switch.tsx +30 -0
  122. package/build/website/elements/table.tsx +112 -0
  123. package/build/website/elements/tabs.tsx +79 -0
  124. package/build/website/elements/textarea.tsx +16 -0
  125. package/build/website/elements/toggle-group.tsx +87 -0
  126. package/build/website/elements/toggle.tsx +43 -0
  127. package/build/website/elements/tooltip.tsx +67 -0
  128. package/build/website/env.d.ts +10 -0
  129. package/build/website/globals.d.ts +8 -0
  130. package/build/website/helpers/article-icon.ts +59 -0
  131. package/build/website/helpers/articles.ts +24 -0
  132. package/build/website/helpers/resolve-includes.ts +131 -0
  133. package/build/website/helpers/resolve-includes.unit.ts +126 -0
  134. package/build/website/helpers/search-text.ts +15 -0
  135. package/build/website/helpers/style.ts +6 -0
  136. package/build/website/helpers/theme.ts +27 -0
  137. package/build/website/helpers/toc-context.ts +10 -0
  138. package/build/website/helpers/toc.ts +51 -0
  139. package/build/website/helpers/toc.unit.ts +74 -0
  140. package/build/website/hooks/in-view.ts +30 -0
  141. package/build/website/hooks/media.ts +19 -0
  142. package/build/website/models/article.ts +40 -0
  143. package/build/website/plugins/rehype-inline-code.ts +107 -0
  144. package/build/website/plugins/remark-abbr.ts +46 -0
  145. package/build/website/plugins/remark-abbr.unit.ts +99 -0
  146. package/build/website/plugins/remark-ansi.ts +247 -0
  147. package/build/website/plugins/remark-ansi.unit.ts +197 -0
  148. package/build/website/plugins/remark-badge.ts +50 -0
  149. package/build/website/plugins/remark-badge.unit.ts +96 -0
  150. package/build/website/plugins/remark-button.ts +60 -0
  151. package/build/website/plugins/remark-button.unit.ts +147 -0
  152. package/build/website/plugins/remark-callout.ts +57 -0
  153. package/build/website/plugins/remark-card.ts +80 -0
  154. package/build/website/plugins/remark-code-tabs.ts +87 -0
  155. package/build/website/plugins/remark-columns.ts +66 -0
  156. package/build/website/plugins/remark-custom-heading-id.ts +70 -0
  157. package/build/website/plugins/remark-custom-heading-id.unit.ts +131 -0
  158. package/build/website/plugins/remark-deflist.ts +36 -0
  159. package/build/website/plugins/remark-details.ts +49 -0
  160. package/build/website/plugins/remark-filetree.ts +89 -0
  161. package/build/website/plugins/remark-filetree.unit.ts +137 -0
  162. package/build/website/plugins/remark-github-callout.ts +71 -0
  163. package/build/website/plugins/remark-icon.ts +42 -0
  164. package/build/website/plugins/remark-image.ts +25 -0
  165. package/build/website/plugins/remark-media.ts +62 -0
  166. package/build/website/plugins/remark-media.unit.ts +74 -0
  167. package/build/website/plugins/remark-mermaid.ts +23 -0
  168. package/build/website/plugins/remark-npm.ts +52 -0
  169. package/build/website/plugins/remark-steps.ts +36 -0
  170. package/build/website/plugins/remark-steps.unit.ts +97 -0
  171. package/build/website/plugins/remark-tab.ts +100 -0
  172. package/build/website/plugins/remark-tab.unit.ts +181 -0
  173. package/build/website/plugins/remark-toc.ts +39 -0
  174. package/build/website/plugins/remark-toc.unit.ts +82 -0
  175. package/build/website/plugins/shiki-icon.ts +82 -0
  176. package/build/website/plugins/shiki-line-highlight.ts +174 -0
  177. package/build/website/public/logo.svg +72 -0
  178. package/build/website/routeTree.gen.ts +86 -0
  179. package/build/website/router.tsx +34 -0
  180. package/build/website/routes/$.tsx +149 -0
  181. package/build/website/routes/__root.tsx +84 -0
  182. package/build/website/routes/index.tsx +13 -0
  183. package/build/website/server.ts +7 -0
  184. package/build/website/styles/general.css +347 -0
  185. package/build/website/vite.config.ts +36 -0
  186. package/entrypoints/main.ts +12 -0
  187. package/package.json +120 -0
@@ -0,0 +1,31 @@
1
+ import { useTocItems } from "../helpers/toc-context.ts"
2
+
3
+ /** Renders the page's table of contents inline within the content */
4
+ export function InlineToc(props: { maxLevel?: string }) {
5
+ const allItems = useTocItems()
6
+ const maxDepth = props.maxLevel ? Number(props.maxLevel) : 4
7
+ const items = allItems.filter(item => item.depth <= maxDepth)
8
+
9
+ if (items.length === 0) return null
10
+
11
+ return (
12
+ <nav className="not-prose my-8 rounded-xl border border-border bg-card p-6">
13
+ <p className="text-base font-semibold mb-4">Table of Contents</p>
14
+ <ul className="text-base space-y-2.5">
15
+ {items.map(item => (
16
+ <li
17
+ key={item.url}
18
+ style={{ paddingLeft: `${(item.depth - 2) * 1.25}rem` }}
19
+ >
20
+ <a
21
+ href={item.url}
22
+ className="text-muted-foreground hover:text-primary transition-colors"
23
+ >
24
+ {item.title}
25
+ </a>
26
+ </li>
27
+ ))}
28
+ </ul>
29
+ </nav>
30
+ )
31
+ }
@@ -0,0 +1,220 @@
1
+ import { useState } from "react"
2
+ import { Link, useLocation } from "@tanstack/react-router"
3
+ import { useHotkey } from "@tanstack/react-hotkeys"
4
+ import {
5
+ ChevronRight,
6
+ ExternalLink,
7
+ FileText,
8
+ Search as SearchIcon,
9
+ } from "lucide-react"
10
+ import { articleTree } from "../helpers/articles.ts"
11
+ import { articleIcons } from "../helpers/article-icon.ts"
12
+ import type { ArticleNode } from "../models/article.ts"
13
+ import {
14
+ Collapsible,
15
+ CollapsibleContent,
16
+ CollapsibleTrigger,
17
+ } from "../elements/collapsible.tsx"
18
+ import {
19
+ Sidebar,
20
+ SidebarContent,
21
+ SidebarFooter,
22
+ SidebarGroup,
23
+ SidebarGroupContent,
24
+ SidebarGroupLabel,
25
+ SidebarHeader,
26
+ SidebarInset,
27
+ SidebarMenu,
28
+ SidebarMenuButton,
29
+ SidebarMenuItem,
30
+ SidebarMenuSub,
31
+ SidebarMenuSubButton,
32
+ SidebarMenuSubItem,
33
+ SidebarProvider,
34
+ SidebarRail,
35
+ SidebarTrigger,
36
+ useSidebar,
37
+ } from "../elements/sidebar.tsx"
38
+ import { Banner } from "./Banner.tsx"
39
+ import { SearchDialog } from "./SearchDialog.tsx"
40
+ import { SiteTitle } from "./SiteTitle.tsx"
41
+ import { Theme } from "./Theme.tsx"
42
+
43
+ export function Layout(props: { children?: React.ReactNode }) {
44
+ useHotkey("J", () =>
45
+ window.scrollBy({ top: window.innerHeight * 0.8, behavior: "smooth" }),
46
+ )
47
+ useHotkey("K", () =>
48
+ window.scrollBy({ top: -window.innerHeight * 0.8, behavior: "smooth" }),
49
+ )
50
+
51
+ return (
52
+ <SidebarProvider>
53
+ <AppSidebar />
54
+ <SidebarInset>
55
+ <header className="sticky top-0 z-20 flex h-16 shrink-0 items-center bg-background">
56
+ <div className="flex items-center self-stretch pl-4 pr-0 border-b">
57
+ <SidebarTrigger />
58
+ </div>
59
+ <div className="flex flex-1 items-center gap-8 self-stretch border-b px-6 text-sm">
60
+ <Link
61
+ to="/"
62
+ className="text-foreground font-medium underline underline-offset-4"
63
+ >
64
+ Docs
65
+ </Link>
66
+ <a
67
+ href="/blog"
68
+ className="text-muted-foreground hover:text-foreground transition-colors"
69
+ >
70
+ Blog
71
+ </a>
72
+ <a
73
+ href="https://github.com/datisthq/livemark"
74
+ target="_blank"
75
+ rel="noopener noreferrer"
76
+ className="text-muted-foreground hover:text-foreground transition-colors"
77
+ >
78
+ GitHub <ExternalLink className="inline size-3 -mt-0.5" />
79
+ </a>
80
+ <Banner />
81
+ </div>
82
+ </header>
83
+ <main className="flex-1">{props.children}</main>
84
+ </SidebarInset>
85
+ </SidebarProvider>
86
+ )
87
+ }
88
+
89
+ function AppSidebar() {
90
+ const pathname = useLocation({ select: l => l.pathname })
91
+ const { toggleSidebar } = useSidebar()
92
+
93
+ useHotkey("S", toggleSidebar)
94
+
95
+ return (
96
+ <Sidebar>
97
+ <SidebarHeader>
98
+ <SidebarMenu>
99
+ <SidebarMenuItem>
100
+ <SidebarMenuButton size="lg" render={<Link to="/" />}>
101
+ <SiteTitle />
102
+ </SidebarMenuButton>
103
+ </SidebarMenuItem>
104
+ </SidebarMenu>
105
+ </SidebarHeader>
106
+ <SidebarContent>
107
+ <SidebarGroup>
108
+ <SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
109
+ Articles
110
+ </SidebarGroupLabel>
111
+ <SidebarGroupContent>
112
+ <SidebarMenu>
113
+ {articleTree.map(node => (
114
+ <NavNode
115
+ key={node.pathname}
116
+ node={node}
117
+ currentPath={pathname}
118
+ />
119
+ ))}
120
+ </SidebarMenu>
121
+ </SidebarGroupContent>
122
+ </SidebarGroup>
123
+ </SidebarContent>
124
+ <SidebarFooter className="p-4 space-y-2">
125
+ <Search />
126
+ <Theme />
127
+ </SidebarFooter>
128
+ <SidebarRail />
129
+ </Sidebar>
130
+ )
131
+ }
132
+
133
+ function splatFor(pathname: string) {
134
+ return { _splat: pathname.replace(/^\/|\/$/g, "") }
135
+ }
136
+
137
+ function isActive(articlePathname: string, currentPath: string) {
138
+ const normalized = `/${currentPath.replace(/^\/|\/$/g, "")}/`
139
+ return articlePathname === normalized
140
+ }
141
+
142
+ function NavNode(props: { node: ArticleNode; currentPath: string }) {
143
+ const { node, currentPath } = props
144
+ const Icon = articleIcons[node.icon] ?? FileText
145
+ const active = isActive(node.pathname, currentPath)
146
+
147
+ if (node.children.length === 0) {
148
+ return (
149
+ <SidebarMenuItem>
150
+ <SidebarMenuButton
151
+ isActive={active}
152
+ className={active ? "" : "opacity-75"}
153
+ render={<Link to="/$" params={splatFor(node.pathname)} />}
154
+ >
155
+ <Icon className="size-4" />
156
+ <span>{node.title}</span>
157
+ </SidebarMenuButton>
158
+ </SidebarMenuItem>
159
+ )
160
+ }
161
+
162
+ return (
163
+ <Collapsible defaultOpen className="group/collapsible">
164
+ <SidebarMenuItem>
165
+ <SidebarMenuButton
166
+ isActive={active}
167
+ className={active ? "" : "opacity-75"}
168
+ render={<Link to="/$" params={splatFor(node.pathname)} />}
169
+ >
170
+ <Icon className="size-4" />
171
+ <span>{node.title}</span>
172
+ </SidebarMenuButton>
173
+ <CollapsibleTrigger className="absolute right-1 top-1.5 p-1 rounded-md hover:bg-sidebar-accent">
174
+ <ChevronRight className="size-3.5 transition-transform group-data-[open]/collapsible:rotate-90" />
175
+ </CollapsibleTrigger>
176
+ </SidebarMenuItem>
177
+ <CollapsibleContent>
178
+ <SidebarMenuSub>
179
+ {node.children.map(child => {
180
+ const childIsActive = isActive(child.pathname, currentPath)
181
+ return (
182
+ <SidebarMenuSubItem key={child.pathname}>
183
+ <SidebarMenuSubButton
184
+ isActive={childIsActive}
185
+ className={childIsActive ? "" : "opacity-75"}
186
+ render={<Link to="/$" params={splatFor(child.pathname)} />}
187
+ >
188
+ <span>{child.title}</span>
189
+ </SidebarMenuSubButton>
190
+ </SidebarMenuSubItem>
191
+ )
192
+ })}
193
+ </SidebarMenuSub>
194
+ </CollapsibleContent>
195
+ </Collapsible>
196
+ )
197
+ }
198
+
199
+ function Search() {
200
+ const [open, setOpen] = useState(false)
201
+
202
+ useHotkey("/", () => setOpen(true))
203
+
204
+ return (
205
+ <>
206
+ <button
207
+ type="button"
208
+ onClick={() => setOpen(true)}
209
+ className="flex w-full items-center gap-2 rounded-xl border border-border bg-sidebar-accent/70 px-3 py-2 text-sm text-muted-foreground shadow-xs hover:bg-background transition-colors cursor-pointer"
210
+ >
211
+ <SearchIcon className="size-4" />
212
+ <span className="flex-1 text-left">Search...</span>
213
+ <kbd className="rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-mono">
214
+ /
215
+ </kbd>
216
+ </button>
217
+ <SearchDialog open={open} onOpenChange={setOpen} />
218
+ </>
219
+ )
220
+ }
@@ -0,0 +1,31 @@
1
+ import { buttonVariants } from "../elements/button.tsx"
2
+ import { cn } from "../helpers/style.ts"
3
+
4
+ /** Styled link button rendered from ::button leaf directives */
5
+ export function LinkButton(props: {
6
+ href: string
7
+ label: string
8
+ variant?:
9
+ | "default"
10
+ | "outline"
11
+ | "secondary"
12
+ | "ghost"
13
+ | "destructive"
14
+ | "link"
15
+ size?: "default" | "sm" | "lg"
16
+ }) {
17
+ return (
18
+ <a
19
+ href={props.href}
20
+ className={cn(
21
+ buttonVariants({
22
+ variant: props.variant,
23
+ size: props.size,
24
+ }),
25
+ "no-underline",
26
+ )}
27
+ >
28
+ {props.label}
29
+ </a>
30
+ )
31
+ }
@@ -0,0 +1,37 @@
1
+ import { useEffect, useRef } from "react"
2
+ import mermaid from "mermaid"
3
+
4
+ mermaid.initialize({ startOnLoad: false })
5
+
6
+ /** Renders a Mermaid diagram from a chart definition string */
7
+ export function Mermaid(props: { children: string }) {
8
+ const ref = useRef<HTMLDivElement>(null)
9
+
10
+ useEffect(() => {
11
+ const el = ref.current
12
+ if (!el) return
13
+
14
+ const render = () => {
15
+ const isDark = document.documentElement.classList.contains("dark")
16
+ mermaid.initialize({
17
+ startOnLoad: false,
18
+ theme: isDark ? "dark" : "default",
19
+ })
20
+ el.textContent = props.children
21
+ el.removeAttribute("data-processed")
22
+ mermaid.run({ nodes: [el] })
23
+ }
24
+
25
+ render()
26
+
27
+ const observer = new MutationObserver(render)
28
+ observer.observe(document.documentElement, {
29
+ attributes: true,
30
+ attributeFilter: ["class"],
31
+ })
32
+
33
+ return () => observer.disconnect()
34
+ }, [props.children])
35
+
36
+ return <div ref={ref} className="not-prose mermaid flex justify-center" />
37
+ }
@@ -0,0 +1,27 @@
1
+ import { Link } from "@tanstack/react-router"
2
+
3
+ export function NotFound({ children }: { children?: any }) {
4
+ return (
5
+ <div className="min-h-[60vh] flex flex-col items-center justify-center text-center px-4">
6
+ <h1 className="font-sans text-6xl font-semibold mb-4">404</h1>
7
+ <div className="text-muted-foreground mb-6">
8
+ {children || <p>The page you're looking for doesn't exist.</p>}
9
+ </div>
10
+ <div className="flex items-center gap-4">
11
+ <button
12
+ type="button"
13
+ onClick={() => window.history.back()}
14
+ className="bg-primary text-primary-foreground px-4 py-2 text-sm font-medium hover:bg-primary/80 transition-colors"
15
+ >
16
+ Go Back
17
+ </button>
18
+ <Link
19
+ to="/"
20
+ className="border border-border px-4 py-2 text-sm font-medium hover:bg-card transition-colors"
21
+ >
22
+ Home
23
+ </Link>
24
+ </div>
25
+ </div>
26
+ )
27
+ }
@@ -0,0 +1,74 @@
1
+ import { Check, Clipboard } from "lucide-react"
2
+ import { useState } from "react"
3
+
4
+ type Manager = "npm" | "pnpm" | "yarn" | "bun"
5
+
6
+ const managers: Manager[] = ["npm", "pnpm", "yarn", "bun"]
7
+
8
+ interface PackageTabsProps {
9
+ npm: string
10
+ pnpm: string
11
+ yarn: string
12
+ bun: string
13
+ }
14
+
15
+ /** Tabbed code block showing a command for each package manager */
16
+ export function PackageTabs(props: PackageTabsProps) {
17
+ const [active, setActive] = useState<Manager>("npm")
18
+ const [copied, setCopied] = useState(false)
19
+
20
+ const commands: Record<Manager, string> = {
21
+ npm: props.npm,
22
+ pnpm: props.pnpm,
23
+ yarn: props.yarn,
24
+ bun: props.bun,
25
+ }
26
+
27
+ const handleCopy = () => {
28
+ navigator.clipboard.writeText(commands[active])
29
+ setCopied(true)
30
+ setTimeout(() => setCopied(false), 2000)
31
+ }
32
+
33
+ return (
34
+ <figure className="not-prose my-4 relative rounded-xl border shadow-sm overflow-hidden text-sm bg-sidebar dark:bg-sidebar">
35
+ <div className="flex items-center border-b">
36
+ <div className="flex flex-1 items-center">
37
+ {managers.map(pm => (
38
+ <button
39
+ key={pm}
40
+ type="button"
41
+ onClick={() => setActive(pm)}
42
+ className={`px-3 py-2 text-xs font-medium transition-colors ${
43
+ pm === active
44
+ ? "text-foreground border-b-2 border-foreground"
45
+ : "text-muted-foreground hover:text-foreground"
46
+ }`}
47
+ >
48
+ {pm}
49
+ </button>
50
+ ))}
51
+ </div>
52
+ <button
53
+ type="button"
54
+ onClick={handleCopy}
55
+ className="me-2 p-1 rounded-md text-muted-foreground hover:text-foreground transition-colors"
56
+ aria-label="Copy code"
57
+ >
58
+ {copied ? (
59
+ <Check className="size-3.5" />
60
+ ) : (
61
+ <Clipboard className="size-3.5" />
62
+ )}
63
+ </button>
64
+ </div>
65
+ <div className="text-[0.8125rem] py-3.5 overflow-auto max-h-[600px]">
66
+ <pre className="min-w-full w-max">
67
+ <code className="flex flex-col">
68
+ <span className="px-4">{commands[active]}</span>
69
+ </code>
70
+ </pre>
71
+ </div>
72
+ </figure>
73
+ )
74
+ }
@@ -0,0 +1,65 @@
1
+ import { useState, useCallback } from "react"
2
+ import { Pencil, Copy, Check, ExternalLink } from "lucide-react"
3
+
4
+ /** Toolbar with action buttons displayed below the article content */
5
+ export function PageToolbar(props: { filePath: string; content: string }) {
6
+ const [copied, setCopied] = useState(false)
7
+
8
+ const handleCopy = useCallback(() => {
9
+ navigator.clipboard.writeText(props.content)
10
+ setCopied(true)
11
+ setTimeout(() => setCopied(false), 2000)
12
+ }, [props.content])
13
+
14
+ const handleOpenIn = useCallback(
15
+ (url: string) => {
16
+ navigator.clipboard.writeText(props.content)
17
+ window.open(url, "_blank", "noopener,noreferrer")
18
+ },
19
+ [props.content],
20
+ )
21
+
22
+ const buttonClass =
23
+ "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
24
+
25
+ return (
26
+ <>
27
+ <div className="mb-6 flex flex-col gap-2 text-sm">
28
+ <p className="text-sm font-medium mb-1">Actions</p>
29
+ <a
30
+ href={`https://github.com/datisthq/livemark/edit/main/${props.filePath}`}
31
+ target="_blank"
32
+ rel="noopener noreferrer"
33
+ className={buttonClass}
34
+ >
35
+ <Pencil className="size-3.5" />
36
+ Edit on GitHub
37
+ </a>
38
+ <button type="button" onClick={handleCopy} className={buttonClass}>
39
+ {copied ? (
40
+ <Check className="size-3.5" />
41
+ ) : (
42
+ <Copy className="size-3.5" />
43
+ )}
44
+ Copy Markdown
45
+ </button>
46
+ <button
47
+ type="button"
48
+ onClick={() => handleOpenIn("https://chatgpt.com/")}
49
+ className={buttonClass}
50
+ >
51
+ <ExternalLink className="size-3.5" />
52
+ Open in ChatGPT
53
+ </button>
54
+ <button
55
+ type="button"
56
+ onClick={() => handleOpenIn("https://claude.ai/")}
57
+ className={buttonClass}
58
+ >
59
+ <ExternalLink className="size-3.5" />
60
+ Open in Claude
61
+ </button>
62
+ </div>
63
+ </>
64
+ )
65
+ }
@@ -0,0 +1,52 @@
1
+ import { Link } from "@tanstack/react-router"
2
+ import { ChevronLeft, ChevronRight } from "lucide-react"
3
+ import { sortedArticles, flatArticles } from "../helpers/articles.ts"
4
+
5
+ /** Previous/next article navigation shown at the bottom of each article page */
6
+ export function PrevNext(props: { pathname: string }) {
7
+ const index = flatArticles.indexOf(props.pathname)
8
+ if (index < 0) return null
9
+
10
+ const prevPath = index > 0 ? flatArticles[index - 1] : undefined
11
+ const nextPath =
12
+ index < flatArticles.length - 1 ? flatArticles[index + 1] : undefined
13
+ const prev = prevPath
14
+ ? sortedArticles.find(a => a.pathname === prevPath)
15
+ : undefined
16
+ const next = nextPath
17
+ ? sortedArticles.find(a => a.pathname === nextPath)
18
+ : undefined
19
+
20
+ if (!prev && !next) return null
21
+
22
+ return (
23
+ <nav className="not-prose mt-10 flex gap-4">
24
+ {prev && (
25
+ <Link
26
+ to="/$"
27
+ params={{ _splat: prev.pathname.replace(/^\/|\/$/g, "") }}
28
+ className="flex-1 rounded-xl border shadow-sm bg-sidebar p-4 hover:bg-background transition-colors"
29
+ >
30
+ <span className="flex items-center gap-1 text-sm text-muted-foreground">
31
+ <ChevronLeft className="size-4" />
32
+ Previous
33
+ </span>
34
+ <span className="mt-1 block font-medium">{prev.title}</span>
35
+ </Link>
36
+ )}
37
+ {next && (
38
+ <Link
39
+ to="/$"
40
+ params={{ _splat: next.pathname.replace(/^\/|\/$/g, "") }}
41
+ className="flex-1 rounded-xl border shadow-sm bg-sidebar p-4 hover:bg-background transition-colors text-right"
42
+ >
43
+ <span className="flex items-center justify-end gap-1 text-sm text-muted-foreground">
44
+ Next
45
+ <ChevronRight className="size-4" />
46
+ </span>
47
+ <span className="mt-1 block font-medium">{next.title}</span>
48
+ </Link>
49
+ )}
50
+ </nav>
51
+ )
52
+ }