@lexho111/plainblog 0.5.2 → 0.5.4
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 +22 -12
- package/README.md +17 -0
- package/blog.json +15 -0
- package/model/DatabaseModel3.js +35 -0
- package/model/{DatabaseModel2.js → FileAdapter.js} +14 -21
- package/package.json +1 -1
- package/public/styles.min.css +16 -111
- package/test/styles.test.js +0 -39
package/Blog.js
CHANGED
|
@@ -3,7 +3,7 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import { URLSearchParams } from "url";
|
|
5
5
|
import Article from "./Article.js";
|
|
6
|
-
import DatabaseModel from "./model/
|
|
6
|
+
import DatabaseModel from "./model/DatabaseModel3.js";
|
|
7
7
|
import { fetchData, postData } from "./model/APIModel.js";
|
|
8
8
|
import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js";
|
|
9
9
|
import pkg from "./package.json" with { type: "json" };
|
|
@@ -16,7 +16,14 @@ const execPromise = promisify(exec);
|
|
|
16
16
|
|
|
17
17
|
export default class Blog {
|
|
18
18
|
constructor() {
|
|
19
|
-
this.
|
|
19
|
+
this.database = {
|
|
20
|
+
type: "file",
|
|
21
|
+
username: "user",
|
|
22
|
+
password: "password",
|
|
23
|
+
host: "localhost",
|
|
24
|
+
dbname: "blog.json",
|
|
25
|
+
};
|
|
26
|
+
this.#title = "";
|
|
20
27
|
this.articles = [];
|
|
21
28
|
this.filename = null;
|
|
22
29
|
this.#server = null;
|
|
@@ -26,14 +33,6 @@ export default class Blog {
|
|
|
26
33
|
this.compiledStyles = "";
|
|
27
34
|
this.compiledScripts = "";
|
|
28
35
|
this.reloadStylesOnGET = false;
|
|
29
|
-
|
|
30
|
-
this.database = {
|
|
31
|
-
type: "file",
|
|
32
|
-
username: "user",
|
|
33
|
-
password: "password",
|
|
34
|
-
host: "localhost",
|
|
35
|
-
dbname: "blog.json",
|
|
36
|
-
};
|
|
37
36
|
this.sessions = new Set();
|
|
38
37
|
|
|
39
38
|
const version = pkg.version;
|
|
@@ -65,7 +64,9 @@ export default class Blog {
|
|
|
65
64
|
|
|
66
65
|
set title(t) {
|
|
67
66
|
this.#title = t;
|
|
68
|
-
this.#databaseModel
|
|
67
|
+
if (!this.#databaseModel) {
|
|
68
|
+
this.#databaseModel = new DatabaseModel(this.database);
|
|
69
|
+
}
|
|
69
70
|
console.log(`connected to database`);
|
|
70
71
|
if(t != this.#title && t.length == 0)
|
|
71
72
|
this.#databaseModel.updateBlogTitle(t);
|
|
@@ -79,6 +80,13 @@ export default class Blog {
|
|
|
79
80
|
this.#password = x;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
setDatabaseAdapter(adapter) {
|
|
84
|
+
if (!this.#databaseModel) {
|
|
85
|
+
this.#databaseModel = new DatabaseModel(this.database);
|
|
86
|
+
}
|
|
87
|
+
this.#databaseModel.setDatabaseAdapter(adapter);
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
set style(style) {
|
|
83
91
|
this.styles += style;
|
|
84
92
|
}
|
|
@@ -196,7 +204,9 @@ export default class Blog {
|
|
|
196
204
|
await this.loadFromAPI();
|
|
197
205
|
} else {
|
|
198
206
|
console.log(`database: ${this.database.type}`);
|
|
199
|
-
this.#databaseModel
|
|
207
|
+
if (!this.#databaseModel) {
|
|
208
|
+
this.#databaseModel = new DatabaseModel(this.database);
|
|
209
|
+
}
|
|
200
210
|
console.log(`connected to database`);
|
|
201
211
|
await this.#databaseModel.initialize();
|
|
202
212
|
const dbTitle = await this.#databaseModel.getBlogTitle();
|
package/README.md
CHANGED
|
@@ -50,6 +50,23 @@ blog.stylesheetPath = "path/to/my/styles.css";
|
|
|
50
50
|
blog.startServer(8080);
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
### use sass compiled stylesheets
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
import * as sass from "sass";
|
|
57
|
+
import fs from "node:fs";
|
|
58
|
+
|
|
59
|
+
const compiled = sass.compile( "stylesheets/styles.scss");
|
|
60
|
+
fs.writeFile("stylesheets_compiled/styles.css", compiled.css, (err) => {
|
|
61
|
+
if (err) console.error(err);
|
|
62
|
+
});
|
|
63
|
+
const blog = new Blog();
|
|
64
|
+
blog.stylesheetPath = "stylesheets_compiled/styles.css";
|
|
65
|
+
blog.title = "My Blog";
|
|
66
|
+
await blog.init();
|
|
67
|
+
blog.startServer(8080);
|
|
68
|
+
```
|
|
69
|
+
|
|
53
70
|
print your blog articles in markdown
|
|
54
71
|
|
|
55
72
|
```
|
package/blog.json
CHANGED
|
@@ -90,6 +90,21 @@
|
|
|
90
90
|
"title": "Test Title from Jest",
|
|
91
91
|
"content": "This is the content of the test article.",
|
|
92
92
|
"createdAt": "2026-01-07T13:45:47.311Z"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"title": "Test Title from Jest",
|
|
96
|
+
"content": "This is the content of the test article.",
|
|
97
|
+
"createdAt": "2026-01-07T18:19:21.946Z"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"title": "Test Title from Jest",
|
|
101
|
+
"content": "This is the content of the test article.",
|
|
102
|
+
"createdAt": "2026-01-07T18:21:43.833Z"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"title": "Test Title from Jest",
|
|
106
|
+
"content": "This is the content of the test article.",
|
|
107
|
+
"createdAt": "2026-01-07T18:24:28.968Z"
|
|
93
108
|
}
|
|
94
109
|
]
|
|
95
110
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import FileAdapter from "./FileAdapter.js";
|
|
2
|
+
|
|
3
|
+
export default class DatabaseModel {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
console.log(JSON.stringify(options));
|
|
6
|
+
if (options.type === "file") {
|
|
7
|
+
this.adapter = new FileAdapter(options);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async setDatabaseAdapter(adapter) {
|
|
12
|
+
this.adapter = adapter;
|
|
13
|
+
console.log("adapter is set");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async initialize() {
|
|
17
|
+
if (this.adapter) return this.adapter.initialize();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async getBlogTitle() {
|
|
21
|
+
return await this.adapter.getBlogTitle();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async save(newArticle) {
|
|
25
|
+
return this.adapter.save(newArticle);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async updateBlogTitle(newTitle) {
|
|
29
|
+
return this.adapter.updateBlogTitle(newTitle);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async findAll(limit, offset, startId, endId, order) {
|
|
33
|
+
return this.adapter.findAll(limit, offset, startId, endId, order);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { save as saveToFile, load as loadFromFile } from "./fileModel.js";
|
|
2
2
|
|
|
3
|
-
export default class
|
|
4
|
-
|
|
3
|
+
export default class FileAdapter {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.filename = "blog.json";
|
|
6
|
+
}
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
async initialize() {
|
|
9
|
+
console.log("file adapter init");
|
|
10
|
+
}
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
test() {
|
|
13
|
+
console.log("hello from adapter!");
|
|
14
|
+
}
|
|
10
15
|
|
|
11
16
|
async getBlogTitle() {
|
|
12
|
-
let blogTitle = "";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.error(err);
|
|
19
|
-
}
|
|
20
|
-
return blogTitle;
|
|
17
|
+
let blogTitle = "TestBlog";
|
|
18
|
+
return new Promise((res, rej) => {
|
|
19
|
+
res(blogTitle);
|
|
20
|
+
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//findAll();
|
|
24
|
-
//save(newArticleData);
|
|
25
23
|
async save(newArticle) {
|
|
26
24
|
if (!newArticle.createdAt) {
|
|
27
25
|
newArticle.createdAt = new Date().toISOString();
|
|
@@ -42,12 +40,10 @@ export default class DatabaseModel {
|
|
|
42
40
|
saveToFile(this.filename, { title: blogTitle, articles });
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
//updateBlogTitle(this.title);
|
|
46
43
|
async updateBlogTitle(newTitle) {
|
|
47
44
|
let articles = [];
|
|
48
45
|
try {
|
|
49
46
|
await loadFromFile(this.filename, (t, a) => {
|
|
50
|
-
//blogTitle = t;
|
|
51
47
|
articles = a || [];
|
|
52
48
|
});
|
|
53
49
|
} catch (err) {
|
|
@@ -56,7 +52,6 @@ export default class DatabaseModel {
|
|
|
56
52
|
saveToFile(this.filename, { title: newTitle, articles });
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
//findAll(limit, 0, startID, endID);
|
|
60
55
|
async findAll(
|
|
61
56
|
limit = 4,
|
|
62
57
|
offset = 0,
|
|
@@ -68,13 +63,11 @@ export default class DatabaseModel {
|
|
|
68
63
|
try {
|
|
69
64
|
await loadFromFile(this.filename, (title, articles) => {
|
|
70
65
|
if (Array.isArray(articles)) {
|
|
71
|
-
// Sort by createdAt
|
|
72
66
|
articles.sort((a, b) => {
|
|
73
67
|
const dateA = new Date(a.createdAt || 0);
|
|
74
68
|
const dateB = new Date(b.createdAt || 0);
|
|
75
69
|
return order === "DESC" ? dateB - dateA : dateA - dateB;
|
|
76
70
|
});
|
|
77
|
-
// Apply pagination
|
|
78
71
|
dbArticles = articles.slice(offset, offset + limit);
|
|
79
72
|
}
|
|
80
73
|
});
|
package/package.json
CHANGED
package/public/styles.min.css
CHANGED
|
@@ -1,126 +1,31 @@
|
|
|
1
|
-
* {
|
|
2
|
-
margin: 0;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
1
|
body {
|
|
6
|
-
color:
|
|
7
|
-
|
|
2
|
+
background-color: rgb(253, 253, 253);
|
|
3
|
+
font-family: Arial;
|
|
8
4
|
}
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
letter-spacing: 0.1em;
|
|
6
|
+
nav a {
|
|
7
|
+
text-decoration: underline;
|
|
8
|
+
color: rgb(59, 64, 193);
|
|
9
|
+
font-size: 20px;
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
.datetime {
|
|
13
|
+
font-style: normal;
|
|
14
|
+
color: rgb(67, 67, 67);
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
grid-template-columns: 1fr;
|
|
25
|
-
gap: 0.25rem;
|
|
26
|
-
border: 0px solid lightgray;
|
|
27
|
-
}
|
|
28
|
-
.grid article h2 {
|
|
29
|
-
color: hsl(100, 59%, 53%);
|
|
17
|
+
h2 {
|
|
18
|
+
margin: 0;
|
|
30
19
|
margin-bottom: 5px;
|
|
31
|
-
}
|
|
32
|
-
.grid article {
|
|
33
|
-
padding: 0.25rem;
|
|
34
|
-
border: 0px solid #ccc;
|
|
35
|
-
border-radius: 4px;
|
|
36
|
-
min-width: 0; /* Allow grid items to shrink */
|
|
37
|
-
overflow-wrap: break-word; /* Break long words */
|
|
38
|
-
}
|
|
39
|
-
.grid article .datetime {
|
|
40
20
|
color: darkgray;
|
|
41
|
-
font-style: italic;
|
|
42
|
-
}
|
|
43
|
-
.grid article p {
|
|
44
|
-
margin-top: 12px;
|
|
45
|
-
margin-bottom: 0;
|
|
46
|
-
}
|
|
47
|
-
.grid article a:link {
|
|
48
|
-
color: hsl(100, 59%, 53%);
|
|
49
|
-
}
|
|
50
|
-
.grid article a:visited {
|
|
51
|
-
color: hsl(100, 59%, 53%);
|
|
52
|
-
text-decoration-color: #fcfcfc;
|
|
53
|
-
}
|
|
54
|
-
.grid article a:hover {
|
|
55
|
-
color: hsl(100, 59%, 53%);
|
|
56
|
-
background-color: #fcfcfc;
|
|
57
|
-
}
|
|
58
|
-
.grid article a:active {
|
|
59
|
-
color: hsl(100, 59%, 53%);
|
|
60
21
|
}
|
|
61
22
|
|
|
62
|
-
|
|
63
|
-
|
|
23
|
+
p {
|
|
24
|
+
margin-top: 10px;
|
|
64
25
|
}
|
|
65
26
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
background-color: #fcfcfc;
|
|
69
|
-
}
|
|
70
|
-
nav a {
|
|
71
|
-
text-decoration: underline;
|
|
72
|
-
color: #fcfcfc;
|
|
73
|
-
background-color: hsl(100, 59%, 53%);
|
|
74
|
-
font-size: 20px;
|
|
75
|
-
padding: 5px;
|
|
76
|
-
padding-left: 15px;
|
|
77
|
-
padding-right: 15px;
|
|
78
|
-
border-bottom: 4px solid rgb(104, 104, 104);
|
|
79
|
-
border-right: 4px solid rgb(104, 104, 104);
|
|
80
|
-
}
|
|
81
|
-
nav a:link {
|
|
82
|
-
color: #fcfcfc;
|
|
83
|
-
}
|
|
84
|
-
nav a:visited {
|
|
85
|
-
color: #fcfcfc;
|
|
86
|
-
text-decoration-color: #fcfcfc;
|
|
87
|
-
}
|
|
88
|
-
nav a:hover {
|
|
89
|
-
color: hsl(100, 59%, 53%);
|
|
90
|
-
background-color: #fcfcfc;
|
|
91
|
-
}
|
|
92
|
-
nav a:active {
|
|
93
|
-
color: #fcfcfc;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#header {
|
|
97
|
-
background: linear-gradient(#fcfcfc, hsl(235, 22%, 73%));
|
|
98
|
-
height: 200px;
|
|
99
|
-
height: 400px;
|
|
100
|
-
}
|
|
101
|
-
#header h1 {
|
|
102
|
-
color: hsl(100, 59%, 53%);
|
|
103
|
-
margin-left: 10px;
|
|
104
|
-
}
|
|
105
|
-
#header img {
|
|
106
|
-
width: 1290px;
|
|
107
|
-
height: 300px;
|
|
108
|
-
margin-left: 10px;
|
|
27
|
+
span {
|
|
28
|
+
margin: 0;
|
|
109
29
|
}
|
|
110
30
|
|
|
111
|
-
/*
|
|
112
|
-
@media screen and (max-width: 1000px) {
|
|
113
|
-
* {
|
|
114
|
-
font-size: 4vw;
|
|
115
|
-
}
|
|
116
|
-
#header h1 {
|
|
117
|
-
font-size: 1em;
|
|
118
|
-
}
|
|
119
|
-
#wrapper {
|
|
120
|
-
max-width: 100%;
|
|
121
|
-
width: 100%;
|
|
122
|
-
padding: 0 10px; /* Prevents text from touching the edges */
|
|
123
|
-
box-sizing: border-box;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/* source-hash: 477a9b23fb6307399e67090d39d6a5e4c322550f5fcdeddc6ce28e4382038f5d */
|
|
31
|
+
/* source-hash: fa9deb7a7f0781f463cd3e8fd3c3ceddec518535b0a6d13af7309ef9a2f76c32 */
|
package/test/styles.test.js
CHANGED
|
@@ -8,45 +8,6 @@ const __dirname = path.dirname(__filename);
|
|
|
8
8
|
const publicDir = path.join(__dirname, "../public");
|
|
9
9
|
|
|
10
10
|
describe("Blog Stylesheet Test", () => {
|
|
11
|
-
it("should load and compile the sass stylesheet (.scss) correctly", async () => {
|
|
12
|
-
const blog = new Blog();
|
|
13
|
-
blog.title = "My Blog";
|
|
14
|
-
blog.database.dbname = "test_styles_1";
|
|
15
|
-
|
|
16
|
-
blog.stylesheetPath = "test/stylesheets/styles.scss";
|
|
17
|
-
|
|
18
|
-
await blog.init();
|
|
19
|
-
|
|
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");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("should load and compile the sass stylesheet (.scss) correctly [Array]", async () => {
|
|
32
|
-
const blog = new Blog();
|
|
33
|
-
blog.title = "My Blog";
|
|
34
|
-
blog.database.dbname = "test_styles_2";
|
|
35
|
-
|
|
36
|
-
blog.stylesheetPath = ["test/stylesheets/styles.scss"];
|
|
37
|
-
|
|
38
|
-
await blog.init();
|
|
39
|
-
const publicCSS = await fs.promises.readFile(
|
|
40
|
-
path.join(publicDir, "styles.min.css"),
|
|
41
|
-
"utf8"
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
expect(publicCSS).toContain(".grid");
|
|
45
|
-
expect(publicCSS).toContain("article");
|
|
46
|
-
expect(publicCSS).toContain("nav");
|
|
47
|
-
expect(publicCSS).toContain("nav a:visited");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
11
|
it("should load the stylesheet (.css) file", async () => {
|
|
51
12
|
const filepath = path.join(__dirname, "stylesheets/styles.css");
|
|
52
13
|
|