Skip to content

Commit 72fec8a

Browse files
committed
feat(export): 支持HTML表格转换为Markdown格式 #289
1 parent 42e215f commit 72fec8a

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
- style(ui): 优化手机端显示问题 #288
1+
- feat(export): 支持HTML表格转换为Markdown格式 #289

entrypoints/SettingComponents/BasicSettings/MenuExportArticle.vue

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,92 @@ export default {
302302
if (node.tagName === "IMG") {
303303
return `![${node.alt}](${node.src})`;
304304
}
305+
// 处理表格
306+
if (node.tagName === "TABLE") {
307+
const rows = [];
308+
const headerRow = [];
309+
let hasHeader = false;
310+
311+
// 查找表头
312+
const thead = node.querySelector("thead");
313+
if (thead) {
314+
const thCells = thead.querySelectorAll("th");
315+
thCells.forEach((th) => {
316+
headerRow.push(html2md_children(th).trim().replace(/\|/g, "\\|").replace(/\n/g, " "));
317+
});
318+
if (headerRow.length > 0) hasHeader = true;
319+
}
320+
321+
// 如果没有 thead,尝试从第一行获取表头
322+
if (!hasHeader) {
323+
const firstRow = node.querySelector("tr");
324+
if (firstRow) {
325+
const thCells = firstRow.querySelectorAll("th");
326+
if (thCells.length > 0) {
327+
thCells.forEach((th) => {
328+
headerRow.push(html2md_children(th).trim().replace(/\|/g, "\\|").replace(/\n/g, " "));
329+
});
330+
hasHeader = true;
331+
}
332+
}
333+
}
334+
335+
// 获取所有数据行
336+
const allRows = node.querySelectorAll("tr");
337+
let startIndex = 0;
338+
339+
// 如果有表头但没有 thead,跳过第一行
340+
if (hasHeader && !thead) {
341+
startIndex = 1;
342+
}
343+
// 如果有 thead,从 tbody 开始处理
344+
if (thead) {
345+
const tbody = node.querySelector("tbody");
346+
if (tbody) {
347+
const tbodyRows = tbody.querySelectorAll("tr");
348+
tbodyRows.forEach((tr) => {
349+
const cells = [];
350+
tr.querySelectorAll("td, th").forEach((cell) => {
351+
cells.push(html2md_children(cell).trim().replace(/\|/g, "\\|").replace(/\n/g, " "));
352+
});
353+
if (cells.length > 0) rows.push(cells);
354+
});
355+
}
356+
} else {
357+
for (let i = startIndex; i < allRows.length; i++) {
358+
const tr = allRows[i];
359+
const cells = [];
360+
tr.querySelectorAll("td, th").forEach((cell) => {
361+
cells.push(html2md_children(cell).trim().replace(/\|/g, "\\|").replace(/\n/g, " "));
362+
});
363+
if (cells.length > 0) rows.push(cells);
364+
}
365+
}
366+
367+
// 如果没有表头,使用第一行作为表头
368+
if (!hasHeader && rows.length > 0) {
369+
headerRow.push(...rows.shift());
370+
}
371+
372+
// 如果仍然没有表头,返回空
373+
if (headerRow.length === 0) return "";
374+
375+
// 构建 Markdown 表格
376+
let tableText = "\n";
377+
tableText += "| " + headerRow.join(" | ") + " |\n";
378+
tableText += "| " + headerRow.map(() => "---").join(" | ") + " |\n";
379+
rows.forEach((row) => {
380+
// 确保每行的列数与表头一致
381+
while (row.length < headerRow.length) row.push("");
382+
tableText += "| " + row.slice(0, headerRow.length).join(" | ") + " |\n";
383+
});
384+
385+
return tableText;
386+
}
387+
// 跳过表格内部元素的单独处理(已在 TABLE 中处理)
388+
if (["THEAD", "TBODY", "TR", "TH", "TD"].includes(node.tagName)) {
389+
return "";
390+
}
305391
if (node.tagName === "STRONG" || node.tagName === "B") {
306392
return `**${html2md_children(node)}**`;
307393
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "linuxdo-scripts",
33
"description": "manifest.json description",
44
"private": true,
5-
"version": "1.5.14",
5+
"version": "1.5.15",
66
"type": "module",
77
"scripts": {
88
"dev": "wxt",

0 commit comments

Comments
 (0)