@link-assistant/web-capture 1.7.15 → 1.7.16

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.16
4
+
5
+ ### Patch Changes
6
+
7
+ - bf7eaec: Keep Google Docs browser-model soft line breaks outside inline mark tags and preserve image dimensions in HTML output.
8
+
3
9
  ## 1.7.15
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.15",
3
+ "version": "1.7.16",
4
4
  "description": "CLI and microservice to render web pages as HTML, Markdown, or PNG",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -138,9 +138,27 @@ function renderTextRunsMarkdown(nodes = []) {
138
138
  italic: Boolean(node.italic),
139
139
  strike: Boolean(node.strike),
140
140
  };
141
- output += markdownStyleTransition(active, next);
142
- output += node.text || '';
143
- active = next;
141
+ const text = node.text || '';
142
+ let start = 0;
143
+ for (let idx = 0; idx < text.length; idx++) {
144
+ if (text[idx] !== '\n') {
145
+ continue;
146
+ }
147
+ if (idx > start) {
148
+ output += markdownStyleTransition(active, next);
149
+ output += text.slice(start, idx);
150
+ active = next;
151
+ }
152
+ output += markdownStyleTransition(active, inactive);
153
+ output += '\n';
154
+ active = inactive;
155
+ start = idx + 1;
156
+ }
157
+ if (start < text.length) {
158
+ output += markdownStyleTransition(active, next);
159
+ output += text.slice(start);
160
+ active = next;
161
+ }
144
162
  }
145
163
  output += markdownStyleTransition(active, inactive);
146
164
  return output;
package/src/gdocs.js CHANGED
@@ -1219,7 +1219,17 @@ function renderContentHtml(content = []) {
1219
1219
  }
1220
1220
 
1221
1221
  function renderMarkedHtml(node) {
1222
- let output = escapeHtml(node.text || '').replace(/\n/g, '<br>');
1222
+ return String(node.text || '')
1223
+ .split('\n')
1224
+ .map((segment) => renderMarkedHtmlSegment(node, segment))
1225
+ .join('<br>');
1226
+ }
1227
+
1228
+ function renderMarkedHtmlSegment(node, text) {
1229
+ if (!text) {
1230
+ return '';
1231
+ }
1232
+ let output = escapeHtml(text);
1223
1233
  if (node.bold) {
1224
1234
  output = `<strong>${output}</strong>`;
1225
1235
  }
@@ -26,10 +26,26 @@ import {
26
26
  import fs from 'fs';
27
27
  import os from 'os';
28
28
  import path from 'path';
29
+ import { fileURLToPath } from 'url';
29
30
  import nock from 'nock';
30
31
 
31
32
  jest.setTimeout(30000);
32
33
 
34
+ const __filename = fileURLToPath(import.meta.url);
35
+ const __dirname = path.dirname(__filename);
36
+ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
37
+ const ISSUE_104_FIXTURE_DIR = path.join(
38
+ REPO_ROOT,
39
+ 'docs',
40
+ 'case-studies',
41
+ 'issue-104',
42
+ 'fixtures'
43
+ );
44
+
45
+ function readIssue104Fixture(filename) {
46
+ return fs.readFileSync(path.join(ISSUE_104_FIXTURE_DIR, filename), 'utf-8');
47
+ }
48
+
33
49
  describe('gdocs', () => {
34
50
  describe('isGoogleDocsUrl', () => {
35
51
  it('returns true for standard Google Docs edit URL', () => {
@@ -648,6 +664,74 @@ describe('gdocs', () => {
648
664
  );
649
665
  });
650
666
 
667
+ it('renders soft breaks outside marks and preserves image dimensions (issue #104)', () => {
668
+ const fixture = JSON.parse(
669
+ readIssue104Fixture('multiline-marked-inline-image-model.json')
670
+ );
671
+ const capture = parseGoogleDocsModelChunks(
672
+ fixture.chunks,
673
+ fixture.cidUrlMap
674
+ );
675
+
676
+ expect(capture.blocks[0].content).toEqual([
677
+ expect.objectContaining({
678
+ type: 'text',
679
+ text: 'Line one of bold text.',
680
+ bold: true,
681
+ }),
682
+ expect.objectContaining({ type: 'text', text: '\n', bold: false }),
683
+ expect.objectContaining({
684
+ type: 'text',
685
+ text: 'Line two of bold text.',
686
+ bold: true,
687
+ }),
688
+ expect.objectContaining({
689
+ type: 'image',
690
+ alt: 'Inline diagram',
691
+ width: 320,
692
+ height: 180,
693
+ }),
694
+ expect.objectContaining({ type: 'text', text: '\n\n', bold: false }),
695
+ expect.objectContaining({
696
+ type: 'text',
697
+ text: 'Line three of bold text.',
698
+ bold: true,
699
+ }),
700
+ ]);
701
+ expect(renderGoogleDocsCapture(capture, 'html')).toBe(
702
+ readIssue104Fixture(
703
+ 'multiline-marked-inline-image.expected.html'
704
+ ).trimEnd()
705
+ );
706
+ expect(renderGoogleDocsCapture(capture, 'markdown')).toBe(
707
+ readIssue104Fixture('multiline-marked-inline-image.expected.md')
708
+ );
709
+ });
710
+
711
+ it('does not keep inline marks open across embedded newlines (issue #104)', () => {
712
+ const capture = {
713
+ blocks: [
714
+ {
715
+ type: 'paragraph',
716
+ content: [
717
+ {
718
+ type: 'text',
719
+ text: 'Alpha\nBeta',
720
+ bold: true,
721
+ },
722
+ ],
723
+ },
724
+ ],
725
+ };
726
+
727
+ expect(renderGoogleDocsCapture(capture, 'html')).toBe(
728
+ '<!doctype html><html><body><p><strong>Alpha</strong><br><strong>Beta</strong></p></body></html>'
729
+ );
730
+ expect(renderGoogleDocsCapture(capture, 'markdown')).toBe(
731
+ '**Alpha**\n**Beta**\n'
732
+ );
733
+ });
734
+
651
735
  it('keeps nested bold and italic markers balanced (issue #96)', () => {
652
736
  const text = 'Bold text with italic inside and back to bold\n';
653
737
  const chunks = [