import type * as DomNs from "@oh-my-pi/pi-utils/dom"; import type * as ReadabilityNs from "@oh-my-pi/pi-utils/readability"; import { htmlToBasicMarkdown } from "../../web/scrapers/types"; export type ReadableFormat = "text" | "markdown"; /** Options for scoping and reducing readable page extraction. */ export interface ReadableExtractOptions { /** Limit extraction to the first element matching this CSS selector. */ selector?: string; /** Return only a compact Markdown-style heading outline. */ outline?: boolean; /** Keep sections whose heading contains this case-insensitive substring. */ filter?: string; } export interface ReadableResult { url: string; title?: string; byline?: string; excerpt?: string; contentLength: number; text?: string; markdown?: string; } /** Trim to non-empty string or undefined. */ function normalize(text: string | null | undefined): string | undefined { const trimmed = text?.trim(); return trimmed || undefined; } let readabilityModule: typeof ReadabilityNs | undefined; async function loadReadability(): Promise { if (!readabilityModule) { readabilityModule = await import("@oh-my-pi/pi-utils/readability"); } return readabilityModule; } let domModule: typeof DomNs | undefined; async function loadDom(): Promise { if (!domModule) { domModule = await import("@oh-my-pi/pi-utils/dom"); } return domModule; } /** * Elements that end a line of rendered text, which `textContent` runs together. As in `innerText`, * `

` also leaves a blank line, `
` is one line break, and table cells are tab-separated. */ const BLOCK_TAGS: Readonly> = { ADDRESS: true, ARTICLE: true, ASIDE: true, BLOCKQUOTE: true, CAPTION: true, DD: true, DETAILS: true, DIALOG: true, DIV: true, DL: true, DT: true, FIELDSET: true, FIGCAPTION: true, FIGURE: true, FOOTER: true, FORM: true, H1: true, H2: true, H3: true, H4: true, H5: true, H6: true, HEADER: true, HR: true, LI: true, MAIN: true, NAV: true, OL: true, P: true, PRE: true, SECTION: true, SUMMARY: true, TABLE: true, TR: true, UL: true, }; /** Never rendered as text; skipped below the extraction root. */ const SKIP_TAGS: Readonly> = { SCRIPT: true, STYLE: true, NOSCRIPT: true, TEMPLATE: true }; const ELEMENT_NODE = 1; const TEXT_NODE = 3; /** * Text of a subtree with line breaks where the document has block boundaries. * Whitespace outside `

` is collapsed the way a renderer collapses it;
 * `
` keeps its indentation and blank lines. A raw-text root (`