feat: 实现文件夹和书签的持久排序与拖拽功能

This commit is contained in:
2026-01-18 23:33:31 +08:00
parent 6eb3c730bb
commit dbeb181e5d
49 changed files with 3141 additions and 507 deletions

View File

@@ -14,22 +14,58 @@ export function parseNetscapeBookmarkHtml(html) {
const bookmarks = [];
let folderIdSeq = 1;
function walkDl(dl, parentFolderId) {
const children = Array.from(dl.children);
for (const node of children) {
if (node.tagName?.toLowerCase() !== "dt") continue;
function normText(s) {
return String(s || "").replace(/\s+/g, " ").trim();
}
const h3 = node.querySelector(":scope > h3");
const a = node.querySelector(":scope > a");
const nextDl = node.nextElementSibling?.tagName?.toLowerCase() === "dl" ? node.nextElementSibling : null;
// Collect <DT> nodes that belong to the current <DL> level.
// Chrome/Edge exported HTML often uses `<DL><p>` and browsers may wrap
// subsequent nodes under <p> or other wrapper elements.
function collectLevelDt(container) {
const out = [];
const els = Array.from(container.children || []);
for (const el of els) {
const tag = el.tagName?.toLowerCase();
if (!tag) continue;
if (tag === "dt") {
out.push(el);
continue;
}
if (tag === "dl") {
// nested list belongs to the previous <DT>
continue;
}
out.push(...collectLevelDt(el));
}
return out;
}
// Find the nested <DL> that belongs to a <DT>, even if <DT> is wrapped (e.g. inside <p>).
function findNextDlForDt(dt, stopDl) {
let cur = dt;
while (cur && cur !== stopDl) {
const next = cur.nextElementSibling;
if (next && next.tagName?.toLowerCase() === "dl") return next;
cur = cur.parentElement;
}
return null;
}
function walkDl(dl, parentFolderId) {
const dts = collectLevelDt(dl);
for (const node of dts) {
const h3 = node.querySelector("h3");
const a = node.querySelector("a");
const nestedDl = node.querySelector("dl");
const nextDl = nestedDl || findNextDlForDt(node, dl);
if (h3) {
const id = String(folderIdSeq++);
const name = (h3.textContent || "").trim();
const name = normText(h3.textContent || "");
folders.push({ id, parentFolderId: parentFolderId ?? null, name });
if (nextDl) walkDl(nextDl, id);
} else if (a) {
const title = (a.textContent || "").trim();
const title = normText(a.textContent || "");
const url = a.getAttribute("href") || "";
bookmarks.push({ parentFolderId: parentFolderId ?? null, title, url });
}