@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.
Files changed (47) hide show
  1. package/.vscode/settings.json +2 -0
  2. package/Blog.js +117 -195
  3. package/Formatter.js +21 -13
  4. package/README.md +2 -38
  5. package/blog.db +0 -0
  6. package/blog.json +87 -1
  7. package/build-styles.js +3 -3
  8. package/model/DatabaseModel2.js +86 -0
  9. package/model/fileModel.js +15 -5
  10. package/package.json +11 -9
  11. package/public/print.min.css +2 -0
  12. package/{scripts.min.js → public/scripts.min.js} +1 -1
  13. package/public/styles.min.css +68 -0
  14. package/src/print.scss +76 -0
  15. package/src/styles.css +18 -0
  16. package/test/blog.test.js +24 -7
  17. package/test/model.test.js +7 -5
  18. package/test/server.test.js +2 -1
  19. package/test/styles.test.js +42 -44
  20. package/blog.db-journal +0 -0
  21. package/model/DatabaseModel.js +0 -139
  22. package/streams.js +0 -17
  23. package/styles.min.css +0 -1
  24. package/test_1767117403635.db +0 -0
  25. package/test_1767117781437.db +0 -0
  26. package/test_1767117944836.db +0 -0
  27. package/test_1767119505118.db +0 -0
  28. package/test_1767173684248.db +0 -0
  29. package/test_1767173763155.db +0 -0
  30. package/test_1767173821563.db +0 -0
  31. package/test_1767260493607.db +0 -0
  32. package/test_1767281040439.db +0 -0
  33. package/test_1767281442334.db +0 -0
  34. package/test_1767286038587.db +0 -0
  35. package/test_1767286127364.db +0 -0
  36. package/test_1767286366239.db +0 -0
  37. package/test_1767286503638.db +0 -0
  38. package/test_1767286637739.db +0 -0
  39. package/test_1767292219862.db +0 -0
  40. package/test_1767292355190.db +0 -0
  41. package/test_server_db.db +0 -0
  42. package/test_styles_1.db +0 -0
  43. package/test_styles_2.db +0 -0
  44. package/test_styles_3.db +0 -0
  45. package/test_styles_4.db +0 -0
  46. /package/{styles.min.css.map → public/styles.min.css.map} +0 -0
  47. /package/src/{loader.js → fetchData.js} +0 -0
@@ -0,0 +1,2 @@
1
+ {
2
+ }
package/Blog.js CHANGED
@@ -2,18 +2,15 @@ import http from "http";
2
2
  import crypto from "crypto";
3
3
  import fs from "fs";
4
4
  import { URLSearchParams } from "url";
5
- import { MapTransform } from "./streams.js";
6
5
  import Article from "./Article.js";
7
- import DatabaseModel from "./model/DatabaseModel.js";
6
+ import DatabaseModel from "./model/DatabaseModel2.js";
8
7
  import { fetchData, postData } from "./model/APIModel.js";
9
- import { save as saveToFile, load as loadFromFile } from "./model/fileModel.js";
10
- import { formatHTML, formatMarkdown, validate } from "./Formatter.js";
8
+ import { formatHTML, header, formatMarkdown, validate } from "./Formatter.js";
11
9
  import pkg from "./package.json" with { type: "json" };
12
10
  import path from "path";
13
11
  import { fileURLToPath } from "url";
14
12
  import { exec } from "child_process";
15
13
  import { promisify } from "util";
16
- import { compileStyles, mergeStyles } from "./build-styles.js";
17
14
 
18
15
  const execPromise = promisify(exec);
19
16
 
@@ -28,13 +25,14 @@ export default class Blog {
28
25
  this.scripts = "";
29
26
  this.compiledStyles = "";
30
27
  this.compiledScripts = "";
28
+ this.reloadStylesOnGET = false;
31
29
 
32
30
  this.database = {
33
- type: "sqlite",
34
- username: "user1",
35
- password: "password1",
31
+ type: "file",
32
+ username: "user",
33
+ password: "password",
36
34
  host: "localhost",
37
- dbname: "blog",
35
+ dbname: "blog.json",
38
36
  };
39
37
  this.sessions = new Set();
40
38
 
@@ -42,63 +40,6 @@ export default class Blog {
42
40
  console.log(`version: ${version}`);
43
41
  }
44
42
 
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
43
  // Private fields
103
44
  #server = null;
104
45
  #password = null;
@@ -108,6 +49,7 @@ export default class Blog {
108
49
  #title = "";
109
50
  #stylesHash = "";
110
51
  #scriptsHash = "";
52
+ #stylesheetPath = "";
111
53
 
112
54
  setTitle(title) {
113
55
  this.#title = title;
@@ -123,6 +65,10 @@ export default class Blog {
123
65
 
124
66
  set title(t) {
125
67
  this.#title = t;
68
+ this.#databaseModel = new DatabaseModel(this.database);
69
+ console.log(`connected to database`);
70
+ if(t != this.#title && t.length == 0)
71
+ this.#databaseModel.updateBlogTitle(t);
126
72
  }
127
73
 
128
74
  get title() {
@@ -138,7 +84,8 @@ export default class Blog {
138
84
  }
139
85
 
140
86
  set stylesheetPath(files) {
141
- this.assetFiles = files;
87
+ this.#stylesheetPath = files;
88
+ console.log(`this.#stylesheetPath: ${this.#stylesheetPath}`)
142
89
  }
143
90
 
144
91
  addArticle(article) {
@@ -170,7 +117,7 @@ export default class Blog {
170
117
  res.end();
171
118
  } else {
172
119
  res.writeHead(401, { "Content-Type": "text/html" });
173
- res.end(`<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${this.styles}</style></head>
120
+ res.end(`${header("My Blog")}
174
121
  <body>
175
122
  <h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
176
123
  <input type="password" name="password" placeholder="Password" />
@@ -197,11 +144,52 @@ export default class Blog {
197
144
  /** initializes database */
198
145
  async init() {
199
146
  //await this.buildFrontend();
200
- this.loadStyles();
201
- this.loadScripts();
202
- //const assetFiles = await this.#findAssetFiles();
203
- if(this.assetFiles != null) {
204
- await this.processAssets(this.assetFiles);
147
+ //this.loadStyles();
148
+ //this.loadScripts();
149
+ if(this.#stylesheetPath != null) {
150
+ await this.processStylesheets(this.#stylesheetPath);
151
+ }
152
+ if(!this.#stylesheetPath) {
153
+ // compile and merge hardcoded styles with src/styles.css and write to file "styles.min.css"
154
+ // which will be imported by webbrowser via '<link rel="stylesheet" href="styles.min.css"...'
155
+
156
+ const __filename = fileURLToPath(import.meta.url);
157
+ const __dirname = path.dirname(__filename);
158
+ const srcStylePath = path.join(__dirname, "src", "styles.css");
159
+ const publicStylePath = path.join(__dirname, "public", "styles.min.css");
160
+
161
+ let publicHash = null;
162
+ try {
163
+ const publicCSS = await fs.promises.readFile(publicStylePath, "utf8");
164
+ const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
165
+ if (match) {
166
+ publicHash = match[1];
167
+ }
168
+ } catch (err) {
169
+ // public/styles.min.css doesn't exist, will be created.
170
+ }
171
+
172
+ let srcStyles = "";
173
+ try {
174
+ srcStyles = await fs.promises.readFile(srcStylePath, "utf8");
175
+ } catch (err) {
176
+ // ignore if src/styles.css doesn't exist
177
+ }
178
+
179
+ const combinedStyles = this.styles + srcStyles;
180
+ const srcHash = crypto.createHash("sha256").update(combinedStyles).digest("hex");
181
+
182
+ if (srcHash !== publicHash) {
183
+ console.log("Styles have changed. Recompiling...");
184
+ //const finalStyles = await mergeStyles(this.styles, srcStyles);
185
+ const finalStyles = this.styles + " " + srcStyles;
186
+ try {
187
+ await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
188
+ await fs.promises.writeFile(publicStylePath, finalStyles + `\n/* source-hash: ${srcHash} */`);
189
+ } catch (err) {
190
+ console.error("Failed to write styles to public folder:", err);
191
+ }
192
+ }
205
193
  }
206
194
  if (this.#isExternalAPI) {
207
195
  console.log("external API");
@@ -218,7 +206,6 @@ export default class Blog {
218
206
  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
207
  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
208
  }
221
- this.reloadStylesOnGET = false;
222
209
  if(this.reloadStylesOnGET) console.log("reload scripts and styles on GET-Request");
223
210
  let title = "";
224
211
  if (this.#title != null && this.#title.length > 0) title = this.#title; // use blog title if set
@@ -280,7 +267,7 @@ export default class Blog {
280
267
  if (req.url === "/login") {
281
268
  if (req.method === "GET") {
282
269
  res.writeHead(200, { "Content-Type": "text/html" });
283
- res.end(`<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${this.styles}</style></head>
270
+ res.end(`${header("My Blog")}
284
271
  <body>
285
272
  <h1>Login</h1><form method="POST">
286
273
  <input type="password" name="password" placeholder="Password" />
@@ -304,7 +291,6 @@ export default class Blog {
304
291
  res.end("Forbidden");
305
292
  return;
306
293
  }
307
- await this.#databaseModel.updateBlogTitle(this.title);
308
294
  await this.postArticle(req, res);
309
295
  // GET artciles
310
296
  } else if (req.method === "GET" && req.url === "/") {
@@ -312,11 +298,8 @@ export default class Blog {
312
298
 
313
299
  // reload styles and scripts on (every) request
314
300
  if(this.reloadStylesOnGET) {
315
- if (this.assetFiles) {
316
- await this.processAssets(this.assetFiles);
317
- } else {
318
- this.loadStyles();
319
- this.loadScripts();
301
+ if (this.#stylesheetPath) {
302
+ await this.processStylesheets(this.#stylesheetPath);
320
303
  }
321
304
  }
322
305
 
@@ -339,6 +322,39 @@ export default class Blog {
339
322
  res.end("Internal Server Error");
340
323
  }
341
324
  } else {
325
+ // Try to serve static files from public folder
326
+ try {
327
+ const __filename = fileURLToPath(import.meta.url);
328
+ const __dirname = path.dirname(__filename);
329
+ const publicDir = path.join(__dirname, "public");
330
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);
331
+ const filePath = path.join(publicDir, parsedUrl.pathname);
332
+
333
+ if (filePath.startsWith(publicDir)) {
334
+ const stats = await fs.promises.stat(filePath);
335
+ if (stats.isFile()) {
336
+ const ext = path.extname(filePath).toLowerCase();
337
+ const mimeTypes = {
338
+ ".html": "text/html",
339
+ ".js": "text/javascript",
340
+ ".css": "text/css",
341
+ ".json": "application/json",
342
+ ".png": "image/png",
343
+ ".jpg": "image/jpeg",
344
+ ".gif": "image/gif",
345
+ ".svg": "image/svg+xml",
346
+ ".ico": "image/x-icon",
347
+ };
348
+ const contentType = mimeTypes[ext] || "application/octet-stream";
349
+ res.writeHead(200, { "Content-Type": contentType });
350
+ fs.createReadStream(filePath).pipe(res);
351
+ return;
352
+ }
353
+ }
354
+ } catch (err) {
355
+ // Continue to 404
356
+ }
357
+
342
358
  // Error 404
343
359
  res.writeHead(404, { "Content-Type": "application/json" });
344
360
  res.end(JSON.stringify({ message: "Not Found" }));
@@ -372,7 +388,7 @@ export default class Blog {
372
388
  /** Populates the blog's title and articles from a data object. */
373
389
  #applyBlogData(data) {
374
390
  this.articles = []; // Clear existing articles before loading new ones
375
- this.title = data.title;
391
+ this.#title = data.title;
376
392
  // Assuming the API returns an array of objects with title and content
377
393
  if (data.articles && Array.isArray(data.articles)) {
378
394
  for (const articleData of data.articles) {
@@ -383,23 +399,6 @@ export default class Blog {
383
399
  }
384
400
  }
385
401
 
386
- async save(filename = this.filename) {
387
- if (this.#databaseModel === undefined) this.init(); // init blog if it didn't already happen
388
- //await this.#apiServer.initialize();
389
- if (this.#isExternalAPI) await this.loadFromAPI();
390
- const blogData = { title: this.title, articles: this.articles };
391
- saveToFile(filename, blogData);
392
- }
393
-
394
- async load(filename) {
395
- loadFromFile(filename, (title, articles) => {
396
- this.title = title;
397
- this.articles = articles.map(
398
- (article) => new Article(article.title, article.content)
399
- );
400
- });
401
- }
402
-
403
402
  async loadFromAPI() {
404
403
  const data = await fetchData(this.#apiUrl);
405
404
  if (data) {
@@ -477,30 +476,6 @@ export default class Blog {
477
476
  console.log(markdown);
478
477
  }
479
478
 
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
479
  /** render this blog content to valid html */
505
480
  async toHTML(loggedin) {
506
481
  const data = {
@@ -512,58 +487,21 @@ export default class Blog {
512
487
 
513
488
  if(loggedin) data.login = `<a href="/logout">logout</a>`;
514
489
  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
490
 
537
- this.loadScripts();
538
-
539
- const finalStyles = await mergeStyles(this.styles, this.compiledStyles);
540
- const html = formatHTML(data, this.scripts, finalStyles);
491
+ const html = formatHTML(data);
541
492
  if (validate(html)) return html;
542
493
  throw new Error("Error. Invalid HTML!");
543
494
  }
544
495
 
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
496
  /**
560
- * Loads files, converts them to Vinyl format, and processes them via Gulp.
561
- * Updates this.styles and this.scripts with the compiled output.
562
- * @param {string[]} files - Array of file paths to process.
497
+ * Loads files, generates checksums and compiles sass files to css if anything changed
498
+ * @param {string[]} files - Array of css/scss file paths to process.
563
499
  */
564
- async processAssets(files) {
500
+ async processStylesheets(files) {
565
501
 
566
502
  // Normalize input to array (handles string or array)
503
+ // "file1.css" --> ["file1.css"]
504
+ // ["file1.css", "file2.css",...]
567
505
  const fileList = Array.isArray(files) ? files : [files];
568
506
 
569
507
  const __filename = fileURLToPath(import.meta.url);
@@ -575,6 +513,7 @@ export default class Blog {
575
513
 
576
514
  // --- Process Styles ---
577
515
  if (styleFiles.length > 0) {
516
+ // read file
578
517
  const fileData = await Promise.all(
579
518
  styleFiles.sort().map(async (f) => {
580
519
  const content = await fs.promises.readFile(f, "utf-8");
@@ -583,6 +522,7 @@ export default class Blog {
583
522
  })
584
523
  );
585
524
 
525
+ // compute hash
586
526
  const currentHash = crypto
587
527
  .createHash("sha256")
588
528
  .update(
@@ -593,16 +533,23 @@ export default class Blog {
593
533
  .join("")
594
534
  )
595
535
  .digest("hex");
536
+
537
+ // check if hash matches
596
538
  if (currentHash !== this.#stylesHash) {
597
539
  console.log("Style assets have changed. Recompiling...");
598
540
  this.#stylesHash = currentHash;
599
541
 
600
- // Compile styles using the standalone script
601
- this.compiledStyles = await compileStyles(fileData);
602
- //console.log(`compiledStyles: ${this.compiledStyles}`)
542
+ // Compile styles using the standalone script from build-styles.js
543
+ //this.compiledStyles = await compileStyles(fileData);
544
+ this.compiledStyles = fileData; // TODO workaround
545
+
546
+ // generate a file
547
+ const __filename = fileURLToPath(import.meta.url);
548
+ const __dirname = path.dirname(__filename);
549
+ const publicDir = path.join(__dirname, "public");
603
550
 
604
551
  await fs.promises.writeFile(
605
- path.join(__dirname, "styles.min.css"),
552
+ path.join(publicDir, "styles.min.css"),
606
553
  this.compiledStyles + `\n/* source-hash: ${currentHash} */`
607
554
  );
608
555
  } else {
@@ -610,29 +557,4 @@ export default class Blog {
610
557
  }
611
558
  }
612
559
  }
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
560
  }
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, script, style) {
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,16 @@ export function formatHTML(data, script, style) {
15
28
  <hr>`;
16
29
  }
17
30
  const form = form1;
18
- return `<!DOCTYPE html>
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
- <h1>${data.title}</h1>
31
- <div style="max-width: 500px; width: 100%;">
36
+ <div id="header">
37
+ <h1>${data.title}</h1>
38
+ <!--<img src="headerphoto.jpg"/>-->
39
+ </div>
40
+ <div id="wrapper">
32
41
  ${form}
33
42
  <section id="articles" class="grid">
34
43
  ${data.articles
@@ -44,8 +53,7 @@ export function formatHTML(data, script, style) {
44
53
  .join("")}
45
54
  </section>
46
55
  </div>
47
- <script>
48
- ${script}
56
+ <script src="scripts.min.js">
49
57
  </script>
50
58
  </body>
51
59
  </html>`;
package/README.md CHANGED
@@ -25,24 +25,6 @@ Now you can open your blog in your webbrowser on `http://localhost:8080`. Login
25
25
 
26
26
  ## More Features
27
27
 
28
- **SQLite** is the default database. But you can use **PostgreSQL** instead.
29
-
30
- ### run api server with postgres database
31
-
32
- ```
33
- import Blog from "@lexho111/plainblog";
34
-
35
- const blog = new Blog();
36
- blog.database.type = "postgres";
37
- blog.database.username = "user";
38
- blog.database.password = "password";
39
- blog.database.host = "localhost";
40
- blog.setStyle("body { font-family: Arial, sans-serif; } h1 { color: #333; }");
41
- await blog.init(); // load data from database
42
-
43
- blog.startServer(8080);
44
- ```
45
-
46
28
  ### set an API to fetch data from an external database
47
29
 
48
30
  ```
@@ -56,36 +38,18 @@ await blog.init(); // load data from database
56
38
  blog.startServer(8080);
57
39
  ```
58
40
 
59
- ### provide custom style sheets and scripts
41
+ ### provide custom style sheets
60
42
 
61
43
  ```
62
44
  const blog = new Blog();
63
45
  blog.title = "My Blog";
64
46
  blog.style = "body { font-family: Arial, sans-serif; } h1 { color: #333; }";
65
47
  blog.password = "mypassword";
66
- blog.stylesheetPath = "path/to/my/styles.scss";
48
+ blog.stylesheetPath = "path/to/my/styles.css";
67
49
 
68
50
  blog.startServer(8080);
69
51
  ```
70
52
 
71
- save data to file
72
-
73
- ```
74
- import Blog from "@lexho111/plainblog";
75
- import { Article } from "@lexho111/plainblog";
76
-
77
- const blog = new Blog();
78
- blog.setStyle("body { font-family: Arial, sans-serif; } h1 { color: #333; }");
79
-
80
- const article = new Article("hello", "hello world!");
81
- blog.addArticle(article);
82
-
83
- blog.save("myblog.json");
84
-
85
- // load data from 'myblog.json'
86
- await blog.load("myblog.json");
87
- ```
88
-
89
53
  print your blog articles in markdown
90
54
 
91
55
  ```
package/blog.db CHANGED
Binary file