@terrymooreii/sia 1.0.2 → 2.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/_config.yml +32 -0
- package/bin/cli.js +51 -0
- package/defaults/includes/footer.njk +14 -0
- package/defaults/includes/header.njk +71 -0
- package/defaults/includes/pagination.njk +26 -0
- package/defaults/includes/tag-list.njk +11 -0
- package/defaults/layouts/base.njk +41 -0
- package/defaults/layouts/note.njk +25 -0
- package/defaults/layouts/page.njk +14 -0
- package/defaults/layouts/post.njk +43 -0
- package/defaults/pages/blog.njk +36 -0
- package/defaults/pages/feed.njk +28 -0
- package/defaults/pages/index.njk +60 -0
- package/defaults/pages/notes.njk +34 -0
- package/defaults/pages/tag.njk +41 -0
- package/defaults/pages/tags.njk +39 -0
- package/defaults/styles/main.css +1074 -0
- package/lib/assets.js +234 -0
- package/lib/build.js +263 -19
- package/lib/collections.js +195 -0
- package/lib/config.js +122 -0
- package/lib/content.js +325 -0
- package/lib/index.js +53 -18
- package/lib/init.js +555 -6
- package/lib/new.js +379 -41
- package/lib/server.js +257 -0
- package/lib/templates.js +268 -0
- package/package.json +30 -15
- package/readme.md +212 -63
- package/src/images/.gitkeep +3 -0
- package/src/notes/2024-12-17-first-note.md +6 -0
- package/src/pages/about.md +29 -0
- package/src/posts/2024-12-16-markdown-features.md +76 -0
- package/src/posts/2024-12-17-welcome-to-sia.md +78 -0
- package/src/posts/2024-12-17-welcome-to-static-forge.md +78 -0
- package/.prettierignore +0 -3
- package/.prettierrc +0 -8
- package/lib/helpers.js +0 -37
- package/lib/markdown.js +0 -33
- package/lib/parse.js +0 -100
- package/lib/readconfig.js +0 -18
- package/lib/rss.js +0 -63
- package/templates/siarc-template.js +0 -53
- package/templates/src/_partials/_footer.njk +0 -1
- package/templates/src/_partials/_head.njk +0 -35
- package/templates/src/_partials/_header.njk +0 -1
- package/templates/src/_partials/_layout.njk +0 -12
- package/templates/src/_partials/_nav.njk +0 -12
- package/templates/src/_partials/page.njk +0 -5
- package/templates/src/_partials/post.njk +0 -13
- package/templates/src/_partials/posts.njk +0 -19
- package/templates/src/assets/android-chrome-192x192.png +0 -0
- package/templates/src/assets/android-chrome-512x512.png +0 -0
- package/templates/src/assets/apple-touch-icon.png +0 -0
- package/templates/src/assets/favicon-16x16.png +0 -0
- package/templates/src/assets/favicon-32x32.png +0 -0
- package/templates/src/assets/favicon.ico +0 -0
- package/templates/src/assets/site.webmanifest +0 -19
- package/templates/src/content/index.md +0 -7
- package/templates/src/css/markdown.css +0 -1210
- package/templates/src/css/theme.css +0 -120
package/lib/templates.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import nunjucks from 'nunjucks';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format a date
|
|
11
|
+
*/
|
|
12
|
+
function dateFilter(date, format = 'long') {
|
|
13
|
+
if (!date) return '';
|
|
14
|
+
|
|
15
|
+
const d = new Date(date);
|
|
16
|
+
|
|
17
|
+
if (isNaN(d.getTime())) return '';
|
|
18
|
+
|
|
19
|
+
const formats = {
|
|
20
|
+
short: { month: 'short', day: 'numeric', year: 'numeric' },
|
|
21
|
+
long: { month: 'long', day: 'numeric', year: 'numeric' },
|
|
22
|
+
iso: null,
|
|
23
|
+
rss: null,
|
|
24
|
+
year: { year: 'numeric' },
|
|
25
|
+
month: { month: 'long', year: 'numeric' },
|
|
26
|
+
time: { hour: 'numeric', minute: '2-digit' },
|
|
27
|
+
full: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (format === 'iso') {
|
|
31
|
+
return d.toISOString().split('T')[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// RSS date format (RFC 822)
|
|
35
|
+
if (format === 'rss') {
|
|
36
|
+
return d.toUTCString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const options = formats[format] || formats.long;
|
|
40
|
+
return d.toLocaleDateString('en-US', options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generate a slug from a string
|
|
45
|
+
*/
|
|
46
|
+
function slugFilter(str) {
|
|
47
|
+
if (!str) return '';
|
|
48
|
+
return str
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^\w\s-]/g, '')
|
|
51
|
+
.replace(/[\s_-]+/g, '-')
|
|
52
|
+
.replace(/^-+|-+$/g, '');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get excerpt from content
|
|
57
|
+
*/
|
|
58
|
+
function excerptFilter(content, length = 200) {
|
|
59
|
+
if (!content) return '';
|
|
60
|
+
|
|
61
|
+
// Strip HTML tags
|
|
62
|
+
const text = content.replace(/<[^>]+>/g, '');
|
|
63
|
+
|
|
64
|
+
if (text.length <= length) return text;
|
|
65
|
+
|
|
66
|
+
// Find last space before limit
|
|
67
|
+
const truncated = text.substring(0, length);
|
|
68
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
69
|
+
|
|
70
|
+
return truncated.substring(0, lastSpace) + '...';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Limit array items
|
|
75
|
+
*/
|
|
76
|
+
function limitFilter(arr, count) {
|
|
77
|
+
if (!Array.isArray(arr)) return arr;
|
|
78
|
+
return arr.slice(0, count);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Skip array items
|
|
83
|
+
*/
|
|
84
|
+
function skipFilter(arr, count) {
|
|
85
|
+
if (!Array.isArray(arr)) return arr;
|
|
86
|
+
return arr.slice(count);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get word count
|
|
91
|
+
*/
|
|
92
|
+
function wordCountFilter(content) {
|
|
93
|
+
if (!content) return 0;
|
|
94
|
+
const text = content.replace(/<[^>]+>/g, '');
|
|
95
|
+
return text.split(/\s+/).filter(word => word.length > 0).length;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Estimate reading time
|
|
100
|
+
*/
|
|
101
|
+
function readingTimeFilter(content, wordsPerMinute = 200) {
|
|
102
|
+
const words = wordCountFilter(content);
|
|
103
|
+
const minutes = Math.ceil(words / wordsPerMinute);
|
|
104
|
+
return minutes === 1 ? '1 min read' : `${minutes} min read`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Group items by a property
|
|
109
|
+
*/
|
|
110
|
+
function groupByFilter(arr, key) {
|
|
111
|
+
if (!Array.isArray(arr)) return {};
|
|
112
|
+
|
|
113
|
+
return arr.reduce((groups, item) => {
|
|
114
|
+
const value = item[key];
|
|
115
|
+
if (!groups[value]) {
|
|
116
|
+
groups[value] = [];
|
|
117
|
+
}
|
|
118
|
+
groups[value].push(item);
|
|
119
|
+
return groups;
|
|
120
|
+
}, {});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sort array by property
|
|
125
|
+
*/
|
|
126
|
+
function sortByFilter(arr, key, order = 'asc') {
|
|
127
|
+
if (!Array.isArray(arr)) return arr;
|
|
128
|
+
|
|
129
|
+
return [...arr].sort((a, b) => {
|
|
130
|
+
const aVal = a[key];
|
|
131
|
+
const bVal = b[key];
|
|
132
|
+
|
|
133
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
134
|
+
return order === 'asc' ? aVal - bVal : bVal - aVal;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
|
138
|
+
return order === 'asc'
|
|
139
|
+
? aVal.localeCompare(bVal)
|
|
140
|
+
: bVal.localeCompare(aVal);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return order === 'asc' ? aVal - bVal : bVal - aVal;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Filter items where property matches value
|
|
149
|
+
*/
|
|
150
|
+
function whereFilter(arr, key, value) {
|
|
151
|
+
if (!Array.isArray(arr)) return arr;
|
|
152
|
+
return arr.filter(item => item[key] === value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Filter items that have a tag
|
|
157
|
+
*/
|
|
158
|
+
function withTagFilter(arr, tag) {
|
|
159
|
+
if (!Array.isArray(arr)) return arr;
|
|
160
|
+
const normalizedTag = tag.toLowerCase();
|
|
161
|
+
return arr.filter(item =>
|
|
162
|
+
item.tags && item.tags.some(t => t.toLowerCase() === normalizedTag)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* JSON stringify for debugging
|
|
168
|
+
*/
|
|
169
|
+
function jsonFilter(obj, spaces = 2) {
|
|
170
|
+
return JSON.stringify(obj, null, spaces);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create a URL filter that prepends the basePath
|
|
175
|
+
*/
|
|
176
|
+
function createUrlFilter(basePath) {
|
|
177
|
+
return function urlFilter(path) {
|
|
178
|
+
if (!path) return basePath || '/';
|
|
179
|
+
// If path is already absolute with protocol, return as-is
|
|
180
|
+
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
181
|
+
return path;
|
|
182
|
+
}
|
|
183
|
+
// Ensure path starts with /
|
|
184
|
+
const normalizedPath = path.startsWith('/') ? path : '/' + path;
|
|
185
|
+
return (basePath || '') + normalizedPath;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create and configure the Nunjucks environment
|
|
191
|
+
*/
|
|
192
|
+
export function createTemplateEngine(config) {
|
|
193
|
+
// Set up template paths - user layouts first, then defaults
|
|
194
|
+
const templatePaths = [];
|
|
195
|
+
|
|
196
|
+
// User's custom layouts
|
|
197
|
+
if (existsSync(config.layoutsDir)) {
|
|
198
|
+
templatePaths.push(config.layoutsDir);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// User's custom includes
|
|
202
|
+
if (existsSync(config.includesDir)) {
|
|
203
|
+
templatePaths.push(config.includesDir);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Default templates from the package
|
|
207
|
+
const defaultTemplatesDir = join(__dirname, '..', 'defaults');
|
|
208
|
+
templatePaths.push(join(defaultTemplatesDir, 'layouts'));
|
|
209
|
+
templatePaths.push(join(defaultTemplatesDir, 'includes'));
|
|
210
|
+
templatePaths.push(join(defaultTemplatesDir, 'pages'));
|
|
211
|
+
|
|
212
|
+
// Create the environment
|
|
213
|
+
const env = nunjucks.configure(templatePaths, {
|
|
214
|
+
autoescape: true,
|
|
215
|
+
noCache: true,
|
|
216
|
+
throwOnUndefined: false
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Add custom filters
|
|
220
|
+
env.addFilter('date', dateFilter);
|
|
221
|
+
env.addFilter('slug', slugFilter);
|
|
222
|
+
env.addFilter('excerpt', excerptFilter);
|
|
223
|
+
env.addFilter('limit', limitFilter);
|
|
224
|
+
env.addFilter('skip', skipFilter);
|
|
225
|
+
env.addFilter('wordCount', wordCountFilter);
|
|
226
|
+
env.addFilter('readingTime', readingTimeFilter);
|
|
227
|
+
env.addFilter('groupBy', groupByFilter);
|
|
228
|
+
env.addFilter('sortBy', sortByFilter);
|
|
229
|
+
env.addFilter('where', whereFilter);
|
|
230
|
+
env.addFilter('withTag', withTagFilter);
|
|
231
|
+
env.addFilter('json', jsonFilter);
|
|
232
|
+
|
|
233
|
+
// Add URL filter with basePath support
|
|
234
|
+
env.addFilter('url', createUrlFilter(config.site.basePath));
|
|
235
|
+
|
|
236
|
+
return env;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Render a template with data
|
|
241
|
+
*/
|
|
242
|
+
export function renderTemplate(env, templateName, data) {
|
|
243
|
+
try {
|
|
244
|
+
return env.render(templateName, data);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error(`Error rendering template "${templateName}":`, err.message);
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Render a string template (for content with Nunjucks syntax)
|
|
253
|
+
*/
|
|
254
|
+
export function renderString(env, content, data) {
|
|
255
|
+
try {
|
|
256
|
+
return env.renderString(content, data);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error('Error rendering string template:', err.message);
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default {
|
|
264
|
+
createTemplateEngine,
|
|
265
|
+
renderTemplate,
|
|
266
|
+
renderString
|
|
267
|
+
};
|
|
268
|
+
|
package/package.json
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terrymooreii/sia",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "A simple, powerful static site generator with markdown, front matter, and Nunjucks templates",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sia": "./bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "node bin/cli.js dev",
|
|
12
|
+
"build": "node bin/cli.js build",
|
|
13
|
+
"new": "node bin/cli.js new"
|
|
14
14
|
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"static-site-generator",
|
|
17
|
+
"ssg",
|
|
18
|
+
"markdown",
|
|
19
|
+
"nunjucks",
|
|
20
|
+
"blog"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
15
24
|
"dependencies": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"chokidar": "^3.5.3",
|
|
27
|
+
"commander": "^11.1.0",
|
|
19
28
|
"gray-matter": "^4.0.3",
|
|
20
29
|
"highlight.js": "^11.9.0",
|
|
21
|
-
"
|
|
30
|
+
"js-yaml": "^4.1.0",
|
|
31
|
+
"marked": "^11.1.1",
|
|
32
|
+
"marked-emoji": "^2.0.2",
|
|
33
|
+
"marked-highlight": "^2.1.0",
|
|
22
34
|
"nunjucks": "^3.2.4",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
35
|
+
"prompts": "^2.4.2",
|
|
36
|
+
"ws": "^8.16.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
25
40
|
}
|
|
26
41
|
}
|
package/readme.md
CHANGED
|
@@ -1,115 +1,264 @@
|
|
|
1
|
-
# Sia
|
|
1
|
+
# Sia
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A simple, powerful static site generator built with JavaScript. Similar to Eleventy/11ty, Sia supports markdown, front matter, Nunjucks templates, and more.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
- **Markdown & Front Matter** - Write content in markdown with YAML front matter
|
|
8
|
+
- **Nunjucks Templates** - Flexible templating with includes and layouts
|
|
9
|
+
- **Multiple Content Types** - Blog posts, pages, and notes (tweet-like short posts)
|
|
10
|
+
- **Tags & Categories** - Organize content with tags, auto-generated tag pages
|
|
11
|
+
- **Pagination** - Built-in pagination for listing pages
|
|
12
|
+
- **Image Support** - Automatic image copying and organization
|
|
13
|
+
- **Live Reload** - Development server with hot reloading
|
|
14
|
+
- **Dark Mode** - Built-in light/dark theme with toggle
|
|
15
|
+
- **RSS Feed** - Automatic RSS feed generation
|
|
16
|
+
- **YAML/JSON Config** - Flexible configuration options
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Create a New Site
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Install Sia globally
|
|
24
|
+
npm install -g sia
|
|
25
|
+
|
|
26
|
+
# Create a new site
|
|
27
|
+
sia init my-blog
|
|
28
|
+
|
|
29
|
+
# Or create in current directory
|
|
30
|
+
sia init
|
|
31
|
+
|
|
32
|
+
# Non-interactive mode
|
|
33
|
+
sia init my-blog --yes
|
|
9
34
|
```
|
|
10
35
|
|
|
11
|
-
|
|
36
|
+
### Development
|
|
12
37
|
|
|
38
|
+
```bash
|
|
39
|
+
cd my-blog
|
|
40
|
+
npm install
|
|
41
|
+
npm run dev
|
|
13
42
|
```
|
|
14
|
-
|
|
43
|
+
|
|
44
|
+
Visit `http://localhost:3000` to see your site.
|
|
45
|
+
|
|
46
|
+
### Production Build
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run build
|
|
15
50
|
```
|
|
16
51
|
|
|
17
|
-
|
|
52
|
+
The output will be in the `dist/` folder, ready to deploy to any static hosting.
|
|
18
53
|
|
|
19
|
-
|
|
54
|
+
## Creating Content
|
|
20
55
|
|
|
21
|
-
|
|
56
|
+
### New Blog Post
|
|
22
57
|
|
|
58
|
+
```bash
|
|
59
|
+
npx sia new post "My Post Title"
|
|
60
|
+
```
|
|
23
61
|
|
|
24
|
-
|
|
62
|
+
Creates a new markdown file in `src/posts/` with front matter template.
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
### New Page
|
|
27
65
|
|
|
66
|
+
```bash
|
|
67
|
+
npx sia new page "About Me"
|
|
28
68
|
```
|
|
29
|
-
|
|
69
|
+
|
|
70
|
+
Creates a new page in `src/pages/`.
|
|
71
|
+
|
|
72
|
+
### New Note
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx sia new note "Quick thought"
|
|
30
76
|
```
|
|
31
77
|
|
|
32
|
-
|
|
78
|
+
Creates a short note in `src/notes/`.
|
|
33
79
|
|
|
34
|
-
|
|
80
|
+
## Project Structure
|
|
35
81
|
|
|
36
82
|
```
|
|
37
|
-
|
|
83
|
+
my-site/
|
|
84
|
+
├── _config.yml # Site configuration
|
|
85
|
+
├── src/
|
|
86
|
+
│ ├── posts/ # Blog posts (markdown)
|
|
87
|
+
│ ├── pages/ # Static pages
|
|
88
|
+
│ ├── notes/ # Short notes/tweets
|
|
89
|
+
│ └── images/ # Images
|
|
90
|
+
├── _layouts/ # Custom layouts (optional)
|
|
91
|
+
├── _includes/ # Custom includes (optional)
|
|
92
|
+
├── styles/ # Custom CSS (optional)
|
|
93
|
+
└── dist/ # Generated output
|
|
38
94
|
```
|
|
39
95
|
|
|
40
|
-
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
Edit `_config.yml` to customize your site:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
site:
|
|
102
|
+
title: "My Blog"
|
|
103
|
+
description: "A personal blog"
|
|
104
|
+
url: "https://example.com"
|
|
105
|
+
author: "Your Name"
|
|
106
|
+
|
|
107
|
+
input: src
|
|
108
|
+
output: dist
|
|
109
|
+
|
|
110
|
+
collections:
|
|
111
|
+
posts:
|
|
112
|
+
path: posts
|
|
113
|
+
layout: post
|
|
114
|
+
permalink: /blog/:slug/
|
|
115
|
+
sortBy: date
|
|
116
|
+
sortOrder: desc
|
|
117
|
+
pages:
|
|
118
|
+
path: pages
|
|
119
|
+
layout: page
|
|
120
|
+
permalink: /:slug/
|
|
121
|
+
notes:
|
|
122
|
+
path: notes
|
|
123
|
+
layout: note
|
|
124
|
+
permalink: /notes/:slug/
|
|
125
|
+
|
|
126
|
+
pagination:
|
|
127
|
+
size: 10
|
|
128
|
+
|
|
129
|
+
server:
|
|
130
|
+
port: 3000
|
|
131
|
+
```
|
|
41
132
|
|
|
42
|
-
|
|
133
|
+
## Front Matter
|
|
43
134
|
|
|
44
|
-
|
|
45
|
-
---
|
|
135
|
+
Each markdown file can have YAML front matter:
|
|
46
136
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
137
|
+
```yaml
|
|
138
|
+
---
|
|
139
|
+
title: "My Post Title"
|
|
140
|
+
date: 2024-12-17
|
|
141
|
+
tags: [javascript, tutorial]
|
|
142
|
+
layout: post
|
|
143
|
+
permalink: /custom-url/
|
|
144
|
+
draft: true # Excludes from build
|
|
145
|
+
excerpt: "Custom excerpt text"
|
|
51
146
|
---
|
|
52
147
|
```
|
|
53
148
|
|
|
54
|
-
|
|
55
|
-
- `title` is the post title and used in the blog posts list page
|
|
56
|
-
- `create_at` is the date of the post
|
|
57
|
-
- `description` a short description of the post
|
|
149
|
+
### Supported Fields
|
|
58
150
|
|
|
59
|
-
|
|
151
|
+
| Field | Description |
|
|
152
|
+
|-------|-------------|
|
|
153
|
+
| `title` | Post/page title |
|
|
154
|
+
| `date` | Publication date |
|
|
155
|
+
| `tags` | Array of tags |
|
|
156
|
+
| `layout` | Template to use |
|
|
157
|
+
| `permalink` | Custom URL |
|
|
158
|
+
| `draft` | If true, excluded from build |
|
|
159
|
+
| `excerpt` | Custom excerpt |
|
|
60
160
|
|
|
61
|
-
##
|
|
161
|
+
## Templates
|
|
62
162
|
|
|
63
|
-
|
|
163
|
+
Sia uses Nunjucks for templating. Templates are loaded from:
|
|
64
164
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
165
|
+
1. `_layouts/` - Your custom layouts
|
|
166
|
+
2. `_includes/` - Your custom includes
|
|
167
|
+
3. Default templates (provided by Sia)
|
|
68
168
|
|
|
69
|
-
|
|
70
|
-
The default output folder is `/public`
|
|
169
|
+
### Available Variables
|
|
71
170
|
|
|
72
|
-
|
|
171
|
+
In templates, you have access to:
|
|
73
172
|
|
|
74
|
-
|
|
173
|
+
- `site` - Site configuration (title, description, etc.)
|
|
174
|
+
- `page` - Current page data
|
|
175
|
+
- `content` - Rendered markdown content
|
|
176
|
+
- `collections` - All content collections
|
|
177
|
+
- `tags` - Tag data with counts
|
|
178
|
+
- `allTags` - Array of all tags
|
|
75
179
|
|
|
76
|
-
|
|
180
|
+
### Custom Filters
|
|
77
181
|
|
|
78
|
-
|
|
182
|
+
| Filter | Description | Example |
|
|
183
|
+
|--------|-------------|---------|
|
|
184
|
+
| `date` | Format dates | `{{ page.date \| date('long') }}` |
|
|
185
|
+
| `slug` | Generate URL slug | `{{ title \| slug }}` |
|
|
186
|
+
| `excerpt` | Get excerpt | `{{ content \| excerpt(200) }}` |
|
|
187
|
+
| `limit` | Limit array | `{{ posts \| limit(5) }}` |
|
|
188
|
+
| `readingTime` | Estimate reading time | `{{ content \| readingTime }}` |
|
|
189
|
+
| `withTag` | Filter by tag | `{{ posts \| withTag('javascript') }}` |
|
|
79
190
|
|
|
80
|
-
##
|
|
191
|
+
## Customization
|
|
81
192
|
|
|
82
|
-
|
|
193
|
+
### Custom Layouts
|
|
83
194
|
|
|
84
|
-
|
|
85
|
-
"scripts": {
|
|
86
|
-
"serve": "npx http-server public",
|
|
87
|
-
"watch": "forever --watchDirectory ./src -w ./node_modules/@terrymooreii/sia/lib/index.js build",
|
|
88
|
-
"dev": "concurrently --kill-others \"npm run watch\" \"npm run serve\"",
|
|
89
|
-
"clean": "rm -rf public",
|
|
90
|
-
"build": "sia build"
|
|
91
|
-
},
|
|
92
|
-
```
|
|
195
|
+
Create `_layouts/post.njk` to override the default post layout:
|
|
93
196
|
|
|
197
|
+
```njk
|
|
198
|
+
{% extends "base.njk" %}
|
|
199
|
+
|
|
200
|
+
{% block content %}
|
|
201
|
+
<article>
|
|
202
|
+
<h1>{{ page.title }}</h1>
|
|
203
|
+
{{ content | safe }}
|
|
204
|
+
</article>
|
|
205
|
+
{% endblock %}
|
|
94
206
|
```
|
|
95
|
-
|
|
207
|
+
|
|
208
|
+
### Custom Styles
|
|
209
|
+
|
|
210
|
+
Create `styles/main.css` to override default styles. Your custom CSS will be used instead of the default theme.
|
|
211
|
+
|
|
212
|
+
### Custom Includes
|
|
213
|
+
|
|
214
|
+
Create `_includes/header.njk` to override the header, etc.
|
|
215
|
+
|
|
216
|
+
## CLI Commands
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Create a new site
|
|
220
|
+
sia init [directory]
|
|
221
|
+
sia init my-blog --yes # Non-interactive
|
|
222
|
+
|
|
223
|
+
# Start development server
|
|
224
|
+
sia dev
|
|
225
|
+
sia dev --port 8080
|
|
226
|
+
|
|
227
|
+
# Build for production
|
|
228
|
+
sia build
|
|
229
|
+
sia build --clean
|
|
230
|
+
|
|
231
|
+
# Create new content
|
|
232
|
+
sia new post "Title"
|
|
233
|
+
sia new page "Title"
|
|
234
|
+
sia new note "Content"
|
|
96
235
|
```
|
|
97
236
|
|
|
98
|
-
|
|
237
|
+
## Upgrading
|
|
238
|
+
|
|
239
|
+
If you installed Sia as a dependency (recommended):
|
|
99
240
|
|
|
100
|
-
|
|
241
|
+
```bash
|
|
242
|
+
# Check for updates
|
|
243
|
+
npm outdated
|
|
101
244
|
|
|
102
|
-
|
|
245
|
+
# Upgrade to latest
|
|
246
|
+
npm update sia
|
|
247
|
+
|
|
248
|
+
# Or upgrade to specific version
|
|
249
|
+
npm install sia@2.0.0
|
|
250
|
+
```
|
|
103
251
|
|
|
104
|
-
|
|
252
|
+
## Deployment
|
|
105
253
|
|
|
254
|
+
After running `npm run build`, deploy the `dist/` folder to:
|
|
106
255
|
|
|
107
|
-
|
|
256
|
+
- **Netlify** - Drag and drop or connect to Git
|
|
257
|
+
- **Vercel** - Import project
|
|
258
|
+
- **GitHub Pages** - Push to `gh-pages` branch
|
|
259
|
+
- **Cloudflare Pages** - Connect repository
|
|
260
|
+
- **Any static host** - Upload the `dist/` folder
|
|
108
261
|
|
|
109
|
-
|
|
262
|
+
## License
|
|
110
263
|
|
|
111
|
-
|
|
112
|
-
[ ] Pagination
|
|
113
|
-
[ ] `sia init` to generate a new site and clean up of the initial theme
|
|
114
|
-
[ ] While pages and longer blog posts are great, i would like to add a mircoblogging feed to the site.
|
|
115
|
-
[ ] Github action to publish new version to npm
|
|
264
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "About"
|
|
3
|
+
layout: page
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## About This Site
|
|
7
|
+
|
|
8
|
+
This site is built with **Sia**, a simple and powerful static site generator.
|
|
9
|
+
|
|
10
|
+
### About Me
|
|
11
|
+
|
|
12
|
+
Hello! I'm the author of this blog. Feel free to customize this page with your own information.
|
|
13
|
+
|
|
14
|
+
### Contact
|
|
15
|
+
|
|
16
|
+
You can reach me at:
|
|
17
|
+
|
|
18
|
+
- Email: your.email@example.com
|
|
19
|
+
- Twitter: @yourhandle
|
|
20
|
+
- GitHub: @yourusername
|
|
21
|
+
|
|
22
|
+
### Colophon
|
|
23
|
+
|
|
24
|
+
This site is:
|
|
25
|
+
|
|
26
|
+
- Generated with [Sia](https://github.com/sia/sia)
|
|
27
|
+
- Written in Markdown
|
|
28
|
+
- Styled with CSS
|
|
29
|
+
- Hosted on [your hosting provider]
|