@terrymooreii/sia 2.1.9 → 2.1.11

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/docs/README.md CHANGED
@@ -121,8 +121,47 @@ pagination:
121
121
  server:
122
122
  port: 3000
123
123
  showDrafts: false
124
+
125
+ assets:
126
+ css: [] # Custom CSS files (paths relative to root)
127
+ js: [] # Custom JavaScript files (paths relative to root)
128
+ ```
129
+
130
+ ## Custom CSS and JavaScript
131
+
132
+ You can inject custom CSS and JavaScript files into your theme by defining them in `_config.yml`:
133
+
134
+ ```yaml
135
+ assets:
136
+ css:
137
+ - custom/styles.css
138
+ - vendor/library.css
139
+ js:
140
+ - custom/script.js
141
+ - vendor/analytics.js
124
142
  ```
125
143
 
144
+ Files are specified as paths relative to your project root. During build, CSS files are copied to `dist/styles/` and JavaScript files to `dist/scripts/`, preserving directory structure. Custom CSS is injected after theme styles (allowing overrides), and JavaScript is injected before the closing `</body>` tag.
145
+
146
+ **Example:**
147
+ ```yaml
148
+ assets:
149
+ css:
150
+ - assets/custom.css
151
+ - vendor/prism.css
152
+ js:
153
+ - assets/analytics.js
154
+ - vendor/prism.js
155
+ ```
156
+
157
+ This will:
158
+ - Copy `assets/custom.css` → `dist/styles/assets/custom.css`
159
+ - Copy `vendor/prism.css` → `dist/styles/vendor/prism.css`
160
+ - Copy `assets/analytics.js` → `dist/scripts/assets/analytics.js`
161
+ - Copy `vendor/prism.js` → `dist/scripts/vendor/prism.js`
162
+
163
+ And inject them into all pages automatically.
164
+
126
165
  ## Static Assets
127
166
 
128
167
  Sia automatically copies static assets during the build process. You can place static files in any of these locations:
@@ -107,6 +107,35 @@ Array of all tags sorted by count (most used first):
107
107
  </ul>
108
108
  ```
109
109
 
110
+ ### `customAssets`
111
+
112
+ Custom CSS and JavaScript files defined in `_config.yml`:
113
+
114
+ | Property | Type | Description |
115
+ |----------|------|-------------|
116
+ | `customAssets.css` | array | Array of CSS file paths (relative to output root) |
117
+ | `customAssets.js` | array | Array of JavaScript file paths (relative to output root) |
118
+
119
+ **Note:** Custom assets are automatically injected into all theme base layouts via shared includes (`custom-assets-css.njk` and `custom-assets-js.njk`). You typically don't need to access this variable directly unless creating custom layouts.
120
+
121
+ **Example:**
122
+
123
+ ```nunjucks
124
+ {# Manually inject custom CSS #}
125
+ {% if customAssets and customAssets.css %}
126
+ {% for css in customAssets.css %}
127
+ <link rel="stylesheet" href="{{ css | url }}">
128
+ {% endfor %}
129
+ {% endif %}
130
+
131
+ {# Manually inject custom JavaScript #}
132
+ {% if customAssets and customAssets.js %}
133
+ {% for js in customAssets.js %}
134
+ <script src="{{ js | url }}"></script>
135
+ {% endfor %}
136
+ {% endif %}
137
+ ```
138
+
110
139
  ---
111
140
 
112
141
  ## Page Context Variables
package/lib/assets.js CHANGED
@@ -134,39 +134,41 @@ function hasCssFiles(dir) {
134
134
  export function copyDefaultStyles(config, resolvedTheme = null) {
135
135
  const outputStylesDir = join(config.outputDir, 'styles');
136
136
 
137
- // Check if user has custom styles (must actually have CSS files)
138
- const userStylesDir = join(config.rootDir, 'styles');
139
-
140
- if (hasCssFiles(userStylesDir)) {
141
- // Copy user styles
142
- const copied = copyAssets(userStylesDir, outputStylesDir);
143
- console.log(`🎨 Copied ${copied.length} custom style files`);
144
- return copied;
145
- }
146
-
147
- // Resolve theme if not already resolved
137
+ // Always copy theme styles first
148
138
  const themeName = config.theme?.name || 'main';
149
139
  const theme = resolvedTheme || resolveTheme(themeName, config.rootDir);
150
140
  const themeStylesDir = join(theme.themeDir, 'styles');
151
141
 
142
+ let themeCopied = [];
152
143
  if (existsSync(themeStylesDir)) {
153
- const copied = copyAssets(themeStylesDir, outputStylesDir);
144
+ themeCopied = copyAssets(themeStylesDir, outputStylesDir);
154
145
  if (!theme.isExternal) {
155
146
  console.log(`🎨 Using "${theme.themeName}" theme`);
156
147
  }
157
- return copied;
148
+ } else {
149
+ // Fallback to main theme if theme styles not found
150
+ const fallbackStylesDir = join(getBuiltInThemesDir(), 'main', 'styles');
151
+ if (existsSync(fallbackStylesDir)) {
152
+ console.log(`⚠️ Theme "${themeName}" styles not found, using "main" theme styles`);
153
+ themeCopied = copyAssets(fallbackStylesDir, outputStylesDir);
154
+ }
158
155
  }
159
156
 
160
- // Fallback to main theme if theme styles not found
161
- const fallbackStylesDir = join(getBuiltInThemesDir(), 'main', 'styles');
162
- if (existsSync(fallbackStylesDir)) {
163
- console.log(`⚠️ Theme "${themeName}" styles not found, using "main" theme styles`);
164
- const copied = copyAssets(fallbackStylesDir, outputStylesDir);
165
- return copied;
157
+ // Then copy user custom styles (can override theme styles)
158
+ const userStylesDir = join(config.rootDir, 'styles');
159
+ let userCopied = [];
160
+
161
+ if (hasCssFiles(userStylesDir)) {
162
+ userCopied = copyAssets(userStylesDir, outputStylesDir);
163
+ console.log(`🎨 Copied ${userCopied.length} custom style file(s)`);
166
164
  }
167
165
 
168
- console.log('⚠️ No styles found');
169
- return [];
166
+ const totalCopied = themeCopied.length + userCopied.length;
167
+ if (totalCopied === 0) {
168
+ console.log('⚠️ No styles found');
169
+ }
170
+
171
+ return [...themeCopied, ...userCopied];
170
172
  }
171
173
 
172
174
  /**
@@ -200,6 +202,82 @@ export function copyStaticAssets(config) {
200
202
  return totalCopied;
201
203
  }
202
204
 
205
+ /**
206
+ * Copy custom CSS and JavaScript files defined in config
207
+ *
208
+ * @param {object} config - Site configuration
209
+ * @returns {object} Object with css and js arrays of output paths
210
+ */
211
+ export function copyCustomAssets(config) {
212
+ const customAssets = {
213
+ css: [],
214
+ js: []
215
+ };
216
+
217
+ if (!config.assets) {
218
+ return customAssets;
219
+ }
220
+
221
+ const outputStylesDir = join(config.outputDir, 'styles');
222
+ const outputScriptsDir = join(config.outputDir, 'scripts');
223
+
224
+ // Copy CSS files
225
+ if (Array.isArray(config.assets.css) && config.assets.css.length > 0) {
226
+ for (const cssPath of config.assets.css) {
227
+ const srcPath = join(config.rootDir, cssPath);
228
+
229
+ if (!existsSync(srcPath)) {
230
+ console.warn(`⚠️ Custom CSS file not found: ${cssPath}`);
231
+ continue;
232
+ }
233
+
234
+ // Preserve directory structure in output
235
+ // e.g., custom/styles.css -> dist/styles/custom/styles.css
236
+ const relativePath = cssPath.startsWith('/') ? cssPath.slice(1) : cssPath;
237
+ const destPath = join(outputStylesDir, relativePath);
238
+
239
+ copyFile(srcPath, destPath);
240
+
241
+ // Store output path for template injection (relative to output root)
242
+ const outputPath = `/styles/${relativePath}`;
243
+ customAssets.css.push(outputPath);
244
+ }
245
+
246
+ if (customAssets.css.length > 0) {
247
+ console.log(`📝 Copied ${customAssets.css.length} custom CSS file(s)`);
248
+ }
249
+ }
250
+
251
+ // Copy JavaScript files
252
+ if (Array.isArray(config.assets.js) && config.assets.js.length > 0) {
253
+ for (const jsPath of config.assets.js) {
254
+ const srcPath = join(config.rootDir, jsPath);
255
+
256
+ if (!existsSync(srcPath)) {
257
+ console.warn(`⚠️ Custom JavaScript file not found: ${jsPath}`);
258
+ continue;
259
+ }
260
+
261
+ // Preserve directory structure in output
262
+ // e.g., custom/script.js -> dist/scripts/custom/script.js
263
+ const relativePath = jsPath.startsWith('/') ? jsPath.slice(1) : jsPath;
264
+ const destPath = join(outputScriptsDir, relativePath);
265
+
266
+ copyFile(srcPath, destPath);
267
+
268
+ // Store output path for template injection (relative to output root)
269
+ const outputPath = `/scripts/${relativePath}`;
270
+ customAssets.js.push(outputPath);
271
+ }
272
+
273
+ if (customAssets.js.length > 0) {
274
+ console.log(`📜 Copied ${customAssets.js.length} custom JavaScript file(s)`);
275
+ }
276
+ }
277
+
278
+ return customAssets;
279
+ }
280
+
203
281
  /**
204
282
  * Write a file with directory creation
205
283
  */
@@ -245,6 +323,7 @@ export default {
245
323
  copyImages,
246
324
  copyDefaultStyles,
247
325
  copyStaticAssets,
326
+ copyCustomAssets,
248
327
  writeFile,
249
328
  cleanDir
250
329
  };
package/lib/build.js CHANGED
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import { loadConfig } from './config.js';
5
5
  import { buildSiteData, paginate, getPaginationUrls } from './collections.js';
6
6
  import { createTemplateEngine, renderTemplate } from './templates.js';
7
- import { copyImages, copyDefaultStyles, copyStaticAssets, writeFile, ensureDir } from './assets.js';
7
+ import { copyImages, copyDefaultStyles, copyStaticAssets, copyCustomAssets, writeFile, ensureDir } from './assets.js';
8
8
  import { resolveTheme } from './theme-resolver.js';
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
@@ -222,6 +222,10 @@ export async function build(options = {}) {
222
222
  // Build site data (collections, tags, etc.)
223
223
  const siteData = buildSiteData(config);
224
224
 
225
+ // Copy custom assets and add to siteData
226
+ const customAssets = copyCustomAssets(config);
227
+ siteData.customAssets = customAssets;
228
+
225
229
  // Create template engine with resolved theme
226
230
  const env = createTemplateEngine(config, resolvedTheme);
227
231
 
@@ -249,6 +253,7 @@ export async function build(options = {}) {
249
253
  // Copy assets
250
254
  copyImages(config);
251
255
  copyDefaultStyles(config, resolvedTheme);
256
+ // Custom assets already copied above and added to siteData
252
257
  copyStaticAssets(config);
253
258
 
254
259
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
package/lib/config.js CHANGED
@@ -44,6 +44,10 @@ const defaultConfig = {
44
44
  server: {
45
45
  port: 3000,
46
46
  showDrafts: false // Show draft posts when using dev server
47
+ },
48
+ assets: {
49
+ css: [], // Array of custom CSS file paths (relative to root)
50
+ js: [] // Array of custom JavaScript file paths (relative to root)
47
51
  }
48
52
  };
49
53
 
package/lib/content.js CHANGED
@@ -351,7 +351,9 @@ export function getDateFromFilename(filename) {
351
351
  const match = name.match(datePattern);
352
352
 
353
353
  if (match) {
354
- return new Date(match[1]);
354
+ // Parse as local date, not UTC
355
+ const [year, month, day] = match[1].split('-').map(Number);
356
+ return new Date(year, month - 1, day);
355
357
  }
356
358
 
357
359
  return null;
@@ -377,7 +379,13 @@ export function parseContent(filePath) {
377
379
  // Get date from front matter or filename
378
380
  let date = frontMatter.date;
379
381
  if (date) {
380
- date = new Date(date);
382
+ // If it's a date-only string (YYYY-MM-DD), parse as local date
383
+ if (typeof date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
384
+ const [year, month, day] = date.split('-').map(Number);
385
+ date = new Date(year, month - 1, day);
386
+ } else {
387
+ date = new Date(date);
388
+ }
381
389
  } else {
382
390
  date = getDateFromFilename(filePath) || new Date();
383
391
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrymooreii/sia",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "description": "A simple, powerful static site generator with markdown, front matter, and Nunjucks templates",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -0,0 +1,7 @@
1
+ {# Custom CSS files - include in <head> after theme styles #}
2
+ {% if customAssets and customAssets.css %}
3
+ {% for css in customAssets.css %}
4
+ <link rel="stylesheet" href="{{ css | url }}">
5
+ {% endfor %}
6
+ {% endif %}
7
+
@@ -0,0 +1,7 @@
1
+ {# Custom JavaScript files - include before </body> #}
2
+ {% if customAssets and customAssets.js %}
3
+ {% for js in customAssets.js %}
4
+ <script src="{{ js | url }}"></script>
5
+ {% endfor %}
6
+ {% endif %}
7
+
@@ -8,6 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
11
+ {% include "custom-assets-css.njk" %}
11
12
 
12
13
  {% block head %}{% endblock %}
13
14
  </head>
@@ -25,5 +26,6 @@
25
26
  {% include "footer.njk" %}
26
27
  </div>
27
28
  </div>
29
+ {% include "custom-assets-js.njk" %}
28
30
  </body>
29
31
  </html>
@@ -26,7 +26,7 @@
26
26
  <a href="{{ post.url }}">{{ post.title }}</a>
27
27
  </h3>
28
28
 
29
- <p class="card-excerpt">{{ post.excerpt | excerpt(120) }}</p>
29
+ <p class="card-excerpt">{{ post.excerptHtml | safe }}</p>
30
30
 
31
31
  <div class="card-meta">
32
32
  <span class="card-date">{{ post.date | date('short') }}</span>
@@ -23,7 +23,7 @@
23
23
  <a href="{{ post.url }}">{{ post.title }}</a>
24
24
  </h2>
25
25
 
26
- <p class="card-excerpt">{{ post.excerpt | excerpt(150) }}</p>
26
+ <p class="card-excerpt">{{ post.excerptHtml | safe }}</p>
27
27
 
28
28
  <div class="card-meta">
29
29
  <span class="card-date">{{ post.date | date('short') }}</span>
@@ -8,6 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500&display=swap" rel="stylesheet">
11
+ {% include "custom-assets-css.njk" %}
11
12
 
12
13
  {% block head %}{% endblock %}
13
14
  </head>
@@ -19,5 +20,6 @@
19
20
  </main>
20
21
 
21
22
  {% include "footer.njk" %}
23
+ {% include "custom-assets-js.njk" %}
22
24
  </body>
23
25
  </html>
@@ -14,7 +14,7 @@
14
14
  <h2 class="row-title">
15
15
  <a href="{{ post.url }}">{{ post.title }}</a>
16
16
  </h2>
17
- <p class="row-excerpt">{{ post.excerpt | excerpt(180) }}</p>
17
+ <p class="row-excerpt">{{ post.excerptHtml | safe }}</p>
18
18
  <div class="row-meta">
19
19
  <span class="row-author">{{ post.author or site.author }}</span>
20
20
  <span class="meta-separator">·</span>
@@ -5,6 +5,7 @@
5
5
  {% include "theme-script.njk" %}
6
6
 
7
7
  <link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
8
+ {% include "custom-assets-css.njk" %}
8
9
 
9
10
  {% block head %}{% endblock %}
10
11
  </head>
@@ -16,6 +17,7 @@
16
17
  </main>
17
18
 
18
19
  {% include "footer.njk" %}
20
+ {% include "custom-assets-js.njk" %}
19
21
  </body>
20
22
  </html>
21
23
 
@@ -20,7 +20,7 @@
20
20
  <ul class="tag-posts">
21
21
  {% for item in tag.items | limit(5) %}
22
22
  <li>
23
- <a href="{{ item.url }}">{{ item.title or item.excerpt }}</a>
23
+ <a href="{{ item.url }}">{{ item.title or item.excerptHtml | safe }}</a>
24
24
  <time datetime="{{ item.date | date('iso') }}">{{ item.date | date('short') }}</time>
25
25
  </li>
26
26
  {% endfor %}
@@ -5,6 +5,7 @@
5
5
  {% include "theme-script.njk" %}
6
6
 
7
7
  <link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
8
+ {% include "custom-assets-css.njk" %}
8
9
 
9
10
  {% block head %}{% endblock %}
10
11
  </head>
@@ -16,6 +17,7 @@
16
17
  </main>
17
18
 
18
19
  {% include "footer.njk" %}
20
+ {% include "custom-assets-js.njk" %}
19
21
  </body>
20
22
  </html>
21
23