@link-assistant/web-capture 2.0.0 → 2.0.1

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 (117) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bin/web-capture.js +2 -5
  3. package/package.json +22 -11
  4. package/src/archive.js +0 -1
  5. package/src/binary-download.js +0 -1
  6. package/src/docx.js +13 -10
  7. package/src/fetch.js +0 -1
  8. package/src/gdocs-images.js +0 -1
  9. package/src/gdocs.js +0 -1
  10. package/src/github.js +0 -1
  11. package/src/lib.js +0 -1
  12. package/src/markdown.js +0 -1
  13. package/src/shared-dialog.js +0 -1
  14. package/src/stream.js +0 -1
  15. package/src/transport.js +23 -5
  16. package/src/txt.js +0 -1
  17. package/.changeset/README.md +0 -8
  18. package/.changeset/config.json +0 -11
  19. package/.dockerignore +0 -6
  20. package/.jscpd.json +0 -19
  21. package/.lenv +0 -8
  22. package/.prettierignore +0 -8
  23. package/.prettierrc +0 -10
  24. package/Dockerfile +0 -52
  25. package/docker-compose.yml +0 -11
  26. package/eslint.config.js +0 -112
  27. package/examples/js/capture_cache.js +0 -12
  28. package/examples/js/engine_comparison.js +0 -39
  29. package/examples/js/html_download.js +0 -23
  30. package/examples/js/image_download.js +0 -36
  31. package/examples/js/markdown_download.js +0 -23
  32. package/examples/js/playwright_example.js +0 -23
  33. package/examples/python/capture_client.py +0 -20
  34. package/examples/python/html_download.py +0 -3
  35. package/examples/python/image_download.py +0 -16
  36. package/examples/python/markdown_download.py +0 -3
  37. package/examples/python/playwright_example.py +0 -22
  38. package/jest.config.mjs +0 -17
  39. package/tests/ci/dependency-freshness.test.js +0 -74
  40. package/tests/ci/detect-code-changes.test.js +0 -187
  41. package/tests/ci/docker-install.test.js +0 -31
  42. package/tests/ci/rust-dockerfile.test.js +0 -54
  43. package/tests/ci/workflow-policy.test.js +0 -79
  44. package/tests/e2e/docker.test.js +0 -186
  45. package/tests/e2e/process.test.js +0 -215
  46. package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
  47. package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
  48. package/tests/fixtures/deepwiki/index.md +0 -1500
  49. package/tests/fixtures/deepwiki/part-1.md +0 -776
  50. package/tests/fixtures/habr-articles.js +0 -45
  51. package/tests/fixtures/heading-numbering.html +0 -19
  52. package/tests/fixtures/list-item-with-br.html +0 -8
  53. package/tests/fixtures/ol-continuous-numbering.html +0 -12
  54. package/tests/fixtures/paragraph-vs-line-break.html +0 -11
  55. package/tests/helpers/nock.js +0 -10
  56. package/tests/helpers/screenshot.js +0 -10
  57. package/tests/helpers/search-fixture.cjs +0 -17
  58. package/tests/helpers/temporary-directory.js +0 -12
  59. package/tests/integration/api-endpoints.test.js +0 -418
  60. package/tests/integration/archive-default-layout.test.js +0 -37
  61. package/tests/integration/browser-engines.test.js +0 -80
  62. package/tests/integration/browser-packaged-path.test.js +0 -45
  63. package/tests/integration/deepwiki-download.test.js +0 -144
  64. package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
  65. package/tests/integration/formalai-contract.test.js +0 -214
  66. package/tests/integration/gdocs-public-doc.test.js +0 -315
  67. package/tests/integration/github-readme.test.js +0 -133
  68. package/tests/integration/google-drive-image.test.js +0 -246
  69. package/tests/integration/habr-article.test.js +0 -337
  70. package/tests/integration/search-endpoint.test.js +0 -88
  71. package/tests/integration/stackoverflow-download.test.js +0 -173
  72. package/tests/integration/wikipedia-download.test.js +0 -134
  73. package/tests/jest.setup.mjs +0 -7
  74. package/tests/mock/index.test.js +0 -225
  75. package/tests/unit/animation.test.js +0 -33
  76. package/tests/unit/batch.test.js +0 -114
  77. package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
  78. package/tests/unit/browser-launch-options.test.js +0 -42
  79. package/tests/unit/browser.test.js +0 -172
  80. package/tests/unit/cache.test.js +0 -182
  81. package/tests/unit/cli.test.js +0 -399
  82. package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
  83. package/tests/unit/convertRelativeUrls.test.js +0 -34
  84. package/tests/unit/extract-images.test.js +0 -259
  85. package/tests/unit/fetch-html.test.js +0 -54
  86. package/tests/unit/figures.test.js +0 -74
  87. package/tests/unit/gdocs-export-headers.test.js +0 -28
  88. package/tests/unit/gdocs-preprocess.test.js +0 -36
  89. package/tests/unit/gdocs.test.js +0 -1490
  90. package/tests/unit/github.test.js +0 -156
  91. package/tests/unit/heading-image.test.js +0 -49
  92. package/tests/unit/heading-numbering.test.js +0 -28
  93. package/tests/unit/html2md-br-in-list-item.test.js +0 -23
  94. package/tests/unit/html2md-ol-numbering.test.js +0 -20
  95. package/tests/unit/html2md.test.js +0 -695
  96. package/tests/unit/image-mode-defaults.test.js +0 -54
  97. package/tests/unit/kreuzberg.test.js +0 -249
  98. package/tests/unit/latex.test.js +0 -123
  99. package/tests/unit/localize-images.test.js +0 -114
  100. package/tests/unit/markdown.test.js +0 -36
  101. package/tests/unit/metadata.test.js +0 -140
  102. package/tests/unit/package-runtime-dependencies.test.js +0 -74
  103. package/tests/unit/paragraph-vs-line-break.test.js +0 -56
  104. package/tests/unit/postprocess.test.js +0 -110
  105. package/tests/unit/proxy-headers.test.js +0 -100
  106. package/tests/unit/publish-verification.test.js +0 -54
  107. package/tests/unit/retry.test.js +0 -111
  108. package/tests/unit/search.test.js +0 -339
  109. package/tests/unit/shared-dialog.test.js +0 -173
  110. package/tests/unit/stackoverflow.test.js +0 -32
  111. package/tests/unit/stream.test.js +0 -83
  112. package/tests/unit/verify.test.js +0 -182
  113. package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
  114. package/tests/xpaste/xpaste.e2e.test.js +0 -36
  115. package/tests/xpaste/xpaste.integration.test.js +0 -41
  116. package/tests/xpaste/xpaste.mock.test.js +0 -261
  117. package/tests/xpaste/xpaste.unit.test.js +0 -119
@@ -1,695 +0,0 @@
1
- import {
2
- convertHtmlToMarkdown,
3
- convertHtmlToMarkdownEnhanced,
4
- } from '../../src/lib.js';
5
-
6
- describe('convertHtmlToMarkdown', () => {
7
- it('removes empty links and anchor headings', () => {
8
- const html = `
9
- <a href="/http-api/authorization"></a>
10
- <a href="/webhooks/authorization"></a>
11
- <a href="/api-reference/http-api"></a>
12
- <h3></h3>
13
- <a href="#quick-start"></a>
14
- <h3>Quick start</h3>
15
- `;
16
- const md = convertHtmlToMarkdown(html);
17
- // Should not contain empty []() links or [](#anchor) or empty headings
18
- expect(md).not.toMatch(/\[\s*\]\([^)]*\)/);
19
- expect(md).not.toMatch(/#+\s*$/m);
20
- expect(md).not.toMatch(/\[\s*\]\(#.*\)/);
21
- // Should contain the real heading
22
- expect(md).toMatch(/### Quick start/);
23
- });
24
-
25
- it('removes empty links in complex content', () => {
26
- const html = `
27
- <div>
28
- <a href="/foo"></a>
29
- <a href="/bar">Bar</a>
30
- <a href="#anchor"></a>
31
- <h2></h2>
32
- <h2>Title</h2>
33
- </div>
34
- `;
35
- const md = convertHtmlToMarkdown(html);
36
- expect(md).not.toMatch(/\[\s*\]\([^)]*\)/);
37
- expect(md).toMatch(/\[Bar\]\(\/bar\)/);
38
- expect(md).toMatch(/## Title/);
39
- });
40
-
41
- it('removes empty links with only whitespace', () => {
42
- const html = `
43
- <a href="/foo"> </a>
44
- <a href="/bar">\n\t</a>
45
- <a href="#anchor"> </a>
46
- `;
47
- const md = convertHtmlToMarkdown(html);
48
- expect(md).not.toMatch(/\[\s*\]\([^)]*\)/);
49
- expect(md).not.toMatch(/\[\s*\]\(#.*\)/);
50
- });
51
-
52
- it('removes empty links with only child elements', () => {
53
- const html = `
54
- <a href="/foo"><span></span></a>
55
- <a href="/bar"><div></div></a>
56
- <a href="#anchor"><span> </span></a>
57
- <a href="/baz"><img src='x.png'></a>
58
- <a href="/qux"><span>\n</span></a>
59
- <a href="/keep">Text<span>child</span></a>
60
- `;
61
- const md = convertHtmlToMarkdown(html);
62
- expect(md).not.toMatch(/\[\s*\]\([^)]*\)/);
63
- expect(md).not.toMatch(/\[\s*\]\(#.*\)/);
64
- expect(md).toMatch(/\[Textchild\]\(\/keep\)/);
65
- });
66
-
67
- it('removes headings with only whitespace', () => {
68
- const html = `
69
- <h2> </h2>
70
- <h3>\n\t</h3>
71
- <h4>\u00A0</h4>
72
- <h2>Valid Heading</h2>
73
- `;
74
- const md = convertHtmlToMarkdown(html);
75
- expect(md).not.toMatch(/#+\s*$/m);
76
- expect(md).toMatch(/## Valid Heading/);
77
- });
78
-
79
- it('converts relative links to absolute if baseUrl is provided', () => {
80
- const html = `
81
- <a href="/foo">Foo</a>
82
- <img src="/bar.png" alt="Bar">
83
- <a href="https://external.com">External</a>
84
- `;
85
- const md = convertHtmlToMarkdown(html, 'https://example.com/base/');
86
- expect(md).toMatch(/\[Foo\]\(https:\/\/example.com\/foo\)/);
87
- expect(md).toMatch(/!\[Bar\]\(https:\/\/example.com\/bar.png\)/);
88
- expect(md).toMatch(/\[External\]\(https:\/\/external.com\/?\)/);
89
- });
90
-
91
- it('does not change links if no baseUrl is provided', () => {
92
- const html = `
93
- <a href="/foo">Foo</a>
94
- <img src="/bar.png" alt="Bar">
95
- `;
96
- const md = convertHtmlToMarkdown(html);
97
- expect(md).toMatch(/\[Foo\]\(\/foo\)/);
98
- expect(md).toMatch(/!\[Bar\]\(\/bar.png\)/);
99
- });
100
-
101
- it('converts ARIA role table to Markdown table', () => {
102
- const html = `
103
- <div
104
- role="table"
105
- aria-label="Semantic Elements"
106
- aria-describedby="semantic_elements_table_desc"
107
- aria-rowcount="81">
108
- <div id="semantic_elements_table_desc">
109
- Semantic Elements to use instead of ARIA's roles
110
- </div>
111
- <div role="rowgroup">
112
- <div role="row">
113
- <span role="columnheader" aria-sort="none">ARIA Role</span>
114
- <span role="columnheader" aria-sort="none">Semantic Element</span>
115
- </div>
116
- </div>
117
- <div role="rowgroup">
118
- <div role="row" aria-rowindex="11">
119
- <span role="cell">header</span>
120
- <span role="cell">h1</span>
121
- </div>
122
- <div role="row" aria-rowindex="16">
123
- <span role="cell">header</span>
124
- <span role="cell">h6</span>
125
- </div>
126
- <div role="row" aria-rowindex="18">
127
- <span role="cell">rowgroup</span>
128
- <span role="cell">thead</span>
129
- </div>
130
- <div role="row" aria-rowindex="24">
131
- <span role="cell">term</span>
132
- <span role="cell">dt</span>
133
- </div>
134
- </div>
135
- </div>
136
- `;
137
- const md = convertHtmlToMarkdown(html);
138
- // Should contain a Markdown table header
139
- expect(md).toMatch(/\|\s*ARIA Role\s*\|\s*Semantic Element\s*\|/);
140
- // Should contain the separator row
141
- expect(md).toMatch(/\|\s*-+\s*\|\s*-+\s*\|/);
142
- // Should contain all data rows
143
- expect(md).toMatch(/\|\s*header\s*\|\s*h1\s*\|/);
144
- expect(md).toMatch(/\|\s*header\s*\|\s*h6\s*\|/);
145
- expect(md).toMatch(/\|\s*rowgroup\s*\|\s*thead\s*\|/);
146
- expect(md).toMatch(/\|\s*term\s*\|\s*dt\s*\|/);
147
- });
148
-
149
- it('converts a regular HTML table to Markdown table', () => {
150
- const html = `
151
- <table>
152
- <caption>Sample Table</caption>
153
- <thead>
154
- <tr>
155
- <th>Header 1</th>
156
- <th>Header 2</th>
157
- </tr>
158
- </thead>
159
- <tbody>
160
- <tr>
161
- <td>Cell 1</td>
162
- <td>Cell 2</td>
163
- </tr>
164
- <tr>
165
- <td>Cell 3</td>
166
- <td>Cell 4</td>
167
- </tr>
168
- </tbody>
169
- </table>
170
- `;
171
- const md = convertHtmlToMarkdown(html);
172
- // Should contain a Markdown table header
173
- expect(md).toMatch(/\|\s*Header 1\s*\|\s*Header 2\s*\|/);
174
- // Should contain the separator row
175
- expect(md).toMatch(/\|\s*-+\s*\|\s*-+\s*\|/);
176
- // Should contain all data rows
177
- expect(md).toMatch(/\|\s*Cell 1\s*\|\s*Cell 2\s*\|/);
178
- expect(md).toMatch(/\|\s*Cell 3\s*\|\s*Cell 4\s*\|/);
179
- });
180
-
181
- it('handles empty tables gracefully', () => {
182
- const html = `
183
- <table>
184
- <thead>
185
- <tr>
186
- <th></th>
187
- <th></th>
188
- </tr>
189
- </thead>
190
- <tbody>
191
- <tr>
192
- <td></td>
193
- <td></td>
194
- </tr>
195
- </tbody>
196
- </table>
197
- `;
198
- const md = convertHtmlToMarkdown(html);
199
- // Should not throw an error
200
- // Accept the actual output for an empty table (2 columns, empty header and row)
201
- expect(md.trim()).toBe('| | |\n| --- | --- |\n| | |');
202
- });
203
-
204
- it('handles specific ARIA table example', () => {
205
- const html = `
206
- <div
207
- class="mx-auto w-full decoration-primary/6 max-w-3xl page-api-block:ml-0 table_tableWrapper__zr7LC"
208
- >
209
- <div role="table" class="flex flex-col">
210
- <div
211
- role="rowgroup"
212
- class="w-full table_rowGroup__IKtSP straight-corners:rounded-none"
213
- >
214
- <div role="row" class="flex w-full">
215
- <div
216
- role="columnheader"
217
- class="table_columnHeader__PGmsy text-left"
218
- title="Attribute"
219
- style="width: 181px; min-width: 181px"
220
- >
221
- Attribute
222
- </div>
223
- <div
224
- role="columnheader"
225
- class="table_columnHeader__PGmsy text-left"
226
- title="Type"
227
- style="width: 110px; min-width: 110px"
228
- >
229
- Type
230
- </div>
231
- <div
232
- role="columnheader"
233
- class="table_columnHeader__PGmsy text-left"
234
- title="Required"
235
- style="width: 104px; min-width: 104px"
236
- >
237
- Required
238
- </div>
239
- <div
240
- role="columnheader"
241
- class="table_columnHeader__PGmsy text-left"
242
- title="Description"
243
- style="width: clamp(100px, 100% - 395px, 100%); min-width: 100px"
244
- >
245
- Description
246
- </div>
247
- </div>
248
- </div>
249
- <div role="rowgroup" class="flex flex-col w-full [&amp;>*+*]:border-t">
250
- <div class="table_row__LpfCG" role="row">
251
- <div
252
- role="cell"
253
- class="table_cell__X_gFM"
254
- style="width: 181px; min-width: 181px"
255
- >
256
- <div
257
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
258
- >
259
- <p class="mx-auto decoration-primary/6 w-full max-w-[unset]">
260
- amount
261
- </p>
262
- </div>
263
- </div>
264
- <div
265
- role="cell"
266
- class="table_cell__X_gFM"
267
- style="width: 110px; min-width: 110px"
268
- >
269
- <div
270
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
271
- >
272
- <p class="mx-auto decoration-primary/6 w-full max-w-[unset]">
273
- decimal
274
- </p>
275
- </div>
276
- </div>
277
- <div
278
- role="cell"
279
- class="table_cell__X_gFM"
280
- style="width: 104px; min-width: 104px"
281
- >
282
- <div
283
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
284
- >
285
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">yes</p>
286
- </div>
287
- </div>
288
- <div
289
- role="cell"
290
- class="table_cell__X_gFM"
291
- style="
292
- width: clamp(100px, 100% - 395px, 100%);
293
- min-width: clamp(100px, 100% - 395px, 100%);
294
- "
295
- >
296
- <div
297
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
298
- >
299
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
300
- order amount
301
- </p>
302
- </div>
303
- </div>
304
- </div>
305
- <div class="table_row__LpfCG" role="row">
306
- <div
307
- role="cell"
308
- class="table_cell__X_gFM"
309
- style="width: 181px; min-width: 181px"
310
- >
311
- <div
312
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
313
- >
314
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
315
- currency_id
316
- </p>
317
- </div>
318
- </div>
319
- <div
320
- role="cell"
321
- class="table_cell__X_gFM"
322
- style="width: 110px; min-width: 110px"
323
- >
324
- <div
325
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
326
- >
327
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
328
- string
329
- </p>
330
- </div>
331
- </div>
332
- <div
333
- role="cell"
334
- class="table_cell__X_gFM"
335
- style="width: 104px; min-width: 104px"
336
- >
337
- <div
338
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
339
- >
340
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">yes</p>
341
- </div>
342
- </div>
343
- <div
344
- role="cell"
345
- class="table_cell__X_gFM"
346
- style="
347
- width: clamp(100px, 100% - 395px, 100%);
348
- min-width: clamp(100px, 100% - 395px, 100%);
349
- "
350
- >
351
- <div
352
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
353
- >
354
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
355
- order currency id
356
- </p>
357
- </div>
358
- </div>
359
- </div>
360
- <div class="table_row__LpfCG" role="row">
361
- <div
362
- role="cell"
363
- class="table_cell__X_gFM"
364
- style="width: 181px; min-width: 181px"
365
- >
366
- <div
367
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
368
- >
369
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
370
- network
371
- </p>
372
- </div>
373
- </div>
374
- <div
375
- role="cell"
376
- class="table_cell__X_gFM"
377
- style="width: 110px; min-width: 110px"
378
- >
379
- <div
380
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
381
- >
382
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
383
- string
384
- </p>
385
- </div>
386
- </div>
387
- <div
388
- role="cell"
389
- class="table_cell__X_gFM"
390
- style="width: 104px; min-width: 104px"
391
- >
392
- <div
393
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
394
- >
395
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">yes</p>
396
- </div>
397
- </div>
398
- <div
399
- role="cell"
400
- class="table_cell__X_gFM"
401
- style="
402
- width: clamp(100px, 100% - 395px, 100%);
403
- min-width: clamp(100px, 100% - 395px, 100%);
404
- "
405
- >
406
- <div
407
- class="blocks w-full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
408
- >
409
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
410
- currency network
411
- </p>
412
- </div>
413
- </div>
414
- </div>
415
- <div class="table_row__LpfCG" role="row">
416
- <div
417
- role="cell"
418
- class="table_cell__X_gFM"
419
- style="width: 181px; min-width: 181px"
420
- >
421
- <div
422
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
423
- >
424
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
425
- external_order_id
426
- </p>
427
- </div>
428
- </div>
429
- <div
430
- role="cell"
431
- class="table_cell__X_gFM"
432
- style="width: 110px; min-width: 110px"
433
- >
434
- <div
435
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
436
- >
437
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
438
- string
439
- </p>
440
- </div>
441
- </div>
442
- <div
443
- role="cell"
444
- class="table_cell__X_gFM"
445
- style="width: 104px; min-width: 104px"
446
- >
447
- <div
448
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
449
- >
450
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">no</p>
451
- </div>
452
- </div>
453
- <div
454
- role="cell"
455
- class="table_cell__X_gFM"
456
- style="
457
- width: clamp(100px, 100% - 395px, 100%);
458
- min-width: clamp(100px, 100% - 395px, 100%);
459
- "
460
- >
461
- <div
462
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
463
- >
464
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
465
- additional external invoice id
466
- </p>
467
- </div>
468
- </div>
469
- </div>
470
- <div class="table_row__LpfCG" role="row">
471
- <div
472
- role="cell"
473
- class="table_cell__X_gFM"
474
- style="width: 181px; min-width: 181px"
475
- >
476
- <div
477
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
478
- >
479
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
480
- email
481
- </p>
482
- </div>
483
- </div>
484
- <div
485
- role="cell"
486
- class="table_cell__X_gFM"
487
- style="width: 110px; min-width: 110px"
488
- >
489
- <div
490
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
491
- >
492
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
493
- string
494
- </p>
495
- </div>
496
- </div>
497
- <div
498
- role="cell"
499
- class="table_cell__X_gFM"
500
- style="width: 104px; min-width: 104px"
501
- >
502
- <div
503
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
504
- >
505
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">no</p>
506
- </div>
507
- </div>
508
- <div
509
- role="cell"
510
- class="table_cell__X_gFM"
511
- style="
512
- width: clamp(100px, 100% - 395px, 100%);
513
- min-width: clamp(100px, 100% - 395px, 100%);
514
- "
515
- >
516
- <div
517
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
518
- >
519
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
520
- end users email
521
- </p>
522
- </div>
523
- </div>
524
- </div>
525
- <div class="table_row__LpfCG" role="row">
526
- <div
527
- role="cell"
528
- class="table_cell__X_gFM"
529
- style="width: 181px; min-width: 181px"
530
- >
531
- <div
532
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
533
- >
534
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
535
- description
536
- </p>
537
- </div>
538
- </div>
539
- <div
540
- role="cell"
541
- class="table_cell__X_gFM"
542
- style="width: 110px; min-width: 110px"
543
- >
544
- <div
545
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
546
- >
547
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
548
- string
549
- </p>
550
- </div>
551
- </div>
552
- <div
553
- role="cell"
554
- class="table_cell__X_gFM"
555
- style="width: 104px; min-width: 104px"
556
- >
557
- <div
558
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
559
- >
560
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">no</p>
561
- </div>
562
- </div>
563
- <div
564
- role="cell"
565
- class="table_cell__X_gFM"
566
- style="
567
- width: clamp(100px, 100% - 395px, 100%);
568
- min-width: clamp(100px, 100% - 395px, 100%);
569
- "
570
- >
571
- <div
572
- class="blocks w_full space-y-2 lg:space-y-3 leading-normal self-center [&amp;_*]:text-left text-left"
573
- >
574
- <p class="mx-auto decoration-primary/6 w_full max-w-[unset]">
575
- order description
576
- </p>
577
- </div>
578
- </div>
579
- </div>
580
- </div>
581
- </div>
582
- </div>
583
- `;
584
-
585
- const md = convertHtmlToMarkdown(html);
586
- // Should contain a Markdown table header
587
- expect(md).toMatch(
588
- /\|\s*Attribute\s*\|\s*Type\s*\|\s*Required\s*\|\s*Description\s*\|/
589
- );
590
- // Should contain the separator row
591
- expect(md).toMatch(/\|\s*-+\s*\|\s*-+\s*\|\s*-+\s*\|\s*-+\s*\|/);
592
- // Should contain all data rows
593
- // Accept any number of backslashes before underscores (Turndown may escape as \\_)
594
- expect(md).toMatch(
595
- /\|\s*amount\s*\|\s*decimal\s*\|\s*yes\s*\|\s*order amount\s*\|/
596
- );
597
- expect(md).toMatch(
598
- /\|\s*currency(?:\\*)_id\s*\|\s*string\s*\|\s*yes\s*\|\s*order currency id\s*\|/
599
- );
600
- expect(md).toMatch(
601
- /\|\s*network\s*\|\s*string\s*\|\s*yes\s*\|\s*currency network\s*\|/
602
- );
603
- expect(md).toMatch(
604
- /\|\s*external(?:\\*)_order(?:\\*)_id\s*\|\s*string\s*\|\s*no\s*\|\s*additional external invoice id\s*\|/
605
- );
606
- expect(md).toMatch(
607
- /\|\s*email\s*\|\s*string\s*\|\s*no\s*\|\s*end users email\s*\|/
608
- );
609
- expect(md).toMatch(
610
- /\|\s*description\s*\|\s*string\s*\|\s*no\s*\|\s*order description\s*\|/
611
- );
612
- });
613
- });
614
-
615
- describe('convertHtmlToMarkdownEnhanced content selectors', () => {
616
- it('scopes Habr-shaped article markdown while keeping full-page metadata', () => {
617
- const html = `
618
- <html>
619
- <head><meta name="keywords" content="links, theory"></head>
620
- <body>
621
- <nav><a href="/en/feed">Habr</a><a href="/en/search">Search</a></nav>
622
- <a href="/en/sandbox/start/">Write a publication</a>
623
- <article>
624
- <header>
625
- <h1>The Links Theory 0.0.2</h1>
626
- <a class="tm-user-info__username" href="/users/links">links</a>
627
- <time datetime="2026-04-01T00:00:00Z">April 1</time>
628
- </header>
629
- <div class="article-formatted-body">
630
- <p>Last April 1st, as you might have guessed, the project shipped.</p>
631
- </div>
632
- </article>
633
- </body>
634
- </html>
635
- `;
636
-
637
- const result = convertHtmlToMarkdownEnhanced(
638
- html,
639
- 'https://habr.com/en/articles/895896/',
640
- {
641
- contentSelector: 'article',
642
- bodySelector: '.article-formatted-body',
643
- }
644
- );
645
-
646
- expect(result.markdown.trim()).toMatch(/^# The Links Theory 0\.0\.2/);
647
- expect(result.markdown).toContain('Last April 1st');
648
- expect(result.markdown).not.toContain('Habr');
649
- expect(result.markdown).not.toContain('Search');
650
- expect(result.markdown).not.toContain('Write a publication');
651
- expect(result.metadata.author).toBe('links');
652
- expect(result.metadata.tags).toEqual(['links', 'theory']);
653
- });
654
- });
655
-
656
- describe('convertHtmlToMarkdownEnhanced Habr compatibility', () => {
657
- it('preserves article code line breaks and extracts formulas from a Habr fixture', () => {
658
- const html = `
659
- <article>
660
- <h1>The Links Theory 0.0.2</h1>
661
- <div class="article-formatted-body">
662
- <p>A formula <img class="formula" source="\\mathbf{L}: L \\to L^2" alt="formula"> remains inline.</p>
663
- <figure>
664
- <img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/abc.png" alt="Figure 1">
665
- <figcaption>Figure 1. Link multiplication.</figcaption>
666
- </figure>
667
- <pre><code>L = { 1 , 2 }
668
- L x L = {
669
- (1, 1),
670
- (1, 2),
671
- }</code></pre>
672
- </div>
673
- </article>
674
- `;
675
-
676
- const { markdown } = convertHtmlToMarkdownEnhanced(
677
- html,
678
- 'https://habr.com/en/articles/895896/',
679
- { preserveCodeWhitespace: true }
680
- );
681
-
682
- expect(markdown).toContain('$\\\\mathbf{L}: L \\\\to L^2$');
683
- expect(markdown).toContain(
684
- '![Figure 1](https://habrastorage.org/r/w1560/getpro/habr/upload_files/abc.png)'
685
- );
686
- expect(markdown).toContain(`\`\`\`
687
- L = { 1 , 2 }
688
- L x L = {
689
- (1, 1),
690
- (1, 2),
691
- }
692
- \`\`\``);
693
- expect(markdown).not.toContain('L = { 1 , 2 }L x L');
694
- });
695
- });