fix: trim zotero import modal shell
This commit is contained in:
parent
6a2250ef1a
commit
4db4632ac8
@ -141,67 +141,6 @@
|
||||
border-radius: 16px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.tree-node {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.tree-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.tree-toggle {
|
||||
width: 1.6rem;
|
||||
min-width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
padding: 0;
|
||||
border-radius: 10px;
|
||||
background: rgba(44, 110, 73, 0.08);
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
.tree-label {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 0.55rem 0.7rem;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
color: var(--ink);
|
||||
}
|
||||
.tree-label.active {
|
||||
border-color: rgba(44, 110, 73, 0.22);
|
||||
background: rgba(44, 110, 73, 0.12);
|
||||
}
|
||||
.tree-children {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
margin-left: 1rem;
|
||||
padding-left: 0.6rem;
|
||||
border-left: 1px dashed var(--line);
|
||||
}
|
||||
.collection-item {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
}
|
||||
.checkbox-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.checkbox-row input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
.action-bar {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
padding-top: 0.4rem;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.modal-shell[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@ -242,6 +181,10 @@
|
||||
.modal-action-bar {
|
||||
border-top: 1px solid var(--line);
|
||||
border-bottom: 0;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
background: var(--surface);
|
||||
}
|
||||
.modal-header-actions {
|
||||
display: flex;
|
||||
@ -439,25 +382,25 @@
|
||||
projects: [],
|
||||
currentProjectId: null,
|
||||
currentCards: [],
|
||||
collectionTree: [],
|
||||
isImportModalOpen: false,
|
||||
selectedCollectionKey: null,
|
||||
expandedCollectionKeys: new Set(),
|
||||
selectedItemKeys: new Set(),
|
||||
visibleCollectionItems: [],
|
||||
};
|
||||
|
||||
const elements = {
|
||||
projectForm: document.getElementById("create-project-form"),
|
||||
projectStatus: document.getElementById("project-status"),
|
||||
projectList: document.getElementById("project-list"),
|
||||
collectionStatus: document.getElementById("collection-status"),
|
||||
collectionTree: document.getElementById("collection-tree"),
|
||||
collectionItems: document.getElementById("collection-items"),
|
||||
selectedCount: document.getElementById("selected-count"),
|
||||
selectDescendantsButton: document.getElementById("select-descendants-button"),
|
||||
clearSelectionButton: document.getElementById("clear-selection-button"),
|
||||
importSelectedItemsButton: document.getElementById("import-selected-items-button"),
|
||||
importModal: document.getElementById("import-modal"),
|
||||
importModalOverlay: document.getElementById("import-modal-overlay"),
|
||||
openImportModalButton: document.getElementById("open-import-modal-button"),
|
||||
closeImportModalButton: document.getElementById("close-import-modal-button"),
|
||||
importModalProjectLabel: document.getElementById("import-modal-project-label"),
|
||||
modalSelectedCount: document.getElementById("modal-selected-count"),
|
||||
modalCollectionTree: document.getElementById("modal-collection-tree"),
|
||||
modalCollectionItems: document.getElementById("modal-collection-items"),
|
||||
modalSelectDescendantsButton: document.getElementById("modal-select-descendants-button"),
|
||||
modalClearSelectionButton: document.getElementById("modal-clear-selection-button"),
|
||||
modalImportSelectedItemsButton: document.getElementById("modal-import-selected-items-button"),
|
||||
previewPopover: document.getElementById("item-preview-popover"),
|
||||
currentProjectLabel: document.getElementById("current-project-label"),
|
||||
cardList: document.getElementById("card-list"),
|
||||
cardDetail: document.getElementById("card-detail"),
|
||||
@ -589,112 +532,6 @@
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function renderCollectionTree() {
|
||||
if (!state.collectionTree.length) {
|
||||
elements.collectionTree.innerHTML = '<div class="empty">没有读取到 Zotero collections。</div>';
|
||||
return;
|
||||
}
|
||||
elements.collectionTree.innerHTML = "";
|
||||
for (const node of state.collectionTree) {
|
||||
elements.collectionTree.appendChild(renderCollectionNode(node));
|
||||
}
|
||||
}
|
||||
|
||||
function renderCollectionNode(node) {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "tree-node";
|
||||
|
||||
const row = document.createElement("div");
|
||||
row.className = "tree-row";
|
||||
|
||||
const toggle = document.createElement("button");
|
||||
toggle.type = "button";
|
||||
toggle.className = "tree-toggle";
|
||||
toggle.textContent = node.children && node.children.length
|
||||
? (state.expandedCollectionKeys.has(node.collection_key) ? "−" : "+")
|
||||
: "·";
|
||||
toggle.disabled = !(node.children && node.children.length);
|
||||
toggle.addEventListener("click", () => toggleCollectionExpanded(node.collection_key));
|
||||
|
||||
const label = document.createElement("button");
|
||||
label.type = "button";
|
||||
label.className = "tree-label" + (state.selectedCollectionKey === node.collection_key ? " active" : "");
|
||||
label.innerHTML = `
|
||||
<strong>${node.name}</strong>
|
||||
<div class="meta">${node.descendant_item_count} 篇</div>
|
||||
`;
|
||||
label.addEventListener("click", () => selectCollection(node.collection_key));
|
||||
|
||||
row.appendChild(toggle);
|
||||
row.appendChild(label);
|
||||
wrapper.appendChild(row);
|
||||
|
||||
if (node.children && node.children.length && state.expandedCollectionKeys.has(node.collection_key)) {
|
||||
const children = document.createElement("div");
|
||||
children.className = "tree-children";
|
||||
for (const child of node.children) {
|
||||
children.appendChild(renderCollectionNode(child));
|
||||
}
|
||||
wrapper.appendChild(children);
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function renderCollectionItems() {
|
||||
renderSelectedCount();
|
||||
if (!state.selectedCollectionKey) {
|
||||
elements.collectionItems.innerHTML = '<div class="empty">先点击一个 collection。</div>';
|
||||
return;
|
||||
}
|
||||
if (!state.visibleCollectionItems.length) {
|
||||
elements.collectionItems.innerHTML = '<div class="empty">当前目录及子目录下没有可导入文献。</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
elements.collectionItems.innerHTML = "";
|
||||
for (const item of state.visibleCollectionItems) {
|
||||
const row = document.createElement("label");
|
||||
row.className = "collection-item";
|
||||
row.innerHTML = `
|
||||
<div class="checkbox-row">
|
||||
<input type="checkbox" ${state.selectedItemKeys.has(item.item_key) ? "checked" : ""} />
|
||||
<div>
|
||||
<strong>${item.title}</strong>
|
||||
<div class="meta">${item.item_key} · ${item.year || "-"} · ${item.item_type || "-"}</div>
|
||||
<p>${item.abstract || "暂无摘要"}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const checkbox = row.querySelector('input[type="checkbox"]');
|
||||
checkbox.addEventListener("change", (event) => {
|
||||
toggleItemSelection(item.item_key, event.target.checked);
|
||||
});
|
||||
elements.collectionItems.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSelectedCount() {
|
||||
elements.selectedCount.textContent = `已选 ${state.selectedItemKeys.size} 篇`;
|
||||
}
|
||||
|
||||
function toggleItemSelection(itemKey, checked) {
|
||||
if (checked) {
|
||||
state.selectedItemKeys.add(itemKey);
|
||||
} else {
|
||||
state.selectedItemKeys.delete(itemKey);
|
||||
}
|
||||
renderSelectedCount();
|
||||
}
|
||||
|
||||
function toggleCollectionExpanded(collectionKey) {
|
||||
if (state.expandedCollectionKeys.has(collectionKey)) {
|
||||
state.expandedCollectionKeys.delete(collectionKey);
|
||||
} else {
|
||||
state.expandedCollectionKeys.add(collectionKey);
|
||||
}
|
||||
renderCollectionTree();
|
||||
}
|
||||
|
||||
async function loadProjects() {
|
||||
const projects = await api("/api/projects");
|
||||
state.projects = projects;
|
||||
@ -725,29 +562,6 @@
|
||||
await selectProject(state.currentProjectId);
|
||||
}
|
||||
|
||||
async function loadCollectionTree() {
|
||||
const payload = await api("/api/zotero/collections/tree");
|
||||
state.collectionTree = payload.collections || [];
|
||||
if (!state.selectedCollectionKey && state.collectionTree.length) {
|
||||
state.selectedCollectionKey = state.collectionTree[0].collection_key;
|
||||
state.expandedCollectionKeys.add(state.selectedCollectionKey);
|
||||
}
|
||||
renderCollectionTree();
|
||||
if (state.selectedCollectionKey) {
|
||||
await selectCollection(state.selectedCollectionKey, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function selectCollection(collectionKey, rerenderTree = true) {
|
||||
state.selectedCollectionKey = collectionKey;
|
||||
const payload = await api(`/api/zotero/collections/${collectionKey}/items?include_descendants=true`);
|
||||
state.visibleCollectionItems = payload.items || [];
|
||||
if (rerenderTree) {
|
||||
renderCollectionTree();
|
||||
}
|
||||
renderCollectionItems();
|
||||
}
|
||||
|
||||
elements.projectForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(elements.projectForm);
|
||||
@ -766,25 +580,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function importItemKeys(itemKeys) {
|
||||
if (!state.currentProjectId) {
|
||||
setStatus(elements.collectionStatus, "请先选择项目。", true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = await api(`/api/projects/${state.currentProjectId}/imports/item-keys`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ item_keys: itemKeys }),
|
||||
});
|
||||
setStatus(elements.collectionStatus, `已导入 ${payload.imported_item_keys.length} 篇文献。`);
|
||||
renderCards(payload.project_view.cards || []);
|
||||
state.selectedItemKeys.clear();
|
||||
renderCollectionItems();
|
||||
} catch (error) {
|
||||
setStatus(elements.collectionStatus, error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
elements.reloadCardsButton.addEventListener("click", async () => {
|
||||
if (!state.currentProjectId) {
|
||||
return;
|
||||
@ -794,22 +589,6 @@
|
||||
|
||||
elements.refreshProjectsButton.addEventListener("click", loadProjects);
|
||||
|
||||
elements.selectDescendantsButton.addEventListener("click", () => {
|
||||
for (const item of state.visibleCollectionItems) {
|
||||
state.selectedItemKeys.add(item.item_key);
|
||||
}
|
||||
renderCollectionItems();
|
||||
});
|
||||
|
||||
elements.clearSelectionButton.addEventListener("click", () => {
|
||||
state.selectedItemKeys.clear();
|
||||
renderCollectionItems();
|
||||
});
|
||||
|
||||
elements.importSelectedItemsButton.addEventListener("click", async () => {
|
||||
await importItemKeys(Array.from(state.selectedItemKeys));
|
||||
});
|
||||
|
||||
elements.recommendForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
if (!state.currentProjectId) {
|
||||
@ -859,9 +638,8 @@
|
||||
elements.recommendTab.addEventListener("click", () => activateTab("recommend"));
|
||||
elements.planTab.addEventListener("click", () => activateTab("plan"));
|
||||
|
||||
Promise.all([loadProjects(), loadCollectionTree()]).catch((error) => {
|
||||
loadProjects().catch((error) => {
|
||||
setStatus(elements.projectStatus, error.message, true);
|
||||
setStatus(elements.collectionStatus, error.message, true);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user