@lexho111/plainblog 0.4.2 → 0.4.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/Blog.js +104 -167
- package/Formatter.js +17 -12
- package/blog.db +0 -0
- package/package.json +3 -3
- package/public/print.min.css +2 -0
- package/{scripts.min.js → public/scripts.min.js} +1 -1
- package/public/styles.min.css +2 -0
- package/src/print.scss +76 -0
- package/src/styles.css +12 -0
- package/test/blog.test.js +24 -7
- package/test/styles.test.js +42 -44
- package/{test_1767119505118.db → test_1767619969536.db} +0 -0
- package/{test_1767117403635.db → test_1767620053052.db} +0 -0
- package/{test_1767117781437.db → test_1767621184498.db} +0 -0
- package/{test_1767117944836.db → test_1767623166288.db} +0 -0
- package/test_1767623750051.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/blog.db-journal +0 -0
- package/styles.min.css +0 -1
- 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/{styles.min.css.map → public/styles.min.css.map} +0 -0
- /package/src/{loader.js → fetchData.js} +0 -0
package/Blog.js
CHANGED
|
@@ -7,7 +7,7 @@ import Article from "./Article.js";
|
|
|
7
7
|
import DatabaseModel from "./model/DatabaseModel.js";
|
|
8
8
|
import { fetchData, postData } from "./model/APIModel.js";
|
|
9
9
|
import { save as saveToFile, load as loadFromFile } from "./model/fileModel.js";
|
|
10
|
-
import { formatHTML, formatMarkdown, validate } from "./Formatter.js";
|
|
10
|
+
import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js";
|
|
11
11
|
import pkg from "./package.json" with { type: "json" };
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
@@ -28,6 +28,7 @@ export default class Blog {
|
|
|
28
28
|
this.scripts = "";
|
|
29
29
|
this.compiledStyles = "";
|
|
30
30
|
this.compiledScripts = "";
|
|
31
|
+
this.reloadStylesOnGET = false;
|
|
31
32
|
|
|
32
33
|
this.database = {
|
|
33
34
|
type: "sqlite",
|
|
@@ -42,63 +43,6 @@ export default class Blog {
|
|
|
42
43
|
console.log(`version: ${version}`);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
/*loadScripts() {
|
|
46
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
47
|
-
const __dirname = path.dirname(__filename);
|
|
48
|
-
try {
|
|
49
|
-
const content = fs.readFileSync(
|
|
50
|
-
path.join(__dirname, "scripts.min.js"),
|
|
51
|
-
"utf-8"
|
|
52
|
-
);
|
|
53
|
-
const match = content.match(/\/\* source-hash: ([a-f0-9]+) \*\//);
|
|
54
|
-
if (match) {
|
|
55
|
-
this.#scriptsHash = match[1];
|
|
56
|
-
this.compiledScripts = content.replace(match[0], "").trim();
|
|
57
|
-
} else {
|
|
58
|
-
this.compiledScripts = content;
|
|
59
|
-
this.#scriptsHash = "";
|
|
60
|
-
}
|
|
61
|
-
} catch(err) {
|
|
62
|
-
this.compiledScripts = "";
|
|
63
|
-
this.#scriptsHash = "";
|
|
64
|
-
}
|
|
65
|
-
}*/
|
|
66
|
-
|
|
67
|
-
loadStyles() {
|
|
68
|
-
console.log("load styles")
|
|
69
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
70
|
-
const __dirname = path.dirname(__filename);
|
|
71
|
-
try {
|
|
72
|
-
const content = fs.readFileSync(
|
|
73
|
-
path.join(__dirname, "styles.min.css"),
|
|
74
|
-
"utf-8"
|
|
75
|
-
);
|
|
76
|
-
const match = content.match(/\/\* source-hash: ([a-f0-9]+) \*\//);
|
|
77
|
-
if (match) {
|
|
78
|
-
this.#stylesHash = match[1];
|
|
79
|
-
this.compiledStyles = content.replace(match[0], "").trim();
|
|
80
|
-
} else {
|
|
81
|
-
this.compiledStyles = content;
|
|
82
|
-
this.#stylesHash = "";
|
|
83
|
-
}
|
|
84
|
-
} catch(err) {
|
|
85
|
-
this.compiledStyles = "";
|
|
86
|
-
this.#stylesHash = "";
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
loadScripts() {
|
|
91
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
92
|
-
const __dirname = path.dirname(__filename);
|
|
93
|
-
try {
|
|
94
|
-
this.scripts = fs.readFileSync(path.join(__dirname, "scripts.min.js"), "utf-8");
|
|
95
|
-
//console.log(this.scripts)
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.error(err);
|
|
98
|
-
this.scripts = "";
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
46
|
// Private fields
|
|
103
47
|
#server = null;
|
|
104
48
|
#password = null;
|
|
@@ -108,6 +52,7 @@ export default class Blog {
|
|
|
108
52
|
#title = "";
|
|
109
53
|
#stylesHash = "";
|
|
110
54
|
#scriptsHash = "";
|
|
55
|
+
#stylesheetPath = "";
|
|
111
56
|
|
|
112
57
|
setTitle(title) {
|
|
113
58
|
this.#title = title;
|
|
@@ -138,7 +83,8 @@ export default class Blog {
|
|
|
138
83
|
}
|
|
139
84
|
|
|
140
85
|
set stylesheetPath(files) {
|
|
141
|
-
this
|
|
86
|
+
this.#stylesheetPath = files;
|
|
87
|
+
console.log(`this.#stylesheetPath: ${this.#stylesheetPath}`)
|
|
142
88
|
}
|
|
143
89
|
|
|
144
90
|
addArticle(article) {
|
|
@@ -170,7 +116,7 @@ export default class Blog {
|
|
|
170
116
|
res.end();
|
|
171
117
|
} else {
|
|
172
118
|
res.writeHead(401, { "Content-Type": "text/html" });
|
|
173
|
-
res.end(
|
|
119
|
+
res.end(`${header("My Blog")}
|
|
174
120
|
<body>
|
|
175
121
|
<h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
|
|
176
122
|
<input type="password" name="password" placeholder="Password" />
|
|
@@ -197,11 +143,51 @@ export default class Blog {
|
|
|
197
143
|
/** initializes database */
|
|
198
144
|
async init() {
|
|
199
145
|
//await this.buildFrontend();
|
|
200
|
-
this.loadStyles();
|
|
201
|
-
this.loadScripts();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
146
|
+
//this.loadStyles();
|
|
147
|
+
//this.loadScripts();
|
|
148
|
+
if(this.#stylesheetPath != null) {
|
|
149
|
+
await this.processStylesheets(this.#stylesheetPath);
|
|
150
|
+
}
|
|
151
|
+
if(!this.#stylesheetPath) {
|
|
152
|
+
// compile and merge hardcoded styles with src/styles.css and write to file "styles.min.css"
|
|
153
|
+
// which will be imported by webbrowser via '<link rel="stylesheet" href="styles.min.css"...'
|
|
154
|
+
|
|
155
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
156
|
+
const __dirname = path.dirname(__filename);
|
|
157
|
+
const srcStylePath = path.join(__dirname, "src", "styles.css");
|
|
158
|
+
const publicStylePath = path.join(__dirname, "public", "styles.min.css");
|
|
159
|
+
|
|
160
|
+
let publicHash = null;
|
|
161
|
+
try {
|
|
162
|
+
const publicCSS = await fs.promises.readFile(publicStylePath, "utf8");
|
|
163
|
+
const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
|
|
164
|
+
if (match) {
|
|
165
|
+
publicHash = match[1];
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// public/styles.min.css doesn't exist, will be created.
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let srcStyles = "";
|
|
172
|
+
try {
|
|
173
|
+
srcStyles = await fs.promises.readFile(srcStylePath, "utf8");
|
|
174
|
+
} catch (err) {
|
|
175
|
+
// ignore if src/styles.css doesn't exist
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const combinedStyles = this.styles + srcStyles;
|
|
179
|
+
const srcHash = crypto.createHash("sha256").update(combinedStyles).digest("hex");
|
|
180
|
+
|
|
181
|
+
if (srcHash !== publicHash) {
|
|
182
|
+
console.log("Styles have changed. Recompiling...");
|
|
183
|
+
const finalStyles = await mergeStyles(this.styles, srcStyles);
|
|
184
|
+
try {
|
|
185
|
+
await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
|
|
186
|
+
await fs.promises.writeFile(publicStylePath, finalStyles + `\n/* source-hash: ${srcHash} */`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error("Failed to write styles to public folder:", err);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
205
191
|
}
|
|
206
192
|
if (this.#isExternalAPI) {
|
|
207
193
|
console.log("external API");
|
|
@@ -218,7 +204,6 @@ export default class Blog {
|
|
|
218
204
|
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()));
|
|
219
205
|
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()));
|
|
220
206
|
}
|
|
221
|
-
this.reloadStylesOnGET = false;
|
|
222
207
|
if(this.reloadStylesOnGET) console.log("reload scripts and styles on GET-Request");
|
|
223
208
|
let title = "";
|
|
224
209
|
if (this.#title != null && this.#title.length > 0) title = this.#title; // use blog title if set
|
|
@@ -280,7 +265,7 @@ export default class Blog {
|
|
|
280
265
|
if (req.url === "/login") {
|
|
281
266
|
if (req.method === "GET") {
|
|
282
267
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
283
|
-
res.end(
|
|
268
|
+
res.end(`${header("My Blog")}
|
|
284
269
|
<body>
|
|
285
270
|
<h1>Login</h1><form method="POST">
|
|
286
271
|
<input type="password" name="password" placeholder="Password" />
|
|
@@ -312,11 +297,8 @@ export default class Blog {
|
|
|
312
297
|
|
|
313
298
|
// reload styles and scripts on (every) request
|
|
314
299
|
if(this.reloadStylesOnGET) {
|
|
315
|
-
if (this
|
|
316
|
-
await this.
|
|
317
|
-
} else {
|
|
318
|
-
this.loadStyles();
|
|
319
|
-
this.loadScripts();
|
|
300
|
+
if (this.#stylesheetPath) {
|
|
301
|
+
await this.processStylesheets(this.#stylesheetPath);
|
|
320
302
|
}
|
|
321
303
|
}
|
|
322
304
|
|
|
@@ -339,6 +321,39 @@ export default class Blog {
|
|
|
339
321
|
res.end("Internal Server Error");
|
|
340
322
|
}
|
|
341
323
|
} else {
|
|
324
|
+
// Try to serve static files from public folder
|
|
325
|
+
try {
|
|
326
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
327
|
+
const __dirname = path.dirname(__filename);
|
|
328
|
+
const publicDir = path.join(__dirname, "public");
|
|
329
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);
|
|
330
|
+
const filePath = path.join(publicDir, parsedUrl.pathname);
|
|
331
|
+
|
|
332
|
+
if (filePath.startsWith(publicDir)) {
|
|
333
|
+
const stats = await fs.promises.stat(filePath);
|
|
334
|
+
if (stats.isFile()) {
|
|
335
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
336
|
+
const mimeTypes = {
|
|
337
|
+
".html": "text/html",
|
|
338
|
+
".js": "text/javascript",
|
|
339
|
+
".css": "text/css",
|
|
340
|
+
".json": "application/json",
|
|
341
|
+
".png": "image/png",
|
|
342
|
+
".jpg": "image/jpeg",
|
|
343
|
+
".gif": "image/gif",
|
|
344
|
+
".svg": "image/svg+xml",
|
|
345
|
+
".ico": "image/x-icon",
|
|
346
|
+
};
|
|
347
|
+
const contentType = mimeTypes[ext] || "application/octet-stream";
|
|
348
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
349
|
+
fs.createReadStream(filePath).pipe(res);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
// Continue to 404
|
|
355
|
+
}
|
|
356
|
+
|
|
342
357
|
// Error 404
|
|
343
358
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
344
359
|
res.end(JSON.stringify({ message: "Not Found" }));
|
|
@@ -477,30 +492,6 @@ export default class Blog {
|
|
|
477
492
|
console.log(markdown);
|
|
478
493
|
}
|
|
479
494
|
|
|
480
|
-
/*async isItFileOrArray(filesInput) {
|
|
481
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
482
|
-
const files = Array.isArray(filesInput) ? filesInput : [filesInput];
|
|
483
|
-
const fileData = await Promise.all(files.map(async (f) => {
|
|
484
|
-
const filepath = path.isAbsolute(f) ? f : path.join(__dirname, f);
|
|
485
|
-
const content = await fs.promises.readFile(filepath, "utf8");
|
|
486
|
-
return { path: filepath, content };
|
|
487
|
-
}));
|
|
488
|
-
return fileData;
|
|
489
|
-
}*/
|
|
490
|
-
|
|
491
|
-
/*computeHashsum(fileData) {
|
|
492
|
-
return crypto
|
|
493
|
-
.createHash("sha256")
|
|
494
|
-
.update(
|
|
495
|
-
fileData
|
|
496
|
-
.map((f) =>
|
|
497
|
-
crypto.createHash("sha256").update(f.content).digest("hex")
|
|
498
|
-
)
|
|
499
|
-
.join("")
|
|
500
|
-
)
|
|
501
|
-
.digest("hex");
|
|
502
|
-
}*/
|
|
503
|
-
|
|
504
495
|
/** render this blog content to valid html */
|
|
505
496
|
async toHTML(loggedin) {
|
|
506
497
|
const data = {
|
|
@@ -512,58 +503,21 @@ export default class Blog {
|
|
|
512
503
|
|
|
513
504
|
if(loggedin) data.login = `<a href="/logout">logout</a>`;
|
|
514
505
|
else data.login = `<a href="/login">login</a>`;
|
|
515
|
-
|
|
516
|
-
/*const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
517
|
-
const filepath = path.join(__dirname, "test/stylesheets/styles.css");
|
|
518
|
-
const fileData = await fs.promises.readFile(filepath, 'utf8');
|
|
519
|
-
console.log(fileData);
|
|
520
|
-
this.compiledStyles = fileData;*/
|
|
521
|
-
|
|
522
|
-
//await this.processAssets(this.assetFiles);
|
|
523
|
-
|
|
524
|
-
// is it file or array of file?
|
|
525
|
-
/*if (this.assetFiles) {
|
|
526
|
-
const fileData = await this.isItFileOrArray(this.assetFiles);
|
|
527
|
-
|
|
528
|
-
const currentHash = this.computeHashsum(fileData);
|
|
529
|
-
console.log(`currentHash: ${currentHash}`)
|
|
530
|
-
if(currentHash !== this.#stylesHash) {
|
|
531
|
-
console.log("Style assets have changed. Recompiling...");
|
|
532
|
-
this.#stylesHash = currentHash;
|
|
533
|
-
this.compiledStyles = await compileStyles(fileData);
|
|
534
|
-
}
|
|
535
|
-
}*/
|
|
536
506
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const finalStyles = await mergeStyles(this.styles, this.compiledStyles);
|
|
540
|
-
const html = formatHTML(data, this.scripts, finalStyles);
|
|
507
|
+
const html = formatHTML(data);
|
|
541
508
|
if (validate(html)) return html;
|
|
542
509
|
throw new Error("Error. Invalid HTML!");
|
|
543
510
|
}
|
|
544
511
|
|
|
545
|
-
async buildFrontend() {
|
|
546
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
547
|
-
const __dirname = path.dirname(__filename);
|
|
548
|
-
const gulpDir = path.join(__dirname, "./gulp_frontend");
|
|
549
|
-
console.log("Building frontend assets...");
|
|
550
|
-
try {
|
|
551
|
-
const { stdout, stderr } = await execPromise("npx gulp build", { cwd: gulpDir });
|
|
552
|
-
if (stdout) console.log(stdout);
|
|
553
|
-
if (stderr) console.error(stderr);
|
|
554
|
-
} catch (err) {
|
|
555
|
-
console.error("Failed to build frontend:", err);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
512
|
/**
|
|
560
|
-
* Loads files,
|
|
561
|
-
*
|
|
562
|
-
* @param {string[]} files - Array of file paths to process.
|
|
513
|
+
* Loads files, generates checksums and compiles sass files to css if anything changed
|
|
514
|
+
* @param {string[]} files - Array of css/scss file paths to process.
|
|
563
515
|
*/
|
|
564
|
-
async
|
|
516
|
+
async processStylesheets(files) {
|
|
565
517
|
|
|
566
518
|
// Normalize input to array (handles string or array)
|
|
519
|
+
// "file1.css" --> ["file1.css"]
|
|
520
|
+
// ["file1.css", "file2.css",...]
|
|
567
521
|
const fileList = Array.isArray(files) ? files : [files];
|
|
568
522
|
|
|
569
523
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -575,6 +529,7 @@ export default class Blog {
|
|
|
575
529
|
|
|
576
530
|
// --- Process Styles ---
|
|
577
531
|
if (styleFiles.length > 0) {
|
|
532
|
+
// read file
|
|
578
533
|
const fileData = await Promise.all(
|
|
579
534
|
styleFiles.sort().map(async (f) => {
|
|
580
535
|
const content = await fs.promises.readFile(f, "utf-8");
|
|
@@ -583,6 +538,7 @@ export default class Blog {
|
|
|
583
538
|
})
|
|
584
539
|
);
|
|
585
540
|
|
|
541
|
+
// compute hash
|
|
586
542
|
const currentHash = crypto
|
|
587
543
|
.createHash("sha256")
|
|
588
544
|
.update(
|
|
@@ -593,16 +549,22 @@ export default class Blog {
|
|
|
593
549
|
.join("")
|
|
594
550
|
)
|
|
595
551
|
.digest("hex");
|
|
552
|
+
|
|
553
|
+
// check if hash matches
|
|
596
554
|
if (currentHash !== this.#stylesHash) {
|
|
597
555
|
console.log("Style assets have changed. Recompiling...");
|
|
598
556
|
this.#stylesHash = currentHash;
|
|
599
557
|
|
|
600
|
-
// Compile styles using the standalone script
|
|
558
|
+
// Compile styles using the standalone script from build-styles.js
|
|
601
559
|
this.compiledStyles = await compileStyles(fileData);
|
|
602
|
-
|
|
560
|
+
|
|
561
|
+
// generate a file
|
|
562
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
563
|
+
const __dirname = path.dirname(__filename);
|
|
564
|
+
const publicDir = path.join(__dirname, "public");
|
|
603
565
|
|
|
604
566
|
await fs.promises.writeFile(
|
|
605
|
-
path.join(
|
|
567
|
+
path.join(publicDir, "styles.min.css"),
|
|
606
568
|
this.compiledStyles + `\n/* source-hash: ${currentHash} */`
|
|
607
569
|
);
|
|
608
570
|
} else {
|
|
@@ -610,29 +572,4 @@ export default class Blog {
|
|
|
610
572
|
}
|
|
611
573
|
}
|
|
612
574
|
}
|
|
613
|
-
|
|
614
|
-
async #findAssetFiles() {
|
|
615
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
616
|
-
const __dirname = path.dirname(__filename);
|
|
617
|
-
const srcDir = path.join(__dirname, "gulp_frontend", "src");
|
|
618
|
-
const files = [];
|
|
619
|
-
|
|
620
|
-
async function walk(dir) {
|
|
621
|
-
try {
|
|
622
|
-
const list = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
623
|
-
for (const dirent of list) {
|
|
624
|
-
const res = path.join(dir, dirent.name);
|
|
625
|
-
if (dirent.isDirectory()) {
|
|
626
|
-
await walk(res);
|
|
627
|
-
} else {
|
|
628
|
-
files.push(res);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
} catch (err) {
|
|
632
|
-
// Directory might not exist or is not accessible
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
await walk(srcDir);
|
|
636
|
-
return files;
|
|
637
|
-
}
|
|
638
575
|
}
|
package/Formatter.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
export function header(title) {
|
|
2
|
+
return `<!DOCTYPE html>
|
|
3
|
+
<html lang="de">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>${title}</title>
|
|
8
|
+
<style type="text/css">body { font-family: Arial,sans-serif; }</style>
|
|
9
|
+
<link rel="stylesheet" href="styles.min.css" type="text/css" media="screen" />
|
|
10
|
+
<link rel="stylesheet" href="print.min.css" type="text/css" media="print" />
|
|
11
|
+
</head>`;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
/** format content to html */
|
|
2
|
-
export function formatHTML(data
|
|
15
|
+
export function formatHTML(data) {
|
|
3
16
|
//console.log(`${data} ${script} ${style}`);
|
|
4
17
|
//export function formatHTML(data) {
|
|
5
18
|
//const button = `<button type="button" onClick="fillWithContent();" style="margin: 4px;">generate random text</button>`;
|
|
@@ -15,20 +28,13 @@ export function formatHTML(data, script, style) {
|
|
|
15
28
|
<hr>`;
|
|
16
29
|
}
|
|
17
30
|
const form = form1;
|
|
18
|
-
return
|
|
19
|
-
<html lang="de">
|
|
20
|
-
<head>
|
|
21
|
-
<meta charset="UTF-8">
|
|
22
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
23
|
-
<title>${data.title}</title>
|
|
24
|
-
<style>${style}</style>
|
|
25
|
-
</head>
|
|
31
|
+
return `${header(data.title)}
|
|
26
32
|
<body>
|
|
27
33
|
<nav>
|
|
28
34
|
${data.login}
|
|
29
35
|
</nav>
|
|
30
36
|
<h1>${data.title}</h1>
|
|
31
|
-
<div
|
|
37
|
+
<div id="wrapper">
|
|
32
38
|
${form}
|
|
33
39
|
<section id="articles" class="grid">
|
|
34
40
|
${data.articles
|
|
@@ -44,8 +50,7 @@ export function formatHTML(data, script, style) {
|
|
|
44
50
|
.join("")}
|
|
45
51
|
</section>
|
|
46
52
|
</div>
|
|
47
|
-
<script>
|
|
48
|
-
${script}
|
|
53
|
+
<script src="scripts.min.js">
|
|
49
54
|
</script>
|
|
50
55
|
</body>
|
|
51
56
|
</html>`;
|
package/blog.db
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexho111/plainblog",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "A tool for creating and serving a minimalist, single-page blog.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"sqlite3": "^5.1.7"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"dom-parser": "^1.1.5",
|
|
30
31
|
"eslint": "^9.8.0",
|
|
31
32
|
"eslint-plugin-jest": "^28.6.0",
|
|
32
|
-
"jest": "^29.7.0"
|
|
33
|
-
"dom-parser": "^1.1.5"
|
|
33
|
+
"jest": "^29.7.0"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -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,2 @@
|
|
|
1
|
+
body{font-family:Arial;font-family:Arial,sans-serif}h1{color:#333}.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}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
|
|
2
|
+
/* source-hash: 374cb67a628dde5695d1104c5f9037d737d17d6b6b9a791be81779d7dd91beac */
|
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,15 @@ nav a:visited {
|
|
|
46
46
|
color: #3b40c1;
|
|
47
47
|
text-decoration-color: #3b40c1;
|
|
48
48
|
}
|
|
49
|
+
/* Mobile Layout (screens smaller than 1000px) */
|
|
50
|
+
@media screen and (max-width: 1000px) {
|
|
51
|
+
* {
|
|
52
|
+
font-size: 4vw;
|
|
53
|
+
}
|
|
54
|
+
#wrapper {
|
|
55
|
+
max-width: 100%;
|
|
56
|
+
width: 100%;
|
|
57
|
+
padding: 0 10px; /* Prevents text from touching the edges */
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
}
|
|
60
|
+
}
|
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:#000;color:#fff",
|
|
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/styles.test.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import Blog from "../Blog.js";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { parseFromString } from "dom-parser";
|
|
5
4
|
import fs from "node:fs";
|
|
6
5
|
|
|
7
|
-
const
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const publicDir = path.join(__dirname, "../public");
|
|
8
9
|
|
|
9
10
|
describe("Blog Stylesheet Test", () => {
|
|
10
11
|
it("should load and compile the sass stylesheet (.scss) correctly", async () => {
|
|
@@ -15,12 +16,16 @@ describe("Blog Stylesheet Test", () => {
|
|
|
15
16
|
blog.stylesheetPath = "test/stylesheets/styles.scss";
|
|
16
17
|
|
|
17
18
|
await blog.init();
|
|
18
|
-
const html = await blog.toHTML();
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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");
|
|
24
29
|
});
|
|
25
30
|
|
|
26
31
|
it("should load and compile the sass stylesheet (.scss) correctly [Array]", async () => {
|
|
@@ -31,12 +36,15 @@ describe("Blog Stylesheet Test", () => {
|
|
|
31
36
|
blog.stylesheetPath = ["test/stylesheets/styles.scss"];
|
|
32
37
|
|
|
33
38
|
await blog.init();
|
|
34
|
-
const
|
|
39
|
+
const publicCSS = await fs.promises.readFile(
|
|
40
|
+
path.join(publicDir, "styles.min.css"),
|
|
41
|
+
"utf8"
|
|
42
|
+
);
|
|
35
43
|
|
|
36
|
-
expect(
|
|
37
|
-
expect(
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
44
|
+
expect(publicCSS).toContain(".grid");
|
|
45
|
+
expect(publicCSS).toContain("article");
|
|
46
|
+
expect(publicCSS).toContain("nav");
|
|
47
|
+
expect(publicCSS).toContain("nav a:visited");
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
it("should load the stylesheet (.css) file", async () => {
|
|
@@ -57,25 +65,20 @@ describe("Blog Stylesheet Test", () => {
|
|
|
57
65
|
blog.database.dbname = "test_styles_3";
|
|
58
66
|
blog.stylesheetPath = "test/stylesheets/styles.css";
|
|
59
67
|
await blog.init();
|
|
60
|
-
const html = await blog.toHTML();
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"text/html"
|
|
69
|
+
const publicCSS = await fs.promises.readFile(
|
|
70
|
+
path.join(publicDir, "styles.min.css"),
|
|
71
|
+
"utf8"
|
|
66
72
|
);
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
expect(
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
76
|
-
expect(css).toContain(".datetime");
|
|
77
|
-
expect(css).toContain("font-style:normal");
|
|
78
|
-
expect(css).toContain("color:");
|
|
74
|
+
expect(publicCSS).not.toContain(
|
|
75
|
+
"<style>body { font-family: Arial; }</style>"
|
|
76
|
+
);
|
|
77
|
+
expect(publicCSS).toContain("body");
|
|
78
|
+
expect(publicCSS).toContain("nav a");
|
|
79
|
+
expect(publicCSS).toContain(".datetime");
|
|
80
|
+
expect(publicCSS).toContain("font-style:normal");
|
|
81
|
+
expect(publicCSS).toContain("color:");
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
it("should load and compile the stylesheet (.css) correctly [Array]", async () => {
|
|
@@ -84,24 +87,19 @@ describe("Blog Stylesheet Test", () => {
|
|
|
84
87
|
blog.database.dbname = "test_styles_4";
|
|
85
88
|
blog.stylesheetPath = ["test/stylesheets/styles.css"];
|
|
86
89
|
await blog.init();
|
|
87
|
-
const html = await blog.toHTML();
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"text/html"
|
|
91
|
+
const publicCSS = await fs.promises.readFile(
|
|
92
|
+
path.join(publicDir, "styles.min.css"),
|
|
93
|
+
"utf8"
|
|
93
94
|
);
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
expect(
|
|
101
|
-
expect(
|
|
102
|
-
expect(
|
|
103
|
-
expect(css).toContain(".datetime");
|
|
104
|
-
expect(css).toContain("font-style:normal");
|
|
105
|
-
expect(css).toContain("color:");
|
|
96
|
+
expect(publicCSS).not.toContain(
|
|
97
|
+
"<style>body { font-family: Arial; }</style>"
|
|
98
|
+
);
|
|
99
|
+
expect(publicCSS).toContain("body");
|
|
100
|
+
expect(publicCSS).toContain("nav a");
|
|
101
|
+
expect(publicCSS).toContain(".datetime");
|
|
102
|
+
expect(publicCSS).toContain("font-style:normal");
|
|
103
|
+
expect(publicCSS).toContain("color:");
|
|
106
104
|
});
|
|
107
105
|
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/test_server_db.db
CHANGED
|
Binary file
|
package/test_styles_1.db
CHANGED
|
Binary file
|
package/test_styles_2.db
CHANGED
|
Binary file
|
package/test_styles_3.db
CHANGED
|
Binary file
|
package/test_styles_4.db
CHANGED
|
Binary file
|
package/blog.db-journal
DELETED
|
Binary file
|
package/styles.min.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
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}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}
|
package/test_1767173684248.db
DELETED
|
Binary file
|
package/test_1767173763155.db
DELETED
|
Binary file
|
package/test_1767173821563.db
DELETED
|
Binary file
|
package/test_1767260493607.db
DELETED
|
Binary file
|
package/test_1767281040439.db
DELETED
|
Binary file
|
package/test_1767281442334.db
DELETED
|
Binary file
|
package/test_1767286038587.db
DELETED
|
Binary file
|
package/test_1767286127364.db
DELETED
|
Binary file
|
package/test_1767286366239.db
DELETED
|
Binary file
|
package/test_1767286503638.db
DELETED
|
Binary file
|
package/test_1767286637739.db
DELETED
|
Binary file
|
package/test_1767292219862.db
DELETED
|
Binary file
|
package/test_1767292355190.db
DELETED
|
Binary file
|
|
File without changes
|
|
File without changes
|