@daz4126/swifty 1.0.0 → 1.1.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/init.js +8 -5
- package/package.json +9 -7
- package/swifty.js +78 -31
- package/config.yaml +0 -11
- package/css/custom.css +0 -30
- package/css/simple.css +0 -1267
- package/dist/about.html +0 -26
- package/dist/css/custom.css +0 -30
- package/dist/css/simple.css +0 -1267
- package/dist/docs/configuration.html +0 -17
- package/dist/docs/folder-structure.html +0 -21
- package/dist/docs/get-started.html +0 -17
- package/dist/docs/layouts.html +0 -28
- package/dist/docs/pages.html +0 -17
- package/dist/docs/partials.html +0 -17
- package/dist/docs/template.html +0 -20
- package/dist/docs.html +0 -60
- package/dist/images/horizon.jpg +0 -0
- package/dist/images/lights.jpg +0 -0
- package/dist/images/raspberries.jpg +0 -0
- package/dist/index.html +0 -98
- package/dist/tags/about.html +0 -16
- package/dist/tags/config.html +0 -19
- package/dist/tags/docs.html +0 -21
- package/dist/tags/swifty.html +0 -23
- package/dist/tags/todo.html +0 -15
- package/dist/tags.html +0 -16
- package/dist/test.html +0 -91
- package/images/horizon.jpg +0 -0
- package/images/lights.jpg +0 -0
- package/images/raspberries.jpg +0 -0
- package/layouts/default.html +0 -5
- package/layouts/docs.html +0 -10
- package/pages/about.md +0 -36
- package/pages/docs/configuration.md +0 -20
- package/pages/docs/folder-structure.md +0 -27
- package/pages/docs/get-started.md +0 -9
- package/pages/docs/layouts.md +0 -50
- package/pages/docs/pages.md +0 -24
- package/pages/docs/partials.md +0 -24
- package/pages/docs/template.md +0 -15
- package/pages/index.md +0 -42
- package/pages/test.md +0 -34
- package/partials/docs.md +0 -9
- package/partials/number.md +0 -9
- package/partials/pages.md +0 -9
- package/template.html +0 -22
package/init.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { dirname } from "path";
|
|
7
|
+
|
|
8
|
+
// Needed to emulate __dirname in ES modules
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
5
11
|
|
|
6
12
|
// Define the structure
|
|
7
13
|
const structure = {
|
|
@@ -28,14 +34,12 @@ const structure = {
|
|
|
28
34
|
</main>
|
|
29
35
|
<footer>
|
|
30
36
|
<p>This site was built with Swifty, the super speedy static site generator.</p>
|
|
31
|
-
<p><a href="/tags.html" data-turbo-frame="content" data-turbo-action="advance">All Tags</a></p>
|
|
32
37
|
</footer>
|
|
33
38
|
</body>
|
|
34
39
|
</html>
|
|
35
40
|
`
|
|
36
41
|
};
|
|
37
42
|
|
|
38
|
-
// Function to create files and folders
|
|
39
43
|
function createStructure(basePath, structure) {
|
|
40
44
|
Object.entries(structure).forEach(([filePath, content]) => {
|
|
41
45
|
const fullPath = path.join(basePath, filePath);
|
|
@@ -48,7 +52,6 @@ function createStructure(basePath, structure) {
|
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
// Execute script
|
|
52
55
|
const projectRoot = process.cwd();
|
|
53
56
|
createStructure(projectRoot, structure);
|
|
54
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daz4126/swifty",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,16 +16,18 @@
|
|
|
16
16
|
"author": "DAZ",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"description": "Super Speedy Static Site Generator",
|
|
19
|
-
"dependencies": {
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"jest": "^29.7.0",
|
|
22
|
+
"mocha": "^11.1.0",
|
|
20
23
|
"fs-extra": "^11.2.0",
|
|
21
24
|
"gray-matter": "^4.0.3",
|
|
25
|
+
"highlight.js": "^11.11.1",
|
|
22
26
|
"js-yaml": "^4.1.0",
|
|
23
27
|
"marked": "^14.1.3",
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"jest": "^29.7.0",
|
|
28
|
-
"mocha": "^11.1.0"
|
|
28
|
+
"marked-highlight": "^2.2.1",
|
|
29
|
+
"serve": "^14.2.4",
|
|
30
|
+
"sharp": "^0.33.5"
|
|
29
31
|
},
|
|
30
32
|
"directories": {
|
|
31
33
|
"test": "test"
|
package/swifty.js
CHANGED
|
@@ -2,9 +2,25 @@ import fs from "fs/promises";
|
|
|
2
2
|
import fsExtra from "fs-extra";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import matter from "gray-matter";
|
|
5
|
-
import { marked } from "marked";
|
|
6
5
|
import yaml from "js-yaml";
|
|
6
|
+
import sharp from "sharp";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { marked } from "marked";
|
|
9
|
+
import { markedHighlight } from "marked-highlight";
|
|
10
|
+
import hljs from 'highlight.js';
|
|
11
|
+
|
|
12
|
+
marked.use(
|
|
13
|
+
markedHighlight({
|
|
14
|
+
langPrefix: 'hljs language-',
|
|
15
|
+
highlight: (code, lang) => {
|
|
16
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
17
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
18
|
+
} else {
|
|
19
|
+
return hljs.highlightAuto(code).value; // Auto-detect the language
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
);
|
|
8
24
|
|
|
9
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
26
|
const __dirname = path.dirname(__filename);
|
|
@@ -21,23 +37,12 @@ const dirs = {
|
|
|
21
37
|
partials: path.join(baseDir, 'partials'),
|
|
22
38
|
};
|
|
23
39
|
|
|
24
|
-
const lastBuildFile = path.join(dirs.dist, '.last_build.json');
|
|
25
|
-
|
|
26
|
-
const loadLastBuildData = async () => {
|
|
27
|
-
try {
|
|
28
|
-
const data = await fs.readFile(lastBuildFile, 'utf-8');
|
|
29
|
-
return JSON.parse(data);
|
|
30
|
-
} catch (err) {
|
|
31
|
-
return {}; // Return empty object if file doesn't exist
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
40
|
const tagsMap = new Map();
|
|
36
41
|
const addToTagMap = (tag, page) => {
|
|
37
42
|
if (!tagsMap.has(tag)) tagsMap.set(tag, []);
|
|
38
43
|
tagsMap.get(tag).push({ title: page.title, url: page.url });
|
|
39
44
|
};
|
|
40
|
-
|
|
45
|
+
const pageIndex = [];
|
|
41
46
|
const partialCache = new Map();
|
|
42
47
|
|
|
43
48
|
const loadPartial = async (partialName) => {
|
|
@@ -90,6 +95,37 @@ const copyAssets = async () => {
|
|
|
90
95
|
await ensureAndCopy(dirs.images, path.join(dirs.dist, 'images'), validExtensions.images);
|
|
91
96
|
};
|
|
92
97
|
|
|
98
|
+
async function optimizeImages() {
|
|
99
|
+
try {
|
|
100
|
+
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png'];
|
|
101
|
+
const images_folder = path.join(dirs.dist, "images");
|
|
102
|
+
const files = await fs.readdir(images_folder);
|
|
103
|
+
|
|
104
|
+
await Promise.all(files.map(async (file) => {
|
|
105
|
+
const filePath = path.join(images_folder, file);
|
|
106
|
+
const ext = path.extname(file).toLowerCase();
|
|
107
|
+
|
|
108
|
+
if (!IMAGE_EXTENSIONS.includes(ext)) return;
|
|
109
|
+
|
|
110
|
+
const optimizedPath = path.join(images_folder, `${path.basename(file, ext)}.webp`);
|
|
111
|
+
|
|
112
|
+
// Ensure we are not overwriting the same file
|
|
113
|
+
if (filePath !== optimizedPath) {
|
|
114
|
+
await sharp(filePath)
|
|
115
|
+
.resize({ width: defaultConfig.max_image_size || 800 })
|
|
116
|
+
.toFormat('webp', { quality: 80 })
|
|
117
|
+
.toFile(optimizedPath);
|
|
118
|
+
|
|
119
|
+
await fs.unlink(filePath);
|
|
120
|
+
|
|
121
|
+
console.log(`Optimized ${file} -> ${optimizedPath}`);
|
|
122
|
+
}
|
|
123
|
+
}));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Error optimizing images:', error);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
93
129
|
// Utility: Generate HTML imports for assets
|
|
94
130
|
const generateAssetImports = async (dir, tagTemplate, validExts) => {
|
|
95
131
|
if (!(await fsExtra.pathExists(dir))) return '';
|
|
@@ -188,7 +224,7 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
188
224
|
const name = root ? "Home" : capitalize(file.name.replace(/\.md$/, "").replace(/-/g, " "));
|
|
189
225
|
const stats = await fs.stat(filePath);
|
|
190
226
|
const isDirectory = file.isDirectory();
|
|
191
|
-
const assetPath = parent ? parent.filename : "
|
|
227
|
+
const assetPath = parent ? parent.filename : "pages";
|
|
192
228
|
const layoutFileExists = await fsExtra.pathExists(dirs.layouts + "/" + assetPath + ".html");
|
|
193
229
|
const layout = !root && layoutFileExists && assetPath;
|
|
194
230
|
|
|
@@ -210,24 +246,34 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
210
246
|
if (path.extname(file.name) === ".md") {
|
|
211
247
|
const markdownContent = await fs.readFile(filePath, "utf-8");
|
|
212
248
|
const { data, content } = matter(markdownContent);
|
|
213
|
-
|
|
249
|
+
const index = pages.findIndex(p => p.url === page.url);
|
|
250
|
+
if (index !== -1) {
|
|
251
|
+
Object.assign(pages[index], { data: { ...page.data, ...data }, content });
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
else Object.assign(page, { data: { ...page.data, ...data }, content });
|
|
214
255
|
}
|
|
215
256
|
if (isDirectory) {
|
|
216
257
|
page.pages = await generatePages(filePath, baseDir, page);
|
|
217
|
-
console.log(page.pages[0].name)
|
|
218
258
|
page.pages.sort((a, b) => {
|
|
219
259
|
if (a.data.position && b.data.position) {
|
|
220
260
|
return a.data.position - b.data.position; // Sort by position first
|
|
221
261
|
}
|
|
222
262
|
return new Date(a.updated_at) - new Date(b.updated_at); // If position is the same, sort by date
|
|
223
263
|
});
|
|
224
|
-
|
|
264
|
+
const index = pages.findIndex(p => p.url === page.url);
|
|
265
|
+
if (index !== -1) {
|
|
266
|
+
page.content = pages[index].content;
|
|
267
|
+
pages.splice(index, 1);
|
|
268
|
+
}
|
|
269
|
+
else page.content = await generateLinkList(page.filename,page.pages);
|
|
225
270
|
}
|
|
226
271
|
|
|
227
272
|
// add tags
|
|
228
273
|
if (page.data.tags) page.data.tags.forEach(tag => addToTagMap(tag, page));
|
|
229
|
-
|
|
274
|
+
|
|
230
275
|
pages.push(page);
|
|
276
|
+
pageIndex.push({url: page.url, title: page.title || page.title, nav: page.nav})
|
|
231
277
|
}
|
|
232
278
|
|
|
233
279
|
} catch (err) {
|
|
@@ -244,7 +290,7 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
244
290
|
folder: true,
|
|
245
291
|
name: "Tags",
|
|
246
292
|
title: "All Tags",
|
|
247
|
-
layout: "
|
|
293
|
+
layout: "pages",
|
|
248
294
|
updated_at: new Date().toLocaleDateString(undefined,defaultConfig.dateFormat),
|
|
249
295
|
data: {...config},
|
|
250
296
|
}
|
|
@@ -256,7 +302,7 @@ const generatePages = async (sourceDir, baseDir = sourceDir, parent) => {
|
|
|
256
302
|
updated_at: new Date().toLocaleDateString(undefined,defaultConfig.dateFormat),
|
|
257
303
|
path: `/tags/${tag}`,
|
|
258
304
|
url: `/tags/${tag}.html`,
|
|
259
|
-
layout: tagLayout ? "tags" : "
|
|
305
|
+
layout: tagLayout ? "tags" : "pages",
|
|
260
306
|
data: {...config, title: `Pages tagged with ${capitalize(tag)}`},
|
|
261
307
|
};
|
|
262
308
|
page.content = pages
|
|
@@ -286,7 +332,8 @@ const generateLinkList = async (name,pages) => {
|
|
|
286
332
|
|
|
287
333
|
const render = async page => {
|
|
288
334
|
const replacedContent = await replacePlaceholders(page.content, page);
|
|
289
|
-
const htmlContent = marked.parse(replacedContent
|
|
335
|
+
const htmlContent = marked.parse(replacedContent); // Markdown processed once
|
|
336
|
+
|
|
290
337
|
const wrappedContent = await applyLayoutAndWrapContent(page, htmlContent);
|
|
291
338
|
return wrappedContent;
|
|
292
339
|
};
|
|
@@ -303,7 +350,8 @@ const renderIndexTemplate = async (content, config) => {
|
|
|
303
350
|
const js = await getJsImports();
|
|
304
351
|
const imports = css + js;
|
|
305
352
|
|
|
306
|
-
|
|
353
|
+
const highlightCSS = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/monokai-sublime.min.css">`;
|
|
354
|
+
templateContent = templateContent.replace('</head>', `${turboMetaTag}\n${imports}\n${highlightCSS}\n</head>`);
|
|
307
355
|
templateContent = await replacePlaceholders(templateContent,{...defaultConfig,...config,content})
|
|
308
356
|
|
|
309
357
|
// Add the missing script to the template
|
|
@@ -390,7 +438,8 @@ const replacePlaceholders = async (template, values) => {
|
|
|
390
438
|
partialContent = await replacePlaceholders(partialContent, values); // Recursive replacement
|
|
391
439
|
return marked(partialContent); // Convert Markdown to HTML
|
|
392
440
|
});
|
|
393
|
-
|
|
441
|
+
// replace image extensions with optimized extension
|
|
442
|
+
template = template.replace(/\.(png|jpe?g|webp)/gi, ".webp");
|
|
394
443
|
// Replace other placeholders **only outside of code blocks**
|
|
395
444
|
const codeBlockRegex = /```[\s\S]*?```|`[^`]+`|<(pre|code)[^>]*>[\s\S]*?<\/\1>/g;
|
|
396
445
|
const codeBlocks = [];
|
|
@@ -398,12 +447,10 @@ const replacePlaceholders = async (template, values) => {
|
|
|
398
447
|
codeBlocks.push(match);
|
|
399
448
|
return `{{CODE_BLOCK_${codeBlocks.length - 1}}}`; // Temporary placeholder
|
|
400
449
|
});
|
|
401
|
-
|
|
402
450
|
// Replace placeholders outside of code blocks
|
|
403
451
|
template = template.replace(/{{\s*([^}\s]+)\s*}}/g, (match, key) => {
|
|
404
452
|
return(values.data && key in values?.data ? values.data[key] : key in values ? values[key] : match)
|
|
405
453
|
});
|
|
406
|
-
|
|
407
454
|
// Restore code blocks
|
|
408
455
|
template = template.replace(/{{CODE_BLOCK_(\d+)}}/g, (_, index) => codeBlocks[index]);
|
|
409
456
|
|
|
@@ -414,17 +461,16 @@ const addLinks = async (pages,parent) => {
|
|
|
414
461
|
pages.forEach(async page => {
|
|
415
462
|
page.data ||= {};
|
|
416
463
|
page.data.links_to_tags = page?.data?.tags?.length
|
|
417
|
-
?
|
|
418
|
-
: ""
|
|
419
|
-
const crumb = page.root ? "" : ` » <a class="${defaultConfig.breadcrumb_class}" href="${page.url}" data-turbo-frame="content" data-turbo-action="advance">${page.name}</a>`;
|
|
464
|
+
? page.data.tags.map(tag => `<a class="${defaultConfig.tag_class}" href="/tags/${tag}.html" data-turbo-frame="content" data-turbo-action="advance">${tag}</a>`).join`` : "";
|
|
465
|
+
const crumb = page.root ? "" : ` ${defaultConfig.breadcrumb_separator} <a class="${defaultConfig.breadcrumb_class}" href="${page.url}" data-turbo-frame="content" data-turbo-action="advance">${page.name}</a>`;
|
|
420
466
|
page.data.breadcrumbs = parent ? parent.data.breadcrumbs + crumb
|
|
421
467
|
: `<a class="${defaultConfig.breadcrumb_class}" href="/" data-turbo-frame="content" data-turbo-action="advance">Home</a>` + crumb;
|
|
422
|
-
page.data.links_to_children =
|
|
468
|
+
page.data.links_to_children = page.pages ? await generateLinkList(page.filename,page.pages) : "";
|
|
423
469
|
page.data.links_to_siblings = await generateLinkList(page.parent?.filename || "pages",pages.filter(p => p.url !== page.url));
|
|
424
470
|
page.data.links_to_self_and_siblings = await generateLinkList(page.parent?.filename || "pages",pages);
|
|
425
|
-
page.data.nav_links = await generateLinkList("nav",
|
|
471
|
+
page.data.nav_links = await generateLinkList("nav",pageIndex.filter(p => p.nav));
|
|
426
472
|
if(page.pages) {
|
|
427
|
-
addLinks(page.pages,page)
|
|
473
|
+
await addLinks(page.pages,page)
|
|
428
474
|
}
|
|
429
475
|
});
|
|
430
476
|
}
|
|
@@ -434,6 +480,7 @@ const generateSite = async () => {
|
|
|
434
480
|
console.log('Starting build process...');
|
|
435
481
|
// Copy images, CSS, and JS files
|
|
436
482
|
await copyAssets();
|
|
483
|
+
await optimizeImages();
|
|
437
484
|
// Convert markdown in pages directory
|
|
438
485
|
const pages = await generatePages(dirs.pages);
|
|
439
486
|
await addLinks(pages);
|
package/config.yaml
DELETED
package/css/custom.css
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
h1.logo {
|
|
2
|
-
color: var(--primary);
|
|
3
|
-
text-transform: none;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.post-info{
|
|
7
|
-
margin-top: -1rem;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.post{
|
|
11
|
-
margin: 1rem;
|
|
12
|
-
border-bottom: 1px dotted var(--grey);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
header{
|
|
16
|
-
padding: 1rem 2rem;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
header a.logo{
|
|
20
|
-
text-decoration: none;
|
|
21
|
-
font-family: var(--fancy-font);
|
|
22
|
-
border-radius:50%;
|
|
23
|
-
background: var(--primary);
|
|
24
|
-
color: var(--bg);
|
|
25
|
-
text-align: center;
|
|
26
|
-
vertical-align: middle;
|
|
27
|
-
height: 2em;
|
|
28
|
-
width: 2em;
|
|
29
|
-
line-height: 2;
|
|
30
|
-
}
|