@wtfalch/threads 0.1.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/LICENSE +21 -0
- package/dist/bin/copy.d.ts +31 -0
- package/dist/bin/copy.js +58 -0
- package/dist/bin/migrations.d.ts +2 -0
- package/dist/bin/migrations.js +21 -0
- package/dist/gates.d.ts +48 -0
- package/dist/gates.js +38 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/migrations/0001_threads.sql +185 -0
- package/dist/react/CategoryList.d.ts +9 -0
- package/dist/react/CategoryList.js +7 -0
- package/dist/react/CommentForm.d.ts +19 -0
- package/dist/react/CommentForm.js +35 -0
- package/dist/react/CommentSection.d.ts +42 -0
- package/dist/react/CommentSection.js +26 -0
- package/dist/react/NewThreadForm.d.ts +18 -0
- package/dist/react/NewThreadForm.js +29 -0
- package/dist/react/Roadmap.d.ts +10 -0
- package/dist/react/Roadmap.js +12 -0
- package/dist/react/ThreadList.d.ts +21 -0
- package/dist/react/ThreadList.js +15 -0
- package/dist/react/ThreadView.d.ts +26 -0
- package/dist/react/ThreadView.js +20 -0
- package/dist/react/format.d.ts +16 -0
- package/dist/react/format.js +42 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/index.js +17 -0
- package/dist/schema.d.ts +2085 -0
- package/dist/schema.js +129 -0
- package/dist/threads.css +182 -0
- package/dist/threads.d.ts +109 -0
- package/dist/threads.js +626 -0
- package/dist/tree.d.ts +47 -0
- package/dist/tree.js +75 -0
- package/package.json +72 -0
package/dist/tree.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The comment tree, from rows to nodes and back to the pieces a page shows.
|
|
3
|
+
*
|
|
4
|
+
* `path` is the chain of zero-padded comment ids from the top of the thread
|
|
5
|
+
* to the comment, written by a trigger, so rows ordered by `path` are already
|
|
6
|
+
* depth-first in creation order. Building the tree is one pass over that
|
|
7
|
+
* order; nothing here touches the database.
|
|
8
|
+
*/
|
|
9
|
+
export const LABEL_WIDTH = 12;
|
|
10
|
+
export function label(id) {
|
|
11
|
+
return String(id).padStart(LABEL_WIDTH, '0');
|
|
12
|
+
}
|
|
13
|
+
export function depthOf(path) {
|
|
14
|
+
return path.split('.').length - 1;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Rows to a forest. Pass `root` to build only the subtree under one comment,
|
|
18
|
+
* with that comment at depth 0; the rows must already be that subtree, or the
|
|
19
|
+
* thread's whole set, either way ordered by `path`.
|
|
20
|
+
*/
|
|
21
|
+
export function buildTree(rows, opts = {}) {
|
|
22
|
+
const sorted = [...rows].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
23
|
+
const byId = new Map();
|
|
24
|
+
const top = [];
|
|
25
|
+
const rootPath = opts.root === undefined ? null : sorted.find((r) => r.id === opts.root)?.path;
|
|
26
|
+
if (opts.root !== undefined && rootPath === undefined)
|
|
27
|
+
return [];
|
|
28
|
+
const base = rootPath ? depthOf(rootPath) : 0;
|
|
29
|
+
for (const row of sorted) {
|
|
30
|
+
if (rootPath && !(row.path === rootPath || row.path.startsWith(`${rootPath}.`)))
|
|
31
|
+
continue;
|
|
32
|
+
const node = { ...row, depth: depthOf(row.path) - base, children: [], descendants: 0 };
|
|
33
|
+
byId.set(row.id, node);
|
|
34
|
+
const parent = row.replyTo === null ? undefined : byId.get(row.replyTo);
|
|
35
|
+
if (parent && node.depth > 0)
|
|
36
|
+
parent.children.push(node);
|
|
37
|
+
else
|
|
38
|
+
top.push(node);
|
|
39
|
+
}
|
|
40
|
+
for (const node of byId.values()) {
|
|
41
|
+
let up = node.replyTo === null ? undefined : byId.get(node.replyTo);
|
|
42
|
+
while (up) {
|
|
43
|
+
up.descendants += 1;
|
|
44
|
+
up = up.replyTo === null ? undefined : byId.get(up.replyTo);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return top;
|
|
48
|
+
}
|
|
49
|
+
/** Every node in depth-first order, which is the order the page draws them. */
|
|
50
|
+
export function walk(nodes) {
|
|
51
|
+
const out = [];
|
|
52
|
+
const visit = (n) => {
|
|
53
|
+
out.push(n);
|
|
54
|
+
for (const c of n.children)
|
|
55
|
+
visit(c);
|
|
56
|
+
};
|
|
57
|
+
for (const n of nodes)
|
|
58
|
+
visit(n);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What a page draws past the depth it is willing to indent: the node stays,
|
|
63
|
+
* its children are replaced by a count and a link. Eight is the reference
|
|
64
|
+
* rendering's choice; a place that renders flat passes 0, one that never
|
|
65
|
+
* folds passes Infinity.
|
|
66
|
+
*/
|
|
67
|
+
export function collapse(nodes, at) {
|
|
68
|
+
const fold = (n) => {
|
|
69
|
+
if (n.depth >= at && n.children.length > 0) {
|
|
70
|
+
return { ...n, children: [], folded: n.descendants };
|
|
71
|
+
}
|
|
72
|
+
return { ...n, children: n.children.map(fold), folded: 0 };
|
|
73
|
+
};
|
|
74
|
+
return nodes.map(fold);
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wtfalch/threads",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Threads, comments, votes and subscriptions attached to a subject the host names: a forum, a feedback board, a comment section. Per-app Postgres, host-supplied gates, React on @wtfalch/design.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/wtfalch/threads",
|
|
8
|
+
"directory": "packages/threads"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"threads-migrations": "./dist/bin/migrations.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./react": {
|
|
24
|
+
"types": "./dist/react/index.d.ts",
|
|
25
|
+
"default": "./dist/react/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./migrations/*.sql": "./dist/migrations/*.sql",
|
|
28
|
+
"./threads.css": "./dist/threads.css"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@wtfalch/design": ">=0.3.0",
|
|
39
|
+
"drizzle-orm": ">=0.39.0",
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@wtfalch/design": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"react": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"react-dom": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@electric-sql/pglite": "^0.5.8",
|
|
56
|
+
"@types/node": "^22",
|
|
57
|
+
"@types/react": "^19",
|
|
58
|
+
"@types/react-dom": "^19",
|
|
59
|
+
"@wtfalch/design": "^0.3.0",
|
|
60
|
+
"drizzle-orm": "^0.39.3",
|
|
61
|
+
"jsdom": "^30.0.1",
|
|
62
|
+
"react": "^19",
|
|
63
|
+
"react-dom": "^19",
|
|
64
|
+
"typescript": "^5.9.0",
|
|
65
|
+
"vitest": "^4.1.6"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/migrations && cp src/migrations/*.sql dist/migrations/ && cp src/react/threads.css dist/threads.css",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"test": "vitest run"
|
|
71
|
+
}
|
|
72
|
+
}
|