@lexho111/plainblog 0.4.2 → 0.5.0
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/.vscode/settings.json +2 -0
- package/Blog.js +117 -195
- package/Formatter.js +21 -13
- package/README.md +2 -38
- package/blog.db +0 -0
- package/blog.json +87 -1
- package/build-styles.js +3 -3
- package/model/DatabaseModel2.js +86 -0
- package/model/fileModel.js +15 -5
- package/package.json +11 -9
- package/public/print.min.css +2 -0
- package/{scripts.min.js → public/scripts.min.js} +1 -1
- package/public/styles.min.css +68 -0
- package/src/print.scss +76 -0
- package/src/styles.css +18 -0
- package/test/blog.test.js +24 -7
- package/test/model.test.js +7 -5
- package/test/server.test.js +2 -1
- package/test/styles.test.js +42 -44
- package/blog.db-journal +0 -0
- package/model/DatabaseModel.js +0 -139
- package/streams.js +0 -17
- package/styles.min.css +0 -1
- package/test_1767117403635.db +0 -0
- package/test_1767117781437.db +0 -0
- package/test_1767117944836.db +0 -0
- package/test_1767119505118.db +0 -0
- package/test_1767173684248.db +0 -0
- package/test_1767173763155.db +0 -0
- package/test_1767173821563.db +0 -0
- package/test_1767260493607.db +0 -0
- package/test_1767281040439.db +0 -0
- package/test_1767281442334.db +0 -0
- package/test_1767286038587.db +0 -0
- package/test_1767286127364.db +0 -0
- package/test_1767286366239.db +0 -0
- package/test_1767286503638.db +0 -0
- package/test_1767286637739.db +0 -0
- package/test_1767292219862.db +0 -0
- package/test_1767292355190.db +0 -0
- package/test_server_db.db +0 -0
- package/test_styles_1.db +0 -0
- package/test_styles_2.db +0 -0
- package/test_styles_3.db +0 -0
- package/test_styles_4.db +0 -0
- /package/{styles.min.css.map → public/styles.min.css.map} +0 -0
- /package/src/{loader.js → fetchData.js} +0 -0
package/blog.json
CHANGED
|
@@ -3,7 +3,93 @@
|
|
|
3
3
|
"articles": [
|
|
4
4
|
{
|
|
5
5
|
"title": "hello",
|
|
6
|
-
"content": "
|
|
6
|
+
"content": "hML`JfDS]ARPVAfsGdogseNVYPXTpZaJhKJkPoMTWA\\q[CkNsXYrUGyGHWTDZpByubZMf_Y\\bMELh[]afM^XMAFqvxuyWat^hqSBMIifvbTdHNOQP_rbwwlWnGvVbPi]jlTIixyAaaU_]ecJYTjr]FqLq`UY\\XoYqhmuY`rvWH]EZO`tBHZSan`mAuhnwpXgdRJtjtH[",
|
|
7
|
+
"createdAt": "2026-01-07T12:49:43.098Z"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"title": "hello",
|
|
11
|
+
"content": "HgcFKFWDMl]cxiCVVrgFddJaOQCSPF^KYkROxkLTmxSc^ZssZh`ENyOXZn_DtOQe^V_vbu^KRonZ]bGcqsP]FYMmY\\YLjWknhiUksXwIUsvtpfXqViiKJ`xcmScZRXfAdHshvFlEHvPXWOfZRRIDIOxAafPoMD[ZgVlFkl\\Uw]RfubZWQ]`IHIUOKea_RTO`KiZisSA[",
|
|
12
|
+
"createdAt": "2026-01-07T12:49:43.444Z"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"title": "Test Title from Jest",
|
|
16
|
+
"content": "This is the content of the test article.",
|
|
17
|
+
"createdAt": "2026-01-07T13:10:50.239Z"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "hello",
|
|
21
|
+
"content": "_\\jAuGsYByS]q\\QsnlqT\\]]TEDDalywtlmwmBZRlL[vtFdGqB\\[dlVx]A^[IV`KjhWU`DW`oorGwUlBU_IMCYoymIFi[`FpXrJfUmLZTRhoisxhXIcWqmk\\SPMV^]dvCuL_Pu^EAcMvBfadWFbUuSxFkjh\\KQyLnNWWsP`agQQkmU\\TAaOvYZAO\\FGtstSbaCJPXRQTO",
|
|
22
|
+
"createdAt": "2026-01-07T13:10:57.249Z"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"title": "hello",
|
|
26
|
+
"content": "HRkfwNRL_r\\DF]tyVBM[AMIjfMoBbohiqUf_T^]LwiJo[duXB`mQiRmmTkbMSJxvvHccfbXsdmKMbuYmxo[JGkwfCpqXSbhKiZID[vouGhbnLZ[wjkFFgm]fOaZcxoWduI`TNvin]\\dXoGW]YtueqSYPRLmGJswvUnyPHhfLUtf`]QNfM]hcvqqZasrQZVcXYjAnJxRP",
|
|
27
|
+
"createdAt": "2026-01-07T13:10:57.357Z"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"title": "hello",
|
|
31
|
+
"content": "UI\\KBodT[CgUg]L^WJ`Tuafq`rS]n\\T_eMqmBQqJVQisfABj^msS]u\\OEoJ]D^CtwE`UY[UbOICHcYPjNqNQ[LlwlJGPOoppSrkaEmDh_tm]CrrPGbBSPxgQQIZ^efcmITQQaAgT_vx[GUepg]LDagmTmihHbOIqHAa_NmRsX_EjxeE]EKhnHwFm]vFUpJTPFiDNU^BE",
|
|
32
|
+
"createdAt": "2026-01-07T13:17:47.559Z"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"title": "hello",
|
|
36
|
+
"content": "aMDnSSZDIZjbfrMlOcjMB\\CPup[GfoqHkLWAi]V_Ctkt[ekYRqIaufps[]TOMcw\\ojbVpPNv`lTTH\\[jbOcJXU[_tBSCsZW\\akiWuRf^mZGGrFxAq^oAdZjHwmJBjt^_GUXnHdXfjSrifiqXdAAdo]EM`aBFpJvyVLnLOfqrqoPrAIhRwoPHRy^lRCLyaS^VXChowHCn",
|
|
37
|
+
"createdAt": "2026-01-07T13:17:47.681Z"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "Test Title from Jest",
|
|
41
|
+
"content": "This is the content of the test article.",
|
|
42
|
+
"createdAt": "2026-01-07T13:18:50.764Z"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "hello",
|
|
46
|
+
"content": "[vL^GSDHJjBq^qdTMyyMm`SGhZhvgXsyTp[kYNkiBDavKevpIkSlWQr`bJaWyH]Qa`LQMKisdHeRvTKm`dviPLkLcJCtWbGPaDnNhJtEcEQM^MmP[RYERk\\uYeXruvPmJMjdvsOxA`btQtTmtBlOvrHLANJhcYRTAXmwOjLhp_SgkhWsB_pcIUQbd^X`RIWAdqUBPVmk",
|
|
47
|
+
"createdAt": "2026-01-07T13:18:52.387Z"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"title": "hello",
|
|
51
|
+
"content": "jRaJYEUFnMoEcjrWFREUuIQEoCtLoEuJSxJapEGpdPvq[GyrOqdSLY`VDlqSMTh\\BgybfwxfQQ_QqKkh\\YcEHYpA^W[ttGyGjA[TdZHn]]yoPWnFyFHJfAUkxkSM[RkQVqxAg_AGoJahaoOsNnoSGx^uM\\smWlWVDGvRcWVW\\rI_[lKnZEifvdPwkCcBRLXKGw[aNedN",
|
|
52
|
+
"createdAt": "2026-01-07T13:18:52.971Z"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"title": "Test Title from Jest",
|
|
56
|
+
"content": "This is the content of the test article.",
|
|
57
|
+
"createdAt": "2026-01-07T13:22:54.227Z"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"title": "Test Title from Jest",
|
|
61
|
+
"content": "This is the content of the test article.",
|
|
62
|
+
"createdAt": "2026-01-07T13:23:08.140Z"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"title": "hello",
|
|
66
|
+
"content": "M_RFGy[auVjFYypv^FdPqesIlcBkg]uoeIwMNLM]TOcDWKw[CFpsPpQWxSAXAupUdVsaiGJgLgIeuJEwBMXk`VG\\OoXIxCERYs`hqGRYGTnP]ZIrOSoJk[MFAxfVjJNMyZycryoRWr\\bhCXlurxgKPFSsTYbhlqZmrqaiJyvXpFsbiLQx^LBcZi`VqZSbjc`TNF_\\Eex",
|
|
67
|
+
"createdAt": "2026-01-07T13:26:02.437Z"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"title": "hello",
|
|
71
|
+
"content": "\\uRyVjar]vDZKHqRqJbEJpZbdKCWmQOGToNFQAqWdJQxSwmKTAGXywMDF`D\\nBA_eJxmFV^XtTuSGNJZTFjYvYibQIlgZdV[YXZpujmuWBgKNNBjrGZLVC[QlRZOwBdY^QPom\\b^WIIkeawsGBaafELGmfmTaIikRhCnCV_SYKEwogtqdfhkMkJslllumPyeGKqe\\cca",
|
|
72
|
+
"createdAt": "2026-01-07T13:26:02.816Z"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"title": "Test Title from Jest",
|
|
76
|
+
"content": "This is the content of the test article.",
|
|
77
|
+
"createdAt": "2026-01-07T13:26:07.866Z"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"title": "hello",
|
|
81
|
+
"content": "UecpUr`PFkpfKJ^V^q[T_lTr\\uP\\WFwcFtZgrdoao[japNpDycoAgWyELuIZtQroKxXSYdI]LrDTQSGqegWjgArWk[[eYXpy_OchWDywkus`GLcdwwr[qHoLINIdUoB^C]JcLfacOEJtMZOeWjRNbGcv\\XCfSAXUKIVqkPwJbaRTjlpGBxCGXxnchUhRoePYoLE]fexb",
|
|
82
|
+
"createdAt": "2026-01-07T13:45:46.304Z"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"title": "hello",
|
|
86
|
+
"content": "ef[SK^DuSJrKOlieeJHPLLoGgS[ZO\\gk_dWggGZchFewEnwspxJlhCWQ\\aTEKlDj\\a\\xRj\\uJANSkNLwrnPoOxJ[h]CrLppVtoeCEWPXRpCcoNFU`YA\\RPK\\dk\\Jp^fbdmC`Hpuf[ZYExseoPRbAYAaIolQ_mkJKRfrKsthmsBtCZdeQiBhY\\XAodKo_rOeQvtfNtDQB",
|
|
87
|
+
"createdAt": "2026-01-07T13:45:46.469Z"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"title": "Test Title from Jest",
|
|
91
|
+
"content": "This is the content of the test article.",
|
|
92
|
+
"createdAt": "2026-01-07T13:45:47.311Z"
|
|
7
93
|
}
|
|
8
94
|
]
|
|
9
95
|
}
|
package/build-styles.js
CHANGED
|
@@ -3,7 +3,7 @@ import { pathToFileURL } from "url";
|
|
|
3
3
|
import * as sass from "sass";
|
|
4
4
|
import postcss from "postcss";
|
|
5
5
|
import autoprefixer from "autoprefixer";
|
|
6
|
-
import cssnano from "cssnano";
|
|
6
|
+
//import cssnano from "cssnano";
|
|
7
7
|
|
|
8
8
|
// array of files or a single file
|
|
9
9
|
export async function compileStyles(fileData) {
|
|
@@ -39,7 +39,7 @@ export async function compileStyles(fileData) {
|
|
|
39
39
|
|
|
40
40
|
// 2. PostCSS (Autoprefixer + CSSNano)
|
|
41
41
|
if (combinedCss) {
|
|
42
|
-
const plugins = [autoprefixer()
|
|
42
|
+
const plugins = [autoprefixer()];
|
|
43
43
|
const result = await postcss(plugins).process(combinedCss, {
|
|
44
44
|
from: undefined,
|
|
45
45
|
});
|
|
@@ -57,7 +57,7 @@ export async function mergeStyles(...cssContents) {
|
|
|
57
57
|
const combinedCss = cssContents.join("\n");
|
|
58
58
|
|
|
59
59
|
if (combinedCss) {
|
|
60
|
-
const plugins = [autoprefixer()
|
|
60
|
+
const plugins = [autoprefixer()];
|
|
61
61
|
const result = await postcss(plugins).process(combinedCss, {
|
|
62
62
|
from: undefined,
|
|
63
63
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { save as saveToFile, load as loadFromFile } from "./fileModel.js";
|
|
2
|
+
|
|
3
|
+
export default class DatabaseModel {
|
|
4
|
+
filename = "blog.json";
|
|
5
|
+
|
|
6
|
+
//new DatabaseModel(this.database);
|
|
7
|
+
constructor(options) {}
|
|
8
|
+
|
|
9
|
+
async initialize() {}
|
|
10
|
+
|
|
11
|
+
async getBlogTitle() {
|
|
12
|
+
let blogTitle = "";
|
|
13
|
+
try {
|
|
14
|
+
await loadFromFile(this.filename, (title) => {
|
|
15
|
+
blogTitle = title;
|
|
16
|
+
});
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(err);
|
|
19
|
+
}
|
|
20
|
+
return blogTitle;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//findAll();
|
|
24
|
+
//save(newArticleData);
|
|
25
|
+
async save(newArticle) {
|
|
26
|
+
if (!newArticle.createdAt) {
|
|
27
|
+
newArticle.createdAt = new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let blogTitle = "";
|
|
31
|
+
let articles = [];
|
|
32
|
+
try {
|
|
33
|
+
await loadFromFile(this.filename, (t, a) => {
|
|
34
|
+
blogTitle = t;
|
|
35
|
+
articles = a || [];
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(err);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
articles.push(newArticle);
|
|
42
|
+
saveToFile(this.filename, { title: blogTitle, articles });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//updateBlogTitle(this.title);
|
|
46
|
+
async updateBlogTitle(newTitle) {
|
|
47
|
+
let articles = [];
|
|
48
|
+
try {
|
|
49
|
+
await loadFromFile(this.filename, (t, a) => {
|
|
50
|
+
//blogTitle = t;
|
|
51
|
+
articles = a || [];
|
|
52
|
+
});
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(err);
|
|
55
|
+
}
|
|
56
|
+
saveToFile(this.filename, { title: newTitle, articles });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//findAll(limit, 0, startID, endID);
|
|
60
|
+
async findAll(
|
|
61
|
+
limit = 4,
|
|
62
|
+
offset = 0,
|
|
63
|
+
startId = null,
|
|
64
|
+
endId = null,
|
|
65
|
+
order = "DESC"
|
|
66
|
+
) {
|
|
67
|
+
let dbArticles = [];
|
|
68
|
+
try {
|
|
69
|
+
await loadFromFile(this.filename, (title, articles) => {
|
|
70
|
+
if (Array.isArray(articles)) {
|
|
71
|
+
// Sort by createdAt
|
|
72
|
+
articles.sort((a, b) => {
|
|
73
|
+
const dateA = new Date(a.createdAt || 0);
|
|
74
|
+
const dateB = new Date(b.createdAt || 0);
|
|
75
|
+
return order === "DESC" ? dateB - dateA : dateA - dateB;
|
|
76
|
+
});
|
|
77
|
+
// Apply pagination
|
|
78
|
+
dbArticles = articles.slice(offset, offset + limit);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(err);
|
|
83
|
+
}
|
|
84
|
+
return dbArticles;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/model/fileModel.js
CHANGED
|
@@ -17,9 +17,19 @@ export async function save(filename, data) {
|
|
|
17
17
|
|
|
18
18
|
/** load blog content from file */
|
|
19
19
|
export async function load(filename, f) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
try {
|
|
21
|
+
const data = await fs.readFile(filename, "utf8");
|
|
22
|
+
const jsonData = JSON.parse(data);
|
|
23
|
+
const title = jsonData.title;
|
|
24
|
+
const articles = jsonData.articles;
|
|
25
|
+
f(title, articles);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err.code === "ENOENT") {
|
|
28
|
+
const defaultData = { title: "Blog", articles: [] };
|
|
29
|
+
await save(filename, defaultData);
|
|
30
|
+
f(defaultData.title, defaultData.articles);
|
|
31
|
+
} else {
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
25
35
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexho111/plainblog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A tool for creating and serving a minimalist, single-page blog.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"dev": "node index.js",
|
|
8
9
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
9
10
|
"lint": "eslint ."
|
|
10
11
|
},
|
|
@@ -17,19 +18,20 @@
|
|
|
17
18
|
"license": "ISC",
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"autoprefixer": "^10.4.23",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"child_process": "^1.0.2",
|
|
22
|
+
"crypto": "^1.0.1",
|
|
23
|
+
"fs": "^0.0.1-security",
|
|
24
|
+
"http": "^0.0.1-security",
|
|
25
|
+
"path": "^0.12.7",
|
|
24
26
|
"postcss": "^8.4.35",
|
|
25
27
|
"sass": "^1.97.1",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
+
"url": "^0.11.4",
|
|
29
|
+
"util": "^0.12.5"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
32
|
+
"dom-parser": "^1.1.5",
|
|
30
33
|
"eslint": "^9.8.0",
|
|
31
34
|
"eslint-plugin-jest": "^28.6.0",
|
|
32
|
-
"jest": "^29.7.0"
|
|
33
|
-
"dom-parser": "^1.1.5"
|
|
35
|
+
"jest": "^29.7.0"
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -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.
|
|
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,68 @@
|
|
|
1
|
+
body { font-family: Arial; } .grid {
|
|
2
|
+
border: 0 solid #000;
|
|
3
|
+
display: grid;
|
|
4
|
+
gap: 0.25rem;
|
|
5
|
+
grid-template-columns: 1fr;
|
|
6
|
+
}
|
|
7
|
+
.grid article {
|
|
8
|
+
border: 0 solid #ccc;
|
|
9
|
+
border-radius: 4px;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
overflow-wrap: break-word;
|
|
12
|
+
padding: 0.25rem;
|
|
13
|
+
}
|
|
14
|
+
.grid article h2 {
|
|
15
|
+
color: rgb(53, 53, 53);
|
|
16
|
+
margin-bottom: 5px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.grid article .datetime {
|
|
20
|
+
margin: 0;
|
|
21
|
+
color: rgb(117, 117, 117);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.grid article p {
|
|
25
|
+
margin-top: 10px;
|
|
26
|
+
margin-bottom: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
article a {
|
|
30
|
+
color: rgb(105, 105, 105);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
article a:visited {
|
|
34
|
+
color: rgb(105, 105, 105);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
h1 {
|
|
38
|
+
color: #696969;
|
|
39
|
+
}
|
|
40
|
+
nav a {
|
|
41
|
+
color: #3b40c1;
|
|
42
|
+
font-size: 20px;
|
|
43
|
+
text-decoration: underline;
|
|
44
|
+
}
|
|
45
|
+
nav a:visited {
|
|
46
|
+
color: #3b40c1;
|
|
47
|
+
text-decoration-color: #3b40c1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#wrapper {
|
|
51
|
+
max-width: 500px;
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Mobile Layout (screens smaller than 1000px) */
|
|
56
|
+
@media screen and (max-width: 1000px) {
|
|
57
|
+
* {
|
|
58
|
+
font-size: 4vw;
|
|
59
|
+
}
|
|
60
|
+
#wrapper {
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
width: 100%;
|
|
63
|
+
padding: 0 10px; /* Prevents text from touching the edges */
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* source-hash: bcb6644ec5b5c6f9685c9ad6c14ee551a6f908b8a2c372d3294e2d2e80d17fb7 */
|
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,21 @@ nav a:visited {
|
|
|
46
46
|
color: #3b40c1;
|
|
47
47
|
text-decoration-color: #3b40c1;
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
#wrapper {
|
|
51
|
+
max-width: 500px;
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Mobile Layout (screens smaller than 1000px) */
|
|
56
|
+
@media screen and (max-width: 1000px) {
|
|
57
|
+
* {
|
|
58
|
+
font-size: 4vw;
|
|
59
|
+
}
|
|
60
|
+
#wrapper {
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
width: 100%;
|
|
63
|
+
padding: 0 10px; /* Prevents text from touching the edges */
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
}
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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:black; color:white",
|
|
66
|
+
},
|
|
67
|
+
{ style: "body{ font-size: 1.2em; }", expected: "font-size: 1.2em" },
|
|
57
68
|
];
|
|
58
69
|
for (const style of styles) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 () => {
|
package/test/model.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import Blog from "../Blog.js";
|
|
|
2
2
|
import Article from "../Article.js";
|
|
3
3
|
import { fetchData, postData } from "../model/APIModel.js";
|
|
4
4
|
import { server } from "./simpleServer.js";
|
|
5
|
+
import { load as loadFromFile } from "../model/fileModel.js";
|
|
5
6
|
|
|
6
7
|
function generateRandomContent(length) {
|
|
7
8
|
let str = "";
|
|
@@ -26,18 +27,19 @@ describe("File Model test", () => {
|
|
|
26
27
|
const article = new Article("hello", content);
|
|
27
28
|
blog.addArticle(article);
|
|
28
29
|
|
|
29
|
-
await
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
await loadFromFile("blog.json", async (title) => {
|
|
31
|
+
expect(await blog.toHTML()).toContain(content);
|
|
32
|
+
expect(blog).toBeDefined();
|
|
33
|
+
});
|
|
33
34
|
});
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
describe("API Model test", () => {
|
|
37
|
-
beforeAll(() => {
|
|
38
|
+
beforeAll((done) => {
|
|
38
39
|
const port = 8081;
|
|
39
40
|
server.listen(port, () => {
|
|
40
41
|
console.log(`Simple server running at http://localhost:${port}/`);
|
|
42
|
+
done();
|
|
41
43
|
});
|
|
42
44
|
});
|
|
43
45
|
afterAll(() => {
|
package/test/server.test.js
CHANGED
|
@@ -104,7 +104,8 @@ describe("server test", () => {
|
|
|
104
104
|
|
|
105
105
|
expect(response.status).toBe(201);
|
|
106
106
|
const responseData = await response.json();
|
|
107
|
-
expect(responseData).toEqual(newArticle);
|
|
107
|
+
expect(responseData.title).toEqual(newArticle.title);
|
|
108
|
+
expect(responseData.content).toEqual(newArticle.content);
|
|
108
109
|
|
|
109
110
|
expect(await blog.toHTML()).toContain(newArticle.content); // does blog contain my new article?
|
|
110
111
|
});
|