@terrymooreii/sia 2.1.13 → 2.2.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/bin/cli.js +7 -0
- package/docs/README.md +11 -0
- package/docs/front-matter.md +16 -8
- package/lib/assets.js +36 -1
- package/lib/build.js +13 -2
- package/lib/content.js +36 -1
- package/lib/migrate.js +180 -0
- package/lib/new.js +21 -9
- package/package.json +1 -1
- package/readme.md +14 -3
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { buildCommand } from '../lib/build.js';
|
|
|
15
15
|
import { newCommand } from '../lib/new.js';
|
|
16
16
|
import { initCommand } from '../lib/init.js';
|
|
17
17
|
import { themeCommand } from '../lib/theme.js';
|
|
18
|
+
import { migrateCommand } from '../lib/migrate.js';
|
|
18
19
|
|
|
19
20
|
program
|
|
20
21
|
.name('sia')
|
|
@@ -54,5 +55,11 @@ program
|
|
|
54
55
|
.option('-q, --quick', 'Skip prompts and use defaults')
|
|
55
56
|
.action(themeCommand);
|
|
56
57
|
|
|
58
|
+
program
|
|
59
|
+
.command('migrate')
|
|
60
|
+
.description('Migrate standalone .md files to folder-based structure (folder/index.md)')
|
|
61
|
+
.option('--dry-run', 'Preview changes without applying them', false)
|
|
62
|
+
.action(migrateCommand);
|
|
63
|
+
|
|
57
64
|
program.parse();
|
|
58
65
|
|
package/docs/README.md
CHANGED
|
@@ -68,8 +68,17 @@ my-site/
|
|
|
68
68
|
├── _config.yml # Site configuration
|
|
69
69
|
├── src/
|
|
70
70
|
│ ├── posts/ # Blog posts (markdown)
|
|
71
|
+
│ │ └── 2024-12-17-my-post/
|
|
72
|
+
│ │ ├── index.md
|
|
73
|
+
│ │ └── (assets can go here)
|
|
71
74
|
│ ├── pages/ # Static pages
|
|
75
|
+
│ │ └── about/
|
|
76
|
+
│ │ ├── index.md
|
|
77
|
+
│ │ └── (assets can go here)
|
|
72
78
|
│ ├── notes/ # Short notes/tweets
|
|
79
|
+
│ │ └── 2024-12-17-note-1234567890/
|
|
80
|
+
│ │ ├── index.md
|
|
81
|
+
│ │ └── (assets can go here)
|
|
73
82
|
│ └── images/ # Images
|
|
74
83
|
├── assets/ # Static assets (optional)
|
|
75
84
|
├── static/ # Static assets (optional)
|
|
@@ -81,6 +90,8 @@ my-site/
|
|
|
81
90
|
└── dist/ # Generated output
|
|
82
91
|
```
|
|
83
92
|
|
|
93
|
+
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.
|
|
94
|
+
|
|
84
95
|
## Configuration
|
|
85
96
|
|
|
86
97
|
Edit `_config.yml` to customize your site:
|
package/docs/front-matter.md
CHANGED
|
@@ -366,19 +366,27 @@ permalink: /featured/special-post/
|
|
|
366
366
|
|
|
367
367
|
## Date from Filename
|
|
368
368
|
|
|
369
|
-
Sia can extract dates from filenames using this pattern:
|
|
369
|
+
Sia can extract dates from filenames (or folder names when using `index.md`) using this pattern:
|
|
370
370
|
|
|
371
371
|
```
|
|
372
372
|
YYYY-MM-DD-slug.md
|
|
373
373
|
```
|
|
374
374
|
|
|
375
|
+
or for folder-based content:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
YYYY-MM-DD-slug/index.md
|
|
379
|
+
```
|
|
380
|
+
|
|
375
381
|
### Examples
|
|
376
382
|
|
|
377
|
-
| Filename | Extracted Date | Extracted Slug |
|
|
378
|
-
|
|
379
|
-
| `2024-12-17-my-post.md` | December 17, 2024 | `my-post` |
|
|
380
|
-
| `2024-01-05-new-year.md` | January 5, 2024 | `new-year` |
|
|
381
|
-
| `about.md` | Current date | `about` |
|
|
383
|
+
| Filename/Folder | Extracted Date | Extracted Slug |
|
|
384
|
+
|-----------------|----------------|----------------|
|
|
385
|
+
| `2024-12-17-my-post/index.md` | December 17, 2024 | `my-post` |
|
|
386
|
+
| `2024-01-05-new-year/index.md` | January 5, 2024 | `new-year` |
|
|
387
|
+
| `about/index.md` | Current date | `about` |
|
|
388
|
+
| `2024-12-17-my-post.md` | December 17, 2024 | `my-post` (backward compatible) |
|
|
389
|
+
| `about.md` | Current date | `about` (backward compatible) |
|
|
382
390
|
|
|
383
391
|
### Priority
|
|
384
392
|
|
|
@@ -391,8 +399,8 @@ Date resolution follows this priority:
|
|
|
391
399
|
Slug resolution:
|
|
392
400
|
|
|
393
401
|
1. `slug` in front matter (highest priority)
|
|
394
|
-
2. Slug extracted from filename (after date prefix)
|
|
395
|
-
3. Slugified filename
|
|
402
|
+
2. Slug extracted from filename or folder name (after date prefix if present)
|
|
403
|
+
3. Slugified filename or folder name
|
|
396
404
|
|
|
397
405
|
---
|
|
398
406
|
|
package/lib/assets.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
unlinkSync,
|
|
10
10
|
rmdirSync
|
|
11
11
|
} from 'fs';
|
|
12
|
-
import { join, dirname, relative, extname } from 'path';
|
|
12
|
+
import { join, dirname, relative, extname, basename } from 'path';
|
|
13
13
|
import { resolveTheme, getBuiltInThemesDir } from './theme-resolver.js';
|
|
14
14
|
|
|
15
15
|
// Supported asset extensions
|
|
@@ -115,6 +115,40 @@ export function copyImages(config) {
|
|
|
115
115
|
return copied;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Copy assets from a content folder to the output directory
|
|
120
|
+
* @param {string} contentFolderPath - Path to the content folder (e.g., src/posts/2024-12-17-my-post/)
|
|
121
|
+
* @param {string} outputDir - Output directory for this content item (e.g., dist/blog/my-post/)
|
|
122
|
+
* @returns {number} Number of assets copied
|
|
123
|
+
*/
|
|
124
|
+
export function copyContentAssets(contentFolderPath, outputDir) {
|
|
125
|
+
if (!existsSync(contentFolderPath)) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const files = getAllFiles(contentFolderPath);
|
|
130
|
+
let copied = 0;
|
|
131
|
+
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
// Skip the index.md file itself
|
|
134
|
+
const fileName = basename(file);
|
|
135
|
+
if (fileName === 'index.md' || fileName === 'index.markdown') {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Only copy asset files
|
|
140
|
+
if (isAsset(file)) {
|
|
141
|
+
const relativePath = relative(contentFolderPath, file);
|
|
142
|
+
const destPath = join(outputDir, relativePath);
|
|
143
|
+
|
|
144
|
+
copyFile(file, destPath);
|
|
145
|
+
copied++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return copied;
|
|
150
|
+
}
|
|
151
|
+
|
|
118
152
|
/**
|
|
119
153
|
* Check if directory has CSS files
|
|
120
154
|
*/
|
|
@@ -321,6 +355,7 @@ export default {
|
|
|
321
355
|
copyFile,
|
|
322
356
|
copyAssets,
|
|
323
357
|
copyImages,
|
|
358
|
+
copyContentAssets,
|
|
324
359
|
copyDefaultStyles,
|
|
325
360
|
copyStaticAssets,
|
|
326
361
|
copyCustomAssets,
|
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, copyCustomAssets, writeFile, ensureDir } from './assets.js';
|
|
7
|
+
import { copyImages, copyDefaultStyles, copyStaticAssets, copyCustomAssets, copyContentAssets, writeFile, ensureDir } from './assets.js';
|
|
8
8
|
import { resolveTheme } from './theme-resolver.js';
|
|
9
9
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -35,6 +35,11 @@ function renderContentItem(env, item, siteData) {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
writeFile(item.outputPath, html);
|
|
38
|
+
|
|
39
|
+
// Copy assets from content folder to output directory
|
|
40
|
+
const contentFolder = dirname(item.filePath);
|
|
41
|
+
const outputDir = dirname(item.outputPath);
|
|
42
|
+
return copyContentAssets(contentFolder, outputDir);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
@@ -231,16 +236,22 @@ export async function build(options = {}) {
|
|
|
231
236
|
|
|
232
237
|
// Render all content items
|
|
233
238
|
let itemCount = 0;
|
|
239
|
+
let totalContentAssets = 0;
|
|
234
240
|
|
|
235
241
|
for (const [collectionName, items] of Object.entries(siteData.collections)) {
|
|
236
242
|
for (const item of items) {
|
|
237
|
-
renderContentItem(env, item, siteData);
|
|
243
|
+
const assetsCopied = renderContentItem(env, item, siteData);
|
|
244
|
+
totalContentAssets += assetsCopied;
|
|
238
245
|
itemCount++;
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
248
|
|
|
242
249
|
console.log(`📄 Generated ${itemCount} content pages`);
|
|
243
250
|
|
|
251
|
+
if (totalContentAssets > 0) {
|
|
252
|
+
console.log(`📦 Copied ${totalContentAssets} asset(s) from content folders`);
|
|
253
|
+
}
|
|
254
|
+
|
|
244
255
|
// Render listing pages
|
|
245
256
|
renderHomepage(env, siteData);
|
|
246
257
|
renderBlogListing(env, siteData);
|
package/lib/content.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
2
|
-
import { join, basename, extname,
|
|
2
|
+
import { join, basename, extname, dirname } from 'path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import { Marked } from 'marked';
|
|
5
5
|
import { markedHighlight } from 'marked-highlight';
|
|
@@ -326,11 +326,28 @@ export function slugify(str) {
|
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Extract slug from filename (removes date prefix if present)
|
|
329
|
+
* If the file is index.md, extracts from parent directory name instead
|
|
329
330
|
*/
|
|
330
331
|
export function getSlugFromFilename(filename) {
|
|
331
332
|
// Remove extension
|
|
332
333
|
const name = basename(filename, extname(filename));
|
|
333
334
|
|
|
335
|
+
// If file is index.md, extract from parent directory name
|
|
336
|
+
if (name === 'index') {
|
|
337
|
+
const parentDir = dirname(filename);
|
|
338
|
+
const parentDirName = basename(parentDir);
|
|
339
|
+
|
|
340
|
+
// Check for date prefix pattern: YYYY-MM-DD-slug
|
|
341
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}-(.+)$/;
|
|
342
|
+
const match = parentDirName.match(datePattern);
|
|
343
|
+
|
|
344
|
+
if (match) {
|
|
345
|
+
return match[1];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return slugify(parentDirName);
|
|
349
|
+
}
|
|
350
|
+
|
|
334
351
|
// Check for date prefix pattern: YYYY-MM-DD-slug
|
|
335
352
|
const datePattern = /^\d{4}-\d{2}-\d{2}-(.+)$/;
|
|
336
353
|
const match = name.match(datePattern);
|
|
@@ -344,9 +361,27 @@ export function getSlugFromFilename(filename) {
|
|
|
344
361
|
|
|
345
362
|
/**
|
|
346
363
|
* Extract date from filename if present
|
|
364
|
+
* If the file is index.md, extracts from parent directory name instead
|
|
347
365
|
*/
|
|
348
366
|
export function getDateFromFilename(filename) {
|
|
349
367
|
const name = basename(filename, extname(filename));
|
|
368
|
+
|
|
369
|
+
// If file is index.md, extract from parent directory name
|
|
370
|
+
if (name === 'index') {
|
|
371
|
+
const parentDir = dirname(filename);
|
|
372
|
+
const parentDirName = basename(parentDir);
|
|
373
|
+
const datePattern = /^(\d{4}-\d{2}-\d{2})/;
|
|
374
|
+
const match = parentDirName.match(datePattern);
|
|
375
|
+
|
|
376
|
+
if (match) {
|
|
377
|
+
// Parse as local date, not UTC
|
|
378
|
+
const [year, month, day] = match[1].split('-').map(Number);
|
|
379
|
+
return new Date(year, month - 1, day);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
350
385
|
const datePattern = /^(\d{4}-\d{2}-\d{2})/;
|
|
351
386
|
const match = name.match(datePattern);
|
|
352
387
|
|
package/lib/migrate.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { readdirSync, statSync, existsSync, mkdirSync, renameSync } from 'fs';
|
|
2
|
+
import { join, dirname, basename, extname } from 'path';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { getMarkdownFiles } from './content.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if a file is already in folder-based structure
|
|
8
|
+
*/
|
|
9
|
+
function isAlreadyMigrated(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
const name = basename(filePath, extname(filePath));
|
|
12
|
+
const parentDir = dirname(filePath);
|
|
13
|
+
// Check if file is index.md and parent is a directory (not the collection root)
|
|
14
|
+
return name === 'index' && statSync(parentDir).isDirectory();
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if a file is a standalone .md file (needs migration)
|
|
22
|
+
* Returns true if file is directly in collection directory (not in subdirectory)
|
|
23
|
+
*/
|
|
24
|
+
function needsMigration(filePath, collectionDir) {
|
|
25
|
+
// Normalize paths for comparison
|
|
26
|
+
const normalizedFilePath = filePath.replace(/\\/g, '/');
|
|
27
|
+
const normalizedCollectionDir = collectionDir.replace(/\\/g, '/');
|
|
28
|
+
|
|
29
|
+
// Get relative path from collection directory
|
|
30
|
+
const relativePath = normalizedFilePath.replace(normalizedCollectionDir + '/', '');
|
|
31
|
+
|
|
32
|
+
// If relative path contains no slashes, it's directly in collection dir
|
|
33
|
+
// Also check it's not already index.md in a folder
|
|
34
|
+
const isStandalone = !relativePath.includes('/');
|
|
35
|
+
const isNotIndex = basename(filePath, extname(filePath)) !== 'index';
|
|
36
|
+
|
|
37
|
+
return isStandalone && isNotIndex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Migrate a single file to folder structure
|
|
42
|
+
*/
|
|
43
|
+
function migrateFile(filePath, collectionDir, dryRun = false) {
|
|
44
|
+
const fileName = basename(filePath);
|
|
45
|
+
const nameWithoutExt = basename(filePath, extname(filePath));
|
|
46
|
+
const newFolder = join(collectionDir, nameWithoutExt);
|
|
47
|
+
const newFilePath = join(newFolder, 'index.md');
|
|
48
|
+
|
|
49
|
+
// Skip if already migrated
|
|
50
|
+
if (isAlreadyMigrated(filePath)) {
|
|
51
|
+
return { status: 'skipped', reason: 'already_migrated', file: filePath };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Skip if target folder already exists
|
|
55
|
+
if (existsSync(newFolder)) {
|
|
56
|
+
return { status: 'skipped', reason: 'folder_exists', file: filePath, folder: newFolder };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (dryRun) {
|
|
60
|
+
return { status: 'would_migrate', from: filePath, to: newFilePath };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Create folder
|
|
65
|
+
mkdirSync(newFolder, { recursive: true });
|
|
66
|
+
|
|
67
|
+
// Move file to new location
|
|
68
|
+
renameSync(filePath, newFilePath);
|
|
69
|
+
|
|
70
|
+
return { status: 'migrated', from: filePath, to: newFilePath };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return { status: 'error', file: filePath, error: error.message };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Migrate all content files to folder structure
|
|
78
|
+
*/
|
|
79
|
+
export async function migrateContent(options = {}) {
|
|
80
|
+
const {
|
|
81
|
+
dryRun = false,
|
|
82
|
+
rootDir = process.cwd()
|
|
83
|
+
} = options;
|
|
84
|
+
|
|
85
|
+
const config = loadConfig(rootDir);
|
|
86
|
+
const collections = ['posts', 'pages', 'notes'];
|
|
87
|
+
const results = {
|
|
88
|
+
migrated: [],
|
|
89
|
+
skipped: [],
|
|
90
|
+
errors: []
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
console.log('\n🔄 Content Migration Tool\n');
|
|
94
|
+
|
|
95
|
+
if (dryRun) {
|
|
96
|
+
console.log('⚠️ DRY RUN MODE - No files will be modified\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const collectionName of collections) {
|
|
100
|
+
const collectionConfig = config.collections[collectionName];
|
|
101
|
+
if (!collectionConfig) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const collectionDir = join(config.inputDir, collectionConfig.path);
|
|
106
|
+
|
|
107
|
+
if (!existsSync(collectionDir)) {
|
|
108
|
+
console.log(`⚠️ Collection directory not found: ${collectionDir}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`📁 Processing ${collectionName} collection...`);
|
|
113
|
+
|
|
114
|
+
// Get all markdown files in the collection directory
|
|
115
|
+
const allFiles = getMarkdownFiles(collectionDir);
|
|
116
|
+
|
|
117
|
+
// Filter to only standalone files (not already in folders)
|
|
118
|
+
const filesToMigrate = allFiles.filter(filePath => {
|
|
119
|
+
return needsMigration(filePath, collectionDir);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (filesToMigrate.length === 0) {
|
|
123
|
+
console.log(` No files to migrate in ${collectionName} collection\n`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(` Found ${filesToMigrate.length} file(s) to migrate`);
|
|
128
|
+
|
|
129
|
+
for (const filePath of filesToMigrate) {
|
|
130
|
+
const result = migrateFile(filePath, collectionDir, dryRun);
|
|
131
|
+
|
|
132
|
+
if (result.status === 'migrated' || result.status === 'would_migrate') {
|
|
133
|
+
results.migrated.push(result);
|
|
134
|
+
const folderName = basename(dirname(result.to));
|
|
135
|
+
console.log(` ✓ ${basename(filePath)} → ${folderName}/index.md`);
|
|
136
|
+
} else if (result.status === 'skipped') {
|
|
137
|
+
results.skipped.push(result);
|
|
138
|
+
const reason = result.reason === 'already_migrated'
|
|
139
|
+
? 'already in folder structure'
|
|
140
|
+
: 'target folder exists';
|
|
141
|
+
console.log(` ⊘ Skipped: ${basename(filePath)} (${reason})`);
|
|
142
|
+
} else {
|
|
143
|
+
results.errors.push(result);
|
|
144
|
+
console.error(` ✗ Error: ${basename(filePath)} - ${result.error}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Print summary
|
|
152
|
+
console.log('='.repeat(50));
|
|
153
|
+
console.log('Migration Summary:');
|
|
154
|
+
console.log(` ✓ Migrated: ${results.migrated.length}`);
|
|
155
|
+
console.log(` ⊘ Skipped: ${results.skipped.length}`);
|
|
156
|
+
console.log(` ✗ Errors: ${results.errors.length}`);
|
|
157
|
+
console.log('='.repeat(50));
|
|
158
|
+
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
console.log('\n⚠️ This was a dry run. No files were actually moved.');
|
|
161
|
+
console.log(' Run without --dry-run to apply changes.\n');
|
|
162
|
+
} else if (results.migrated.length > 0) {
|
|
163
|
+
console.log('\n✨ Migration complete! Your content is now in folder-based structure.\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Migration command handler for CLI
|
|
171
|
+
*/
|
|
172
|
+
export async function migrateCommand(options = {}) {
|
|
173
|
+
await migrateContent({
|
|
174
|
+
dryRun: options.dryRun === true,
|
|
175
|
+
rootDir: process.cwd()
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default { migrateContent, migrateCommand };
|
|
180
|
+
|
package/lib/new.js
CHANGED
|
@@ -51,14 +51,18 @@ function formatTags(tags) {
|
|
|
51
51
|
function createPost(config, options) {
|
|
52
52
|
const slug = slugify(options.title);
|
|
53
53
|
const date = getDateString();
|
|
54
|
-
const
|
|
54
|
+
const folderName = `${date}-${slug}`;
|
|
55
55
|
const postsDir = join(config.inputDir, config.collections.posts?.path || 'posts');
|
|
56
|
-
const
|
|
56
|
+
const postFolder = join(postsDir, folderName);
|
|
57
|
+
const filePath = join(postFolder, 'index.md');
|
|
57
58
|
|
|
58
|
-
// Ensure
|
|
59
|
+
// Ensure directories exist
|
|
59
60
|
if (!existsSync(postsDir)) {
|
|
60
61
|
mkdirSync(postsDir, { recursive: true });
|
|
61
62
|
}
|
|
63
|
+
if (!existsSync(postFolder)) {
|
|
64
|
+
mkdirSync(postFolder, { recursive: true });
|
|
65
|
+
}
|
|
62
66
|
|
|
63
67
|
let frontMatter = `---
|
|
64
68
|
title: "${options.title}"
|
|
@@ -87,14 +91,18 @@ ${options.content || 'Write your post content here...'}
|
|
|
87
91
|
*/
|
|
88
92
|
function createPage(config, options) {
|
|
89
93
|
const slug = slugify(options.title);
|
|
90
|
-
const
|
|
94
|
+
const folderName = slug;
|
|
91
95
|
const pagesDir = join(config.inputDir, config.collections.pages?.path || 'pages');
|
|
92
|
-
const
|
|
96
|
+
const pageFolder = join(pagesDir, folderName);
|
|
97
|
+
const filePath = join(pageFolder, 'index.md');
|
|
93
98
|
|
|
94
|
-
// Ensure
|
|
99
|
+
// Ensure directories exist
|
|
95
100
|
if (!existsSync(pagesDir)) {
|
|
96
101
|
mkdirSync(pagesDir, { recursive: true });
|
|
97
102
|
}
|
|
103
|
+
if (!existsSync(pageFolder)) {
|
|
104
|
+
mkdirSync(pageFolder, { recursive: true });
|
|
105
|
+
}
|
|
98
106
|
|
|
99
107
|
let frontMatter = `---
|
|
100
108
|
title: "${options.title}"
|
|
@@ -120,14 +128,18 @@ function createNote(config, options) {
|
|
|
120
128
|
const slug = options.title ? slugify(options.title) : 'note';
|
|
121
129
|
const date = getDateString();
|
|
122
130
|
const timestamp = Date.now();
|
|
123
|
-
const
|
|
131
|
+
const folderName = `${date}-${slug}-${timestamp}`;
|
|
124
132
|
const notesDir = join(config.inputDir, config.collections.notes?.path || 'notes');
|
|
125
|
-
const
|
|
133
|
+
const noteFolder = join(notesDir, folderName);
|
|
134
|
+
const filePath = join(noteFolder, 'index.md');
|
|
126
135
|
|
|
127
|
-
// Ensure
|
|
136
|
+
// Ensure directories exist
|
|
128
137
|
if (!existsSync(notesDir)) {
|
|
129
138
|
mkdirSync(notesDir, { recursive: true });
|
|
130
139
|
}
|
|
140
|
+
if (!existsSync(noteFolder)) {
|
|
141
|
+
mkdirSync(noteFolder, { recursive: true });
|
|
142
|
+
}
|
|
131
143
|
|
|
132
144
|
const content = `---
|
|
133
145
|
date: ${getISODate()}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -66,7 +66,7 @@ The output will be in the `dist/` folder, ready to deploy to any static hosting.
|
|
|
66
66
|
npx sia new post "My Post Title"
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Creates a new
|
|
69
|
+
Creates a new folder `src/posts/YYYY-MM-DD-my-post-title/` with an `index.md` file inside. You can organize assets (images, PDFs, etc.) in the same folder.
|
|
70
70
|
|
|
71
71
|
### New Page
|
|
72
72
|
|
|
@@ -74,7 +74,7 @@ Creates a new markdown file in `src/posts/` with front matter template.
|
|
|
74
74
|
npx sia new page "About Me"
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
Creates a new
|
|
77
|
+
Creates a new folder `src/pages/about-me/` with an `index.md` file inside. You can organize assets in the same folder.
|
|
78
78
|
|
|
79
79
|
### New Note
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ Creates a new page in `src/pages/`.
|
|
|
82
82
|
npx sia new note "Quick thought"
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
Creates a
|
|
85
|
+
Creates a new folder `src/notes/YYYY-MM-DD-quick-thought-TIMESTAMP/` with an `index.md` file inside. You can organize assets in the same folder.
|
|
86
86
|
|
|
87
87
|
## Project Structure
|
|
88
88
|
|
|
@@ -91,8 +91,17 @@ my-site/
|
|
|
91
91
|
├── _config.yml # Site configuration
|
|
92
92
|
├── src/
|
|
93
93
|
│ ├── posts/ # Blog posts (markdown)
|
|
94
|
+
│ │ └── 2024-12-17-my-post/
|
|
95
|
+
│ │ ├── index.md
|
|
96
|
+
│ │ └── (assets can go here)
|
|
94
97
|
│ ├── pages/ # Static pages
|
|
98
|
+
│ │ └── about/
|
|
99
|
+
│ │ ├── index.md
|
|
100
|
+
│ │ └── (assets can go here)
|
|
95
101
|
│ ├── notes/ # Short notes/tweets
|
|
102
|
+
│ │ └── 2024-12-17-note-1234567890/
|
|
103
|
+
│ │ ├── index.md
|
|
104
|
+
│ │ └── (assets can go here)
|
|
96
105
|
│ └── images/ # Images
|
|
97
106
|
├── assets/ # Static assets (optional)
|
|
98
107
|
├── static/ # Static assets (optional)
|
|
@@ -104,6 +113,8 @@ my-site/
|
|
|
104
113
|
└── dist/ # Generated output
|
|
105
114
|
```
|
|
106
115
|
|
|
116
|
+
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.
|
|
117
|
+
|
|
107
118
|
## Configuration
|
|
108
119
|
|
|
109
120
|
Edit `_config.yml` to customize your site:
|