@geenius/docs 0.1.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/.changeset/config.json +11 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.nvmrc +1 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +11 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONTRIBUTING.md +26 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +15 -0
- package/SUPPORT.md +8 -0
- package/package.json +58 -0
- package/packages/convex/README.md +1 -0
- package/packages/convex/package.json +12 -0
- package/packages/convex/src/convex.config.ts +3 -0
- package/packages/convex/src/index.ts +3 -0
- package/packages/convex/src/mutations.ts +270 -0
- package/packages/convex/src/queries.ts +175 -0
- package/packages/convex/src/schema.ts +55 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +36 -0
- package/packages/react/src/DocsLayout.tsx +116 -0
- package/packages/react/src/DocsProvider.tsx +93 -0
- package/packages/react/src/RouterDocsContent.tsx +148 -0
- package/packages/react/src/RouterDocsLayout.tsx +161 -0
- package/packages/react/src/components/Breadcrumbs.tsx +34 -0
- package/packages/react/src/components/DocPage.tsx +191 -0
- package/packages/react/src/components/DocSearch.tsx +140 -0
- package/packages/react/src/components/DocSidebar.tsx +86 -0
- package/packages/react/src/components/DocsLayout.tsx +62 -0
- package/packages/react/src/components/EditButton.tsx +26 -0
- package/packages/react/src/components/PageNavigation.tsx +45 -0
- package/packages/react/src/components/TableOfContents.tsx +46 -0
- package/packages/react/src/components/VersionSelector.tsx +60 -0
- package/packages/react/src/components/index.ts +9 -0
- package/packages/react/src/hooks/index.ts +8 -0
- package/packages/react/src/hooks/useDocSearch.ts +55 -0
- package/packages/react/src/hooks/useDocs.ts +57 -0
- package/packages/react/src/hooks/useDocsAdmin.ts +151 -0
- package/packages/react/src/hooks/useTableOfContents.ts +66 -0
- package/packages/react/src/index.ts +38 -0
- package/packages/react/src/pages/DocSearchPage.tsx +129 -0
- package/packages/react/src/pages/DocViewPage.tsx +158 -0
- package/packages/react/src/pages/DocsAdminPage.tsx +330 -0
- package/packages/react/src/pages/DocsIndexPage.tsx +172 -0
- package/packages/react/src/pages/index.ts +4 -0
- package/packages/react/src/useDocs.ts +58 -0
- package/packages/react/tsup.config.ts +12 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +37 -0
- package/packages/react-css/src/DocsLayout.tsx +117 -0
- package/packages/react-css/src/DocsProvider.tsx +93 -0
- package/packages/react-css/src/RouterDocsContent.tsx +60 -0
- package/packages/react-css/src/RouterDocsLayout.tsx +101 -0
- package/packages/react-css/src/components/DocPage.tsx +21 -0
- package/packages/react-css/src/components/DocSearch.tsx +55 -0
- package/packages/react-css/src/components/DocSidebar.tsx +56 -0
- package/packages/react-css/src/components/DocsLayout.tsx +28 -0
- package/packages/react-css/src/components/common.tsx +93 -0
- package/packages/react-css/src/components/index.ts +5 -0
- package/packages/react-css/src/hooks/index.ts +2 -0
- package/packages/react-css/src/index.ts +6 -0
- package/packages/react-css/src/index.tsx +3 -0
- package/packages/react-css/src/pages/DocViewPage.tsx +78 -0
- package/packages/react-css/src/pages/DocsAdminPage.tsx +101 -0
- package/packages/react-css/src/pages/DocsIndexPage.tsx +68 -0
- package/packages/react-css/src/pages/index.ts +3 -0
- package/packages/react-css/src/styles.css +1271 -0
- package/packages/react-css/src/useDocs.ts +58 -0
- package/packages/react-css/tsconfig.json +19 -0
- package/packages/react-css/tsup.config.ts +10 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +31 -0
- package/packages/shared/src/__tests__/docs.test.ts +69 -0
- package/packages/shared/src/config.ts +80 -0
- package/packages/shared/src/index.ts +179 -0
- package/packages/shared/src/providers/astro.ts +94 -0
- package/packages/shared/src/providers/fumadocs.ts +116 -0
- package/packages/shared/src/providers/internal.ts +80 -0
- package/packages/shared/src/types.ts +73 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/shared/tsup.config.ts +12 -0
- package/packages/shared/vitest.config.ts +4 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +33 -0
- package/packages/solidjs/src/DocsLayout.tsx +87 -0
- package/packages/solidjs/src/DocsProvider.tsx +95 -0
- package/packages/solidjs/src/RouterDocsContent.tsx +147 -0
- package/packages/solidjs/src/RouterDocsLayout.tsx +161 -0
- package/packages/solidjs/src/components/Breadcrumbs.tsx +27 -0
- package/packages/solidjs/src/components/DocPage.tsx +110 -0
- package/packages/solidjs/src/components/DocSearch.tsx +81 -0
- package/packages/solidjs/src/components/DocSidebar.tsx +92 -0
- package/packages/solidjs/src/components/DocsLayout.tsx +38 -0
- package/packages/solidjs/src/components/EditButton.tsx +15 -0
- package/packages/solidjs/src/components/PageNavigation.tsx +31 -0
- package/packages/solidjs/src/components/TableOfContents.tsx +41 -0
- package/packages/solidjs/src/components/VersionSelector.tsx +30 -0
- package/packages/solidjs/src/components/index.ts +9 -0
- package/packages/solidjs/src/createDocs.ts +62 -0
- package/packages/solidjs/src/index.ts +28 -0
- package/packages/solidjs/src/pages/DocSearchPage.tsx +72 -0
- package/packages/solidjs/src/pages/DocViewPage.tsx +80 -0
- package/packages/solidjs/src/pages/DocsAdminPage.tsx +123 -0
- package/packages/solidjs/src/pages/DocsIndexPage.tsx +85 -0
- package/packages/solidjs/src/pages/index.ts +4 -0
- package/packages/solidjs/src/primitives/createDocSearch.ts +42 -0
- package/packages/solidjs/src/primitives/createDocs.ts +35 -0
- package/packages/solidjs/src/primitives/createDocsAdmin.ts +63 -0
- package/packages/solidjs/src/primitives/createTableOfContents.ts +51 -0
- package/packages/solidjs/src/primitives/index.ts +4 -0
- package/packages/solidjs/tsup.config.ts +12 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +36 -0
- package/packages/solidjs-css/src/DocsLayout.tsx +106 -0
- package/packages/solidjs-css/src/DocsProvider.tsx +95 -0
- package/packages/solidjs-css/src/RouterDocsContent.tsx +54 -0
- package/packages/solidjs-css/src/RouterDocsLayout.tsx +104 -0
- package/packages/solidjs-css/src/createDocs.ts +62 -0
- package/packages/solidjs-css/src/index.ts +7 -0
- package/packages/solidjs-css/src/index.tsx +17 -0
- package/packages/solidjs-css/src/pages/DocViewPage.tsx +111 -0
- package/packages/solidjs-css/src/pages/DocsAdminPage.tsx +332 -0
- package/packages/solidjs-css/src/pages/DocsIndexPage.tsx +116 -0
- package/packages/solidjs-css/src/pages/index.ts +3 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/src/styles.css +1271 -0
- package/packages/solidjs-css/tsconfig.json +20 -0
- package/packages/solidjs-css/tsup.config.ts +10 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
interface EditButtonProps {
|
|
4
|
+
pageSlug: string
|
|
5
|
+
editUrl?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function EditButton({ pageSlug, editUrl }: EditButtonProps) {
|
|
9
|
+
if (!editUrl) return null
|
|
10
|
+
|
|
11
|
+
const href = `${editUrl.replace(/\/$/, '')}/${pageSlug}.mdx`
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<a
|
|
15
|
+
href={href}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noopener noreferrer"
|
|
18
|
+
className="inline-flex items-center gap-1.5 rounded-lg border border-white/10 px-3 py-1.5 text-xs text-white/40 transition-colors hover:border-white/20 hover:text-white/60"
|
|
19
|
+
>
|
|
20
|
+
<svg className="h-3.5 w-3.5" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
|
21
|
+
<path d="M11.5 1.5l3 3-9 9H2.5v-3l9-9z" strokeWidth="1.5" strokeLinejoin="round" />
|
|
22
|
+
</svg>
|
|
23
|
+
Edit this page
|
|
24
|
+
</a>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
interface PageNavigationProps {
|
|
4
|
+
prev?: { title: string; href: string }
|
|
5
|
+
next?: { title: string; href: string }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PageNavigation({ prev, next }: PageNavigationProps) {
|
|
9
|
+
if (!prev && !next) return null
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="mt-12 flex items-stretch gap-4 border-t border-white/10 pt-8">
|
|
13
|
+
{prev ? (
|
|
14
|
+
<a
|
|
15
|
+
href={prev.href}
|
|
16
|
+
className="group flex flex-1 flex-col rounded-xl border border-white/10 p-4 transition-all hover:border-indigo-500/40 hover:bg-white/5"
|
|
17
|
+
>
|
|
18
|
+
<span className="text-xs text-white/40 group-hover:text-indigo-400 transition-colors">
|
|
19
|
+
← Previous
|
|
20
|
+
</span>
|
|
21
|
+
<span className="mt-1 text-sm font-medium text-white/80 group-hover:text-white transition-colors truncate">
|
|
22
|
+
{prev.title}
|
|
23
|
+
</span>
|
|
24
|
+
</a>
|
|
25
|
+
) : (
|
|
26
|
+
<div className="flex-1" />
|
|
27
|
+
)}
|
|
28
|
+
{next ? (
|
|
29
|
+
<a
|
|
30
|
+
href={next.href}
|
|
31
|
+
className="group flex flex-1 flex-col items-end rounded-xl border border-white/10 p-4 text-right transition-all hover:border-indigo-500/40 hover:bg-white/5"
|
|
32
|
+
>
|
|
33
|
+
<span className="text-xs text-white/40 group-hover:text-indigo-400 transition-colors">
|
|
34
|
+
Next →
|
|
35
|
+
</span>
|
|
36
|
+
<span className="mt-1 text-sm font-medium text-white/80 group-hover:text-white transition-colors truncate">
|
|
37
|
+
{next.title}
|
|
38
|
+
</span>
|
|
39
|
+
</a>
|
|
40
|
+
) : (
|
|
41
|
+
<div className="flex-1" />
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { TocItem } from '@geenius-docs/shared'
|
|
3
|
+
|
|
4
|
+
interface TableOfContentsProps {
|
|
5
|
+
toc: TocItem[]
|
|
6
|
+
activeId?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function TocLink({ item, activeId, depth = 0 }: { item: TocItem; activeId?: string; depth?: number }) {
|
|
10
|
+
const isActive = activeId === item.id
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<a
|
|
14
|
+
href={`#${item.id}`}
|
|
15
|
+
className={`block truncate border-l-2 py-1 text-[13px] transition-all duration-200 ${
|
|
16
|
+
isActive
|
|
17
|
+
? 'border-indigo-500 text-indigo-300 font-medium'
|
|
18
|
+
: 'border-transparent text-white/40 hover:text-white/70 hover:border-white/20'
|
|
19
|
+
}`}
|
|
20
|
+
style={{ paddingLeft: 12 + depth * 12 }}
|
|
21
|
+
>
|
|
22
|
+
{item.text}
|
|
23
|
+
</a>
|
|
24
|
+
{item.children.map((child) => (
|
|
25
|
+
<TocLink key={child.id} item={child} activeId={activeId} depth={depth + 1} />
|
|
26
|
+
))}
|
|
27
|
+
</>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function TableOfContents({ toc, activeId }: TableOfContentsProps) {
|
|
32
|
+
if (toc.length === 0) return null
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<nav className="sticky top-24">
|
|
36
|
+
<h4 className="mb-3 text-xs font-semibold uppercase tracking-widest text-white/40">
|
|
37
|
+
On this page
|
|
38
|
+
</h4>
|
|
39
|
+
<div className="flex flex-col">
|
|
40
|
+
{toc.map((item) => (
|
|
41
|
+
<TocLink key={item.id} item={item} activeId={activeId} />
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
</nav>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
interface VersionSelectorProps {
|
|
4
|
+
versions: string[]
|
|
5
|
+
current: string
|
|
6
|
+
onSelect: (version: string) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function VersionSelector({ versions, current, onSelect }: VersionSelectorProps) {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
11
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
15
|
+
if (ref.current && !ref.current.contains(e.target as Node)) setIsOpen(false)
|
|
16
|
+
}
|
|
17
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
18
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
if (versions.length <= 1) return null
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div ref={ref} className="relative">
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
28
|
+
className="flex items-center gap-1.5 rounded-lg border border-white/10 bg-white/5 px-3 py-1.5 text-xs font-medium text-white/60 transition-colors hover:border-indigo-500/30 hover:text-white/80"
|
|
29
|
+
>
|
|
30
|
+
<span>v{current}</span>
|
|
31
|
+
<svg className={`h-3 w-3 transition-transform ${isOpen ? 'rotate-180' : ''}`} viewBox="0 0 16 16" fill="currentColor">
|
|
32
|
+
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="2" fill="none" />
|
|
33
|
+
</svg>
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
{isOpen && (
|
|
37
|
+
<div className="absolute right-0 top-full z-20 mt-1 min-w-[120px] overflow-hidden rounded-lg border border-white/10 bg-[#111218] shadow-xl">
|
|
38
|
+
{versions.map((v) => (
|
|
39
|
+
<button
|
|
40
|
+
key={v}
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={() => {
|
|
43
|
+
onSelect(v)
|
|
44
|
+
setIsOpen(false)
|
|
45
|
+
}}
|
|
46
|
+
className={`flex w-full items-center px-3 py-2 text-xs transition-colors ${
|
|
47
|
+
v === current
|
|
48
|
+
? 'bg-indigo-500/15 text-indigo-300 font-medium'
|
|
49
|
+
: 'text-white/60 hover:bg-white/5 hover:text-white/80'
|
|
50
|
+
}`}
|
|
51
|
+
>
|
|
52
|
+
v{v}
|
|
53
|
+
{v === current && <span className="ml-auto text-[10px]">✓</span>}
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { DocSidebar } from './DocSidebar'
|
|
2
|
+
export { DocPage } from './DocPage'
|
|
3
|
+
export { TableOfContents } from './TableOfContents'
|
|
4
|
+
export { Breadcrumbs } from './Breadcrumbs'
|
|
5
|
+
export { PageNavigation } from './PageNavigation'
|
|
6
|
+
export { DocSearch } from './DocSearch'
|
|
7
|
+
export { VersionSelector } from './VersionSelector'
|
|
8
|
+
export { EditButton } from './EditButton'
|
|
9
|
+
export { DocsLayout } from './DocsLayout'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useDocs } from './useDocs'
|
|
2
|
+
export type { DocsState } from './useDocs'
|
|
3
|
+
export { useDocSearch } from './useDocSearch'
|
|
4
|
+
export type { DocSearchState } from './useDocSearch'
|
|
5
|
+
export { useDocsAdmin } from './useDocsAdmin'
|
|
6
|
+
export type { DocsAdminActions } from './useDocsAdmin'
|
|
7
|
+
export { useTableOfContents } from './useTableOfContents'
|
|
8
|
+
export type { TableOfContentsState } from './useTableOfContents'
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
|
+
import type { SearchResult } from '@geenius-docs/shared'
|
|
3
|
+
|
|
4
|
+
export interface DocSearchState {
|
|
5
|
+
results: SearchResult[]
|
|
6
|
+
isSearching: boolean
|
|
7
|
+
query: string
|
|
8
|
+
setQuery: (q: string) => void
|
|
9
|
+
clearSearch: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useDocSearch(
|
|
13
|
+
searchFn: (query: string) => SearchResult[] | Promise<SearchResult[]>,
|
|
14
|
+
debounceMs = 250
|
|
15
|
+
): DocSearchState {
|
|
16
|
+
const [query, setQuery] = useState('')
|
|
17
|
+
const [results, setResults] = useState<SearchResult[]>([])
|
|
18
|
+
const [isSearching, setIsSearching] = useState(false)
|
|
19
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!query.trim()) {
|
|
23
|
+
setResults([])
|
|
24
|
+
setIsSearching(false)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setIsSearching(true)
|
|
29
|
+
|
|
30
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
31
|
+
|
|
32
|
+
timerRef.current = setTimeout(async () => {
|
|
33
|
+
try {
|
|
34
|
+
const r = await searchFn(query)
|
|
35
|
+
setResults(r)
|
|
36
|
+
} catch {
|
|
37
|
+
setResults([])
|
|
38
|
+
} finally {
|
|
39
|
+
setIsSearching(false)
|
|
40
|
+
}
|
|
41
|
+
}, debounceMs)
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
45
|
+
}
|
|
46
|
+
}, [query, searchFn, debounceMs])
|
|
47
|
+
|
|
48
|
+
const clearSearch = useCallback(() => {
|
|
49
|
+
setQuery('')
|
|
50
|
+
setResults([])
|
|
51
|
+
setIsSearching(false)
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
return { results, isSearching, query, setQuery, clearSearch }
|
|
55
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useState, useMemo, useCallback } from 'react'
|
|
2
|
+
import type { DocPage, DocSection, DocsConfig } from '@geenius-docs/shared'
|
|
3
|
+
import { defaultDocsConfig } from '@geenius-docs/shared'
|
|
4
|
+
|
|
5
|
+
export interface DocsState {
|
|
6
|
+
sections: (DocSection & { pages: DocPage[]; pageCount: number })[]
|
|
7
|
+
currentSection: DocSection | null
|
|
8
|
+
setSection: (section: DocSection | null) => void
|
|
9
|
+
currentPage: DocPage | null
|
|
10
|
+
setPage: (page: DocPage | null) => void
|
|
11
|
+
searchQuery: string
|
|
12
|
+
setSearchQuery: (query: string) => void
|
|
13
|
+
isLoading: boolean
|
|
14
|
+
config: DocsConfig
|
|
15
|
+
flatPages: DocPage[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useDocs(
|
|
19
|
+
tree: (DocSection & { pages: DocPage[]; pageCount: number })[] | undefined,
|
|
20
|
+
config?: Partial<DocsConfig>
|
|
21
|
+
): DocsState {
|
|
22
|
+
const [currentSection, setSection] = useState<DocSection | null>(null)
|
|
23
|
+
const [currentPage, setPage] = useState<DocPage | null>(null)
|
|
24
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
25
|
+
|
|
26
|
+
const mergedConfig = useMemo(
|
|
27
|
+
() => ({ ...defaultDocsConfig, ...config }),
|
|
28
|
+
[config]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const sections = useMemo(() => tree ?? [], [tree])
|
|
32
|
+
|
|
33
|
+
const flatPages = useMemo(
|
|
34
|
+
() => sections.flatMap((s) => s.pages ?? []),
|
|
35
|
+
[sections]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const isLoading = tree === undefined
|
|
39
|
+
|
|
40
|
+
const handleSetSection = useCallback((section: DocSection | null) => {
|
|
41
|
+
setSection(section)
|
|
42
|
+
setPage(null)
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
sections,
|
|
47
|
+
currentSection,
|
|
48
|
+
setSection: handleSetSection,
|
|
49
|
+
currentPage,
|
|
50
|
+
setPage,
|
|
51
|
+
searchQuery,
|
|
52
|
+
setSearchQuery,
|
|
53
|
+
isLoading,
|
|
54
|
+
config: mergedConfig,
|
|
55
|
+
flatPages,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface DocsAdminActions {
|
|
4
|
+
createSection: (data: {
|
|
5
|
+
title: string
|
|
6
|
+
slug: string
|
|
7
|
+
parentId?: string
|
|
8
|
+
order: number
|
|
9
|
+
icon?: string
|
|
10
|
+
description?: string
|
|
11
|
+
access: 'public' | 'team' | 'admin'
|
|
12
|
+
}) => Promise<void>
|
|
13
|
+
updateSection: (
|
|
14
|
+
sectionId: string,
|
|
15
|
+
data: Record<string, unknown>
|
|
16
|
+
) => Promise<void>
|
|
17
|
+
deleteSection: (sectionId: string) => Promise<void>
|
|
18
|
+
reorderSections: (sectionIds: string[]) => Promise<void>
|
|
19
|
+
createPage: (data: {
|
|
20
|
+
title: string
|
|
21
|
+
slug: string
|
|
22
|
+
content: string
|
|
23
|
+
sectionId: string
|
|
24
|
+
access: 'public' | 'team' | 'admin'
|
|
25
|
+
tags?: string[]
|
|
26
|
+
version?: string
|
|
27
|
+
status?: 'draft' | 'published' | 'archived'
|
|
28
|
+
author?: { name: string; avatar?: string }
|
|
29
|
+
excerpt?: string
|
|
30
|
+
order?: number
|
|
31
|
+
}) => Promise<void>
|
|
32
|
+
updatePage: (pageId: string, data: Record<string, unknown>) => Promise<void>
|
|
33
|
+
publishPage: (pageId: string) => Promise<void>
|
|
34
|
+
archivePage: (pageId: string) => Promise<void>
|
|
35
|
+
deletePage: (pageId: string) => Promise<void>
|
|
36
|
+
reorderPages: (pageIds: string[]) => Promise<void>
|
|
37
|
+
movePage: (
|
|
38
|
+
pageId: string,
|
|
39
|
+
newSectionId: string,
|
|
40
|
+
newOrder: number
|
|
41
|
+
) => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Admin hook factory — accepts mutation functions (from Convex useMutation or similar)
|
|
46
|
+
* and returns a stable set of admin action callbacks.
|
|
47
|
+
*/
|
|
48
|
+
export function useDocsAdmin(mutations: {
|
|
49
|
+
createSection: (args: Record<string, unknown>) => Promise<unknown>
|
|
50
|
+
updateSection: (args: Record<string, unknown>) => Promise<unknown>
|
|
51
|
+
deleteSection: (args: Record<string, unknown>) => Promise<unknown>
|
|
52
|
+
reorderSections: (args: Record<string, unknown>) => Promise<unknown>
|
|
53
|
+
createPage: (args: Record<string, unknown>) => Promise<unknown>
|
|
54
|
+
updatePage: (args: Record<string, unknown>) => Promise<unknown>
|
|
55
|
+
publishPage: (args: Record<string, unknown>) => Promise<unknown>
|
|
56
|
+
archivePage: (args: Record<string, unknown>) => Promise<unknown>
|
|
57
|
+
deletePage: (args: Record<string, unknown>) => Promise<unknown>
|
|
58
|
+
reorderPages: (args: Record<string, unknown>) => Promise<unknown>
|
|
59
|
+
movePage: (args: Record<string, unknown>) => Promise<unknown>
|
|
60
|
+
}): DocsAdminActions {
|
|
61
|
+
const createSection = useCallback(
|
|
62
|
+
async (data: Parameters<DocsAdminActions['createSection']>[0]) => {
|
|
63
|
+
await mutations.createSection(data)
|
|
64
|
+
},
|
|
65
|
+
[mutations]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const updateSection = useCallback(
|
|
69
|
+
async (sectionId: string, data: Record<string, unknown>) => {
|
|
70
|
+
await mutations.updateSection({ sectionId, ...data })
|
|
71
|
+
},
|
|
72
|
+
[mutations]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const deleteSection = useCallback(
|
|
76
|
+
async (sectionId: string) => {
|
|
77
|
+
await mutations.deleteSection({ sectionId })
|
|
78
|
+
},
|
|
79
|
+
[mutations]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const reorderSections = useCallback(
|
|
83
|
+
async (sectionIds: string[]) => {
|
|
84
|
+
await mutations.reorderSections({ sectionIds })
|
|
85
|
+
},
|
|
86
|
+
[mutations]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const createPage = useCallback(
|
|
90
|
+
async (data: Parameters<DocsAdminActions['createPage']>[0]) => {
|
|
91
|
+
await mutations.createPage(data)
|
|
92
|
+
},
|
|
93
|
+
[mutations]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const updatePage = useCallback(
|
|
97
|
+
async (pageId: string, data: Record<string, unknown>) => {
|
|
98
|
+
await mutations.updatePage({ pageId, ...data })
|
|
99
|
+
},
|
|
100
|
+
[mutations]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const publishPage = useCallback(
|
|
104
|
+
async (pageId: string) => {
|
|
105
|
+
await mutations.publishPage({ pageId })
|
|
106
|
+
},
|
|
107
|
+
[mutations]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const archivePage = useCallback(
|
|
111
|
+
async (pageId: string) => {
|
|
112
|
+
await mutations.archivePage({ pageId })
|
|
113
|
+
},
|
|
114
|
+
[mutations]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const deletePage = useCallback(
|
|
118
|
+
async (pageId: string) => {
|
|
119
|
+
await mutations.deletePage({ pageId })
|
|
120
|
+
},
|
|
121
|
+
[mutations]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const reorderPages = useCallback(
|
|
125
|
+
async (pageIds: string[]) => {
|
|
126
|
+
await mutations.reorderPages({ pageIds })
|
|
127
|
+
},
|
|
128
|
+
[mutations]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const movePage = useCallback(
|
|
132
|
+
async (pageId: string, newSectionId: string, newOrder: number) => {
|
|
133
|
+
await mutations.movePage({ pageId, newSectionId, newOrder })
|
|
134
|
+
},
|
|
135
|
+
[mutations]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
createSection,
|
|
140
|
+
updateSection,
|
|
141
|
+
deleteSection,
|
|
142
|
+
reorderSections,
|
|
143
|
+
createPage,
|
|
144
|
+
updatePage,
|
|
145
|
+
publishPage,
|
|
146
|
+
archivePage,
|
|
147
|
+
deletePage,
|
|
148
|
+
reorderPages,
|
|
149
|
+
movePage,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useRef, useCallback } from 'react'
|
|
2
|
+
import type { TocItem } from '@geenius-docs/shared'
|
|
3
|
+
import { extractToc } from '@geenius-docs/shared'
|
|
4
|
+
|
|
5
|
+
export interface TableOfContentsState {
|
|
6
|
+
toc: TocItem[]
|
|
7
|
+
activeId: string
|
|
8
|
+
setActiveId: (id: string) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useTableOfContents(mdxContent: string | undefined): TableOfContentsState {
|
|
12
|
+
const [activeId, setActiveId] = useState('')
|
|
13
|
+
const observerRef = useRef<IntersectionObserver | null>(null)
|
|
14
|
+
|
|
15
|
+
const toc = useMemo(() => {
|
|
16
|
+
if (!mdxContent) return []
|
|
17
|
+
return extractToc(mdxContent)
|
|
18
|
+
}, [mdxContent])
|
|
19
|
+
|
|
20
|
+
const flatIds = useMemo(() => {
|
|
21
|
+
const ids: string[] = []
|
|
22
|
+
const collect = (items: TocItem[]) => {
|
|
23
|
+
for (const item of items) {
|
|
24
|
+
ids.push(item.id)
|
|
25
|
+
collect(item.children)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
collect(toc)
|
|
29
|
+
return ids
|
|
30
|
+
}, [toc])
|
|
31
|
+
|
|
32
|
+
const handleSetActiveId = useCallback((id: string) => {
|
|
33
|
+
setActiveId(id)
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (flatIds.length === 0) return
|
|
38
|
+
|
|
39
|
+
if (observerRef.current) observerRef.current.disconnect()
|
|
40
|
+
|
|
41
|
+
const callback: IntersectionObserverCallback = (entries) => {
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.isIntersecting) {
|
|
44
|
+
setActiveId(entry.target.id)
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
observerRef.current = new IntersectionObserver(callback, {
|
|
51
|
+
rootMargin: '-80px 0px -70% 0px',
|
|
52
|
+
threshold: 0,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
for (const id of flatIds) {
|
|
56
|
+
const el = document.getElementById(id)
|
|
57
|
+
if (el) observerRef.current.observe(el)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
observerRef.current?.disconnect()
|
|
62
|
+
}
|
|
63
|
+
}, [flatIds])
|
|
64
|
+
|
|
65
|
+
return { toc, activeId, setActiveId: handleSetActiveId }
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Hooks
|
|
2
|
+
export { useDocs } from './hooks/useDocs'
|
|
3
|
+
export type { DocsState } from './hooks/useDocs'
|
|
4
|
+
export { useDocSearch } from './hooks/useDocSearch'
|
|
5
|
+
export type { DocSearchState } from './hooks/useDocSearch'
|
|
6
|
+
export { useDocsAdmin } from './hooks/useDocsAdmin'
|
|
7
|
+
export type { DocsAdminActions } from './hooks/useDocsAdmin'
|
|
8
|
+
export { useTableOfContents } from './hooks/useTableOfContents'
|
|
9
|
+
export type { TableOfContentsState } from './hooks/useTableOfContents'
|
|
10
|
+
|
|
11
|
+
// Components
|
|
12
|
+
export { DocSidebar } from './components/DocSidebar'
|
|
13
|
+
export { DocPage } from './components/DocPage'
|
|
14
|
+
export { TableOfContents } from './components/TableOfContents'
|
|
15
|
+
export { Breadcrumbs } from './components/Breadcrumbs'
|
|
16
|
+
export { PageNavigation } from './components/PageNavigation'
|
|
17
|
+
export { DocSearch } from './components/DocSearch'
|
|
18
|
+
export { VersionSelector } from './components/VersionSelector'
|
|
19
|
+
export { EditButton } from './components/EditButton'
|
|
20
|
+
export { DocsLayout } from './components/DocsLayout'
|
|
21
|
+
|
|
22
|
+
// Pages
|
|
23
|
+
export { DocsIndexPage } from './pages/DocsIndexPage'
|
|
24
|
+
export { DocViewPage } from './pages/DocViewPage'
|
|
25
|
+
export { DocSearchPage } from './pages/DocSearchPage'
|
|
26
|
+
export { DocsAdminPage } from './pages/DocsAdminPage'
|
|
27
|
+
|
|
28
|
+
// Re-export shared types
|
|
29
|
+
export type {
|
|
30
|
+
DocPage as DocPageType,
|
|
31
|
+
DocSection,
|
|
32
|
+
SearchResult,
|
|
33
|
+
TocItem,
|
|
34
|
+
BreadcrumbItem,
|
|
35
|
+
DocsConfig,
|
|
36
|
+
DocAccess,
|
|
37
|
+
DocStatus,
|
|
38
|
+
} from '@geenius-docs/shared'
|