@lexho111/plainblog 0.8.6 → 0.9.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.
- package/Blog.js +25 -1
- package/Formatter.js +33 -7
- package/dist/model/PostgresAdapter.js +230 -244
- package/dist/model/datastructures/BinarySearchTreeWithInvertedIndex.js +138 -123
- package/index.js +0 -2
- package/model/datastructures/BinarySearchTreeWithInvertedIndex.js +124 -107
- package/package.json +1 -2
- package/public/scripts.min.js +75 -9
- package/public/styles.min.css +7 -7
- package/server.js +178 -62
- package/src/fetchData.js +74 -8
- package/src/styles.css +63 -16
- package/src1/model/SqliteAdapter.ts +2 -1
- package/src1/model/datastructures/BinarySearchTreeWithInvertedIndex.ts +122 -118
- package/styles.hash +1 -1
- package/templates/article.html +1 -1
- package/templates/login.html +18 -0
- package/templates/moreButton.html +1 -1
- package/model/SequelizeAdapter.js +0 -189
|
@@ -26,9 +26,11 @@ export var TraversalOrder;
|
|
|
26
26
|
})(TraversalOrder || (TraversalOrder = {}));
|
|
27
27
|
export class BinarySearchTree {
|
|
28
28
|
root;
|
|
29
|
+
dateRoot;
|
|
29
30
|
searchIndex;
|
|
30
31
|
constructor() {
|
|
31
32
|
this.root = null;
|
|
33
|
+
this.dateRoot = null;
|
|
32
34
|
this.searchIndex = new InvertedIndexSearch();
|
|
33
35
|
}
|
|
34
36
|
// O(n) - Linear search (Base class has no HashMap)
|
|
@@ -37,34 +39,63 @@ export class BinarySearchTree {
|
|
|
37
39
|
}
|
|
38
40
|
// Insert a new article
|
|
39
41
|
insert(article) {
|
|
42
|
+
const time_start = performance.now();
|
|
40
43
|
this.searchIndex.add(article);
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// Add to the Set
|
|
44
|
-
const newNode = new BlogNode(article);
|
|
44
|
+
// 1. Insert into ID Tree
|
|
45
|
+
const idNode = new BlogNode(article);
|
|
45
46
|
if (!this.root) {
|
|
46
|
-
this.root =
|
|
47
|
-
return;
|
|
47
|
+
this.root = idNode;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
else {
|
|
50
|
+
let current = this.root;
|
|
51
|
+
while (true) {
|
|
52
|
+
if (article.id < current.article.id) {
|
|
53
|
+
if (!current.left) {
|
|
54
|
+
current.left = idNode;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
current = current.left;
|
|
58
|
+
}
|
|
59
|
+
else if (article.id > current.article.id) {
|
|
60
|
+
if (!current.right) {
|
|
61
|
+
current.right = idNode;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
current = current.right;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
current.article = article; // Update existing
|
|
56
68
|
break;
|
|
57
69
|
}
|
|
58
|
-
current = current.left;
|
|
59
70
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
}
|
|
72
|
+
// 2. Insert into Date Tree
|
|
73
|
+
const dateNode = new BlogNode(article);
|
|
74
|
+
if (!this.dateRoot) {
|
|
75
|
+
this.dateRoot = dateNode;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
let current = this.dateRoot;
|
|
79
|
+
const newTime = new Date(article.createdAt).getTime();
|
|
80
|
+
while (true) {
|
|
81
|
+
// Duplicates go to the right
|
|
82
|
+
if (newTime < new Date(current.article.createdAt).getTime()) {
|
|
83
|
+
if (!current.left) {
|
|
84
|
+
current.left = dateNode;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
current = current.left;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (!current.right) {
|
|
91
|
+
current.right = dateNode;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
current = current.right;
|
|
64
95
|
}
|
|
65
|
-
current = current.right;
|
|
66
96
|
}
|
|
67
97
|
}
|
|
98
|
+
debug_perf("BST.insert", performance.now() - time_start);
|
|
68
99
|
}
|
|
69
100
|
update(id, title, content) {
|
|
70
101
|
const article = this.find(id);
|
|
@@ -90,29 +121,27 @@ export class BinarySearchTree {
|
|
|
90
121
|
}
|
|
91
122
|
// Search for a specific article by ID
|
|
92
123
|
find(id) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
stack.push(node);
|
|
100
|
-
while (stack.length > 0) {
|
|
101
|
-
const current = stack.pop();
|
|
102
|
-
if (current.article.id === id)
|
|
124
|
+
const time_start = performance.now();
|
|
125
|
+
// Use ID Tree for O(log n) lookup
|
|
126
|
+
let current = this.root;
|
|
127
|
+
while (current) {
|
|
128
|
+
if (id === current.article.id) {
|
|
129
|
+
debug_perf("BST.find", performance.now() - time_start);
|
|
103
130
|
return current.article;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
131
|
+
}
|
|
132
|
+
if (id < current.article.id)
|
|
133
|
+
current = current.left;
|
|
134
|
+
else
|
|
135
|
+
current = current.right;
|
|
108
136
|
}
|
|
137
|
+
debug_perf("BST.find", performance.now() - time_start);
|
|
109
138
|
return null;
|
|
110
139
|
}
|
|
111
140
|
// Get all articles in order (Sorted by ID)
|
|
112
141
|
getAllArticles(order = TraversalOrder.NewestFirst) {
|
|
113
142
|
const articles = [];
|
|
114
|
-
if (this.
|
|
115
|
-
this._inOrder(this.
|
|
143
|
+
if (this.dateRoot) {
|
|
144
|
+
this._inOrder(this.dateRoot, articles, order);
|
|
116
145
|
}
|
|
117
146
|
return articles;
|
|
118
147
|
}
|
|
@@ -144,7 +173,7 @@ export class BinarySearchTree {
|
|
|
144
173
|
}
|
|
145
174
|
*traverse(order = TraversalOrder.NewestFirst) {
|
|
146
175
|
const stack = [];
|
|
147
|
-
let current = this.
|
|
176
|
+
let current = this.dateRoot;
|
|
148
177
|
let visitedCount = 0;
|
|
149
178
|
if (order === TraversalOrder.NewestFirst) {
|
|
150
179
|
while (current || stack.length > 0) {
|
|
@@ -173,6 +202,7 @@ export class BinarySearchTree {
|
|
|
173
202
|
debug("Traversed %d nodes of %d", visitedCount, this.size());
|
|
174
203
|
}
|
|
175
204
|
getRange(startdate, enddate, limit = null) {
|
|
205
|
+
const time_start = performance.now();
|
|
176
206
|
debug("Using BST.getRange for start: '%s', end: '%s', limit: %s", startdate, enddate, limit);
|
|
177
207
|
if (typeof startdate === "number") {
|
|
178
208
|
debug("startdate is number");
|
|
@@ -193,7 +223,8 @@ export class BinarySearchTree {
|
|
|
193
223
|
const end = toTimestamp(enddate, Infinity);
|
|
194
224
|
debug("startdate: %d", start);
|
|
195
225
|
debug("enddate: %d", end);
|
|
196
|
-
this._rangeSearch(this.
|
|
226
|
+
this._rangeSearch(this.dateRoot, start, end, limit, results);
|
|
227
|
+
debug_perf("BST.getRange", performance.now() - time_start);
|
|
197
228
|
return results;
|
|
198
229
|
}
|
|
199
230
|
_rangeSearch(node, start, end, limit, results) {
|
|
@@ -206,7 +237,7 @@ export class BinarySearchTree {
|
|
|
206
237
|
// Go right as much as possible, but prune if > end
|
|
207
238
|
while (current) {
|
|
208
239
|
visitedCount++;
|
|
209
|
-
if (current.article.createdAt > end) {
|
|
240
|
+
if (new Date(current.article.createdAt).getTime() > end) {
|
|
210
241
|
current = current.left;
|
|
211
242
|
}
|
|
212
243
|
else {
|
|
@@ -218,7 +249,7 @@ export class BinarySearchTree {
|
|
|
218
249
|
current = stack.pop();
|
|
219
250
|
if (limit !== null && results.length >= limit)
|
|
220
251
|
break;
|
|
221
|
-
if (current.article.createdAt >= start) {
|
|
252
|
+
if (new Date(current.article.createdAt).getTime() >= start) {
|
|
222
253
|
results.push(current.article);
|
|
223
254
|
current = current.left;
|
|
224
255
|
}
|
|
@@ -246,103 +277,87 @@ export class BinarySearchTree {
|
|
|
246
277
|
return count;
|
|
247
278
|
}
|
|
248
279
|
remove(data) {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
280
|
+
const time_start = performance.now();
|
|
281
|
+
let articleToRemove = null;
|
|
282
|
+
if (data instanceof Article) {
|
|
283
|
+
articleToRemove = data;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
articleToRemove = this.find(data);
|
|
255
287
|
}
|
|
256
|
-
|
|
288
|
+
if (articleToRemove) {
|
|
289
|
+
// Remove from ID tree
|
|
290
|
+
this.root = this._removeNodeID(this.root, articleToRemove.id);
|
|
291
|
+
// Remove from Date tree
|
|
292
|
+
this.dateRoot = this._removeNodeDate(this.dateRoot, articleToRemove);
|
|
293
|
+
this.searchIndex.remove(articleToRemove.id);
|
|
294
|
+
}
|
|
295
|
+
debug_perf("BST.remove", performance.now() - time_start);
|
|
296
|
+
return articleToRemove;
|
|
257
297
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// Optimized search using BST property (Date)
|
|
265
|
-
while (current) {
|
|
266
|
-
if (current.article.id == id)
|
|
267
|
-
break;
|
|
268
|
-
parent = current;
|
|
269
|
-
if (date < current.article.createdAt) {
|
|
270
|
-
current = current.left;
|
|
271
|
-
isLeftChild = true;
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
current = current.right;
|
|
275
|
-
isLeftChild = false;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
298
|
+
_removeNodeID(root, id) {
|
|
299
|
+
if (!root)
|
|
300
|
+
return null;
|
|
301
|
+
if (id < root.article.id) {
|
|
302
|
+
root.left = this._removeNodeID(root.left, id);
|
|
303
|
+
return root;
|
|
278
304
|
}
|
|
279
|
-
else {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
while (stack.length > 0) {
|
|
283
|
-
const item = stack.pop();
|
|
284
|
-
if (item.node.article.id === id) {
|
|
285
|
-
current = item.node;
|
|
286
|
-
parent = item.parent;
|
|
287
|
-
isLeftChild = item.isLeft;
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
if (item.node.right)
|
|
291
|
-
stack.push({
|
|
292
|
-
node: item.node.right,
|
|
293
|
-
parent: item.node,
|
|
294
|
-
isLeft: false,
|
|
295
|
-
});
|
|
296
|
-
if (item.node.left)
|
|
297
|
-
stack.push({ node: item.node.left, parent: item.node, isLeft: true });
|
|
298
|
-
}
|
|
305
|
+
else if (id > root.article.id) {
|
|
306
|
+
root.right = this._removeNodeID(root.right, id);
|
|
307
|
+
return root;
|
|
299
308
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
else {
|
|
310
|
+
// Found
|
|
311
|
+
if (!root.left)
|
|
312
|
+
return root.right;
|
|
313
|
+
if (!root.right)
|
|
314
|
+
return root.left;
|
|
315
|
+
let minRight = root.right;
|
|
316
|
+
while (minRight.left)
|
|
317
|
+
minRight = minRight.left;
|
|
318
|
+
root.article = minRight.article;
|
|
319
|
+
root.right = this._removeNodeID(root.right, minRight.article.id);
|
|
320
|
+
return root;
|
|
311
321
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
}
|
|
323
|
+
_removeNodeDate(root, article) {
|
|
324
|
+
if (!root)
|
|
325
|
+
return null;
|
|
326
|
+
const time = new Date(article.createdAt).getTime();
|
|
327
|
+
const rootTime = new Date(root.article.createdAt).getTime();
|
|
328
|
+
if (time < rootTime) {
|
|
329
|
+
root.left = this._removeNodeDate(root.left, article);
|
|
330
|
+
return root;
|
|
319
331
|
}
|
|
320
|
-
else if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (isLeftChild)
|
|
324
|
-
parent.left = current.right;
|
|
325
|
-
else
|
|
326
|
-
parent.right = current.right;
|
|
332
|
+
else if (time > rootTime) {
|
|
333
|
+
root.right = this._removeNodeDate(root.right, article);
|
|
334
|
+
return root;
|
|
327
335
|
}
|
|
328
336
|
else {
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
337
|
+
// Dates equal. Check ID.
|
|
338
|
+
if (article.id === root.article.id) {
|
|
339
|
+
// Found exact match
|
|
340
|
+
if (!root.left)
|
|
341
|
+
return root.right;
|
|
342
|
+
if (!root.right)
|
|
343
|
+
return root.left;
|
|
344
|
+
let minRight = root.right;
|
|
345
|
+
while (minRight.left)
|
|
346
|
+
minRight = minRight.left;
|
|
347
|
+
root.article = minRight.article;
|
|
348
|
+
root.right = this._removeNodeDate(root.right, minRight.article);
|
|
349
|
+
return root;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// Dates equal but ID different. Duplicates are on right.
|
|
353
|
+
root.right = this._removeNodeDate(root.right, article);
|
|
354
|
+
return root;
|
|
335
355
|
}
|
|
336
|
-
current.article = successor.article;
|
|
337
|
-
if (successorParent === current)
|
|
338
|
-
successorParent.right = successor.right;
|
|
339
|
-
else
|
|
340
|
-
successorParent.left = successor.right;
|
|
341
356
|
}
|
|
342
|
-
return { root, removed };
|
|
343
357
|
}
|
|
344
358
|
clear() {
|
|
345
359
|
this.root = null;
|
|
360
|
+
this.dateRoot = null;
|
|
346
361
|
this.searchIndex = new InvertedIndexSearch();
|
|
347
362
|
}
|
|
348
363
|
size() {
|
package/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Blog, BlogBuilder } from "./Blog.js";
|
|
|
2
2
|
import Article from "./Article.js";
|
|
3
3
|
import FileAdapter from "./model/FileAdapter.js";
|
|
4
4
|
import PostgresAdapter from "./model/PostgresAdapter.js";
|
|
5
|
-
import SequelizeAdapter from "./model/SequelizeAdapter.js";
|
|
6
5
|
import SqliteAdapter from "./model/SqliteAdapter.js";
|
|
7
6
|
import { startCluster } from "./cluster-server.js";
|
|
8
7
|
import { articleFactory, articleGenerator } from "./utilities.js";
|
|
@@ -12,7 +11,6 @@ export {
|
|
|
12
11
|
BlogBuilder,
|
|
13
12
|
Article,
|
|
14
13
|
PostgresAdapter,
|
|
15
|
-
SequelizeAdapter,
|
|
16
14
|
FileAdapter,
|
|
17
15
|
SqliteAdapter,
|
|
18
16
|
startCluster,
|