@link-assistant/web-capture 1.7.12 → 1.7.14
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 +12 -0
- package/package.json +1 -1
- package/src/gdocs-render-markdown.js +2 -4
- package/src/gdocs.js +18 -5
- package/tests/integration/gdocs-public-doc.test.js +9 -0
- package/tests/unit/gdocs.test.js +152 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/web-capture
|
|
2
2
|
|
|
3
|
+
## 1.7.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b2a57f5: Fix Google Docs browser-model capture for live table separators, nested ordered lists, and archive image localization.
|
|
8
|
+
|
|
9
|
+
## 1.7.13
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Auto-release unreleased changes
|
|
14
|
+
|
|
3
15
|
## 1.7.12
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -42,9 +42,7 @@ export function renderBlocksMarkdown(blocks) {
|
|
|
42
42
|
.slice(0, i)
|
|
43
43
|
.reverse()
|
|
44
44
|
.find((entry) => entry.text);
|
|
45
|
-
|
|
46
|
-
cur.list && prev?.list && (cur.list.id || '') === (prev.list.id || '');
|
|
47
|
-
if (sameList) {
|
|
45
|
+
if (cur.list && prev?.list) {
|
|
48
46
|
joined.push('\n');
|
|
49
47
|
} else if (cur.quote && prev?.quote) {
|
|
50
48
|
joined.push('\n>\n');
|
|
@@ -75,7 +73,7 @@ function renderParagraphMarkdown(block, context = {}) {
|
|
|
75
73
|
return `## ${text}`;
|
|
76
74
|
}
|
|
77
75
|
if (block.list) {
|
|
78
|
-
const indent = '
|
|
76
|
+
const indent = ' '.repeat(Math.max(0, block.list.level || 0));
|
|
79
77
|
const orderedIndex = Math.max(1, Number(context.orderedIndex) || 1);
|
|
80
78
|
const marker = block.list.ordered ? `${orderedIndex}.` : '-';
|
|
81
79
|
return `${indent}${marker} ${text}`;
|
package/src/gdocs.js
CHANGED
|
@@ -651,6 +651,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
651
651
|
const tableHistograms = [];
|
|
652
652
|
let currentHistogram = null;
|
|
653
653
|
let previousTableControl = null;
|
|
654
|
+
let skipNextTableNewline = false;
|
|
654
655
|
|
|
655
656
|
const bumpHistogram = (code) => {
|
|
656
657
|
if (!currentHistogram) {
|
|
@@ -731,6 +732,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
731
732
|
flushParagraph(i + 1);
|
|
732
733
|
table = { type: 'table', rows: [] };
|
|
733
734
|
previousTableControl = code;
|
|
735
|
+
skipNextTableNewline = false;
|
|
734
736
|
currentHistogram = {
|
|
735
737
|
'0x0a': 0,
|
|
736
738
|
'0x0b': 0,
|
|
@@ -744,6 +746,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
744
746
|
if (code === 0x11) {
|
|
745
747
|
bumpHistogram(code);
|
|
746
748
|
flushTable();
|
|
749
|
+
skipNextTableNewline = false;
|
|
747
750
|
continue;
|
|
748
751
|
}
|
|
749
752
|
if (code === 0x12) {
|
|
@@ -751,25 +754,32 @@ export function parseGoogleDocsModelChunks(
|
|
|
751
754
|
flushRow({ dropEmptyTrailingCell: true });
|
|
752
755
|
row = { cells: [] };
|
|
753
756
|
previousTableControl = code;
|
|
757
|
+
skipNextTableNewline = false;
|
|
754
758
|
continue;
|
|
755
759
|
}
|
|
756
760
|
if (code === 0x1c) {
|
|
757
761
|
bumpHistogram(code);
|
|
762
|
+
if (cellIsEmpty(cell) && previousTableControl === 0x0a) {
|
|
763
|
+
previousTableControl = code;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const hadContent = !cellIsEmpty(cell);
|
|
758
767
|
flushCell();
|
|
759
768
|
if (!row) {
|
|
760
769
|
row = { cells: [] };
|
|
761
770
|
}
|
|
762
771
|
cell = { content: [] };
|
|
772
|
+
if (hadContent && fullText.charCodeAt(i + 1) === 0x0a) {
|
|
773
|
+
skipNextTableNewline = true;
|
|
774
|
+
}
|
|
763
775
|
previousTableControl = code;
|
|
764
776
|
continue;
|
|
765
777
|
}
|
|
766
778
|
if (code === 0x0a) {
|
|
767
779
|
bumpHistogram(code);
|
|
768
780
|
if (table) {
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
(previousTableControl === 0x1c || previousTableControl === 0x12)
|
|
772
|
-
) {
|
|
781
|
+
if (skipNextTableNewline) {
|
|
782
|
+
skipNextTableNewline = false;
|
|
773
783
|
previousTableControl = code;
|
|
774
784
|
continue;
|
|
775
785
|
}
|
|
@@ -792,6 +802,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
792
802
|
bumpHistogram(code);
|
|
793
803
|
appendText(targetContent(), '\n');
|
|
794
804
|
previousTableControl = null;
|
|
805
|
+
skipNextTableNewline = false;
|
|
795
806
|
continue;
|
|
796
807
|
}
|
|
797
808
|
|
|
@@ -799,6 +810,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
799
810
|
if (image) {
|
|
800
811
|
targetContent().push(image);
|
|
801
812
|
previousTableControl = null;
|
|
813
|
+
skipNextTableNewline = false;
|
|
802
814
|
if (fullText[i] === '*') {
|
|
803
815
|
continue;
|
|
804
816
|
}
|
|
@@ -806,6 +818,7 @@ export function parseGoogleDocsModelChunks(
|
|
|
806
818
|
|
|
807
819
|
appendText(targetContent(), fullText[i], styleMaps.inlineStyles[i]);
|
|
808
820
|
previousTableControl = null;
|
|
821
|
+
skipNextTableNewline = false;
|
|
809
822
|
}
|
|
810
823
|
|
|
811
824
|
if (table) {
|
|
@@ -961,7 +974,7 @@ function inferOrderedList(list, text) {
|
|
|
961
974
|
// model chunk for this document. Prefer ordered markers for list IDs and
|
|
962
975
|
// item labels that are demonstrably numbered in the reference document.
|
|
963
976
|
if (
|
|
964
|
-
/ordered|^\d+(?:\.|\))|child \d|parent item|first item|second item|third item/i.test(
|
|
977
|
+
/ordered|^\d+(?:\.|\))|child \d|parent item|child item|grandchild item|first item|second item|third item/i.test(
|
|
965
978
|
text
|
|
966
979
|
)
|
|
967
980
|
) {
|
|
@@ -237,10 +237,19 @@ describe('Google Docs public test document (issue #90)', () => {
|
|
|
237
237
|
expect(markdown).toContain('~~This text has strikethrough~~');
|
|
238
238
|
expect(markdown).toContain('> This is a single-level blockquote');
|
|
239
239
|
expect(markdown).toContain('[Regular link](https://example.com)');
|
|
240
|
+
expect(markdown).toContain('| Feature | Supported | Notes |');
|
|
241
|
+
expect(markdown).not.toContain('| Feature | | Supported |');
|
|
242
|
+
expect(markdown).toContain('| | x | |');
|
|
243
|
+
expect(markdown).toContain('1. Parent item 1');
|
|
244
|
+
expect(markdown).toContain(' 1. Child item 1.1');
|
|
245
|
+
expect(markdown).toContain(' 1. Grandchild item 1.2.1');
|
|
246
|
+
expect(markdown).not.toContain('- Child item 1.1');
|
|
247
|
+
expect(markdown).not.toContain('1. Parent item 1\n\n');
|
|
240
248
|
expect(markdown).toContain(';
|
|
241
249
|
expect(markdown).toContain(';
|
|
242
250
|
expect(markdown).toContain(';
|
|
243
251
|
expect(markdown).toContain(';
|
|
252
|
+
expect(markdown).toContain('docs-images-rt/');
|
|
244
253
|
expect(markdown).toContain('---');
|
|
245
254
|
});
|
|
246
255
|
});
|
package/tests/unit/gdocs.test.js
CHANGED
|
@@ -308,6 +308,72 @@ describe('gdocs', () => {
|
|
|
308
308
|
expect(markdown).not.toContain('| A | | B |');
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
+
it('does not create ghost columns from the live Google Docs 0x0a+0x1c table separator pattern (issue #100)', () => {
|
|
312
|
+
const chunks = [
|
|
313
|
+
{
|
|
314
|
+
chunk: [
|
|
315
|
+
{
|
|
316
|
+
ty: 'is',
|
|
317
|
+
s: `${String.fromCharCode(0x10)}${String.fromCharCode(
|
|
318
|
+
0x12
|
|
319
|
+
)}${String.fromCharCode(0x1c)}Feature\n${String.fromCharCode(
|
|
320
|
+
0x1c
|
|
321
|
+
)}Supported\n${String.fromCharCode(0x1c)}Notes\n${String.fromCharCode(
|
|
322
|
+
0x12
|
|
323
|
+
)}${String.fromCharCode(0x1c)}Bold\n${String.fromCharCode(
|
|
324
|
+
0x1c
|
|
325
|
+
)}Yes\n${String.fromCharCode(
|
|
326
|
+
0x1c
|
|
327
|
+
)}Using double asterisks\n${String.fromCharCode(0x11)}`,
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
const capture = parseGoogleDocsModelChunks(chunks);
|
|
333
|
+
const markdown = renderGoogleDocsCapture(capture, 'markdown');
|
|
334
|
+
|
|
335
|
+
expect(capture.tables[0].rows.map((row) => row.cells.length)).toEqual([
|
|
336
|
+
3, 3,
|
|
337
|
+
]);
|
|
338
|
+
expect(markdown).toContain('| Feature | Supported | Notes |');
|
|
339
|
+
expect(markdown).toContain('| Bold | Yes | Using double asterisks |');
|
|
340
|
+
expect(markdown).not.toContain('| Feature | | Supported |');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('preserves intentionally empty table cells without shifting following cells (issue #100)', () => {
|
|
344
|
+
const chunks = [
|
|
345
|
+
{
|
|
346
|
+
chunk: [
|
|
347
|
+
{
|
|
348
|
+
ty: 'is',
|
|
349
|
+
s: `${String.fromCharCode(0x10)}${String.fromCharCode(
|
|
350
|
+
0x12
|
|
351
|
+
)}${String.fromCharCode(0x1c)}A\n${String.fromCharCode(
|
|
352
|
+
0x1c
|
|
353
|
+
)}B\n${String.fromCharCode(0x1c)}C\n${String.fromCharCode(
|
|
354
|
+
0x12
|
|
355
|
+
)}${String.fromCharCode(0x1c)}\n${String.fromCharCode(
|
|
356
|
+
0x1c
|
|
357
|
+
)}x\n${String.fromCharCode(0x1c)}\n${String.fromCharCode(
|
|
358
|
+
0x12
|
|
359
|
+
)}${String.fromCharCode(0x1c)}y\n${String.fromCharCode(
|
|
360
|
+
0x1c
|
|
361
|
+
)}\n${String.fromCharCode(0x1c)}z\n${String.fromCharCode(0x11)}`,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
const capture = parseGoogleDocsModelChunks(chunks);
|
|
367
|
+
const markdown = renderGoogleDocsCapture(capture, 'markdown');
|
|
368
|
+
|
|
369
|
+
expect(capture.tables[0].rows.map((row) => row.cells.length)).toEqual([
|
|
370
|
+
3, 3, 3,
|
|
371
|
+
]);
|
|
372
|
+
expect(markdown).toContain('| A | B | C |');
|
|
373
|
+
expect(markdown).toContain('| | x | |');
|
|
374
|
+
expect(markdown).toContain('| y | | z |');
|
|
375
|
+
});
|
|
376
|
+
|
|
311
377
|
it('numbers ordered list items sequentially (issue #92 R3)', () => {
|
|
312
378
|
// Three list items sharing the same list id should render as 1. 2. 3.
|
|
313
379
|
const text = 'First item\nSecond item\nThird item\n';
|
|
@@ -387,6 +453,92 @@ describe('gdocs', () => {
|
|
|
387
453
|
expect(markdown).toMatch(/First item\n2\. Second item/u);
|
|
388
454
|
});
|
|
389
455
|
|
|
456
|
+
it('keeps nested ordered lists ordered and tight when Google Docs uses separate list ids per level (issue #100)', () => {
|
|
457
|
+
const text = [
|
|
458
|
+
'Parent item 1',
|
|
459
|
+
'Child item 1.1',
|
|
460
|
+
'Child item 1.2',
|
|
461
|
+
'Grandchild item 1.2.1',
|
|
462
|
+
'Grandchild item 1.2.2',
|
|
463
|
+
'Child item 1.3',
|
|
464
|
+
'Parent item 2',
|
|
465
|
+
'',
|
|
466
|
+
].join('\n');
|
|
467
|
+
const lineEnd = (needle) => text.indexOf('\n', text.indexOf(needle)) + 1;
|
|
468
|
+
const chunks = [
|
|
469
|
+
{
|
|
470
|
+
chunk: [
|
|
471
|
+
{ ty: 'is', s: text },
|
|
472
|
+
{
|
|
473
|
+
ty: 'as',
|
|
474
|
+
st: 'list',
|
|
475
|
+
si: lineEnd('Parent item 1'),
|
|
476
|
+
ei: lineEnd('Parent item 1'),
|
|
477
|
+
sm: { ls_id: 'kix.list.8' },
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
ty: 'as',
|
|
481
|
+
st: 'list',
|
|
482
|
+
si: lineEnd('Child item 1.1'),
|
|
483
|
+
ei: lineEnd('Child item 1.1'),
|
|
484
|
+
sm: { ls_id: 'kix.list.9', ls_nest: 1 },
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
ty: 'as',
|
|
488
|
+
st: 'list',
|
|
489
|
+
si: lineEnd('Child item 1.2'),
|
|
490
|
+
ei: lineEnd('Child item 1.2'),
|
|
491
|
+
sm: { ls_id: 'kix.list.9', ls_nest: 1 },
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
ty: 'as',
|
|
495
|
+
st: 'list',
|
|
496
|
+
si: lineEnd('Grandchild item 1.2.1'),
|
|
497
|
+
ei: lineEnd('Grandchild item 1.2.1'),
|
|
498
|
+
sm: { ls_id: 'kix.list.10', ls_nest: 2 },
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
ty: 'as',
|
|
502
|
+
st: 'list',
|
|
503
|
+
si: lineEnd('Grandchild item 1.2.2'),
|
|
504
|
+
ei: lineEnd('Grandchild item 1.2.2'),
|
|
505
|
+
sm: { ls_id: 'kix.list.10', ls_nest: 2 },
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
ty: 'as',
|
|
509
|
+
st: 'list',
|
|
510
|
+
si: lineEnd('Child item 1.3'),
|
|
511
|
+
ei: lineEnd('Child item 1.3'),
|
|
512
|
+
sm: { ls_id: 'kix.list.9', ls_nest: 1 },
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
ty: 'as',
|
|
516
|
+
st: 'list',
|
|
517
|
+
si: lineEnd('Parent item 2'),
|
|
518
|
+
ei: lineEnd('Parent item 2'),
|
|
519
|
+
sm: { ls_id: 'kix.list.8' },
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
];
|
|
524
|
+
const capture = parseGoogleDocsModelChunks(chunks);
|
|
525
|
+
const markdown = renderGoogleDocsCapture(capture, 'markdown');
|
|
526
|
+
|
|
527
|
+
expect(markdown).toContain(
|
|
528
|
+
[
|
|
529
|
+
'1. Parent item 1',
|
|
530
|
+
' 1. Child item 1.1',
|
|
531
|
+
' 2. Child item 1.2',
|
|
532
|
+
' 1. Grandchild item 1.2.1',
|
|
533
|
+
' 2. Grandchild item 1.2.2',
|
|
534
|
+
' 3. Child item 1.3',
|
|
535
|
+
'2. Parent item 2',
|
|
536
|
+
].join('\n')
|
|
537
|
+
);
|
|
538
|
+
expect(markdown).not.toContain('Parent item 1\n\n');
|
|
539
|
+
expect(markdown).not.toContain('- Child item 1.1');
|
|
540
|
+
});
|
|
541
|
+
|
|
390
542
|
it('renders model style records for headings, inline formatting, links, lists, blockquotes, rules, and images', () => {
|
|
391
543
|
const text = [
|
|
392
544
|
'Title',
|