@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.
Files changed (47) hide show
  1. package/init.js +8 -5
  2. package/package.json +9 -7
  3. package/swifty.js +78 -31
  4. package/config.yaml +0 -11
  5. package/css/custom.css +0 -30
  6. package/css/simple.css +0 -1267
  7. package/dist/about.html +0 -26
  8. package/dist/css/custom.css +0 -30
  9. package/dist/css/simple.css +0 -1267
  10. package/dist/docs/configuration.html +0 -17
  11. package/dist/docs/folder-structure.html +0 -21
  12. package/dist/docs/get-started.html +0 -17
  13. package/dist/docs/layouts.html +0 -28
  14. package/dist/docs/pages.html +0 -17
  15. package/dist/docs/partials.html +0 -17
  16. package/dist/docs/template.html +0 -20
  17. package/dist/docs.html +0 -60
  18. package/dist/images/horizon.jpg +0 -0
  19. package/dist/images/lights.jpg +0 -0
  20. package/dist/images/raspberries.jpg +0 -0
  21. package/dist/index.html +0 -98
  22. package/dist/tags/about.html +0 -16
  23. package/dist/tags/config.html +0 -19
  24. package/dist/tags/docs.html +0 -21
  25. package/dist/tags/swifty.html +0 -23
  26. package/dist/tags/todo.html +0 -15
  27. package/dist/tags.html +0 -16
  28. package/dist/test.html +0 -91
  29. package/images/horizon.jpg +0 -0
  30. package/images/lights.jpg +0 -0
  31. package/images/raspberries.jpg +0 -0
  32. package/layouts/default.html +0 -5
  33. package/layouts/docs.html +0 -10
  34. package/pages/about.md +0 -36
  35. package/pages/docs/configuration.md +0 -20
  36. package/pages/docs/folder-structure.md +0 -27
  37. package/pages/docs/get-started.md +0 -9
  38. package/pages/docs/layouts.md +0 -50
  39. package/pages/docs/pages.md +0 -24
  40. package/pages/docs/partials.md +0 -24
  41. package/pages/docs/template.md +0 -15
  42. package/pages/index.md +0 -42
  43. package/pages/test.md +0 -34
  44. package/partials/docs.md +0 -9
  45. package/partials/number.md +0 -9
  46. package/partials/pages.md +0 -9
  47. package/template.html +0 -22
package/init.js CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
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.0.0",
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
- "serve": "^14.2.4"
25
- },
26
- "devDependencies": {
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 : "default";
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
- Object.assign(page, { data: { ...page.data, ...data }, content });
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
- if(!page.content) page.content = await generateLinkList(page.filename,page.pages);
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: "default",
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" : "default",
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, { gfm: true, breaks: true }); // Markdown processed once
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
- templateContent = templateContent.replace('</head>', `${turboMetaTag}\n${imports}\n</head>`);
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
- ? `<div class="tags">${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``}</div>`
418
- : "";
419
- const crumb = page.root ? "" : ` &raquo; <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 = await pages.pages ? generateLinkList(page.filename,page.pages) : "";
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",pages.filter(p => p.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
@@ -1,11 +0,0 @@
1
- sitename: Swifty
2
- author: Taylor Swift
3
- breadcrumb_class: swifty_breadcrumb
4
- link_class: swifty_link
5
- tag_class: tag
6
-
7
- dateFormat:
8
- weekday: short
9
- month: short
10
- day: numeric
11
- year: numeric
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
- }