page-markdown 0.2.0 → 0.3.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/README.md CHANGED
@@ -44,7 +44,7 @@ import { pageToMarkdown } from "page-markdown";
44
44
  const result = pageToMarkdown(document);
45
45
  result.markdown; // "# Title\n\n…"
46
46
  result.metadata; // { url, canonicalUrl, title, author, published, siteName, lang, … }
47
- result.extraction.method; // "readability" | "full_page"
47
+ result.extraction.method; // "readability" | "main_content" | "full_page" | custom method
48
48
 
49
49
  // In Node, with any DOM implementation (linkedom shown)
50
50
  import { parseHTML } from "linkedom";
@@ -57,11 +57,11 @@ pageToMarkdown(doc, { url: "https://example.com/article" });
57
57
  | Option | Default | Meaning |
58
58
  |---|---|---|
59
59
  | `url` | the document's URL | Base for absolute links and images |
60
- | `extract` | `"readable"` | `"readable"` (Readability, full-page fallback), `"full"`, or a function `(docCopy) => { html, method }` |
61
- | `minExtractChars` | `100` | Below this, readable extraction falls back to the full page |
60
+ | `extract` | `"readable"` | `"readable"` (Readability with main-content or full-page fallback), `"full"`, or a function `(docCopy) => { html, method }` |
61
+ | `minExtractChars` | `100` | Below this, Readability falls back to a sufficiently long `<main>` or `[role="main"]`, else the full page; custom extractors fall back to the full page |
62
62
  | `minExtractShare` | `0.4` | When Readability keeps less than this share of the page's main content (and misses at least 500 characters), the main content is used instead: forum threads and product grids. `0` turns it off |
63
63
  | `includeFullPage` | `false` | Also return `fullPageMarkdown` |
64
- | `includeTitle` | `true` | Start readable output with `# <headline>`: the page's H1, else its title without the site name |
64
+ | `includeTitle` | `true` | Add `# <headline>` to Readability or main-content output when no H1 remains; skipped in `text_only` mode |
65
65
  | `recoverLead` | `true` | Restore a lead paragraph the extractor dropped, when it matches the meta description |
66
66
  | `pageFacts` | `false` | Append `## Page facts`: short label/value pairs outside the article (a sidebar's version or license, spec rows, infobox rows) that the extractor dropped. `extraction.pageFacts` counts them |
67
67
  | `removeHiddenText` | `false` | Remove text hidden from people but read by models: runs of tag characters, variation selectors, invisible operators and reserved tag-plane code points, found by [text-sanctifier](https://github.com/iWhatty/text-sanctifier)'s `findHiddenRuns`. Applies to `markdown` (fenced code included), `fullPageMarkdown` and `metadata`. The England, Scotland and Wales flags and a lone emoji or text presentation selector are kept. `omissions` gets one entry per kind (`hidden_tag`, `hidden_variation_selector`, `hidden_invisible_operator`, `hidden_reserved_tag_plane`) with `count` runs and `chars` code points removed from `markdown` |
@@ -88,17 +88,64 @@ The stages are also exported for advanced use: `sanitizeToInertDocument`,
88
88
  | `metadata.headline` | `string?` | The heading chosen for the capture |
89
89
  | `metadata.extractedTitle`, `byline` | `string?` | What the extractor reported |
90
90
  | `extraction.method` | `string` | `"readability"`, `"main_content"`, `"full_page"`, or the name a custom extractor gave |
91
- | `extraction.fallbackReason` | `string?` | Why extraction fell back: `extracted_small_share_of_page`, `extracted_site_menu` or `extracted_few_sections_of_page` (to the main content), or `extractor_found_nothing`, `extracted_text_below_minimum` or `extractor_error: …` (to the full page) |
91
+ | `extraction.fallbackReason` | `string?` | Why extraction fell back: `extracted_small_share_of_page`, `extracted_site_menu` or `extracted_few_sections_of_page` use the main content; `extractor_found_nothing`, `extracted_text_below_minimum` or `extractor_error: ...` use a sufficiently long main-content region for Readability, else the full page. Custom extractors fall back to the full page |
92
92
  | `extraction.leadRecovered` | `true?` | A dropped lead paragraph was restored |
93
93
  | `extraction.pageFacts` | `number?` | How many facts `pageFacts` appended |
94
94
  | `omissions[]` | `{ kind, count, note, chars? }` | What was left out or removed. `kind` is `shadow_content`, `json_line`, `inactive_tabs`, `comments` or a `hidden_*` kind; only `hidden_*` entries have `chars`. All but `hidden_*` also add a closing line to `markdown`, as a fallback does, so a caller that passes on only the Markdown still says so |
95
95
  | `warnings[]` | `string` | Read these before trusting a capture: a fallback, or shadow content cut off at the capture limit, in which case the main content may be missing |
96
96
  | `stats.mode` | `string` | The output mode used |
97
97
  | `stats.extractedHtmlChars`, `convertedChars`, `markdownChars` | `number` | Sizes at each step. `markdownChars` is the returned Markdown's length |
98
- | `stats.standardization` | `object` | Counts of what standardizing changed: `lazyImagesResolved`, `inlineImagesDropped`, `scriptLinksUnwrapped`, `emptyLinksRemoved`, `preWrapped`, `headingWidgetsRemoved`, `headingsUnwrapped` |
98
+ | `stats.pageTextChars` | `number` | Whitespace-normalized text length of the standardized page at capture time |
99
+ | `stats.standardization` | `object` | Counts of what standardizing changed: `lazyImagesResolved`, `inlineImagesDropped`, `scriptLinksUnwrapped`, `invalidLinksUnwrapped`, `emptyLinksRemoved`, `preWrapped`, `headingWidgetsRemoved`, `headingsUnwrapped`; `omittedTabs` lists labels of inactive panels left out |
99
100
 
100
101
  A `?` marks a field that is absent, not empty, when it doesn't apply.
101
102
 
103
+ ### Concealed text (opt-in)
104
+
105
+ Detection is off by default. Enable it for a live document:
106
+
107
+ ```js
108
+ const result = pageToMarkdown(document, {
109
+ detectConcealedText: true,
110
+ includeFullPage: true,
111
+ });
112
+ ```
113
+
114
+ Supported computed styles separate concealed DOM text from the article and
115
+ return it in `concealedText` as `{ reason, chars, text }` blocks. Text is capped
116
+ at 2,048 UTF-16 units per block, 16,384 total and 100 entries;
117
+ `concealedTextTruncated` reports capping. Omission counts and character totals
118
+ are uncapped. A `Concealed text omitted:` notice appears in both `markdown`
119
+ and `fullPageMarkdown` when text is captured.
120
+
121
+ The seven reasons are `display_none`, `visibility_hidden`, `opacity_zero`,
122
+ `font_size_zero`, `clipped`, `transparent_text` and `same_color`. Supported
123
+ clips are zero-area legacy rectangles on absolute/fixed elements,
124
+ `inset(50%)` and zero-radius circles. Colour checks compare computed sRGB
125
+ rgb()/rgba() values for an element's own text against its own or nearest
126
+ declared opaque background.
127
+
128
+ `concealmentCheck` is `disabled` by default (no computed-style calls),
129
+ `static-only` when enabled without a computed-style API, `computed` after a
130
+ successful check, or `incomplete` if the capability probe or a consulted
131
+ property fails. Detection applies to live documents only; enabled static
132
+ input retains its previous output. A top-level `detectConcealedText` option
133
+ takes precedence over `sanitize.detectConcealedText`, then the default false.
134
+
135
+ Limits include white text with no declared opaque background, non-zero alpha,
136
+ near-colour matches, and unparsed OKLCH and Display P3 colours. Intervening
137
+ images or translucent backgrounds and uncertain text painting effects prevent
138
+ colour equality detection. Existing short inactive tab, collapsed answer and
139
+ code wrapper exceptions remain; independent concealment reasons still apply.
140
+ Off-screen or covered text, attributes, metadata and custom-extractor output
141
+ are outside this check.
142
+
143
+ The scroll-reveal rule is **provisional**: zero-opacity content with an
144
+ opacity-applicable transition or named animation of nonzero duration stays
145
+ in place with a warning. A declaration does not prove it will appear, and a
146
+ page can use one to evade detection. Real-site validation is still required.
147
+ Chrome is the only browser tested; Firefox and Safari are untested.
148
+
102
149
  ### TypeScript
103
150
 
104
151
  Types ship with the package, generated from the JSDoc in `src`: `pageToMarkdown`,
@@ -127,7 +174,7 @@ const { markdown: safe, omissions } = pageToMarkdown(document, { removeHiddenTex
127
174
  ## Quality
128
175
 
129
176
  `npm test` runs the contract suite. It covers:
130
- - golden outputs for 34 fixtures;
177
+ - golden outputs for 51 fixtures;
131
178
  - regressions for known converter bugs, including dropped lead paragraphs,
132
179
  raw HTML tables, flattened code in tables, site-suffixed titles and
133
180
  unescaped table pipes;
@@ -179,7 +226,7 @@ its products. What it still fails on these pages:
179
226
 
180
227
  - **Promo boxes kept** inside a BBC Arabic article.
181
228
 
182
- Browser bundle: about 75 KB minified, 26 KB gzip (`npm run size`, ceiling 76 KB
229
+ Browser bundle: about 86.0 KB minified, 31.2 KB gzip (`npm run size`, ceiling 86 KB
183
230
  minified). Readability is most of it.
184
231
 
185
232
  `npm run ci` runs the contract suite, the type check and the size check, and must
@@ -202,8 +249,6 @@ deliberately has no cloud CI.
202
249
  kept by the main-content fallback only; Readability removes hidden text.
203
250
  - **Bylines and dates can be missing from the Markdown.** Readability often leaves
204
251
  them out of the article. Look in `metadata.author` and `metadata.published`.
205
- - **Note labels can be lost.** On Docker's docs, "Warning", "Note" and "Tip" are
206
- dropped, though the blockquotes stay.
207
252
  - **Some legacy markup loses paragraph breaks.** Readability can drop runs of
208
253
  `<br>` between inline elements in old HTML. The text survives; a paragraph
209
254
  break may not.