@geenius/docs 0.4.1 → 0.5.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/CHANGELOG.md +6 -0
- package/README.md +77 -34
- package/package.json +11 -4
- package/packages/convex/dist/index.d.ts +45 -0
- package/packages/convex/dist/index.js.map +1 -1
- package/packages/react/dist/index.js +315 -3659
- package/packages/react/dist/index.js.map +1 -1
- package/packages/react-css/dist/index.d.ts +1 -0
- package/packages/react-css/dist/index.js +19 -3498
- package/packages/react-css/dist/index.js.map +1 -1
- package/packages/shared/dist/chunk-BR2XBF64.js +157 -0
- package/packages/shared/dist/chunk-BR2XBF64.js.map +1 -0
- package/packages/shared/dist/index.d.ts +3 -501
- package/packages/shared/dist/index.js +2 -716
- package/packages/shared/dist/index.js.map +1 -1
- package/packages/shared/dist/providers.d.ts +234 -0
- package/packages/shared/dist/providers.js +564 -0
- package/packages/shared/dist/providers.js.map +1 -0
- package/packages/shared/dist/types-n-Ryn258.d.ts +270 -0
- package/packages/solidjs/dist/index.d.ts +132 -108
- package/packages/solidjs/dist/index.js +367 -3608
- package/packages/solidjs/dist/index.js.map +1 -1
- package/packages/solidjs-css/dist/index.d.ts +31 -0
- package/packages/solidjs-css/dist/index.js +8 -3494
- package/packages/solidjs-css/dist/index.js.map +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// src/utilities.ts
|
|
2
|
+
function toTocLevel(level) {
|
|
3
|
+
if (level === 2 || level === 3 || level === 4) {
|
|
4
|
+
return level;
|
|
5
|
+
}
|
|
6
|
+
return 4;
|
|
7
|
+
}
|
|
8
|
+
function extractToc(mdxContent) {
|
|
9
|
+
const headingRegex = /^(#{2,4})\s+(.+)$/gm;
|
|
10
|
+
const items = [];
|
|
11
|
+
let match = headingRegex.exec(mdxContent);
|
|
12
|
+
while (match !== null) {
|
|
13
|
+
const text = match[2].trim();
|
|
14
|
+
items.push({
|
|
15
|
+
id: slugify(text),
|
|
16
|
+
text,
|
|
17
|
+
level: toTocLevel(match[1].length),
|
|
18
|
+
children: []
|
|
19
|
+
});
|
|
20
|
+
match = headingRegex.exec(mdxContent);
|
|
21
|
+
}
|
|
22
|
+
const nested = [];
|
|
23
|
+
let currentH2 = null;
|
|
24
|
+
let currentH3 = null;
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (item.level === 2) {
|
|
27
|
+
currentH2 = item;
|
|
28
|
+
currentH3 = null;
|
|
29
|
+
nested.push(item);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (item.level === 3) {
|
|
33
|
+
currentH3 = item;
|
|
34
|
+
if (currentH2) {
|
|
35
|
+
currentH2.children.push(item);
|
|
36
|
+
} else {
|
|
37
|
+
nested.push(item);
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (currentH3) {
|
|
42
|
+
currentH3.children.push(item);
|
|
43
|
+
} else if (currentH2) {
|
|
44
|
+
currentH2.children.push(item);
|
|
45
|
+
} else {
|
|
46
|
+
nested.push(item);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return nested;
|
|
50
|
+
}
|
|
51
|
+
function calcWordCount(content) {
|
|
52
|
+
const stripped = content.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]+)\]\(.*?\)/g, "$1").replace(/[#*_~>|-]/g, "").trim();
|
|
53
|
+
const words = stripped.split(/\s+/).filter(Boolean);
|
|
54
|
+
return {
|
|
55
|
+
wordCount: words.length,
|
|
56
|
+
readingTime: Math.max(1, Math.ceil(words.length / 225))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildBreadcrumbs(sections, sectionId, slug) {
|
|
60
|
+
const crumbs = [{ title: "Docs", href: "/docs" }];
|
|
61
|
+
const sectionMap = new Map(sections.map((section) => [section.id, section]));
|
|
62
|
+
const trail = [];
|
|
63
|
+
let current = sectionMap.get(sectionId);
|
|
64
|
+
while (current) {
|
|
65
|
+
trail.unshift(current);
|
|
66
|
+
current = current.parentId ? sectionMap.get(current.parentId) : void 0;
|
|
67
|
+
}
|
|
68
|
+
for (const section of trail) {
|
|
69
|
+
crumbs.push({ title: section.title, href: `/docs/${section.slug}` });
|
|
70
|
+
}
|
|
71
|
+
if (slug) {
|
|
72
|
+
const parentSlug = trail[trail.length - 1]?.slug ?? "";
|
|
73
|
+
crumbs.push({ title: slug, href: `/docs/${parentSlug}/${slug}` });
|
|
74
|
+
}
|
|
75
|
+
return crumbs;
|
|
76
|
+
}
|
|
77
|
+
function buildDocsIndex(pages, sections) {
|
|
78
|
+
const sectionMap = new Map(sections.map((section) => [section.id, section]));
|
|
79
|
+
return pages.filter((page) => page.status === "published").map((page) => {
|
|
80
|
+
const section = sectionMap.get(page.sectionId);
|
|
81
|
+
return {
|
|
82
|
+
pageId: page.id,
|
|
83
|
+
pageTitle: page.title,
|
|
84
|
+
sectionTitle: section?.title ?? "",
|
|
85
|
+
sectionSlug: section?.slug ?? "",
|
|
86
|
+
slug: page.slug,
|
|
87
|
+
highlight: page.excerpt ?? page.content.slice(0, 160),
|
|
88
|
+
score: 0,
|
|
89
|
+
tags: page.tags
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function searchDocs(query, index) {
|
|
94
|
+
if (!query.trim()) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
98
|
+
return index.map((entry) => {
|
|
99
|
+
let score = 0;
|
|
100
|
+
const titleLower = entry.pageTitle.toLowerCase();
|
|
101
|
+
const highlightLower = entry.highlight.toLowerCase();
|
|
102
|
+
const sectionLower = entry.sectionTitle.toLowerCase();
|
|
103
|
+
const tagsLower = entry.tags.map((tag) => tag.toLowerCase());
|
|
104
|
+
for (const term of terms) {
|
|
105
|
+
if (titleLower.includes(term)) {
|
|
106
|
+
score += 10;
|
|
107
|
+
}
|
|
108
|
+
if (tagsLower.some((tag) => tag.includes(term))) {
|
|
109
|
+
score += 5;
|
|
110
|
+
}
|
|
111
|
+
if (sectionLower.includes(term)) {
|
|
112
|
+
score += 3;
|
|
113
|
+
}
|
|
114
|
+
if (highlightLower.includes(term)) {
|
|
115
|
+
score += 2;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { ...entry, score };
|
|
119
|
+
}).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score).slice(0, 20);
|
|
120
|
+
}
|
|
121
|
+
function slugify(title) {
|
|
122
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
123
|
+
}
|
|
124
|
+
function highlightMatch(text, query, maxLen = 160) {
|
|
125
|
+
if (!query.trim()) {
|
|
126
|
+
return text.slice(0, maxLen);
|
|
127
|
+
}
|
|
128
|
+
const lowerText = text.toLowerCase();
|
|
129
|
+
const lowerQuery = query.toLowerCase();
|
|
130
|
+
const index = lowerText.indexOf(lowerQuery);
|
|
131
|
+
if (index === -1) {
|
|
132
|
+
return text.slice(0, maxLen);
|
|
133
|
+
}
|
|
134
|
+
const start = Math.max(0, index - 40);
|
|
135
|
+
const end = Math.min(text.length, index + query.length + (maxLen - 80));
|
|
136
|
+
let snippet = text.slice(start, end);
|
|
137
|
+
if (start > 0) {
|
|
138
|
+
snippet = `\u2026${snippet}`;
|
|
139
|
+
}
|
|
140
|
+
if (end < text.length) {
|
|
141
|
+
snippet = `${snippet}\u2026`;
|
|
142
|
+
}
|
|
143
|
+
return snippet;
|
|
144
|
+
}
|
|
145
|
+
var defaultDocsConfig = {
|
|
146
|
+
siteName: "Docs",
|
|
147
|
+
cmdKEnabled: true,
|
|
148
|
+
showReadingTime: true,
|
|
149
|
+
showLastEdited: true,
|
|
150
|
+
versionsEnabled: false,
|
|
151
|
+
defaultAccess: "public",
|
|
152
|
+
printModeEnabled: true
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export { buildBreadcrumbs, buildDocsIndex, calcWordCount, defaultDocsConfig, extractToc, highlightMatch, searchDocs, slugify };
|
|
156
|
+
//# sourceMappingURL=chunk-BR2XBF64.js.map
|
|
157
|
+
//# sourceMappingURL=chunk-BR2XBF64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utilities.ts"],"names":[],"mappings":"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","file":"chunk-BR2XBF64.js","sourcesContent":["/**\n * @module docsUtilities\n * @package @geenius/docs-shared\n * @description Hosts the framework-agnostic docs helpers used by the shared\n * barrel and provider implementations for search, slugging, breadcrumbs, table\n * of contents extraction, and default config resolution.\n */\n\nimport type {\n BreadcrumbItem,\n DocPage,\n DocSection,\n DocsConfig,\n SearchResult,\n TocItem,\n} from './types'\n\nfunction toTocLevel(level: number): TocItem['level'] {\n if (level === 2 || level === 3 || level === 4) {\n return level\n }\n\n return 4\n}\n\n/**\n * Extracts a nested table of contents from Markdown heading lines.\n *\n * @param mdxContent - Markdown or MDX content to parse.\n * @returns A nested heading tree containing only level 2 through 4 headings.\n */\nexport function extractToc(mdxContent: string): TocItem[] {\n const headingRegex = /^(#{2,4})\\s+(.+)$/gm\n const items: TocItem[] = []\n let match = headingRegex.exec(mdxContent)\n\n while (match !== null) {\n const text = match[2].trim()\n items.push({\n id: slugify(text),\n text,\n level: toTocLevel(match[1].length),\n children: [],\n })\n match = headingRegex.exec(mdxContent)\n }\n\n const nested: TocItem[] = []\n let currentH2: TocItem | null = null\n let currentH3: TocItem | null = null\n\n for (const item of items) {\n if (item.level === 2) {\n currentH2 = item\n currentH3 = null\n nested.push(item)\n continue\n }\n\n if (item.level === 3) {\n currentH3 = item\n if (currentH2) {\n currentH2.children.push(item)\n } else {\n nested.push(item)\n }\n continue\n }\n\n if (currentH3) {\n currentH3.children.push(item)\n } else if (currentH2) {\n currentH2.children.push(item)\n } else {\n nested.push(item)\n }\n }\n\n return nested\n}\n\n/**\n * Calculates approximate word count and reading time for page content.\n *\n * @param content - Markdown or MDX content to measure.\n * @returns Word count and reading-time metadata derived from the content.\n */\nexport function calcWordCount(\n content: string,\n): { wordCount: number; readingTime: number } {\n const stripped = content\n .replace(/```[\\s\\S]*?```/g, '')\n .replace(/`[^`]+`/g, '')\n .replace(/!\\[.*?\\]\\(.*?\\)/g, '')\n .replace(/\\[([^\\]]+)\\]\\(.*?\\)/g, '$1')\n .replace(/[#*_~>|-]/g, '')\n .trim()\n\n const words = stripped.split(/\\s+/).filter(Boolean)\n\n return {\n wordCount: words.length,\n readingTime: Math.max(1, Math.ceil(words.length / 225)),\n }\n}\n\n/**\n * Builds a breadcrumb trail for a page from a section tree.\n *\n * @param sections - Full section list used to resolve parent relationships.\n * @param sectionId - Identifier of the page's containing section.\n * @param slug - Page slug appended as the terminal breadcrumb.\n * @returns Breadcrumb items suitable for page-level navigation UI.\n */\nexport function buildBreadcrumbs(\n sections: DocSection[],\n sectionId: string,\n slug: string,\n): BreadcrumbItem[] {\n const crumbs: BreadcrumbItem[] = [{ title: 'Docs', href: '/docs' }]\n const sectionMap = new Map(sections.map((section) => [section.id, section]))\n const trail: DocSection[] = []\n let current = sectionMap.get(sectionId)\n\n while (current) {\n trail.unshift(current)\n current = current.parentId ? sectionMap.get(current.parentId) : undefined\n }\n\n for (const section of trail) {\n crumbs.push({ title: section.title, href: `/docs/${section.slug}` })\n }\n\n if (slug) {\n const parentSlug = trail[trail.length - 1]?.slug ?? ''\n crumbs.push({ title: slug, href: `/docs/${parentSlug}/${slug}` })\n }\n\n return crumbs\n}\n\n/**\n * Builds a full-text search index from published pages and their sections.\n *\n * @param pages - Page records to index.\n * @param sections - Section metadata used to enrich each result.\n * @returns Search index entries derived from the published pages.\n */\nexport function buildDocsIndex(\n pages: DocPage[],\n sections: DocSection[],\n): SearchResult[] {\n const sectionMap = new Map(sections.map((section) => [section.id, section]))\n\n return pages\n .filter((page) => page.status === 'published')\n .map((page) => {\n const section = sectionMap.get(page.sectionId)\n\n return {\n pageId: page.id,\n pageTitle: page.title,\n sectionTitle: section?.title ?? '',\n sectionSlug: section?.slug ?? '',\n slug: page.slug,\n highlight: page.excerpt ?? page.content.slice(0, 160),\n score: 0,\n tags: page.tags,\n }\n })\n}\n\n/**\n * Searches a shared search index and returns ranked matches.\n *\n * @param query - User-entered search query.\n * @param index - Search index entries to rank.\n * @returns Ranked search results limited to the top twenty entries.\n */\nexport function searchDocs(\n query: string,\n index: SearchResult[],\n): SearchResult[] {\n if (!query.trim()) {\n return []\n }\n\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean)\n\n return index\n .map((entry) => {\n let score = 0\n const titleLower = entry.pageTitle.toLowerCase()\n const highlightLower = entry.highlight.toLowerCase()\n const sectionLower = entry.sectionTitle.toLowerCase()\n const tagsLower = entry.tags.map((tag) => tag.toLowerCase())\n\n for (const term of terms) {\n if (titleLower.includes(term)) {\n score += 10\n }\n if (tagsLower.some((tag) => tag.includes(term))) {\n score += 5\n }\n if (sectionLower.includes(term)) {\n score += 3\n }\n if (highlightLower.includes(term)) {\n score += 2\n }\n }\n\n return { ...entry, score }\n })\n .filter((entry) => entry.score > 0)\n .sort((left, right) => right.score - left.score)\n .slice(0, 20)\n}\n\n/**\n * Creates a URL-safe slug from arbitrary text.\n *\n * @param title - Source text to slugify.\n * @returns A lower-case kebab-case slug.\n */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n}\n\n/**\n * Produces a search-result snippet centred around a matching query.\n *\n * @param text - Source text used to build the snippet.\n * @param query - User-entered search query.\n * @param maxLen - Maximum snippet length.\n * @returns A trimmed snippet containing the best query match when available.\n */\nexport function highlightMatch(\n text: string,\n query: string,\n maxLen = 160,\n): string {\n if (!query.trim()) {\n return text.slice(0, maxLen)\n }\n\n const lowerText = text.toLowerCase()\n const lowerQuery = query.toLowerCase()\n const index = lowerText.indexOf(lowerQuery)\n\n if (index === -1) {\n return text.slice(0, maxLen)\n }\n\n const start = Math.max(0, index - 40)\n const end = Math.min(text.length, index + query.length + (maxLen - 80))\n let snippet = text.slice(start, end)\n\n if (start > 0) {\n snippet = `…${snippet}`\n }\n if (end < text.length) {\n snippet = `${snippet}…`\n }\n\n return snippet\n}\n\n/**\n * Baseline config used by the framework variants when no explicit config is provided.\n */\nexport const defaultDocsConfig: DocsConfig = {\n siteName: 'Docs',\n cmdKEnabled: true,\n showReadingTime: true,\n showLastEdited: true,\n versionsEnabled: false,\n defaultAccess: 'public',\n printModeEnabled: true,\n}\n"]}
|