Skip to content

Commit 35e7b34

Browse files
committed
feat: sheet parser for drop-paste feature
1 parent 7b8e275 commit 35e7b34

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

editors/vscode/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,7 @@
11551155
"test": "rimraf test-dist/ && tsc -p tsconfig.test.json && node test-dist/test/runTests.js"
11561156
},
11571157
"dependencies": {
1158+
"cheerio": "^1.0.0",
11581159
"cpr": "^3.0.1",
11591160
"esbuild-plugin-polyfill-node": "^0.3.0",
11601161
"node-fetch": "^3.3.2",

editors/vscode/src/features/drop-paste.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
typstPasteUriEditKind,
1616
Schemes,
1717
} from "./drop-paste.def";
18+
import { convertHtmlToTypst } from "./html-table-converter";
1819

1920
export function dragAndDropActivate(context: vscode.ExtensionContext) {
2021
context.subscriptions.push(
@@ -58,7 +59,7 @@ class DropOrPasteContext<A extends DropPasteAction> {
5859
private context: vscode.DocumentPasteEditContext | undefined,
5960
private document: vscode.TextDocument,
6061
private token: vscode.CancellationToken,
61-
) {}
62+
) { }
6263

6364
private readonly _yieldTo = [
6465
vscode.DocumentDropOrPasteEditKind.Text,
@@ -90,6 +91,19 @@ class DropOrPasteContext<A extends DropPasteAction> {
9091
dataTransfer: vscode.DataTransfer,
9192
): Promise<boolean> {
9293
{
94+
const htmlData = await dataTransfer.get("text/html")?.asString();
95+
if (htmlData) {
96+
const snippet = await convertHtmlToTypst(htmlData);
97+
const additionalEdits = new vscode.WorkspaceEdit();
98+
additionalEdits.replace(this.document.uri, ranges[0], snippet);
99+
this.resolved.push({
100+
snippet: new vscode.SnippetString(""),
101+
additionalEdits,
102+
yieldTo: [],
103+
});
104+
return this.wrapRangeAsLinkContent();
105+
}
106+
93107
const mediaFiles = await this.takeMediaFiles(dataTransfer);
94108
if (mediaFiles) {
95109
const edit = await this.handleMediaFiles(ranges, mediaFiles);
@@ -595,7 +609,7 @@ export class UriList {
595609

596610
constructor(
597611
public readonly entries: ReadonlyArray<{ readonly uri: vscode.Uri; readonly str: string }>,
598-
) {}
612+
) { }
599613
}
600614

601615
function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as cheerio from 'cheerio';
2+
3+
export async function convertHtmlToTypst(html: string): Promise<string> {
4+
const $ = cheerio.load(html);
5+
const table = $('table').first();
6+
if (!table.length) {
7+
throw new Error('Table element not found. Please check the HTML content.');
8+
}
9+
10+
const rows = table.find('tr');
11+
if (!rows.length) {
12+
throw new Error('No tr tag found in the table.');
13+
}
14+
15+
const firstRow = rows.first();
16+
const firstRowCells = firstRow.find('td, th');
17+
const columnCount = firstRowCells.toArray().reduce((count, cell) => {
18+
const colspanAttr = $(cell).attr('colspan');
19+
return count + (Number(colspanAttr) || 1);
20+
}, 0);
21+
22+
let out = `#table(\n columns: ${columnCount},\n`;
23+
24+
rows.each((_, rowElem) => {
25+
out += ' ';
26+
const row = $(rowElem);
27+
const cells = row.find('td, th');
28+
cells.each((_, cellElem) => {
29+
const cell = $(cellElem);
30+
const rowspan = Number(cell.attr('rowspan')) || 1;
31+
const colspan = Number(cell.attr('colspan')) || 1;
32+
const spanOpts =
33+
`${rowspan > 1 ? `rowspan: ${rowspan}, ` : ''}${colspan > 1 ? `colspan: ${colspan}, ` : ''}`;
34+
const content = cell.text().trim();
35+
out += spanOpts ? `table.cell(${spanOpts})[${content}], ` : `[${content}], `;
36+
});
37+
out += '\n';
38+
});
39+
40+
return out + ')';
41+
}

yarn.lock

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,7 @@ cheerio-select@^2.1.0:
12021202
domhandler "^5.0.3"
12031203
domutils "^3.0.1"
12041204

1205-
cheerio@^1.0.0-rc.9:
1205+
cheerio@^1.0.0, cheerio@^1.0.0-rc.9:
12061206
version "1.0.0"
12071207
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0.tgz#1ede4895a82f26e8af71009f961a9b8cb60d6a81"
12081208
integrity sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==
@@ -3675,8 +3675,16 @@ stoppable@^1.1.0:
36753675
resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b"
36763676
integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==
36773677

3678-
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
3679-
name string-width-cjs
3678+
"string-width-cjs@npm:string-width@^4.2.0":
3679+
version "4.2.3"
3680+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
3681+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
3682+
dependencies:
3683+
emoji-regex "^8.0.0"
3684+
is-fullwidth-code-point "^3.0.0"
3685+
strip-ansi "^6.0.1"
3686+
3687+
string-width@^4.1.0, string-width@^4.2.0:
36803688
version "4.2.3"
36813689
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
36823690
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -3745,8 +3753,14 @@ string_decoder@~1.1.1:
37453753
dependencies:
37463754
safe-buffer "~5.1.0"
37473755

3748-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
3749-
name strip-ansi-cjs
3756+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
3757+
version "6.0.1"
3758+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
3759+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
3760+
dependencies:
3761+
ansi-regex "^5.0.1"
3762+
3763+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
37503764
version "6.0.1"
37513765
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
37523766
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4224,8 +4238,16 @@ workerpool@^6.5.1:
42244238
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
42254239
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
42264240

4227-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
4228-
name wrap-ansi-cjs
4241+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
4242+
version "7.0.0"
4243+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
4244+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
4245+
dependencies:
4246+
ansi-styles "^4.0.0"
4247+
string-width "^4.1.0"
4248+
strip-ansi "^6.0.0"
4249+
4250+
wrap-ansi@^7.0.0:
42294251
version "7.0.0"
42304252
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
42314253
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

0 commit comments

Comments
 (0)