diff --git a/src/CsvEditorProvider.ts b/src/CsvEditorProvider.ts index 9135ca8..0fb74ce 100644 --- a/src/CsvEditorProvider.ts +++ b/src/CsvEditorProvider.ts @@ -1999,7 +1999,7 @@ class CsvEditorController { private isAllowedExternalScheme(scheme: string): boolean { const normalized = scheme.toLowerCase(); - return normalized === 'http' || normalized === 'https' || normalized === 'ftp' || normalized === 'mailto'; + return normalized === 'http' || normalized === 'https' || normalized === 'ftp' || normalized === 'mailto' || normalized === 'file'; } private isAllowedExternalUrl(rawUrl: unknown): rawUrl is string { @@ -2029,8 +2029,8 @@ class CsvEditorController { private linkifyUrls(escapedText: string): string { // Match URLs in already-escaped text (handles & in query strings). - // Supports http, https, ftp, mailto, and www.*.* (Google Sheets-like behavior). - const urlPattern = /\b(?:(?:https?:\/\/|ftp:\/\/|mailto:)[^\s<>&"']+(?:&[^\s<>&"']+)*|www\.[^\s<>&"']+\.[^\s<>&"']+)/gi; + // Supports http, https, ftp, file, mailto, and www.*.* (Google Sheets-like behavior). + const urlPattern = /\b(?:(?:https?:\/\/|ftp:\/\/|file:\/\/|mailto:)[^\s<>&"']+(?:&[^\s<>&"']+)*|www\.[^\s<>&"']+\.[^\s<>&"']+)/gi; return escapedText.replace(urlPattern, (rawMatch) => { let matched = rawMatch; let trailing = ''; diff --git a/src/test/provider-utils.test.ts b/src/test/provider-utils.test.ts index d968cc1..8976249 100644 --- a/src/test/provider-utils.test.ts +++ b/src/test/provider-utils.test.ts @@ -46,6 +46,14 @@ describe('CsvEditorProvider utility methods', () => { assert.ok(html.includes('>www.google.com,')); }); + it('formatCellContent linkifies file:/// URLs when enabled', () => { + const format = CsvEditorProvider.__test.formatCellContent; + const html = format('Open file:///tmp/example.csv and continue.', true); + assert.ok(html.includes('class="csv-link"')); + assert.ok(html.includes('data-href="file:///tmp/example.csv"')); + assert.ok(html.includes('>file:///tmp/example.csv')); + }); + it('formatCellContent leaves URLs as plain text when linkify is disabled', () => { const format = CsvEditorProvider.__test.formatCellContent; const html = format('https://example.com?a=1&b=2', false); @@ -66,9 +74,9 @@ describe('CsvEditorProvider utility methods', () => { assert.strictEqual(allowed('http://example.com'), true); assert.strictEqual(allowed('ftp://example.com/file.txt'), true); assert.strictEqual(allowed('mailto:user@example.com'), true); + assert.strictEqual(allowed('file:///tmp/x.csv'), true); assert.strictEqual(allowed('javascript:alert(1)'), false); assert.strictEqual(allowed('data:text/plain,hello'), false); - assert.strictEqual(allowed('file:///tmp/x.csv'), false); assert.strictEqual(allowed(''), false); });