@lexho111/plainblog 0.4.1 → 0.4.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.
Files changed (39) hide show
  1. package/Blog.js +105 -168
  2. package/Formatter.js +17 -12
  3. package/blog.db +0 -0
  4. package/build-styles.js +18 -0
  5. package/package.json +3 -3
  6. package/public/print.min.css +2 -0
  7. package/{scripts.min.js → public/scripts.min.js} +1 -1
  8. package/public/styles.min.css +2 -0
  9. package/src/print.scss +76 -0
  10. package/src/styles.css +12 -0
  11. package/test/blog.test.js +24 -7
  12. package/test/styles.test.js +42 -44
  13. package/{test_1767119505118.db → test_1767619969536.db} +0 -0
  14. package/{test_1767117403635.db → test_1767620053052.db} +0 -0
  15. package/{test_1767117781437.db → test_1767621184498.db} +0 -0
  16. package/{test_1767117944836.db → test_1767623166288.db} +0 -0
  17. package/test_1767623750051.db +0 -0
  18. package/test_server_db.db +0 -0
  19. package/test_styles_1.db +0 -0
  20. package/test_styles_2.db +0 -0
  21. package/test_styles_3.db +0 -0
  22. package/test_styles_4.db +0 -0
  23. package/blog.db-journal +0 -0
  24. package/styles.min.css +0 -1
  25. package/test_1767173684248.db +0 -0
  26. package/test_1767173763155.db +0 -0
  27. package/test_1767173821563.db +0 -0
  28. package/test_1767260493607.db +0 -0
  29. package/test_1767281040439.db +0 -0
  30. package/test_1767281442334.db +0 -0
  31. package/test_1767286038587.db +0 -0
  32. package/test_1767286127364.db +0 -0
  33. package/test_1767286366239.db +0 -0
  34. package/test_1767286503638.db +0 -0
  35. package/test_1767286637739.db +0 -0
  36. package/test_1767292219862.db +0 -0
  37. package/test_1767292355190.db +0 -0
  38. /package/{styles.min.css.map → public/styles.min.css.map} +0 -0
  39. /package/src/{loader.js → fetchData.js} +0 -0
package/Blog.js CHANGED
@@ -7,13 +7,13 @@ import Article from "./Article.js";
7
7
  import DatabaseModel from "./model/DatabaseModel.js";
8
8
  import { fetchData, postData } from "./model/APIModel.js";
9
9
  import { save as saveToFile, load as loadFromFile } from "./model/fileModel.js";
10
- import { formatHTML, formatMarkdown, validate } from "./Formatter.js";
10
+ import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js";
11
11
  import pkg from "./package.json" with { type: "json" };
12
12
  import path from "path";
13
13
  import { fileURLToPath } from "url";
14
14
  import { exec } from "child_process";
15
15
  import { promisify } from "util";
16
- import { compileStyles } from "./build-styles.js";
16
+ import { compileStyles, mergeStyles } from "./build-styles.js";
17
17
 
18
18
  const execPromise = promisify(exec);
19
19
 
@@ -28,6 +28,7 @@ export default class Blog {
28
28
  this.scripts = "";
29
29
  this.compiledStyles = "";
30
30
  this.compiledScripts = "";
31
+ this.reloadStylesOnGET = false;
31
32
 
32
33
  this.database = {
33
34
  type: "sqlite",
@@ -42,63 +43,6 @@ export default class Blog {
42
43
  console.log(`version: ${version}`);
43
44
  }
44
45
 
45
- /*loadScripts() {
46
- const __filename = fileURLToPath(import.meta.url);
47
- const __dirname = path.dirname(__filename);
48
- try {
49
- const content = fs.readFileSync(
50
- path.join(__dirname, "scripts.min.js"),
51
- "utf-8"
52
- );
53
- const match = content.match(/\/\* source-hash: ([a-f0-9]+) \*\//);
54
- if (match) {
55
- this.#scriptsHash = match[1];
56
- this.compiledScripts = content.replace(match[0], "").trim();
57
- } else {
58
- this.compiledScripts = content;
59
- this.#scriptsHash = "";
60
- }
61
- } catch(err) {
62
- this.compiledScripts = "";
63
- this.#scriptsHash = "";
64
- }
65
- }*/
66
-
67
- loadStyles() {
68
- console.log("load styles")
69
- const __filename = fileURLToPath(import.meta.url);
70
- const __dirname = path.dirname(__filename);
71
- try {
72
- const content = fs.readFileSync(
73
- path.join(__dirname, "styles.min.css"),
74
- "utf-8"
75
- );
76
- const match = content.match(/\/\* source-hash: ([a-f0-9]+) \*\//);
77
- if (match) {
78
- this.#stylesHash = match[1];
79
- this.compiledStyles = content.replace(match[0], "").trim();
80
- } else {
81
- this.compiledStyles = content;
82
- this.#stylesHash = "";
83
- }
84
- } catch(err) {
85
- this.compiledStyles = "";
86
- this.#stylesHash = "";
87
- }
88
- }
89
-
90
- loadScripts() {
91
- const __filename = fileURLToPath(import.meta.url);
92
- const __dirname = path.dirname(__filename);
93
- try {
94
- this.scripts = fs.readFileSync(path.join(__dirname, "scripts.min.js"), "utf-8");
95
- //console.log(this.scripts)
96
- } catch (err) {
97
- console.error(err);
98
- this.scripts = "";
99
- }
100
- }
101
-
102
46
  // Private fields
103
47
  #server = null;
104
48
  #password = null;
@@ -108,6 +52,7 @@ export default class Blog {
108
52
  #title = "";
109
53
  #stylesHash = "";
110
54
  #scriptsHash = "";
55
+ #stylesheetPath = "";
111
56
 
112
57
  setTitle(title) {
113
58
  this.#title = title;
@@ -138,7 +83,8 @@ export default class Blog {
138
83
  }
139
84
 
140
85
  set stylesheetPath(files) {
141
- this.assetFiles = files;
86
+ this.#stylesheetPath = files;
87
+ console.log(`this.#stylesheetPath: ${this.#stylesheetPath}`)
142
88
  }
143
89
 
144
90
  addArticle(article) {
@@ -170,7 +116,7 @@ export default class Blog {
170
116
  res.end();
171
117
  } else {
172
118
  res.writeHead(401, { "Content-Type": "text/html" });
173
- res.end(`<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${this.styles}</style></head>
119
+ res.end(`${header("My Blog")}
174
120
  <body>
175
121
  <h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
176
122
  <input type="password" name="password" placeholder="Password" />
@@ -197,11 +143,51 @@ export default class Blog {
197
143
  /** initializes database */
198
144
  async init() {
199
145
  //await this.buildFrontend();
200
- this.loadStyles();
201
- this.loadScripts();
202
- //const assetFiles = await this.#findAssetFiles();
203
- if(this.assetFiles != null) {
204
- await this.processAssets(this.assetFiles);
146
+ //this.loadStyles();
147
+ //this.loadScripts();
148
+ if(this.#stylesheetPath != null) {
149
+ await this.processStylesheets(this.#stylesheetPath);
150
+ }
151
+ if(!this.#stylesheetPath) {
152
+ // compile and merge hardcoded styles with src/styles.css and write to file "styles.min.css"
153
+ // which will be imported by webbrowser via '<link rel="stylesheet" href="styles.min.css"...'
154
+
155
+ const __filename = fileURLToPath(import.meta.url);
156
+ const __dirname = path.dirname(__filename);
157
+ const srcStylePath = path.join(__dirname, "src", "styles.css");
158
+ const publicStylePath = path.join(__dirname, "public", "styles.min.css");
159
+
160
+ let publicHash = null;
161
+ try {
162
+ const publicCSS = await fs.promises.readFile(publicStylePath, "utf8");
163
+ const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
164
+ if (match) {
165
+ publicHash = match[1];
166
+ }
167
+ } catch (err) {
168
+ // public/styles.min.css doesn't exist, will be created.
169
+ }
170
+
171
+ let srcStyles = "";
172
+ try {
173
+ srcStyles = await fs.promises.readFile(srcStylePath, "utf8");
174
+ } catch (err) {
175
+ // ignore if src/styles.css doesn't exist
176
+ }
177
+
178
+ const combinedStyles = this.styles + srcStyles;
179
+ const srcHash = crypto.createHash("sha256").update(combinedStyles).digest("hex");
180
+
181
+ if (srcHash !== publicHash) {
182
+ console.log("Styles have changed. Recompiling...");
183
+ const finalStyles = await mergeStyles(this.styles, srcStyles);
184
+ try {
185
+ await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
186
+ await fs.promises.writeFile(publicStylePath, finalStyles + `\n/* source-hash: ${srcHash} */`);
187
+ } catch (err) {
188
+ console.error("Failed to write styles to public folder:", err);
189
+ }
190
+ }
205
191
  }
206
192
  if (this.#isExternalAPI) {
207
193
  console.log("external API");
@@ -218,7 +204,6 @@ export default class Blog {
218
204
  dbArticles.push(new Article("Sample Entry #1", "Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.", new Date()));
219
205
  dbArticles.push(new Article("Sample Entry #2", "Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.", new Date()));
220
206
  }
221
- this.reloadStylesOnGET = false;
222
207
  if(this.reloadStylesOnGET) console.log("reload scripts and styles on GET-Request");
223
208
  let title = "";
224
209
  if (this.#title != null && this.#title.length > 0) title = this.#title; // use blog title if set
@@ -280,7 +265,7 @@ export default class Blog {
280
265
  if (req.url === "/login") {
281
266
  if (req.method === "GET") {
282
267
  res.writeHead(200, { "Content-Type": "text/html" });
283
- res.end(`<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${this.styles}</style></head>
268
+ res.end(`${header("My Blog")}
284
269
  <body>
285
270
  <h1>Login</h1><form method="POST">
286
271
  <input type="password" name="password" placeholder="Password" />
@@ -312,11 +297,8 @@ export default class Blog {
312
297
 
313
298
  // reload styles and scripts on (every) request
314
299
  if(this.reloadStylesOnGET) {
315
- if (this.assetFiles) {
316
- await this.processAssets(this.assetFiles);
317
- } else {
318
- this.loadStyles();
319
- this.loadScripts();
300
+ if (this.#stylesheetPath) {
301
+ await this.processStylesheets(this.#stylesheetPath);
320
302
  }
321
303
  }
322
304
 
@@ -339,6 +321,39 @@ export default class Blog {
339
321
  res.end("Internal Server Error");
340
322
  }
341
323
  } else {
324
+ // Try to serve static files from public folder
325
+ try {
326
+ const __filename = fileURLToPath(import.meta.url);
327
+ const __dirname = path.dirname(__filename);
328
+ const publicDir = path.join(__dirname, "public");
329
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);
330
+ const filePath = path.join(publicDir, parsedUrl.pathname);
331
+
332
+ if (filePath.startsWith(publicDir)) {
333
+ const stats = await fs.promises.stat(filePath);
334
+ if (stats.isFile()) {
335
+ const ext = path.extname(filePath).toLowerCase();
336
+ const mimeTypes = {
337
+ ".html": "text/html",
338
+ ".js": "text/javascript",
339
+ ".css": "text/css",
340
+ ".json": "application/json",
341
+ ".png": "image/png",
342
+ ".jpg": "image/jpeg",
343
+ ".gif": "image/gif",
344
+ ".svg": "image/svg+xml",
345
+ ".ico": "image/x-icon",
346
+ };
347
+ const contentType = mimeTypes[ext] || "application/octet-stream";
348
+ res.writeHead(200, { "Content-Type": contentType });
349
+ fs.createReadStream(filePath).pipe(res);
350
+ return;
351
+ }
352
+ }
353
+ } catch (err) {
354
+ // Continue to 404
355
+ }
356
+
342
357
  // Error 404
343
358
  res.writeHead(404, { "Content-Type": "application/json" });
344
359
  res.end(JSON.stringify({ message: "Not Found" }));
@@ -477,30 +492,6 @@ export default class Blog {
477
492
  console.log(markdown);
478
493
  }
479
494
 
480
- /*async isItFileOrArray(filesInput) {
481
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
482
- const files = Array.isArray(filesInput) ? filesInput : [filesInput];
483
- const fileData = await Promise.all(files.map(async (f) => {
484
- const filepath = path.isAbsolute(f) ? f : path.join(__dirname, f);
485
- const content = await fs.promises.readFile(filepath, "utf8");
486
- return { path: filepath, content };
487
- }));
488
- return fileData;
489
- }*/
490
-
491
- /*computeHashsum(fileData) {
492
- return crypto
493
- .createHash("sha256")
494
- .update(
495
- fileData
496
- .map((f) =>
497
- crypto.createHash("sha256").update(f.content).digest("hex")
498
- )
499
- .join("")
500
- )
501
- .digest("hex");
502
- }*/
503
-
504
495
  /** render this blog content to valid html */
505
496
  async toHTML(loggedin) {
506
497
  const data = {
@@ -512,58 +503,21 @@ export default class Blog {
512
503
 
513
504
  if(loggedin) data.login = `<a href="/logout">logout</a>`;
514
505
  else data.login = `<a href="/login">login</a>`;
515
-
516
- /*const __dirname = path.dirname(fileURLToPath(import.meta.url));
517
- const filepath = path.join(__dirname, "test/stylesheets/styles.css");
518
- const fileData = await fs.promises.readFile(filepath, 'utf8');
519
- console.log(fileData);
520
- this.compiledStyles = fileData;*/
521
-
522
- //await this.processAssets(this.assetFiles);
523
-
524
- // is it file or array of file?
525
- /*if (this.assetFiles) {
526
- const fileData = await this.isItFileOrArray(this.assetFiles);
527
-
528
- const currentHash = this.computeHashsum(fileData);
529
- console.log(`currentHash: ${currentHash}`)
530
- if(currentHash !== this.#stylesHash) {
531
- console.log("Style assets have changed. Recompiling...");
532
- this.#stylesHash = currentHash;
533
- this.compiledStyles = await compileStyles(fileData);
534
- }
535
- }*/
536
506
 
537
- this.loadScripts();
538
-
539
- const finalStyles = this.styles + this.compiledStyles;
540
- const html = formatHTML(data, this.scripts, finalStyles);
507
+ const html = formatHTML(data);
541
508
  if (validate(html)) return html;
542
509
  throw new Error("Error. Invalid HTML!");
543
510
  }
544
511
 
545
- async buildFrontend() {
546
- const __filename = fileURLToPath(import.meta.url);
547
- const __dirname = path.dirname(__filename);
548
- const gulpDir = path.join(__dirname, "./gulp_frontend");
549
- console.log("Building frontend assets...");
550
- try {
551
- const { stdout, stderr } = await execPromise("npx gulp build", { cwd: gulpDir });
552
- if (stdout) console.log(stdout);
553
- if (stderr) console.error(stderr);
554
- } catch (err) {
555
- console.error("Failed to build frontend:", err);
556
- }
557
- }
558
-
559
512
  /**
560
- * Loads files, converts them to Vinyl format, and processes them via Gulp.
561
- * Updates this.styles and this.scripts with the compiled output.
562
- * @param {string[]} files - Array of file paths to process.
513
+ * Loads files, generates checksums and compiles sass files to css if anything changed
514
+ * @param {string[]} files - Array of css/scss file paths to process.
563
515
  */
564
- async processAssets(files) {
516
+ async processStylesheets(files) {
565
517
 
566
518
  // Normalize input to array (handles string or array)
519
+ // "file1.css" --> ["file1.css"]
520
+ // ["file1.css", "file2.css",...]
567
521
  const fileList = Array.isArray(files) ? files : [files];
568
522
 
569
523
  const __filename = fileURLToPath(import.meta.url);
@@ -575,6 +529,7 @@ export default class Blog {
575
529
 
576
530
  // --- Process Styles ---
577
531
  if (styleFiles.length > 0) {
532
+ // read file
578
533
  const fileData = await Promise.all(
579
534
  styleFiles.sort().map(async (f) => {
580
535
  const content = await fs.promises.readFile(f, "utf-8");
@@ -583,6 +538,7 @@ export default class Blog {
583
538
  })
584
539
  );
585
540
 
541
+ // compute hash
586
542
  const currentHash = crypto
587
543
  .createHash("sha256")
588
544
  .update(
@@ -593,16 +549,22 @@ export default class Blog {
593
549
  .join("")
594
550
  )
595
551
  .digest("hex");
552
+
553
+ // check if hash matches
596
554
  if (currentHash !== this.#stylesHash) {
597
555
  console.log("Style assets have changed. Recompiling...");
598
556
  this.#stylesHash = currentHash;
599
557
 
600
- // Compile styles using the standalone script
558
+ // Compile styles using the standalone script from build-styles.js
601
559
  this.compiledStyles = await compileStyles(fileData);
602
- //console.log(`compiledStyles: ${this.compiledStyles}`)
560
+
561
+ // generate a file
562
+ const __filename = fileURLToPath(import.meta.url);
563
+ const __dirname = path.dirname(__filename);
564
+ const publicDir = path.join(__dirname, "public");
603
565
 
604
566
  await fs.promises.writeFile(
605
- path.join(__dirname, "styles.min.css"),
567
+ path.join(publicDir, "styles.min.css"),
606
568
  this.compiledStyles + `\n/* source-hash: ${currentHash} */`
607
569
  );
608
570
  } else {
@@ -610,29 +572,4 @@ export default class Blog {
610
572
  }
611
573
  }
612
574
  }
613
-
614
- async #findAssetFiles() {
615
- const __filename = fileURLToPath(import.meta.url);
616
- const __dirname = path.dirname(__filename);
617
- const srcDir = path.join(__dirname, "gulp_frontend", "src");
618
- const files = [];
619
-
620
- async function walk(dir) {
621
- try {
622
- const list = await fs.promises.readdir(dir, { withFileTypes: true });
623
- for (const dirent of list) {
624
- const res = path.join(dir, dirent.name);
625
- if (dirent.isDirectory()) {
626
- await walk(res);
627
- } else {
628
- files.push(res);
629
- }
630
- }
631
- } catch (err) {
632
- // Directory might not exist or is not accessible
633
- }
634
- }
635
- await walk(srcDir);
636
- return files;
637
- }
638
575
  }
package/Formatter.js CHANGED
@@ -1,5 +1,18 @@
1
+ export function header(title) {
2
+ return `<!DOCTYPE html>
3
+ <html lang="de">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>${title}</title>
8
+ <style type="text/css">body { font-family: Arial,sans-serif; }</style>
9
+ <link rel="stylesheet" href="styles.min.css" type="text/css" media="screen" />
10
+ <link rel="stylesheet" href="print.min.css" type="text/css" media="print" />
11
+ </head>`;
12
+ }
13
+
1
14
  /** format content to html */
2
- export function formatHTML(data, script, style) {
15
+ export function formatHTML(data) {
3
16
  //console.log(`${data} ${script} ${style}`);
4
17
  //export function formatHTML(data) {
5
18
  //const button = `<button type="button" onClick="fillWithContent();" style="margin: 4px;">generate random text</button>`;
@@ -15,20 +28,13 @@ export function formatHTML(data, script, style) {
15
28
  <hr>`;
16
29
  }
17
30
  const form = form1;
18
- return `<!DOCTYPE html>
19
- <html lang="de">
20
- <head>
21
- <meta charset="UTF-8">
22
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
- <title>${data.title}</title>
24
- <style>${style}</style>
25
- </head>
31
+ return `${header(data.title)}
26
32
  <body>
27
33
  <nav>
28
34
  ${data.login}
29
35
  </nav>
30
36
  <h1>${data.title}</h1>
31
- <div style="max-width: 500px; width: 100%;">
37
+ <div id="wrapper">
32
38
  ${form}
33
39
  <section id="articles" class="grid">
34
40
  ${data.articles
@@ -44,8 +50,7 @@ export function formatHTML(data, script, style) {
44
50
  .join("")}
45
51
  </section>
46
52
  </div>
47
- <script>
48
- ${script}
53
+ <script src="scripts.min.js">
49
54
  </script>
50
55
  </body>
51
56
  </html>`;
package/blog.db CHANGED
Binary file
package/build-styles.js CHANGED
@@ -51,3 +51,21 @@ export async function compileStyles(fileData) {
51
51
  return "";
52
52
  }
53
53
  }
54
+
55
+ export async function mergeStyles(...cssContents) {
56
+ try {
57
+ const combinedCss = cssContents.join("\n");
58
+
59
+ if (combinedCss) {
60
+ const plugins = [autoprefixer(), cssnano()];
61
+ const result = await postcss(plugins).process(combinedCss, {
62
+ from: undefined,
63
+ });
64
+ return result.css;
65
+ }
66
+ return "";
67
+ } catch (error) {
68
+ console.error("Merge failed:", error);
69
+ return "";
70
+ }
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -27,9 +27,9 @@
27
27
  "sqlite3": "^5.1.7"
28
28
  },
29
29
  "devDependencies": {
30
+ "dom-parser": "^1.1.5",
30
31
  "eslint": "^9.8.0",
31
32
  "eslint-plugin-jest": "^28.6.0",
32
- "jest": "^29.7.0",
33
- "dom-parser": "^1.1.5"
33
+ "jest": "^29.7.0"
34
34
  }
35
35
  }
@@ -0,0 +1,2 @@
1
+ *{font-size:.9rem}body{background-color:#fff;color:#000}h1{font:italic small-caps 700 2.5em/2.3em Verdana,sans-serif;letter-spacing:.1em;margin:0;padding:0}#wrapper{max-width:600px;width:100%}.grid{border:2px solid #d3d3d3;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article h2{color:primary;margin-bottom:2px}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article .datetime{font-style:italic;margin:0}.grid article p{margin-bottom:0;margin-top:10px}.grid article a,.grid article a:link a:visited{color:#000}h1{color:primary}nav a{color:#3c3c3c;font-size:20px;text-decoration:underline}nav a[href*=login]{display:none}nav a:visited{color:#3c3c3c;text-decoration-color:#3c3c3c}
2
+ /*# sourceMappingURL=print-compiled.css.map */
@@ -1 +1 @@
1
- function _createForOfIteratorHelper(e,t){var r,n,o,a,i="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(i)return o=!(n=!0),{s:function(){i=i.call(e)},n:function(){var e=i.next();return n=e.done,e},e:function(e){o=!0,r=e},f:function(){try{n||null==i.return||i.return()}finally{if(o)throw r}}};if(Array.isArray(e)||(i=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return i&&(e=i),a=0,{s:t=function(){},n:function(){return a>=e.length?{done:!0}:{done:!1,value:e[a++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var r;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(r="Object"===(r={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:r)||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function _regenerator(){var g,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",r=e.toStringTag||"@@toStringTag";function n(e,t,r,n){var o,a,i,c,l,u,f,s,d,t=t&&t.prototype instanceof h?t:h,t=Object.create(t.prototype);return _regeneratorDefine2(t,"_invoke",(o=e,a=r,f=n||[],s=!1,d={p:u=0,n:0,v:g,a:p,f:p.bind(g,4),d:function(e,t){return i=e,c=0,l=g,d.n=t,y}},function(e,t,r){if(1<u)throw TypeError("Generator is already running");for(s&&1===t&&p(t,r),c=t,l=r;(m=c<2?g:l)||!s;){i||(c?c<3?(1<c&&(d.n=-1),p(c,l)):d.n=l:d.v=l);try{if(u=2,i){if(m=i[e=c?e:"next"]){if(!(m=m.call(i,l)))throw TypeError("iterator result is not an object");if(!m.done)return m;l=m.value,c<2&&(c=0)}else 1===c&&(m=i.return)&&m.call(i),c<2&&(l=TypeError("The iterator does not provide a '"+e+"' method"),c=1);i=g}else if((m=(s=d.n<0)?l:o.call(a,d))!==y)break}catch(e){i=g,c=1,l=e}finally{u=1}}return{value:m,done:s}}),!0),t;function p(e,t){for(c=e,l=t,m=0;!s&&u&&!r&&m<f.length;m++){var r,n=f[m],o=d.p,a=n[2];3<e?(r=a===t)&&(l=n[(c=n[4])?5:c=3],n[4]=n[5]=g):n[0]<=o&&((r=e<2&&o<n[1])?(c=0,d.v=t,d.n=n[1]):o<a&&(r=e<3||n[0]>t||a<t)&&(n[4]=e,n[5]=t,d.n=a,c=0))}if(r||1<e)return y;throw s=!0,t}}var y={};function h(){}function o(){}function a(){}var m=Object.getPrototypeOf,e=[][t]?m(m([][t]())):(_regeneratorDefine2(m={},t,function(){return this}),m),i=a.prototype=h.prototype=Object.create(e);function c(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,a):(e.__proto__=a,_regeneratorDefine2(e,r,"GeneratorFunction")),e.prototype=Object.create(i),e}return _regeneratorDefine2(i,"constructor",o.prototype=a),_regeneratorDefine2(a,"constructor",o),_regeneratorDefine2(a,r,o.displayName="GeneratorFunction"),_regeneratorDefine2(i),_regeneratorDefine2(i,r,"Generator"),_regeneratorDefine2(i,t,function(){return this}),_regeneratorDefine2(i,"toString",function(){return"[object Generator]"}),(_regenerator=function(){return{w:n,m:c}})()}function _regeneratorDefine2(e,t,r,n){var a=Object.defineProperty;try{a({},"",{})}catch(e){a=0}(_regeneratorDefine2=function(e,t,r,n){function o(t,r){_regeneratorDefine2(e,t,function(e){return this._invoke(t,r,e)})}t?a?a(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(o("next",0),o("throw",1),o("return",2))})(e,t,r,n)}function asyncGeneratorStep(e,t,r,n,o,a,i){try{var c=e[a](i),l=c.value}catch(e){return void r(e)}c.done?t(l):Promise.resolve(l).then(n,o)}function _asyncToGenerator(c){return function(){var e=this,i=arguments;return new Promise(function(t,r){var n=c.apply(e,i);function o(e){asyncGeneratorStep(n,t,r,o,a,"next",e)}function a(e){asyncGeneratorStep(n,t,r,o,a,"throw",e)}o(void 0)})}}var isLoading=!1;function handleScroll(){return _handleScroll.apply(this,arguments)}function _handleScroll(){return(_handleScroll=_asyncToGenerator(_regenerator().m(function e(){return _regenerator().w(function(e){for(;;)switch(e.n){case 0:if(isLoading)return e.a(2);e.n=1;break;case 1:if(document.documentElement.scrollHeight-window.innerHeight-window.scrollY<100)return e.n=2,loadMore();e.n=2;break;case 2:return e.a(2)}},e)}))).apply(this,arguments)}function loadMore(){return _loadMore.apply(this,arguments)}function _loadMore(){return(_loadMore=_asyncToGenerator(_regenerator().m(function e(){var t,r,n,o,a,i,c,l;return _regenerator().w(function(e){for(;;)switch(e.p=e.n){case 0:if(console.log("load more"),t=document.getElementById("articles"),t=t.querySelectorAll("article"),t=t[t.length-1],console.log("lastArticle",t),t){e.n=1;break}return e.a(2);case 1:if(r=parseInt(t.getAttribute("data-id")),console.log("lastId ".concat(r)),isNaN(r))return e.a(2);e.n=2;break;case 2:if((n=r-1)<1)return console.log("nextId < 1 ".concat(n<1)),window.removeEventListener("scroll",handleScroll),e.a(2);e.n=3;break;case 3:return isLoading=!0,o="/api/articles?startID=".concat(n,"&limit=5"),console.log("load more ".concat(o)),e.p=4,e.n=5,fetch(o);case 5:if((o=e.v).ok)return e.n=6,o.json();e.n=7;break;case 6:if((l=e.v).articles&&0<l.articles.length){a=_createForOfIteratorHelper(l.articles);try{for(a.s();!(i=a.n()).done;)fillWithContent((c=i.value).title,c.content,c.createdAt,c.id)}catch(e){a.e(e)}finally{a.f()}}case 7:e.n=9;break;case 8:e.p=8,l=e.v,console.error("Failed to load articles:",l);case 9:isLoading=!1;case 10:return e.a(2)}},e,null,[[4,8]])}))).apply(this,arguments)}function getFormattedDate(e){var t=e.getFullYear(),r=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0"),o=String(e.getHours()).padStart(2,"0"),e=String(e.getMinutes()).padStart(2,"0");return"".concat(t,"/").concat(r,"/").concat(n," ").concat(o,":").concat(e)}function fillWithContent(e,t,r,n){var o=document.createElement("article"),n=(n&&o.setAttribute("data-id",n),document.createElement("h2")),a=document.createElement("span"),i=document.createElement("p");n.textContent=e,a.textContent=getFormattedDate(new Date(r)),i.textContent=t,o.appendChild(n),o.appendChild(a),o.appendChild(i),document.getElementById("articles").appendChild(o)}function test(){console.log("123")}document.addEventListener("DOMContentLoaded",function(){window.addEventListener("scroll",handleScroll)});
1
+ function _createForOfIteratorHelper(e,t){var r,n,o,a,i="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(i)return o=!(n=!0),{s:function(){i=i.call(e)},n:function(){var e=i.next();return n=e.done,e},e:function(e){o=!0,r=e},f:function(){try{n||null==i.return||i.return()}finally{if(o)throw r}}};if(Array.isArray(e)||(i=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return i&&(e=i),a=0,{s:t=function(){},n:function(){return a>=e.length?{done:!0}:{done:!1,value:e[a++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var r;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(r="Object"===(r={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:r)||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function _regenerator(){var g,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",r=e.toStringTag||"@@toStringTag";function n(e,t,r,n){var o,a,i,c,l,u,f,s,d,t=t&&t.prototype instanceof h?t:h,t=Object.create(t.prototype);return _regeneratorDefine2(t,"_invoke",(o=e,a=r,f=n||[],s=!1,d={p:u=0,n:0,v:g,a:p,f:p.bind(g,4),d:function(e,t){return i=e,c=0,l=g,d.n=t,y}},function(e,t,r){if(1<u)throw TypeError("Generator is already running");for(s&&1===t&&p(t,r),c=t,l=r;(m=c<2?g:l)||!s;){i||(c?c<3?(1<c&&(d.n=-1),p(c,l)):d.n=l:d.v=l);try{if(u=2,i){if(m=i[e=c?e:"next"]){if(!(m=m.call(i,l)))throw TypeError("iterator result is not an object");if(!m.done)return m;l=m.value,c<2&&(c=0)}else 1===c&&(m=i.return)&&m.call(i),c<2&&(l=TypeError("The iterator does not provide a '"+e+"' method"),c=1);i=g}else if((m=(s=d.n<0)?l:o.call(a,d))!==y)break}catch(e){i=g,c=1,l=e}finally{u=1}}return{value:m,done:s}}),!0),t;function p(e,t){for(c=e,l=t,m=0;!s&&u&&!r&&m<f.length;m++){var r,n=f[m],o=d.p,a=n[2];3<e?(r=a===t)&&(l=n[(c=n[4])?5:c=3],n[4]=n[5]=g):n[0]<=o&&((r=e<2&&o<n[1])?(c=0,d.v=t,d.n=n[1]):o<a&&(r=e<3||n[0]>t||a<t)&&(n[4]=e,n[5]=t,d.n=a,c=0))}if(r||1<e)return y;throw s=!0,t}}var y={};function h(){}function o(){}function a(){}var m=Object.getPrototypeOf,e=[][t]?m(m([][t]())):(_regeneratorDefine2(m={},t,function(){return this}),m),i=a.prototype=h.prototype=Object.create(e);function c(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,a):(e.__proto__=a,_regeneratorDefine2(e,r,"GeneratorFunction")),e.prototype=Object.create(i),e}return _regeneratorDefine2(i,"constructor",o.prototype=a),_regeneratorDefine2(a,"constructor",o),_regeneratorDefine2(a,r,o.displayName="GeneratorFunction"),_regeneratorDefine2(i),_regeneratorDefine2(i,r,"Generator"),_regeneratorDefine2(i,t,function(){return this}),_regeneratorDefine2(i,"toString",function(){return"[object Generator]"}),(_regenerator=function(){return{w:n,m:c}})()}function _regeneratorDefine2(e,t,r,n){var a=Object.defineProperty;try{a({},"",{})}catch(e){a=0}(_regeneratorDefine2=function(e,t,r,n){function o(t,r){_regeneratorDefine2(e,t,function(e){return this._invoke(t,r,e)})}t?a?a(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(o("next",0),o("throw",1),o("return",2))})(e,t,r,n)}function asyncGeneratorStep(e,t,r,n,o,a,i){try{var c=e[a](i),l=c.value}catch(e){return void r(e)}c.done?t(l):Promise.resolve(l).then(n,o)}function _asyncToGenerator(c){return function(){var e=this,i=arguments;return new Promise(function(t,r){var n=c.apply(e,i);function o(e){asyncGeneratorStep(n,t,r,o,a,"next",e)}function a(e){asyncGeneratorStep(n,t,r,o,a,"throw",e)}o(void 0)})}}var isLoading=!1;function handleScroll(){return _handleScroll.apply(this,arguments)}function _handleScroll(){return(_handleScroll=_asyncToGenerator(_regenerator().m(function e(){var t,r;return _regenerator().w(function(e){for(;;)switch(e.n){case 0:if(isLoading)return e.a(2);e.n=1;break;case 1:if(t=window.scrollY||document.documentElement.scrollTop||document.body.scrollTop,r=window.innerHeight||document.documentElement.clientHeight,(document.documentElement.scrollHeight||document.body.scrollHeight)-t-r<100)return e.n=2,loadMore();e.n=2;break;case 2:return e.a(2)}},e)}))).apply(this,arguments)}function loadMore(){return _loadMore.apply(this,arguments)}function _loadMore(){return(_loadMore=_asyncToGenerator(_regenerator().m(function e(){var t,r,n,o,a,i,c,l,u;return _regenerator().w(function(e){for(;;)switch(e.p=e.n){case 0:if(console.log("load more"),t=document.getElementById("articles"),t=t.querySelectorAll("article"),t=t[t.length-1],console.log("lastArticle",t),t){e.n=1;break}return e.a(2);case 1:if(r=parseInt(t.getAttribute("data-id")),console.log("lastId ".concat(r)),isNaN(r))return e.a(2);e.n=2;break;case 2:if((n=r-1)<1)return console.log("nextId < 1 ".concat(n<1)),window.removeEventListener("scroll",handleScroll),e.a(2);e.n=3;break;case 3:return o=!(isLoading=!0),a="/api/articles?startID=".concat(n,"&limit=5"),console.log("load more ".concat(a)),e.p=4,e.n=5,fetch(a);case 5:if((a=e.v).ok)return e.n=6,a.json();e.n=7;break;case 6:if((u=e.v).articles&&0<u.articles.length){i=_createForOfIteratorHelper(u.articles);try{for(i.s();!(c=i.n()).done;)fillWithContent((l=c.value).title,l.content,l.createdAt,l.id)}catch(e){i.e(e)}finally{i.f()}o=!0}case 7:e.n=9;break;case 8:e.p=8,u=e.v,console.error("Failed to load articles:",u);case 9:isLoading=!1,o&&handleScroll();case 10:return e.a(2)}},e,null,[[4,8]])}))).apply(this,arguments)}function getFormattedDate(e){var t=e.getFullYear(),r=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0"),o=String(e.getHours()).padStart(2,"0"),e=String(e.getMinutes()).padStart(2,"0");return"".concat(t,"/").concat(r,"/").concat(n," ").concat(o,":").concat(e)}function fillWithContent(e,t,r,n){var o=document.createElement("article"),n=(n&&o.setAttribute("data-id",n),document.createElement("h2")),a=document.createElement("span"),i=document.createElement("p");n.textContent=e,a.textContent=getFormattedDate(new Date(r)),i.textContent=t,o.appendChild(n),o.appendChild(a),o.appendChild(i),document.getElementById("articles").appendChild(o)}function test(){console.log("123")}document.addEventListener("DOMContentLoaded",function(){window.addEventListener("scroll",handleScroll,{passive:!0}),handleScroll()});
@@ -0,0 +1,2 @@
1
+ body{font-family:Arial;font-family:Arial,sans-serif}h1{color:#333}.grid{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
2
+ /* source-hash: 374cb67a628dde5695d1104c5f9037d737d17d6b6b9a791be81779d7dd91beac */
package/src/print.scss ADDED
@@ -0,0 +1,76 @@
1
+ $primary: black;
2
+ $secondary: black;
3
+ $nav: rgb(60, 60, 60);
4
+ $background: white;
5
+
6
+ * {
7
+ font-size: 0.5rem; /* Forces every single element to this size */
8
+ }
9
+
10
+
11
+ body {
12
+ color: $primary;
13
+ background-color: $background;
14
+ }
15
+
16
+ h1 { font: italic small-caps bold 2.5em/2.3em Verdana,sans-serif;
17
+ margin: 0; padding: 0;
18
+ letter-spacing: 0.1em;
19
+ }
20
+
21
+ #wrapper {
22
+ max-width: 600px; width: 100%;
23
+ }
24
+
25
+ .grid {
26
+ display: grid;
27
+ grid-template-columns: 1fr;
28
+ gap: 0.25rem;
29
+ border: 2px solid lightgray;
30
+
31
+ article {
32
+ h2 { color: primary;
33
+ margin-bottom: 2px;
34
+ }
35
+ padding: 0.25rem;
36
+ border: 0px solid #ccc;
37
+ border-radius: 4px;
38
+ min-width: 0; /* Allow grid items to shrink */
39
+ overflow-wrap: break-word; /* Break long words */
40
+ .datetime {
41
+ margin: 0;
42
+ font-style: italic;
43
+ }
44
+ p {
45
+ margin-top: 10px;
46
+ margin-bottom: 0;
47
+ }
48
+ a {
49
+ color: black;
50
+ }
51
+ a:link a:visited {
52
+ color: black;
53
+ }
54
+ }
55
+ }
56
+
57
+ h1 {
58
+ color: primary;
59
+ }
60
+
61
+ nav {
62
+ a {
63
+ text-decoration: underline;
64
+ color: $nav;
65
+ font-size: 20px;
66
+ }
67
+
68
+ a[href*="login"] {
69
+ display: none;
70
+ }
71
+
72
+ a:visited {
73
+ color: $nav;
74
+ text-decoration-color: $nav;
75
+ }
76
+ }
package/src/styles.css CHANGED
@@ -46,3 +46,15 @@ nav a:visited {
46
46
  color: #3b40c1;
47
47
  text-decoration-color: #3b40c1;
48
48
  }
49
+ /* Mobile Layout (screens smaller than 1000px) */
50
+ @media screen and (max-width: 1000px) {
51
+ * {
52
+ font-size: 4vw;
53
+ }
54
+ #wrapper {
55
+ max-width: 100%;
56
+ width: 100%;
57
+ padding: 0 10px; /* Prevents text from touching the edges */
58
+ box-sizing: border-box;
59
+ }
60
+ }
package/test/blog.test.js CHANGED
@@ -1,3 +1,6 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+ import fs from "node:fs";
1
4
  import Blog from "../Blog.js";
2
5
  import Article from "../Article.js";
3
6
  import { jest } from "@jest/globals";
@@ -48,17 +51,31 @@ describe("test blog", () => {
48
51
  expect(html).toContain("<article");
49
52
  expect(myblog.articles.length).toBe(size);
50
53
  });
54
+ const __filename = fileURLToPath(import.meta.url);
55
+ const __dirname = path.dirname(__filename);
56
+ const publicDir = path.join(__dirname, "../public");
51
57
  test("set style", async () => {
52
- const myblog = new Blog();
53
58
  const styles = [
54
- "body { font-family: Courier; }",
55
- "body { background-color: black; color: white; }",
56
- "body { font-size: 1.2em; }",
59
+ {
60
+ style: "body { font-family: Courier; }",
61
+ expected: "font-family:Courier",
62
+ },
63
+ {
64
+ style: "body{background-color:black;color:white;}",
65
+ expected: "background-color:#000;color:#fff",
66
+ },
67
+ { style: "body{font-size: 1.2em;}", expected: "font-size:1.2em" },
57
68
  ];
58
69
  for (const style of styles) {
59
- myblog.setStyle(style);
60
- const html = await myblog.toHTML();
61
- expect(html).toContain(style);
70
+ const cssPath = path.join(publicDir, "styles.min.css");
71
+ if (fs.existsSync(cssPath)) {
72
+ await fs.promises.unlink(cssPath);
73
+ }
74
+ const myblog = new Blog();
75
+ myblog.setStyle(style.style);
76
+ await myblog.init();
77
+ const publicCSS = await fs.promises.readFile(cssPath, "utf8");
78
+ expect(publicCSS).toContain(style.expected);
62
79
  }
63
80
  });
64
81
  test("print method logs title and articles to console", async () => {
@@ -1,10 +1,11 @@
1
1
  import Blog from "../Blog.js";
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
- import { parseFromString } from "dom-parser";
5
4
  import fs from "node:fs";
6
5
 
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const publicDir = path.join(__dirname, "../public");
8
9
 
9
10
  describe("Blog Stylesheet Test", () => {
10
11
  it("should load and compile the sass stylesheet (.scss) correctly", async () => {
@@ -15,12 +16,16 @@ describe("Blog Stylesheet Test", () => {
15
16
  blog.stylesheetPath = "test/stylesheets/styles.scss";
16
17
 
17
18
  await blog.init();
18
- const html = await blog.toHTML();
19
19
 
20
- expect(html).toContain(".grid");
21
- expect(html).toContain("article");
22
- expect(html).toContain("nav");
23
- expect(html).toContain("nav a:visited");
20
+ const publicCSS = await fs.promises.readFile(
21
+ path.join(publicDir, "styles.min.css"),
22
+ "utf8"
23
+ );
24
+
25
+ expect(publicCSS).toContain(".grid");
26
+ expect(publicCSS).toContain("article");
27
+ expect(publicCSS).toContain("nav");
28
+ expect(publicCSS).toContain("nav a:visited");
24
29
  });
25
30
 
26
31
  it("should load and compile the sass stylesheet (.scss) correctly [Array]", async () => {
@@ -31,12 +36,15 @@ describe("Blog Stylesheet Test", () => {
31
36
  blog.stylesheetPath = ["test/stylesheets/styles.scss"];
32
37
 
33
38
  await blog.init();
34
- const html = await blog.toHTML();
39
+ const publicCSS = await fs.promises.readFile(
40
+ path.join(publicDir, "styles.min.css"),
41
+ "utf8"
42
+ );
35
43
 
36
- expect(html).toContain(".grid");
37
- expect(html).toContain("article");
38
- expect(html).toContain("nav");
39
- expect(html).toContain("nav a:visited");
44
+ expect(publicCSS).toContain(".grid");
45
+ expect(publicCSS).toContain("article");
46
+ expect(publicCSS).toContain("nav");
47
+ expect(publicCSS).toContain("nav a:visited");
40
48
  });
41
49
 
42
50
  it("should load the stylesheet (.css) file", async () => {
@@ -57,25 +65,20 @@ describe("Blog Stylesheet Test", () => {
57
65
  blog.database.dbname = "test_styles_3";
58
66
  blog.stylesheetPath = "test/stylesheets/styles.css";
59
67
  await blog.init();
60
- const html = await blog.toHTML();
61
68
 
62
- // dom-parser fails on <!DOCTYPE html>, so we remove it before parsing
63
- const doc = parseFromString(
64
- html.replace("<!DOCTYPE html>", ""),
65
- "text/html"
69
+ const publicCSS = await fs.promises.readFile(
70
+ path.join(publicDir, "styles.min.css"),
71
+ "utf8"
66
72
  );
67
73
 
68
- // Find all style tags
69
- const styleTags = doc.getElementsByTagName("style");
70
- const css = styleTags.map((tag) => tag.innerHTML).join("\n");
71
- console.log(css);
72
-
73
- expect(html).not.toContain("<style>body { font-family: Arial; }</style>");
74
- expect(css).toContain("body");
75
- expect(css).toContain("nav a");
76
- expect(css).toContain(".datetime");
77
- expect(css).toContain("font-style:normal");
78
- expect(css).toContain("color:");
74
+ expect(publicCSS).not.toContain(
75
+ "<style>body { font-family: Arial; }</style>"
76
+ );
77
+ expect(publicCSS).toContain("body");
78
+ expect(publicCSS).toContain("nav a");
79
+ expect(publicCSS).toContain(".datetime");
80
+ expect(publicCSS).toContain("font-style:normal");
81
+ expect(publicCSS).toContain("color:");
79
82
  });
80
83
 
81
84
  it("should load and compile the stylesheet (.css) correctly [Array]", async () => {
@@ -84,24 +87,19 @@ describe("Blog Stylesheet Test", () => {
84
87
  blog.database.dbname = "test_styles_4";
85
88
  blog.stylesheetPath = ["test/stylesheets/styles.css"];
86
89
  await blog.init();
87
- const html = await blog.toHTML();
88
90
 
89
- // dom-parser fails on <!DOCTYPE html>, so we remove it before parsing
90
- const doc = parseFromString(
91
- html.replace("<!DOCTYPE html>", ""),
92
- "text/html"
91
+ const publicCSS = await fs.promises.readFile(
92
+ path.join(publicDir, "styles.min.css"),
93
+ "utf8"
93
94
  );
94
95
 
95
- // Find all style tags
96
- const styleTags = doc.getElementsByTagName("style");
97
- const css = styleTags.map((tag) => tag.innerHTML).join("\n");
98
- console.log(css);
99
-
100
- expect(html).not.toContain("<style>body { font-family: Arial; }</style>");
101
- expect(css).toContain("body");
102
- expect(css).toContain("nav a");
103
- expect(css).toContain(".datetime");
104
- expect(css).toContain("font-style:normal");
105
- expect(css).toContain("color:");
96
+ expect(publicCSS).not.toContain(
97
+ "<style>body { font-family: Arial; }</style>"
98
+ );
99
+ expect(publicCSS).toContain("body");
100
+ expect(publicCSS).toContain("nav a");
101
+ expect(publicCSS).toContain(".datetime");
102
+ expect(publicCSS).toContain("font-style:normal");
103
+ expect(publicCSS).toContain("color:");
106
104
  });
107
105
  });
Binary file
package/test_server_db.db CHANGED
Binary file
package/test_styles_1.db CHANGED
Binary file
package/test_styles_2.db CHANGED
Binary file
package/test_styles_3.db CHANGED
Binary file
package/test_styles_4.db CHANGED
Binary file
package/blog.db-journal DELETED
Binary file
package/styles.min.css DELETED
@@ -1 +0,0 @@
1
- .grid{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
File without changes