@tayacrystals/lore 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/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tayacrystals/lore",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./index.ts",
7
+ "./schema": "./schema.ts",
8
+ "./loaders": "./loaders.ts",
9
+ "./components": "./components.ts",
10
+ "./styles/*": "./styles/*"
11
+ },
12
+ "files": [
13
+ "*.ts",
14
+ "*.d.ts",
15
+ "components/",
16
+ "layouts/",
17
+ "routes/",
18
+ "styles/",
19
+ "utils/",
20
+ "!**/*.test.ts"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "peerDependencies": {
26
+ "astro": "^5.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@astrojs/mdx": "^4.3.13",
30
+ "@astrojs/sitemap": "^3.7.0",
31
+ "@fontsource-variable/inter": "^5.2.8",
32
+ "@fontsource/geist-mono": "^5.2.7",
33
+ "@iconify-json/lucide": "^1.2.89",
34
+ "@tailwindcss/vite": "^4.1.18",
35
+ "astro-expressive-code": "^0.41.6",
36
+ "astro-icon": "^1.1.5",
37
+ "astro-pagefind": "^1.8.5",
38
+ "hast-util-to-html": "^9.0.5",
39
+ "hastscript": "^9.0.1",
40
+ "rehype": "^13.0.2",
41
+ "tailwindcss": "^4.1.18",
42
+ "unist-util-visit": "^5.1.0",
43
+ "zod": "^3.24.0"
44
+ },
45
+ "devDependencies": {
46
+ "@astrojs/check": "^0.9.6",
47
+ "@types/hast": "^3.0.4",
48
+ "typescript": "^5.9.3",
49
+ "vite": "6"
50
+ }
51
+ }
@@ -0,0 +1,201 @@
1
+ ---
2
+ import { getCollection, render } from "astro:content";
3
+ import DocsLayout from "../layouts/DocsLayout.astro";
4
+ import { buildSidebar } from "../utils/sidebar";
5
+ import type { TocItem } from "../utils/toc";
6
+ import { flattenSidebar, getPrevNext } from "../utils/navigation";
7
+ import config from "virtual:lore/config";
8
+ import type { SidebarGroupConfig } from "../config";
9
+
10
+ interface DocsEntry {
11
+ id: string;
12
+ data: {
13
+ title: string;
14
+ description?: string;
15
+ order: number;
16
+ group?: string;
17
+ icon?: string;
18
+ toc: boolean;
19
+ draft: boolean;
20
+ };
21
+ }
22
+
23
+ export async function getStaticPaths() {
24
+ const entries: DocsEntry[] = await getCollection("docs", ({ data }: DocsEntry) => !data.draft);
25
+
26
+ // Content entry paths
27
+ const entryPaths = entries.map((entry: DocsEntry) => ({
28
+ params: {
29
+ slug: entry.id === "index" ? undefined : entry.id,
30
+ },
31
+ props: { entry, isSection: false as const },
32
+ }));
33
+
34
+ // Collect ALL intermediate path prefixes as group slugs
35
+ const groupSlugs = new Set<string>();
36
+ for (const entry of entries) {
37
+ const parts = entry.id.split("/");
38
+ // Build up path prefixes: e.g. "a/b/c.mdx" -> "a", "a/b"
39
+ for (let i = 1; i < parts.length; i++) {
40
+ groupSlugs.add(parts.slice(0, i).join("/"));
41
+ }
42
+ }
43
+
44
+ // Only create section pages for groups that don't have a matching content entry
45
+ const entryIds = new Set(entries.map((e: DocsEntry) => e.id));
46
+ const sectionPaths = [...groupSlugs]
47
+ .filter((slug) => !entryIds.has(slug))
48
+ .map((slug) => ({
49
+ params: { slug },
50
+ props: { groupSlug: slug, isSection: true as const },
51
+ }));
52
+
53
+ return [...entryPaths, ...sectionPaths];
54
+ }
55
+
56
+ const allEntries: DocsEntry[] = await getCollection("docs");
57
+ const sidebar = buildSidebar(allEntries, config.sidebar);
58
+ const flatItems = flattenSidebar(sidebar);
59
+ const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/docs";
60
+ const { prev, next } = getPrevNext(flatItems, currentPath);
61
+
62
+ let title: string;
63
+ let description: string | undefined;
64
+ let toc: TocItem[] = [];
65
+ let showToc = true;
66
+ let Content: any = null;
67
+ let sectionItems: { label: string; href: string; description?: string }[] = [];
68
+ let lastUpdated: Date | null = null;
69
+
70
+ if (Astro.props.isSection) {
71
+ // Section listing page
72
+ const groupSlug = Astro.props.groupSlug;
73
+
74
+ // Traverse nested config to find label for this group
75
+ const slugParts = groupSlug.split("/");
76
+ let currentConfig: Record<string, SidebarGroupConfig> = config.sidebar ?? {};
77
+ let label = slugParts[slugParts.length - 1];
78
+ for (const part of slugParts) {
79
+ const found = currentConfig[part];
80
+ if (found) {
81
+ label = found.label;
82
+ currentConfig = found.children ?? {};
83
+ } else {
84
+ // Use titleCase fallback
85
+ label = part.replace(/[-_]/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase());
86
+ break;
87
+ }
88
+ }
89
+
90
+ title = label;
91
+ description = `Browse all pages in ${title}.`;
92
+ showToc = false;
93
+
94
+ // Get direct children entries (one level deep)
95
+ const groupEntries = allEntries
96
+ .filter((e) => {
97
+ if (e.data.draft) return false;
98
+ if (!e.id.startsWith(groupSlug + "/")) return false;
99
+ const rest = e.id.slice(groupSlug.length + 1);
100
+ return !rest.includes("/"); // direct children only
101
+ })
102
+ .sort((a, b) => a.data.order - b.data.order);
103
+
104
+ sectionItems = groupEntries.map((e) => ({
105
+ label: e.data.title,
106
+ href: `/docs/${e.id}`,
107
+ description: e.data.description,
108
+ }));
109
+
110
+ // Also add sub-group links
111
+ const subGroups = new Set<string>();
112
+ for (const entry of allEntries) {
113
+ if (entry.data.draft) continue;
114
+ if (!entry.id.startsWith(groupSlug + "/")) continue;
115
+ const rest = entry.id.slice(groupSlug.length + 1);
116
+ const parts = rest.split("/");
117
+ if (parts.length > 1) {
118
+ subGroups.add(parts[0]);
119
+ }
120
+ }
121
+
122
+ for (const sub of subGroups) {
123
+ const subSlug = `${groupSlug}/${sub}`;
124
+ // Traverse config tree to find the label for this specific sub-group
125
+ let subLabel = sub.replace(/[-_]/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase());
126
+ let cfg = config.sidebar as Record<string, SidebarGroupConfig>;
127
+ for (const part of subSlug.split("/")) {
128
+ const found = cfg[part];
129
+ if (found) {
130
+ cfg = found.children ?? {};
131
+ // Only use label from the final segment (the sub-group itself)
132
+ if (part === sub) {
133
+ subLabel = found.label;
134
+ }
135
+ } else {
136
+ break;
137
+ }
138
+ }
139
+
140
+ sectionItems.push({
141
+ label: subLabel,
142
+ href: `/docs/${subSlug}`,
143
+ description: `Browse ${subLabel} pages.`,
144
+ });
145
+ }
146
+ } else {
147
+ // Regular content page
148
+ const { entry } = Astro.props;
149
+ title = entry.data.title;
150
+ description = entry.data.description;
151
+ const rendered = await render(entry);
152
+ Content = rendered.Content;
153
+ toc = (await import("../utils/toc")).buildToc(rendered.headings);
154
+ showToc = entry.data.toc;
155
+
156
+ // Get last git commit date for this file
157
+ try {
158
+ const { execSync } = await import("child_process");
159
+ const timestamp = execSync(
160
+ `git log -1 --format=%cI -- "src/content/docs/${entry.id}.mdx" "src/content/docs/${entry.id}.md"`,
161
+ { encoding: "utf-8", timeout: 5000 },
162
+ ).trim();
163
+ if (timestamp) {
164
+ lastUpdated = new Date(timestamp);
165
+ }
166
+ } catch {
167
+ // Fallback: no git date available
168
+ }
169
+ }
170
+ ---
171
+
172
+ <DocsLayout
173
+ title={title}
174
+ description={description}
175
+ sidebar={sidebar}
176
+ toc={toc}
177
+ showToc={showToc}
178
+ prev={prev}
179
+ next={next}
180
+ lastUpdated={lastUpdated}
181
+ >
182
+ {Content ? (
183
+ <Content />
184
+ ) : (
185
+ <ul class="not-prose list-none p-0 m-0 space-y-3">
186
+ {sectionItems.map((item) => (
187
+ <li>
188
+ <a
189
+ href={item.href}
190
+ class="block rounded-lg border border-fd-border p-4 hover:bg-fd-muted/50 transition-colors no-underline"
191
+ >
192
+ <span class="font-medium text-fd-foreground">{item.label}</span>
193
+ {item.description && (
194
+ <span class="block text-sm text-fd-muted-foreground mt-1">{item.description}</span>
195
+ )}
196
+ </a>
197
+ </li>
198
+ ))}
199
+ </ul>
200
+ )}
201
+ </DocsLayout>
package/schema.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+
3
+ export function docsSchema() {
4
+ return z.object({
5
+ title: z.string(),
6
+ description: z.string().optional(),
7
+ order: z.number().default(999),
8
+ group: z.string().optional(),
9
+ icon: z.string().optional(),
10
+ toc: z.boolean().default(true),
11
+ draft: z.boolean().default(false),
12
+ });
13
+ }
@@ -0,0 +1,78 @@
1
+ @import "tailwindcss";
2
+ @source "../components";
3
+ @source "../layouts";
4
+ @source "../routes";
5
+ @import "@fontsource-variable/inter";
6
+ @import "@fontsource/geist-mono";
7
+
8
+ @theme {
9
+ --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
10
+ --font-mono: "Geist Mono", ui-monospace, monospace;
11
+
12
+ --color-fd-background: oklch(0.985 0 0);
13
+ --color-fd-foreground: oklch(0.145 0 0);
14
+ --color-fd-muted: oklch(0.955 0 0);
15
+ --color-fd-muted-foreground: oklch(0.46 0 0);
16
+ --color-fd-border: oklch(0.915 0 0);
17
+ --color-fd-primary: oklch(0.6 0.16 var(--fd-hue));
18
+ --color-fd-primary-foreground: oklch(0.98 0 0);
19
+ --color-fd-accent: oklch(0.96 0 0);
20
+ --color-fd-accent-foreground: oklch(0.145 0 0);
21
+ --color-fd-card: oklch(0.985 0 0);
22
+ --color-fd-card-foreground: oklch(0.145 0 0);
23
+ --color-fd-popover: oklch(0.985 0 0);
24
+ --color-fd-popover-foreground: oklch(0.145 0 0);
25
+ --color-fd-ring: oklch(0.6 0.16 var(--fd-hue));
26
+
27
+ --radius-lg: 0.75rem;
28
+ --radius-md: 0.5rem;
29
+ --radius-sm: 0.375rem;
30
+
31
+ --width-sidebar: 17rem;
32
+ --width-toc: 14rem;
33
+ --width-content: 50rem;
34
+ --height-header: 3.5rem;
35
+ --max-width-layout: 88rem;
36
+ }
37
+
38
+ .dark {
39
+ --color-fd-background: oklch(0.13 0 0);
40
+ --color-fd-foreground: oklch(0.92 0 0);
41
+ --color-fd-muted: oklch(0.18 0 0);
42
+ --color-fd-muted-foreground: oklch(0.55 0 0);
43
+ --color-fd-border: oklch(0.2 0 0);
44
+ --color-fd-primary: oklch(0.7 0.16 var(--fd-hue));
45
+ --color-fd-primary-foreground: oklch(0.1 0 0);
46
+ --color-fd-accent: oklch(0.16 0 0);
47
+ --color-fd-accent-foreground: oklch(0.92 0 0);
48
+ --color-fd-card: oklch(0.15 0 0);
49
+ --color-fd-card-foreground: oklch(0.92 0 0);
50
+ --color-fd-popover: oklch(0.15 0 0);
51
+ --color-fd-popover-foreground: oklch(0.92 0 0);
52
+ --color-fd-ring: oklch(0.7 0.16 var(--fd-hue));
53
+ }
54
+
55
+ @layer base {
56
+ * {
57
+ border-color: var(--color-fd-border);
58
+ }
59
+
60
+ body {
61
+ font-family: var(--font-sans);
62
+ background-color: var(--color-fd-background);
63
+ color: var(--color-fd-foreground);
64
+ -webkit-font-smoothing: antialiased;
65
+ -moz-osx-font-smoothing: grayscale;
66
+ }
67
+
68
+ ::selection {
69
+ background-color: oklch(0.6 0.16 var(--fd-hue) / 0.2);
70
+ }
71
+ }
72
+
73
+ @layer utilities {
74
+ .scrollbar-thin {
75
+ scrollbar-width: thin;
76
+ scrollbar-color: var(--color-fd-border) transparent;
77
+ }
78
+ }
@@ -0,0 +1,148 @@
1
+ .fd-prose {
2
+ line-height: 1.7;
3
+ max-width: none;
4
+ font-size: 0.9375rem;
5
+ }
6
+
7
+ .fd-prose h1 {
8
+ font-size: 2rem;
9
+ font-weight: 700;
10
+ margin-top: 0;
11
+ margin-bottom: 0.75rem;
12
+ letter-spacing: -0.03em;
13
+ line-height: 1.2;
14
+ }
15
+
16
+ .fd-prose h2 {
17
+ font-size: 1.375rem;
18
+ font-weight: 600;
19
+ margin-top: 2.5rem;
20
+ margin-bottom: 0.75rem;
21
+ letter-spacing: -0.02em;
22
+ scroll-margin-top: 4rem;
23
+ }
24
+
25
+ .fd-prose h2 a {
26
+ text-decoration: none;
27
+ color: inherit;
28
+ }
29
+
30
+ .fd-prose h3 {
31
+ font-size: 1.125rem;
32
+ font-weight: 600;
33
+ margin-top: 2rem;
34
+ margin-bottom: 0.5rem;
35
+ scroll-margin-top: 4rem;
36
+ }
37
+
38
+ .fd-prose h4 {
39
+ font-size: 1rem;
40
+ font-weight: 600;
41
+ margin-top: 1.5rem;
42
+ margin-bottom: 0.5rem;
43
+ }
44
+
45
+ .fd-prose p {
46
+ margin-top: 0;
47
+ margin-bottom: 1rem;
48
+ }
49
+
50
+ .fd-prose a {
51
+ color: var(--color-fd-foreground);
52
+ text-decoration: underline;
53
+ text-underline-offset: 3px;
54
+ text-decoration-thickness: 1px;
55
+ text-decoration-color: var(--color-fd-border);
56
+ transition: text-decoration-color 0.15s;
57
+ }
58
+
59
+ .fd-prose a:hover {
60
+ text-decoration-color: var(--color-fd-foreground);
61
+ }
62
+
63
+ .fd-prose code:not(pre code) {
64
+ font-family: var(--font-mono);
65
+ font-size: 0.8125em;
66
+ background-color: var(--color-fd-muted);
67
+ padding: 0.2em 0.4em;
68
+ border-radius: 0.25rem;
69
+ font-weight: 500;
70
+ }
71
+
72
+ .fd-prose pre {
73
+ margin-top: 0;
74
+ margin-bottom: 1rem;
75
+ border-radius: 0.5rem;
76
+ }
77
+
78
+ .fd-prose ul {
79
+ list-style-type: disc;
80
+ padding-left: 1.5rem;
81
+ margin-bottom: 1rem;
82
+ }
83
+
84
+ .fd-prose ol {
85
+ list-style-type: decimal;
86
+ padding-left: 1.5rem;
87
+ margin-bottom: 1rem;
88
+ }
89
+
90
+ .fd-prose li {
91
+ margin-bottom: 0.25rem;
92
+ }
93
+
94
+ .fd-prose li > ul,
95
+ .fd-prose li > ol {
96
+ margin-top: 0.25rem;
97
+ margin-bottom: 0;
98
+ }
99
+
100
+ .fd-prose blockquote {
101
+ border-left: 2px solid var(--color-fd-border);
102
+ padding-left: 1rem;
103
+ margin: 1rem 0;
104
+ color: var(--color-fd-muted-foreground);
105
+ }
106
+
107
+ .fd-prose table {
108
+ width: 100%;
109
+ border-collapse: collapse;
110
+ margin-bottom: 1rem;
111
+ font-size: 0.875rem;
112
+ }
113
+
114
+ .fd-prose thead th {
115
+ text-align: left;
116
+ font-weight: 600;
117
+ padding: 0.5rem 1rem;
118
+ border-bottom: 2px solid var(--color-fd-border);
119
+ }
120
+
121
+ .fd-prose tbody td {
122
+ padding: 0.5rem 1rem;
123
+ border-bottom: 1px solid var(--color-fd-border);
124
+ }
125
+
126
+ .fd-prose hr {
127
+ border: none;
128
+ border-top: 1px solid var(--color-fd-border);
129
+ margin: 2rem 0;
130
+ }
131
+
132
+ .fd-prose img {
133
+ border-radius: 0.5rem;
134
+ margin: 1.5rem 0;
135
+ max-width: 100%;
136
+ }
137
+
138
+ .fd-prose strong {
139
+ font-weight: 600;
140
+ }
141
+
142
+ .fd-prose .callout-content p {
143
+ margin-bottom: 0;
144
+ }
145
+
146
+ .fd-prose .callout-content p:not(:last-child) {
147
+ margin-bottom: 0.25rem;
148
+ }
@@ -0,0 +1,32 @@
1
+ import type { SidebarEntry } from "./sidebar";
2
+
3
+ export interface FlatNavItem {
4
+ label: string;
5
+ href: string;
6
+ }
7
+
8
+ export function flattenSidebar(entries: SidebarEntry[]): FlatNavItem[] {
9
+ const result: FlatNavItem[] = [];
10
+ for (const entry of entries) {
11
+ if (entry.type === "link") {
12
+ result.push({ label: entry.label, href: entry.href });
13
+ } else {
14
+ result.push(...flattenSidebar(entry.items));
15
+ }
16
+ }
17
+ return result;
18
+ }
19
+
20
+ export function getPrevNext(
21
+ flatItems: FlatNavItem[],
22
+ currentPath: string,
23
+ ): { prev: FlatNavItem | null; next: FlatNavItem | null } {
24
+ const BASE_URL = import.meta.env.BASE_URL || "/";
25
+ const normalized = currentPath.replace(/\/$/, "") || `${BASE_URL}docs`;
26
+ const index = flatItems.findIndex((item) => item.href === normalized);
27
+
28
+ return {
29
+ prev: index > 0 ? flatItems[index - 1] : null,
30
+ next: index < flatItems.length - 1 ? flatItems[index + 1] : null,
31
+ };
32
+ }