@lexho111/plainblog 0.5.28 → 0.6.1

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 (64) hide show
  1. package/Article.js +73 -4
  2. package/Blog.js +342 -140
  3. package/Formatter.js +2 -11
  4. package/README.md +1 -1
  5. package/{blog → blog_test_empty.db} +0 -0
  6. package/blog_test_load.db +0 -0
  7. package/build-scripts.js +54 -0
  8. package/coverage/clover.xml +1051 -0
  9. package/coverage/coverage-final.json +20 -0
  10. package/coverage/lcov-report/base.css +224 -0
  11. package/coverage/lcov-report/block-navigation.js +87 -0
  12. package/coverage/lcov-report/favicon.png +0 -0
  13. package/coverage/lcov-report/index.html +161 -0
  14. package/coverage/lcov-report/package/Article.js.html +406 -0
  15. package/coverage/lcov-report/package/Blog.js.html +2674 -0
  16. package/coverage/lcov-report/package/Formatter.js.html +379 -0
  17. package/coverage/lcov-report/package/build-scripts.js.html +247 -0
  18. package/coverage/lcov-report/package/build-styles.js.html +367 -0
  19. package/coverage/lcov-report/package/index.html +191 -0
  20. package/coverage/lcov-report/package/model/APIModel.js.html +190 -0
  21. package/coverage/lcov-report/package/model/ArrayList.js.html +382 -0
  22. package/coverage/lcov-report/package/model/ArrayListHashMap.js.html +379 -0
  23. package/coverage/lcov-report/package/model/BinarySearchTree.js.html +856 -0
  24. package/coverage/lcov-report/package/model/BinarySearchTreeHashMap.js.html +346 -0
  25. package/coverage/lcov-report/package/model/DataModel.js.html +322 -0
  26. package/coverage/lcov-report/package/model/DatabaseModel.js.html +232 -0
  27. package/coverage/lcov-report/package/model/FileAdapter.js.html +394 -0
  28. package/coverage/lcov-report/package/model/FileList.js.html +244 -0
  29. package/coverage/lcov-report/package/model/FileModel.js.html +358 -0
  30. package/coverage/lcov-report/package/model/SequelizeAdapter.js.html +538 -0
  31. package/coverage/lcov-report/package/model/SqliteAdapter.js.html +247 -0
  32. package/coverage/lcov-report/package/model/datastructures/ArrayList.js.html +439 -0
  33. package/coverage/lcov-report/package/model/datastructures/ArrayListHashMap.js.html +196 -0
  34. package/coverage/lcov-report/package/model/datastructures/BinarySearchTree.js.html +913 -0
  35. package/coverage/lcov-report/package/model/datastructures/BinarySearchTreeHashMap.js.html +352 -0
  36. package/coverage/lcov-report/package/model/datastructures/FileList.js.html +244 -0
  37. package/coverage/lcov-report/package/model/datastructures/index.html +176 -0
  38. package/coverage/lcov-report/package/model/index.html +206 -0
  39. package/coverage/lcov-report/package/utilities.js.html +511 -0
  40. package/coverage/lcov-report/prettify.css +1 -0
  41. package/coverage/lcov-report/prettify.js +2 -0
  42. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  43. package/coverage/lcov-report/sorter.js +210 -0
  44. package/coverage/lcov.info +2078 -0
  45. package/index.js +25 -1
  46. package/model/DataModel.js +80 -0
  47. package/model/DatabaseModel.js +21 -8
  48. package/model/FileAdapter.js +44 -4
  49. package/model/FileModel.js +48 -9
  50. package/model/SequelizeAdapter.js +11 -3
  51. package/model/datastructures/ArrayList.js +118 -0
  52. package/model/datastructures/ArrayListHashMap.js +37 -0
  53. package/model/datastructures/ArrayListHashMap.js.bk +90 -0
  54. package/model/datastructures/BinarySearchTree.js +277 -0
  55. package/model/datastructures/BinarySearchTreeHashMap.js +90 -0
  56. package/model/datastructures/BinarySearchTreeTest.js +16 -0
  57. package/model/datastructures/FileList.js +53 -0
  58. package/package.json +9 -3
  59. package/public/fetchData.js +0 -0
  60. package/public/scripts.min.js +2 -1
  61. package/public/styles.min.css +2 -1
  62. package/src/fetchData.js +150 -30
  63. package/src/styles.css +29 -0
  64. package/utilities.js +142 -0
package/src/fetchData.js CHANGED
@@ -2,8 +2,45 @@ let isLoading = false;
2
2
 
3
3
  document.addEventListener("DOMContentLoaded", () => {
4
4
  window.addEventListener("scroll", handleScroll);
5
+
6
+ const searchInput = document.getElementById("search");
7
+ if (searchInput) {
8
+ searchInput.addEventListener(
9
+ "input",
10
+ debounce(async (e) => {
11
+ const query = e.target.value;
12
+ if (query.length > 0) {
13
+ const res = await fetch(
14
+ `/api/articles?q=${encodeURIComponent(query)}`,
15
+ );
16
+ const data = await res.json();
17
+ const container = document.getElementById("articles");
18
+ container.innerHTML = "";
19
+ const fragment = document.createDocumentFragment();
20
+ // Limit results to 50 to prevent browser freeze
21
+ const articles = data.articles ? data.articles.slice(0, 50) : [];
22
+ articles.forEach((a) =>
23
+ fillWithContent(a.title, a.content, a.createdAt, a.id, fragment),
24
+ );
25
+ container.appendChild(fragment);
26
+ } else {
27
+ window.location.reload();
28
+ }
29
+ }, 300),
30
+ );
31
+ }
5
32
  });
6
33
 
34
+ function debounce(func, timeout = 300) {
35
+ let timer;
36
+ return (...args) => {
37
+ clearTimeout(timer);
38
+ timer = setTimeout(() => {
39
+ func.apply(this, args);
40
+ }, timeout);
41
+ };
42
+ }
43
+
7
44
  async function handleScroll() {
8
45
  if (isLoading) return;
9
46
  const scrollable = document.documentElement.scrollHeight - window.innerHeight;
@@ -22,42 +59,69 @@ async function handleScroll() {
22
59
  async function loadMore() {
23
60
  console.log(`load more`);
24
61
  const articlesContainer = document.getElementById("articles");
25
- const articles = articlesContainer.querySelectorAll("article");
26
- const lastArticle = articles[articles.length - 1];
27
- console.log("lastArticle", lastArticle);
28
- if (!lastArticle) return;
29
-
30
- const lastId = parseInt(lastArticle.getAttribute("data-id"));
31
- console.log(`lastId ${lastId}`);
32
- if (isNaN(lastId)) return;
33
-
34
- // Fetch older items: startID should be lastId - 1 (since we want older/smaller IDs)
35
- const nextId = lastId - 1;
36
- if (nextId < 1) {
37
- console.log(`nextId < 1 ${nextId < 1}`);
62
+ const articles = Array.from(articlesContainer.querySelectorAll("article"));
63
+
64
+ // Find the last article that has a valid data-date
65
+ let lastTimestamp = NaN;
66
+ for (let i = articles.length - 1; i >= 0; i--) {
67
+ const val = articles[i].getAttribute("data-date");
68
+ if (val) {
69
+ const parsed = parseInt(val, 10);
70
+ if (!isNaN(parsed)) {
71
+ lastTimestamp = parsed;
72
+ break;
73
+ }
74
+ }
75
+ }
76
+
77
+ console.log(`lastTimestamp ${lastTimestamp}`);
78
+ if (isNaN(lastTimestamp)) {
38
79
  window.removeEventListener("scroll", handleScroll);
39
80
  return;
40
81
  }
41
82
 
83
+ //const lastDate = new Date(lastTimestamp);
84
+ /*let endDate = null;
85
+ const lastArticle = articles[articles.length - 1]; //
86
+ if (lastArticle) {
87
+ endDate = lastArticle.dataset.date;
88
+ }*/
89
+ const endDate = lastTimestamp;
90
+
42
91
  isLoading = true;
43
- const url = `/api/articles?startID=${nextId}&limit=5`;
92
+ const limit = 50;
93
+ // Fetch one more than needed to handle the duplicate last item.
94
+ const url = `/api/articles?enddate=${endDate}&limit=${limit + 1}`;
44
95
  console.log(`load more ${url}`);
96
+ console.log(`endDate: ${new Date(endDate)}`);
45
97
  try {
46
98
  const response = await fetch(url);
47
99
  if (response.ok) {
48
100
  const result = await response.json();
49
- if (result.articles && result.articles.length > 0) {
50
- for (const article of result.articles) {
51
- fillWithContent(
52
- article.title,
53
- article.content,
54
- article.createdAt,
55
- article.id
56
- );
101
+ let newArticles = result.articles || [];
102
+
103
+ if (newArticles.length > 0) {
104
+ let addedCount = 0;
105
+ const fragment = document.createDocumentFragment();
106
+ for (const article of newArticles) {
107
+ if (
108
+ fillWithContent(
109
+ article.title,
110
+ article.content,
111
+ article.createdAt,
112
+ article.id,
113
+ fragment,
114
+ )
115
+ ) {
116
+ addedCount++;
117
+ }
118
+ }
119
+ document.getElementById("articles").appendChild(fragment);
120
+ if (addedCount < limit) {
121
+ window.removeEventListener("scroll", handleScroll);
57
122
  }
58
123
  } else {
59
- // No more articles to load
60
- //window.removeEventListener("scroll", handleScroll);
124
+ window.removeEventListener("scroll", handleScroll);
61
125
  }
62
126
  }
63
127
  } catch (error) {
@@ -73,22 +137,78 @@ function getFormattedDate(date) {
73
137
  const minutes = String(date.getMinutes()).padStart(2, "0");
74
138
  return `${year}/${month}/${day} ${hours}:${minutes}`;
75
139
  }
76
- function fillWithContent(title, content, date, id) {
140
+ function fillWithContent(title, content, date, id, targetContainer) {
141
+ if (id) {
142
+ // Check if an article with this ID already exists in the container.
143
+ if (document.querySelector(`#articles > article[data-id='${id}']`)) {
144
+ console.log("article found. not adding it.");
145
+ console.log(`${id} ${title} ${date}`);
146
+ return false; // Indicate that nothing was added.
147
+ }
148
+ }
149
+
77
150
  const article = document.createElement("article");
78
- if (id) article.setAttribute("data-id", id);
151
+ const articleDate = new Date(date);
152
+
153
+ if (id !== null && id !== undefined) article.setAttribute("data-id", id);
154
+ article.setAttribute("data-date", articleDate.getTime());
79
155
 
80
156
  const heading = document.createElement("h2");
81
157
  const time = document.createElement("span");
82
158
  const text = document.createElement("p");
83
159
 
160
+ const loggedin = document.querySelector('a[href="/logout"]') !== null;
161
+ if (loggedin) {
162
+ const buttons = document.createElement("div");
163
+ const editButton = document.createElement("div");
164
+ const deleteButton = document.createElement("div");
165
+ const somethingButton = document.createElement("div");
166
+ buttons.setAttribute("class", "buttons");
167
+ editButton.setAttribute("class", "button edit");
168
+ deleteButton.setAttribute("class", "button delete");
169
+ somethingButton.setAttribute("class", "button delete");
170
+ editButton.textContent = "edit";
171
+ deleteButton.textContent = "delete";
172
+ somethingButton.textContent = "something";
173
+ buttons.appendChild(editButton);
174
+ buttons.appendChild(deleteButton);
175
+ buttons.appendChild(somethingButton);
176
+
177
+ editButton.addEventListener("click", () => handleAction("edit"));
178
+ deleteButton.addEventListener("click", () => handleAction("delete"));
179
+ article.appendChild(buttons);
180
+ }
181
+
182
+ function handleAction(button) {
183
+ if (button === "delete") {
184
+ if (confirm("Delete this article?")) {
185
+ fetch("/api/articles?id=" + id, { method: "DELETE" }).then((res) => {
186
+ if (res.ok) article.remove();
187
+ else alert("Error: " + res.statusText);
188
+ });
189
+ }
190
+ } else if (button === "edit") {
191
+ const newTitle = prompt("New Title", title);
192
+ const newContent = prompt("New Content", content);
193
+ if (newTitle && newContent) {
194
+ fetch("/api/articles?id=" + id, {
195
+ method: "PUT",
196
+ body: JSON.stringify({ title: newTitle, content: newContent }),
197
+ }).then((res) => {
198
+ if (res.ok) window.location.reload();
199
+ else alert("Error: " + res.statusText);
200
+ });
201
+ }
202
+ }
203
+ }
204
+
84
205
  heading.textContent = title;
85
- time.textContent = getFormattedDate(new Date(date));
206
+ time.textContent = getFormattedDate(articleDate);
86
207
  text.textContent = content;
87
208
 
88
- //article.textContent = "article1";
89
209
  article.appendChild(heading);
90
210
  article.appendChild(time);
91
211
  article.appendChild(text);
92
- const articles = document.getElementById("articles");
93
- articles.appendChild(article);
212
+ (targetContainer || document.getElementById("articles")).appendChild(article);
213
+ return true; // Indicate that an article was added.
94
214
  }
package/src/styles.css CHANGED
@@ -82,3 +82,32 @@ nav a:visited {
82
82
  box-sizing: border-box;
83
83
  }
84
84
  }
85
+
86
+ h2 {
87
+ margin-top: 0;
88
+ border: 0px solid black;
89
+ }
90
+ .buttons {
91
+ width: 100%;
92
+ height: 25px;
93
+ border: 0px solid black;
94
+ margin-bottom: 0px;
95
+ }
96
+ .button {
97
+ border: 1px solid black;
98
+ width: 100px;
99
+ height: 19px;
100
+ float: left;
101
+ padding: 2px;
102
+ cursor: pointer;
103
+ user-select: none; /* Prevents text selection on double-click */
104
+ }
105
+ .button:focus {
106
+ outline: 2px solid orange; /* Vital for keyboard accessibility */
107
+ }
108
+ .edit {
109
+ background-color: blue;
110
+ }
111
+ .delete {
112
+ background-color: red;
113
+ }
package/utilities.js ADDED
@@ -0,0 +1,142 @@
1
+ import path from "node:path";
2
+
3
+ export function generateTitle(number) {
4
+ return numberToGerman(number % 1000);
5
+ }
6
+
7
+ export function generateContent(number) {
8
+ return (
9
+ numberToGerman(number % 1000) +
10
+ ". " +
11
+ numberToGerman(number % 1000) +
12
+ ". " +
13
+ numberToGerman(number % 1000) +
14
+ ". " +
15
+ numberToGerman(number % 1000) +
16
+ ". " +
17
+ numberToGerman(number % 1000) +
18
+ ". " +
19
+ numberToGerman(number % 1000) +
20
+ ". " +
21
+ numberToGerman(number % 1000) +
22
+ ". " +
23
+ numberToGerman(number % 1000) +
24
+ ". " +
25
+ numberToGerman(number % 1000) +
26
+ ". " +
27
+ numberToGerman(number % 1000) +
28
+ ". "
29
+ );
30
+ }
31
+
32
+ function numberToGerman(n) {
33
+ if (n === 0) return "Null";
34
+
35
+ const units = [
36
+ "",
37
+ "eins",
38
+ "zwei",
39
+ "drei",
40
+ "vier",
41
+ "fünf",
42
+ "sechs",
43
+ "sieben",
44
+ "acht",
45
+ "neun",
46
+ ];
47
+ const teens = [
48
+ "zehn",
49
+ "elf",
50
+ "zwölf",
51
+ "dreizehn",
52
+ "vierzehn",
53
+ "fünfzehn",
54
+ "sechzehn",
55
+ "siebzehn",
56
+ "achtzehn",
57
+ "neunzehn",
58
+ ];
59
+ const tens = [
60
+ "",
61
+ "",
62
+ "zwanzig",
63
+ "dreißig",
64
+ "vierzig",
65
+ "fünfzig",
66
+ "sechzig",
67
+ "siebzig",
68
+ "achtzig",
69
+ "neunzig",
70
+ ];
71
+
72
+ function convert(num) {
73
+ if (num < 10) return units[num];
74
+ if (num < 20) return teens[num - 10];
75
+ if (num < 100) {
76
+ const t = Math.floor(num / 10);
77
+ const u = num % 10;
78
+ if (u === 0) return tens[t];
79
+ return (u === 1 ? "ein" : units[u]) + "und" + tens[t];
80
+ }
81
+ if (num < 1000) {
82
+ const h = Math.floor(num / 100);
83
+ const r = num % 100;
84
+ let str = (h === 1 ? "ein" : units[h]) + "hundert";
85
+ if (r > 0) str += convert(r);
86
+ return str;
87
+ }
88
+ if (num < 1000000) {
89
+ const t = Math.floor(num / 1000);
90
+ const r = num % 1000;
91
+ let str = convert(t);
92
+ if (str.endsWith("eins")) str = str.slice(0, -1);
93
+ str += "tausend";
94
+ if (r > 0) str += convert(r);
95
+ return str;
96
+ }
97
+ return num.toString();
98
+ }
99
+
100
+ const result = convert(n);
101
+ return result.charAt(0).toUpperCase() + result.slice(1);
102
+ }
103
+
104
+ export function generateDateList(startStr, endStr) {
105
+ const start = new Date(startStr);
106
+ // Optional: Reset minutes/seconds if you only want clean hourly markers
107
+ start.setMinutes(0, 0, 0);
108
+
109
+ const end = new Date(endStr);
110
+ const dates = [];
111
+
112
+ let current = new Date(start);
113
+
114
+ while (current <= end) {
115
+ // Push a new Date instance to avoid mutating existing array elements
116
+ dates.push(new Date(current));
117
+
118
+ // Increment by 1 hour
119
+ current.setHours(current.getHours() + 1);
120
+ }
121
+
122
+ return dates;
123
+ }
124
+
125
+ export function table(articles) {
126
+ const tableData = [];
127
+ for (let article of articles) {
128
+ tableData.push({
129
+ Title: article.title,
130
+ Date: article.createdAt,
131
+ });
132
+ }
133
+ console.table(tableData);
134
+ }
135
+
136
+ export function log(filename, message) {
137
+ if (filename) {
138
+ console.log(`${filename}: ${message}`);
139
+ } else {
140
+ console.log(`Unknown: ${message}`);
141
+ }
142
+ }