@geenius/docs 0.1.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +53 -1
  3. package/package.json +101 -13
  4. package/packages/convex/dist/index.d.ts +503 -0
  5. package/packages/convex/dist/index.js +482 -0
  6. package/packages/convex/dist/index.js.map +1 -0
  7. package/packages/react/dist/index.d.ts +439 -0
  8. package/packages/react/dist/index.js +4954 -0
  9. package/packages/react/dist/index.js.map +1 -0
  10. package/packages/react-css/{src/styles.css → dist/index.css} +183 -223
  11. package/packages/react-css/dist/index.css.map +1 -0
  12. package/packages/react-css/dist/index.d.ts +443 -0
  13. package/packages/react-css/dist/index.js +5058 -0
  14. package/packages/react-css/dist/index.js.map +1 -0
  15. package/packages/shared/dist/index.d.ts +684 -0
  16. package/packages/shared/dist/index.js +788 -0
  17. package/packages/shared/dist/index.js.map +1 -0
  18. package/packages/solidjs/dist/index.d.ts +435 -0
  19. package/packages/solidjs/dist/index.js +4584 -0
  20. package/packages/solidjs/dist/index.js.map +1 -0
  21. package/packages/solidjs-css/{src/styles.css → dist/index.css} +183 -223
  22. package/packages/solidjs-css/dist/index.css.map +1 -0
  23. package/packages/solidjs-css/dist/index.d.ts +432 -0
  24. package/packages/solidjs-css/dist/index.js +4934 -0
  25. package/packages/solidjs-css/dist/index.js.map +1 -0
  26. package/.changeset/config.json +0 -11
  27. package/.github/CODEOWNERS +0 -1
  28. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  29. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  30. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  31. package/.github/dependabot.yml +0 -11
  32. package/.github/workflows/ci.yml +0 -23
  33. package/.github/workflows/release.yml +0 -29
  34. package/.nvmrc +0 -1
  35. package/.project/ACCOUNT.yaml +0 -4
  36. package/.project/IDEAS.yaml +0 -7
  37. package/.project/PROJECT.yaml +0 -11
  38. package/.project/ROADMAP.yaml +0 -15
  39. package/CODE_OF_CONDUCT.md +0 -16
  40. package/CONTRIBUTING.md +0 -26
  41. package/SECURITY.md +0 -15
  42. package/SUPPORT.md +0 -8
  43. package/packages/convex/README.md +0 -1
  44. package/packages/convex/package.json +0 -12
  45. package/packages/convex/src/convex.config.ts +0 -3
  46. package/packages/convex/src/index.ts +0 -3
  47. package/packages/convex/src/mutations.ts +0 -270
  48. package/packages/convex/src/queries.ts +0 -175
  49. package/packages/convex/src/schema.ts +0 -55
  50. package/packages/react/README.md +0 -1
  51. package/packages/react/package.json +0 -36
  52. package/packages/react/src/DocsLayout.tsx +0 -116
  53. package/packages/react/src/DocsProvider.tsx +0 -93
  54. package/packages/react/src/RouterDocsContent.tsx +0 -148
  55. package/packages/react/src/RouterDocsLayout.tsx +0 -161
  56. package/packages/react/src/components/Breadcrumbs.tsx +0 -34
  57. package/packages/react/src/components/DocPage.tsx +0 -191
  58. package/packages/react/src/components/DocSearch.tsx +0 -140
  59. package/packages/react/src/components/DocSidebar.tsx +0 -86
  60. package/packages/react/src/components/DocsLayout.tsx +0 -62
  61. package/packages/react/src/components/EditButton.tsx +0 -26
  62. package/packages/react/src/components/PageNavigation.tsx +0 -45
  63. package/packages/react/src/components/TableOfContents.tsx +0 -46
  64. package/packages/react/src/components/VersionSelector.tsx +0 -60
  65. package/packages/react/src/components/index.ts +0 -9
  66. package/packages/react/src/hooks/index.ts +0 -8
  67. package/packages/react/src/hooks/useDocSearch.ts +0 -55
  68. package/packages/react/src/hooks/useDocs.ts +0 -57
  69. package/packages/react/src/hooks/useDocsAdmin.ts +0 -151
  70. package/packages/react/src/hooks/useTableOfContents.ts +0 -66
  71. package/packages/react/src/index.ts +0 -38
  72. package/packages/react/src/pages/DocSearchPage.tsx +0 -129
  73. package/packages/react/src/pages/DocViewPage.tsx +0 -158
  74. package/packages/react/src/pages/DocsAdminPage.tsx +0 -330
  75. package/packages/react/src/pages/DocsIndexPage.tsx +0 -172
  76. package/packages/react/src/pages/index.ts +0 -4
  77. package/packages/react/src/useDocs.ts +0 -58
  78. package/packages/react/tsup.config.ts +0 -12
  79. package/packages/react-css/README.md +0 -1
  80. package/packages/react-css/package.json +0 -37
  81. package/packages/react-css/src/DocsLayout.tsx +0 -117
  82. package/packages/react-css/src/DocsProvider.tsx +0 -93
  83. package/packages/react-css/src/RouterDocsContent.tsx +0 -60
  84. package/packages/react-css/src/RouterDocsLayout.tsx +0 -101
  85. package/packages/react-css/src/components/DocPage.tsx +0 -21
  86. package/packages/react-css/src/components/DocSearch.tsx +0 -55
  87. package/packages/react-css/src/components/DocSidebar.tsx +0 -56
  88. package/packages/react-css/src/components/DocsLayout.tsx +0 -28
  89. package/packages/react-css/src/components/common.tsx +0 -93
  90. package/packages/react-css/src/components/index.ts +0 -5
  91. package/packages/react-css/src/hooks/index.ts +0 -2
  92. package/packages/react-css/src/index.ts +0 -6
  93. package/packages/react-css/src/index.tsx +0 -3
  94. package/packages/react-css/src/pages/DocViewPage.tsx +0 -78
  95. package/packages/react-css/src/pages/DocsAdminPage.tsx +0 -101
  96. package/packages/react-css/src/pages/DocsIndexPage.tsx +0 -68
  97. package/packages/react-css/src/pages/index.ts +0 -3
  98. package/packages/react-css/src/useDocs.ts +0 -58
  99. package/packages/react-css/tsconfig.json +0 -19
  100. package/packages/react-css/tsup.config.ts +0 -10
  101. package/packages/shared/README.md +0 -1
  102. package/packages/shared/package.json +0 -31
  103. package/packages/shared/src/__tests__/docs.test.ts +0 -69
  104. package/packages/shared/src/config.ts +0 -80
  105. package/packages/shared/src/index.ts +0 -179
  106. package/packages/shared/src/providers/astro.ts +0 -94
  107. package/packages/shared/src/providers/fumadocs.ts +0 -116
  108. package/packages/shared/src/providers/internal.ts +0 -80
  109. package/packages/shared/src/types.ts +0 -73
  110. package/packages/shared/tsconfig.json +0 -18
  111. package/packages/shared/tsup.config.ts +0 -12
  112. package/packages/shared/vitest.config.ts +0 -4
  113. package/packages/solidjs/README.md +0 -1
  114. package/packages/solidjs/package.json +0 -33
  115. package/packages/solidjs/src/DocsLayout.tsx +0 -87
  116. package/packages/solidjs/src/DocsProvider.tsx +0 -95
  117. package/packages/solidjs/src/RouterDocsContent.tsx +0 -147
  118. package/packages/solidjs/src/RouterDocsLayout.tsx +0 -161
  119. package/packages/solidjs/src/components/Breadcrumbs.tsx +0 -27
  120. package/packages/solidjs/src/components/DocPage.tsx +0 -110
  121. package/packages/solidjs/src/components/DocSearch.tsx +0 -81
  122. package/packages/solidjs/src/components/DocSidebar.tsx +0 -92
  123. package/packages/solidjs/src/components/DocsLayout.tsx +0 -38
  124. package/packages/solidjs/src/components/EditButton.tsx +0 -15
  125. package/packages/solidjs/src/components/PageNavigation.tsx +0 -31
  126. package/packages/solidjs/src/components/TableOfContents.tsx +0 -41
  127. package/packages/solidjs/src/components/VersionSelector.tsx +0 -30
  128. package/packages/solidjs/src/components/index.ts +0 -9
  129. package/packages/solidjs/src/createDocs.ts +0 -62
  130. package/packages/solidjs/src/index.ts +0 -28
  131. package/packages/solidjs/src/pages/DocSearchPage.tsx +0 -72
  132. package/packages/solidjs/src/pages/DocViewPage.tsx +0 -80
  133. package/packages/solidjs/src/pages/DocsAdminPage.tsx +0 -123
  134. package/packages/solidjs/src/pages/DocsIndexPage.tsx +0 -85
  135. package/packages/solidjs/src/pages/index.ts +0 -4
  136. package/packages/solidjs/src/primitives/createDocSearch.ts +0 -42
  137. package/packages/solidjs/src/primitives/createDocs.ts +0 -35
  138. package/packages/solidjs/src/primitives/createDocsAdmin.ts +0 -63
  139. package/packages/solidjs/src/primitives/createTableOfContents.ts +0 -51
  140. package/packages/solidjs/src/primitives/index.ts +0 -4
  141. package/packages/solidjs/tsup.config.ts +0 -12
  142. package/packages/solidjs-css/README.md +0 -1
  143. package/packages/solidjs-css/package.json +0 -36
  144. package/packages/solidjs-css/src/DocsLayout.tsx +0 -106
  145. package/packages/solidjs-css/src/DocsProvider.tsx +0 -95
  146. package/packages/solidjs-css/src/RouterDocsContent.tsx +0 -54
  147. package/packages/solidjs-css/src/RouterDocsLayout.tsx +0 -104
  148. package/packages/solidjs-css/src/createDocs.ts +0 -62
  149. package/packages/solidjs-css/src/index.ts +0 -7
  150. package/packages/solidjs-css/src/index.tsx +0 -17
  151. package/packages/solidjs-css/src/pages/DocViewPage.tsx +0 -111
  152. package/packages/solidjs-css/src/pages/DocsAdminPage.tsx +0 -332
  153. package/packages/solidjs-css/src/pages/DocsIndexPage.tsx +0 -116
  154. package/packages/solidjs-css/src/pages/index.ts +0 -3
  155. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  156. package/packages/solidjs-css/tsconfig.json +0 -20
  157. package/packages/solidjs-css/tsup.config.ts +0 -10
  158. package/pnpm-workspace.yaml +0 -2
@@ -1,31 +0,0 @@
1
- {
2
- "name": "@geenius-docs/shared",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "description": "Framework-agnostic docs types, providers, and utilities",
6
- "main": "./src/index.ts",
7
- "types": "./src/index.ts",
8
- "exports": {
9
- ".": "./src/index.ts"
10
- },
11
- "dependencies": {
12
- "gray-matter": "^4.0.3"
13
- },
14
- "scripts": {
15
- "type-check": "tsc --noEmit",
16
- "test": "vitest run",
17
- "test:watch": "vitest",
18
- "test:coverage": "vitest run --coverage"
19
- },
20
- "author": "Antigravity HQ",
21
- "license": "MIT",
22
- "engines": {
23
- "node": ">=20.0.0"
24
- },
25
- "publishConfig": {
26
- "access": "public"
27
- },
28
- "devDependencies": {
29
- "vitest": "^4.0.0"
30
- }
31
- }
@@ -1,69 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- calcWordCount, slugify, highlightMatch,
4
- searchDocs, buildDocsIndex, defaultDocsConfig,
5
- } from '../index'
6
-
7
- describe('calcWordCount', () => {
8
- it('counts words and estimates reading time', () => {
9
- const result = calcWordCount('The quick brown fox jumps over the lazy dog')
10
- expect(result.wordCount).toBe(9)
11
- expect(result.readingTime).toBeGreaterThanOrEqual(1)
12
- })
13
-
14
- it('handles empty content', () => {
15
- const result = calcWordCount('')
16
- expect(result.wordCount).toBe(0)
17
- })
18
-
19
- it('handles long content with realistic reading time', () => {
20
- const words = Array(500).fill('word').join(' ')
21
- const result = calcWordCount(words)
22
- expect(result.wordCount).toBe(500)
23
- expect(result.readingTime).toBeGreaterThanOrEqual(2)
24
- })
25
- })
26
-
27
- describe('slugify', () => {
28
- it('converts to lowercase with hyphens', () => {
29
- expect(slugify('Getting Started')).toBe('getting-started')
30
- })
31
-
32
- it('removes special characters', () => {
33
- expect(slugify('API Reference (v2)')).toBe('api-reference-v2')
34
- })
35
-
36
- it('trims leading/trailing hyphens', () => {
37
- expect(slugify(' Hello World! ')).toBe('hello-world')
38
- })
39
- })
40
-
41
- describe('highlightMatch', () => {
42
- it('returns snippet around match', () => {
43
- const text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
44
- const result = highlightMatch(text, 'dolor')
45
- expect(result).toContain('dolor')
46
- })
47
-
48
- it('returns start of text when no match', () => {
49
- const result = highlightMatch('Hello world', 'xyz', 160)
50
- expect(result.length).toBeLessThanOrEqual(160)
51
- })
52
-
53
- it('returns text slice when query is empty', () => {
54
- const result = highlightMatch('Hello world', '')
55
- expect(result).toBe('Hello world')
56
- })
57
- })
58
-
59
- describe('Search & Config', () => {
60
- it('searchDocs returns empty for blank query', () => {
61
- expect(searchDocs('', [])).toEqual([])
62
- })
63
-
64
- it('defaultDocsConfig has expected fields', () => {
65
- expect(defaultDocsConfig.siteName).toBe('Docs')
66
- expect(defaultDocsConfig.cmdKEnabled).toBe(true)
67
- expect(defaultDocsConfig.showReadingTime).toBe(true)
68
- })
69
- })
@@ -1,80 +0,0 @@
1
- // @geenius-docs/shared — src/config.ts
2
-
3
- import type { DocsConfig } from './types'
4
-
5
- let _config: DocsConfig | null = null
6
-
7
- /**
8
- * Initialize the docs system with a configuration.
9
- * Call this once at app startup before using any docs features.
10
- *
11
- * @example
12
- * ```ts
13
- * import { configureDocs } from '@geenius-docs/shared'
14
- *
15
- * configureDocs({
16
- * baseUrl: '/docs',
17
- * siteName: 'My Docs',
18
- * versionsEnabled: true,
19
- * })
20
- * ```
21
- */
22
- export function configureDocs(config: DocsConfig): void {
23
- _config = config
24
- }
25
-
26
- /**
27
- * Get the current docs configuration.
28
- * Throws if `configureDocs()` has not been called.
29
- */
30
- export function getDocsConfig(): DocsConfig {
31
- if (!_config) {
32
- throw new Error('Docs not configured. Call configureDocs() first.')
33
- }
34
- return _config
35
- }
36
-
37
- /**
38
- * Check if docs have been configured.
39
- */
40
- export function isDocsConfigured(): boolean {
41
- return _config !== null
42
- }
43
-
44
- /**
45
- * Reset docs configuration (useful for testing).
46
- */
47
- export function resetDocsConfig(): void {
48
- _config = null
49
- }
50
-
51
- /**
52
- * Create a fully resolved docs config with defaults.
53
- */
54
- export function defineDocsConfig(
55
- overrides: Partial<DocsConfig>
56
- ): DocsConfig {
57
- return {
58
- baseUrl: '/docs',
59
- defaultAccess: 'public',
60
- versionsEnabled: false,
61
- cmdKEnabled: true,
62
- showReadingTime: true,
63
- showLastEdited: true,
64
- printModeEnabled: true,
65
- ...overrides,
66
- }
67
- }
68
-
69
- /**
70
- * Merge an existing config with partial overrides.
71
- */
72
- export function mergeDocsConfig(
73
- base: DocsConfig,
74
- overrides: Partial<DocsConfig>
75
- ): DocsConfig {
76
- return {
77
- ...base,
78
- ...overrides,
79
- }
80
- }
@@ -1,179 +0,0 @@
1
- export * from './types'
2
-
3
- import type { DocPage, DocSection, TocItem, BreadcrumbItem, SearchResult, DocsConfig } from './types'
4
-
5
- // ─── Utility: extract ToC from MDX heading lines ─────────────
6
- export function extractToc(mdxContent: string): TocItem[] {
7
- const headingRegex = /^(#{2,4})\s+(.+)$/gm
8
- const items: TocItem[] = []
9
- let match: RegExpExecArray | null
10
-
11
- while ((match = headingRegex.exec(mdxContent)) !== null) {
12
- const level = match[1].length as 2 | 3 | 4
13
- const text = match[2].trim()
14
- const id = slugify(text)
15
- items.push({ id, text, level, children: [] })
16
- }
17
-
18
- // Nest h3 under h2, h4 under h3
19
- const nested: TocItem[] = []
20
- let currentH2: TocItem | null = null
21
- let currentH3: TocItem | null = null
22
-
23
- for (const item of items) {
24
- if (item.level === 2) {
25
- currentH2 = item
26
- currentH3 = null
27
- nested.push(item)
28
- } else if (item.level === 3) {
29
- currentH3 = item
30
- if (currentH2) {
31
- currentH2.children.push(item)
32
- } else {
33
- nested.push(item)
34
- }
35
- } else if (item.level === 4) {
36
- if (currentH3) {
37
- currentH3.children.push(item)
38
- } else if (currentH2) {
39
- currentH2.children.push(item)
40
- } else {
41
- nested.push(item)
42
- }
43
- }
44
- }
45
-
46
- return nested
47
- }
48
-
49
- // ─── Utility: count words and reading time ───────────────────
50
- export function calcWordCount(content: string): { wordCount: number; readingTime: number } {
51
- const stripped = content
52
- .replace(/```[\s\S]*?```/g, '')
53
- .replace(/`[^`]+`/g, '')
54
- .replace(/!\[.*?\]\(.*?\)/g, '')
55
- .replace(/\[([^\]]+)\]\(.*?\)/g, '$1')
56
- .replace(/[#*_~>|-]/g, '')
57
- .trim()
58
-
59
- const words = stripped.split(/\s+/).filter(Boolean)
60
- const wordCount = words.length
61
- const readingTime = Math.max(1, Math.ceil(wordCount / 225))
62
-
63
- return { wordCount, readingTime }
64
- }
65
-
66
- // ─── Utility: build breadcrumbs from section tree ────────────
67
- export function buildBreadcrumbs(
68
- sections: DocSection[],
69
- sectionId: string,
70
- slug: string
71
- ): BreadcrumbItem[] {
72
- const crumbs: BreadcrumbItem[] = [{ title: 'Docs', href: '/docs' }]
73
- const sectionMap = new Map(sections.map(s => [s.id, s]))
74
-
75
- const trail: DocSection[] = []
76
- let current = sectionMap.get(sectionId)
77
- while (current) {
78
- trail.unshift(current)
79
- current = current.parentId ? sectionMap.get(current.parentId) : undefined
80
- }
81
-
82
- for (const section of trail) {
83
- crumbs.push({ title: section.title, href: `/docs/${section.slug}` })
84
- }
85
-
86
- if (slug) {
87
- crumbs.push({ title: slug, href: `/docs/${trail[trail.length - 1]?.slug ?? ''}/${slug}` })
88
- }
89
-
90
- return crumbs
91
- }
92
-
93
- // ─── Utility: full-text search index ─────────────────────────
94
- export function buildDocsIndex(pages: DocPage[], sections: DocSection[]): SearchResult[] {
95
- const sectionMap = new Map(sections.map(s => [s.id, s]))
96
-
97
- return pages
98
- .filter(p => p.status === 'published')
99
- .map(page => {
100
- const section = sectionMap.get(page.sectionId)
101
- return {
102
- pageId: page.id,
103
- pageTitle: page.title,
104
- sectionTitle: section?.title ?? '',
105
- sectionSlug: section?.slug ?? '',
106
- slug: page.slug,
107
- highlight: page.excerpt ?? page.content.slice(0, 160),
108
- score: 0,
109
- tags: page.tags,
110
- }
111
- })
112
- }
113
-
114
- export function searchDocs(query: string, index: SearchResult[]): SearchResult[] {
115
- if (!query.trim()) return []
116
-
117
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean)
118
-
119
- const scored = index
120
- .map(entry => {
121
- let score = 0
122
- const titleLower = entry.pageTitle.toLowerCase()
123
- const highlightLower = entry.highlight.toLowerCase()
124
- const tagsLower = entry.tags.map(t => t.toLowerCase())
125
-
126
- for (const term of terms) {
127
- if (titleLower.includes(term)) score += 10
128
- if (tagsLower.some(t => t.includes(term))) score += 5
129
- if (highlightLower.includes(term)) score += 2
130
- if (entry.sectionTitle.toLowerCase().includes(term)) score += 3
131
- }
132
-
133
- return { ...entry, score }
134
- })
135
- .filter(r => r.score > 0)
136
- .sort((a, b) => b.score - a.score)
137
- .slice(0, 20)
138
-
139
- return scored
140
- }
141
-
142
- // ─── Utility: slugify for doc pages ──────────────────────────
143
- export function slugify(title: string): string {
144
- return title
145
- .toLowerCase()
146
- .replace(/[^a-z0-9]+/g, '-')
147
- .replace(/(^-|-$)/g, '')
148
- }
149
-
150
- // ─── Utility: highlight search term in text ──────────────────
151
- export function highlightMatch(text: string, query: string, maxLen = 160): string {
152
- if (!query.trim()) return text.slice(0, maxLen)
153
-
154
- const lowerText = text.toLowerCase()
155
- const lowerQuery = query.toLowerCase()
156
- const idx = lowerText.indexOf(lowerQuery)
157
-
158
- if (idx === -1) return text.slice(0, maxLen)
159
-
160
- const start = Math.max(0, idx - 40)
161
- const end = Math.min(text.length, idx + query.length + (maxLen - 80))
162
- let snippet = text.slice(start, end)
163
-
164
- if (start > 0) snippet = '…' + snippet
165
- if (end < text.length) snippet = snippet + '…'
166
-
167
- return snippet
168
- }
169
-
170
- // ─── Default config ──────────────────────────────────────────
171
- export const defaultDocsConfig: DocsConfig = {
172
- siteName: 'Docs',
173
- cmdKEnabled: true,
174
- showReadingTime: true,
175
- showLastEdited: true,
176
- versionsEnabled: false,
177
- defaultAccess: 'team',
178
- printModeEnabled: true,
179
- }
@@ -1,94 +0,0 @@
1
- import type { DocsProvider, DocsPage, DocsSidebar, DocsSection, DocsSearchResult, TocEntry } from '../types'
2
-
3
- /**
4
- * Astro docs provider — reads MDX files from filesystem at build time.
5
- * Designed for Astro Starlight or custom Astro docs sites.
6
- *
7
- * Usage:
8
- * const provider = new AstroDocsProvider()
9
- * await provider.loadFromGlob(import.meta.glob('./content/docs/** /*.mdx', { as: 'raw' }))
10
- */
11
- export class AstroDocsProvider implements DocsProvider {
12
- readonly name = 'astro' as const
13
- private pages: DocsPage[] = []
14
-
15
- async loadFromGlob(globResult: Record<string, () => Promise<string> | string>): Promise<void> {
16
- const entries = Object.entries(globResult)
17
- const parsed = await Promise.all(entries.map(async ([path, loader]) => {
18
- const raw = typeof loader === 'function' ? await loader() : loader
19
- return this.parseDoc(path, raw)
20
- }))
21
- this.pages = this.linkPages(parsed)
22
- }
23
-
24
- async loadFromFiles(files: { path: string; content: string }[]): Promise<void> {
25
- const parsed = files.map(f => this.parseDoc(f.path, f.content))
26
- this.pages = this.linkPages(parsed)
27
- }
28
-
29
- private parseDoc(path: string, raw: string): DocsPage {
30
- let frontmatter: Record<string, any> = {}
31
- let content = raw
32
- const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
33
- if (fmMatch) {
34
- // Simple YAML parser for common fields
35
- frontmatter = Object.fromEntries(fmMatch[1].split('\n').map(line => { const [k, ...v] = line.split(':'); return [k.trim(), v.join(':').trim()] }).filter(([k]) => k))
36
- content = fmMatch[2]
37
- }
38
-
39
- const pathParts = path.replace(/.*content\/docs\//, '').replace(/\.(mdx?|md)$/, '').split('/')
40
- const slug = pathParts.join('/')
41
- const section = pathParts.length > 1 ? pathParts[0].replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'General'
42
-
43
- return {
44
- slug, title: frontmatter.title ?? pathParts.pop() ?? slug,
45
- description: frontmatter.description, content, section,
46
- order: frontmatter.order ? parseInt(frontmatter.order) : undefined,
47
- icon: frontmatter.icon, draft: frontmatter.draft === 'true',
48
- toc: this.extractToc(content),
49
- }
50
- }
51
-
52
- private extractToc(content: string): TocEntry[] {
53
- const entries: TocEntry[] = []
54
- const regex = /^(#{2,4})\s+(.+)$/gm
55
- let match
56
- while ((match = regex.exec(content)) !== null) {
57
- const title = match[2].trim()
58
- entries.push({ id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), title, depth: match[1].length })
59
- }
60
- return entries
61
- }
62
-
63
- private linkPages(pages: DocsPage[]): DocsPage[] {
64
- const sorted = [...pages].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
65
- return sorted.map((p, i) => ({
66
- ...p,
67
- prev: i > 0 ? { slug: sorted[i - 1].slug, title: sorted[i - 1].title } : undefined,
68
- next: i < sorted.length - 1 ? { slug: sorted[i + 1].slug, title: sorted[i + 1].title } : undefined,
69
- }))
70
- }
71
-
72
- async getSidebar(): Promise<DocsSidebar> {
73
- const sectionMap = new Map<string, DocsPage[]>()
74
- for (const p of this.pages.filter(p => !p.draft)) {
75
- if (!sectionMap.has(p.section)) sectionMap.set(p.section, [])
76
- sectionMap.get(p.section)!.push(p)
77
- }
78
- return { sections: [...sectionMap.entries()].map(([title, pages]) => ({ title, slug: title.toLowerCase().replace(/\s+/g, '-'), pages })) }
79
- }
80
-
81
- async getPage(slug: string): Promise<DocsPage | null> { return this.pages.find(p => p.slug === slug) ?? null }
82
- async getAllPages(): Promise<DocsPage[]> { return this.pages.filter(p => !p.draft) }
83
-
84
- async search(query: string): Promise<DocsSearchResult[]> {
85
- const q = query.toLowerCase()
86
- return this.pages.filter(p => !p.draft && (p.title.toLowerCase().includes(q) || p.content.toLowerCase().includes(q)))
87
- .map(page => ({ page, matches: [{ field: 'content', snippet: page.title }], score: 1 }))
88
- }
89
-
90
- async getNavigation(slug: string): Promise<{ prev: DocsPage | null; next: DocsPage | null }> {
91
- const page = this.pages.find(p => p.slug === slug)
92
- return { prev: page?.prev ? this.pages.find(p => p.slug === page.prev!.slug) ?? null : null, next: page?.next ? this.pages.find(p => p.slug === page.next!.slug) ?? null : null }
93
- }
94
- }
@@ -1,116 +0,0 @@
1
- import type { DocsProvider, DocsPage, DocsSidebar, DocsSection, DocsSearchResult, TocEntry } from '../types'
2
-
3
- /**
4
- * Fumadocs docs provider — wraps Fumadocs content source API.
5
- * Use when docs are managed by Fumadocs in a Next.js app.
6
- *
7
- * Usage:
8
- * import { FumadocsDocsProvider } from '@geenius-docs/shared/providers/fumadocs'
9
- * import { docs } from '@/lib/source'
10
- * const provider = new FumadocsDocsProvider()
11
- * provider.loadFromSource(docs.getPages(), docs.pageTree)
12
- */
13
- export class FumadocsDocsProvider implements DocsProvider {
14
- readonly name = 'fumadocs' as const
15
- private pages: DocsPage[] = []
16
- private tree: FumadocsTreeNode[] = []
17
-
18
- /**
19
- * Load from Fumadocs source pages and page tree.
20
- */
21
- loadFromSource(pages: FumadocsSourcePage[], tree?: FumadocsTreeNode[]): void {
22
- this.tree = tree ?? []
23
- this.pages = this.linkPages(pages.map(p => this.normalizePage(p)))
24
- }
25
-
26
- private normalizePage(page: FumadocsSourcePage): DocsPage {
27
- const slugParts = page.slugs ?? page.slug?.split('/') ?? []
28
- return {
29
- slug: slugParts.join('/'),
30
- title: page.data?.title ?? slugParts[slugParts.length - 1] ?? '',
31
- description: page.data?.description,
32
- content: page.data?.body ?? '',
33
- section: slugParts.length > 1
34
- ? slugParts[0].replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
35
- : 'General',
36
- order: page.data?.order ?? undefined,
37
- icon: page.data?.icon,
38
- lastUpdated: page.data?.lastModified ?? page.data?.lastUpdated,
39
- toc: (page.data?.toc as TocEntry[]) ?? this.extractToc(page.data?.body ?? ''),
40
- draft: page.data?.draft ?? false,
41
- }
42
- }
43
-
44
- private extractToc(content: string): TocEntry[] {
45
- const entries: TocEntry[] = []
46
- const regex = /^(#{2,4})\s+(.+)$/gm
47
- let match
48
- while ((match = regex.exec(content)) !== null) {
49
- const title = match[2].trim()
50
- entries.push({ id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), title, depth: match[1].length })
51
- }
52
- return entries
53
- }
54
-
55
- private linkPages(pages: DocsPage[]): DocsPage[] {
56
- const sorted = [...pages].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
57
- return sorted.map((p, i) => ({
58
- ...p,
59
- prev: i > 0 ? { slug: sorted[i - 1].slug, title: sorted[i - 1].title } : undefined,
60
- next: i < sorted.length - 1 ? { slug: sorted[i + 1].slug, title: sorted[i + 1].title } : undefined,
61
- }))
62
- }
63
-
64
- async getSidebar(): Promise<DocsSidebar> {
65
- // If we have a Fumadocs tree, use it for sidebar structure
66
- if (this.tree.length > 0) {
67
- return { sections: this.treeToSections(this.tree) }
68
- }
69
- // Fallback: group by section
70
- const sectionMap = new Map<string, DocsPage[]>()
71
- for (const p of this.pages.filter(p => !p.draft)) {
72
- if (!sectionMap.has(p.section)) sectionMap.set(p.section, [])
73
- sectionMap.get(p.section)!.push(p)
74
- }
75
- return { sections: [...sectionMap.entries()].map(([title, pages]) => ({ title, slug: title.toLowerCase().replace(/\s+/g, '-'), pages })) }
76
- }
77
-
78
- private treeToSections(nodes: FumadocsTreeNode[]): DocsSection[] {
79
- return nodes.filter(n => n.type === 'folder').map(folder => ({
80
- title: folder.name ?? '',
81
- slug: folder.name?.toLowerCase().replace(/\s+/g, '-') ?? '',
82
- icon: folder.icon,
83
- pages: (folder.children ?? []).filter(c => c.type === 'page').map(c => this.pages.find(p => p.slug === c.slug) ?? { slug: c.slug ?? '', title: c.name ?? '', content: '', section: folder.name ?? '' }),
84
- }))
85
- }
86
-
87
- async getPage(slug: string): Promise<DocsPage | null> { return this.pages.find(p => p.slug === slug) ?? null }
88
- async getAllPages(): Promise<DocsPage[]> { return this.pages.filter(p => !p.draft) }
89
-
90
- async search(query: string): Promise<DocsSearchResult[]> {
91
- const q = query.toLowerCase()
92
- return this.pages.filter(p => !p.draft && (p.title.toLowerCase().includes(q) || p.content.toLowerCase().includes(q) || p.description?.toLowerCase().includes(q)))
93
- .map(page => ({ page, matches: [{ field: 'title', snippet: page.title }], score: 1 }))
94
- }
95
-
96
- async getNavigation(slug: string): Promise<{ prev: DocsPage | null; next: DocsPage | null }> {
97
- const page = this.pages.find(p => p.slug === slug)
98
- return { prev: page?.prev ? this.pages.find(p => p.slug === page.prev!.slug) ?? null : null, next: page?.next ? this.pages.find(p => p.slug === page.next!.slug) ?? null : null }
99
- }
100
- }
101
-
102
- // ─── Fumadocs Source Types ───────────────────────────────────
103
- export interface FumadocsSourcePage {
104
- slug?: string
105
- slugs?: string[]
106
- url?: string
107
- data: { title?: string; description?: string; body?: string; icon?: string; order?: number; toc?: unknown; draft?: boolean; lastModified?: string; lastUpdated?: string; [key: string]: unknown }
108
- }
109
-
110
- export interface FumadocsTreeNode {
111
- type: 'page' | 'folder' | 'separator'
112
- name?: string
113
- slug?: string
114
- icon?: string
115
- children?: FumadocsTreeNode[]
116
- }
@@ -1,80 +0,0 @@
1
- import type { DocsProvider, DocsPage, DocsSidebar, DocsSection, DocsSearchResult, TocEntry } from '../types'
2
-
3
- /**
4
- * Internal docs provider — renders docs from an in-memory page array.
5
- * Use when loading docs from CMS, API, or static JSON.
6
- */
7
- export class InternalDocsProvider implements DocsProvider {
8
- readonly name = 'internal' as const
9
- private pages: DocsPage[] = []
10
-
11
- constructor(pages?: DocsPage[]) { if (pages) this.pages = pages }
12
-
13
- setPages(pages: DocsPage[]): void { this.pages = this.linkPages(pages) }
14
-
15
- private linkPages(pages: DocsPage[]): DocsPage[] {
16
- const sorted = [...pages].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
17
- return sorted.map((page, i) => ({
18
- ...page,
19
- prev: i > 0 ? { slug: sorted[i - 1].slug, title: sorted[i - 1].title } : undefined,
20
- next: i < sorted.length - 1 ? { slug: sorted[i + 1].slug, title: sorted[i + 1].title } : undefined,
21
- toc: page.toc ?? this.extractToc(page.content),
22
- }))
23
- }
24
-
25
- private extractToc(content: string): TocEntry[] {
26
- const regex = /^(#{2,4})\s+(.+)$/gm
27
- const entries: TocEntry[] = []
28
- let match
29
- while ((match = regex.exec(content)) !== null) {
30
- const title = match[2].trim()
31
- entries.push({ id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), title, depth: match[1].length })
32
- }
33
- return entries
34
- }
35
-
36
- async getSidebar(): Promise<DocsSidebar> {
37
- const sectionMap = new Map<string, DocsPage[]>()
38
- for (const page of this.pages.filter(p => !p.draft)) {
39
- const section = page.section || 'General'
40
- if (!sectionMap.has(section)) sectionMap.set(section, [])
41
- sectionMap.get(section)!.push(page)
42
- }
43
- const sections: DocsSection[] = [...sectionMap.entries()].map(([title, pages]) => ({
44
- title, slug: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
45
- pages: pages.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
46
- }))
47
- return { sections }
48
- }
49
-
50
- async getPage(slug: string): Promise<DocsPage | null> {
51
- return this.pages.find(p => p.slug === slug) ?? null
52
- }
53
-
54
- async getAllPages(): Promise<DocsPage[]> {
55
- return this.pages.filter(p => !p.draft)
56
- }
57
-
58
- async search(query: string): Promise<DocsSearchResult[]> {
59
- const q = query.toLowerCase()
60
- return this.pages.filter(p => !p.draft).map(page => {
61
- const matches: DocsSearchResult['matches'] = []
62
- if (page.title.toLowerCase().includes(q)) matches.push({ field: 'title', snippet: page.title })
63
- if (page.description?.toLowerCase().includes(q)) matches.push({ field: 'description', snippet: page.description })
64
- if (page.content.toLowerCase().includes(q)) {
65
- const idx = page.content.toLowerCase().indexOf(q)
66
- matches.push({ field: 'content', snippet: page.content.slice(Math.max(0, idx - 40), idx + q.length + 40) })
67
- }
68
- return matches.length > 0 ? { page, matches, score: matches.length } : null
69
- }).filter(Boolean) as DocsSearchResult[]
70
- }
71
-
72
- async getNavigation(slug: string): Promise<{ prev: DocsPage | null; next: DocsPage | null }> {
73
- const page = this.pages.find(p => p.slug === slug)
74
- if (!page) return { prev: null, next: null }
75
- return {
76
- prev: page.prev ? (this.pages.find(p => p.slug === page.prev!.slug) ?? null) : null,
77
- next: page.next ? (this.pages.find(p => p.slug === page.next!.slug) ?? null) : null,
78
- }
79
- }
80
- }