@lexho111/plainblog 0.5.26 → 0.5.28
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 +10 -12
- package/Formatter.js +3 -1
- package/README.md +4 -0
- package/package.json +2 -1
- package/postinstall.js +89 -0
- package/public/styles.min.css +1 -2
- package/src/styles.css +18 -0
- package/api-server.js +0 -70
package/Blog.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from "path";
|
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { compileStyles, mergeStyles } from "./build-styles.js";
|
|
12
12
|
import pkg from "./package.json" with { type: "json" };
|
|
13
|
+
import process from 'node:process';
|
|
13
14
|
|
|
14
15
|
export default class Blog {
|
|
15
16
|
constructor() {
|
|
@@ -166,9 +167,9 @@ export default class Blog {
|
|
|
166
167
|
res.writeHead(401, { "Content-Type": "text/html" });
|
|
167
168
|
res.end(`${header("My Blog")}
|
|
168
169
|
<body>
|
|
169
|
-
<h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
|
|
170
|
+
<h1>Unauthorized</h1><div class="box"><p>Please enter the password.<form method="POST">
|
|
170
171
|
<input type="password" name="password" placeholder="Password" />
|
|
171
|
-
<button style="margin: 2px;">Login</button></form>
|
|
172
|
+
<button style="margin: 2px;">Login</button></form></div>
|
|
172
173
|
</body></html>`);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
@@ -210,7 +211,7 @@ export default class Blog {
|
|
|
210
211
|
const __filename = fileURLToPath(import.meta.url);
|
|
211
212
|
const __dirname = path.dirname(__filename);
|
|
212
213
|
const srcStylePath = path.join(__dirname, "src", "styles.css");
|
|
213
|
-
const publicStylePath = path.join(
|
|
214
|
+
const publicStylePath = path.join(process.cwd(), "public", "styles.min.css");
|
|
214
215
|
|
|
215
216
|
let publicHash = null;
|
|
216
217
|
let srcStyles = "";
|
|
@@ -243,7 +244,7 @@ export default class Blog {
|
|
|
243
244
|
console.log("Styles have changed. Recompiling...");
|
|
244
245
|
const finalStyles = await mergeStyles(this.#styles, srcStyles);
|
|
245
246
|
try {
|
|
246
|
-
|
|
247
|
+
await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
|
|
247
248
|
await fs.promises.writeFile(
|
|
248
249
|
publicStylePath,
|
|
249
250
|
finalStyles + `\n/* source-hash: ${srcHash} */`
|
|
@@ -355,9 +356,9 @@ export default class Blog {
|
|
|
355
356
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
356
357
|
res.end(`${header("My Blog")}
|
|
357
358
|
<body>
|
|
358
|
-
<h1>Login</h1><form method="POST">
|
|
359
|
+
<h1>Login</h1><div class="box"><form method="POST">
|
|
359
360
|
<input type="password" name="password" placeholder="Password" />
|
|
360
|
-
<button style="margin: 2px;">Login</button></form>
|
|
361
|
+
<button style="margin: 2px;">Login</button></form></div>
|
|
361
362
|
</body></html>`);
|
|
362
363
|
return;
|
|
363
364
|
} else if (req.method === "POST") {
|
|
@@ -410,9 +411,7 @@ export default class Blog {
|
|
|
410
411
|
} else {
|
|
411
412
|
// Try to serve static files from public folder
|
|
412
413
|
try {
|
|
413
|
-
const
|
|
414
|
-
const __dirname = path.dirname(__filename);
|
|
415
|
-
const publicDir = path.join(__dirname, "public");
|
|
414
|
+
const publicDir = path.join(process.cwd(), "public");
|
|
416
415
|
const parsedUrl = new URL(
|
|
417
416
|
req.url,
|
|
418
417
|
`http://${req.headers.host || "localhost"}`
|
|
@@ -648,10 +647,9 @@ export default class Blog {
|
|
|
648
647
|
this.compiledStyles = await compileStyles(fileData);
|
|
649
648
|
|
|
650
649
|
// generate a file
|
|
651
|
-
const
|
|
652
|
-
const __dirname = path.dirname(__filename);
|
|
653
|
-
const publicDir = path.join(__dirname, "public");
|
|
650
|
+
const publicDir = path.join(process.cwd(), "public");
|
|
654
651
|
|
|
652
|
+
await fs.promises.mkdir(publicDir, { recursive: true });
|
|
655
653
|
await fs.promises.writeFile(
|
|
656
654
|
path.join(publicDir, "styles.min.css"),
|
|
657
655
|
this.compiledStyles + `\n/* source-hash: ${currentHash} */`
|
package/Formatter.js
CHANGED
|
@@ -27,11 +27,13 @@ export function formatHTML(data) {
|
|
|
27
27
|
let form1 = "";
|
|
28
28
|
if (data.loggedin) {
|
|
29
29
|
form1 = `<form action="/" method="POST">
|
|
30
|
+
<div class="box">
|
|
30
31
|
<h3>Add a New Article</h3>
|
|
31
32
|
<input type="text" id="title" name="title" placeholder="Article Title" required style="display: block; width: 300px; margin-bottom: 10px;">
|
|
32
33
|
<textarea id="content" name="content" placeholder="Article Content" required style="display: block; width: 300px; height: 100px; margin-bottom: 10px;"></textarea>
|
|
33
34
|
<button type="submit">Add Article</button>${button}
|
|
34
35
|
</form>
|
|
36
|
+
</div>
|
|
35
37
|
<hr>`;
|
|
36
38
|
}
|
|
37
39
|
const form = form1;
|
|
@@ -42,7 +44,7 @@ export function formatHTML(data) {
|
|
|
42
44
|
</nav>
|
|
43
45
|
<div id="header">
|
|
44
46
|
<h1>${data.title}</h1>
|
|
45
|
-
|
|
47
|
+
<img src="headerphoto.jpg" onerror="this.classList.add('hide-image')" />
|
|
46
48
|
</div>
|
|
47
49
|
<div id="wrapper">
|
|
48
50
|
${form}
|
package/README.md
CHANGED
|
@@ -26,6 +26,10 @@ Now you can open your blog in your webbrowser on `http://localhost:8080`. Login
|
|
|
26
26
|
|
|
27
27
|
## More Features
|
|
28
28
|
|
|
29
|
+
### add a header photo
|
|
30
|
+
|
|
31
|
+
To add a headerphoto to your blog simply name it "headerphoto.jpg" and put it in the _public_ folder.
|
|
32
|
+
|
|
29
33
|
### set a Database Adapter
|
|
30
34
|
|
|
31
35
|
#### connect to a sqlite database
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexho111/plainblog",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.28",
|
|
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
8
|
"dev": "node index.js",
|
|
9
|
+
"postinstall": "node postinstall.js",
|
|
9
10
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
10
11
|
"lint": "eslint ."
|
|
11
12
|
},
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
// This script is executed after the package is installed.
|
|
7
|
+
// Its purpose is to copy default static assets from the package's `public`
|
|
8
|
+
// directory to the user's project's `public` directory.
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
try {
|
|
15
|
+
// The source is the 'public' directory inside our package.
|
|
16
|
+
// We use __dirname to be sure we are looking inside the package itself.
|
|
17
|
+
const sourceDir = path.join(__dirname, "public");
|
|
18
|
+
|
|
19
|
+
// The destination is the 'public' folder in the user's project root.
|
|
20
|
+
// process.env.INIT_CWD is the directory where `npm install` was run.
|
|
21
|
+
// If not available, we fallback to traversing up from node_modules.
|
|
22
|
+
const projectRoot =
|
|
23
|
+
process.env.INIT_CWD || path.resolve(__dirname, "../..");
|
|
24
|
+
const destDir = path.join(projectRoot, "public");
|
|
25
|
+
|
|
26
|
+
console.log(
|
|
27
|
+
`[plainblog] Postinstall: Copying assets from ${sourceDir} to ${destDir}`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// 1. Check if source directory exists
|
|
31
|
+
try {
|
|
32
|
+
await fs.promises.access(sourceDir);
|
|
33
|
+
} catch {
|
|
34
|
+
console.log(
|
|
35
|
+
`[plainblog] Source directory '${sourceDir}' not found. Skipping.`
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Avoid copying if source and destination are the same (e.g. local dev install)
|
|
41
|
+
if (path.relative(sourceDir, destDir) === "") {
|
|
42
|
+
console.log(
|
|
43
|
+
"[plainblog] Source and destination are the same. Skipping copy."
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure the user's public directory exists, creating it if not.
|
|
49
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
50
|
+
|
|
51
|
+
const filesToCopy = await fs.promises.readdir(sourceDir);
|
|
52
|
+
|
|
53
|
+
for (const file of filesToCopy) {
|
|
54
|
+
const sourceFile = path.join(sourceDir, file);
|
|
55
|
+
const destFile = path.join(destDir, file);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Check if destination file exists.
|
|
59
|
+
await fs.promises.access(destFile);
|
|
60
|
+
// If it exists, we do nothing to avoid overwriting user files.
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.code === "ENOENT") {
|
|
63
|
+
// File doesn't exist in the destination, so copy it.
|
|
64
|
+
try {
|
|
65
|
+
await fs.promises.copyFile(sourceFile, destFile);
|
|
66
|
+
console.log(
|
|
67
|
+
`[plainblog] Copied default asset '${file}' to project's '/public' directory.`
|
|
68
|
+
);
|
|
69
|
+
} catch (copyError) {
|
|
70
|
+
console.error(
|
|
71
|
+
`[plainblog] Error copying asset '${file}':`,
|
|
72
|
+
copyError
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
console.error(
|
|
77
|
+
`[plainblog] Error checking asset '${destFile}':`,
|
|
78
|
+
error
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// We log errors but don't fail the entire installation.
|
|
85
|
+
console.error("[plainblog] Postinstall error:", error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main();
|
package/public/styles.min.css
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
body{font-family:Arial;
|
|
2
|
-
/* source-hash: a07f631befba4b6bc703f8709f5ef455faafeff4e5f00b62f835576eea7fb529 */
|
|
1
|
+
body{font-family:Arial;margin:0}nav{margin-top:10px}.box,h1,nav{margin-left:10px}.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%}.hide-image{display:none}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
|
package/src/styles.css
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: Arial;
|
|
4
|
+
}
|
|
5
|
+
nav {
|
|
6
|
+
margin-top: 10px;
|
|
7
|
+
}
|
|
8
|
+
nav,
|
|
9
|
+
h1 {
|
|
10
|
+
margin-left: 10px;
|
|
11
|
+
}
|
|
12
|
+
.box {
|
|
13
|
+
margin-left: 10px;
|
|
14
|
+
}
|
|
1
15
|
.grid {
|
|
2
16
|
border: 0 solid #000;
|
|
3
17
|
display: grid;
|
|
@@ -52,6 +66,10 @@ nav a:visited {
|
|
|
52
66
|
width: 100%;
|
|
53
67
|
}
|
|
54
68
|
|
|
69
|
+
.hide-image {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
/* Mobile Layout (screens smaller than 1000px) */
|
|
56
74
|
@media screen and (max-width: 1000px) {
|
|
57
75
|
* {
|
package/api-server.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
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
|
-
});
|