@lexho111/plainblog 0.2.1 → 0.2.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.
package/Article.js CHANGED
@@ -1,7 +1,8 @@
1
1
  export default class Article {
2
- constructor(title, content) {
2
+ constructor(title, content, createdAt) {
3
3
  this.title = title;
4
4
  this.content = content;
5
+ this.createdAt = createdAt;
5
6
  }
6
7
 
7
8
  getContent() {
@@ -14,4 +15,22 @@ export default class Article {
14
15
  contentShort += "...";
15
16
  return contentShort;
16
17
  }
18
+
19
+ getFormattedDate() {
20
+ const date = new Date(this.createdAt);
21
+ const year = date.getFullYear();
22
+ const month = String(date.getMonth() + 1).padStart(2, "0");
23
+ const day = String(date.getDate()).padStart(2, "0");
24
+ const hours = String(date.getHours()).padStart(2, "0");
25
+ const minutes = String(date.getMinutes()).padStart(2, "0");
26
+ return `${year}/${month}/${day} ${hours}:${minutes}`;
27
+ }
28
+
29
+ toHTML() {
30
+ return ` <article>
31
+ <h2>${this.title}</h2>
32
+ <span class="datetime">${this.getFormattedDate()}</span>
33
+ <p>${this.getContentShort()}</p>
34
+ </article>`;
35
+ }
17
36
  }
package/Blog.js CHANGED
@@ -146,6 +146,9 @@ export default class Blog {
146
146
  await this.#databaseModel.initialize();
147
147
  const dbTitle = await this.#databaseModel.getBlogTitle();
148
148
  const dbArticles = await this.#databaseModel.findAll();
149
+ //console.log(`articles: ${JSON.stringify(dbarticles)}`)
150
+ this.reloadScriptsStylesOnGET = true;
151
+ if(this.reloadScriptsStylesOnGET) console.log("reload scripts and styles on GET-Request");
149
152
  let title = "";
150
153
  if (this.title.length > 0) title = this.title; // use blog title if set
151
154
  else title = dbTitle;
@@ -185,7 +188,7 @@ export default class Blog {
185
188
  if (this.#databaseModel) await this.#databaseModel.save(newArticleData);
186
189
  if (this.#isExternalAPI) await postData(this.#apiUrl, newArticleData);
187
190
  // Add the article to the local list for immediate display
188
- this.addArticle(new Article(title, content));
191
+ this.articles.unshift(new Article(title, content));
189
192
  } catch (error) {
190
193
  console.error("Failed to post new article to API:", error);
191
194
  }
@@ -239,7 +242,7 @@ export default class Blog {
239
242
  // load articles
240
243
 
241
244
  // reload styles and scripts on (every) request
242
- //this.loadScripts(); this.loadStyles();
245
+ if(this.reloadScriptsStylesOnGET) this.loadScripts(); this.loadStyles();
243
246
 
244
247
  let loggedin = false;
245
248
  if (!this.isAuthenticated(req)) {
@@ -250,9 +253,14 @@ export default class Blog {
250
253
  loggedin = true;
251
254
  }
252
255
 
253
- const html = await this.toHTML(loggedin); // render this blog to HTML
254
- res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8" });
255
- res.end(html);
256
+ try {
257
+ const html = this.toHTML(loggedin); // render this blog to HTML
258
+ res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8" });
259
+ res.end(html);
260
+ } catch (err) {
261
+ res.writeHead(500, { "Content-Type": "text/plain" });
262
+ res.end("Internal Server Error");
263
+ }
256
264
  } else {
257
265
  // Error 404
258
266
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -291,7 +299,7 @@ export default class Blog {
291
299
  // Assuming the API returns an array of objects with title and content
292
300
  if (data.articles && Array.isArray(data.articles)) {
293
301
  for (const articleData of data.articles) {
294
- this.addArticle(new Article(articleData.title, articleData.content));
302
+ this.addArticle(new Article(articleData.title, articleData.content, articleData.createdAt));
295
303
  }
296
304
  }
297
305
  }
@@ -373,37 +381,17 @@ export default class Blog {
373
381
  }
374
382
 
375
383
  /** print markdown to the console */
376
- async print() {
384
+ print() {
377
385
  const data = {
378
386
  title: this.title,
379
387
  articles: this.articles,
380
388
  };
381
- //const markdown = formatMarkdown(data);
382
-
383
- return new Promise((resolve, reject) => {
384
- let markdownResult = "";
385
- pipeline(
386
- Readable.from([data]),
387
- new MapTransform((chunk) => formatMarkdown(chunk, this.scripts, this.styles)),
388
- new Writable({
389
- write(chunk, encoding, callback) {
390
- markdownResult += chunk.toString();
391
- callback();
392
- },
393
- }),
394
- (err) => {
395
- if (err) reject(err);
396
- else {
397
- console.log(markdownResult)
398
- resolve(markdownResult);
399
- }
400
- }
401
- );
402
- });
389
+ const markdown = formatMarkdown(data);
390
+ return markdown;
403
391
  }
404
392
 
405
393
  /** render this blog content to valid html */
406
- async toHTML(loggedin) {
394
+ toHTML(loggedin) {
407
395
  const data = {
408
396
  title: this.title,
409
397
  articles: this.articles,
@@ -413,28 +401,8 @@ export default class Blog {
413
401
 
414
402
  if(loggedin) data.login = `<a href="/logout">logout</a>`;
415
403
  else data.login = `<a href="/login">login</a>`;
416
-
417
- return new Promise((resolve, reject) => {
418
- let htmlResult = "";
419
- pipeline(
420
- Readable.from([data]),
421
- new MapTransform((chunk) => formatHTML(chunk, this.scripts, this.styles)),
422
- new MapTransform((chunk) => {
423
- const html = chunk.toString();
424
- if (validate(html)) return html;
425
- throw new Error("Error. Invalid HTML!");
426
- }),
427
- new Writable({
428
- write(chunk, encoding, callback) {
429
- htmlResult += chunk.toString();
430
- callback();
431
- },
432
- }),
433
- (err) => {
434
- if (err) reject(err);
435
- else resolve(htmlResult);
436
- }
437
- );
438
- });
404
+ const html = formatHTML(data, this.scripts, this.styles);
405
+ if (validate(html)) return html;
406
+ throw new Error("Error. Invalid HTML!");
439
407
  }
440
408
  }
package/Formatter.js CHANGED
@@ -32,15 +32,7 @@ export function formatHTML(data, script, style) {
32
32
  <div style="width: 500px;">
33
33
  ${form}
34
34
  <section class="grid">
35
- ${data.articles
36
- .map(
37
- (article) => `
38
- <article>
39
- <h2>${article.title}</h2>
40
- <p>${article.getContentShort()}</p>
41
- </article>`
42
- )
43
- .join("")}
35
+ ${data.articles.map((article) => article.toHTML()).join("")}
44
36
  </section>
45
37
  </div>
46
38
  </body>
@@ -52,9 +52,17 @@ export default class DatabaseModel {
52
52
  {
53
53
  title: DataTypes.STRING,
54
54
  content: DataTypes.TEXT,
55
+ createdAt: {
56
+ type: DataTypes.DATE,
57
+ defaultValue: DataTypes.NOW,
58
+ },
59
+ updatedAt: {
60
+ type: DataTypes.DATE,
61
+ defaultValue: DataTypes.NOW,
62
+ },
55
63
  },
56
64
  {
57
- timestamps: false, // Assuming you don't need createdAt/updatedAt for this simple model
65
+ timestamps: true,
58
66
  }
59
67
  );
60
68
 
@@ -83,8 +91,16 @@ export default class DatabaseModel {
83
91
  }
84
92
 
85
93
  // model
86
- async findAll() {
87
- return this.#Article.findAll();
94
+ async findAll(limit = 10, offset = 0) {
95
+ const options = {
96
+ order: [
97
+ ["createdAt", "DESC"],
98
+ ["id", "DESC"],
99
+ ],
100
+ limit,
101
+ offset,
102
+ };
103
+ return this.#Article.findAll(options);
88
104
  }
89
105
 
90
106
  async save(newArticle) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/styles.min.css CHANGED
@@ -1,2 +1,2 @@
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}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}body{background-color:#fdfdfd;font-family:Arial}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}
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}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}body{background-color:#fdfdfd;font-family:Arial}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}.datetime{color:#434343;font-style:italic}
2
2
  /*# sourceMappingURL=styles.min.css.map */
@@ -1 +1 @@
1
- {"version":3,"sources":["styles-compiled.css","styles.css"],"names":[],"mappings":"AAAA,MAIA,mBAAA,CAHA,YAAA,CAEA,UAAA,CADA,yBAGA,CACA,cAEA,mBAAA,CACA,iBAAA,CACA,WAAA,CACA,wBAAA,CAJA,cAKA,CAOA,cACA,aAAA,CACA,6BACA,CCtBA,KACA,wBAAA,CACA,iBACA,CAEA,MAEA,aAAA,CACA,cAAA,CAFA,yBAGA","file":"styles.min.css","sourcesContent":[".grid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 0.25rem;\n border: 0px solid black;\n}\n.grid article {\n padding: 0.25rem;\n border: 0px solid #ccc;\n border-radius: 4px;\n min-width: 0; /* Allow grid items to shrink */\n overflow-wrap: break-word; /* Break long words */\n}\n\nnav a {\n text-decoration: underline;\n color: rgb(59, 64, 193);\n font-size: 20px;\n}\nnav a:visited {\n color: rgb(59, 64, 193);\n text-decoration-color: rgb(59, 64, 193);\n}","body {\n background-color: rgb(253, 253, 253);\n font-family: Arial;\n}\n\nnav a {\n text-decoration: underline;\n color: rgb(59, 64, 193);\n font-size: 20px;\n}\n"]}
1
+ {"version":3,"sources":["styles-compiled.css","styles.css"],"names":[],"mappings":"AAAA,MAIA,mBAAA,CAHA,YAAA,CAEA,UAAA,CADA,yBAGA,CACA,cAEA,mBAAA,CACA,iBAAA,CACA,WAAA,CACA,wBAAA,CAJA,cAKA,CAOA,cACA,aAAA,CACA,6BACA,CCtBA,KACA,wBAAA,CACA,iBACA,CAEA,MAEA,aAAA,CACA,cAAA,CAFA,yBAGA,CAEA,UAEA,aAAA,CADA,iBAEA","file":"styles.min.css","sourcesContent":[".grid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 0.25rem;\n border: 0px solid black;\n}\n.grid article {\n padding: 0.25rem;\n border: 0px solid #ccc;\n border-radius: 4px;\n min-width: 0; /* Allow grid items to shrink */\n overflow-wrap: break-word; /* Break long words */\n}\n\nnav a {\n text-decoration: underline;\n color: rgb(59, 64, 193);\n font-size: 20px;\n}\nnav a:visited {\n color: rgb(59, 64, 193);\n text-decoration-color: rgb(59, 64, 193);\n}","body {\n background-color: rgb(253, 253, 253);\n font-family: Arial;\n}\n\nnav a {\n text-decoration: underline;\n color: rgb(59, 64, 193);\n font-size: 20px;\n}\n\n.datetime {\n font-style: italic;\n color: rgb(67, 67, 67);\n}\n"]}
@@ -105,6 +105,6 @@ describe("server test", () => {
105
105
  const responseData = await response.json();
106
106
  expect(responseData).toEqual(newArticle);
107
107
 
108
- expect(await blog.toHTML()).toContain(newArticle.content); // does blog contain my new article?
108
+ expect(blog.toHTML()).toContain(newArticle.content); // does blog contain my new article?
109
109
  });
110
110
  });