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.
@@ -16,13 +16,30 @@ export function createInertDocument(doc: Document): Document;
16
16
  *
17
17
  * @param {Document|Element} root
18
18
  * @param {import("./types.js").SanitizeOptions} [options]
19
- * @returns {{ document: Document, omissions: { shadowHosts: number } }}
20
19
  */
21
20
  export function sanitizeToInertDocument(root: Document | Element, options?: import("./types.js").SanitizeOptions): {
22
21
  document: Document;
23
22
  omissions: {
24
23
  shadowHosts: number;
25
24
  };
25
+ concealment: {
26
+ process: (el: any, copy: any, work: any, pairs: any) => void;
27
+ initial: (source: any) => any;
28
+ inherit: (work: any, child: any, clone: any) => any;
29
+ exempt: (el: any) => void;
30
+ lift: (inert: any) => void;
31
+ entries: import("./types.js").ConcealedText[];
32
+ summary: {
33
+ count: number;
34
+ chars: number;
35
+ truncated: boolean;
36
+ };
37
+ readonly counters: {
38
+ [k: string]: number;
39
+ };
40
+ readonly check: import("./types.js").ConcealmentCheck;
41
+ readonly pendingOpacity: number;
42
+ };
26
43
  };
27
44
  /** @type {Readonly<Required<import("./types.js").SanitizeOptions>>} */
28
45
  export const DEFAULT_SANITIZE_OPTIONS: Readonly<Required<import("./types.js").SanitizeOptions>>;
@@ -1,13 +1,26 @@
1
+ /**
2
+ * Tabs read as one document: inactive panels are shown, each under its tab's
3
+ * name, in place of a tab strip that points nowhere. A long inactive panel is
4
+ * left out, and named in stats.omittedTabs.
5
+ */
6
+ export function prepareConcealment(doc: any, concealment: any): {
7
+ panels: any[];
8
+ hasProvenance: any;
9
+ inactive: Map<any, boolean>;
10
+ panelLengths: Map<any, any>;
11
+ };
1
12
  /**
2
13
  * @param {Document} doc the inert clone (mutated in place)
3
- * @param {{ url?: string }} [options]
14
+ * @param {{ url?: string, concealment?: ReturnType<typeof import('./concealed.js').createConcealmentWalk> }} [options]
4
15
  */
5
16
  export function standardizeDocument(doc: Document, options?: {
6
17
  url?: string;
18
+ concealment?: ReturnType<typeof import("./concealed.js").createConcealmentWalk>;
7
19
  }): {
8
20
  lazyImagesResolved: number;
9
21
  inlineImagesDropped: number;
10
22
  scriptLinksUnwrapped: number;
23
+ invalidLinksUnwrapped: number;
11
24
  emptyLinksRemoved: number;
12
25
  preWrapped: number;
13
26
  headingWidgetsRemoved: number;
@@ -7,6 +7,10 @@ export type SanitizeOptions = {
7
7
  stripMeta?: boolean | undefined;
8
8
  stripLinkTags?: boolean | undefined;
9
9
  stripAds?: boolean | undefined;
10
+ /**
11
+ * opt-in computed-style check, default false
12
+ */
13
+ detectConcealedText?: boolean | undefined;
10
14
  /**
11
15
  * app-specific overlays or widgets to drop, e.g. "[data-app-overlay]"
12
16
  */
@@ -97,7 +101,8 @@ export type HiddenTextOmission = {
97
101
  note: string;
98
102
  };
99
103
  /**
100
- * Check `kind` before reading `chars`: only hidden-text omissions have it.
104
+ * Check `kind` before reading `chars`: hidden Unicode omissions use code points;
105
+ * concealed_text uses UTF-16 units. Other kinds do not have it.
101
106
  */
102
107
  export type PageMarkdownOmission = {
103
108
  kind: "shadow_content";
@@ -115,6 +120,11 @@ export type PageMarkdownOmission = {
115
120
  kind: "comments";
116
121
  count: number;
117
122
  note: string;
123
+ } | {
124
+ kind: "concealed_text";
125
+ count: number;
126
+ chars: number;
127
+ note: string;
118
128
  } | HiddenTextOmission;
119
129
  export type StandardizationStats = ReturnType<typeof import("./standardize.js").standardizeDocument>;
120
130
  export type PageMarkdownStats = {
@@ -134,6 +144,18 @@ export type PageMarkdownResult = {
134
144
  * the package version that made this result
135
145
  */
136
146
  version: string;
147
+ /**
148
+ * capped captured blocks, raw text in UTF-16 units
149
+ */
150
+ concealedText: ConcealedText[];
151
+ /**
152
+ * source computed-style capability
153
+ */
154
+ concealmentCheck: ConcealmentCheck;
155
+ /**
156
+ * true if any block, text or entry cap omitted report text
157
+ */
158
+ concealedTextTruncated: boolean;
137
159
  markdown: string;
138
160
  metadata: PageMarkdownMetadata;
139
161
  extraction: PageMarkdownExtraction;
@@ -149,6 +171,20 @@ export type PageFact = {
149
171
  label: string;
150
172
  value: string;
151
173
  };
174
+ /**
175
+ * The one-time probe establishes that the document's view can return every
176
+ * required field and exposed optional field. Afterwards, incomplete means a
177
+ * CONSULTED value was empty or threw; unread irrelevant values are not checked.
178
+ * Computed means no such failure; static-only means no computed-style API;
179
+ * disabled means detection was turned off.
180
+ */
181
+ export type ConcealmentCheck = "computed" | "static-only" | "disabled" | "incomplete";
182
+ export type ConcealmentReason = "display_none" | "visibility_hidden" | "opacity_zero" | "font_size_zero" | "clipped" | "transparent_text" | "same_color";
183
+ export type ConcealedText = {
184
+ reason: ConcealmentReason;
185
+ chars: number;
186
+ text: string;
187
+ };
152
188
  export type TurndownFilter = string | string[] | ((node: HTMLElement, options: Record<string, unknown>) => boolean);
153
189
  /**
154
190
  * Turndown's public methods; Turndown ships no types of its own.
package/package.json CHANGED
@@ -1,68 +1,69 @@
1
- {
2
- "name": "page-markdown",
3
- "version": "0.2.0",
4
- "description": "Faithful page-to-Markdown for humans and agents: sanitize, extract the readable article, convert with GFM, and clean without losing content.",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./dist/types/index.d.ts",
9
- "default": "./src/index.js"
10
- }
11
- },
12
- "types": "./dist/types/index.d.ts",
13
- "files": [
14
- "src",
15
- "dist/types",
16
- "LICENSE",
17
- "ADDITIONAL_TERMS.md",
18
- "THIRD_PARTY_NOTICES.md",
19
- "README.md",
20
- "CHANGELOG.md",
21
- "robots.txt"
22
- ],
23
- "sideEffects": false,
24
- "packageManager": "pnpm@10.33.0",
25
- "engines": {
26
- "node": ">=20"
27
- },
28
- "scripts": {
29
- "test": "node --test \"test/**/*.test.mjs\"",
30
- "build:types": "tsc -p tsconfig.json",
31
- "typecheck": "npm run build:types && node test/types/real-output.mjs && tsc -p test/types && tsc -p test/types/linkedom",
32
- "size": "node bench/size.mjs",
33
- "test:browser": "node test/browser/run.mjs",
34
- "ci": "npm test && npm run typecheck && npm run size",
35
- "prepack": "npm run build:types",
36
- "prepublishOnly": "npm run ci",
37
- "bench": "node bench/compare.mjs",
38
- "bench:write": "node bench/compare.mjs --write"
39
- },
40
- "keywords": [
41
- "markdown",
42
- "readability",
43
- "html-to-markdown",
44
- "reader-mode",
45
- "web-clipper",
46
- "browser-extension"
47
- ],
48
- "author": "WATT3D <WATT3D@protonmail.com>",
49
- "license": "SEE LICENSE IN LICENSE",
50
- "repository": {
51
- "type": "git",
52
- "url": "git+https://github.com/iWhatty/page-markdown.git"
53
- },
54
- "homepage": "https://github.com/iWhatty/page-markdown#readme",
55
- "bugs": {
56
- "url": "https://github.com/iWhatty/page-markdown/issues"
57
- },
58
- "dependencies": {
59
- "@mozilla/readability": "0.6.0",
60
- "text-sanctifier": "1.3.8",
61
- "turndown": "7.2.4"
62
- },
63
- "devDependencies": {
64
- "esbuild": "0.27.0",
65
- "linkedom": "0.18.12",
66
- "typescript": "5.9.3"
67
- }
68
- }
1
+ {
2
+ "name": "page-markdown",
3
+ "version": "0.3.0",
4
+ "description": "Faithful page-to-Markdown for humans and agents: sanitize, extract the readable article, convert with GFM, and clean without losing content.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/types/index.d.ts",
9
+ "default": "./src/index.js"
10
+ }
11
+ },
12
+ "types": "./dist/types/index.d.ts",
13
+ "files": [
14
+ "src",
15
+ "dist/types",
16
+ "LICENSE",
17
+ "ADDITIONAL_TERMS.md",
18
+ "THIRD_PARTY_NOTICES.md",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "robots.txt"
22
+ ],
23
+ "sideEffects": false,
24
+ "packageManager": "pnpm@10.33.0",
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "scripts": {
29
+ "test": "node --test \"test/**/*.test.mjs\"",
30
+ "build:types": "tsc -p tsconfig.json",
31
+ "typecheck": "npm run build:types && node test/types/real-output.mjs && tsc -p test/types && tsc -p test/types/linkedom",
32
+ "size": "node bench/size.mjs",
33
+ "test:browser": "node test/browser/run.mjs",
34
+ "report:build": "node test/browser/build-report.mjs",
35
+ "ci": "npm test && npm run typecheck && npm run size",
36
+ "prepack": "npm run build:types",
37
+ "prepublishOnly": "npm run ci",
38
+ "bench": "node bench/compare.mjs",
39
+ "bench:write": "node bench/compare.mjs --write"
40
+ },
41
+ "keywords": [
42
+ "markdown",
43
+ "readability",
44
+ "html-to-markdown",
45
+ "reader-mode",
46
+ "web-clipper",
47
+ "browser-extension"
48
+ ],
49
+ "author": "WATT3D <WATT3D@protonmail.com>",
50
+ "license": "SEE LICENSE IN LICENSE",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/iWhatty/page-markdown.git"
54
+ },
55
+ "homepage": "https://github.com/iWhatty/page-markdown#readme",
56
+ "bugs": {
57
+ "url": "https://github.com/iWhatty/page-markdown/issues"
58
+ },
59
+ "dependencies": {
60
+ "@mozilla/readability": "0.6.0",
61
+ "text-sanctifier": "1.3.8",
62
+ "turndown": "7.2.4"
63
+ },
64
+ "devDependencies": {
65
+ "esbuild": "0.27.0",
66
+ "linkedom": "0.18.12",
67
+ "typescript": "5.9.3"
68
+ }
69
+ }
package/robots.txt CHANGED
@@ -1,79 +1,79 @@
1
- # WATT3D — AI scraping policy
2
- #
3
- # This file enumerates known AI / LLM training and inference crawlers
4
- # that the WATT3D AI Training Rider does not consent to. See
5
- # ./ADDITIONAL_TERMS.md for the legal scope and definitions.
6
- #
7
- # Honoring robots.txt is voluntary. The rider applies whether or not
8
- # a bot respects this file. The enumeration is non-exhaustive — bots
9
- # not listed here are still bound by the rider if they perform
10
- # Training Use as defined in ADDITIONAL_TERMS.md §1.
11
-
12
- User-agent: GPTBot
13
- Disallow: /
14
-
15
- User-agent: ChatGPT-User
16
- Disallow: /
17
-
18
- User-agent: OAI-SearchBot
19
- Disallow: /
20
-
21
- User-agent: anthropic-ai
22
- Disallow: /
23
-
24
- User-agent: ClaudeBot
25
- Disallow: /
26
-
27
- User-agent: Claude-Web
28
- Disallow: /
29
-
30
- User-agent: Claude-SearchBot
31
- Disallow: /
32
-
33
- User-agent: Google-Extended
34
- Disallow: /
35
-
36
- User-agent: Applebot-Extended
37
- Disallow: /
38
-
39
- User-agent: Meta-ExternalAgent
40
- Disallow: /
41
-
42
- User-agent: Meta-ExternalFetcher
43
- Disallow: /
44
-
45
- User-agent: FacebookBot
46
- Disallow: /
47
-
48
- User-agent: CCBot
49
- Disallow: /
50
-
51
- User-agent: PerplexityBot
52
- Disallow: /
53
-
54
- User-agent: Perplexity-User
55
- Disallow: /
56
-
57
- User-agent: cohere-ai
58
- Disallow: /
59
-
60
- User-agent: Bytespider
61
- Disallow: /
62
-
63
- User-agent: Amazonbot
64
- Disallow: /
65
-
66
- User-agent: Omgilibot
67
- Disallow: /
68
-
69
- User-agent: AI2Bot
70
- Disallow: /
71
-
72
- User-agent: Diffbot
73
- Disallow: /
74
-
75
- # Everyone else: full access. Search engines and human-driven crawlers
76
- # remain welcome.
77
-
78
- User-agent: *
79
- Allow: /
1
+ # WATT3D — AI scraping policy
2
+ #
3
+ # This file enumerates known AI / LLM training and inference crawlers
4
+ # that the WATT3D AI Training Rider does not consent to. See
5
+ # ./ADDITIONAL_TERMS.md for the legal scope and definitions.
6
+ #
7
+ # Honoring robots.txt is voluntary. The rider applies whether or not
8
+ # a bot respects this file. The enumeration is non-exhaustive — bots
9
+ # not listed here are still bound by the rider if they perform
10
+ # Training Use as defined in ADDITIONAL_TERMS.md §1.
11
+
12
+ User-agent: GPTBot
13
+ Disallow: /
14
+
15
+ User-agent: ChatGPT-User
16
+ Disallow: /
17
+
18
+ User-agent: OAI-SearchBot
19
+ Disallow: /
20
+
21
+ User-agent: anthropic-ai
22
+ Disallow: /
23
+
24
+ User-agent: ClaudeBot
25
+ Disallow: /
26
+
27
+ User-agent: Claude-Web
28
+ Disallow: /
29
+
30
+ User-agent: Claude-SearchBot
31
+ Disallow: /
32
+
33
+ User-agent: Google-Extended
34
+ Disallow: /
35
+
36
+ User-agent: Applebot-Extended
37
+ Disallow: /
38
+
39
+ User-agent: Meta-ExternalAgent
40
+ Disallow: /
41
+
42
+ User-agent: Meta-ExternalFetcher
43
+ Disallow: /
44
+
45
+ User-agent: FacebookBot
46
+ Disallow: /
47
+
48
+ User-agent: CCBot
49
+ Disallow: /
50
+
51
+ User-agent: PerplexityBot
52
+ Disallow: /
53
+
54
+ User-agent: Perplexity-User
55
+ Disallow: /
56
+
57
+ User-agent: cohere-ai
58
+ Disallow: /
59
+
60
+ User-agent: Bytespider
61
+ Disallow: /
62
+
63
+ User-agent: Amazonbot
64
+ Disallow: /
65
+
66
+ User-agent: Omgilibot
67
+ Disallow: /
68
+
69
+ User-agent: AI2Bot
70
+ Disallow: /
71
+
72
+ User-agent: Diffbot
73
+ Disallow: /
74
+
75
+ # Everyone else: full access. Search engines and human-driven crawlers
76
+ # remain welcome.
77
+
78
+ User-agent: *
79
+ Allow: /
package/src/clean.js CHANGED
@@ -103,7 +103,7 @@ const stripEmphasis = (md) => replaceOutsideCode(
103
103
  /(?<![\w*])([*_])(?!\s)(.+?)(?<!\s)\1(?![\w*])/,
104
104
  (all, mark, text) => text,
105
105
  );
106
- const stripImages = (md) => replaceOutsideCode(md, /!\[([^\]]*)\]\([^)]*\)/, (all, alt) => alt);
106
+ const stripImages = (md) => replaceOutsideCode(md, INLINE_LINK, (all, bang, text) => text);
107
107
 
108
108
  /**
109
109
  * Output modes. "no_links" keeps link text. "text_only" also drops headings,