@terrymooreii/sia 2.3.1 → 2.3.3
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 +46 -8
- package/docs/front-matter.md +40 -2
- package/lib/content.js +113 -3
- package/lib/migrate.js +5 -2
- package/lib/new.js +9 -3
- package/package.json +1 -1
package/docs/README.md
CHANGED
|
@@ -71,17 +71,26 @@ my-site/
|
|
|
71
71
|
├── _config.yml # Site configuration
|
|
72
72
|
├── src/
|
|
73
73
|
│ ├── posts/ # Blog posts (markdown)
|
|
74
|
-
│ │
|
|
75
|
-
│ │
|
|
76
|
-
│ │
|
|
74
|
+
│ │ ├── 2024-12-17-my-post/ # Flat structure (default)
|
|
75
|
+
│ │ │ ├── index.md
|
|
76
|
+
│ │ │ └── (assets can go here)
|
|
77
|
+
│ │ └── 2024/ # Or date-organized (if path: posts/:year/:month)
|
|
78
|
+
│ │ └── 12/
|
|
79
|
+
│ │ └── 2024-12-17-my-post/
|
|
80
|
+
│ │ ├── index.md
|
|
81
|
+
│ │ └── (assets can go here)
|
|
77
82
|
│ ├── pages/ # Static pages
|
|
78
83
|
│ │ └── about/
|
|
79
84
|
│ │ ├── index.md
|
|
80
85
|
│ │ └── (assets can go here)
|
|
81
86
|
│ ├── notes/ # Short notes/tweets
|
|
82
|
-
│ │
|
|
83
|
-
│ │
|
|
84
|
-
│ │
|
|
87
|
+
│ │ ├── 2024-12-17-note-1234567890/ # Flat structure (default)
|
|
88
|
+
│ │ │ ├── index.md
|
|
89
|
+
│ │ │ └── (assets can go here)
|
|
90
|
+
│ │ └── 2024/ # Or date-organized (if path: notes/:year)
|
|
91
|
+
│ │ └── 2024-12-17-note-1234567890/
|
|
92
|
+
│ │ ├── index.md
|
|
93
|
+
│ │ └── (assets can go here)
|
|
85
94
|
│ └── images/ # Images
|
|
86
95
|
├── assets/ # Static assets (optional)
|
|
87
96
|
├── static/ # Static assets (optional)
|
|
@@ -97,10 +106,39 @@ my-site/
|
|
|
97
106
|
|
|
98
107
|
Each post, page, and note is created as a folder containing an `index.md` file. This allows you to organize assets (images, PDFs, etc.) alongside your content in the same folder.
|
|
99
108
|
|
|
109
|
+
**Note:** You can organize posts and notes by date using date variables in the `path` configuration (e.g., `posts/:year/:month`). See [Date Variables in Paths](#date-variables-in-paths) for details.
|
|
110
|
+
|
|
100
111
|
## Configuration
|
|
101
112
|
|
|
102
113
|
Edit `_config.yml` to customize your site:
|
|
103
114
|
|
|
115
|
+
### Date Variables in Paths
|
|
116
|
+
|
|
117
|
+
You can organize posts and notes by date using date variables in the `path` property:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
collections:
|
|
121
|
+
posts:
|
|
122
|
+
path: posts/:year/:month # Organizes posts by year and month
|
|
123
|
+
# New posts will be created in: posts/2024/01/2024-01-15-slug/
|
|
124
|
+
|
|
125
|
+
notes:
|
|
126
|
+
path: notes/:year # Organizes notes by year only
|
|
127
|
+
# New notes will be created in: notes/2024/2024-01-15-note-1234567890/
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Supported date variables:**
|
|
131
|
+
- `:year` - 4-digit year (e.g., `2024`)
|
|
132
|
+
- `:month` - 2-digit month (e.g., `01`, `12`)
|
|
133
|
+
- `:day` - 2-digit day (e.g., `01`, `31`)
|
|
134
|
+
|
|
135
|
+
**Examples:**
|
|
136
|
+
- `posts/:year/:month` → `posts/2024/01/`
|
|
137
|
+
- `posts/:year` → `posts/2024/`
|
|
138
|
+
- `notes/:year/:month/:day` → `notes/2024/01/15/`
|
|
139
|
+
|
|
140
|
+
When loading collections, Sia automatically searches recursively through all date-organized directories, so existing content will be found regardless of the path structure.
|
|
141
|
+
|
|
104
142
|
```yaml
|
|
105
143
|
site:
|
|
106
144
|
title: "My Blog"
|
|
@@ -117,7 +155,7 @@ output: dist
|
|
|
117
155
|
|
|
118
156
|
collections:
|
|
119
157
|
posts:
|
|
120
|
-
path: posts
|
|
158
|
+
path: posts # Or use date variables: posts/:year/:month
|
|
121
159
|
layout: post
|
|
122
160
|
permalink: /blog/:slug/
|
|
123
161
|
sortBy: date
|
|
@@ -127,7 +165,7 @@ collections:
|
|
|
127
165
|
layout: page
|
|
128
166
|
permalink: /:slug/
|
|
129
167
|
notes:
|
|
130
|
-
path: notes
|
|
168
|
+
path: notes # Or use date variables: notes/:year
|
|
131
169
|
layout: note
|
|
132
170
|
permalink: /notes/:slug/
|
|
133
171
|
|
package/docs/front-matter.md
CHANGED
|
@@ -165,13 +165,32 @@ Blog posts are stored in `src/posts/` (or your configured path).
|
|
|
165
165
|
# From _config.yml
|
|
166
166
|
collections:
|
|
167
167
|
posts:
|
|
168
|
-
path: posts
|
|
168
|
+
path: posts # Or use date variables: posts/:year/:month
|
|
169
169
|
layout: post
|
|
170
170
|
permalink: /blog/:slug/
|
|
171
171
|
sortBy: date
|
|
172
172
|
sortOrder: desc
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
+
**Date Variables in Paths:**
|
|
176
|
+
|
|
177
|
+
You can organize posts by date using date variables in the `path` property:
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
collections:
|
|
181
|
+
posts:
|
|
182
|
+
path: posts/:year/:month # Creates: posts/2024/01/2024-01-15-slug/
|
|
183
|
+
# Or
|
|
184
|
+
path: posts/:year # Creates: posts/2024/2024-01-15-slug/
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Supported variables:**
|
|
188
|
+
- `:year` - 4-digit year (e.g., `2024`)
|
|
189
|
+
- `:month` - 2-digit month (e.g., `01`, `12`)
|
|
190
|
+
- `:day` - 2-digit day (e.g., `01`, `31`)
|
|
191
|
+
|
|
192
|
+
When you create a new post, it will be automatically placed in the correct date-organized directory based on the current date.
|
|
193
|
+
|
|
175
194
|
### Typical Post Front Matter
|
|
176
195
|
|
|
177
196
|
```yaml
|
|
@@ -268,13 +287,32 @@ Notes are short-form content stored in `src/notes/` (or your configured path). T
|
|
|
268
287
|
# From _config.yml
|
|
269
288
|
collections:
|
|
270
289
|
notes:
|
|
271
|
-
path: notes
|
|
290
|
+
path: notes # Or use date variables: notes/:year
|
|
272
291
|
layout: note
|
|
273
292
|
permalink: /notes/:slug/
|
|
274
293
|
sortBy: date
|
|
275
294
|
sortOrder: desc
|
|
276
295
|
```
|
|
277
296
|
|
|
297
|
+
**Date Variables in Paths:**
|
|
298
|
+
|
|
299
|
+
You can organize notes by date using date variables in the `path` property:
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
collections:
|
|
303
|
+
notes:
|
|
304
|
+
path: notes/:year # Creates: notes/2024/2024-01-15-note-1234567890/
|
|
305
|
+
# Or
|
|
306
|
+
path: notes/:year/:month # Creates: notes/2024/01/2024-01-15-note-1234567890/
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Supported variables:**
|
|
310
|
+
- `:year` - 4-digit year (e.g., `2024`)
|
|
311
|
+
- `:month` - 2-digit month (e.g., `01`, `12`)
|
|
312
|
+
- `:day` - 2-digit day (e.g., `01`, `31`)
|
|
313
|
+
|
|
314
|
+
When you create a new note, it will be automatically placed in the correct date-organized directory based on the current date.
|
|
315
|
+
|
|
278
316
|
### Typical Note Front Matter
|
|
279
317
|
|
|
280
318
|
Notes often have minimal front matter since they're short-form:
|
package/lib/content.js
CHANGED
|
@@ -321,6 +321,49 @@ function truncateMarkdownSafely(text, maxLength) {
|
|
|
321
321
|
return text.substring(0, truncateAt).trim() + '...';
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Expand date variables in a path template
|
|
326
|
+
* Supports :year, :month, :day variables
|
|
327
|
+
* @param {string} pathTemplate - Path template with date variables (e.g., "posts/:year/:month")
|
|
328
|
+
* @param {Date} date - Date to use for expansion
|
|
329
|
+
* @returns {string} Expanded path
|
|
330
|
+
*/
|
|
331
|
+
export function expandDatePath(pathTemplate, date) {
|
|
332
|
+
if (!pathTemplate || typeof pathTemplate !== 'string') {
|
|
333
|
+
return pathTemplate;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const year = date.getFullYear();
|
|
337
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
338
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
339
|
+
|
|
340
|
+
return pathTemplate
|
|
341
|
+
.replace(/:year/g, year)
|
|
342
|
+
.replace(/:month/g, month)
|
|
343
|
+
.replace(/:day/g, day);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get base path from a path template (removes date variables for recursive searching)
|
|
348
|
+
* @param {string} pathTemplate - Path template with date variables
|
|
349
|
+
* @returns {string} Base path (everything before the first date variable)
|
|
350
|
+
*/
|
|
351
|
+
export function getBasePath(pathTemplate) {
|
|
352
|
+
if (!pathTemplate || typeof pathTemplate !== 'string') {
|
|
353
|
+
return pathTemplate;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Find the first occurrence of a date variable (can be at start or after a slash)
|
|
357
|
+
const firstVariable = pathTemplate.match(/:(\w+)/);
|
|
358
|
+
if (!firstVariable) {
|
|
359
|
+
// No date variables, return as-is
|
|
360
|
+
return pathTemplate;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Return everything before the first date variable
|
|
364
|
+
return pathTemplate.substring(0, firstVariable.index);
|
|
365
|
+
}
|
|
366
|
+
|
|
324
367
|
/**
|
|
325
368
|
* Generate a URL-friendly slug from a string
|
|
326
369
|
*/
|
|
@@ -402,10 +445,66 @@ export function getDateFromFilename(filename) {
|
|
|
402
445
|
return null;
|
|
403
446
|
}
|
|
404
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Convert relative image and link paths to absolute paths based on base URL
|
|
450
|
+
* @param {string} html - HTML content with potential relative paths
|
|
451
|
+
* @param {string} baseUrl - Base URL for the content item (e.g., '/blog/my-post/')
|
|
452
|
+
* @returns {string} HTML with absolute paths
|
|
453
|
+
*/
|
|
454
|
+
function fixRelativePaths(html, baseUrl) {
|
|
455
|
+
if (!html || !baseUrl) return html;
|
|
456
|
+
|
|
457
|
+
// Normalize baseUrl to ensure it ends with a slash
|
|
458
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
|
|
459
|
+
|
|
460
|
+
// Helper function to convert relative path to absolute
|
|
461
|
+
const makeAbsolute = (path) => {
|
|
462
|
+
// Skip if it's already an absolute URL (http://, https://) or absolute path (/)
|
|
463
|
+
if (/^(https?:|\/|#|mailto:)/.test(path)) {
|
|
464
|
+
return path;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Convert relative path to absolute path
|
|
468
|
+
// Remove leading ./ if present
|
|
469
|
+
const cleanPath = path.replace(/^\.\//, '');
|
|
470
|
+
|
|
471
|
+
// Combine base URL with path
|
|
472
|
+
return normalizedBaseUrl + cleanPath;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Fix relative image paths
|
|
476
|
+
html = html.replace(
|
|
477
|
+
/<img\s+([^>]*?)(?:src\s*=\s*(["'])([^"']+)\2)([^>]*)>/gi,
|
|
478
|
+
(match, beforeAttrs, quote, src, afterAttrs) => {
|
|
479
|
+
const absolutePath = makeAbsolute(src);
|
|
480
|
+
|
|
481
|
+
// Reconstruct the img tag with the absolute path
|
|
482
|
+
const before = beforeAttrs ? beforeAttrs.trim() + ' ' : '';
|
|
483
|
+
const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
|
|
484
|
+
return `<img ${before}src=${quote}${absolutePath}${quote}${after}>`;
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Fix relative link paths (but skip anchor links and external URLs)
|
|
489
|
+
html = html.replace(
|
|
490
|
+
/<a\s+([^>]*?)(?:href\s*=\s*(["'])([^"']+)\2)([^>]*)>/gi,
|
|
491
|
+
(match, beforeAttrs, quote, href, afterAttrs) => {
|
|
492
|
+
const absolutePath = makeAbsolute(href);
|
|
493
|
+
|
|
494
|
+
// Reconstruct the link tag with the absolute path
|
|
495
|
+
const before = beforeAttrs ? beforeAttrs.trim() + ' ' : '';
|
|
496
|
+
const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
|
|
497
|
+
return `<a ${before}href=${quote}${absolutePath}${quote}${after}>`;
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
return html;
|
|
502
|
+
}
|
|
503
|
+
|
|
405
504
|
/**
|
|
406
505
|
* Parse a markdown file with front matter
|
|
407
506
|
*/
|
|
408
|
-
export async function parseContent(filePath) {
|
|
507
|
+
export async function parseContent(filePath, options = {}) {
|
|
409
508
|
let content = readFileSync(filePath, 'utf-8');
|
|
410
509
|
|
|
411
510
|
// Execute beforeParse hook
|
|
@@ -521,7 +620,11 @@ export async function loadCollection(config, collectionName) {
|
|
|
521
620
|
return [];
|
|
522
621
|
}
|
|
523
622
|
|
|
524
|
-
|
|
623
|
+
// If path contains date variables, use base path for recursive searching
|
|
624
|
+
// Otherwise use the path as-is
|
|
625
|
+
const pathTemplate = collectionConfig.path;
|
|
626
|
+
const basePath = getBasePath(pathTemplate);
|
|
627
|
+
const collectionDir = join(config.inputDir, basePath);
|
|
525
628
|
const files = getMarkdownFiles(collectionDir);
|
|
526
629
|
|
|
527
630
|
const items = await Promise.all(
|
|
@@ -546,6 +649,11 @@ export async function loadCollection(config, collectionName) {
|
|
|
546
649
|
item.url = basePath + permalink;
|
|
547
650
|
item.outputPath = join(config.outputDir, permalink, 'index.html');
|
|
548
651
|
|
|
652
|
+
// Fix relative image and link paths in content and excerptHtml
|
|
653
|
+
// This ensures images and links work on both individual pages and list pages
|
|
654
|
+
item.content = fixRelativePaths(item.content, item.url);
|
|
655
|
+
item.excerptHtml = fixRelativePaths(item.excerptHtml, item.url);
|
|
656
|
+
|
|
549
657
|
return item;
|
|
550
658
|
} catch (err) {
|
|
551
659
|
console.error(`Error parsing ${filePath}:`, err.message);
|
|
@@ -610,6 +718,8 @@ export default {
|
|
|
610
718
|
getMarkdownFiles,
|
|
611
719
|
loadCollection,
|
|
612
720
|
loadAllCollections,
|
|
613
|
-
addMarkedExtension
|
|
721
|
+
addMarkedExtension,
|
|
722
|
+
expandDatePath,
|
|
723
|
+
getBasePath
|
|
614
724
|
};
|
|
615
725
|
|
package/lib/migrate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdirSync, statSync, existsSync, mkdirSync, renameSync } from 'fs';
|
|
2
2
|
import { join, dirname, basename, extname } from 'path';
|
|
3
3
|
import { loadConfig } from './config.js';
|
|
4
|
-
import { getMarkdownFiles } from './content.js';
|
|
4
|
+
import { getMarkdownFiles, getBasePath } from './content.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Check if a file is already in folder-based structure
|
|
@@ -102,7 +102,10 @@ export async function migrateContent(options = {}) {
|
|
|
102
102
|
continue;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
// If path contains date variables, use base path for recursive searching
|
|
106
|
+
const pathTemplate = collectionConfig.path;
|
|
107
|
+
const basePath = getBasePath(pathTemplate);
|
|
108
|
+
const collectionDir = join(config.inputDir, basePath);
|
|
106
109
|
|
|
107
110
|
if (!existsSync(collectionDir)) {
|
|
108
111
|
console.log(`⚠️ Collection directory not found: ${collectionDir}`);
|
package/lib/new.js
CHANGED
|
@@ -2,7 +2,7 @@ import prompts from 'prompts';
|
|
|
2
2
|
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { loadConfig } from './config.js';
|
|
5
|
-
import { slugify } from './content.js';
|
|
5
|
+
import { slugify, expandDatePath } from './content.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Get current date in YYYY-MM-DD format
|
|
@@ -52,7 +52,10 @@ function createPost(config, options) {
|
|
|
52
52
|
const slug = slugify(options.title);
|
|
53
53
|
const date = getDateString();
|
|
54
54
|
const folderName = `${date}-${slug}`;
|
|
55
|
-
const
|
|
55
|
+
const now = new Date();
|
|
56
|
+
const pathTemplate = config.collections.posts?.path || 'posts';
|
|
57
|
+
const expandedPath = expandDatePath(pathTemplate, now);
|
|
58
|
+
const postsDir = join(config.inputDir, expandedPath);
|
|
56
59
|
const postFolder = join(postsDir, folderName);
|
|
57
60
|
const filePath = join(postFolder, 'index.md');
|
|
58
61
|
|
|
@@ -129,7 +132,10 @@ function createNote(config, options) {
|
|
|
129
132
|
const date = getDateString();
|
|
130
133
|
const timestamp = Date.now();
|
|
131
134
|
const folderName = `${date}-${slug}-${timestamp}`;
|
|
132
|
-
const
|
|
135
|
+
const now = new Date();
|
|
136
|
+
const pathTemplate = config.collections.notes?.path || 'notes';
|
|
137
|
+
const expandedPath = expandDatePath(pathTemplate, now);
|
|
138
|
+
const notesDir = join(config.inputDir, expandedPath);
|
|
133
139
|
const noteFolder = join(notesDir, folderName);
|
|
134
140
|
const filePath = join(noteFolder, 'index.md');
|
|
135
141
|
|