@lexho111/plainblog 0.5.23 → 0.5.25

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 CHANGED
@@ -456,7 +456,7 @@ export default class Blog {
456
456
  return new Promise((resolve, reject) => {
457
457
  const errorHandler = (err) => reject(err);
458
458
  this.#server.once("error", errorHandler);
459
- this.#server.listen(port, "127.0.0.1", () => {
459
+ this.#server.listen(port, "0.0.0.0", () => { // <-- for docker 0.0.0.0, localhost 127.0.0.1
460
460
  this.#server.removeListener("error", errorHandler);
461
461
  console.log(`server running at http://localhost:${port}/`);
462
462
  resolve(); // Resolve the promise when the server is listening
package/README.md CHANGED
@@ -74,6 +74,21 @@ await blog.init(); // load data from database
74
74
  blog.startServer(8080);
75
75
  ```
76
76
 
77
+ The API should respond to GET-Requests (*http://example.com:5432/blog*) with json like this:
78
+
79
+ ```
80
+ {
81
+ "title": "My Remote Blog",
82
+ "articles": [
83
+ {
84
+ "title": "Welcome to the API Server",
85
+ "content": "This content is served from a separate API server.",
86
+ "createdAt": "2026-01-12T11:21:55.561Z"
87
+ }
88
+ ]
89
+ }
90
+ ```
91
+
77
92
  ### provide custom style sheets
78
93
 
79
94
  ```
package/api-server.js ADDED
@@ -0,0 +1,70 @@
1
+ import http from "http";
2
+
3
+ const PORT = 5432;
4
+
5
+ // In-memory data store
6
+ const blogData = {
7
+ title: "My Remote Blog",
8
+ articles: [
9
+ {
10
+ title: "Welcome to the API Server",
11
+ content: "This content is served from a separate API server.",
12
+ createdAt: new Date().toISOString(),
13
+ },
14
+ ],
15
+ };
16
+
17
+ const server = http.createServer((req, res) => {
18
+ // Set CORS headers to allow requests from the blog application
19
+ res.setHeader("Access-Control-Allow-Origin", "*");
20
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
21
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
22
+
23
+ // Handle preflight requests
24
+ if (req.method === "OPTIONS") {
25
+ res.writeHead(204);
26
+ res.end();
27
+ return;
28
+ }
29
+
30
+ const url = new URL(req.url, `http://${req.headers.host}`);
31
+
32
+ if (url.pathname === "/blog") {
33
+ if (req.method === "GET") {
34
+ res.writeHead(200, { "Content-Type": "application/json" });
35
+ res.end(JSON.stringify(blogData));
36
+ } else if (req.method === "POST") {
37
+ let body = "";
38
+ req.on("data", (chunk) => (body += chunk.toString()));
39
+ req.on("end", () => {
40
+ try {
41
+ const newArticle = JSON.parse(body);
42
+ if (!newArticle.createdAt) {
43
+ newArticle.createdAt = new Date().toISOString();
44
+ }
45
+ // Add the new article to the beginning of the list
46
+ blogData.articles.unshift(newArticle);
47
+
48
+ console.log("New article received:", newArticle.title);
49
+
50
+ res.writeHead(201, { "Content-Type": "application/json" });
51
+ res.end(JSON.stringify(newArticle));
52
+ } catch (error) {
53
+ console.error("Error parsing JSON:", error);
54
+ res.writeHead(400, { "Content-Type": "application/json" });
55
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
56
+ }
57
+ });
58
+ } else {
59
+ res.writeHead(405); // Method Not Allowed
60
+ res.end();
61
+ }
62
+ } else {
63
+ res.writeHead(404); // Not Found
64
+ res.end();
65
+ }
66
+ });
67
+
68
+ server.listen(PORT, () => {
69
+ console.log(`API Server running at http://localhost:${PORT}/blog`);
70
+ });
package/build-styles.js CHANGED
@@ -56,7 +56,8 @@ async function runPostcss(css) {
56
56
  postcss = (await import("postcss")).default;
57
57
  autoprefixer = (await import("autoprefixer")).default;
58
58
  cssnano = (await import("cssnano")).default;
59
- } catch (e) {
59
+ } catch (err) {
60
+ console.error(err);
60
61
  throw new Error("Missing optional dependencies");
61
62
  }
62
63
  } else {
@@ -39,7 +39,8 @@ export default class PostgresAdapter extends SequelizeAdapter {
39
39
  stdio: "inherit",
40
40
  });
41
41
  pgPkg = await import("pg");
42
- } catch (e) {
42
+ } catch (err) {
43
+ console.error(err);
43
44
  throw new Error("Missing optional dependencies");
44
45
  }
45
46
  }
@@ -37,7 +37,8 @@ export default class SequelizeAdapter {
37
37
  stdio: "inherit",
38
38
  });
39
39
  sequelizePkg = await import("sequelize");
40
- } catch (e) {
40
+ } catch (err) {
41
+ console.error(err);
41
42
  throw new Error("Missing optional dependencies");
42
43
  }
43
44
  } else {
@@ -27,7 +27,8 @@ export default class SqliteAdapter extends SequelizeAdapter {
27
27
  stdio: "inherit",
28
28
  });
29
29
  sqlite3Pkg = await import("sqlite3");
30
- } catch (e) {
30
+ } catch (err) {
31
+ console.error(err);
31
32
  throw new Error("Missing optional dependencies");
32
33
  }
33
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -16,14 +16,21 @@
16
16
  ],
17
17
  "author": "lexho111",
18
18
  "license": "ISC",
19
- "dependencies": {},
20
19
  "devDependencies": {
21
20
  "@eslint/js": "^9.39.2",
22
21
  "@types/node": "^25.0.3",
22
+ "autoprefixer": "^10.4.23",
23
+ "cssnano": "^7.1.2",
23
24
  "eslint": "^9.39.2",
24
25
  "eslint-plugin-jest": "^28.6.0",
25
26
  "globals": "^17.0.0",
26
27
  "jest": "^29.7.0",
27
- "typescript": "^5.9.3"
28
+ "pg": "^8.16.3",
29
+ "pg-hstore": "^2.3.4",
30
+ "postcss": "^8.5.6",
31
+ "sequelize": "^6.37.7",
32
+ "sqlite3": "^5.1.7",
33
+ "typescript": "^5.9.3",
34
+ "w3c-css-validator": "^1.4.1"
28
35
  }
29
36
  }
@@ -1,2 +1 @@
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
+ *{font-size:.5rem}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{display:grid;grid-template-columns:1fr;grid-gap:.25rem;border:2px solid #d3d3d3;gap:.25rem}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;padding:.25rem;word-wrap:break-word}.grid article h2{color:#000;margin-bottom:2px}.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,h1{color:#000}nav a{color:#3c3c3c;font-size:20px;-webkit-text-decoration:underline;text-decoration:underline}nav a[href*=login]{display:none}nav a:visited{color:#3c3c3c;text-decoration-color:#3c3c3c}
@@ -1 +1,2 @@
1
- body{font-family:Arial}.grid{border:0 solid #000;display:grid;grid-gap:.25rem;gap:.25rem;grid-template-columns:1fr}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;word-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;-webkit-text-decoration:underline;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}#wrapper{max-width:500px;width:100%}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
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}#wrapper{max-width:500px;width:100%}@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: a07f631befba4b6bc703f8709f5ef455faafeff4e5f00b62f835576eea7fb529 */
package/src/print.css ADDED
@@ -0,0 +1,71 @@
1
+ * {
2
+ font-size: 0.5rem; /* Forces every single element to this size */
3
+ }
4
+
5
+ body {
6
+ color: black;
7
+ background-color: white;
8
+ }
9
+
10
+ h1 {
11
+ font: italic small-caps bold 2.5em/2.3em Verdana, sans-serif;
12
+ margin: 0;
13
+ padding: 0;
14
+ letter-spacing: 0.1em;
15
+ }
16
+
17
+ #wrapper {
18
+ max-width: 600px;
19
+ width: 100%;
20
+ }
21
+
22
+ .grid {
23
+ display: grid;
24
+ grid-template-columns: 1fr;
25
+ gap: 0.25rem;
26
+ border: 2px solid lightgray;
27
+ }
28
+ .grid article {
29
+ padding: 0.25rem;
30
+ border: 0px solid #ccc;
31
+ border-radius: 4px;
32
+ min-width: 0; /* Allow grid items to shrink */
33
+ overflow-wrap: break-word; /* Break long words */
34
+ }
35
+ .grid article h2 {
36
+ color: black;
37
+ margin-bottom: 2px;
38
+ }
39
+ .grid article .datetime {
40
+ margin: 0;
41
+ font-style: italic;
42
+ }
43
+ .grid article p {
44
+ margin-top: 10px;
45
+ margin-bottom: 0;
46
+ }
47
+ .grid article a {
48
+ color: black;
49
+ }
50
+ .grid article a:link a:visited {
51
+ color: black;
52
+ }
53
+
54
+ h1 {
55
+ color: black;
56
+ }
57
+
58
+ nav a {
59
+ text-decoration: underline;
60
+ color: rgb(60, 60, 60);
61
+ font-size: 20px;
62
+ }
63
+ nav a[href*=login] {
64
+ display: none;
65
+ }
66
+ nav a:visited {
67
+ color: rgb(60, 60, 60);
68
+ text-decoration-color: rgb(60, 60, 60);
69
+ }
70
+
71
+ /*# sourceMappingURL=print.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["print.scss"],"names":[],"mappings":"AAKA;EACE;;;AAIF;EACE,OAXQ;EAYR,kBATW;;;AAYb;EAAK;EACL;EAAW;EACX;;;AAGA;EACE;EAAkB;;;AAGpB;EACE;EACA;EACA;EACA;;AAEA;EAIE;EACA;EACA;EACA;EACA;;AAPA;EAAK,OA/BC;EAgCN;;AAOA;EACI;EACA;;AAEJ;EACI;EACF;;AAEF;EACI;;AAEJ;EACI;;;AAKR;EACE,OAzDQ;;;AA6DR;EACE;EACA,OA7DE;EA8DF;;AAGF;EACE;;AAGF;EACE,OAtEE;EAuEF,uBAvEE","file":"print.css"}
package/src/print.scss CHANGED
@@ -29,7 +29,7 @@ letter-spacing: 0.1em;
29
29
  border: 2px solid lightgray;
30
30
 
31
31
  article {
32
- h2 { color: primary;
32
+ h2 { color: $primary;
33
33
  margin-bottom: 2px;
34
34
  }
35
35
  padding: 0.25rem;
@@ -55,7 +55,7 @@ letter-spacing: 0.1em;
55
55
  }
56
56
 
57
57
  h1 {
58
- color: primary;
58
+ color: $primary;
59
59
  }
60
60
 
61
61
  nav {