livemark 0.25.0 → 0.27.0
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.
- package/README.md +3 -2
- package/build/actions/config/define.d.ts +12 -0
- package/build/actions/config/define.unit.js +8 -1
- package/build/actions/escape/copy.d.ts +11 -0
- package/build/actions/escape/copy.js +29 -0
- package/build/actions/escape/copy.unit.d.ts +1 -0
- package/build/actions/escape/copy.unit.js +37 -0
- package/build/actions/escape/list.d.ts +12 -0
- package/build/actions/escape/list.js +27 -0
- package/build/actions/escape/list.unit.d.ts +1 -0
- package/build/actions/escape/list.unit.js +36 -0
- package/build/actions/target/build.d.ts +9 -26
- package/build/actions/target/build.js +50 -71
- package/build/actions/target/build.unit.d.ts +1 -0
- package/build/actions/target/build.unit.js +76 -0
- package/build/actions/target/clear.d.ts +4 -0
- package/build/actions/target/clear.js +14 -0
- package/build/actions/target/clear.unit.d.ts +1 -0
- package/build/actions/target/clear.unit.js +23 -0
- package/build/commands/build.js +1 -2
- package/build/commands/clear.d.ts +5 -0
- package/build/commands/clear.js +15 -0
- package/build/commands/escape.d.ts +4 -3
- package/build/commands/escape.js +45 -48
- package/build/commands/preview.js +1 -2
- package/build/commands/start.js +1 -2
- package/build/components/Abbr.d.ts +1 -1
- package/build/components/Abbr.js +2 -4
- package/build/components/AnsiCode.d.ts +1 -1
- package/build/components/AnsiCode.js +2 -2
- package/build/components/Article.d.ts +2 -1
- package/build/components/Article.js +7 -14
- package/build/components/BackToTop.d.ts +1 -1
- package/build/components/Banner.d.ts +2 -2
- package/build/components/Banner.js +7 -3
- package/build/components/BlogIndex.d.ts +1 -1
- package/build/components/BlogIndex.js +2 -11
- package/build/components/Breadcrumbs.d.ts +1 -1
- package/build/components/Breadcrumbs.js +2 -3
- package/build/components/Callout.d.ts +1 -1
- package/build/components/Callout.js +5 -9
- package/build/components/Card.d.ts +1 -1
- package/build/components/Card.js +2 -3
- package/build/components/Cards.d.ts +1 -1
- package/build/components/ChangelogIndex.d.ts +1 -1
- package/build/components/ChangelogIndex.js +2 -11
- package/build/components/ChoiceList.d.ts +15 -0
- package/build/components/ChoiceList.js +13 -0
- package/build/components/CodeBlock.d.ts +1 -1
- package/build/components/CodeBlock.js +3 -16
- package/build/components/CodeTabs.d.ts +1 -1
- package/build/components/CodeTabs.js +2 -3
- package/build/components/Column.d.ts +1 -1
- package/build/components/Columns.d.ts +1 -1
- package/build/components/Content.d.ts +1 -1
- package/build/components/ContentTab.d.ts +1 -1
- package/build/components/ContentTabs.d.ts +1 -1
- package/build/components/ContentTabs.js +2 -3
- package/build/components/DefaultCatchBoundary.d.ts +1 -1
- package/build/components/DefaultCatchBoundary.js +5 -7
- package/build/components/DefinitionList.d.ts +3 -3
- package/build/components/Details.d.ts +1 -1
- package/build/components/Details.js +2 -5
- package/build/components/FileTree.d.ts +1 -1
- package/build/components/FileTree.js +3 -5
- package/build/components/Footer.d.ts +1 -1
- package/build/components/Footer.js +2 -5
- package/build/components/FootnoteRef.d.ts +1 -1
- package/build/components/FootnoteRef.js +2 -4
- package/build/components/Header.d.ts +6 -0
- package/build/components/Header.js +26 -0
- package/build/components/HeaderMenu.d.ts +2 -0
- package/build/components/HeaderMenu.js +27 -0
- package/build/components/HeaderNav.d.ts +6 -0
- package/build/components/HeaderNav.js +74 -0
- package/build/components/HeaderSearch.d.ts +4 -0
- package/build/components/HeaderSearch.js +33 -0
- package/build/components/Heading.d.ts +6 -6
- package/build/components/InlineBadge.d.ts +2 -2
- package/build/components/InlineBadge.js +1 -1
- package/build/components/InlineIcon.d.ts +1 -1
- package/build/components/InlineToc.d.ts +1 -1
- package/build/components/InlineToc.js +2 -4
- package/build/components/Layout.d.ts +2 -1
- package/build/components/Layout.js +5 -58
- package/build/components/LinkButton.d.ts +2 -2
- package/build/components/LinkButton.js +1 -1
- package/build/components/MenuSheet.d.ts +22 -0
- package/build/components/MenuSheet.js +27 -0
- package/build/components/Mermaid.d.ts +1 -1
- package/build/components/MermaidRenderer.d.ts +1 -1
- package/build/components/MermaidRenderer.js +20 -8
- package/build/components/NotFound.d.ts +1 -1
- package/build/components/NotFound.js +3 -6
- package/build/components/PackageTabs.d.ts +1 -1
- package/build/components/PackageTabs.js +3 -7
- package/build/components/PageToolbar.d.ts +1 -1
- package/build/components/PageToolbar.js +2 -10
- package/build/components/PrevNext.d.ts +1 -1
- package/build/components/PrevNext.js +2 -10
- package/build/components/Root.d.ts +4 -0
- package/build/components/Root.js +8 -0
- package/build/components/Search.d.ts +5 -2
- package/build/components/Search.js +11 -9
- package/build/components/SearchDialog.d.ts +1 -1
- package/build/components/SearchDialog.js +10 -133
- package/build/components/Section.d.ts +1 -1
- package/build/components/SectionHeader.d.ts +1 -1
- package/build/components/SheetPanel.d.ts +12 -0
- package/build/components/SheetPanel.js +19 -0
- package/build/components/Sidebar.d.ts +1 -1
- package/build/components/Sidebar.js +12 -14
- package/build/components/SidebarArticles.d.ts +4 -1
- package/build/components/SidebarArticles.js +5 -9
- package/build/components/SidebarLinks.d.ts +4 -1
- package/build/components/SidebarLinks.js +6 -9
- package/build/components/SidebarPosts.d.ts +4 -1
- package/build/components/SidebarPosts.js +7 -23
- package/build/components/SidebarReleases.d.ts +4 -1
- package/build/components/SidebarReleases.js +5 -15
- package/build/components/SidebarSectionPicker.d.ts +8 -0
- package/build/components/SidebarSectionPicker.js +31 -0
- package/build/components/SidebarSections.d.ts +1 -1
- package/build/components/SidebarSections.js +5 -9
- package/build/components/SiteLink.d.ts +1 -1
- package/build/components/SiteTitle.d.ts +1 -1
- package/build/components/SiteTitle.js +2 -6
- package/build/components/SortableTable.d.ts +1 -1
- package/build/components/SoundCloud.d.ts +1 -1
- package/build/components/SoundCloud.js +2 -2
- package/build/components/TagIndex.d.ts +1 -1
- package/build/components/TagIndex.js +2 -11
- package/build/components/Theme.d.ts +2 -1
- package/build/components/Theme.js +35 -15
- package/build/components/Toc.d.ts +5 -4
- package/build/components/Toc.js +35 -35
- package/build/components/YouTube.d.ts +1 -1
- package/build/components/YouTube.js +2 -2
- package/build/components/ZoomImage.d.ts +1 -1
- package/build/content/article.js +4 -11
- package/build/content-collections.js +6 -4
- package/build/elements/accordion.d.ts +4 -4
- package/build/elements/accordion.js +2 -3
- package/build/elements/alert-dialog.d.ts +12 -12
- package/build/elements/alert-dialog.js +2 -4
- package/build/elements/alert.d.ts +4 -4
- package/build/elements/alert.js +2 -2
- package/build/elements/aspect-ratio.d.ts +1 -1
- package/build/elements/avatar.d.ts +6 -6
- package/build/elements/badge.d.ts +1 -1
- package/build/elements/badge.js +3 -1
- package/build/elements/breadcrumb.d.ts +6 -6
- package/build/elements/breadcrumb.js +2 -4
- package/build/elements/button-group.d.ts +2 -2
- package/build/elements/button-group.js +5 -5
- package/build/elements/button.d.ts +2 -2
- package/build/elements/button.js +9 -7
- package/build/elements/calendar.d.ts +2 -2
- package/build/elements/calendar.js +2 -2
- package/build/elements/card.d.ts +7 -7
- package/build/elements/card.js +4 -4
- package/build/elements/carousel.d.ts +5 -5
- package/build/elements/carousel.js +3 -7
- package/build/elements/chart.d.ts +6 -6
- package/build/elements/chart.js +5 -11
- package/build/elements/checkbox.d.ts +1 -1
- package/build/elements/collapsible.d.ts +3 -3
- package/build/elements/command.d.ts +9 -9
- package/build/elements/command.js +5 -11
- package/build/elements/context-menu.d.ts +15 -15
- package/build/elements/context-menu.js +6 -9
- package/build/elements/dialog.d.ts +10 -10
- package/build/elements/dialog.js +2 -6
- package/build/elements/drawer.d.ts +14 -12
- package/build/elements/drawer.js +14 -15
- package/build/elements/dropdown-menu.d.ts +15 -15
- package/build/elements/dropdown-menu.js +7 -10
- package/build/elements/empty.d.ts +6 -6
- package/build/elements/empty.js +2 -2
- package/build/elements/field.d.ts +10 -10
- package/build/elements/field.js +3 -4
- package/build/elements/hover-card.d.ts +3 -3
- package/build/elements/hover-card.js +2 -2
- package/build/elements/input-group.d.ts +7 -7
- package/build/elements/input-group.js +5 -5
- package/build/elements/input-otp.d.ts +4 -4
- package/build/elements/input.d.ts +1 -1
- package/build/elements/input.js +2 -2
- package/build/elements/item.d.ts +9 -9
- package/build/elements/item.js +2 -2
- package/build/elements/kbd.d.ts +2 -2
- package/build/elements/label.d.ts +1 -1
- package/build/elements/menubar.d.ts +16 -16
- package/build/elements/menubar.js +9 -11
- package/build/elements/native-select.d.ts +3 -3
- package/build/elements/native-select.js +2 -4
- package/build/elements/navigation-menu.d.ts +8 -8
- package/build/elements/navigation-menu.js +3 -5
- package/build/elements/pagination.d.ts +7 -7
- package/build/elements/pagination.js +4 -10
- package/build/elements/popover.d.ts +6 -6
- package/build/elements/popover.js +2 -2
- package/build/elements/progress.d.ts +5 -5
- package/build/elements/progress.js +2 -3
- package/build/elements/radio-group.d.ts +2 -2
- package/build/elements/resizable.d.ts +3 -3
- package/build/elements/scroll-area.d.ts +2 -2
- package/build/elements/scroll-area.js +2 -4
- package/build/elements/select.d.ts +9 -9
- package/build/elements/select.js +4 -9
- package/build/elements/separator.d.ts +1 -1
- package/build/elements/sheet.d.ts +8 -8
- package/build/elements/sheet.js +2 -6
- package/build/elements/sidebar.d.ts +19 -19
- package/build/elements/sidebar.js +15 -37
- package/build/elements/skeleton.d.ts +1 -1
- package/build/elements/slider.d.ts +2 -1
- package/build/elements/slider.js +2 -3
- package/build/elements/sonner.d.ts +1 -1
- package/build/elements/spinner.d.ts +1 -1
- package/build/elements/switch.d.ts +1 -1
- package/build/elements/table.d.ts +8 -8
- package/build/elements/tabs.d.ts +4 -4
- package/build/elements/tabs.js +3 -3
- package/build/elements/textarea.d.ts +1 -1
- package/build/elements/textarea.js +2 -2
- package/build/elements/toggle-group.d.ts +3 -2
- package/build/elements/toggle-group.js +3 -3
- package/build/elements/toggle.d.ts +1 -1
- package/build/elements/toggle.js +2 -2
- package/build/elements/tooltip.d.ts +4 -4
- package/build/elements/tooltip.js +2 -3
- package/build/head.d.ts +4 -0
- package/build/head.js +6 -0
- package/build/helpers/agent-skills.d.ts +22 -0
- package/build/helpers/agent-skills.js +46 -0
- package/build/helpers/agent-skills.unit.d.ts +1 -0
- package/build/helpers/agent-skills.unit.js +49 -0
- package/build/helpers/article-search.d.ts +19 -0
- package/build/helpers/article-search.js +134 -0
- package/build/helpers/article.d.ts +13 -0
- package/build/helpers/article.js +17 -1
- package/build/helpers/dependency-dir.d.ts +5 -0
- package/build/helpers/dependency-dir.js +20 -0
- package/build/helpers/dependency-dir.unit.d.ts +1 -0
- package/build/helpers/dependency-dir.unit.js +25 -0
- package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
- package/build/helpers/dynamic-icon.d.ts +1 -1
- package/build/helpers/head.d.ts +58 -0
- package/build/helpers/head.js +80 -0
- package/build/helpers/head.unit.d.ts +1 -0
- package/build/helpers/head.unit.js +110 -0
- package/build/helpers/llms.d.ts +33 -0
- package/build/helpers/llms.js +72 -0
- package/build/helpers/llms.unit.d.ts +1 -0
- package/build/helpers/llms.unit.js +98 -0
- package/build/helpers/nav-items.d.ts +11 -0
- package/build/helpers/nav-items.js +65 -0
- package/build/helpers/render-inline-markdown.d.ts +1 -0
- package/build/helpers/render-inline-markdown.js +34 -0
- package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
- package/build/helpers/render-inline-markdown.unit.js +20 -0
- package/build/helpers/search-highlight.d.ts +8 -0
- package/build/helpers/search-highlight.js +59 -0
- package/build/helpers/search-highlight.unit.d.ts +1 -0
- package/build/helpers/search-highlight.unit.js +36 -0
- package/build/helpers/target-export.d.ts +9 -0
- package/build/helpers/target-export.js +24 -0
- package/build/helpers/target-export.unit.d.ts +1 -0
- package/build/helpers/target-export.unit.js +25 -0
- package/build/helpers/theme.d.ts +6 -2
- package/build/helpers/theme.js +40 -7
- package/build/helpers/theme.unit.d.ts +1 -0
- package/build/helpers/theme.unit.js +30 -0
- package/build/hooks/hydrated.d.ts +2 -0
- package/build/hooks/hydrated.js +15 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -1
- package/build/models/config.d.ts +52 -6
- package/build/models/config.js +17 -1
- package/build/models/section.d.ts +4 -2
- package/build/models/section.js +1 -1
- package/build/plugins/vite-agents.d.ts +17 -0
- package/build/plugins/vite-agents.js +109 -0
- package/build/plugins/vite-agents.unit.d.ts +1 -0
- package/build/plugins/vite-agents.unit.js +20 -0
- package/build/plugins/vite-livemark.d.ts +4 -5
- package/build/plugins/vite-livemark.js +56 -90
- package/build/plugins/vite-robots.d.ts +4 -2
- package/build/plugins/vite-robots.js +15 -2
- package/build/plugins/vite-robots.unit.js +5 -1
- package/build/program.js +4 -2
- package/build/routes/$.d.ts +4 -1
- package/build/routes/$.js +20 -9
- package/build/routes/__root.js +29 -63
- package/build/settings.js +2 -1
- package/build/vite.config.js +3 -1
- package/package.json +69 -66
- package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
- package/{.agents/skills → skills}/livemark-configuration/SKILL.md +16 -5
- package/skills/livemark-customization/SKILL.md +146 -0
- package/source/actions/config/define.unit.ts +9 -0
- package/source/actions/escape/copy.ts +35 -0
- package/source/actions/escape/copy.unit.ts +51 -0
- package/source/actions/escape/list.ts +30 -0
- package/source/actions/escape/list.unit.ts +42 -0
- package/source/actions/target/build.ts +49 -69
- package/source/actions/target/build.unit.ts +95 -0
- package/source/actions/target/clear.ts +14 -0
- package/source/actions/target/clear.unit.ts +25 -0
- package/source/commands/build.ts +0 -1
- package/source/commands/clear.ts +15 -0
- package/source/commands/escape.ts +42 -59
- package/source/commands/preview.ts +0 -1
- package/source/commands/start.ts +0 -1
- package/source/components/AnsiCode.tsx +1 -1
- package/source/components/Article.tsx +8 -4
- package/source/components/Banner.tsx +12 -2
- package/source/components/BlogIndex.tsx +1 -1
- package/source/components/Callout.tsx +4 -6
- package/source/components/ChangelogIndex.tsx +1 -1
- package/source/components/ChoiceList.tsx +69 -0
- package/source/components/CodeBlock.tsx +6 -6
- package/source/components/CodeTabs.tsx +1 -1
- package/source/components/ContentTabs.tsx +1 -1
- package/source/components/DefaultCatchBoundary.tsx +4 -6
- package/source/components/Details.tsx +1 -1
- package/source/components/FileTree.tsx +1 -1
- package/source/components/Header.tsx +52 -0
- package/source/components/HeaderMenu.tsx +41 -0
- package/source/components/HeaderNav.tsx +145 -0
- package/source/components/HeaderSearch.tsx +77 -0
- package/source/components/InlineBadge.tsx +1 -1
- package/source/components/InlineToc.tsx +1 -1
- package/source/components/Layout.tsx +16 -160
- package/source/components/LinkButton.tsx +9 -1
- package/source/components/MenuSheet.tsx +92 -0
- package/source/components/MermaidRenderer.tsx +19 -8
- package/source/components/NotFound.tsx +3 -5
- package/source/components/PackageTabs.tsx +2 -2
- package/source/components/PageToolbar.tsx +11 -3
- package/source/components/PrevNext.tsx +2 -2
- package/source/components/Root.tsx +13 -0
- package/source/components/Search.tsx +28 -8
- package/source/components/SearchDialog.tsx +15 -189
- package/source/components/SheetPanel.tsx +69 -0
- package/source/components/Sidebar.tsx +27 -14
- package/source/components/SidebarArticles.tsx +4 -3
- package/source/components/SidebarLinks.tsx +3 -2
- package/source/components/SidebarPosts.tsx +3 -2
- package/source/components/SidebarReleases.tsx +3 -2
- package/source/components/SidebarSectionPicker.tsx +70 -0
- package/source/components/SiteTitle.tsx +5 -5
- package/source/components/SoundCloud.tsx +1 -1
- package/source/components/TagIndex.tsx +1 -1
- package/source/components/Theme.tsx +55 -29
- package/source/components/Toc.tsx +76 -55
- package/source/components/YouTube.tsx +1 -1
- package/source/content/article.ts +8 -13
- package/source/content-collections.ts +5 -4
- package/source/elements/alert-dialog.tsx +1 -1
- package/source/elements/alert.tsx +1 -1
- package/source/elements/badge.tsx +4 -0
- package/source/elements/button-group.tsx +4 -4
- package/source/elements/button.tsx +10 -6
- package/source/elements/calendar.tsx +1 -1
- package/source/elements/card.tsx +3 -3
- package/source/elements/command.tsx +7 -4
- package/source/elements/context-menu.tsx +5 -5
- package/source/elements/dialog.tsx +1 -1
- package/source/elements/drawer.tsx +59 -43
- package/source/elements/dropdown-menu.tsx +6 -6
- package/source/elements/empty.tsx +1 -1
- package/source/elements/field.tsx +1 -1
- package/source/elements/hover-card.tsx +1 -1
- package/source/elements/input-group.tsx +4 -4
- package/source/elements/input.tsx +1 -1
- package/source/elements/item.tsx +1 -1
- package/source/elements/menubar.tsx +8 -8
- package/source/elements/native-select.tsx +1 -1
- package/source/elements/popover.tsx +1 -1
- package/source/elements/select.tsx +3 -3
- package/source/elements/sidebar.tsx +8 -21
- package/source/elements/tabs.tsx +2 -2
- package/source/elements/textarea.tsx +1 -1
- package/source/elements/toggle-group.tsx +2 -2
- package/source/elements/toggle.tsx +1 -1
- package/source/env.d.ts +1 -0
- package/source/head.ts +7 -0
- package/source/helpers/agent-skills.ts +59 -0
- package/source/helpers/agent-skills.unit.ts +56 -0
- package/source/helpers/article-search.tsx +192 -0
- package/source/helpers/article.ts +17 -0
- package/source/helpers/dependency-dir.ts +20 -0
- package/source/helpers/dependency-dir.unit.ts +39 -0
- package/source/helpers/head.ts +105 -0
- package/source/helpers/head.unit.ts +118 -0
- package/source/helpers/llms.ts +109 -0
- package/source/helpers/llms.unit.ts +115 -0
- package/source/helpers/nav-items.tsx +102 -0
- package/source/helpers/render-inline-markdown.ts +44 -0
- package/source/helpers/render-inline-markdown.unit.ts +30 -0
- package/source/helpers/search-highlight.ts +73 -0
- package/source/helpers/search-highlight.unit.ts +42 -0
- package/source/helpers/target-export.ts +28 -0
- package/source/helpers/target-export.unit.ts +37 -0
- package/source/helpers/theme.ts +40 -6
- package/source/helpers/theme.unit.ts +32 -0
- package/source/hooks/hydrated.ts +18 -0
- package/source/index.ts +1 -0
- package/source/models/config.ts +24 -0
- package/source/models/section.ts +3 -0
- package/source/plugins/vite-agents.ts +135 -0
- package/source/plugins/vite-agents.unit.ts +26 -0
- package/source/plugins/vite-livemark.ts +54 -95
- package/source/plugins/vite-robots.ts +18 -3
- package/source/plugins/vite-robots.unit.ts +7 -0
- package/source/program.ts +2 -0
- package/source/routes/$.tsx +27 -9
- package/source/routes/__root.tsx +51 -71
- package/source/settings.ts +1 -0
- package/source/styles/general.css +59 -11
- package/source/styles/markdown.css +13 -7
- package/source/styles/shape.css +14 -0
- package/source/vite.config.ts +2 -0
- package/.agents/skills/change-review/SKILL.md +0 -47
- package/.agents/skills/livemark-customization/SKILL.md +0 -118
|
@@ -1,127 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useNavigate } from "@tanstack/react-router"
|
|
3
|
-
import { create, insertMultiple, search } from "@orama/orama"
|
|
4
|
-
import { sortedArticles } from "../content/article.ts"
|
|
5
|
-
import { resolveArticleIcon } from "../helpers/article-icon.ts"
|
|
1
|
+
import { SearchResultGroups, useArticleSearch } from "../helpers/article-search.tsx"
|
|
6
2
|
import {
|
|
7
3
|
CommandDialog,
|
|
8
4
|
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
5
|
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
6
|
CommandList,
|
|
13
7
|
} from "../elements/command.tsx"
|
|
14
8
|
|
|
15
|
-
interface SearchResult {
|
|
16
|
-
title: string
|
|
17
|
-
path: string
|
|
18
|
-
icon?: string
|
|
19
|
-
snippet: string
|
|
20
|
-
group: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
/** Full-text search dialog powered by Orama */
|
|
24
10
|
export function SearchDialog(props: {
|
|
25
11
|
open: boolean
|
|
26
12
|
onOpenChange: (open: boolean) => void
|
|
27
13
|
}) {
|
|
28
|
-
const
|
|
29
|
-
const [query, setQuery] = useState("")
|
|
30
|
-
const dbRef = useRef<Awaited<ReturnType<typeof create>> | null>(null)
|
|
31
|
-
const navigate = useNavigate()
|
|
32
|
-
|
|
33
|
-
const ensureIndex = useCallback(async () => {
|
|
34
|
-
if (dbRef.current) return dbRef.current
|
|
35
|
-
const db = await create({
|
|
36
|
-
schema: {
|
|
37
|
-
title: "string",
|
|
38
|
-
description: "string",
|
|
39
|
-
searchText: "string",
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
await insertMultiple(
|
|
43
|
-
db,
|
|
44
|
-
sortedArticles.map(a => ({
|
|
45
|
-
title: a.title,
|
|
46
|
-
description: a.description ?? "",
|
|
47
|
-
searchText: a.searchText,
|
|
48
|
-
})),
|
|
49
|
-
)
|
|
50
|
-
dbRef.current = db
|
|
51
|
-
return db
|
|
52
|
-
}, [])
|
|
53
|
-
|
|
54
|
-
const toResult = useCallback(
|
|
55
|
-
(article: (typeof sortedArticles)[number], term: string): SearchResult => ({
|
|
56
|
-
title: article.title,
|
|
57
|
-
path: article.path,
|
|
58
|
-
icon: article.icon,
|
|
59
|
-
snippet: term
|
|
60
|
-
? extractSnippet(article.searchText, term)
|
|
61
|
-
: (article.description ?? ""),
|
|
62
|
-
group: groupFor(article.path),
|
|
63
|
-
}),
|
|
64
|
-
[],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const handleSearch = useCallback(
|
|
68
|
-
async (term: string) => {
|
|
69
|
-
setQuery(term)
|
|
70
|
-
if (!term.trim()) {
|
|
71
|
-
setResults([])
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
const db = await ensureIndex()
|
|
75
|
-
const hits = await search(db, {
|
|
76
|
-
term,
|
|
77
|
-
properties: ["title", "description", "searchText"],
|
|
78
|
-
tolerance: 1,
|
|
79
|
-
boost: { title: 3, description: 2 },
|
|
80
|
-
})
|
|
81
|
-
setResults(
|
|
82
|
-
hits.hits.map(hit => {
|
|
83
|
-
const article = sortedArticles.find(a => a.title === hit.document.title)
|
|
84
|
-
if (!article)
|
|
85
|
-
return toResult(
|
|
86
|
-
{
|
|
87
|
-
...sortedArticles[0],
|
|
88
|
-
title: hit.document.title,
|
|
89
|
-
path: "",
|
|
90
|
-
searchText: hit.document.description,
|
|
91
|
-
description: hit.document.description,
|
|
92
|
-
icon: "",
|
|
93
|
-
},
|
|
94
|
-
term,
|
|
95
|
-
)
|
|
96
|
-
return toResult(article, term)
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
},
|
|
100
|
-
[ensureIndex, toResult],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const handleSelect = useCallback(
|
|
104
|
-
(path: string) => {
|
|
105
|
-
props.onOpenChange(false)
|
|
106
|
-
setQuery("")
|
|
107
|
-
setResults([])
|
|
108
|
-
navigate({
|
|
109
|
-
to: "/$",
|
|
110
|
-
params: { _splat: path.replace(/^\/|\/$/g, "") },
|
|
111
|
-
})
|
|
112
|
-
},
|
|
113
|
-
[navigate, props.onOpenChange],
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
const grouped = useMemo(() => {
|
|
117
|
-
const groups = new Map<string, SearchResult[]>()
|
|
118
|
-
for (const r of results) {
|
|
119
|
-
const list = groups.get(r.group) ?? []
|
|
120
|
-
list.push(r)
|
|
121
|
-
groups.set(r.group, list)
|
|
122
|
-
}
|
|
123
|
-
return groups
|
|
124
|
-
}, [results])
|
|
14
|
+
const search = useArticleSearch()
|
|
125
15
|
|
|
126
16
|
return (
|
|
127
17
|
<CommandDialog
|
|
@@ -134,43 +24,24 @@ export function SearchDialog(props: {
|
|
|
134
24
|
>
|
|
135
25
|
<CommandInput
|
|
136
26
|
placeholder="Search docs..."
|
|
137
|
-
value={query}
|
|
138
|
-
onValueChange={
|
|
139
|
-
onClear={() =>
|
|
27
|
+
value={search.query}
|
|
28
|
+
onValueChange={search.searchArticles}
|
|
29
|
+
onClear={() => search.searchArticles("")}
|
|
140
30
|
/>
|
|
141
31
|
<CommandList className="max-h-80">
|
|
142
32
|
<CommandEmpty>
|
|
143
|
-
{query.trim()
|
|
33
|
+
{search.query.trim()
|
|
144
34
|
? "No results found."
|
|
145
|
-
: "Type to search across all
|
|
35
|
+
: "Type to search across all contents."}
|
|
146
36
|
</CommandEmpty>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
onSelect={() => handleSelect(result.path)}
|
|
156
|
-
className="py-2.5"
|
|
157
|
-
>
|
|
158
|
-
{Icon && (
|
|
159
|
-
<Icon className="size-5 shrink-0 mt-0.5 self-start opacity-60" />
|
|
160
|
-
)}
|
|
161
|
-
<div className="flex flex-col gap-0.5 min-w-0">
|
|
162
|
-
<span className="text-base font-medium">{result.title}</span>
|
|
163
|
-
{result.snippet && (
|
|
164
|
-
<span className="text-sm text-muted-foreground line-clamp-1">
|
|
165
|
-
<HighlightedText text={result.snippet} term={query} />
|
|
166
|
-
</span>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
</CommandItem>
|
|
170
|
-
)
|
|
171
|
-
})}
|
|
172
|
-
</CommandGroup>
|
|
173
|
-
))}
|
|
37
|
+
<SearchResultGroups
|
|
38
|
+
query={search.query}
|
|
39
|
+
grouped={search.grouped}
|
|
40
|
+
onSelect={path => {
|
|
41
|
+
props.onOpenChange(false)
|
|
42
|
+
search.selectArticle(path)
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
174
45
|
</CommandList>
|
|
175
46
|
<div className="flex items-center gap-4 border-t mt-2 px-4 py-2.5 text-sm text-muted-foreground">
|
|
176
47
|
<span className="flex items-center gap-1.5">
|
|
@@ -195,48 +66,3 @@ export function SearchDialog(props: {
|
|
|
195
66
|
</CommandDialog>
|
|
196
67
|
)
|
|
197
68
|
}
|
|
198
|
-
|
|
199
|
-
function groupFor(pathname: string) {
|
|
200
|
-
const segments = pathname.replace(/^\/|\/$/g, "").split("/")
|
|
201
|
-
if (segments.length <= 2) return "Pages"
|
|
202
|
-
return segments
|
|
203
|
-
.slice(1, -1)
|
|
204
|
-
.map(s =>
|
|
205
|
-
s
|
|
206
|
-
.split("-")
|
|
207
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
208
|
-
.join(" "),
|
|
209
|
-
)
|
|
210
|
-
.join(" › ")
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function extractSnippet(text: string, term: string) {
|
|
214
|
-
const lower = text.toLowerCase()
|
|
215
|
-
const idx = lower.indexOf(term.toLowerCase())
|
|
216
|
-
if (idx === -1) return text.slice(0, 120)
|
|
217
|
-
const start = Math.max(0, idx - 40)
|
|
218
|
-
const end = Math.min(text.length, idx + term.length + 80)
|
|
219
|
-
const prefix = start > 0 ? "…" : ""
|
|
220
|
-
const suffix = end < text.length ? "…" : ""
|
|
221
|
-
return `${prefix}${text.slice(start, end)}${suffix}`
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function HighlightedText(props: { text: string; term: string }) {
|
|
225
|
-
if (!props.term.trim()) return <>{props.text}</>
|
|
226
|
-
const escaped = props.term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
227
|
-
const regex = new RegExp(`(${escaped})`, "gi")
|
|
228
|
-
const parts = props.text.split(regex)
|
|
229
|
-
return (
|
|
230
|
-
<>
|
|
231
|
-
{parts.map((part, i) =>
|
|
232
|
-
regex.test(part) ? (
|
|
233
|
-
<span key={i} className="text-foreground font-medium">
|
|
234
|
-
{part}
|
|
235
|
-
</span>
|
|
236
|
-
) : (
|
|
237
|
-
part
|
|
238
|
-
),
|
|
239
|
-
)}
|
|
240
|
-
</>
|
|
241
|
-
)
|
|
242
|
-
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { XIcon } from "lucide-react"
|
|
2
|
+
import { useRef, type ReactNode } from "react"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
import { Button } from "../elements/button.tsx"
|
|
5
|
+
import {
|
|
6
|
+
Drawer,
|
|
7
|
+
DrawerClose,
|
|
8
|
+
DrawerContent,
|
|
9
|
+
DrawerHeader,
|
|
10
|
+
DrawerTitle,
|
|
11
|
+
} from "../elements/drawer.tsx"
|
|
12
|
+
|
|
13
|
+
/** A phone's bottom sheet: title left, close right, a scrolling body and an
|
|
14
|
+
* optional pinned footer. */
|
|
15
|
+
export function SheetPanel(props: {
|
|
16
|
+
open: boolean
|
|
17
|
+
onOpenChange: (open: boolean) => void
|
|
18
|
+
onOpenChangeComplete?: (open: boolean) => void
|
|
19
|
+
title: ReactNode
|
|
20
|
+
footer?: ReactNode
|
|
21
|
+
bodyClassName?: string
|
|
22
|
+
children: ReactNode
|
|
23
|
+
}) {
|
|
24
|
+
const panelRef = useRef<HTMLDivElement>(null)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Drawer
|
|
28
|
+
open={props.open}
|
|
29
|
+
onOpenChange={open => props.onOpenChange(open)}
|
|
30
|
+
onOpenChangeComplete={props.onOpenChangeComplete}
|
|
31
|
+
>
|
|
32
|
+
<DrawerContent
|
|
33
|
+
// The body, never its first link: focusing that scrolls the list to it
|
|
34
|
+
// and paints a focus ring the tap never asked for.
|
|
35
|
+
initialFocus={panelRef}
|
|
36
|
+
showCloseButton={false}
|
|
37
|
+
className="max-h-[85dvh] gap-0"
|
|
38
|
+
>
|
|
39
|
+
<DrawerHeader className="shrink-0 flex-row items-center gap-2 pb-2">
|
|
40
|
+
<DrawerTitle className="line-clamp-1 text-lg font-semibold">
|
|
41
|
+
{props.title}
|
|
42
|
+
</DrawerTitle>
|
|
43
|
+
<DrawerClose
|
|
44
|
+
render={<Button variant="ghost" size="icon-sm" className="-mr-2 ml-auto" />}
|
|
45
|
+
>
|
|
46
|
+
<XIcon className="size-5" />
|
|
47
|
+
<span className="sr-only">Close</span>
|
|
48
|
+
</DrawerClose>
|
|
49
|
+
</DrawerHeader>
|
|
50
|
+
<div
|
|
51
|
+
ref={panelRef}
|
|
52
|
+
tabIndex={-1}
|
|
53
|
+
className={cn(
|
|
54
|
+
"min-h-0 flex-1 overflow-y-auto px-4 pt-2 text-base outline-none empty:hidden",
|
|
55
|
+
props.footer ? "pb-2" : "pb-[max(1.5rem,env(safe-area-inset-bottom))]",
|
|
56
|
+
props.bodyClassName,
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{props.children}
|
|
60
|
+
</div>
|
|
61
|
+
{props.footer && (
|
|
62
|
+
<div className="shrink-0 px-4 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
63
|
+
{props.footer}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</DrawerContent>
|
|
67
|
+
</Drawer>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { config } from "livemark:virtual"
|
|
2
|
+
import { useState } from "react"
|
|
2
3
|
import { useLocation, useMatch } from "@tanstack/react-router"
|
|
3
4
|
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
4
5
|
import { currentSection } from "../content/article.ts"
|
|
@@ -6,7 +7,6 @@ import { activeSiteLink } from "../helpers/section.ts"
|
|
|
6
7
|
import {
|
|
7
8
|
Sidebar as SidebarRoot,
|
|
8
9
|
SidebarContent,
|
|
9
|
-
SidebarFooter,
|
|
10
10
|
SidebarHeader,
|
|
11
11
|
SidebarMenu,
|
|
12
12
|
SidebarMenuButton,
|
|
@@ -14,15 +14,15 @@ import {
|
|
|
14
14
|
SidebarRail,
|
|
15
15
|
useSidebar,
|
|
16
16
|
} from "../elements/sidebar.tsx"
|
|
17
|
-
import { Search } from "./Search.tsx"
|
|
18
17
|
import { SidebarArticles } from "./SidebarArticles.tsx"
|
|
19
18
|
import { SidebarLinks } from "./SidebarLinks.tsx"
|
|
20
19
|
import { SidebarPosts } from "./SidebarPosts.tsx"
|
|
21
20
|
import { SidebarReleases } from "./SidebarReleases.tsx"
|
|
21
|
+
import { SidebarSectionPicker } from "./SidebarSectionPicker.tsx"
|
|
22
22
|
import { SidebarSections } from "./SidebarSections.tsx"
|
|
23
23
|
import { SiteLink } from "./SiteLink.tsx"
|
|
24
24
|
import { SiteTitle } from "./SiteTitle.tsx"
|
|
25
|
-
import {
|
|
25
|
+
import type { RoutedSection } from "../models/section.ts"
|
|
26
26
|
|
|
27
27
|
/** Application sidebar shell: header, body slot picked by section.type, footer */
|
|
28
28
|
export function Sidebar(props: { withSidebar?: boolean }) {
|
|
@@ -30,13 +30,19 @@ export function Sidebar(props: { withSidebar?: boolean }) {
|
|
|
30
30
|
const articleRoute = useMatch({ from: "/$", shouldThrow: false })
|
|
31
31
|
const { toggleSidebar, isMobile } = useSidebar()
|
|
32
32
|
|
|
33
|
-
useHotkey("
|
|
33
|
+
useHotkey("L", () => {
|
|
34
34
|
if (props.withSidebar || isMobile) toggleSidebar()
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
const
|
|
37
|
+
const [browsed, setBrowsed] = useState<{ section: RoutedSection; pathname: string }>()
|
|
38
|
+
|
|
39
|
+
const routeSection = articleRoute
|
|
38
40
|
? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
39
41
|
: undefined
|
|
42
|
+
const picking = isMobile && routeSection !== undefined
|
|
43
|
+
// Browsing is scoped to the page it started on, so navigating resets it
|
|
44
|
+
const section =
|
|
45
|
+
picking && browsed?.pathname === pathname ? browsed.section : routeSection
|
|
40
46
|
const siteLinkTarget = activeSiteLink(pathname, config.sections ?? [])
|
|
41
47
|
|
|
42
48
|
return (
|
|
@@ -51,18 +57,25 @@ export function Sidebar(props: { withSidebar?: boolean }) {
|
|
|
51
57
|
</SidebarMenu>
|
|
52
58
|
</SidebarHeader>
|
|
53
59
|
<SidebarContent>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
{picking && section ? (
|
|
61
|
+
<SidebarSectionPicker
|
|
62
|
+
section={section}
|
|
63
|
+
onSelect={picked => setBrowsed({ section: picked, pathname })}
|
|
64
|
+
/>
|
|
65
|
+
) : (
|
|
66
|
+
<SidebarSections />
|
|
67
|
+
)}
|
|
68
|
+
{props.withSidebar && section?.type === "blog" && (
|
|
69
|
+
<SidebarPosts section={section} />
|
|
70
|
+
)}
|
|
71
|
+
{props.withSidebar && section?.type === "changelog" && (
|
|
72
|
+
<SidebarReleases section={section} />
|
|
73
|
+
)}
|
|
57
74
|
{props.withSidebar &&
|
|
58
75
|
section?.type !== "blog" &&
|
|
59
|
-
section?.type !== "changelog" && <SidebarArticles />}
|
|
60
|
-
<SidebarLinks />
|
|
76
|
+
section?.type !== "changelog" && <SidebarArticles section={section} />}
|
|
77
|
+
<SidebarLinks section={section} />
|
|
61
78
|
</SidebarContent>
|
|
62
|
-
<SidebarFooter className="p-4 space-y-2">
|
|
63
|
-
<Search />
|
|
64
|
-
<Theme />
|
|
65
|
-
</SidebarFooter>
|
|
66
79
|
<SidebarRail />
|
|
67
80
|
</SidebarRoot>
|
|
68
81
|
)
|
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
} from "../elements/sidebar.tsx"
|
|
21
21
|
import { resolveArticleIcon } from "../helpers/article-icon.ts"
|
|
22
22
|
import type { ArticleNode } from "../models/article.ts"
|
|
23
|
+
import type { RoutedSection } from "../models/section.ts"
|
|
23
24
|
|
|
24
25
|
/** Article-tree body slot for the Sidebar shell */
|
|
25
|
-
export function SidebarArticles() {
|
|
26
|
+
export function SidebarArticles(props: { section?: RoutedSection }) {
|
|
26
27
|
const pathname = useLocation({ select: l => l.pathname })
|
|
27
28
|
|
|
28
29
|
const configSections = config.sections
|
|
29
|
-
const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
30
|
+
const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
30
31
|
const tree = configSections?.length
|
|
31
32
|
? (sectionArticleTrees.get(section?.prefix ?? "__default__") ?? articleTree)
|
|
32
33
|
: articleTree
|
|
@@ -88,7 +89,7 @@ function NavNode(props: { node: ArticleNode; currentPath: string; depth?: number
|
|
|
88
89
|
</Button>
|
|
89
90
|
<CollapsibleTrigger
|
|
90
91
|
aria-label={`Toggle ${node.label ?? node.title} submenu`}
|
|
91
|
-
className="absolute right-1 top-1.5 p-1 rounded-
|
|
92
|
+
className="absolute right-1 top-1.5 p-1 rounded-control hover:bg-sidebar-accent"
|
|
92
93
|
>
|
|
93
94
|
<ChevronRight className="size-3.5 transition-transform group-data-[open]/collapsible:rotate-90" />
|
|
94
95
|
</CollapsibleTrigger>
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
SidebarMenuButton,
|
|
12
12
|
SidebarMenuItem,
|
|
13
13
|
} from "../elements/sidebar.tsx"
|
|
14
|
+
import type { RoutedSection } from "../models/section.ts"
|
|
14
15
|
|
|
15
16
|
/** Shared Links group. Renders sidebar-positioned links always, and
|
|
16
17
|
* header-positioned links only on mobile (mobile collapses all chrome
|
|
17
18
|
* links into the sidebar since the header strip is hidden below `md`). */
|
|
18
|
-
export function SidebarLinks() {
|
|
19
|
+
export function SidebarLinks(props: { section?: RoutedSection }) {
|
|
19
20
|
const pathname = useLocation({ select: l => l.pathname })
|
|
20
|
-
const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
21
|
+
const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
21
22
|
const links =
|
|
22
23
|
config.links?.filter(link => !link.prefix || link.prefix === section?.prefix) ?? []
|
|
23
24
|
|
|
@@ -17,12 +17,13 @@ import {
|
|
|
17
17
|
SidebarMenuSubButton,
|
|
18
18
|
SidebarMenuSubItem,
|
|
19
19
|
} from "../elements/sidebar.tsx"
|
|
20
|
+
import type { RoutedSection } from "../models/section.ts"
|
|
20
21
|
|
|
21
22
|
/** Blog body slot for the Sidebar shell: All Posts / Recent Posts / RSS / Tags */
|
|
22
|
-
export function SidebarPosts() {
|
|
23
|
+
export function SidebarPosts(props: { section?: RoutedSection }) {
|
|
23
24
|
const pathname = useLocation({ select: l => l.pathname })
|
|
24
25
|
|
|
25
|
-
const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
26
|
+
const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
26
27
|
const flat = section ? (sectionFlatArticles.get(section.prefix) ?? []) : []
|
|
27
28
|
const posts = flat
|
|
28
29
|
.map(p => sortedArticles.find(a => a.path === p))
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
SidebarMenuSubButton,
|
|
17
17
|
SidebarMenuSubItem,
|
|
18
18
|
} from "../elements/sidebar.tsx"
|
|
19
|
+
import type { RoutedSection } from "../models/section.ts"
|
|
19
20
|
|
|
20
21
|
/** Changelog body slot for the Sidebar shell: All Releases / Recent Releases */
|
|
21
|
-
export function SidebarReleases() {
|
|
22
|
+
export function SidebarReleases(props: { section?: RoutedSection }) {
|
|
22
23
|
const pathname = useLocation({ select: l => l.pathname })
|
|
23
24
|
|
|
24
|
-
const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
25
|
+
const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
25
26
|
const flat = section ? (sectionFlatArticles.get(section.prefix) ?? []) : []
|
|
26
27
|
const entries = flat
|
|
27
28
|
.map(p => sortedArticles.find(a => a.path === p))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { config } from "livemark:virtual"
|
|
2
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
3
|
+
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
4
|
+
import { prefixUrl } from "../helpers/prefix-url.ts"
|
|
5
|
+
import {
|
|
6
|
+
SidebarGroup,
|
|
7
|
+
SidebarGroupContent,
|
|
8
|
+
SidebarGroupLabel,
|
|
9
|
+
} from "../elements/sidebar.tsx"
|
|
10
|
+
import type { RoutedSection, Section } from "../models/section.ts"
|
|
11
|
+
import { MenuSheet } from "./MenuSheet.tsx"
|
|
12
|
+
|
|
13
|
+
/** Mobile Sections group: a select-like trigger opening a bottom sheet that swaps the
|
|
14
|
+
* sidebar body to another section without navigating; custom sections still navigate
|
|
15
|
+
* to their URL. */
|
|
16
|
+
export function SidebarSectionPicker(props: {
|
|
17
|
+
section: RoutedSection
|
|
18
|
+
onSelect: (section: RoutedSection) => void
|
|
19
|
+
}) {
|
|
20
|
+
const sections = config.sections ?? []
|
|
21
|
+
|
|
22
|
+
const selectSection = (picked: Section) => {
|
|
23
|
+
if (picked.type === "custom") {
|
|
24
|
+
window.location.assign(prefixUrl(picked.url, config.base))
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
props.onSelect(picked)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<SidebarGroup>
|
|
32
|
+
<SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
|
|
33
|
+
Sections
|
|
34
|
+
</SidebarGroupLabel>
|
|
35
|
+
<SidebarGroupContent>
|
|
36
|
+
<MenuSheet
|
|
37
|
+
title="Choose section"
|
|
38
|
+
items={sections.map(s => ({
|
|
39
|
+
id: sectionKey(s),
|
|
40
|
+
label: s.title,
|
|
41
|
+
icon: s.icon ? <DynamicIcon name={s.icon} className="size-4" /> : undefined,
|
|
42
|
+
active: s.type !== "custom" && s.prefix === props.section.prefix,
|
|
43
|
+
onSelect: () => selectSection(s),
|
|
44
|
+
}))}
|
|
45
|
+
trigger={
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
aria-label="Section"
|
|
49
|
+
aria-haspopup="dialog"
|
|
50
|
+
className="flex h-9 w-full min-w-0 cursor-pointer items-center gap-2 rounded-control border border-input bg-transparent px-3 text-left text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
{props.section.icon && (
|
|
55
|
+
<DynamicIcon name={props.section.icon} className="size-4 shrink-0" />
|
|
56
|
+
)}
|
|
57
|
+
<span className="flex-1 truncate">{props.section.title}</span>
|
|
58
|
+
<ChevronDownIcon
|
|
59
|
+
className="size-4 shrink-0 text-muted-foreground"
|
|
60
|
+
aria-hidden="true"
|
|
61
|
+
/>
|
|
62
|
+
</MenuSheet>
|
|
63
|
+
</SidebarGroupContent>
|
|
64
|
+
</SidebarGroup>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sectionKey(section: Section) {
|
|
69
|
+
return section.type === "custom" ? `url:${section.url}` : section.prefix
|
|
70
|
+
}
|
|
@@ -14,11 +14,11 @@ export function SiteTitle() {
|
|
|
14
14
|
const description = section?.siteDescription ?? config.description
|
|
15
15
|
const logo = prefixUrl(config.logo ?? defaultLogo, config.base)
|
|
16
16
|
return (
|
|
17
|
-
<div className="flex items-end gap-2.5 text-sm">
|
|
18
|
-
<img src={logo} alt={title} className="size-6.5 mb-1.25" />
|
|
19
|
-
<div className="flex flex-col gap-0.5 leading-none">
|
|
20
|
-
<span className="font-semibold">{title}</span>
|
|
21
|
-
<span className="text-xs opacity-80">{description}</span>
|
|
17
|
+
<div className="flex min-w-0 items-end gap-2.5 text-sm">
|
|
18
|
+
<img src={logo} alt={title} className="size-6.5 mb-1.25 shrink-0" />
|
|
19
|
+
<div className="flex min-w-0 flex-col gap-0.5 leading-none">
|
|
20
|
+
<span className="truncate font-semibold">{title}</span>
|
|
21
|
+
<span className="truncate text-xs opacity-80">{description}</span>
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
24
|
)
|
|
@@ -3,7 +3,7 @@ export function SoundCloud(props: { url: string }) {
|
|
|
3
3
|
const src = `https://w.soundcloud.com/player/?url=${encodeURIComponent(props.url)}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false`
|
|
4
4
|
|
|
5
5
|
return (
|
|
6
|
-
<div className="not-prose my-4 overflow-hidden rounded-
|
|
6
|
+
<div className="not-prose my-4 overflow-hidden rounded-card border">
|
|
7
7
|
<iframe
|
|
8
8
|
className="w-full"
|
|
9
9
|
height={166}
|
|
@@ -39,7 +39,7 @@ export function TagIndex(props: { sectionPrefix: string; tag: string }) {
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<TocContext.Provider value={tocItems}>
|
|
42
|
-
<MobileToc items={tocItems}>
|
|
42
|
+
<MobileToc items={tocItems} title={title}>
|
|
43
43
|
<PageToolbar content={content} />
|
|
44
44
|
</MobileToc>
|
|
45
45
|
<div className="flex flex-1 gap-10 p-4 pt-8 md:p-10">
|
|
@@ -1,39 +1,65 @@
|
|
|
1
|
-
import { Moon, Sun } from "lucide-react"
|
|
1
|
+
import { Moon, Sun, SunMoon } from "lucide-react"
|
|
2
2
|
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
3
|
+
import { useState } from "react"
|
|
3
4
|
import { Button } from "../elements/button.tsx"
|
|
4
|
-
import {
|
|
5
|
+
import type { ThemePref } from "../helpers/theme.ts"
|
|
6
|
+
import { getCurrentTheme, getThemePref, setTheme } from "../helpers/theme.ts"
|
|
7
|
+
import { MenuSheet } from "./MenuSheet.tsx"
|
|
5
8
|
|
|
9
|
+
/** Header menu choosing light, dark or system theme; "T" flips light and dark */
|
|
6
10
|
export function Theme() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
11
|
+
// Read as the menu opens: on the server there is no stored preference to read.
|
|
12
|
+
const [current, setCurrent] = useState<ThemePref>("system")
|
|
13
|
+
|
|
14
|
+
useHotkey("T", () => setTheme(getCurrentTheme() === "light" ? "dark" : "light"))
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
const choose = (pref: ThemePref) => {
|
|
17
|
+
setCurrent(pref)
|
|
18
|
+
setTheme(pref)
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
<MenuSheet
|
|
23
|
+
title="Change theme"
|
|
24
|
+
onOpenChange={open => open && setCurrent(getThemePref())}
|
|
25
|
+
items={[
|
|
26
|
+
{
|
|
27
|
+
id: "light",
|
|
28
|
+
label: "Light",
|
|
29
|
+
icon: <Sun className="size-4 shrink-0" />,
|
|
30
|
+
active: current === "light",
|
|
31
|
+
onSelect: () => choose("light"),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "dark",
|
|
35
|
+
label: "Dark",
|
|
36
|
+
icon: <Moon className="size-4 shrink-0" />,
|
|
37
|
+
active: current === "dark",
|
|
38
|
+
onSelect: () => choose("dark"),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "system",
|
|
42
|
+
label: "System",
|
|
43
|
+
icon: <SunMoon className="size-4 shrink-0" />,
|
|
44
|
+
active: current === "system",
|
|
45
|
+
onSelect: () => choose("system"),
|
|
46
|
+
},
|
|
47
|
+
]}
|
|
48
|
+
trigger={
|
|
49
|
+
<Button
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size="icon-sm"
|
|
52
|
+
title="Change theme"
|
|
53
|
+
aria-label="Change theme"
|
|
54
|
+
aria-keyshortcuts="T"
|
|
55
|
+
className="cursor-pointer text-muted-foreground"
|
|
56
|
+
/>
|
|
57
|
+
}
|
|
22
58
|
>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</kbd>
|
|
29
|
-
</div>
|
|
30
|
-
<div className="flex-1 gap-2 items-center hidden dark:flex">
|
|
31
|
-
<Moon className="size-4" />
|
|
32
|
-
<span className="flex-1 text-left">Dark Theme</span>
|
|
33
|
-
<kbd className="rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-mono">
|
|
34
|
-
T
|
|
35
|
-
</kbd>
|
|
36
|
-
</div>
|
|
37
|
-
</Button>
|
|
59
|
+
{/* Swapped on the `.dark` class rather than on state, so the server render
|
|
60
|
+
matches: no hydration mismatch, no wrong-icon flash. */}
|
|
61
|
+
<Sun className="size-4 dark:hidden" />
|
|
62
|
+
<Moon className="hidden size-4 dark:block" />
|
|
63
|
+
</MenuSheet>
|
|
38
64
|
)
|
|
39
65
|
}
|