diff options
25 files changed, 298 insertions, 21 deletions
diff --git a/docs/docs/01-getting-started/_category_.json b/docs/docs/01-getting-started/_category_.json index 4bfa738a..3562d433 100644 --- a/docs/docs/01-getting-started/_category_.json +++ b/docs/docs/01-getting-started/_category_.json @@ -1,4 +1,4 @@ { - "label": "🚀 Getting Started", + "label": "Getting Started", "position": 1 } diff --git a/docs/docs/02-installation/_category_.json b/docs/docs/02-installation/_category_.json index d39d5f24..874082cc 100644 --- a/docs/docs/02-installation/_category_.json +++ b/docs/docs/02-installation/_category_.json @@ -1,4 +1,4 @@ { - "label": "📦 Installation", + "label": "Installation", "position": 3 } diff --git a/docs/docs/03-configuration/_category_.json b/docs/docs/03-configuration/_category_.json index ae9f593e..0aea1748 100644 --- a/docs/docs/03-configuration/_category_.json +++ b/docs/docs/03-configuration/_category_.json @@ -1,4 +1,4 @@ { - "label": "⚙️ Configuration", + "label": "Configuration", "position": 4 } diff --git a/docs/docs/04-using-karakeep/_category_.json b/docs/docs/04-using-karakeep/_category_.json index 95ef28ea..b0784ea6 100644 --- a/docs/docs/04-using-karakeep/_category_.json +++ b/docs/docs/04-using-karakeep/_category_.json @@ -1,4 +1,4 @@ { - "label": "📖 Using Karakeep", + "label": "Using Karakeep", "position": 2 } diff --git a/docs/docs/05-integrations/_category_.json b/docs/docs/05-integrations/_category_.json index e526841b..c2495ffd 100644 --- a/docs/docs/05-integrations/_category_.json +++ b/docs/docs/05-integrations/_category_.json @@ -1,4 +1,4 @@ { - "label": "🔌 Integrations", + "label": "Integrations", "position": 5 } diff --git a/docs/docs/06-administration/_category_.json b/docs/docs/06-administration/_category_.json index 9847810a..8998ac56 100644 --- a/docs/docs/06-administration/_category_.json +++ b/docs/docs/06-administration/_category_.json @@ -1,4 +1,4 @@ { - "label": "🛠️ Administration", + "label": "Administration", "position": 6 } diff --git a/docs/docs/07-community/_category_.json b/docs/docs/07-community/_category_.json index 265fd50d..6fe06d4a 100644 --- a/docs/docs/07-community/_category_.json +++ b/docs/docs/07-community/_category_.json @@ -1,4 +1,4 @@ { - "label": "👥 Community", + "label": "Community", "position": 7 } diff --git a/docs/docs/08-development/_category_.json b/docs/docs/08-development/_category_.json index 5cfb6b1d..3f59497c 100644 --- a/docs/docs/08-development/_category_.json +++ b/docs/docs/08-development/_category_.json @@ -1,4 +1,4 @@ { - "label": "💻 Development", + "label": "Development", "position": 8 } diff --git a/docs/docs/api/_category_.json b/docs/docs/api/_category_.json index b493787e..695d9dbe 100644 --- a/docs/docs/api/_category_.json +++ b/docs/docs/api/_category_.json @@ -1 +1 @@ -{ "label": "🔗 API", "position": 9 } +{ "label": "API", "position": 9 } diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 4bbc208d..db857986 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -42,7 +42,7 @@ const config: Config = { }) => { const sidebarItems = await defaultSidebarItemsGenerator(args); return sidebarItems.filter( - (item) => !(item.type == "category" && item.label === "🔗 API"), + (item) => !(item.type == "category" && item.label === "API"), ); }, editUrl: "https://github.com/karakeep-app/karakeep/tree/main/docs/", diff --git a/docs/package.json b/docs/package.json index d02b5271..713e31fa 100644 --- a/docs/package.json +++ b/docs/package.json @@ -23,6 +23,7 @@ "clsx": "^2.1.0", "docusaurus-plugin-openapi-docs": "^4.3.7", "docusaurus-theme-openapi-docs": "^4.4.0", + "lucide-react": "^0.501.0", "prism-react-renderer": "^2.4.1", "react": "^19.2.1", "react-dom": "^19.2.1", diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 67337c10..9943e513 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -6,7 +6,9 @@ const sidebars: SidebarsConfig = { sidebar: [ { type: "autogenerated", dirName: "." }, { - "🔗 API": openapiSidebar, + type: "category", + label: "API", + items: openapiSidebar, }, ], }; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 9cfc2a44..573d7e39 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -593,6 +593,7 @@ li::marker { /* ============================================ API Docs Specific Styles ============================================ */ + .api-method > .menu__link { align-items: center; justify-content: start; diff --git a/docs/src/theme/DocSidebarItem/Category/index.tsx b/docs/src/theme/DocSidebarItem/Category/index.tsx new file mode 100644 index 00000000..9426a026 --- /dev/null +++ b/docs/src/theme/DocSidebarItem/Category/index.tsx @@ -0,0 +1,270 @@ +import React, { + type ComponentProps, + type ReactNode, + useEffect, + useMemo, +} from 'react'; +import clsx from 'clsx'; +import { + ThemeClassNames, + useThemeConfig, + usePrevious, + Collapsible, + useCollapsible, +} from '@docusaurus/theme-common'; +import {isSamePath} from '@docusaurus/theme-common/internal'; +import { + isActiveSidebarItem, + findFirstSidebarItemLink, + useDocSidebarItemsExpandedState, +} from '@docusaurus/plugin-content-docs/client'; +import Link from '@docusaurus/Link'; +import {translate} from '@docusaurus/Translate'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebarItem/Category'; +import { + Rocket, + Package, + Settings, + BookOpen, + Plug, + Wrench, + Users, + Code, + type LucideIcon, +} from 'lucide-react'; + +// Map category labels to Lucide icons +const categoryIcons: Record<string, LucideIcon> = { + 'Getting Started': Rocket, + 'Installation': Package, + 'Configuration': Settings, + 'Using Karakeep': BookOpen, + 'Integrations': Plug, + 'Administration': Wrench, + 'Community': Users, + 'Development': Code, + 'API': Code, +}; + +// If we navigate to a category and it becomes active, it should automatically +// expand itself +function useAutoExpandActiveCategory({ + isActive, + collapsed, + updateCollapsed, +}: { + isActive: boolean; + collapsed: boolean; + updateCollapsed: (b: boolean) => void; +}) { + const wasActive = usePrevious(isActive); + useEffect(() => { + const justBecameActive = isActive && !wasActive; + if (justBecameActive && collapsed) { + updateCollapsed(false); + } + }, [isActive, wasActive, collapsed, updateCollapsed]); +} + +/** + * When a collapsible category has no link, we still link it to its first child + * during SSR as a temporary fallback. This allows to be able to navigate inside + * the category even when JS fails to load, is delayed or simply disabled + * React hydration becomes an optional progressive enhancement + * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188 + * see https://github.com/facebook/docusaurus/issues/3030 + */ +function useCategoryHrefWithSSRFallback( + item: Props['item'], +): string | undefined { + const isBrowser = useIsBrowser(); + return useMemo(() => { + if (item.href && !item.linkUnlisted) { + return item.href; + } + // In these cases, it's not necessary to render a fallback + // We skip the "findFirstCategoryLink" computation + if (isBrowser || !item.collapsible) { + return undefined; + } + return findFirstSidebarItemLink(item); + }, [item, isBrowser]); +} + +function CollapseButton({ + collapsed, + categoryLabel, + onClick, +}: { + collapsed: boolean; + categoryLabel: string; + onClick: ComponentProps<'button'>['onClick']; +}) { + return ( + <button + aria-label={ + collapsed + ? translate( + { + id: 'theme.DocSidebarItem.expandCategoryAriaLabel', + message: "Expand sidebar category '{label}'", + description: 'The ARIA label to expand the sidebar category', + }, + {label: categoryLabel}, + ) + : translate( + { + id: 'theme.DocSidebarItem.collapseCategoryAriaLabel', + message: "Collapse sidebar category '{label}'", + description: 'The ARIA label to collapse the sidebar category', + }, + {label: categoryLabel}, + ) + } + aria-expanded={!collapsed} + type="button" + className="clean-btn menu__caret" + onClick={onClick} + /> + ); +} + +export default function DocSidebarItemCategory({ + item, + onItemClick, + activePath, + level, + index, + ...props +}: Props): ReactNode { + const {items, label, collapsible, className, href} = item; + const { + docs: { + sidebar: {autoCollapseCategories}, + }, + } = useThemeConfig(); + const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item); + + const isActive = isActiveSidebarItem(item, activePath); + const isCurrentPage = isSamePath(href, activePath); + + const {collapsed, setCollapsed} = useCollapsible({ + // Active categories are always initialized as expanded. The default + // (`item.collapsed`) is only used for non-active categories. + initialState: () => { + if (!collapsible) { + return false; + } + return isActive ? false : item.collapsed; + }, + }); + + const {expandedItem, setExpandedItem} = useDocSidebarItemsExpandedState(); + // Use this instead of `setCollapsed`, because it is also reactive + const updateCollapsed = (toCollapsed: boolean = !collapsed) => { + setExpandedItem(toCollapsed ? null : index); + setCollapsed(toCollapsed); + }; + useAutoExpandActiveCategory({isActive, collapsed, updateCollapsed}); + useEffect(() => { + if ( + collapsible && + expandedItem != null && + expandedItem !== index && + autoCollapseCategories + ) { + setCollapsed(true); + } + }, [collapsible, expandedItem, index, setCollapsed, autoCollapseCategories]); + + return ( + <li + className={clsx( + ThemeClassNames.docs.docSidebarItemCategory, + ThemeClassNames.docs.docSidebarItemCategoryLevel(level), + 'menu__list-item', + { + 'menu__list-item--collapsed': collapsed, + }, + className, + )}> + <div + className={clsx('menu__list-item-collapsible', { + 'menu__list-item-collapsible--active': isCurrentPage, + })}> + <Link + className={clsx('menu__link', { + 'menu__link--sublist': collapsible, + 'menu__link--sublist-caret': !href && collapsible, + 'menu__link--active': isActive, + })} + onClick={ + collapsible + ? (e) => { + onItemClick?.(item); + if (href) { + // When already on the category's page, we collapse it + // We don't use "isActive" because it would collapse the + // category even when we browse a children element + // See https://github.com/facebook/docusaurus/issues/11213 + if (isCurrentPage) { + e.preventDefault(); + updateCollapsed(); + } else { + // When navigating to a new category, we always expand + // see https://github.com/facebook/docusaurus/issues/10854#issuecomment-2609616182 + updateCollapsed(false); + } + } else { + e.preventDefault(); + updateCollapsed(); + } + } + : () => { + onItemClick?.(item); + } + } + aria-current={isCurrentPage ? 'page' : undefined} + role={collapsible && !href ? 'button' : undefined} + aria-expanded={collapsible && !href ? !collapsed : undefined} + href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback} + {...props}> + {(() => { + const Icon = categoryIcons[label]; + if (Icon) { + return ( + <span style={{display: 'flex', alignItems: 'center', gap: '0.625rem'}}> + <Icon size={16} strokeWidth={1.5} style={{opacity: 0.6}} /> + {label} + </span> + ); + } + return label; + })()} + </Link> + {href && collapsible && ( + <CollapseButton + collapsed={collapsed} + categoryLabel={label} + onClick={(e) => { + e.preventDefault(); + updateCollapsed(); + }} + /> + )} + </div> + + <Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}> + <DocSidebarItems + items={items} + tabIndex={collapsed ? -1 : 0} + onItemClick={onItemClick} + activePath={activePath} + level={level + 1} + /> + </Collapsible> + </li> + ); +} diff --git a/docs/versioned_docs/version-v0.30.0/01-getting-started/_category_.json b/docs/versioned_docs/version-v0.30.0/01-getting-started/_category_.json index 4bfa738a..3562d433 100644 --- a/docs/versioned_docs/version-v0.30.0/01-getting-started/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/01-getting-started/_category_.json @@ -1,4 +1,4 @@ { - "label": "🚀 Getting Started", + "label": "Getting Started", "position": 1 } diff --git a/docs/versioned_docs/version-v0.30.0/02-installation/_category_.json b/docs/versioned_docs/version-v0.30.0/02-installation/_category_.json index d39d5f24..874082cc 100644 --- a/docs/versioned_docs/version-v0.30.0/02-installation/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/02-installation/_category_.json @@ -1,4 +1,4 @@ { - "label": "📦 Installation", + "label": "Installation", "position": 3 } diff --git a/docs/versioned_docs/version-v0.30.0/03-configuration/_category_.json b/docs/versioned_docs/version-v0.30.0/03-configuration/_category_.json index ae9f593e..0aea1748 100644 --- a/docs/versioned_docs/version-v0.30.0/03-configuration/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/03-configuration/_category_.json @@ -1,4 +1,4 @@ { - "label": "⚙️ Configuration", + "label": "Configuration", "position": 4 } diff --git a/docs/versioned_docs/version-v0.30.0/04-using-karakeep/_category_.json b/docs/versioned_docs/version-v0.30.0/04-using-karakeep/_category_.json index 95ef28ea..b0784ea6 100644 --- a/docs/versioned_docs/version-v0.30.0/04-using-karakeep/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/04-using-karakeep/_category_.json @@ -1,4 +1,4 @@ { - "label": "📖 Using Karakeep", + "label": "Using Karakeep", "position": 2 } diff --git a/docs/versioned_docs/version-v0.30.0/05-integrations/_category_.json b/docs/versioned_docs/version-v0.30.0/05-integrations/_category_.json index e526841b..c2495ffd 100644 --- a/docs/versioned_docs/version-v0.30.0/05-integrations/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/05-integrations/_category_.json @@ -1,4 +1,4 @@ { - "label": "🔌 Integrations", + "label": "Integrations", "position": 5 } diff --git a/docs/versioned_docs/version-v0.30.0/06-administration/_category_.json b/docs/versioned_docs/version-v0.30.0/06-administration/_category_.json index 9847810a..8998ac56 100644 --- a/docs/versioned_docs/version-v0.30.0/06-administration/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/06-administration/_category_.json @@ -1,4 +1,4 @@ { - "label": "🛠️ Administration", + "label": "Administration", "position": 6 } diff --git a/docs/versioned_docs/version-v0.30.0/07-community/_category_.json b/docs/versioned_docs/version-v0.30.0/07-community/_category_.json index 265fd50d..6fe06d4a 100644 --- a/docs/versioned_docs/version-v0.30.0/07-community/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/07-community/_category_.json @@ -1,4 +1,4 @@ { - "label": "👥 Community", + "label": "Community", "position": 7 } diff --git a/docs/versioned_docs/version-v0.30.0/08-development/_category_.json b/docs/versioned_docs/version-v0.30.0/08-development/_category_.json index 5cfb6b1d..3f59497c 100644 --- a/docs/versioned_docs/version-v0.30.0/08-development/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/08-development/_category_.json @@ -1,4 +1,4 @@ { - "label": "💻 Development", + "label": "Development", "position": 8 } diff --git a/docs/versioned_docs/version-v0.30.0/api/_category_.json b/docs/versioned_docs/version-v0.30.0/api/_category_.json index b493787e..695d9dbe 100644 --- a/docs/versioned_docs/version-v0.30.0/api/_category_.json +++ b/docs/versioned_docs/version-v0.30.0/api/_category_.json @@ -1 +1 @@ -{ "label": "🔗 API", "position": 9 } +{ "label": "API", "position": 9 } diff --git a/docs/versioned_sidebars/version-v0.30.0-sidebars.json b/docs/versioned_sidebars/version-v0.30.0-sidebars.json index a5808711..f887c909 100644 --- a/docs/versioned_sidebars/version-v0.30.0-sidebars.json +++ b/docs/versioned_sidebars/version-v0.30.0-sidebars.json @@ -5,7 +5,7 @@ "dirName": "." }, { - "🔗 API": [ + "API": [ { "type": "doc", "id": "api/karakeep-api" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62e8a485..56e9081e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1007,6 +1007,9 @@ importers: docusaurus-theme-openapi-docs: specifier: ^4.4.0 version: 4.4.0(@docusaurus/theme-common@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.5)(react@19.2.3))(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.5)(docusaurus-plugin-openapi-docs@4.4.0(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.5)(react@19.2.3))(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3))(docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.5)(react@19.2.3))(acorn@8.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(sass@1.89.1)(webpack@5.99.9))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.99.9) + lucide-react: + specifier: ^0.501.0 + version: 0.501.0(react@19.2.3) prism-react-renderer: specifier: ^2.4.1 version: 2.4.1(react@19.2.3) |
