@lexho111/plainblog 0.7.2 → 0.7.4

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/Article.js CHANGED
@@ -1,96 +1,88 @@
1
- export default class Article {
2
- constructor(id, title, content, createdAt) {
3
- this.id = id;
4
- this.title = title;
5
- this.content = content;
6
- this.createdAt = new Date(createdAt).getTime();
7
- }
8
-
9
- static createNew(title, content, createdAt = new Date()) {
10
- const id = this.getNextID(); // Your existing ID generator
11
- //const createdAt = new Date();
12
- return new Article(id, title, content, createdAt);
13
- }
14
-
15
- static nextID = 100;
16
- static getNextID() {
17
- // TODO fix use nextID from BlogTreeSearch!
18
- this.nextID++;
19
- return Math.floor(Math.random() * 100000000);
20
- }
21
-
22
- getContent() {
23
- return this.content;
24
- }
25
-
26
- getContentVeryShort() {
27
- if (!this.content) return "";
28
- if (this.content.length < 50) return this.content;
29
- let contentShort = this.content.substring(0, 50);
30
- contentShort += "...";
31
- return contentShort;
32
- }
33
-
34
- getContentShort() {
35
- if (!this.content) return "";
36
- if (this.content.length < 400) return this.content;
37
- let contentShort = this.content.substring(0, 400);
38
- contentShort += "...";
39
- return contentShort;
40
- }
41
-
42
- /**
43
- * Returns a JSON-serializable representation of the article.
44
- * This method is automatically called by JSON.stringify().
45
- */
46
- toJSON() {
47
- // Return a plain object with the desired properties
48
- let date = new Date();
49
- try {
50
- date = new Date(this.createdAt).toISOString();
51
- } catch (err) {
52
- console.error(err);
53
- }
54
- return {
55
- id: this.id,
56
- title: this.title,
57
- content: this.content,
58
- createdAt: date,
59
- };
60
- }
61
-
62
- /**
63
- * Returns a JSON-serializable representation of the article.
64
- * This method is automatically called by JSON.stringify().
65
- */
66
- json() {
67
- return this.toJSON();
68
- }
69
-
70
- getFormattedDate() {
71
- const date = new Date(this.createdAt);
72
- const year = date.getFullYear();
73
- const month = String(date.getMonth() + 1).padStart(2, "0");
74
- const day = String(date.getDate()).padStart(2, "0");
75
- const hours = String(date.getHours()).padStart(2, "0");
76
- const minutes = String(date.getMinutes()).padStart(2, "0");
77
- return `${year}/${month}/${day} ${hours}:${minutes}`;
78
- }
79
-
80
- toHTML(loggedin = false) {
81
- if (this.content == null) return "";
82
- const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
83
- const buttons = loggedin // prettier-ignore
84
- ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
85
- : "";
86
- // prettier-ignore
87
- return `<article data-id="${this.id}" data-date="${this.createdAt}">
1
+ export default class Article {
2
+ constructor(id, title, content, createdAt) {
3
+ this.id = id;
4
+ this.title = title;
5
+ this.content = content;
6
+ this.createdAt = new Date(createdAt).getTime();
7
+ }
8
+ static createNew(title, content, createdAt = new Date()) {
9
+ const id = this.getNextID(); // Your existing ID generator
10
+ //const createdAt = new Date();
11
+ return new Article(id, title, content, createdAt);
12
+ }
13
+ static getNextID() {
14
+ // Generiert eine kollisionssichere ID basierend auf Zeitstempel + Zufall
15
+ // Passt in JS Safe Integer und SQLite Integer (64-bit)
16
+ return Date.now() * 1000 + Math.floor(Math.random() * 1000);
17
+ }
18
+ getContent() {
19
+ return this.content;
20
+ }
21
+ getContentVeryShort() {
22
+ if (!this.content)
23
+ return "";
24
+ if (this.content.length < 50)
25
+ return this.content;
26
+ let contentShort = this.content.substring(0, 50);
27
+ contentShort += "...";
28
+ return contentShort;
29
+ }
30
+ getContentShort() {
31
+ if (!this.content)
32
+ return "";
33
+ if (this.content.length < 400)
34
+ return this.content;
35
+ let contentShort = this.content.substring(0, 400);
36
+ contentShort += "...";
37
+ return contentShort;
38
+ }
39
+ /**
40
+ * Returns a JSON-serializable representation of the article.
41
+ * This method is automatically called by JSON.stringify().
42
+ */
43
+ toJSON() {
44
+ // Return a plain object with the desired properties
45
+ const date = !isNaN(this.createdAt)
46
+ ? new Date(this.createdAt).toISOString()
47
+ : new Date().toISOString();
48
+ return {
49
+ id: this.id,
50
+ title: this.title,
51
+ content: this.content,
52
+ createdAt: date,
53
+ };
54
+ }
55
+ /**
56
+ * Returns a JSON-serializable representation of the article.
57
+ * This method is automatically called by JSON.stringify().
58
+ */
59
+ json() {
60
+ return this.toJSON();
61
+ }
62
+ getFormattedDate() {
63
+ const date = new Date(this.createdAt);
64
+ const year = date.getFullYear();
65
+ const month = String(date.getMonth() + 1).padStart(2, "0");
66
+ const day = String(date.getDate()).padStart(2, "0");
67
+ const hours = String(date.getHours()).padStart(2, "0");
68
+ const minutes = String(date.getMinutes()).padStart(2, "0");
69
+ return `${year}/${month}/${day} ${hours}:${minutes}`;
70
+ }
71
+ toHTML(loggedin = false) {
72
+ if (this.content == null)
73
+ return "";
74
+ const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
75
+ const buttons = loggedin // prettier-ignore
76
+ ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
77
+ : "";
78
+ // prettier-ignore
79
+ return `<article data-id="${this.id}" data-date="${this.createdAt}">
88
80
  ${buttons}
89
81
  <h2>${this.title}</h2>
90
82
  <span class="datetime">${this.getFormattedDate()}</span>
91
83
  <p>${this.getContentShort()}</p>
92
84
  <div class="full-content" style="display: none;">${this.content}</div>
93
85
  ${moreButton}
94
- </article>`;
95
- }
96
- }
86
+ </article>`;
87
+ }
88
+ }
package/Article.ts ADDED
@@ -0,0 +1,97 @@
1
+ export default class Article {
2
+ id: number;
3
+ title: string;
4
+ content: string;
5
+ createdAt: number;
6
+
7
+ constructor(id: number, title: string, content: string, createdAt: string | number | Date) {
8
+ this.id = id;
9
+ this.title = title;
10
+ this.content = content;
11
+ this.createdAt = new Date(createdAt).getTime();
12
+ }
13
+
14
+ static createNew(title: string, content: string, createdAt: string | number | Date = new Date()): Article {
15
+ const id = this.getNextID(); // Your existing ID generator
16
+ //const createdAt = new Date();
17
+ return new Article(id, title, content, createdAt);
18
+ }
19
+
20
+ static getNextID(): number {
21
+ // Generiert eine kollisionssichere ID basierend auf Zeitstempel + Zufall
22
+ // Passt in JS Safe Integer und SQLite Integer (64-bit)
23
+ return Date.now() * 1000 + Math.floor(Math.random() * 1000);
24
+ }
25
+
26
+ getContent(): string {
27
+ return this.content;
28
+ }
29
+
30
+ getContentVeryShort(): string {
31
+ if (!this.content) return "";
32
+ if (this.content.length < 50) return this.content;
33
+ let contentShort = this.content.substring(0, 50);
34
+ contentShort += "...";
35
+ return contentShort;
36
+ }
37
+
38
+ getContentShort(): string {
39
+ if (!this.content) return "";
40
+ if (this.content.length < 400) return this.content;
41
+ let contentShort = this.content.substring(0, 400);
42
+ contentShort += "...";
43
+ return contentShort;
44
+ }
45
+
46
+ /**
47
+ * Returns a JSON-serializable representation of the article.
48
+ * This method is automatically called by JSON.stringify().
49
+ */
50
+ toJSON(): { id: number; title: string; content: string; createdAt: string } {
51
+ // Return a plain object with the desired properties
52
+ const date = !isNaN(this.createdAt)
53
+ ? new Date(this.createdAt).toISOString()
54
+ : new Date().toISOString();
55
+ return {
56
+ id: this.id,
57
+ title: this.title,
58
+ content: this.content,
59
+ createdAt: date,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Returns a JSON-serializable representation of the article.
65
+ * This method is automatically called by JSON.stringify().
66
+ */
67
+ json(): { id: number; title: string; content: string; createdAt: string } {
68
+ return this.toJSON();
69
+ }
70
+
71
+ getFormattedDate(): string {
72
+ const date = new Date(this.createdAt);
73
+ const year = date.getFullYear();
74
+ const month = String(date.getMonth() + 1).padStart(2, "0");
75
+ const day = String(date.getDate()).padStart(2, "0");
76
+ const hours = String(date.getHours()).padStart(2, "0");
77
+ const minutes = String(date.getMinutes()).padStart(2, "0");
78
+ return `${year}/${month}/${day} ${hours}:${minutes}`;
79
+ }
80
+
81
+ toHTML(loggedin = false): string {
82
+ if (this.content == null) return "";
83
+ const moreButton = this.content.length > 400 ? `<a href="#">more</a>` : "";
84
+ const buttons = loggedin // prettier-ignore
85
+ ? `<div class="buttons"><div id="editButton${this.id}" class="btn edit" role="button" tabindex="0" data-action="edit" data-id="${this.id}">edit</div><div id="deleteButton${this.id}" class="btn delete" role="button" tabindex="0" data-action="delete" data-id="${this.id}">delete</div><div id="somethingelseButton${this.id}" class="btn light">something else</div></div>`
86
+ : "";
87
+ // prettier-ignore
88
+ return `<article data-id="${this.id}" data-date="${this.createdAt}">
89
+ ${buttons}
90
+ <h2>${this.title}</h2>
91
+ <span class="datetime">${this.getFormattedDate()}</span>
92
+ <p>${this.getContentShort()}</p>
93
+ <div class="full-content" style="display: none;">${this.content}</div>
94
+ ${moreButton}
95
+ </article>`;
96
+ }
97
+ }
package/AssetManager.js CHANGED
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { Worker } from "node:worker_threads";
6
6
  import { createDebug } from "./debug-loader.js";
7
+ import { checkSourceCSS } from "./modules/csscompiler/csscompiler.js";
7
8
 
8
9
  const debug = createDebug("plainblog:AssetManager");
9
10
  const __filename = fileURLToPath(import.meta.url);
@@ -13,27 +14,30 @@ export default class AssetManager {
13
14
  constructor() {
14
15
  this.publicDir = path.join(process.cwd(), "public");
15
16
  this.reloadStylesOnGET = false;
16
- this.stylesheetPath = null;
17
+ this._stylesheetPath = null;
17
18
  this.compilestyle = false;
18
- this.styles = "";
19
- this.compiledStyles = "";
20
- this.stylesHash = "";
19
+ this._styles = "";
21
20
  this.scriptsHash = "";
22
21
  }
23
22
 
24
- setStyle(style) {
25
- this.styles += style;
23
+ set style(value) {
24
+ debug("set style %s", value);
25
+ this._styles += value;
26
26
  this.compilestyle = true;
27
27
  }
28
28
 
29
+ set stylesheetPath(value) {
30
+ debug(`set Stylesheet path: ${value}`);
31
+ this._stylesheetPath = value;
32
+ }
33
+
34
+ get stylesheetPath() {
35
+ return this._stylesheetPath;
36
+ }
37
+
29
38
  async init() {
30
- // if there is a stylesheet path provided, process it
31
- if (this.stylesheetPath != null && this.compilestyle) {
32
- await this.processStylesheets(this.stylesheetPath);
33
- }
34
- if (!this.stylesheetPath) {
35
- await this.processDefaultStyles();
36
- }
39
+ debug("init");
40
+ await this.processStyles();
37
41
 
38
42
  // Process Scripts
39
43
  const srcScriptPath = path.join(__dirname, "src", "fetchData.js");
@@ -42,112 +46,35 @@ export default class AssetManager {
42
46
 
43
47
  async reload() {
44
48
  debug("reload");
45
- if (this.stylesheetPath != null && this.compilestyle) {
46
- await this.processStylesheets(this.stylesheetPath);
47
- }
48
- if (!this.stylesheetPath) {
49
- await this.processDefaultStyles();
50
- }
49
+ await this.processStyles();
51
50
  const srcScriptPath = path.join(__dirname, "src", "fetchData.js");
52
51
  await this.processScripts(srcScriptPath);
53
52
  }
54
53
 
55
- async processDefaultStyles() {
56
- // compile and merge hardcoded styles in "this.styles" with "src/styles.css"
57
- const srcStylePath = path.join(__dirname, "src", "styles.css");
58
- const publicStylePath = path.join(this.publicDir, "styles.min.css");
59
-
60
- let publicHash = null;
61
- let srcStyles = "";
62
-
63
- await Promise.all([
64
- fs.promises
65
- .readFile(publicStylePath, "utf8")
66
- .then((publicCSS) => {
67
- const match = publicCSS.match(
68
- /\/\* source-hash: ([a-f0-9]{64}) \*\//,
69
- );
70
- if (match) publicHash = match[1];
71
- })
72
- .catch((err) => {
73
- if (err.code !== "ENOENT" && !err.message.includes("ENOENT"))
74
- console.error(err);
75
- }),
76
- fs.promises
77
- .readFile(srcStylePath, "utf8")
78
- .then((content) => {
79
- srcStyles = content;
80
- })
81
- .catch((err) => {
82
- if (err.code !== "ENOENT" && !err.message.includes("ENOENT"))
83
- console.error(err);
84
- }),
85
- ]);
86
-
87
- const combinedStyles = srcStyles + "\n" + this.styles;
88
- const srcHash = crypto
89
- .createHash("sha256")
90
- .update(combinedStyles)
91
- .digest("hex");
92
-
93
- if (srcHash !== publicHash) {
94
- debug("Styles have changed. Recompiling in worker...");
95
- const finalStyles = await this.runWorker("mergeStyles", [
96
- srcStyles,
97
- this.styles,
98
- ]);
99
-
100
- try {
101
- await fs.promises.mkdir(path.dirname(publicStylePath), {
102
- recursive: true,
103
- });
104
- await fs.promises.writeFile(
105
- publicStylePath,
106
- finalStyles + `\n/* source-hash: ${srcHash} */`,
54
+ async processStyles(compilerOptions = {}) {
55
+ let resolvedStylesheetPath = this.stylesheetPath;
56
+ if (resolvedStylesheetPath) {
57
+ if (Array.isArray(resolvedStylesheetPath)) {
58
+ resolvedStylesheetPath = resolvedStylesheetPath.map((p) =>
59
+ path.isAbsolute(p) ? p : path.resolve(process.cwd(), p),
107
60
  );
108
- } catch (err) {
109
- console.error("Failed to write styles to public folder:", err);
110
- }
111
- }
112
- }
113
-
114
- async processStylesheets(files) {
115
- debug("process stylesheets");
116
- const fileList = Array.isArray(files) ? files : [files];
117
- const styleFiles = fileList.filter(
118
- (f) =>
119
- typeof f === "string" &&
120
- (f.endsWith(".scss") || f.endsWith(".css")) &&
121
- !f.endsWith(".min.css"),
122
- );
123
-
124
- if (styleFiles.length > 0) {
125
- const fileData = await Promise.all(
126
- styleFiles.sort().map(async (f) => {
127
- const content = await fs.promises.readFile(f, "utf-8");
128
- if (content == "") throw new Error("Invalid Filepath or empty file!");
129
- return { path: f, content };
130
- }),
131
- );
132
-
133
- const hash = crypto.createHash("sha256");
134
- for (const file of fileData) hash.update(file.content);
135
- const currentHash = hash.digest("hex");
136
-
137
- if (currentHash !== this.stylesHash && this.compilestyle) {
138
- console.log("style assets have changed. recompiling in worker...");
139
- this.stylesHash = currentHash;
140
- this.compiledStyles = await this.runWorker("styles", fileData);
141
-
142
- await fs.promises.mkdir(this.publicDir, { recursive: true });
143
- await fs.promises.writeFile(
144
- path.join(this.publicDir, "styles.min.css"),
145
- this.compiledStyles + `\n/* source-hash: ${currentHash} */`,
61
+ } else if (
62
+ typeof resolvedStylesheetPath === "string" &&
63
+ !path.isAbsolute(resolvedStylesheetPath)
64
+ ) {
65
+ resolvedStylesheetPath = path.resolve(
66
+ process.cwd(),
67
+ resolvedStylesheetPath,
146
68
  );
147
- } else {
148
- console.log("styles are up-to-date");
149
69
  }
150
70
  }
71
+ await checkSourceCSS({
72
+ stylesheetPath: resolvedStylesheetPath,
73
+ style: this._styles,
74
+ outputDir: this.publicDir,
75
+ outputPath: path.join(this.publicDir, "styles.min.css"),
76
+ ...compilerOptions,
77
+ });
151
78
  }
152
79
 
153
80
  async processScripts(files) {
@@ -184,14 +111,28 @@ export default class AssetManager {
184
111
  }
185
112
 
186
113
  if (currentHash !== this.scriptsHash) {
187
- console.log("script assets have changed. recompiling in worker...");
188
- this.scriptsHash = currentHash;
189
- const compiledScripts = await this.runWorker("scripts", fileData);
114
+ console.log("script assets have changed. recompiling...");
115
+ let compiledScripts;
116
+ let success = false;
117
+ try {
118
+ compiledScripts = await this.runWorker("scripts", fileData);
119
+ success = true;
120
+ this.scriptsHash = currentHash;
121
+ } catch (e) {
122
+ console.warn(
123
+ "Script compilation failed (using fallback):",
124
+ e.message,
125
+ );
126
+ compiledScripts = fileData.map((f) => f.content).join(";\n");
127
+ }
190
128
 
191
129
  await fs.promises.mkdir(this.publicDir, { recursive: true });
130
+ const content = success
131
+ ? compiledScripts + `\n/* source-hash: ${currentHash} */`
132
+ : compiledScripts;
192
133
  await fs.promises.writeFile(
193
134
  path.join(this.publicDir, "scripts.min.js"),
194
- compiledScripts + `\n/* source-hash: ${currentHash} */`,
135
+ content,
195
136
  );
196
137
  }
197
138
  }
@@ -205,11 +146,17 @@ export default class AssetManager {
205
146
  : { type, fileData: data };
206
147
 
207
148
  const worker = new Worker(
208
- path.resolve(__dirname, "workers", "compiler-worker.js"),
149
+ path.resolve(
150
+ __dirname,
151
+ "modules",
152
+ "jscompiler",
153
+ "compile-js-worker.js",
154
+ ),
209
155
  { workerData },
210
156
  );
211
157
  worker.on("message", (msg) => {
212
158
  if (msg.status === "success") {
159
+ console.log("compilation complete: public/styles.min.css created");
213
160
  resolve(msg.result);
214
161
  } else {
215
162
  reject(new Error(msg.error));
package/Auth.js CHANGED
@@ -16,14 +16,14 @@ export default class Auth {
16
16
  if (!req.headers.cookie) {
17
17
  const time_end = performance.now();
18
18
  const duration = time_end - time_start;
19
- debug_perf("isAuthenticated took " + duration + "ms", duration);
19
+ debug_perf("isAuthenticated", duration);
20
20
  return false;
21
21
  }
22
22
  const match = req.headers.cookie.match(/(?:^|;\s*)session=([^;]*)/);
23
23
  const result = match ? this.sessions.has(match[1]) : false;
24
24
  const time_end = performance.now();
25
25
  const duration = time_end - time_start;
26
- debug_perf("isAuthenticated took " + duration + "ms", duration);
26
+ debug_perf("isAuthenticated", duration);
27
27
  return result;
28
28
  }
29
29