webforai 1.5.1 → 1.6.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.
Files changed (73) hide show
  1. package/dist/bin.js +277 -334
  2. package/dist/cjs/cli/bin.js +3 -4
  3. package/dist/cjs/cli/commands/{handlers → webforai}/index.js +33 -31
  4. package/dist/cjs/cli/commands/webforai/loadHtml.js +93 -0
  5. package/dist/cjs/cli/utils.js +18 -51
  6. package/dist/cjs/extract-hast/index.js +4 -3
  7. package/dist/cjs/extract-hast/readability.js +28 -61
  8. package/dist/cjs/extract-hast/strongly.js +2 -1
  9. package/dist/cjs/extract-hast/utils.js +0 -15
  10. package/dist/cjs/html-to-mdast.js +6 -6
  11. package/dist/cjs/link-replacer.js +10 -5
  12. package/dist/cjs/loaders/playwright.js +36 -7
  13. package/dist/cjs/mdast-handlers/custom-br-handler.js +31 -0
  14. package/dist/cjs/mdast-to-markdown.js +1 -1
  15. package/dist/cjs/utils/hast-utils.js +71 -0
  16. package/dist/cli/bin.js +3 -4
  17. package/dist/cli/commands/webforai/index.js +53 -0
  18. package/dist/cli/commands/webforai/loadHtml.js +60 -0
  19. package/dist/cli/utils.js +18 -51
  20. package/dist/extract-hast/index.js +4 -3
  21. package/dist/extract-hast/readability.js +29 -62
  22. package/dist/extract-hast/strongly.js +2 -1
  23. package/dist/extract-hast/utils.js +0 -14
  24. package/dist/html-to-mdast.js +6 -6
  25. package/dist/link-replacer.js +10 -5
  26. package/dist/loaders/playwright.js +36 -7
  27. package/dist/mdast-handlers/custom-br-handler.js +8 -0
  28. package/dist/mdast-to-markdown.js +1 -1
  29. package/dist/types/cli/bin.js.map +1 -1
  30. package/dist/types/cli/commands/webforai/index.d.ts +6 -0
  31. package/dist/types/cli/commands/webforai/index.js.map +1 -0
  32. package/dist/types/cli/commands/webforai/loadHtml.d.ts +3 -0
  33. package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -0
  34. package/dist/types/cli/utils.d.ts +27 -0
  35. package/dist/types/cli/utils.js.map +1 -1
  36. package/dist/types/extract-hast/index.d.ts +9 -4
  37. package/dist/types/extract-hast/index.js.map +1 -1
  38. package/dist/types/extract-hast/readability.d.ts +2 -1
  39. package/dist/types/extract-hast/readability.js.map +1 -1
  40. package/dist/types/extract-hast/strongly.d.ts +2 -1
  41. package/dist/types/extract-hast/strongly.js.map +1 -1
  42. package/dist/types/extract-hast/utils.d.ts +0 -1
  43. package/dist/types/extract-hast/utils.js.map +1 -1
  44. package/dist/types/html-to-mdast.d.ts +4 -0
  45. package/dist/types/html-to-mdast.js.map +1 -1
  46. package/dist/types/link-replacer.d.ts +1 -1
  47. package/dist/types/link-replacer.js.map +1 -1
  48. package/dist/types/loaders/playwright.d.ts +8 -2
  49. package/dist/types/loaders/playwright.js.map +1 -1
  50. package/dist/types/mdast-handlers/custom-br-handler.d.ts +2 -0
  51. package/dist/types/mdast-handlers/custom-br-handler.js.map +1 -0
  52. package/dist/types/mdast-to-markdown.js.map +1 -1
  53. package/dist/types/utils/hast-utils.d.ts +4 -0
  54. package/dist/types/utils/hast-utils.js.map +1 -0
  55. package/dist/utils/hast-utils.js +46 -0
  56. package/package.json +21 -10
  57. package/dist/cjs/cli/commands/handlers/loadHtml.js +0 -127
  58. package/dist/cjs/html-to-markdown.test.js +0 -118
  59. package/dist/cjs/link-replacer.test.js +0 -47
  60. package/dist/cjs/loaders/fetch.test.js +0 -9
  61. package/dist/cjs/loaders/playwright.test.js +0 -17
  62. package/dist/cjs/loaders/puppeteer.test.js +0 -17
  63. package/dist/cli/commands/handlers/index.js +0 -51
  64. package/dist/cli/commands/handlers/loadHtml.js +0 -94
  65. package/dist/html-to-markdown.test.js +0 -118
  66. package/dist/link-replacer.test.js +0 -47
  67. package/dist/loaders/fetch.test.js +0 -9
  68. package/dist/loaders/playwright.test.js +0 -17
  69. package/dist/loaders/puppeteer.test.js +0 -17
  70. package/dist/types/cli/commands/handlers/index.d.ts +0 -13
  71. package/dist/types/cli/commands/handlers/index.js.map +0 -1
  72. package/dist/types/cli/commands/handlers/loadHtml.d.ts +0 -2
  73. package/dist/types/cli/commands/handlers/loadHtml.js.map +0 -1
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var custom_br_handler_exports = {};
20
+ __export(custom_br_handler_exports, {
21
+ customBrHandler: () => customBrHandler
22
+ });
23
+ module.exports = __toCommonJS(custom_br_handler_exports);
24
+ var import_hast_util_to_mdast = require("hast-util-to-mdast");
25
+ const customBrHandler = (state, node) => {
26
+ return import_hast_util_to_mdast.defaultHandlers.br(state, node);
27
+ };
28
+ // Annotate the CommonJS export names for ESM import in node:
29
+ 0 && (module.exports = {
30
+ customBrHandler
31
+ });
@@ -33,7 +33,7 @@ const DEFAULT_MDAST_TO_MARKDOWN_OPTIONS = {
33
33
  };
34
34
  const mdastToMarkdown = (mdast, options) => {
35
35
  const { baseUrl, ...toMarkdownOptions } = { ...DEFAULT_MDAST_TO_MARKDOWN_OPTIONS, ...options };
36
- let markdown = (0, import_mdast_util_to_markdown.toMarkdown)((0, import_mdast_utils.warpRoot)(mdast), toMarkdownOptions);
36
+ let markdown = (0, import_mdast_util_to_markdown.toMarkdown)((0, import_mdast_utils.warpRoot)(mdast), toMarkdownOptions).replace(/\*\*\*\*/g, "");
37
37
  if (baseUrl) {
38
38
  markdown = (0, import_link_replacer.linkReplacer)(markdown, baseUrl);
39
39
  }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var hast_utils_exports = {};
20
+ __export(hast_utils_exports, {
21
+ getLangFromHast: () => getLangFromHast,
22
+ getLangFromStr: () => getLangFromStr,
23
+ getUrlFromHast: () => getUrlFromHast
24
+ });
25
+ module.exports = __toCommonJS(hast_utils_exports);
26
+ var import_hast_util_select = require("hast-util-select");
27
+ const getLangFromHast = (node) => {
28
+ const html = (0, import_hast_util_select.select)("html", node);
29
+ if (html && typeof html.properties.lang === "string") {
30
+ return html.properties.lang;
31
+ }
32
+ if (node.type !== "element") {
33
+ return;
34
+ }
35
+ const element = node;
36
+ if (element.tagName !== "html") {
37
+ return;
38
+ }
39
+ const langAttr = element.properties.lang || element.properties["xml:lang"];
40
+ if (langAttr) {
41
+ return langAttr;
42
+ }
43
+ return void 0;
44
+ };
45
+ const getLangFromStr = (str) => {
46
+ const match = str.match(/lang=["']([^"']+)["']/);
47
+ if (match) {
48
+ return match[1];
49
+ }
50
+ return void 0;
51
+ };
52
+ const getUrlFromHast = (node) => {
53
+ if (node.type !== "element") {
54
+ return void 0;
55
+ }
56
+ const metaTagAttributes = ["og:url", "twitter:url"];
57
+ const metaTags = (0, import_hast_util_select.selectAll)("meta", node);
58
+ for (const meta of metaTags) {
59
+ const property = meta.properties.property || meta.properties.name;
60
+ if (typeof property === "string" && metaTagAttributes.includes(property)) {
61
+ return typeof meta.properties.content === "string" ? meta.properties.content : void 0;
62
+ }
63
+ }
64
+ return void 0;
65
+ };
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ getLangFromHast,
69
+ getLangFromStr,
70
+ getUrlFromHast
71
+ });
package/dist/cli/bin.js CHANGED
@@ -3,10 +3,9 @@ import { intro } from "@clack/prompts";
3
3
  import { program } from "commander";
4
4
  import pc from "picocolors";
5
5
  import packageInfo from "../../package.json";
6
- import { webforaiCommand } from "./commands/handlers/index.js";
6
+ import { webforaiCommand } from "./commands/webforai/index.js";
7
7
  import { LOADERS, MODES } from "./constants.js";
8
- program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(packageInfo.version, "-v, --version", "output the current version").argument("[source]", "URL or path to process").argument("[outputPath]", "Path to output file or directory").option("--mode <mode>", `Processing mode (${MODES.join(", ")})`).option("--loader <loader>", `Loader to use (${LOADERS.join(", ")})`).option("--baseUrl <baseUrl>", "Base URL for relative links").option("-o, --stdout", "output to stdout").option("-d, --debug", "output extra debugging information").on("option:debug", () => {
9
- process.env.DEBUG = "true";
10
- }).action(webforaiCommand);
8
+ program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(packageInfo.version, "-v, --version", "output the current version");
9
+ program.argument("[source]", "URL or path to process").option("-o, --output <output>", "Path to output file or directory").option("-m, --mode <mode>", `Processing mode (${MODES.join(", ")})`).option("-l, --loader <loader>", `Loader to use (${LOADERS.join(", ")})`).option("-d, --debug", "output extra debugging information").action(webforaiCommand);
11
10
  intro(pc.inverse(`webforai CLI version ${packageInfo.version}`));
12
11
  program.parse();
@@ -0,0 +1,53 @@
1
+ // src/cli/commands/webforai/index.ts
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { intro, outro, spinner } from "@clack/prompts";
5
+ import pc from "picocolors";
6
+ import packageInfo from "../../../../package.json";
7
+ import { htmlToMarkdown } from "../../../html-to-markdown.js";
8
+ import { inputOutputPath } from "../../helpers/inputOutputPath.js";
9
+ import { inputSourcePath } from "../../helpers/inputSourcePath.js";
10
+ import { selectExtractMode } from "../../helpers/selectExtractMode.js";
11
+ import { selectLoader } from "../../helpers/selectLoader.js";
12
+ import { isUrl } from "../../utils.js";
13
+ import { loadHtml } from "./loadHtml.js";
14
+ var aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
15
+ var readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
16
+ var webforaiCommand = async (initialPath, options) => {
17
+ intro(pc.bold(pc.green(`webforai CLI version ${packageInfo.version}`)));
18
+ const sourcePath = initialPath ?? await inputSourcePath();
19
+ options.debug && console.debug(`sourcePath: ${sourcePath}`);
20
+ const loader = isUrl(sourcePath) ? options.loader ?? await selectLoader() : "local";
21
+ options.debug && console.debug(`loader: ${loader}`);
22
+ const outputPath = options.output ?? await inputOutputPath(sourcePath);
23
+ options.debug && console.debug(`outputPath: ${outputPath}`);
24
+ const mode = options.mode ?? await selectExtractMode();
25
+ options.debug && console.debug(`mode: ${mode}`);
26
+ let html;
27
+ const s = spinner();
28
+ try {
29
+ s.start("Loading content...");
30
+ html = await loadHtml(sourcePath, loader, { debug: options.debug });
31
+ s.stop(pc.green("Content loaded!"));
32
+ } catch (error) {
33
+ s.stop(pc.red("Content loading failed!"));
34
+ console.error(error);
35
+ process.exit(1);
36
+ }
37
+ options.debug && console.debug(`html: ${html}`);
38
+ const markdown = htmlToMarkdown(html, {
39
+ baseUrl: isUrl(sourcePath) ? sourcePath : void 0,
40
+ ...mode === "ai" ? aiModeOptions : readabilityModeOptions
41
+ });
42
+ options.debug && console.debug(`markdown: ${markdown}`);
43
+ const directory = path.dirname(outputPath);
44
+ const isDirectoryExists = await fs.stat(directory).then((stat) => stat.isDirectory());
45
+ if (!isDirectoryExists) {
46
+ await fs.mkdir(directory, { recursive: true });
47
+ }
48
+ await fs.writeFile(outputPath, markdown);
49
+ outro(pc.green(`${pc.bold("Done!")} Markdown saved to ${outputPath}`));
50
+ };
51
+ export {
52
+ webforaiCommand
53
+ };
@@ -0,0 +1,60 @@
1
+ // src/cli/commands/webforai/loadHtml.ts
2
+ import fs from "node:fs/promises";
3
+ import pc from "picocolors";
4
+ import { chromium } from "playwright-core";
5
+ import playwrightPackageJson from "playwright-core/package.json";
6
+ import { loadHtml as loadHtmlByFetch } from "../../../loaders/fetch.js";
7
+ import { loadHtml as loadHtmlByPlaywright } from "../../../loaders/playwright.js";
8
+ var checkPlaywrightAvailable = async () => {
9
+ const path = chromium.executablePath();
10
+ const isAvailable = await fs.access(path).catch(() => false);
11
+ return !!isAvailable;
12
+ };
13
+ var loadHtml = async (sourcePath, loader, options) => {
14
+ if (loader === "local") {
15
+ options.debug && console.debug(`Loading HTML from local file: ${sourcePath}`);
16
+ const content = await fs.readFile(sourcePath, "utf-8");
17
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
18
+ return content;
19
+ }
20
+ if (loader === "fetch") {
21
+ options.debug && console.debug(`Loading HTML from URL: ${sourcePath}`);
22
+ const content = await loadHtmlByFetch(sourcePath);
23
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
24
+ return content;
25
+ }
26
+ if (loader === "playwright") {
27
+ options.debug && console.debug(`Loading HTML from playwright: ${sourcePath}`);
28
+ const isPlaywrightAvailable = await checkPlaywrightAvailable();
29
+ options.debug && console.debug(`Playwright available: ${isPlaywrightAvailable}`);
30
+ if (!isPlaywrightAvailable) {
31
+ const message = [
32
+ "To use playwright loader, you need to install playwright.",
33
+ "You can install it by running:",
34
+ "",
35
+ ` ${pc.bold(`npx playwright@${playwrightPackageJson.version} install chromium`)} `,
36
+ "",
37
+ "Hint1: If you get warning like this:",
38
+ "WARNING: It looks like you are running 'npx playwright install' without first installing your project's dependencies. ",
39
+ `${pc.bold("Ignore this warning.")}`,
40
+ "",
41
+ "Hint2: If you get message like this:",
42
+ "Host system is missing dependencies to run browsers.",
43
+ "You should install dependencies by running:",
44
+ "",
45
+ ` ${pc.bold(`sudo npx playwright@${playwrightPackageJson.version} install-deps`)} `,
46
+ "",
47
+ pc.gray("Note: Good luck with that.")
48
+ ];
49
+ console.info(message.join("\n"));
50
+ throw new Error("Playwright is not available");
51
+ }
52
+ const content = await loadHtmlByPlaywright(sourcePath);
53
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
54
+ return content;
55
+ }
56
+ throw new Error(`Unsupported loader: ${loader}`);
57
+ };
58
+ export {
59
+ loadHtml
60
+ };
package/dist/cli/utils.js CHANGED
@@ -10,71 +10,38 @@ var isUrl = (maybeUrl) => {
10
10
  }
11
11
  };
12
12
  function changeFileExtension(filePath, newExtension) {
13
- const parsedPath = filePath.split("/");
14
- const fileName = parsedPath[parsedPath.length - 1];
15
- const formattedNewExtension = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
16
- if (fileName.startsWith(".")) {
17
- const parts = fileName.split(".");
18
- if (parts.length === 2) {
19
- return parsedPath.slice(0, -1).concat(`${fileName}${formattedNewExtension}`).join("/");
20
- }
21
- parts[parts.length - 1] = newExtension.replace(/^\./, "");
22
- return parsedPath.slice(0, -1).concat(parts.join(".")).join("/");
23
- }
24
- const lastDotIndex = fileName.lastIndexOf(".");
25
- const baseName = lastDotIndex !== -1 ? fileName.slice(0, lastDotIndex) : fileName;
26
- const newFileName = `${baseName}${formattedNewExtension}`;
27
- parsedPath[parsedPath.length - 1] = newFileName;
28
- return parsedPath.join("/");
13
+ const parsed = path.parse(filePath);
14
+ const ext = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
15
+ return path.format({ ...parsed, ext });
29
16
  }
30
17
  function urlToFilename(url) {
31
18
  try {
32
19
  const urlObj = new URL(url);
33
- const domainParts = urlObj.hostname.split(".").reverse().reduce((acc, part, index) => {
34
- if (index === 0) {
35
- return acc;
36
- }
37
- if (acc.length >= 2) {
38
- return acc;
39
- }
40
- if (part === "www") {
41
- return acc;
42
- }
43
- return [part, ...acc];
44
- }, []);
45
- const domainString = domainParts.reverse().join("-");
20
+ const hostname = urlObj.hostname.startsWith("www.") ? urlObj.hostname.slice(4) : urlObj.hostname;
21
+ const domainParts = hostname.split(".").slice(-2).join("-");
46
22
  const pathParts = urlObj.pathname.split("/").filter(Boolean);
47
- const relevantPathParts = pathParts.slice(-2);
48
- const pathString = relevantPathParts.map((part) => decodeURIComponent(part)).join("-");
49
- let filename = [domainString, pathString].filter(Boolean).join("-");
23
+ const relevantPathParts = pathParts.slice(-2).map(decodeURIComponent).join("-");
24
+ let filename = [domainParts, relevantPathParts].filter(Boolean).join("-");
50
25
  filename = filename.toLowerCase().replace(/[<>:"/\\|?*\x00-\x1F]/g, "").replace(/[\s.]+/g, "-").replace(/^-+|-+$/g, "");
51
26
  return filename || "output";
52
27
  } catch {
53
28
  return "output";
54
29
  }
55
30
  }
56
- var sourcePathToOutputPath = (sourcePath) => {
57
- return isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
58
- };
31
+ var sourcePathToOutputPath = (sourcePath) => isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
59
32
  function getNextAvailableFilePath(filePath) {
60
- const parsedPath = path.parse(filePath);
61
- const directory = parsedPath.dir;
62
- const fullName = parsedPath.base;
63
- const [firstPart, ...restParts] = fullName.split(".");
64
- const restName = restParts.length > 0 ? `.${restParts.join(".")}` : "";
65
- const baseName = firstPart.replace(/_\d+$/, "");
66
- let counter = 1;
67
- let nextFilePath = filePath;
68
- while (fs.existsSync(nextFilePath)) {
69
- const match = firstPart.match(/_(\d+)$/);
70
- if (match) {
71
- counter = Number.parseInt(match[1], 10) + 1;
72
- }
73
- const newName = `${baseName}_${counter}${restName}`;
74
- nextFilePath = path.join(directory, newName);
33
+ const parsed = path.parse(filePath);
34
+ const dir = parsed.dir;
35
+ const ext = parsed.ext;
36
+ const baseName = parsed.name.replace(/_\d+$/, "");
37
+ const match = parsed.name.match(/_(\d+)$/);
38
+ let counter = match ? Number(match[1]) + 1 : 1;
39
+ let newPath = path.join(dir, `${baseName}_${counter}${ext}`);
40
+ while (fs.existsSync(newPath)) {
75
41
  counter++;
42
+ newPath = path.join(dir, `${baseName}_${counter}${ext}`);
76
43
  }
77
- return nextFilePath;
44
+ return newPath;
78
45
  }
79
46
  export {
80
47
  changeFileExtension,
@@ -6,17 +6,18 @@ var PRESET_EXTRACT_HAST = {
6
6
  readability: readabilityExtractHast
7
7
  };
8
8
  var DEFAULT_EXTRACT_HAST = ["readability"];
9
- var extractHast = (hast, extractors = DEFAULT_EXTRACT_HAST) => {
9
+ var extractHast = (params, extractors = DEFAULT_EXTRACT_HAST) => {
10
+ const { hast, lang } = params;
10
11
  const _extractors = Array.isArray(extractors) ? extractors : [extractors];
11
12
  const extracted = _extractors.reduce((acc, extractor) => {
12
13
  if (extractor === false) {
13
14
  return acc;
14
15
  }
15
16
  if (typeof extractor === "string" && extractor in PRESET_EXTRACT_HAST) {
16
- return PRESET_EXTRACT_HAST[extractor](acc);
17
+ return PRESET_EXTRACT_HAST[extractor]({ hast: acc, lang });
17
18
  }
18
19
  if (typeof extractor === "function") {
19
- return extractor(acc);
20
+ return extractor({ hast: acc, lang });
20
21
  }
21
22
  throw new Error(`Invalid extractor: ${extractor}`);
22
23
  }, hast) || hast;
@@ -2,41 +2,30 @@
2
2
  import { select, selectAll } from "hast-util-select";
3
3
  import { toString as hastToString } from "hast-util-to-string";
4
4
  import { filter } from "unist-util-filter";
5
- import { classnames, hasAncestors, isStrInclude, matchString } from "./utils.js";
5
+ import { classnames, isStrInclude, matchString } from "./utils.js";
6
6
  var UNLIKELY_ROLES = ["menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog"];
7
7
  var REGEXPS = {
8
8
  hidden: /hidden|invisible|fallback-image/i,
9
9
  byline: /byline|author|dateline|writtenby|p-author/i,
10
10
  specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
11
- unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore/i,
11
+ unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|tooltip|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore|avatar/i,
12
12
  okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
13
13
  };
14
- var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry", "main"];
15
- var PARAGRAPH_TAGS = ["p", "div", "section", "article", "main", "ul", "ol", "li"];
14
+ var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
15
+ var PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
16
16
  var BASE_MINIMAL_LENGTH = {
17
17
  ja: 200,
18
18
  en: 500
19
19
  };
20
- var tryGetLang = (node) => {
21
- if (node.type !== "element") {
22
- return;
23
- }
24
- const element = node;
25
- if (element.tagName !== "html") {
26
- return;
27
- }
28
- const langAttr = element.properties.lang || element.properties["xml:lang"];
29
- if (langAttr) {
30
- return langAttr;
31
- }
20
+ var metadataFilter = (node) => {
21
+ return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
32
22
  };
33
- var metadataFilter = (node) => !["comment", "doctype"].includes(node.type);
34
23
  var universalElementFilter = (node) => {
35
24
  if (node.type !== "element") {
36
25
  return true;
37
26
  }
38
27
  const element = node;
39
- if (["script", "aside"].includes(element.tagName)) {
28
+ if (["aside", "nav"].includes(element.tagName)) {
40
29
  return false;
41
30
  }
42
31
  if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
@@ -67,10 +56,7 @@ var unlikelyElementFilter = (node) => {
67
56
  return true;
68
57
  }
69
58
  const element = node;
70
- if (["body", "article", "main", "section", "a"].includes(element.tagName)) {
71
- return true;
72
- }
73
- if (hasAncestors(element, ["table", "code"], 3)) {
59
+ if (["body", "article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"].includes(element.tagName)) {
74
60
  return true;
75
61
  }
76
62
  const match = matchString(element);
@@ -82,55 +68,36 @@ var unlikelyElementFilter = (node) => {
82
68
  }
83
69
  return true;
84
70
  };
85
- var isImageLink = (element) => {
86
- const a = select("a", element);
87
- const img = select("img", a);
88
- if (!(a && img)) {
89
- return false;
90
- }
91
- const imgFilename = img.properties.src?.toString().split("/").pop();
92
- const hrefFilename = a.properties.href?.toString().split("/").pop();
93
- return imgFilename === hrefFilename && imgFilename;
94
- };
95
- var removeEmptyFilter = (node) => {
71
+ var removeEmptyFilter = (node, _lang) => {
96
72
  if (node.type !== "element") {
97
73
  return true;
98
74
  }
99
75
  const element = node;
100
- if (!PARAGRAPH_TAGS.includes(element.tagName)) {
76
+ if (PARAGRAPH_TAGS.includes(element.tagName)) {
101
77
  return true;
102
78
  }
103
- if (isImageLink(element)) {
104
- return true;
105
- }
106
- const text = hastToString(element);
107
- if (text.length < 10) {
79
+ if (element.tagName === "img" && !element.properties.src) {
108
80
  return false;
109
81
  }
110
82
  return true;
111
83
  };
112
- var readabilityExtractHast = (hast) => {
113
- const lang = String(select("html", hast)?.properties.lang || tryGetLang(hast) || "en");
84
+ var readabilityExtractHast = (params) => {
85
+ const { hast, lang = "en" } = params;
114
86
  const body = select("body", hast) ?? hast;
115
- const bodyText = hastToString(body);
116
- const baseFilterd = filter(body, (node) => {
117
- if (!metadataFilter(node)) {
118
- return false;
119
- }
120
- if (!universalElementFilter(node)) {
121
- return false;
122
- }
123
- return true;
124
- });
125
- if (!baseFilterd) {
126
- return { type: "root", children: [] };
127
- }
128
- const baseFilterdText = hastToString(baseFilterd);
129
- let [baseTree, baseText] = baseFilterdText.length > bodyText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [body, bodyText];
87
+ const metadataFilteredHast = filter(body, (node) => metadataFilter(node));
88
+ const metadataFilteredHastText = metadataFilteredHast && hastToString(metadataFilteredHast);
89
+ if (!(metadataFilteredHast && metadataFilteredHastText)) {
90
+ return body;
91
+ }
92
+ const baseFilterd = filter(metadataFilteredHast, (node) => universalElementFilter(node));
93
+ const baseFilterdText = baseFilterd ? hastToString(baseFilterd) : "";
94
+ const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
130
95
  let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
131
96
  if (baseText.length < minimalLength) {
132
97
  minimalLength = Math.max(0, baseText.length - 200);
133
98
  }
99
+ let extractedTree = baseTree;
100
+ let extractedText = baseText;
134
101
  for (const selector of BODY_SELECTORS) {
135
102
  const content = { type: "root", children: selectAll(selector, baseFilterd) };
136
103
  const contentText = hastToString(content);
@@ -139,18 +106,18 @@ var readabilityExtractHast = (hast) => {
139
106
  }
140
107
  const links = selectAll("a", content);
141
108
  const linkText = links.map((link) => hastToString(link)).join("");
142
- const linkDensity = linkText.length / bodyText.length;
109
+ const linkDensity = linkText.length / contentText.length;
143
110
  if (linkDensity > 0.4) {
144
111
  continue;
145
112
  }
146
113
  if (contentText.length > minimalLength) {
147
- baseTree = content;
148
- baseText = contentText;
114
+ extractedTree = content;
115
+ extractedText = contentText;
149
116
  break;
150
117
  }
151
118
  }
152
- const finalFilteredTree = filter(baseTree, (node) => {
153
- if (!removeEmptyFilter(node)) {
119
+ const finalFilteredTree = filter(extractedTree, (node) => {
120
+ if (!removeEmptyFilter(node, lang)) {
154
121
  return false;
155
122
  }
156
123
  if (!unlikelyElementFilter(node)) {
@@ -158,7 +125,7 @@ var readabilityExtractHast = (hast) => {
158
125
  }
159
126
  return true;
160
127
  });
161
- const finalTree = hastToString(finalFilteredTree).length > baseText.length / 3 ? finalFilteredTree : baseTree;
128
+ const finalTree = hastToString(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
162
129
  return finalTree;
163
130
  };
164
131
  export {
@@ -2,7 +2,8 @@
2
2
  import { select } from "hast-util-select";
3
3
  import { filter } from "unist-util-filter";
4
4
  var REMOVE_REGEX = /fallback|icon|header|nav|speechify-ignore|assist|-ad-|banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|assist|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i;
5
- var stronglyExtractHast = (hast) => {
5
+ var stronglyExtractHast = (params) => {
6
+ const { hast } = params;
6
7
  const selectors = ["article", "#article", "#content", "main", "div.container"];
7
8
  let selectedElement = hast;
8
9
  for (const selector of selectors) {
@@ -12,22 +12,8 @@ var isStrInclude = (value, match) => {
12
12
  }
13
13
  return false;
14
14
  };
15
- var hasAncestors = (element, tagNames, depth = 3) => {
16
- let parent = element.parent;
17
- for (let i = 0; i < depth; i++) {
18
- if (!parent || parent.type !== "element") {
19
- return false;
20
- }
21
- if (tagNames.includes(parent.tagName)) {
22
- return true;
23
- }
24
- parent = parent.parent;
25
- }
26
- return false;
27
- };
28
15
  export {
29
16
  classnames,
30
- hasAncestors,
31
17
  isStrInclude,
32
18
  matchString
33
19
  };
@@ -8,12 +8,13 @@ import { customCodeHandler } from "./mdast-handlers/custom-code-handler.js";
8
8
  import { customDivHandler } from "./mdast-handlers/custom-div-handler.js";
9
9
  import { customImgHandler } from "./mdast-handlers/custom-img-handler.js";
10
10
  import { customTableHandler } from "./mdast-handlers/custom-table-handler.js";
11
- import { emptyHandler } from "./mdast-handlers/empty-handler.js";
12
11
  import { mathHandler } from "./mdast-handlers/math-handler.js";
12
+ import { getLangFromHast, getLangFromStr, getUrlFromHast } from "./utils/hast-utils.js";
13
13
  var htmlToMdast = (htmlOrHast, options) => {
14
- const { extractors } = options || {};
15
- const hast = typeof htmlOrHast === "string" ? fromHtml(htmlOrHast, { fragment: true }) : htmlOrHast;
16
- const extractedHast = extractHast(hast, extractors);
14
+ const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
15
+ const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || getLangFromStr(htmlOrHast), fromHtml(htmlOrHast, { fragment: true })] : [defaultLang || getLangFromHast(htmlOrHast), htmlOrHast];
16
+ const url = defaultUrl || getUrlFromHast(hast);
17
+ const extractedHast = extractHast({ hast, lang, url }, extractors);
17
18
  const mdast = toMdast(extractedHast, {
18
19
  handlers: {
19
20
  math: mathHandler,
@@ -21,8 +22,7 @@ var htmlToMdast = (htmlOrHast, options) => {
21
22
  pre: customCodeHandler,
22
23
  a: customAHandler({ asText: options?.linkAsText }),
23
24
  img: customImgHandler({ hideImage: options?.hideImage }),
24
- table: customTableHandler({ asText: options?.tableAsText }),
25
- br: emptyHandler
25
+ table: customTableHandler({ asText: options?.tableAsText })
26
26
  }
27
27
  });
28
28
  const extractedMdast = extractMdast(mdast);
@@ -1,11 +1,16 @@
1
1
  // src/link-replacer.ts
2
- var linkReplacer = (markdown, link) => {
3
- const regex = /(!?\[.*?\]\()(.+?)(\))/g;
4
- return markdown.replace(regex, (match, pre, thisUrl, post) => {
5
- if (thisUrl.startsWith("http") || thisUrl.startsWith("#")) {
2
+ var linkReplacer = (markdown, base) => {
3
+ const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
4
+ return markdown.replace(regex, (match, pre, url, post) => {
5
+ if (/^(https?:|#)/.test(url)) {
6
+ return match;
7
+ }
8
+ try {
9
+ const absoluteUrl = new URL(url, base).href;
10
+ return `${pre}${absoluteUrl}${post}`;
11
+ } catch {
6
12
  return match;
7
13
  }
8
- return `${pre}${new URL(thisUrl, link).href}${post}`;
9
14
  });
10
15
  };
11
16
  export {