@link-assistant/web-capture 1.7.22 → 1.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/web-capture
|
|
2
2
|
|
|
3
|
+
## 1.7.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6b36b5b: Fix `<br><br>` collapsing into two CommonMark hard breaks (` \n \n`) instead of a paragraph break (`\n\n`). Google Docs export-html marks paragraph boundaries with `<br><br>`, which Turndown faithfully emitted as two trailing-two-space-newline pairs. Renderers (GitHub, MkDocs, Pandoc) then joined the surrounding lines into a single `<p>` with a `<br>`, cramming captions against images with no vertical spacing, and the "blank" separator line in the markdown source actually carried trailing whitespace that polluted diffs. Two or more adjacent hard breaks now coalesce to `\n\n` after Turndown runs, restoring true paragraph breaks. Applied in both `convertHtmlToMarkdown` (used by `--capture api`) and `convertHtmlToMarkdownEnhanced`.
|
|
8
|
+
|
|
3
9
|
## 1.7.22
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/web-capture",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.23",
|
|
4
4
|
"description": "CLI and microservice to render web pages as HTML, Markdown, or PNG",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"jest": "^29.7.0",
|
|
63
63
|
"jscpd": "^4.0.5",
|
|
64
64
|
"lint-staged": "^16.2.6",
|
|
65
|
+
"markdown-it": "^14.1.1",
|
|
65
66
|
"nock": "^13.5.4",
|
|
66
67
|
"prettier": "^3.6.2",
|
|
67
68
|
"supertest": "^6.3.4"
|
package/src/lib.js
CHANGED
|
@@ -184,7 +184,19 @@ export function convertHtmlToMarkdown(html, baseUrl) {
|
|
|
184
184
|
preserveTableCellLineBreaks(turndown);
|
|
185
185
|
// Decode HTML entities to unicode after markdown conversion
|
|
186
186
|
// Preserve non-breaking spaces as entities for clear marking
|
|
187
|
-
return
|
|
187
|
+
return coalesceBrRunsToParagraphBreak(
|
|
188
|
+
he.decode(turndown.turndown($.html())).replace(/\u00A0/g, ' ')
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Coalesce runs of CommonMark hard breaks (` \n`) into a single paragraph
|
|
193
|
+
// break. Google Docs export wraps every visual line break in `<br>`, including
|
|
194
|
+
// `<br><br>` at paragraph boundaries; Turndown faithfully emits two trailing-
|
|
195
|
+
// two-space-newline pairs for that, which renders as `<br><br>` joined inside
|
|
196
|
+
// one `<p>` instead of two paragraphs. Two or more adjacent hard breaks always
|
|
197
|
+
// mean "paragraph break" in the source HTML, so collapse them to `\n\n`.
|
|
198
|
+
function coalesceBrRunsToParagraphBreak(markdown) {
|
|
199
|
+
return markdown.replace(/(?: {2,}\n){2,}/g, '\n\n');
|
|
188
200
|
}
|
|
189
201
|
|
|
190
202
|
// Lift <br> nodes out of inline parents when they sit at the leading or
|
|
@@ -764,7 +776,9 @@ export function convertHtmlToMarkdownEnhanced(html, baseUrl, options = {}) {
|
|
|
764
776
|
|
|
765
777
|
// Decode HTML entities to unicode after markdown conversion
|
|
766
778
|
// Normalize non-breaking spaces to regular spaces in Markdown text
|
|
767
|
-
let markdown =
|
|
779
|
+
let markdown = coalesceBrRunsToParagraphBreak(
|
|
780
|
+
he.decode(turndown.turndown($.html())).replace(/\u00A0/g, ' ')
|
|
781
|
+
);
|
|
768
782
|
|
|
769
783
|
// Apply post-processing
|
|
770
784
|
if (postProcess) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<body>
|
|
4
|
+
<p>
|
|
5
|
+
<span>Caption A:</span><br />
|
|
6
|
+
<img alt="" src="data:image/png;base64,iVBORw0KGgo=" /><br /><br />
|
|
7
|
+
<span>Caption B:</span><br />
|
|
8
|
+
<img alt="" src="data:image/png;base64,iVBORw0KGgo=" />
|
|
9
|
+
</p>
|
|
10
|
+
</body>
|
|
11
|
+
</html>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
import {
|
|
6
|
+
convertHtmlToMarkdown,
|
|
7
|
+
convertHtmlToMarkdownEnhanced,
|
|
8
|
+
} from '../../src/lib.js';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const HTML = fs.readFileSync(
|
|
13
|
+
path.join(__dirname, '..', 'fixtures', 'paragraph-vs-line-break.html'),
|
|
14
|
+
'utf8'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
describe('<br><br> coalesces to a paragraph break (not two hard breaks)', () => {
|
|
18
|
+
it('renders a paragraph break between Caption A and Caption B (convertHtmlToMarkdown)', () => {
|
|
19
|
+
const md = convertHtmlToMarkdown(HTML);
|
|
20
|
+
const lines = md.split('\n');
|
|
21
|
+
const idxA = lines.findIndex((l) => l.includes('Caption A:'));
|
|
22
|
+
const idxB = lines.findIndex((l) => l.includes('Caption B:'));
|
|
23
|
+
expect(idxA).toBeGreaterThanOrEqual(0);
|
|
24
|
+
expect(idxB).toBeGreaterThan(idxA);
|
|
25
|
+
|
|
26
|
+
const between = lines.slice(idxA + 1, idxB);
|
|
27
|
+
expect(between.some((l) => l === '')).toBe(true);
|
|
28
|
+
|
|
29
|
+
expect(md).not.toMatch(/^[ \t]+ {2}$/m);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders a paragraph break between Caption A and Caption B (convertHtmlToMarkdownEnhanced)', () => {
|
|
33
|
+
const { markdown } = convertHtmlToMarkdownEnhanced(HTML);
|
|
34
|
+
const lines = markdown.split('\n');
|
|
35
|
+
const idxA = lines.findIndex((l) => l.includes('Caption A:'));
|
|
36
|
+
const idxB = lines.findIndex((l) => l.includes('Caption B:'));
|
|
37
|
+
expect(idxA).toBeGreaterThanOrEqual(0);
|
|
38
|
+
expect(idxB).toBeGreaterThan(idxA);
|
|
39
|
+
|
|
40
|
+
const between = lines.slice(idxA + 1, idxB);
|
|
41
|
+
expect(between.some((l) => l === '')).toBe(true);
|
|
42
|
+
|
|
43
|
+
expect(markdown).not.toMatch(/^[ \t]+ {2}$/m);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('CommonMark renders Caption A and Caption B as separate paragraphs', () => {
|
|
47
|
+
const md = convertHtmlToMarkdown(HTML);
|
|
48
|
+
const html = new MarkdownIt('commonmark').render(md);
|
|
49
|
+
const aIdx = html.indexOf('Caption A:');
|
|
50
|
+
const bIdx = html.indexOf('Caption B:');
|
|
51
|
+
expect(aIdx).toBeGreaterThan(-1);
|
|
52
|
+
expect(bIdx).toBeGreaterThan(aIdx);
|
|
53
|
+
const between = html.slice(aIdx, bIdx);
|
|
54
|
+
expect(between).toMatch(/<\/p>\s*<p>/);
|
|
55
|
+
});
|
|
56
|
+
});
|