@rahuldshetty/inscribe 0.0.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.
- package/README.md +65 -0
- package/cli/api/builder.ts +145 -0
- package/cli/api/inscribe_reader.ts +14 -0
- package/cli/api/renderer.ts +199 -0
- package/cli/api/server.ts +144 -0
- package/cli/api/theme_resolver.ts +31 -0
- package/cli/index.ts +127 -0
- package/cli/schemas/blog.ts +23 -0
- package/cli/schemas/folder.ts +8 -0
- package/cli/schemas/inscribe.ts +14 -0
- package/cli/utils/markdown.ts +72 -0
- package/cli/utils/minifier.ts +20 -0
- package/dist/index.js +102296 -0
- package/package.json +71 -0
- package/template/blogs/doc1.md +16 -0
- package/template/blogs/doc2.md +16 -0
- package/template/blogs/sample.mdx +22 -0
- package/template/docs/hello-docs.md +8 -0
- package/template/inscribe.yaml +5 -0
- package/template/layouts/base.njk +113 -0
- package/template/layouts/blog.njk +79 -0
- package/template/layouts/blog_index.njk +83 -0
- package/template/layouts/doc.njk +106 -0
- package/template/layouts/doc_index.njk +140 -0
- package/template/layouts/home.njk +28 -0
- package/template/layouts/partials/footer.njk +7 -0
- package/template/layouts/partials/header.njk +25 -0
- package/template/themes/default.css +41 -0
- package/template/themes/medium.css +39 -0
- package/template/themes/nord.css +38 -0
- package/template/themes/sepia.css +39 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rahuldshetty/inscribe",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "A minimalist Static Site Generator (SSG) for blogs and documentation.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"inscribe": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cli",
|
|
12
|
+
"template",
|
|
13
|
+
"package.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20.0.0",
|
|
19
|
+
"bun": ">=1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ssg",
|
|
23
|
+
"static-site-generator",
|
|
24
|
+
"blog",
|
|
25
|
+
"documentation",
|
|
26
|
+
"bun",
|
|
27
|
+
"mdx",
|
|
28
|
+
"preact"
|
|
29
|
+
],
|
|
30
|
+
"author": "rahuldshetty",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"lint": "prettier --check . && eslint .",
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"inscribe": "bun ./cli/index.ts",
|
|
36
|
+
"build:cli": "bun build ./cli/index.ts --outdir ./dist --target bun"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/compat": "^2.0.2",
|
|
40
|
+
"@eslint/js": "^9.39.2",
|
|
41
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
42
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
43
|
+
"@types/bun": "^1.3.10",
|
|
44
|
+
"@types/html-minifier-terser": "^7.0.2",
|
|
45
|
+
"@types/node": "^24",
|
|
46
|
+
"@types/nunjucks": "^3.2.6",
|
|
47
|
+
"eslint": "^9.39.2",
|
|
48
|
+
"eslint-config-prettier": "^10.1.8",
|
|
49
|
+
"globals": "^17.3.0",
|
|
50
|
+
"prettier": "^3.8.1",
|
|
51
|
+
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
52
|
+
"tailwindcss": "^4.1.18",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"typescript-eslint": "^8.54.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
58
|
+
"@types/fs-extra": "^11.0.4",
|
|
59
|
+
"clerc": "^1.3.1",
|
|
60
|
+
"dompurify": "^3.3.2",
|
|
61
|
+
"fs-extra": "^11.3.4",
|
|
62
|
+
"html-minifier-terser": "^7.2.0",
|
|
63
|
+
"jsdom": "^28.1.0",
|
|
64
|
+
"marked": "^17.0.4",
|
|
65
|
+
"nunjucks": "^3.2.4",
|
|
66
|
+
"preact": "^10.28.4",
|
|
67
|
+
"preact-render-to-string": "^6.6.6",
|
|
68
|
+
"yaml": "^2.8.2",
|
|
69
|
+
"zod": "^4.3.6"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Lorem Ipsum'
|
|
3
|
+
date: 2023-01-01T00:00:00+00:00
|
|
4
|
+
slug: doc1
|
|
5
|
+
draft: true
|
|
6
|
+
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
|
7
|
+
author: 'John Doe'
|
|
8
|
+
showToc: true
|
|
9
|
+
cover: 'https://example.com/image.png'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Lorem Ipsum Dolor
|
|
13
|
+
|
|
14
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
15
|
+
|
|
16
|
+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Lorem Ipsum'
|
|
3
|
+
date: 2023-01-01T00:00:00+00:00
|
|
4
|
+
slug: doc2
|
|
5
|
+
draft: true
|
|
6
|
+
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
|
7
|
+
author: 'John Doe'
|
|
8
|
+
showToc: true
|
|
9
|
+
cover: 'https://example.com/image.png'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Lorem Ipsum Dolor
|
|
13
|
+
|
|
14
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
15
|
+
|
|
16
|
+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Testing MDX Support'
|
|
3
|
+
author: 'Antigravity'
|
|
4
|
+
date: '2026-03-10'
|
|
5
|
+
slug: 'testing-mdx-support'
|
|
6
|
+
description: 'A sample post to verify MDX support in Inscribe.'
|
|
7
|
+
tags: 'test, mdx, inscribe'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Hello MDX!
|
|
11
|
+
|
|
12
|
+
This is a sample blog post written in **MDX**.
|
|
13
|
+
|
|
14
|
+
<div style={{ padding: '1rem', backgroundColor: '#f0f0f0', borderRadius: '8px' }}>
|
|
15
|
+
This is a JSX-style div inside the MDX file.
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Features tested:
|
|
19
|
+
|
|
20
|
+
1. YAML frontmatter parsing.
|
|
21
|
+
2. MDX compilation.
|
|
22
|
+
3. File discovery.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{% block title %}{{ title }}{% endblock %}</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Merriweather:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
{# Inject the resolved theme CSS (light + dark variables) #}
|
|
12
|
+
<style>{{ themeCSS | safe }}
|
|
13
|
+
[data-theme="light"] { color-scheme: light; }
|
|
14
|
+
[data-theme="dark"] { color-scheme: dark; }
|
|
15
|
+
</style>
|
|
16
|
+
|
|
17
|
+
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
|
18
|
+
<script>
|
|
19
|
+
tailwind.config = {
|
|
20
|
+
theme: {
|
|
21
|
+
extend: {
|
|
22
|
+
fontFamily: {
|
|
23
|
+
sans: ['Inter', 'sans-serif'],
|
|
24
|
+
serif: ['Merriweather', 'Georgia', 'serif'],
|
|
25
|
+
},
|
|
26
|
+
typography: () => ({
|
|
27
|
+
DEFAULT: {
|
|
28
|
+
css: {
|
|
29
|
+
'--tw-prose-body': 'var(--color-text)',
|
|
30
|
+
'--tw-prose-headings': 'var(--color-text)',
|
|
31
|
+
'--tw-prose-lead': 'var(--color-muted)',
|
|
32
|
+
'--tw-prose-links': 'var(--color-accent)',
|
|
33
|
+
'--tw-prose-bold': 'var(--color-text)',
|
|
34
|
+
'--tw-prose-counters': 'var(--color-muted)',
|
|
35
|
+
'--tw-prose-bullets': 'var(--color-border)',
|
|
36
|
+
'--tw-prose-hr': 'var(--color-border)',
|
|
37
|
+
'--tw-prose-quotes': 'var(--color-muted)',
|
|
38
|
+
'--tw-prose-quote-borders': 'var(--color-border)',
|
|
39
|
+
'--tw-prose-captions': 'var(--color-muted)',
|
|
40
|
+
'--tw-prose-code': 'var(--color-text)',
|
|
41
|
+
'--tw-prose-pre-code': 'var(--color-pre-text)',
|
|
42
|
+
'--tw-prose-pre-bg': 'var(--color-pre-bg)',
|
|
43
|
+
'--tw-prose-th-borders': 'var(--color-border)',
|
|
44
|
+
'--tw-prose-td-borders': 'var(--color-border)',
|
|
45
|
+
fontFamily: 'var(--font-serif)',
|
|
46
|
+
fontSize: '1.125rem',
|
|
47
|
+
lineHeight: '1.85',
|
|
48
|
+
maxWidth: 'none',
|
|
49
|
+
a: {
|
|
50
|
+
textDecoration: 'underline',
|
|
51
|
+
textDecorationColor: 'var(--color-border)',
|
|
52
|
+
fontWeight: '500',
|
|
53
|
+
'&:hover': {
|
|
54
|
+
color: 'var(--color-accent)',
|
|
55
|
+
textDecorationColor: 'var(--color-accent)',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
h1: { fontFamily: 'var(--font-sans)', fontWeight: '700', letterSpacing: '-0.02em' },
|
|
59
|
+
h2: { fontFamily: 'var(--font-sans)', fontWeight: '700', letterSpacing: '-0.01em' },
|
|
60
|
+
h3: { fontFamily: 'var(--font-sans)', fontWeight: '600' },
|
|
61
|
+
blockquote: { fontStyle: 'italic' },
|
|
62
|
+
'code::before': { content: '""' },
|
|
63
|
+
'code::after': { content: '""' },
|
|
64
|
+
code: {
|
|
65
|
+
backgroundColor: 'var(--color-code-bg)',
|
|
66
|
+
padding: '0.15rem 0.4rem',
|
|
67
|
+
borderRadius: '0.25rem',
|
|
68
|
+
fontWeight: '400',
|
|
69
|
+
fontSize: '0.875em',
|
|
70
|
+
},
|
|
71
|
+
img: {
|
|
72
|
+
borderRadius: '0.5rem',
|
|
73
|
+
margin: '2rem auto',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
{% block head %}{% endblock %}
|
|
83
|
+
</head>
|
|
84
|
+
<body class="bg-[var(--color-bg)] text-[var(--color-text)] font-sans antialiased">
|
|
85
|
+
|
|
86
|
+
{% block header %}
|
|
87
|
+
{% include "partials/header.njk" %}
|
|
88
|
+
{% endblock %}
|
|
89
|
+
|
|
90
|
+
{% block content %}{% endblock %}
|
|
91
|
+
|
|
92
|
+
{% block footer %}
|
|
93
|
+
{% include "partials/footer.njk" %}
|
|
94
|
+
{% endblock %}
|
|
95
|
+
|
|
96
|
+
{% if isDev %}
|
|
97
|
+
<script>
|
|
98
|
+
const socket = new WebSocket("ws://" + location.host + "/_reload");
|
|
99
|
+
socket.onmessage = (event) => {
|
|
100
|
+
if (event.data === "reload") {
|
|
101
|
+
console.log("Reloading...");
|
|
102
|
+
location.reload();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
socket.onclose = () => {
|
|
106
|
+
console.log("Reload connection closed. Retrying in 2s...");
|
|
107
|
+
setTimeout(() => location.reload(), 2000);
|
|
108
|
+
};
|
|
109
|
+
</script>
|
|
110
|
+
{% endif %}
|
|
111
|
+
{% block scripts %}{% endblock %}
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ config.title }} | {{ blog.metadata.title }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="min-h-screen">
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
{# Article Container #}
|
|
10
|
+
<main class="max-w-2xl mx-auto px-6 py-12">
|
|
11
|
+
|
|
12
|
+
{# Article Header #}
|
|
13
|
+
<header class="mb-10">
|
|
14
|
+
<h1 class="font-sans text-4xl sm:text-5xl font-bold leading-tight tracking-tight text-[var(--color-text)] mb-4">
|
|
15
|
+
{{ blog.metadata.title }}
|
|
16
|
+
</h1>
|
|
17
|
+
|
|
18
|
+
{# Meta: author + date #}
|
|
19
|
+
<div class="flex items-center gap-3 mt-6">
|
|
20
|
+
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-gray-700 to-gray-900 flex items-center justify-center text-white font-semibold text-sm flex-shrink-0">
|
|
21
|
+
{{ (blog.metadata.author or "A")[0] | upper }}
|
|
22
|
+
</div>
|
|
23
|
+
<div>
|
|
24
|
+
<p class="text-sm font-medium text-[var(--color-text)]">{{ blog.metadata.author or "Unknown" }}</p>
|
|
25
|
+
<p class="text-xs text-[var(--color-muted)]">{{ blog.metadata.date or "Unknown date" }}</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{# Tags #}
|
|
30
|
+
{% if blog.metadata.tags and blog.metadata.tags.length %}
|
|
31
|
+
<div class="flex flex-wrap gap-2 mt-5">
|
|
32
|
+
{% for tag in blog.metadata.tags %}
|
|
33
|
+
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-[var(--color-tag-bg)] text-[var(--color-tag-text)] hover:opacity-80 transition-opacity cursor-default">
|
|
34
|
+
{{ tag }}
|
|
35
|
+
</span>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
</div>
|
|
38
|
+
{% endif %}
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
{# Cover Image #}
|
|
42
|
+
{% if blog.metadata.cover %}
|
|
43
|
+
<figure class="mb-10 -mx-6 sm:-mx-12">
|
|
44
|
+
<img
|
|
45
|
+
src="{{ blog.metadata.cover }}"
|
|
46
|
+
alt="{{ blog.metadata.cover_alt or blog.metadata.title }}"
|
|
47
|
+
class="w-full object-cover rounded-none sm:rounded-xl max-h-[480px]"
|
|
48
|
+
/>
|
|
49
|
+
{% if blog.metadata.cover_alt %}
|
|
50
|
+
<figcaption class="text-center text-sm text-[var(--color-muted)] mt-3 italic px-6">
|
|
51
|
+
{{ blog.metadata.cover_alt }}
|
|
52
|
+
</figcaption>
|
|
53
|
+
{% endif %}
|
|
54
|
+
</figure>
|
|
55
|
+
{% endif %}
|
|
56
|
+
|
|
57
|
+
{# Article Body — Typography plugin styles prose content #}
|
|
58
|
+
<article class="prose prose-lg max-w-none
|
|
59
|
+
prose-headings:font-sans prose-headings:font-bold prose-headings:tracking-tight
|
|
60
|
+
prose-img:rounded-xl prose-img:shadow-sm prose-img:mx-auto">
|
|
61
|
+
{{ content | safe }}
|
|
62
|
+
</article>
|
|
63
|
+
|
|
64
|
+
{# Footer divider #}
|
|
65
|
+
<hr class="my-12 border-[var(--color-border)]" />
|
|
66
|
+
|
|
67
|
+
<footer class="flex items-center justify-between text-sm text-[var(--color-muted)]">
|
|
68
|
+
<a href="/"
|
|
69
|
+
class="hover:text-[var(--color-text)] transition-colors flex items-center gap-1.5 font-medium">
|
|
70
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
71
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
72
|
+
</svg>
|
|
73
|
+
All posts
|
|
74
|
+
</a>
|
|
75
|
+
<span>{{ config.title }}</span>
|
|
76
|
+
</footer>
|
|
77
|
+
</main>
|
|
78
|
+
</div>
|
|
79
|
+
{% endblock %}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ config.title }} — Blog{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="min-h-screen bg-[var(--color-bg)]">
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
{# Post List #}
|
|
10
|
+
<main class="max-w-2xl mx-auto px-6 py-10">
|
|
11
|
+
|
|
12
|
+
{% if blogs and blogs.length %}
|
|
13
|
+
<p class="text-xs font-semibold uppercase tracking-widest text-[var(--color-muted)] mb-6">
|
|
14
|
+
{{ blogs.length }} post{% if blogs.length !== 1 %}s{% endif %}
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<ul class="divide-y divide-[var(--color-border)]">
|
|
18
|
+
{% for blog in blogs %}
|
|
19
|
+
<li>
|
|
20
|
+
<a href="/blog/{{ blog.metadata.slug }}"
|
|
21
|
+
class="group flex items-start justify-between gap-6 py-7 hover:no-underline">
|
|
22
|
+
|
|
23
|
+
{# Text content #}
|
|
24
|
+
<div class="flex-1 min-w-0">
|
|
25
|
+
<h2 class="text-xl font-bold text-[var(--color-text)] leading-snug tracking-tight group-hover:text-[var(--color-muted)] transition-colors duration-150 truncate">
|
|
26
|
+
{{ blog.metadata.title }}
|
|
27
|
+
</h2>
|
|
28
|
+
|
|
29
|
+
{% if blog.metadata.description or blog.metadata.excerpt %}
|
|
30
|
+
<p class="mt-1.5 text-sm text-[var(--color-muted)] line-clamp-2 leading-relaxed">
|
|
31
|
+
{{ blog.metadata.description or blog.metadata.excerpt }}
|
|
32
|
+
</p>
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
<div class="flex items-center gap-3 mt-3">
|
|
36
|
+
{% if blog.metadata.author %}
|
|
37
|
+
<span class="text-xs text-[var(--color-muted)] font-medium">{{ blog.metadata.author }}</span>
|
|
38
|
+
<span class="text-[var(--color-border)]">·</span>
|
|
39
|
+
{% endif %}
|
|
40
|
+
<span class="text-xs text-[var(--color-muted)]">{{ blog.metadata.date or 'Undated' }}</span>
|
|
41
|
+
|
|
42
|
+
{# Tags — show first 2 #}
|
|
43
|
+
{% if blog.metadata.tags and blog.metadata.tags.length %}
|
|
44
|
+
<span class="text-[var(--color-border)]">·</span>
|
|
45
|
+
{% for tag in blog.metadata.tags %}
|
|
46
|
+
{% if loop.index <= 2 %}
|
|
47
|
+
<span class="inline-block text-xs px-2 py-0.5 rounded-full bg-[var(--color-tag-bg)] text-[var(--color-tag-text)]">{{ tag }}</span>
|
|
48
|
+
{% endif %}
|
|
49
|
+
{% endfor %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
{# Cover thumbnail #}
|
|
55
|
+
{% if blog.metadata.cover %}
|
|
56
|
+
<div class="flex-shrink-0 w-24 h-16 sm:w-32 sm:h-20 rounded-lg overflow-hidden bg-[var(--color-tag-bg)]">
|
|
57
|
+
<img
|
|
58
|
+
src="{{ blog.metadata.cover }}"
|
|
59
|
+
alt="{{ blog.metadata.title }}"
|
|
60
|
+
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
{% endif %}
|
|
64
|
+
</a>
|
|
65
|
+
</li>
|
|
66
|
+
{% endfor %}
|
|
67
|
+
</ul>
|
|
68
|
+
|
|
69
|
+
{% else %}
|
|
70
|
+
{# Empty state #}
|
|
71
|
+
<div class="text-center py-24">
|
|
72
|
+
<div class="w-14 h-14 rounded-2xl bg-[var(--color-tag-bg)] flex items-center justify-center mx-auto mb-4">
|
|
73
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-7 h-7 text-[var(--color-muted)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
|
74
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
77
|
+
<h2 class="text-lg font-semibold text-[var(--color-text)] mb-1">No posts yet</h2>
|
|
78
|
+
<p class="text-sm text-[var(--color-muted)]">Start writing your first blog post.</p>
|
|
79
|
+
</div>
|
|
80
|
+
{% endif %}
|
|
81
|
+
</main>
|
|
82
|
+
</div>
|
|
83
|
+
{% endblock %}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ config.title }} | {{ blog.metadata.title }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="min-h-screen bg-[var(--color-bg)] w-full max-w-[1400px] mx-auto flex flex-col md:flex-row md:px-8">
|
|
7
|
+
|
|
8
|
+
{% if config.show_doc_nav !== false %}
|
|
9
|
+
{# Left Navigation Sidebar #}
|
|
10
|
+
<aside class="w-full md:w-64 flex-shrink-0 md:border-r border-[var(--color-border)] py-8 md:pr-8 px-6 md:px-0">
|
|
11
|
+
<nav class="sticky top-8 overflow-y-auto overflow-x-hidden max-h-[calc(100vh-4rem)]">
|
|
12
|
+
{% macro renderNode(node, depth, currentDirPath) %}
|
|
13
|
+
{% if node.title %}
|
|
14
|
+
<details class="group" {% if currentDirPath == node.path or currentDirPath.startsWith(node.path + '/') %}open{% endif %}>
|
|
15
|
+
<summary class="flex items-center justify-between cursor-pointer list-none py-1.5 px-2 hover:bg-[var(--color-tag-bg)] rounded-md transition-all"
|
|
16
|
+
style="padding-left: {{ depth * 16 + 8 }}px">
|
|
17
|
+
<span class="text-sm font-bold text-[var(--color-text)]">
|
|
18
|
+
{{ node.title }}
|
|
19
|
+
</span>
|
|
20
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 text-[var(--color-muted)] transition-transform group-open:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
22
|
+
</svg>
|
|
23
|
+
</summary>
|
|
24
|
+
<div class="mt-0.5">
|
|
25
|
+
{% for item in node.items %}
|
|
26
|
+
{% if item.type == 'folder' %}
|
|
27
|
+
{{ renderNode(item.node, depth + 1, currentDirPath) }}
|
|
28
|
+
{% else %}
|
|
29
|
+
<div class="rounded-md hover:bg-[var(--color-tag-bg)] transition-all">
|
|
30
|
+
<a href="/doc/{{ item.post.metadata.slug }}"
|
|
31
|
+
style="padding-left: {{ (depth + 1) * 16 + 8 }}px"
|
|
32
|
+
class="text-sm py-1.5 block transition-colors {% if item.post.metadata.slug == doc.metadata.slug %}text-[var(--color-text)] font-semibold border-l-2 border-[var(--color-text)] -ml-[2px]!{% else %}text-[var(--color-muted)] hover:text-[var(--color-text)]{% endif %}">
|
|
33
|
+
{{ item.post.metadata.title }}
|
|
34
|
+
</a>
|
|
35
|
+
</div>
|
|
36
|
+
{% endif %}
|
|
37
|
+
{% endfor %}
|
|
38
|
+
</div>
|
|
39
|
+
</details>
|
|
40
|
+
{% else %}
|
|
41
|
+
{# Root node - just render items directly #}
|
|
42
|
+
{% for item in node.items %}
|
|
43
|
+
{% if item.type == 'folder' %}
|
|
44
|
+
{{ renderNode(item.node, depth, currentDirPath) }}
|
|
45
|
+
{% else %}
|
|
46
|
+
<div class="rounded-md hover:bg-[var(--color-tag-bg)] transition-all">
|
|
47
|
+
<a href="/doc/{{ item.post.metadata.slug }}"
|
|
48
|
+
style="padding-left: {{ depth * 16 + 8 }}px"
|
|
49
|
+
class="text-sm py-1.5 block transition-colors {% if item.post.metadata.slug == doc.metadata.slug %}text-[var(--color-text)] font-semibold border-l-2 border-[var(--color-text)] -ml-[2px]!{% else %}text-[var(--color-muted)] hover:text-[var(--color-text)]{% endif %}">
|
|
50
|
+
{{ item.post.metadata.title }}
|
|
51
|
+
</a>
|
|
52
|
+
</div>
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% endfor %}
|
|
55
|
+
{% endif %}
|
|
56
|
+
{% endmacro %}
|
|
57
|
+
|
|
58
|
+
{% for rootNode in sidebarTree %}
|
|
59
|
+
{{ renderNode(rootNode, 0, currentDirPath) }}
|
|
60
|
+
{% endfor %}
|
|
61
|
+
</nav>
|
|
62
|
+
</aside>
|
|
63
|
+
{% endif %}
|
|
64
|
+
|
|
65
|
+
{# Article Container #}
|
|
66
|
+
<main class="flex-1 min-w-0 px-6 py-12 lg:px-12 {% if config.show_doc_nav === false %}max-w-2xl mx-auto{% else %}max-w-3xl{% endif %}">
|
|
67
|
+
|
|
68
|
+
{# Cover Image #}
|
|
69
|
+
{% if blog.metadata.cover %}
|
|
70
|
+
<figure class="mb-10 -mx-6 sm:-mx-12">
|
|
71
|
+
<img
|
|
72
|
+
src="{{ blog.metadata.cover }}"
|
|
73
|
+
alt="{{ blog.metadata.cover_alt or blog.metadata.title }}"
|
|
74
|
+
class="w-full object-cover rounded-none sm:rounded-xl max-h-[480px]"
|
|
75
|
+
/>
|
|
76
|
+
{% if blog.metadata.cover_alt %}
|
|
77
|
+
<figcaption class="text-center text-sm text-[var(--color-muted)] mt-3 italic px-6">
|
|
78
|
+
{{ blog.metadata.cover_alt }}
|
|
79
|
+
</figcaption>
|
|
80
|
+
{% endif %}
|
|
81
|
+
</figure>
|
|
82
|
+
{% endif %}
|
|
83
|
+
|
|
84
|
+
{# Article Body — Typography plugin styles prose content #}
|
|
85
|
+
<article class="prose prose-lg max-w-none
|
|
86
|
+
prose-headings:font-sans prose-headings:font-bold prose-headings:tracking-tight
|
|
87
|
+
prose-img:rounded-xl prose-img:shadow-sm prose-img:mx-auto">
|
|
88
|
+
{{ content | safe }}
|
|
89
|
+
</article>
|
|
90
|
+
|
|
91
|
+
{# Footer divider #}
|
|
92
|
+
<hr class="my-12 border-[var(--color-border)]" />
|
|
93
|
+
|
|
94
|
+
<footer class="flex items-center justify-between text-sm text-[var(--color-muted)]">
|
|
95
|
+
<a href="/docs/"
|
|
96
|
+
class="hover:text-[var(--color-text)] transition-colors flex items-center gap-1.5 font-medium">
|
|
97
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
98
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
99
|
+
</svg>
|
|
100
|
+
All docs
|
|
101
|
+
</a>
|
|
102
|
+
<span>{{ config.title }}</span>
|
|
103
|
+
</footer>
|
|
104
|
+
</main>
|
|
105
|
+
</div>
|
|
106
|
+
{% endblock %}
|