@lexho111/plainblog 0.5.10 → 0.5.12

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/Blog.js CHANGED
@@ -5,16 +5,11 @@ import { URLSearchParams } from "url";
5
5
  import Article from "./Article.js";
6
6
  import DatabaseModel from "./model/DatabaseModel.js";
7
7
  import { fetchData, postData } from "./model/APIModel.js";
8
- import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js";
9
- import pkg from "./package.json" with { type: "json" };
8
+ import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js"; // import pkg from "./package.json" with { type: "json" };
10
9
  import path from "path";
11
10
  import { fileURLToPath } from "url";
12
- import { exec } from "child_process";
13
- import { promisify } from "util";
14
11
  import { compileStyles, mergeStyles } from "./build-styles.js";
15
12
 
16
- const execPromise = promisify(exec);
17
-
18
13
  export default class Blog {
19
14
  constructor() {
20
15
  this.database = {
@@ -22,33 +17,60 @@ export default class Blog {
22
17
  username: "user",
23
18
  password: "password",
24
19
  host: "localhost",
25
- dbname: "blog.json",
20
+ dbname: "articles.txt", // x
26
21
  };
27
22
  this.#title = "";
28
- this.articles = [];
29
- this.filename = null;
23
+ this.#articles = [];
30
24
  this.#server = null;
31
25
  this.#password = "admin";
32
- this.styles = "body { font-family: Arial; }";
33
- this.scripts = "";
26
+ this.#styles = "body { font-family: Arial; }";
27
+ //this.scripts = "";
34
28
  this.compiledStyles = "";
35
- this.compiledScripts = "";
29
+ //this.compiledScripts = "";
36
30
  this.reloadStylesOnGET = false;
37
31
  this.sessions = new Set();
38
32
 
39
- const version = pkg.version;
40
- console.log(`version: ${version}`);
33
+ this.#version = "0.0.1"; //pkg.version;
34
+ console.log(`version: ${this.#version}`);
35
+ }
36
+
37
+ /** @returns a json representation of the blog */
38
+ json() {
39
+ const serverInfo = this.#server
40
+ ? {
41
+ listening: this.#server.listening,
42
+ address: this.#server.address(),
43
+ }
44
+ : null;
45
+
46
+ const json = {
47
+ version: this.#version,
48
+ title: this.#title,
49
+ articles: this.#articles,
50
+ server: serverInfo,
51
+ compiledStyles: this.compiledStyles,
52
+ sessions: this.sessions,
53
+ database: this.database,
54
+ password: this.#password,
55
+ styles: this.#styles,
56
+ reloadStylesOnGET: this.reloadStylesOnGET,
57
+ };
58
+
59
+ return JSON.parse(JSON.stringify(json));
41
60
  }
42
61
 
43
62
  // Private fields
63
+ #version = null;
44
64
  #server = null;
45
65
  #password = null;
46
66
  #databaseModel;
47
67
  #isExternalAPI = false;
48
68
  #apiUrl = "";
49
69
  #title = "";
70
+ #articles = [];
71
+ #styles = "";
50
72
  #stylesHash = "";
51
- #scriptsHash = "";
73
+ //#scriptsHash = "";
52
74
  #stylesheetPath = "";
53
75
 
54
76
  setTitle(title) {
@@ -60,7 +82,7 @@ export default class Blog {
60
82
  }
61
83
 
62
84
  setStyle(style) {
63
- this.styles += style;
85
+ this.#styles += style;
64
86
  }
65
87
 
66
88
  set title(t) {
@@ -69,7 +91,7 @@ export default class Blog {
69
91
  this.#databaseModel = new DatabaseModel(this.database);
70
92
  }
71
93
  console.log(`connected to database`);
72
- if(t != this.#title && t.length == 0)
94
+ if (t != this.#title && t.length == 0)
73
95
  this.#databaseModel.updateBlogTitle(t);
74
96
  }
75
97
 
@@ -81,6 +103,10 @@ export default class Blog {
81
103
  this.#password = x;
82
104
  }
83
105
 
106
+ /**
107
+ * allows you to inject a specific database implementation
108
+ * @param {*} adapter a database adapter like PostgresAdapter or SqliteAdapter
109
+ */
84
110
  setDatabaseAdapter(adapter) {
85
111
  if (!this.#databaseModel) {
86
112
  this.#databaseModel = new DatabaseModel(this.database);
@@ -88,112 +114,134 @@ export default class Blog {
88
114
  this.#databaseModel.setDatabaseAdapter(adapter);
89
115
  }
90
116
 
117
+ /**
118
+ * Appends CSS rules to the \<style\>-tag.
119
+ * @param {string} style - A string containing CSS rules.
120
+ */
91
121
  set style(style) {
92
- this.styles += style;
122
+ this.#styles += style;
93
123
  }
94
124
 
125
+ /**
126
+ * Sets the path(s) to custom CSS or SCSS files to be compiled and used by the blog.
127
+ * @param {string|string[]} files - A single file path or an array of file paths.
128
+ */
95
129
  set stylesheetPath(files) {
96
130
  this.#stylesheetPath = files;
97
- console.log(`this.#stylesheetPath: ${this.#stylesheetPath}`)
131
+ console.log(`this.#stylesheetPath: ${this.#stylesheetPath}`);
98
132
  }
99
133
 
100
134
  addArticle(article) {
101
- this.articles.push(article);
135
+ this.#articles.push(article);
102
136
  }
103
137
 
104
- isAuthenticated(req) {
105
- if (!req.headers.cookie) return false;
106
- const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
107
- return this.sessions.has(params.get("session"));
108
- }
109
-
110
- async handleLogin(req, res) {
111
- const body = await new Promise((resolve, reject) => {
112
- let data = "";
113
- req.on("data", (chunk) => (data += chunk.toString()));
114
- req.on("end", () => resolve(data));
115
- req.on("error", reject);
138
+ #isAuthenticated(req) {
139
+ if (!req.headers.cookie) return false;
140
+ const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
141
+ return this.sessions.has(params.get("session"));
142
+ }
143
+
144
+ async #handleLogin(req, res) {
145
+ const body = await new Promise((resolve, reject) => {
146
+ let data = "";
147
+ req.on("data", (chunk) => (data += chunk.toString()));
148
+ req.on("end", () => resolve(data));
149
+ req.on("error", reject);
150
+ });
151
+ const params = new URLSearchParams(body);
152
+
153
+ if (params.get("password") === this.#password) {
154
+ const id = crypto.randomUUID();
155
+ this.sessions.add(id);
156
+ res.writeHead(303, {
157
+ "Set-Cookie": `session=${id}; HttpOnly; Path=/`,
158
+ Location: "/",
116
159
  });
117
- const params = new URLSearchParams(body);
118
-
119
- if (params.get("password") === this.#password) {
120
- const id = crypto.randomUUID();
121
- this.sessions.add(id);
122
- res.writeHead(303, {
123
- "Set-Cookie": `session=${id}; HttpOnly; Path=/`,
124
- Location: "/",
125
- });
126
- res.end();
127
- } else {
128
- res.writeHead(401, { "Content-Type": "text/html" });
129
- res.end(`${header("My Blog")}
160
+ res.end();
161
+ } else {
162
+ res.writeHead(401, { "Content-Type": "text/html" });
163
+ res.end(`${header("My Blog")}
130
164
  <body>
131
165
  <h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
132
166
  <input type="password" name="password" placeholder="Password" />
133
167
  <button style="margin: 2px;">Login</button></form>
134
168
  </body></html>`);
135
- }
136
169
  }
137
-
138
- handleLogout(req, res) {
139
- if (req.headers.cookie) {
140
- const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
141
- const sessionId = params.get("session");
142
- if (this.sessions.has(sessionId)) {
143
- this.sessions.delete(sessionId);
144
- }
170
+ }
171
+
172
+ #handleLogout(req, res) {
173
+ if (req.headers.cookie) {
174
+ const params = new URLSearchParams(
175
+ req.headers.cookie.replace(/; /g, "&")
176
+ );
177
+ const sessionId = params.get("session");
178
+ if (this.sessions.has(sessionId)) {
179
+ this.sessions.delete(sessionId);
145
180
  }
146
- res.writeHead(303, {
147
- "Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",
148
- Location: "/",
149
- });
150
- res.end();
151
181
  }
182
+ res.writeHead(303, {
183
+ "Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",
184
+ Location: "/",
185
+ });
186
+ res.end();
187
+ }
152
188
 
153
189
  /** initializes database */
154
190
  async init() {
155
- //await this.buildFrontend();
156
191
  //this.loadStyles();
157
192
  //this.loadScripts();
158
- if(this.#stylesheetPath != null) {
159
- await this.processStylesheets(this.#stylesheetPath);
193
+ // if there is a stylesheet path provided, process it
194
+ if (this.#stylesheetPath != null) {
195
+ // read file from stylesheet path, compare checksums and write to public/styles.min.css
196
+ await this.#processStylesheets(this.#stylesheetPath);
160
197
  }
161
- if(!this.#stylesheetPath) {
162
- // compile and merge hardcoded styles with src/styles.css and write to file "styles.min.css"
198
+ if (!this.#stylesheetPath) {
199
+ // this.#styles
200
+ // src/styles.css
201
+ // compile and merge hardcoded styles in "this.#styles" with "src/styles.css" and write to file "styles.min.css"
163
202
  // which will be imported by webbrowser via '<link rel="stylesheet" href="styles.min.css"...'
164
-
203
+
165
204
  const __filename = fileURLToPath(import.meta.url);
166
205
  const __dirname = path.dirname(__filename);
167
206
  const srcStylePath = path.join(__dirname, "src", "styles.css");
168
207
  const publicStylePath = path.join(__dirname, "public", "styles.min.css");
169
208
 
170
209
  let publicHash = null;
171
- try {
172
- const publicCSS = await fs.promises.readFile(publicStylePath, "utf8");
173
- const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
174
- if (match) {
175
- publicHash = match[1];
176
- }
177
- } catch (err) {
178
- // public/styles.min.css doesn't exist, will be created.
179
- }
180
-
181
210
  let srcStyles = "";
182
- try {
183
- srcStyles = await fs.promises.readFile(srcStylePath, "utf8");
184
- } catch (err) {
185
- // ignore if src/styles.css doesn't exist
186
- }
187
211
 
188
- const combinedStyles = this.styles + srcStyles;
189
- const srcHash = crypto.createHash("sha256").update(combinedStyles).digest("hex");
212
+ await Promise.all([
213
+ fs.promises
214
+ .readFile(publicStylePath, "utf8")
215
+ .then((publicCSS) => {
216
+ const match = publicCSS.match(
217
+ /\/\* source-hash: ([a-f0-9]{64}) \*\//
218
+ );
219
+ if (match) publicHash = match[1];
220
+ })
221
+ .catch((err) => console.error(err)), // public/styles.min.css doesn't exist, will be created.
222
+ fs.promises
223
+ .readFile(srcStylePath, "utf8")
224
+ .then((content) => {
225
+ srcStyles = content;
226
+ })
227
+ .catch((err) => console.error(err)), // ignore if src/styles.css doesn't exist
228
+ ]);
229
+
230
+ const combinedStyles = this.#styles + srcStyles;
231
+ const srcHash = crypto
232
+ .createHash("sha256")
233
+ .update(combinedStyles)
234
+ .digest("hex");
190
235
 
191
236
  if (srcHash !== publicHash) {
192
237
  console.log("Styles have changed. Recompiling...");
193
- const finalStyles = await mergeStyles(this.styles, srcStyles);
238
+ const finalStyles = await mergeStyles(this.#styles, srcStyles);
194
239
  try {
195
- await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
196
- await fs.promises.writeFile(publicStylePath, finalStyles + `\n/* source-hash: ${srcHash} */`);
240
+ //await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
241
+ await fs.promises.writeFile(
242
+ publicStylePath,
243
+ finalStyles + `\n/* source-hash: ${srcHash} */`
244
+ );
197
245
  } catch (err) {
198
246
  console.error("Failed to write styles to public folder:", err);
199
247
  }
@@ -201,7 +249,7 @@ export default class Blog {
201
249
  }
202
250
  if (this.#isExternalAPI) {
203
251
  console.log("external API");
204
- await this.loadFromAPI();
252
+ await this.#loadFromAPI();
205
253
  } else {
206
254
  console.log(`database: ${this.database.type}`);
207
255
  if (!this.#databaseModel) {
@@ -209,17 +257,32 @@ export default class Blog {
209
257
  }
210
258
  console.log(`connected to database`);
211
259
  await this.#databaseModel.initialize();
212
- const dbTitle = await this.#databaseModel.getBlogTitle();
213
- const dbArticles = await this.#databaseModel.findAll();
214
- //console.log(`articles: ${JSON.stringify(dbarticles)}`)
215
- console.log(`dbArticles.length: ${dbArticles.length}`)
216
- if(dbArticles.length == 0) {
217
- 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()));
218
- 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()));
260
+ const [dbTitle, dbArticles] = await Promise.all([
261
+ this.#databaseModel.getBlogTitle(),
262
+ this.#databaseModel.findAll(),
263
+ ]);
264
+
265
+ if (dbArticles.length == 0) {
266
+ dbArticles.push(
267
+ new Article(
268
+ "Sample Entry #1",
269
+ "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.",
270
+ new Date()
271
+ )
272
+ );
273
+ dbArticles.push(
274
+ new Article(
275
+ "Sample Entry #2",
276
+ "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.",
277
+ new Date()
278
+ )
279
+ );
219
280
  }
220
- if(this.reloadStylesOnGET) console.log("reload scripts and styles on GET-Request");
281
+ if (this.reloadStylesOnGET)
282
+ console.log("reload scripts and styles on GET-Request");
221
283
  let title = "";
222
- if (this.#title != null && this.#title.length > 0) title = this.#title; // use blog title if set
284
+ if (this.#title != null && this.#title.length > 0)
285
+ title = this.#title; // use blog title if set
223
286
  else title = dbTitle; // use title from the database
224
287
  const responseData = { title: title, articles: dbArticles };
225
288
  this.#applyBlogData(responseData);
@@ -244,12 +307,16 @@ export default class Blog {
244
307
  const newArticleData = { title, content };
245
308
  try {
246
309
  // Save the new article to the database via the ApiServer
247
- if (this.#databaseModel) await this.#databaseModel.save(newArticleData);
248
- if (this.#isExternalAPI) await postData(this.#apiUrl, newArticleData);
310
+ const promises = [];
311
+ if (this.#databaseModel)
312
+ promises.push(this.#databaseModel.save(newArticleData));
313
+ if (this.#isExternalAPI)
314
+ promises.push(postData(this.#apiUrl, newArticleData));
315
+ await Promise.all(promises);
249
316
  // Add the article to the local list for immediate display
250
- this.articles.unshift(new Article(title, content, new Date()));
317
+ this.#articles.unshift(new Article(title, content, new Date()));
251
318
  // remove sample entries
252
- this.articles = this.articles.filter(
319
+ this.#articles = this.#articles.filter(
253
320
  (art) =>
254
321
  art.title !== "Sample Entry #1" && art.title !== "Sample Entry #2"
255
322
  );
@@ -286,18 +353,18 @@ export default class Blog {
286
353
  </body></html>`);
287
354
  return;
288
355
  } else if (req.method === "POST") {
289
- await this.handleLogin(req, res);
356
+ await this.#handleLogin(req, res);
290
357
  return;
291
358
  }
292
359
  }
293
360
 
294
361
  if (req.url === "/logout") {
295
- this.handleLogout(req, res);
362
+ this.#handleLogout(req, res);
296
363
  return;
297
364
  }
298
365
  // POST new article
299
366
  if (req.method === "POST" && req.url === "/") {
300
- if (!this.isAuthenticated(req)) {
367
+ if (!this.#isAuthenticated(req)) {
301
368
  res.writeHead(403, { "Content-Type": "text/plain" });
302
369
  res.end("Forbidden");
303
370
  return;
@@ -308,14 +375,14 @@ export default class Blog {
308
375
  // load articles
309
376
 
310
377
  // reload styles and scripts on (every) request
311
- if(this.reloadStylesOnGET) {
378
+ if (this.reloadStylesOnGET) {
312
379
  if (this.#stylesheetPath) {
313
- await this.processStylesheets(this.#stylesheetPath);
380
+ await this.#processStylesheets(this.#stylesheetPath);
314
381
  }
315
382
  }
316
383
 
317
384
  let loggedin = false;
318
- if (!this.isAuthenticated(req)) {
385
+ if (!this.#isAuthenticated(req)) {
319
386
  // login
320
387
  loggedin = false;
321
388
  } else {
@@ -338,7 +405,10 @@ export default class Blog {
338
405
  const __filename = fileURLToPath(import.meta.url);
339
406
  const __dirname = path.dirname(__filename);
340
407
  const publicDir = path.join(__dirname, "public");
341
- const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);
408
+ const parsedUrl = new URL(
409
+ req.url,
410
+ `http://${req.headers.host || "localhost"}`
411
+ );
342
412
  const filePath = path.join(publicDir, parsedUrl.pathname);
343
413
 
344
414
  if (filePath.startsWith(publicDir)) {
@@ -363,6 +433,7 @@ export default class Blog {
363
433
  }
364
434
  }
365
435
  } catch (err) {
436
+ console.error(err);
366
437
  // Continue to 404
367
438
  }
368
439
 
@@ -374,8 +445,11 @@ export default class Blog {
374
445
 
375
446
  this.#server = server;
376
447
 
377
- return new Promise((resolve) => {
378
- this.#server.listen(port, () => {
448
+ return new Promise((resolve, reject) => {
449
+ const errorHandler = (err) => reject(err);
450
+ this.#server.once("error", errorHandler);
451
+ this.#server.listen(port, "127.0.0.1", () => {
452
+ this.#server.removeListener("error", errorHandler);
379
453
  console.log(`server running at http://localhost:${port}/`);
380
454
  resolve(); // Resolve the promise when the server is listening
381
455
  });
@@ -385,12 +459,14 @@ export default class Blog {
385
459
  async closeServer() {
386
460
  return new Promise((resolve, reject) => {
387
461
  if (this.#server) {
462
+ // if server is running
388
463
  this.#server.close((err) => {
389
- if (err) return reject(err);
464
+ if (err && err.code !== "ERR_SERVER_NOT_RUNNING") return reject(err);
390
465
  console.log("Server closed.");
391
466
  resolve();
392
467
  });
393
468
  } else {
469
+ // server is not running
394
470
  resolve(); // Nothing to close
395
471
  }
396
472
  });
@@ -398,19 +474,23 @@ export default class Blog {
398
474
 
399
475
  /** Populates the blog's title and articles from a data object. */
400
476
  #applyBlogData(data) {
401
- this.articles = []; // Clear existing articles before loading new ones
477
+ this.#articles = []; // Clear existing articles before loading new ones
402
478
  this.#title = data.title;
403
- // Assuming the API returns an array of objects with title and content
479
+ // Assuming data contains a title and an array of articles with title and content
404
480
  if (data.articles && Array.isArray(data.articles)) {
405
481
  for (const articleData of data.articles) {
406
- const article = new Article(articleData.title, articleData.content, articleData.createdAt);
407
- article.id = articleData.id;
482
+ const article = new Article(
483
+ articleData.title,
484
+ articleData.content,
485
+ articleData.createdAt
486
+ );
487
+ article.id = articleData.id; // TODO x
408
488
  this.addArticle(article);
409
489
  }
410
490
  }
411
491
  }
412
492
 
413
- async loadFromAPI() {
493
+ async #loadFromAPI() {
414
494
  const data = await fetchData(this.#apiUrl);
415
495
  if (data) {
416
496
  this.#applyBlogData(data);
@@ -423,35 +503,40 @@ export default class Blog {
423
503
  const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
424
504
  const pathname = url.pathname;
425
505
 
426
- if(req.method === "GET") {
427
- if(pathname === "/api" || pathname === "/api/") {
428
- res.writeHead(200, { "Content-Type": "application/json" });
429
- const data = {
430
- title: this.title
506
+ if (req.method === "GET") {
507
+ if (pathname === "/api" || pathname === "/api/") {
508
+ res.writeHead(200, { "Content-Type": "application/json" });
509
+ const data = {
510
+ title: this.title,
511
+ };
512
+ res.end(JSON.stringify(data));
513
+ }
514
+ // GET all blog data
515
+ if (pathname === "/api/articles") {
516
+ // Use 'offset' param as startId (filter) to get items starting at ID
517
+ const pStartID = parseInt(url.searchParams.get("startID"));
518
+ const startID = !isNaN(pStartID) ? pStartID : null;
519
+ const pEndID = parseInt(url.searchParams.get("endID"));
520
+ const endID = !isNaN(pEndID) ? pEndID : null;
521
+ const limit = parseInt(url.searchParams.get("limit")) || 10;
522
+ // controller
523
+ res.writeHead(200, { "Content-Type": "application/json" });
524
+ const dbArticles = await this.#databaseModel.findAll(
525
+ limit,
526
+ 0,
527
+ startID,
528
+ endID
529
+ );
530
+ const responseData = {
531
+ title: this.title, // Keep the title from the original constant
532
+ articles: dbArticles,
533
+ };
534
+ res.end(JSON.stringify(responseData));
431
535
  }
432
- res.end(JSON.stringify(data));
433
- }
434
- // GET all blog data
435
- if (pathname === "/api/articles") {
436
- // Use 'offset' param as startId (filter) to get items starting at ID
437
- const pStartID = parseInt(url.searchParams.get("startID"));
438
- const startID = !isNaN(pStartID) ? pStartID : null;
439
- const pEndID = parseInt(url.searchParams.get("endID"));
440
- const endID = !isNaN(pEndID) ? pEndID : null;
441
- const limit = parseInt(url.searchParams.get("limit")) || 10;
442
- // controller
443
- res.writeHead(200, { "Content-Type": "application/json" });
444
- const dbArticles = await this.#databaseModel.findAll(limit, 0, startID, endID);
445
- const responseData = {
446
- title: this.title, // Keep the title from the original constant
447
- articles: dbArticles,
448
- };
449
- res.end(JSON.stringify(responseData));
450
- }
451
536
 
452
- // POST a new article
453
- } else if (req.method === "POST" && pathname === "/api/articles") {
454
- if (!this.isAuthenticated(req)) {
537
+ // POST a new article
538
+ } else if (req.method === "POST" && pathname === "/api/articles") {
539
+ if (!this.#isAuthenticated(req)) {
455
540
  res.writeHead(403, { "Content-Type": "application/json" });
456
541
  res.end(JSON.stringify({ error: "Forbidden" }));
457
542
  return;
@@ -481,7 +566,7 @@ export default class Blog {
481
566
  print() {
482
567
  const data = {
483
568
  title: this.title,
484
- articles: this.articles,
569
+ articles: this.#articles,
485
570
  };
486
571
  const markdown = formatMarkdown(data);
487
572
  console.log(markdown);
@@ -491,12 +576,12 @@ export default class Blog {
491
576
  async toHTML(loggedin) {
492
577
  const data = {
493
578
  title: this.title,
494
- articles: this.articles,
579
+ articles: this.#articles,
495
580
  loggedin,
496
- login: ""
581
+ login: "",
497
582
  };
498
583
 
499
- if(loggedin) data.login = `<a href="/logout">logout</a>`;
584
+ if (loggedin) data.login = `<a href="/logout">logout</a>`;
500
585
  else data.login = `<a href="/login">login</a>`;
501
586
 
502
587
  const html = formatHTML(data);
@@ -505,21 +590,21 @@ export default class Blog {
505
590
  }
506
591
 
507
592
  /**
508
- * Loads files, generates checksums and compiles sass files to css if anything changed
593
+ * read files, compare checksums, compile and write to public/styles.min.css
509
594
  * @param {string[]} files - Array of css/scss file paths to process.
510
595
  */
511
- async processStylesheets(files) {
512
- console.log("process stylesheets")
513
-
596
+ async #processStylesheets(files) {
597
+ console.log("process stylesheets");
598
+
514
599
  // Normalize input to array (handles string or array)
515
600
  // "file1.css" --> ["file1.css"]
516
601
  // ["file1.css", "file2.css",...]
517
602
  const fileList = Array.isArray(files) ? files : [files];
518
-
519
- const __filename = fileURLToPath(import.meta.url);
520
- const __dirname = path.dirname(__filename);
521
603
  const styleFiles = fileList.filter(
522
- (f) => typeof f === "string" && (f.endsWith(".scss") || f.endsWith(".css")) && !f.endsWith(".min.css")
604
+ (f) =>
605
+ typeof f === "string" &&
606
+ (f.endsWith(".scss") || f.endsWith(".css")) &&
607
+ !f.endsWith(".min.css")
523
608
  );
524
609
  //const scriptFiles = files.filter((f) => f.endsWith(".js") && !f.endsWith(".min.js"));
525
610
 
@@ -529,7 +614,7 @@ export default class Blog {
529
614
  const fileData = await Promise.all(
530
615
  styleFiles.sort().map(async (f) => {
531
616
  const content = await fs.promises.readFile(f, "utf-8");
532
- if(content == "") throw new Error("Invalid Filepath or empty file!");
617
+ if (content == "") throw new Error("Invalid Filepath or empty file!");
533
618
  return { path: f, content };
534
619
  })
535
620
  );
@@ -550,7 +635,7 @@ export default class Blog {
550
635
  if (currentHash !== this.#stylesHash) {
551
636
  console.log("Style assets have changed. Recompiling...");
552
637
  this.#stylesHash = currentHash;
553
-
638
+
554
639
  // Compile styles using the standalone script from build-styles.js
555
640
  this.compiledStyles = await compileStyles(fileData);
556
641
 
@@ -558,13 +643,13 @@ export default class Blog {
558
643
  const __filename = fileURLToPath(import.meta.url);
559
644
  const __dirname = path.dirname(__filename);
560
645
  const publicDir = path.join(__dirname, "public");
561
-
646
+
562
647
  await fs.promises.writeFile(
563
648
  path.join(publicDir, "styles.min.css"),
564
649
  this.compiledStyles + `\n/* source-hash: ${currentHash} */`
565
650
  );
566
651
  } else {
567
- console.log("styles are up-to-date")
652
+ console.log("styles are up-to-date");
568
653
  }
569
654
  }
570
655
  }