@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 +20 -1
- package/Blog.js +21 -53
- package/Formatter.js +1 -9
- package/model/DatabaseModel.js +19 -3
- package/package.json +1 -1
- package/styles.min.css +1 -1
- package/styles.min.css.map +1 -1
- package/test/server.test.js +1 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
384
|
+
print() {
|
|
377
385
|
const data = {
|
|
378
386
|
title: this.title,
|
|
379
387
|
articles: this.articles,
|
|
380
388
|
};
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
-
|
|
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>
|
package/model/DatabaseModel.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
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 */
|
package/styles.min.css.map
CHANGED
|
@@ -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"]}
|
package/test/server.test.js
CHANGED
|
@@ -105,6 +105,6 @@ describe("server test", () => {
|
|
|
105
105
|
const responseData = await response.json();
|
|
106
106
|
expect(responseData).toEqual(newArticle);
|
|
107
107
|
|
|
108
|
-
expect(
|
|
108
|
+
expect(blog.toHTML()).toContain(newArticle.content); // does blog contain my new article?
|
|
109
109
|
});
|
|
110
110
|
});
|