@j0hanz/fetch-url-mcp 1.10.2 → 1.10.4

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 (63) hide show
  1. package/dist/http/native.d.ts.map +1 -1
  2. package/dist/http/native.js +20 -26
  3. package/dist/http/session-teardown.d.ts +17 -0
  4. package/dist/http/session-teardown.d.ts.map +1 -0
  5. package/dist/http/session-teardown.js +30 -0
  6. package/dist/lib/code-lang.d.ts.map +1 -1
  7. package/dist/lib/code-lang.js +2 -8
  8. package/dist/lib/core.d.ts.map +1 -1
  9. package/dist/lib/core.js +2 -0
  10. package/dist/lib/dom-prep.d.ts +2 -0
  11. package/dist/lib/dom-prep.d.ts.map +1 -1
  12. package/dist/lib/dom-prep.js +150 -139
  13. package/dist/lib/md-cleanup.d.ts.map +1 -1
  14. package/dist/lib/md-cleanup.js +53 -20
  15. package/dist/lib/progress.d.ts.map +1 -1
  16. package/dist/lib/progress.js +59 -32
  17. package/dist/lib/sdk-interop.d.ts +8 -0
  18. package/dist/lib/sdk-interop.d.ts.map +1 -0
  19. package/dist/lib/sdk-interop.js +73 -0
  20. package/dist/lib/task-handlers.d.ts +0 -2
  21. package/dist/lib/task-handlers.d.ts.map +1 -1
  22. package/dist/lib/task-handlers.js +9 -94
  23. package/dist/resources/index.js +1 -1
  24. package/dist/server.d.ts.map +1 -1
  25. package/dist/server.js +10 -3
  26. package/dist/tasks/call-contract.d.ts +25 -0
  27. package/dist/tasks/call-contract.d.ts.map +1 -0
  28. package/dist/tasks/call-contract.js +58 -0
  29. package/dist/tasks/cursor-codec.d.ts +5 -0
  30. package/dist/tasks/cursor-codec.d.ts.map +1 -0
  31. package/dist/tasks/cursor-codec.js +41 -0
  32. package/dist/tasks/execution.d.ts +1 -20
  33. package/dist/tasks/execution.d.ts.map +1 -1
  34. package/dist/tasks/execution.js +7 -32
  35. package/dist/tasks/manager.d.ts +1 -5
  36. package/dist/tasks/manager.d.ts.map +1 -1
  37. package/dist/tasks/manager.js +16 -144
  38. package/dist/tasks/owner.d.ts +12 -2
  39. package/dist/tasks/owner.d.ts.map +1 -1
  40. package/dist/tasks/owner.js +52 -3
  41. package/dist/tasks/tool-registry.d.ts +1 -0
  42. package/dist/tasks/tool-registry.d.ts.map +1 -1
  43. package/dist/tasks/waiters.d.ts +27 -0
  44. package/dist/tasks/waiters.d.ts.map +1 -0
  45. package/dist/tasks/waiters.js +113 -0
  46. package/dist/tools/fetch-url-progress.d.ts +17 -0
  47. package/dist/tools/fetch-url-progress.d.ts.map +1 -0
  48. package/dist/tools/fetch-url-progress.js +78 -0
  49. package/dist/tools/fetch-url.d.ts +5 -9
  50. package/dist/tools/fetch-url.d.ts.map +1 -1
  51. package/dist/tools/fetch-url.js +43 -96
  52. package/dist/transform/next-flight.d.ts +2 -0
  53. package/dist/transform/next-flight.d.ts.map +1 -0
  54. package/dist/transform/next-flight.js +285 -0
  55. package/dist/transform/title-policy.d.ts +12 -0
  56. package/dist/transform/title-policy.d.ts.map +1 -0
  57. package/dist/transform/title-policy.js +85 -0
  58. package/dist/transform/transform.d.ts.map +1 -1
  59. package/dist/transform/transform.js +62 -475
  60. package/package.json +4 -4
  61. package/dist/tools/index.d.ts +0 -3
  62. package/dist/tools/index.d.ts.map +0 -1
  63. package/dist/tools/index.js +0 -4
@@ -1,11 +1,18 @@
1
1
  import { parseHTML } from 'linkedom';
2
2
  import { config, logDebug } from './core.js';
3
+ // ── Thresholds ──────────────────────────────────────────────────────
3
4
  const NOISE_SCAN_LIMIT = 50_000;
4
5
  const MIN_BODY_CONTENT_LENGTH = 100;
5
6
  const DIALOG_MIN_CHARS_FOR_PRESERVATION = 500;
6
7
  const NAV_FOOTER_MIN_CHARS_FOR_PRESERVATION = 500;
7
8
  const ABORT_CHECK_INTERVAL = 500;
8
9
  const NODE_FILTER_SHOW_TEXT = 4;
10
+ const ASIDE_NAV_LINK_DENSITY_THRESHOLD = 0.5;
11
+ const ASIDE_NAV_MIN_LINKS = 10;
12
+ const INLINE_DEMO_INSTRUCTION_MAX_CHARS = 160;
13
+ const REDUNDANT_PREVIEW_SEGMENT_MAX_CHARS = 60;
14
+ const REDUNDANT_PREVIEW_MAX_SEGMENTS = 12;
15
+ // ── Regex patterns ──────────────────────────────────────────────────
9
16
  const HTML_DOCUMENT_MARKERS = /<\s*(?:!doctype|html|head|body)\b/i;
10
17
  const HTML_FRAGMENT_MARKERS = /<\s*(?:article|main|section|div|nav|footer|header|aside|table|ul|ol)\b/i;
11
18
  const NOISE_PATTERNS = [
@@ -19,6 +26,10 @@ const HEADER_NOISE_PATTERN = /\b(site-header|masthead|topbar|navbar|nav(?:bar)?|
19
26
  const FIXED_OR_HIGH_Z_PATTERN = /\b(?:fixed|sticky|z-(?:4\d|50)|isolate)\b/;
20
27
  const HEADING_PERMALINK_TEXT_PATTERN = /^(?:#|¶|§|¤|🔗)$/u;
21
28
  const HEADING_PERMALINK_CLASS_PATTERN = /\b(?:mark|permalink|hash-link|anchor(?:js)?-?link|header-?link|heading-anchor|deep-link)\b/i;
29
+ const HIDDEN_STYLE_REGEX = /\b(?:display\s*:\s*none|visibility\s*:\s*hidden)\b/i;
30
+ const UTM_PARAM_REGEX = /[?&]utm_(?:source|medium|campaign)=/i;
31
+ const NO_MATCH_REGEX = /a^/i;
32
+ // ── URL prefixes to skip during resolution ──────────────────────────
22
33
  const SKIP_URL_PREFIXES = [
23
34
  '#',
24
35
  'javascript:',
@@ -27,6 +38,7 @@ const SKIP_URL_PREFIXES = [
27
38
  'data:',
28
39
  'blob:',
29
40
  ];
41
+ // ── Tag / role sets ─────────────────────────────────────────────────
30
42
  const BASE_STRUCTURAL_TAGS = new Set([
31
43
  'script',
32
44
  'style',
@@ -52,9 +64,6 @@ const NAVIGATION_ROLES = new Set([
52
64
  'alertdialog',
53
65
  'search',
54
66
  ]);
55
- const INLINE_DEMO_INSTRUCTION_MAX_CHARS = 160;
56
- const REDUNDANT_PREVIEW_SEGMENT_MAX_CHARS = 60;
57
- const REDUNDANT_PREVIEW_MAX_SEGMENTS = 12;
58
67
  const INTERACTIVE_CONTENT_ROLES = new Set([
59
68
  'tabpanel',
60
69
  'tab',
@@ -69,6 +78,7 @@ const INTERACTIVE_CONTENT_ROLES = new Set([
69
78
  'tooltip',
70
79
  'alert',
71
80
  ]);
81
+ // ── Promo tokens ────────────────────────────────────────────────────
72
82
  const PROMO_TOKENS_ALWAYS = [
73
83
  'banner',
74
84
  'promo',
@@ -87,20 +97,30 @@ const PROMO_TOKENS_ALWAYS = [
87
97
  'fb-post',
88
98
  'instagram-media',
89
99
  'social-embed',
100
+ 'author-bio',
101
+ 'byline',
102
+ 'sharedaddy',
103
+ 'sharing',
90
104
  ];
91
105
  const PROMO_TOKENS_AGGRESSIVE = ['ad', 'related', 'comment'];
92
106
  const PROMO_TOKENS_BY_CATEGORY = {
93
107
  'cookie-banners': ['cookie', 'consent', 'popup', 'modal', 'overlay', 'toast'],
94
108
  newsletters: ['newsletter', 'subscribe'],
95
- 'social-share': ['share', 'social'],
109
+ 'social-share': ['share', 'social', 'share-button'],
110
+ 'author-blocks': ['author-bio', 'byline', 'author-info', 'writer-profile'],
111
+ 'related-content': [
112
+ 'related-post',
113
+ 'related-article',
114
+ 'more-stories',
115
+ 'recommended-posts',
116
+ ],
96
117
  };
97
- // Noise selector configurations
118
+ // ── Noise selector configurations ───────────────────────────────────
98
119
  const BASE_NOISE_SELECTORS = {
99
120
  navFooter: 'nav,footer,header[class*="site"],header[class*="nav"],header[class*="menu"],[role="banner"],[role="navigation"],[class*="breadcrumb"]',
100
121
  cookieBanners: '[role="dialog"]',
101
122
  hidden: '[style*="display: none"],[style*="display:none"],[style*="visibility: hidden"],[style*="visibility:hidden"],[hidden],[aria-hidden="true"]',
102
123
  };
103
- const NO_MATCH_REGEX = /a^/i;
104
124
  let cachedContext;
105
125
  let lastContextKey;
106
126
  function escapeRegexLiteral(value) {
@@ -111,24 +131,18 @@ function buildTokenRegex(tokens) {
111
131
  return NO_MATCH_REGEX;
112
132
  return new RegExp(`(?:^|[^a-z0-9])(?:${[...tokens].map(escapeRegexLiteral).join('|')})(?:$|[^a-z0-9])`, 'i');
113
133
  }
114
- function addTokens(target, tokens) {
115
- for (const token of tokens)
116
- target.add(token);
117
- }
118
- function getPromoMatchers(currentConfig, flags) {
134
+ function getPromoMatchers(currentConfig, enabledCategories) {
119
135
  const baseTokens = new Set(PROMO_TOKENS_ALWAYS);
120
136
  const aggressiveTokens = new Set();
121
137
  if (currentConfig.aggressiveMode) {
122
- addTokens(aggressiveTokens, PROMO_TOKENS_AGGRESSIVE);
138
+ for (const token of PROMO_TOKENS_AGGRESSIVE)
139
+ aggressiveTokens.add(token);
123
140
  }
124
- if (flags.cookieBanners) {
125
- addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['cookie-banners']);
126
- }
127
- if (flags.newsletters) {
128
- addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['newsletters']);
129
- }
130
- if (flags.socialShare) {
131
- addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['social-share']);
141
+ for (const [category, tokens] of Object.entries(PROMO_TOKENS_BY_CATEGORY)) {
142
+ if (enabledCategories.has(category)) {
143
+ for (const token of tokens)
144
+ baseTokens.add(token);
145
+ }
132
146
  }
133
147
  for (const t of currentConfig.extraTokens) {
134
148
  const n = t.toLowerCase().trim();
@@ -149,7 +163,6 @@ function getContext() {
149
163
  extraSelectors: currentConfig.extraSelectors,
150
164
  aggressiveMode: currentConfig.aggressiveMode,
151
165
  preserveSvgCanvas: currentConfig.preserveSvgCanvas,
152
- weights: currentConfig.weights,
153
166
  });
154
167
  if (cachedContext !== undefined && lastContextKey === contextKey)
155
168
  return cachedContext;
@@ -164,15 +177,13 @@ function getContext() {
164
177
  const flags = {
165
178
  navFooter: isEnabled('nav-footer'),
166
179
  cookieBanners: isEnabled('cookie-banners'),
167
- newsletters: isEnabled('newsletters'),
168
- socialShare: isEnabled('social-share'),
169
180
  };
170
181
  const structuralTags = new Set(BASE_STRUCTURAL_TAGS);
171
182
  if (!currentConfig.preserveSvgCanvas) {
172
183
  structuralTags.add('svg');
173
184
  structuralTags.add('canvas');
174
185
  }
175
- const promoMatchers = getPromoMatchers(currentConfig, flags);
186
+ const promoMatchers = getPromoMatchers(currentConfig, enabled);
176
187
  const extraSelectors = currentConfig.extraSelectors
177
188
  .map((s) => s.trim())
178
189
  .filter((s) => s.length > 0);
@@ -182,7 +193,8 @@ function getContext() {
182
193
  selectors.push(BASE_NOISE_SELECTORS.navFooter);
183
194
  if (flags.cookieBanners)
184
195
  selectors.push(BASE_NOISE_SELECTORS.cookieBanners);
185
- const baseSelector = selectors.join(',');
196
+ const noiseSelector = selectors.join(',');
197
+ const extraSelector = extraSelectors.length > 0 ? extraSelectors.join(',') : null;
186
198
  const candidateSelector = [
187
199
  ...structuralTags,
188
200
  ...ALWAYS_NOISE_TAGS,
@@ -196,11 +208,10 @@ function getContext() {
196
208
  cachedContext = {
197
209
  flags,
198
210
  structuralTags,
199
- weights: currentConfig.weights,
200
211
  promoMatchers,
201
- promoEnabled: flags.cookieBanners || flags.newsletters || flags.socialShare,
202
- extraSelectors,
203
- baseSelector,
212
+ promoEnabled: Object.keys(PROMO_TOKENS_BY_CATEGORY).some((cat) => enabled.has(cat)),
213
+ noiseSelector,
214
+ extraSelector,
204
215
  candidateSelector,
205
216
  };
206
217
  lastContextKey = contextKey;
@@ -231,8 +242,6 @@ function isWithinPrimaryContent(element) {
231
242
  }
232
243
  return false;
233
244
  }
234
- const ASIDE_NAV_LINK_DENSITY_THRESHOLD = 0.5;
235
- const ASIDE_NAV_MIN_LINKS = 10;
236
245
  function isNavigationAside(element) {
237
246
  if (element.querySelector('nav'))
238
247
  return true;
@@ -278,69 +287,57 @@ function removeNodes(nodes) {
278
287
  }
279
288
  }
280
289
  }
281
- const HIDDEN_STYLE_REGEX = /\b(?:display\s*:\s*none|visibility\s*:\s*hidden)\b/i;
282
- function calculateNavFooterScore(tagName, className, id, role, weights) {
283
- let score = 0;
284
- if (ALWAYS_NOISE_TAGS.has(tagName))
285
- score += weights.structural;
286
- if (tagName === 'header') {
287
- if ((role && NAVIGATION_ROLES.has(role)) ||
288
- HEADER_NOISE_PATTERN.test(`${className} ${id}`)) {
289
- score += weights.structural;
290
- }
291
- }
292
- if (tagName === 'aside') {
293
- score += weights.structural;
294
- }
295
- if (role && NAVIGATION_ROLES.has(role)) {
296
- if (tagName !== 'aside' || role !== 'complementary') {
297
- score += weights.structural;
298
- }
299
- }
300
- return score;
301
- }
302
- function calculatePromoScore(element, className, id, context) {
290
+ function isPromoMatch(className, id, element, context) {
303
291
  if (!context.promoEnabled)
304
- return 0;
292
+ return false;
305
293
  const aggTest = context.promoMatchers.aggressive.test(className) ||
306
294
  context.promoMatchers.aggressive.test(id);
307
- const isAggressiveMatch = aggTest && !isWithinPrimaryContent(element);
308
- const isBaseMatch = !aggTest &&
309
- (context.promoMatchers.base.test(className) ||
310
- context.promoMatchers.base.test(id));
311
- return isAggressiveMatch || isBaseMatch ? context.weights.promo : 0;
295
+ if (aggTest)
296
+ return !isWithinPrimaryContent(element);
297
+ return (context.promoMatchers.base.test(className) ||
298
+ context.promoMatchers.base.test(id));
312
299
  }
313
300
  function isNoiseElement(element, context) {
314
301
  const tagName = element.tagName.toLowerCase();
302
+ const role = element.getAttribute('role');
315
303
  const className = element.getAttribute('class') ?? '';
316
304
  const id = element.getAttribute('id') ?? '';
317
- const role = element.getAttribute('role');
305
+ const interactive = isInteractive(element, role);
318
306
  const style = element.getAttribute('style');
319
- const elIsInteractive = isInteractive(element, role);
320
- const elIsHidden = element.hasAttribute('hidden') ||
307
+ const hidden = element.hasAttribute('hidden') ||
321
308
  element.getAttribute('aria-hidden') === 'true' ||
322
309
  (style !== null && HIDDEN_STYLE_REGEX.test(style));
323
- let score = 0;
324
- const { weights } = context;
325
- // Structural
326
- if (context.structuralTags.has(tagName) && !elIsInteractive) {
327
- score += weights.structural;
328
- }
329
- // Nav/Footer Scoring
310
+ // Structural tags (script, style, form, etc.)
311
+ if (context.structuralTags.has(tagName) && !interactive)
312
+ return true;
330
313
  if (context.flags.navFooter) {
331
- score += calculateNavFooterScore(tagName, className, id, role, weights);
332
- }
333
- // Hidden
334
- if (elIsHidden && !elIsInteractive) {
335
- score += weights.hidden;
336
- }
337
- // Sticky/Fixed
338
- if (FIXED_OR_HIGH_Z_PATTERN.test(className)) {
339
- score += weights.stickyFixed;
314
+ // Always-noise tags (nav, footer)
315
+ if (ALWAYS_NOISE_TAGS.has(tagName))
316
+ return true;
317
+ // Header with navigation role or noise class/id
318
+ if (tagName === 'header' &&
319
+ ((role !== null && NAVIGATION_ROLES.has(role)) ||
320
+ HEADER_NOISE_PATTERN.test(`${className} ${id}`)))
321
+ return true;
322
+ // Aside elements
323
+ if (tagName === 'aside')
324
+ return true;
325
+ // Navigation roles (except aside+complementary)
326
+ if (role !== null &&
327
+ NAVIGATION_ROLES.has(role) &&
328
+ (tagName !== 'aside' || role !== 'complementary'))
329
+ return true;
340
330
  }
341
- // Promo
342
- score += calculatePromoScore(element, className, id, context);
343
- return score >= weights.threshold;
331
+ // Hidden elements
332
+ if (hidden && !interactive)
333
+ return true;
334
+ // Sticky/fixed positioned elements
335
+ if (FIXED_OR_HIGH_Z_PATTERN.test(className))
336
+ return true;
337
+ // Promotional/noise content
338
+ if (isPromoMatch(className, id, element, context))
339
+ return true;
340
+ return false;
344
341
  }
345
342
  function cleanHeadings(document) {
346
343
  const headings = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
@@ -421,13 +418,18 @@ function hoistNestedRows(table) {
421
418
  }
422
419
  }
423
420
  }
424
- function stripNoise(document, context, signal) {
425
- cleanHeadings(document);
421
+ function stripNoise(document, signal) {
422
+ const context = getContext();
423
+ if (config.noiseRemoval.debug) {
424
+ logDebug('Noise removal audit enabled', {
425
+ categories: [...(context.flags.navFooter ? ['nav-footer'] : [])],
426
+ });
427
+ }
426
428
  // Structural Removal
427
- const { baseSelector, extraSelectors } = context;
428
- removeNodes(document.querySelectorAll(baseSelector));
429
- if (extraSelectors.length > 0) {
430
- removeNodes(document.querySelectorAll(extraSelectors.join(',')));
429
+ removeNodes(document.querySelectorAll(context.noiseSelector));
430
+ // Extra selectors (evaluated after base removal so DOM state is updated)
431
+ if (context.extraSelector) {
432
+ removeNodes(document.querySelectorAll(context.extraSelector));
431
433
  }
432
434
  // Candidates (conditional removal)
433
435
  const candidates = document.querySelectorAll(context.candidateSelector);
@@ -531,20 +533,54 @@ function surfaceHiddenTabPanels(document) {
531
533
  }
532
534
  }
533
535
  function stripTabTriggers(document) {
534
- surfaceHiddenTabPanels(document);
535
- const tabs = document.querySelectorAll('button[role="tab"]');
536
+ const tabs = document.querySelectorAll('[role="tab"]');
536
537
  for (let i = tabs.length - 1; i >= 0; i--) {
537
- tabs[i]?.remove();
538
+ const tab = tabs[i];
539
+ if (!tab)
540
+ continue;
541
+ const isSelected = tab.getAttribute('aria-selected') === 'true' ||
542
+ tab.getAttribute('data-state') === 'active' ||
543
+ tab.hasAttribute('data-selected');
544
+ if (!isSelected) {
545
+ tab.remove();
546
+ }
538
547
  }
539
548
  }
540
- function escapeTableCellPipes(document) {
549
+ /** Surface hidden tab panels, then strip unselected tab triggers. */
550
+ export function normalizeTabContent(document) {
551
+ surfaceHiddenTabPanels(document);
552
+ stripTabTriggers(document);
553
+ }
554
+ function normalizeTableCells(document) {
541
555
  const cells = document.querySelectorAll('td, th');
542
556
  for (const cell of cells) {
557
+ const brs = cell.querySelectorAll('br');
558
+ for (const br of brs) {
559
+ br.replaceWith(' ');
560
+ }
561
+ const blocks = Array.from(cell.querySelectorAll('div, p, ul, li, h1, h2, h3, h4, h5, h6'));
562
+ for (const block of blocks) {
563
+ if (!block.parentNode)
564
+ continue;
565
+ const span = document.createElement('span');
566
+ span.appendChild(document.createTextNode(' '));
567
+ while (block.firstChild) {
568
+ span.appendChild(block.firstChild);
569
+ }
570
+ span.appendChild(document.createTextNode(' '));
571
+ for (const attr of Array.from(block.attributes)) {
572
+ span.setAttribute(attr.name, attr.value);
573
+ }
574
+ block.replaceWith(span);
575
+ }
543
576
  const walker = document.createTreeWalker(cell, NODE_FILTER_SHOW_TEXT);
544
577
  let node;
545
578
  while ((node = walker.nextNode())) {
546
- if (node.textContent?.includes('|')) {
547
- node.textContent = node.textContent.replace(/\|/g, '\\|');
579
+ if (node.nodeValue) {
580
+ node.nodeValue = node.nodeValue.replace(/\r?\n/g, ' ');
581
+ if (node.nodeValue.includes('|')) {
582
+ node.nodeValue = node.nodeValue.replace(/\|/g, '\\|');
583
+ }
548
584
  }
549
585
  }
550
586
  }
@@ -673,8 +709,20 @@ function cleanCodeExamples(document) {
673
709
  pruneDemoInstructionBlocks(document);
674
710
  normalizeHighlightedCodeLines(document);
675
711
  }
712
+ function stripPromoLinks(document) {
713
+ const links = document.querySelectorAll('a[href]');
714
+ for (let i = links.length - 1; i >= 0; i--) {
715
+ const link = links[i];
716
+ if (!link)
717
+ continue;
718
+ const href = link.getAttribute('href');
719
+ if (href && UTM_PARAM_REGEX.test(href)) {
720
+ link.remove();
721
+ }
722
+ }
723
+ }
676
724
  function separateAdjacentInlineElements(document) {
677
- const badges = document.querySelectorAll('span.chakra-badge, [data-scope="badge"], [class*="badge"]');
725
+ const badges = document.querySelectorAll('span.chakra-badge, [data-scope="badge"], [class*="badge"], [data-slot="label"], [slot="label"]');
678
726
  for (const badge of badges) {
679
727
  const next = badge.nextSibling;
680
728
  if (next?.nodeType === 1) {
@@ -682,19 +730,17 @@ function separateAdjacentInlineElements(document) {
682
730
  }
683
731
  }
684
732
  }
733
+ // Called on both raw documents (pre-article path) and article fragments
734
+ // (post-Readability). Some passes (stripTabTriggers, etc.) are no-ops
735
+ // on Readability output since tabs are already stripped or absent.
685
736
  export function prepareDocumentForMarkdown(document, baseUrl, signal) {
686
- const context = getContext();
687
- if (config.noiseRemoval.debug) {
688
- logDebug('Noise removal audit enabled', {
689
- categories: [...(context.flags.navFooter ? ['nav-footer'] : [])],
690
- });
691
- }
692
- stripNoise(document, context, signal);
693
- stripTabTriggers(document);
737
+ normalizeTabContent(document);
738
+ cleanHeadings(document);
739
+ stripNoise(document, signal);
740
+ stripPromoLinks(document);
694
741
  cleanCodeExamples(document);
695
742
  separateAdjacentInlineElements(document);
696
- flattenTableCellBreaks(document);
697
- escapeTableCellPipes(document);
743
+ normalizeTableCells(document);
698
744
  normalizeTableStructure(document);
699
745
  if (baseUrl)
700
746
  resolveUrls(document, baseUrl);
@@ -723,41 +769,6 @@ function normalizeTableStructure(document) {
723
769
  hoistNestedRows(table);
724
770
  }
725
771
  }
726
- function flattenTableCellBreaks(document) {
727
- const cells = document.querySelectorAll('td, th');
728
- for (const cell of cells) {
729
- const brs = cell.querySelectorAll('br');
730
- for (const br of brs) {
731
- br.replaceWith(' ');
732
- }
733
- const blocks = Array.from(cell.querySelectorAll('div, p, ul, li, h1, h2, h3, h4, h5, h6'));
734
- for (const block of blocks) {
735
- if (!block.parentNode)
736
- continue;
737
- const span = document.createElement('span');
738
- span.appendChild(document.createTextNode(' '));
739
- while (block.firstChild) {
740
- span.appendChild(block.firstChild);
741
- }
742
- span.appendChild(document.createTextNode(' '));
743
- for (const attr of Array.from(block.attributes)) {
744
- span.setAttribute(attr.name, attr.value);
745
- }
746
- block.replaceWith(span);
747
- }
748
- const filterNewlines = (node) => {
749
- if (node.nodeType === 3 && node.nodeValue) {
750
- node.nodeValue = node.nodeValue.replace(/\r?\n/g, ' ');
751
- }
752
- else {
753
- for (const child of Array.from(node.childNodes)) {
754
- filterNewlines(child);
755
- }
756
- }
757
- };
758
- filterNewlines(cell);
759
- }
760
- }
761
772
  export function removeNoiseFromHtml(html, document, baseUrl, signal) {
762
773
  const shouldParse = isFullDocumentHtml(html) ||
763
774
  mayContainNoise(html) ||
@@ -1 +1 @@
1
- {"version":3,"file":"md-cleanup.d.ts","sourceRoot":"","sources":["../../src/lib/md-cleanup.ts"],"names":[],"mappings":"AAwEA,UAAU,cAAc;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA6cD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3C,MAAM,CAuCR;AAWD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAmBR"}
1
+ {"version":3,"file":"md-cleanup.d.ts","sourceRoot":"","sources":["../../src/lib/md-cleanup.ts"],"names":[],"mappings":"AAiGA,UAAU,cAAc;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAkeD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3C,MAAM,CAuCR;AAWD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAmBR"}
@@ -1,5 +1,6 @@
1
1
  import { config } from './core.js';
2
2
  import { throwIfAborted } from './utils.js';
3
+ // ── ASCII code constants ────────────────────────────────────────────
3
4
  const ASCII_HASH = 35;
4
5
  const ASCII_ASTERISK = 42;
5
6
  const ASCII_PLUS = 43;
@@ -10,13 +11,21 @@ const ASCII_DIGIT_9 = 57;
10
11
  const ASCII_EXCLAMATION = 33;
11
12
  const ASCII_QUESTION = 63;
12
13
  const ASCII_BRACKET_OPEN = 91;
14
+ // ── Title heuristic thresholds ──────────────────────────────────────
13
15
  const TITLE_MIN_WORDS = 2;
14
16
  const TITLE_MAX_WORDS = 10;
15
17
  const TITLE_MIN_CAPITALIZED = 2;
18
+ // ── Processing limits ───────────────────────────────────────────────
16
19
  const HAS_FOLLOWING_LOOKAHEAD = 10;
17
20
  const PROPERTY_FIX_MAX_PASSES = 5;
18
21
  const MAX_LINE_LENGTH = 80;
22
+ // ── TOC thresholds ──────────────────────────────────────────────────
23
+ const TOC_SCAN_LIMIT = 20;
24
+ const TOC_MAX_NON_EMPTY = 12;
25
+ const TOC_LINK_RATIO_THRESHOLD = 0.8;
26
+ // ── Fence pattern ───────────────────────────────────────────────────
19
27
  const FENCE_PATTERN = /^\s*(`{3,}|~{3,})/;
28
+ // ── Regex collection ────────────────────────────────────────────────
20
29
  const REGEX = {
21
30
  HEADING_MARKER: /^#{1,6}\s/m,
22
31
  HEADING_STRICT: /^#{1,6}\s+/m,
@@ -47,11 +56,11 @@ const REGEX = {
47
56
  NESTED_LIST_INDENT: /^( +)((?:[-*+])|\d+\.)\s/gm,
48
57
  TYPEDOC_COMMENT: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
49
58
  };
59
+ // ── Heading keywords (config-driven) ────────────────────────────────
50
60
  const HEADING_KEYWORDS = new Set(config.markdownCleanup.headingKeywords.map((value) => value.toLocaleLowerCase(config.i18n.locale)));
61
+ // ── Prefix patterns ─────────────────────────────────────────────────
51
62
  const SPECIAL_PREFIXES = /^(?:example|note|tip|warning|important|caution):\s+\S/i;
52
- const TOC_SCAN_LIMIT = 20;
53
- const TOC_MAX_NON_EMPTY = 12;
54
- const TOC_LINK_RATIO_THRESHOLD = 0.8;
63
+ // ── TypeDoc prefixes ────────────────────────────────────────────────
55
64
  const TYPEDOC_PREFIXES = [
56
65
  'Defined in:',
57
66
  'Returns:',
@@ -399,24 +408,48 @@ function fixConcatenatedProperties(text) {
399
408
  }
400
409
  function applyGlobalRegexes(text, options) {
401
410
  const checkAbort = createAbortChecker(options);
402
- let result = text.replace(/\u00A0/g, ' ');
403
- checkAbort('markdown:cleanup:headings');
404
- result = result
405
- .replace(REGEX.HEADING_SPACING, '$1\n\n$2')
406
- .replace(REGEX.HEADING_CODE_BLOCK, '$1\n\n```');
407
- if (config.markdownCleanup.removeTypeDocComments) {
408
- checkAbort('markdown:cleanup:typedoc');
409
- result = removeTypeDocArtifacts(result);
410
- }
411
- if (config.markdownCleanup.removeSkipLinks) {
412
- checkAbort('markdown:cleanup:skip-links');
413
- result = removeSkipLinks(result);
411
+ const passes = [
412
+ {
413
+ stage: 'markdown:cleanup:nbsp',
414
+ apply: (t) => t.replace(/\u00A0/g, ' '),
415
+ },
416
+ {
417
+ stage: 'markdown:cleanup:headings',
418
+ apply: (t) => t
419
+ .replace(REGEX.HEADING_SPACING, '$1\n\n$2')
420
+ .replace(REGEX.HEADING_CODE_BLOCK, '$1\n\n```'),
421
+ },
422
+ {
423
+ stage: 'markdown:cleanup:typedoc',
424
+ enabled: () => config.markdownCleanup.removeTypeDocComments,
425
+ apply: removeTypeDocArtifacts,
426
+ },
427
+ {
428
+ stage: 'markdown:cleanup:skip-links',
429
+ enabled: () => config.markdownCleanup.removeSkipLinks,
430
+ apply: removeSkipLinks,
431
+ },
432
+ {
433
+ stage: 'markdown:cleanup:spacing',
434
+ apply: normalizeMarkdownSpacing,
435
+ },
436
+ {
437
+ stage: 'markdown:cleanup:properties',
438
+ apply: fixConcatenatedProperties,
439
+ },
440
+ {
441
+ stage: 'markdown:cleanup:permalinks',
442
+ apply: stripTrailingHeadingPermalinks,
443
+ },
444
+ ];
445
+ let result = text;
446
+ for (const pass of passes) {
447
+ if (pass.enabled !== undefined && !pass.enabled())
448
+ continue;
449
+ checkAbort(pass.stage);
450
+ result = pass.apply(result);
414
451
  }
415
- checkAbort('markdown:cleanup:spacing');
416
- result = normalizeMarkdownSpacing(result);
417
- checkAbort('markdown:cleanup:properties');
418
- result = fixConcatenatedProperties(result);
419
- return stripTrailingHeadingPermalinks(result);
452
+ return result;
420
453
  }
421
454
  function normalizeNestedListIndentation(text) {
422
455
  return text.replace(REGEX.NESTED_LIST_INDENT, (match, spaces, marker) => {
@@ -1 +1 @@
1
- {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/lib/progress.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AACrC,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,0BAA0B,CAAC;CACpC;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;CACnC;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAkJD,wBAAgB,sBAAsB,CACpC,KAAK,CAAC,EAAE,gBAAgB,GACvB,gBAAgB,CAElB"}
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/lib/progress.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AACrC,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,0BAA0B,CAAC;CACpC;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;CACnC;AACD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAiLD,wBAAgB,sBAAsB,CACpC,KAAK,CAAC,EAAE,gBAAgB,GACvB,gBAAgB,CAElB"}