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

@@ -8,26 +8,58 @@ export function parseNetscapeBookmarkHtmlNode(html) {
const folders = [];
const bookmarks = [];
function walkDl($dl, parentTempId) {
// Netscape format: <DL><p> contains repeating <DT> items and nested <DL>
const children = $dl.children().toArray();
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (!node || node.tagName?.toLowerCase() !== "dt") continue;
function normText(s) {
return String(s || "").replace(/\s+/g, " ").trim();
}
function collectLevelDt(node) {
const out = [];
const children = $(node).contents().toArray();
for (const child of children) {
if (!child || child.type !== "tag") continue;
const tag = child.tagName?.toLowerCase();
if (tag === "dt") {
out.push(child);
continue;
}
if (tag === "dl") {
// nested list belongs to the previous <DT>
continue;
}
out.push(...collectLevelDt(child));
}
return out;
}
function findNextDlForDt(dtNode, stopDlNode) {
let cur = dtNode;
while (cur && cur !== stopDlNode) {
let next = cur.nextSibling;
while (next && next.type !== "tag") next = next.nextSibling;
if (next && next.type === "tag" && next.tagName?.toLowerCase() === "dl") return $(next);
cur = cur.parent;
}
return null;
}
function walkDl($dl, parentTempId) {
// Netscape format: <DL><p> contains repeating <DT> items and nested <DL>.
// When parsed, <DT> may be wrapped (e.g. inside <p>), so we must be robust.
const dts = collectLevelDt($dl[0]);
for (const node of dts) {
const $dt = $(node);
const $h3 = $dt.children("h3").first();
const $a = $dt.children("a").first();
const $next = $(children[i + 1] || null);
const nextIsDl = $next && $next[0]?.tagName?.toLowerCase() === "dl";
const $h3 = $dt.children("h3").first().length ? $dt.children("h3").first() : $dt.find("h3").first();
const $a = $dt.children("a").first().length ? $dt.children("a").first() : $dt.find("a").first();
const $nestedDl = $dt.children("dl").first();
const $nextDl = $nestedDl.length ? $nestedDl : findNextDlForDt(node, $dl[0]);
if ($h3.length) {
const tempId = `${folders.length + 1}`;
const name = ($h3.text() || "").trim();
const name = normText($h3.text() || "");
folders.push({ tempId, parentTempId: parentTempId ?? null, name });
if (nextIsDl) walkDl($next, tempId);
if ($nextDl?.length) walkDl($nextDl, tempId);
} else if ($a.length) {
const title = ($a.text() || "").trim();
const title = normText($a.text() || "");
const url = $a.attr("href") || "";
bookmarks.push({ parentTempId: parentTempId ?? null, title, url });
}