@pkcprotocol/active-page-sort 0.0.4 → 0.0.5
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/AGENTS.md +10 -11
- package/README.md +12 -12
- package/dist/active-page-sort.js +4 -8
- package/dist/active-page-sort.js.map +1 -1
- package/dist/no-bump.d.ts +27 -28
- package/dist/no-bump.js +26 -44
- package/dist/no-bump.js.map +1 -1
- package/dist/types.d.ts +4 -11
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -28,17 +28,16 @@ Conventions specific to this package:
|
|
|
28
28
|
- The match is a whole line equal to the keyword, exact and case-sensitive. `src/no-bump.ts` imports
|
|
29
29
|
nothing and must keep matching what the README describes, because UIs copy or bundle it for the browser.
|
|
30
30
|
- `score` is sync and never returns `null`: a saged thread sinks, it is not filtered. It reads
|
|
31
|
-
`comment.timestamp`,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
(pkc-js#401).
|
|
31
|
+
`comment.timestamp`, the reserved `exclude*` options, and per reply `timestamp`, `content`, and the
|
|
32
|
+
CommentUpdate's `removed`, `approved`, `pendingApproval` and `edit.deleted`. There is no database access and
|
|
33
|
+
no network; a need for either is a later iteration of the pkc-js contract, not a reason to reach around it.
|
|
34
|
+
- `isCountedReply` / `bumpTimestamp` (in `src/no-bump.ts`) apply the `exclude*` options to each reply's own
|
|
35
|
+
CommentUpdate, the tombstone rule of pkc-js's "Which replies count" (`docs/protocol/comment-lifecycle.md`):
|
|
36
|
+
a dropped reply does not bump, the replies under it are judged on their own, never cut. pkc-js passes every
|
|
37
|
+
reply to `score` except foreign and CommentUpdate-less ones (pkc-js#407). Flag parsing matches pkc-js
|
|
38
|
+
(trimmed, case-insensitive `true`). With the `posts` defaults it must match the built-in `active`. A new
|
|
39
|
+
reserved exclusion in pkc-js means a release here. `excludeCommentsWithDifferentCommunityAddress` is left
|
|
40
|
+
out on purpose: pkc-js applies it, and `score` has no community address (pkc-js#401).
|
|
42
41
|
- The scorer ignores `commentUpdate.lastReplyTimestamp` on purpose. That aggregate counts saged replies.
|
|
43
42
|
- `validatePageSortSettings` is sync and rejects only what can never work (empty list, line break, size
|
|
44
43
|
bounds). An unset option is valid and means plain bump order.
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A board's catalog shows threads in bump order: the thread with the newest reply
|
|
|
9
9
|
- A thread's **bump time** is the newest of the post's own timestamp and the timestamps of its replies that are **not** no-bump replies.
|
|
10
10
|
- A **no-bump reply** is a reply whose content has a whole line equal to one of the configured keywords. The match is exact and case-sensitive: a content of `sage`, or a multi-line content with a line that is exactly `sage`, does not bump. `sage is overused` in prose, `Sage`, `#sage` and ` sage` all bump.
|
|
11
11
|
- Replies **under** a no-bump reply still count, at any depth. Someone answering a saged reply bumps the thread as usual.
|
|
12
|
-
- A reply the sort's exclusion options drop (by default: removed, deleted, disapproved or pending approval)
|
|
12
|
+
- A reply the sort's exclusion options drop (by default: removed, deleted, disapproved or pending approval) does not bump the thread, but the present replies under it still do, at any depth: a removed or deleted reply is a **tombstone**, not a cut. This is pkc-js's [Which replies count](https://github.com/pkcprotocol/pkc-js/blob/master/docs/protocol/comment-lifecycle.md#which-replies-count) rule, the one the built-in `active` follows through `lastReplyTimestamp`. A board that turns a flag off (`excludeDeletedComments: "false"`) lets those replies bump again.
|
|
13
13
|
- A saged thread **sinks, it is never hidden**. The sort ranks, it does not filter.
|
|
14
14
|
- With the keyword option unset the sort is plain bump order, identical to pkc-js's built-in `active`.
|
|
15
15
|
|
|
@@ -19,14 +19,14 @@ The word `sage` appears nowhere in this package. The board supplies its vocabula
|
|
|
19
19
|
|
|
20
20
|
## How the sorting works
|
|
21
21
|
|
|
22
|
-
pkc-js regenerates a community's post pages on every publish cycle. For each configured sort it calls the sort file's `score` once per post, higher scores first, equal scores in insertion order. Because this sort declares `requireReplies`, pkc-js hands `score` every descendant of the post as one flat list
|
|
22
|
+
pkc-js regenerates a community's post pages on every publish cycle. For each configured sort it calls the sort file's `score` once per post, higher scores first, equal scores in insertion order. Because this sort declares `requireReplies`, pkc-js hands `score` every descendant of the post as one flat list. pkc-js drops only replies from another community and replies with no CommentUpdate yet; removed, deleted, disapproved and pending replies all arrive, and the sort applies its `exclude*` options to them itself ([pkc-js#407](https://github.com/pkcprotocol/pkc-js/issues/407)). The score is:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
counted = replies
|
|
25
|
+
counted = replies no exclude* option that is on drops (each judged on its own)
|
|
26
26
|
max(post.timestamp, ...counted.filter(not no-bump).map(reply => reply.timestamp))
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Each reply is judged on its own, so a skipped reply never hides the replies under it. The reply pages a UI walks keep removed and deleted replies as tombstones too, so a raw walk scores like the community (see [Re-sort a page locally](#4-re-sort-a-page-locally-optional)). That is the whole algorithm. pkc-js owns everything around it: pinned threads first (`pinnedFirst`), the optional age window (`maxAge`), chunking into pages, the moderation exclusions on the threads being ranked. The file contains no date arithmetic and never sees a pinned flag.
|
|
30
30
|
|
|
31
31
|
The same `score` function runs on a client that installed this package, so a UI can re-sort a page it holds and land on the community's order. See [For UI developers](#for-ui-developers).
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@ The built-in `active` reads one number off each post's CommentUpdate (`lastReply
|
|
|
36
36
|
|
|
37
37
|
## Requirements
|
|
38
38
|
|
|
39
|
-
- pkc-js `>=0.0.
|
|
39
|
+
- pkc-js `>=0.0.114` (configurable page sorts, `settings.pages`, the page sort types exported from the root, and tombstone replies passed to `score`, pkc-js#407)
|
|
40
40
|
- Node.js `>=22` on the machine running the community
|
|
41
41
|
- ESM-only environment
|
|
42
42
|
|
|
@@ -121,9 +121,9 @@ Page sort options are strings, like challenge options. Unlike challenge options
|
|
|
121
121
|
| `noBumpKeywords` | no | yes, unless listed in `privateOptions` | unset (plain bump order) | Comma-separated keywords, e.g. `sage` or `sage,nobump`. A reply whose content has a whole line equal to one of these does not bump its thread. Exact and case-sensitive |
|
|
122
122
|
| `maxAge` | no | always (reserved) | none | pkc-js reserved: a sliding window such as `"36h"`, `"7d"`, `"2w"`, `"1M"`, `"1y"`. Threads older than the window drop out of the pages (pinned threads bypass it) |
|
|
123
123
|
| `pinnedFirst` | no | always (reserved) | `"true"` | pkc-js reserved: pinned threads first and outside the window. `"false"` treats them as ordinary threads |
|
|
124
|
-
| `excludeRemovedComments`, `excludeDeletedComments`, `excludeCommentPendingApproval`, `excludeCommentWithApprovedFalse`, `excludeCommentsWithDifferentCommunityAddress` | no | always (reserved) | `"true"` for the `posts` scope | pkc-js reserved moderation filters on the
|
|
124
|
+
| `excludeRemovedComments`, `excludeDeletedComments`, `excludeCommentPendingApproval`, `excludeCommentWithApprovedFalse`, `excludeCommentsWithDifferentCommunityAddress` | no | always (reserved) | `"true"` for the `posts` scope | pkc-js reserved moderation filters on the threads the sort ranks. `score` also applies the first four to the replies: a reply one of them drops does not bump, the replies under it still do. `excludeCommentsWithDifferentCommunityAddress` is applied to replies by pkc-js before `score` runs (checking it needs the community's address, which `score` does not get, [pkc-js#401](https://github.com/pkcprotocol/pkc-js/issues/401)) |
|
|
125
125
|
|
|
126
|
-
Keep `noBumpKeywords` public unless you have a reason not to: a UI reads it to know the board supports sage, to put the keyword on the wire the way the sort reads it, and to mark saged replies. With the option private the order still applies but UIs cannot do any of that. The reserved options cannot be made private (`ERR_PAGE_SORT_RESERVED_OPTION_CANNOT_BE_PRIVATE`): a client re-sorting a page must know how its comment set was filtered. Their full semantics are in the pkc-js page sorts doc, "Reserved options".
|
|
126
|
+
Keep `noBumpKeywords` public unless you have a reason not to: a UI reads it to know the board supports sage, to put the keyword on the wire the way the sort reads it, and to mark saged replies. With the option private the order still applies but UIs cannot do any of that. The reserved options cannot be made private (`ERR_PAGE_SORT_RESERVED_OPTION_CANNOT_BE_PRIVATE`): a client re-sorting a page must know how its comment set was filtered. Their full semantics are in the pkc-js page sorts doc, "Reserved options". If pkc-js adds a reserved exclusion, this package needs a release that applies it to replies too.
|
|
127
127
|
|
|
128
128
|
### Validation
|
|
129
129
|
|
|
@@ -190,11 +190,11 @@ import { isNoBumpReplyForOptions } from "@pkcprotocol/active-page-sort";
|
|
|
190
190
|
const saged = isNoBumpReplyForOptions(reply.content, community.pageSorts?.posts?.active?.publicOptions);
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
`isNoBumpContent(content, keywords)` and `splitKeywords(raw)` are the same test without the options lookup, and `bumpTimestamp({ postTimestamp, replies, keywords })` gives a thread's effective bump time
|
|
193
|
+
`isNoBumpContent(content, keywords)` and `splitKeywords(raw)` are the same test without the options lookup, and `bumpTimestamp({ postTimestamp, replies, keywords, options })` gives a thread's effective bump time from a reply tree walked from reply pages, tombstones included, with the published `publicOptions` as `options`. `isCountedReply(reply, options)` is the exclusion check on its own. `src/no-bump.ts` imports nothing and bundles for a browser.
|
|
194
194
|
|
|
195
195
|
### 4. Re-sort a page locally (optional)
|
|
196
196
|
|
|
197
|
-
pkc-js exports no sorter: the package's `score` is the whole contract. A UI that wants to re-order threads between community publishes (a user's own reply just landed, say) applies `score` itself. This sort declares `requireReplies`, so it needs each thread's replies: walk a flat reply sort (`newFlat` / `oldFlat`) when the post carries one, otherwise the nested sort's pages recursively, at every depth. Walk all of it: every page of every chain, for every thread you re-sort. `score` receives a plain list and cannot tell a partial walk from a complete one, so a thread whose newest bump is on a page you did not fetch ranks too low, with no error. Hand each post its descendants by `parentCid`, as walked, and pass the published `publicOptions` as `options`.
|
|
197
|
+
pkc-js exports no sorter: the package's `score` is the whole contract. A UI that wants to re-order threads between community publishes (a user's own reply just landed, say) applies `score` itself. This sort declares `requireReplies`, so it needs each thread's replies: walk a flat reply sort (`newFlat` / `oldFlat`) when the post carries one, otherwise the nested sort's pages recursively, at every depth. Walk all of it: every page of every chain, for every thread you re-sort. `score` receives a plain list and cannot tell a partial walk from a complete one, so a thread whose newest bump is on a page you did not fetch ranks too low, with no error. Hand each post its descendants by `parentCid`, as walked, tombstones included, and pass the published `publicOptions` as `options`. `score` skips what the published `exclude*` options drop, keeping the replies under it, so no filtering is needed on the UI side. Pass the options as published: without the `exclude*` flags nothing is skipped. Reply pages keep removed and deleted replies only while the reply sort's `excludeRemovedComments` and `excludeDeletedComments` are `"false"` (the default). A reply sort with either turned on leaves tombstones out, and a nested one loses the replies under them too, so check `community.pageSorts.replies` and walk a published reply sort that keeps them. If none does, a client cannot reproduce the community's order. The worked example of the walk and the re-sort is `test/node-and-browser/pages/page-sorts-client-test-util.ts` in pkc-js; this package's own copy of the re-sort is `test/unit/resort-like-a-ui.ts`. A re-sorted page is still one slice of one sort: re-sorting it gives that slice in another order, not the whole board.
|
|
198
198
|
|
|
199
199
|
```ts
|
|
200
200
|
import activePageSort from "@pkcprotocol/active-page-sort";
|
|
@@ -218,7 +218,7 @@ Treat `community.pageSorts[].name` as data: look it up in your own registry, nev
|
|
|
218
218
|
| `SORT_NAME`, `DESCRIPTION`, `optionInputs`, `validatePageSortSettings` | The file's declared surface, for config UIs and tests |
|
|
219
219
|
| `NO_BUMP_KEYWORDS_OPTION` | `"noBumpKeywords"`, the option key. Renaming it is a new contract |
|
|
220
220
|
| `splitKeywords`, `isNoBumpContent`, `bumpTimestamp`, `isNoBumpReplyForOptions` | The matching rules, import-free, for browsers |
|
|
221
|
-
| `
|
|
221
|
+
| `isCountedReply`, `EXCLUSION_OPTIONS` | Whether a reply can bump at all: the published `exclude*` options checked against its own CommentUpdate, never its ancestors. Import-free |
|
|
222
222
|
| `PageSortFile`, `PageSortFileInput`, `PageSortFileFactory`, `PageSortFileFactoryInput`, `PageSortFileFactoryArgs`, `PageSortScoreArgs`, `PageSortReplyEntry`, `PageSortScope`, `CommunityPageSortSetting` | Re-exported pkc-js types (root exports since pkc-js 0.0.110) |
|
|
223
223
|
| `PageSortSettingsEntry` | Alias of `CommunityPageSortSetting`, the `settings.pages` entry the factory receives |
|
|
224
224
|
|
|
@@ -234,10 +234,10 @@ npm run test:e2e # builds, starts a throwaway kubo daemon, runs a real commun
|
|
|
234
234
|
| Path | What it is |
|
|
235
235
|
|---|---|
|
|
236
236
|
| `src/active-page-sort.ts` | The factory: `sortName`, `scope`, `requireReplies`, `optionInputs`, `score`, `validatePageSortSettings` |
|
|
237
|
-
| `src/no-bump.ts` | Keyword splitting, the line match, the bump-time fold. Imports nothing |
|
|
237
|
+
| `src/no-bump.ts` | Keyword splitting, the line match, the tombstone rule, the bump-time fold. Imports nothing |
|
|
238
238
|
| `src/types.ts` | Re-exports of the pkc-js page sort types |
|
|
239
239
|
| `test/unit/` | The contract: match semantics, scorer, validation, and a client re-sort reproducing the community order |
|
|
240
|
-
| `test/e2e/` | A kubo daemon from the `kubo` npm package on ports 15301/18380/24301 (override with `KUBO_E2E_*_PORT`), a pkc-js community configured by `path`, `sage` and normal replies at depths 1 to 3, author deletions
|
|
240
|
+
| `test/e2e/` | A kubo daemon from the `kubo` npm package on ports 15301/18380/24301 (override with `KUBO_E2E_*_PORT`), a pkc-js community configured by `path`, `sage` and normal replies at depths 1 to 3, author deletions, including a deleted sage reply whose answer still bumps, read back by a second PKC over IPNS and re-sorted from the walked reply tree |
|
|
241
241
|
|
|
242
242
|
The e2e writes everything under `.tmp/` in the repository and removes it afterwards.
|
|
243
243
|
|
package/dist/active-page-sort.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NO_BUMP_KEYWORDS_OPTION,
|
|
1
|
+
import { NO_BUMP_KEYWORDS_OPTION, bumpTimestamp, isNoBumpContent, splitKeywords } from "./no-bump.js";
|
|
2
2
|
/** The wire key: this sort publishes under `community.posts.pages.active` / `pageCids.active`, like the built-in it replaces. */
|
|
3
3
|
export const SORT_NAME = "active";
|
|
4
4
|
export const DESCRIPTION = "Bump order where a reply whose content has a line equal to a configured keyword does not bump its thread";
|
|
@@ -49,13 +49,9 @@ export const activePageSort = ({ pageSortSettings }) => {
|
|
|
49
49
|
optionInputs,
|
|
50
50
|
defaultOptions: {},
|
|
51
51
|
// MAX(timestamp) over the post and its counted, non-no-bump descendants. pkc-js hands `replies` as one flat
|
|
52
|
-
// list of every descendant
|
|
53
|
-
//
|
|
54
|
-
score: ({ comment,
|
|
55
|
-
postTimestamp: comment.timestamp,
|
|
56
|
-
replies: applyReplyExclusions({ postCid: commentUpdate.cid, replies: replies ?? [], options }),
|
|
57
|
-
keywords
|
|
58
|
-
}),
|
|
52
|
+
// list of every descendant, tombstones and pending replies included (pkc-js#407), and so does a client's walk
|
|
53
|
+
// of the reply pages; bumpTimestamp skips what the exclude* options drop and keeps the replies under them.
|
|
54
|
+
score: ({ comment, options, replies }) => bumpTimestamp({ postTimestamp: comment.timestamp, replies: replies ?? [], keywords, options }),
|
|
59
55
|
validatePageSortSettings
|
|
60
56
|
};
|
|
61
57
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active-page-sort.js","sourceRoot":"","sources":["../src/active-page-sort.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"active-page-sort.js","sourceRoot":"","sources":["../src/active-page-sort.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEtG,iIAAiI;AACjI,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAElC,MAAM,CAAC,MAAM,WAAW,GAAG,0GAA0G,CAAC;AAEtI,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAEtC,MAAM,CAAC,MAAM,YAAY,GAA8C;IACrE;QACE,MAAM,EAAE,uBAAuB;QAC/B,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,mKAAmK;QACrK,WAAW,EAAE,MAAM;KACpB;CACF,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,gBAAuC,EAAY,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC;AAE7I;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,EAAE,gBAAgB,EAA+C,EAAQ,EAAE;IAClH,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,EAAE,CAAC,uBAAuB,CAAC,CAAC;IAChE,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO;IAC9B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,uBAAuB,6DAA6D,CAAC,CAAC;IACpI,IAAI,QAAQ,CAAC,MAAM,GAAG,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,uBAAuB,UAAU,QAAQ,CAAC,MAAM,6BAA6B,YAAY,EAAE,CAAC,CAAC;IACpJ,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,kBAAkB;YACrC,MAAM,IAAI,KAAK,CAAC,GAAG,uBAAuB,aAAa,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,OAAO,CAAC,MAAM,0BAA0B,kBAAkB,EAAE,CAAC,CAAC;QACtJ,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,uBAAuB,2DAA2D,CAAC,CAAC;IACrI,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAwB,CAAC,EAAE,gBAAgB,EAA2B,EAAgB,EAAE;IACjH,MAAM,QAAQ,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC9C,OAAO;QACL,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,WAAW;QACxB,KAAK,EAAE,OAAO;QACd,cAAc,EAAE,IAAI;QACpB,YAAY;QACZ,cAAc,EAAE,EAAE;QAClB,4GAA4G;QAC5G,8GAA8G;QAC9G,2GAA2G;QAC3G,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACxI,wBAAwB;KACzB,CAAC;AACJ,CAAC,CAAC;AAEF,qHAAqH;AACrH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,OAAgB,EAAE,OAA2C,EAAW,EAAE,CAChH,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;AAE9E,eAAe,cAAc,CAAC"}
|
package/dist/no-bump.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NoBumpReplyLike } from "./types.js";
|
|
2
2
|
/** The option key under `settings.pages.posts[i].options`. Published as `community.pageSorts.posts.active.publicOptions.noBumpKeywords`. */
|
|
3
3
|
export declare const NO_BUMP_KEYWORDS_OPTION = "noBumpKeywords";
|
|
4
4
|
/** Comma-separated list to keywords: trimmed, empty entries dropped, duplicates kept out. */
|
|
@@ -11,35 +11,34 @@ export declare const splitKeywords: (raw: string | undefined) => string[];
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const isNoBumpContent: (content: unknown, keywords: readonly string[]) => boolean;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
export declare const bumpTimestamp: ({ postTimestamp, replies, keywords }: {
|
|
19
|
-
postTimestamp: number;
|
|
20
|
-
replies: readonly NoBumpReplyLike[];
|
|
21
|
-
keywords: readonly string[];
|
|
22
|
-
}) => number;
|
|
23
|
-
/**
|
|
24
|
-
* The reserved exclusion options this package re-applies, and what each drops. `excludeCommentsWithDifferentCommunityAddress`
|
|
25
|
-
* is not here: it compares against the community's address, which a sort file is never given (pkc-js#401).
|
|
14
|
+
* The reserved exclusion options a reply's own CommentUpdate can be checked against, and what each drops.
|
|
15
|
+
* `excludeCommentsWithDifferentCommunityAddress` is not here: pkc-js drops foreign replies before `score` runs,
|
|
16
|
+
* and checking it needs the community's address, which a sort file is never given (pkc-js#401).
|
|
26
17
|
*/
|
|
27
18
|
export declare const EXCLUSION_OPTIONS: {
|
|
28
|
-
readonly excludeRemovedComments: (update:
|
|
29
|
-
readonly excludeDeletedComments: (update:
|
|
30
|
-
readonly excludeCommentWithApprovedFalse: (update:
|
|
31
|
-
readonly excludeCommentPendingApproval: (update:
|
|
19
|
+
readonly excludeRemovedComments: (update: NoBumpReplyLike["commentUpdate"]) => boolean;
|
|
20
|
+
readonly excludeDeletedComments: (update: NoBumpReplyLike["commentUpdate"]) => boolean;
|
|
21
|
+
readonly excludeCommentWithApprovedFalse: (update: NoBumpReplyLike["commentUpdate"]) => boolean;
|
|
22
|
+
readonly excludeCommentPendingApproval: (update: NoBumpReplyLike["commentUpdate"]) => boolean;
|
|
32
23
|
};
|
|
33
24
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* off. Pass the published `publicOptions`; without the flags nothing is dropped. Linear in the thread's replies.
|
|
25
|
+
* Whether a reply's own timestamp can bump its thread: no `exclude*` option that is on drops it. The tombstone rule
|
|
26
|
+
* of pkc-js's "Which replies count": a dropped reply does not bump, the replies under it are judged on their own, so
|
|
27
|
+
* a present reply under a removed or deleted one still bumps. With the `posts` defaults (every flag on) this is what
|
|
28
|
+
* the built-in `active` counts. Flags parse like pkc-js parses them: trimmed, case-insensitive `"true"` is on,
|
|
29
|
+
* anything else off. Pass the published `publicOptions`; without the flags nothing is dropped.
|
|
40
30
|
*/
|
|
41
|
-
export declare const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
export declare const isCountedReply: (reply: NoBumpReplyLike, options: Readonly<Record<string, string>>) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* A thread's bump time: the newest of the post's own timestamp and the timestamps of its counted replies (see
|
|
34
|
+
* `isCountedReply`) that are not no-bump replies. Each reply is judged on its own: a normal reply under a saged
|
|
35
|
+
* reply or a tombstone bumps. This is exactly what `score` returns, exposed so a UI can show "last bump" without
|
|
36
|
+
* the page sort machinery. Pass the replies as pkc-js or a reply-page walk gives them, tombstones included, and
|
|
37
|
+
* the published `publicOptions` as `options`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const bumpTimestamp: ({ postTimestamp, replies, keywords, options }: {
|
|
40
|
+
postTimestamp: number;
|
|
41
|
+
replies: readonly NoBumpReplyLike[];
|
|
42
|
+
keywords: readonly string[];
|
|
43
|
+
options?: Readonly<Record<string, string>>;
|
|
44
|
+
}) => number;
|
package/dist/no-bump.js
CHANGED
|
@@ -26,23 +26,9 @@ export const isNoBumpContent = (content, keywords) => {
|
|
|
26
26
|
return keywords.some((keyword) => lines.includes(keyword));
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*/
|
|
33
|
-
export const bumpTimestamp = ({ postTimestamp, replies, keywords }) => {
|
|
34
|
-
let bump = postTimestamp;
|
|
35
|
-
for (const reply of replies) {
|
|
36
|
-
if (isNoBumpContent(reply.comment.content, keywords))
|
|
37
|
-
continue;
|
|
38
|
-
if (reply.comment.timestamp > bump)
|
|
39
|
-
bump = reply.comment.timestamp;
|
|
40
|
-
}
|
|
41
|
-
return bump;
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* The reserved exclusion options this package re-applies, and what each drops. `excludeCommentsWithDifferentCommunityAddress`
|
|
45
|
-
* is not here: it compares against the community's address, which a sort file is never given (pkc-js#401).
|
|
29
|
+
* The reserved exclusion options a reply's own CommentUpdate can be checked against, and what each drops.
|
|
30
|
+
* `excludeCommentsWithDifferentCommunityAddress` is not here: pkc-js drops foreign replies before `score` runs,
|
|
31
|
+
* and checking it needs the community's address, which a sort file is never given (pkc-js#401).
|
|
46
32
|
*/
|
|
47
33
|
export const EXCLUSION_OPTIONS = {
|
|
48
34
|
excludeRemovedComments: (update) => update.removed === true,
|
|
@@ -50,37 +36,33 @@ export const EXCLUSION_OPTIONS = {
|
|
|
50
36
|
excludeCommentWithApprovedFalse: (update) => update.approved === false,
|
|
51
37
|
excludeCommentPendingApproval: (update) => update.pendingApproval === true
|
|
52
38
|
};
|
|
39
|
+
const exclusionsOf = (options) => Object.entries(EXCLUSION_OPTIONS)
|
|
40
|
+
.filter(([option]) => options[option]?.trim().toLowerCase() === "true")
|
|
41
|
+
.map(([, drops]) => drops);
|
|
42
|
+
/**
|
|
43
|
+
* Whether a reply's own timestamp can bump its thread: no `exclude*` option that is on drops it. The tombstone rule
|
|
44
|
+
* of pkc-js's "Which replies count": a dropped reply does not bump, the replies under it are judged on their own, so
|
|
45
|
+
* a present reply under a removed or deleted one still bumps. With the `posts` defaults (every flag on) this is what
|
|
46
|
+
* the built-in `active` counts. Flags parse like pkc-js parses them: trimmed, case-insensitive `"true"` is on,
|
|
47
|
+
* anything else off. Pass the published `publicOptions`; without the flags nothing is dropped.
|
|
48
|
+
*/
|
|
49
|
+
export const isCountedReply = (reply, options) => !exclusionsOf(options).some((drops) => drops(reply.commentUpdate));
|
|
53
50
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* off. Pass the published `publicOptions`; without the flags nothing is dropped. Linear in the thread's replies.
|
|
51
|
+
* A thread's bump time: the newest of the post's own timestamp and the timestamps of its counted replies (see
|
|
52
|
+
* `isCountedReply`) that are not no-bump replies. Each reply is judged on its own: a normal reply under a saged
|
|
53
|
+
* reply or a tombstone bumps. This is exactly what `score` returns, exposed so a UI can show "last bump" without
|
|
54
|
+
* the page sort machinery. Pass the replies as pkc-js or a reply-page walk gives them, tombstones included, and
|
|
55
|
+
* the published `publicOptions` as `options`.
|
|
60
56
|
*/
|
|
61
|
-
export const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
.map(([, drops]) => drops);
|
|
65
|
-
const childrenOf = new Map();
|
|
57
|
+
export const bumpTimestamp = ({ postTimestamp, replies, keywords, options = {} }) => {
|
|
58
|
+
const exclusions = exclusionsOf(options);
|
|
59
|
+
let bump = postTimestamp;
|
|
66
60
|
for (const reply of replies) {
|
|
67
|
-
|
|
68
|
-
if (!parentCid || active.some((drops) => drops(reply.commentUpdate)))
|
|
61
|
+
if (exclusions.some((drops) => drops(reply.commentUpdate)) || isNoBumpContent(reply.comment.content, keywords))
|
|
69
62
|
continue;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
siblings.push(reply);
|
|
73
|
-
else
|
|
74
|
-
childrenOf.set(parentCid, [reply]);
|
|
75
|
-
}
|
|
76
|
-
const counted = [];
|
|
77
|
-
const stack = [postCid];
|
|
78
|
-
for (let cid = stack.pop(); cid !== undefined; cid = stack.pop()) {
|
|
79
|
-
for (const child of childrenOf.get(cid) ?? []) {
|
|
80
|
-
counted.push(child);
|
|
81
|
-
stack.push(child.commentUpdate.cid);
|
|
82
|
-
}
|
|
63
|
+
if (reply.comment.timestamp > bump)
|
|
64
|
+
bump = reply.comment.timestamp;
|
|
83
65
|
}
|
|
84
|
-
return
|
|
66
|
+
return bump;
|
|
85
67
|
};
|
|
86
68
|
//# sourceMappingURL=no-bump.js.map
|
package/dist/no-bump.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-bump.js","sourceRoot":"","sources":["../src/no-bump.ts"],"names":[],"mappings":"AAEA,wGAAwG;AACxG,qGAAqG;AACrG,oFAAoF;AAEpF,4IAA4I;AAC5I,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAExD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAuB,EAAY,EAAE;IACjE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAE,QAA2B,EAAW,EAAE;IACxF,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClG,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"no-bump.js","sourceRoot":"","sources":["../src/no-bump.ts"],"names":[],"mappings":"AAEA,wGAAwG;AACxG,qGAAqG;AACrG,oFAAoF;AAEpF,4IAA4I;AAC5I,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAExD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAuB,EAAY,EAAE;IACjE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAE,QAA2B,EAAW,EAAE;IACxF,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClG,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,sBAAsB,EAAE,CAAC,MAAwC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI;IAC7F,sBAAsB,EAAE,CAAC,MAAwC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;IACnG,+BAA+B,EAAE,CAAC,MAAwC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,KAAK;IACxG,6BAA6B,EAAE,CAAC,MAAwC,EAAE,EAAE,CAAC,MAAM,CAAC,eAAe,KAAK,IAAI;CACpG,CAAC;AAEX,MAAM,YAAY,GAAG,CAAC,OAAyC,EAAE,EAAE,CACjE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC;KAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;KACtE,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAsB,EAAE,OAAyC,EAAW,EAAE,CAC3G,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;AAErE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,aAAa,EACb,OAAO,EACP,QAAQ,EACR,OAAO,GAAG,EAAE,EAMb,EAAU,EAAE;IACX,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,IAAI,GAAG,aAAa,CAAC;IACzB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC;YAAE,SAAS;QACzH,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI;YAAE,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;IACrE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,23 +2,16 @@ import type { CommunityPageSortSetting, PageSortFile, PageSortFileFactory, PageS
|
|
|
2
2
|
export type { CommunityPageSortSetting, PageSortFile, PageSortFileFactory, PageSortFileFactoryArgs, PageSortFileFactoryInput, PageSortFileInput, PageSortReplyEntry, PageSortScope, PageSortScoreArgs };
|
|
3
3
|
/** One `settings.pages.posts[i]` entry as pkc-js hands it to the factory. Alias of pkc-js's `CommunityPageSortSetting`. */
|
|
4
4
|
export type PageSortSettingsEntry = CommunityPageSortSetting;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The subset of a reply this package reads. A `PageSortReplyEntry` and a page entry's `raw` are both supersets;
|
|
7
|
+
* the optional fields accept an explicit `undefined`, as page entries carry them.
|
|
8
|
+
*/
|
|
6
9
|
export interface NoBumpReplyLike {
|
|
7
10
|
comment: {
|
|
8
11
|
timestamp: number;
|
|
9
12
|
content?: string | undefined;
|
|
10
13
|
};
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The subset of a reply `applyReplyExclusions` reads. A `PageSortReplyEntry` and a page entry's `raw` are both
|
|
14
|
-
* supersets; the optional fields accept an explicit `undefined`, as page entries carry them.
|
|
15
|
-
*/
|
|
16
|
-
export interface ExcludableReplyLike {
|
|
17
|
-
comment: {
|
|
18
|
-
parentCid?: string | undefined;
|
|
19
|
-
};
|
|
20
14
|
commentUpdate: {
|
|
21
|
-
cid: string;
|
|
22
15
|
removed?: boolean | undefined;
|
|
23
16
|
approved?: boolean | undefined;
|
|
24
17
|
pendingApproval?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pkcprotocol/active-page-sort",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Bump-order (active) page sort for pkc-js communities where replies carrying a configured keyword, such as sage, do not bump the thread",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@commitlint/cli": "20.4.3",
|
|
48
48
|
"@commitlint/config-conventional": "20.4.3",
|
|
49
|
-
"@pkcprotocol/pkc-js": "0.0.
|
|
49
|
+
"@pkcprotocol/pkc-js": "0.0.114",
|
|
50
50
|
"@release-it/conventional-changelog": "10.0.5",
|
|
51
51
|
"@types/node": "25.3.5",
|
|
52
52
|
"husky": "9.1.7",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"vitest": "4.0.18"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@pkcprotocol/pkc-js": ">=0.0.
|
|
59
|
+
"@pkcprotocol/pkc-js": ">=0.0.114"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"@pkcprotocol/pkc-js": {
|