A newly loaded reply.
diff --git a/src/content/definition-engine.ts b/src/content/definition-engine.ts index 02b4476..7adebc2 100644 --- a/src/content/definition-engine.ts +++ b/src/content/definition-engine.ts @@ -78,7 +78,7 @@ export class DefinitionEngine { const matches = Array.from(post.querySelectorAll(selector)); const contents = post.matches(selector) ? [post, ...matches] : matches; for (const content of contents) { - const text = visibleText(content, this.definition.post.excludedSelectors ?? []); + const text = visibleText(content, this.definition.post.excludedSelectors ?? [], post); if (text) return text; } } @@ -140,13 +140,19 @@ function wildcardMatches(pattern: string, value: string): boolean { function escapeRegex(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -function visibleText(element: Element, excludedSelectors: string[]): string { +function visibleText(element: Element, excludedSelectors: string[], postBoundary: Element): string { const chunks: string[] = []; const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); let node: Node | null; while ((node = walker.nextNode())) { const parent = node.parentElement; - if (!parent || excludedSelectors.some((selector) => parent.closest(selector))) continue; + // An excluded container only applies when it is inside this post's + // boundary. Nested Reddit comments are themselves inside an ancestor + // `.child` container, which must not exclude the comment's own text. + if (!parent || excludedSelectors.some((selector) => { + const excluded = parent.closest(selector); + return excluded !== null && postBoundary.contains(excluded); + })) continue; if (isVisible(parent, element)) chunks.push(node.textContent ?? ""); } return normalizeText(chunks.join(" ")); diff --git a/src/content/definitions/default.json b/src/content/definitions/default.json index a2fd27b..b0f8934 100644 --- a/src/content/definitions/default.json +++ b/src/content/definitions/default.json @@ -44,12 +44,12 @@ "https://old.reddit.com/*" ], "requiredSelectors": [ - "#siteTable .thing.link, #siteTable .thing.comment" + "#siteTable .thing.link, .commentarea .thing.comment > .entry" ], "post": { "rootSelectors": [ "#siteTable > .thing.link", - "#siteTable .thing.comment" + ".commentarea .thing.comment > .entry" ], "textSelectors": [ ".md", diff --git a/src/content/definitions/sites/reddit-old.json b/src/content/definitions/sites/reddit-old.json index a7998a8..00c4efd 100644 --- a/src/content/definitions/sites/reddit-old.json +++ b/src/content/definitions/sites/reddit-old.json @@ -2,9 +2,9 @@ "id": "reddit-old", "name": "Reddit (old interface)", "urlPatterns": ["https://old.reddit.com/*"], - "requiredSelectors": ["#siteTable .thing.link, #siteTable .thing.comment"], + "requiredSelectors": ["#siteTable .thing.link, .commentarea .thing.comment > .entry"], "post": { - "rootSelectors": ["#siteTable > .thing.link", "#siteTable .thing.comment"], + "rootSelectors": ["#siteTable > .thing.link", ".commentarea .thing.comment > .entry"], "textSelectors": [".md", ".title"], "excludedSelectors": [".expando", ".child"], "idAttributes": ["data-fullname"], diff --git a/tests/definition-engine.test.ts b/tests/definition-engine.test.ts index c0708cc..bb065d9 100644 --- a/tests/definition-engine.test.ts +++ b/tests/definition-engine.test.ts @@ -95,12 +95,35 @@ describe("DefinitionEngine", () => { expect(posts.map((post) => post.text)).toEqual(["the main post on the threads detail page", "a reply on the threads detail page"]); }); - it("discovers posts from the old Reddit interface using fullname IDs", () => { + it("discovers old Reddit posts and comment entries from their separate page containers", () => { document.body.innerHTML = readFileSync("tests/fixtures/reddit-old-feed.html", "utf8"); const reddit = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "reddit-old")!; const posts = new DefinitionEngine(reddit).discover(document); - expect(posts.map((post) => post.id)).toEqual(["reddit-old:t3_abc123", "reddit-old:t1_def456"]); - expect(posts.map((post) => post.text)).toEqual(["the post body with visible text.", "a useful comment with visible text."]); + expect(posts.map((post) => post.id)).toEqual([ + "reddit-old:t3_abc123", + "reddit-old:https://old.reddit.com/r/example/comments/abc123/example_post/def456/", + "reddit-old:https://old.reddit.com/r/example/comments/abc123/example_post/ghi789/" + ]); + expect(posts.map((post) => post.text)).toEqual([ + "the post body with visible text.", + "a useful comment with visible text.", + "a nested reply with its own visible text." + ]); + }); + + it("discovers old Reddit comment entries appended after observation starts", async () => { + document.body.innerHTML = readFileSync("tests/fixtures/reddit-old-feed.html", "utf8"); + const reddit = BUILTIN_DEFINITIONS.find((candidate) => candidate.id === "reddit-old")!; + const engine = new DefinitionEngine(reddit); + const observed: string[] = []; + const stop = engine.observe((posts) => observed.push(...posts.map((post) => post.text))); + const comment = document.createElement("div"); + comment.className = "thing comment"; + comment.innerHTML = '
A newly loaded reply.
The post body with visible text.
A useful comment with visible text.