fix: sequence modal collection item loads

This commit is contained in:
Saberlve 2026-04-15 12:44:55 +08:00
parent 4b6c78105e
commit 3a1c046a68
2 changed files with 34 additions and 2 deletions

View File

@ -418,6 +418,10 @@
: [] : []
), ),
visibleCollectionItems: [], visibleCollectionItems: [],
collectionItemsLoaded: false,
loadingCollectionKey: null,
nextCollectionItemsRequestToken: 0,
activeCollectionItemsRequestToken: 0,
}; };
const elements = { const elements = {
@ -599,7 +603,7 @@
syncCurrentProjectLabels(); syncCurrentProjectLabels();
elements.importSelectedItemsButton.disabled = !hasProject || !hasSelection; elements.importSelectedItemsButton.disabled = !hasProject || !hasSelection;
elements.importSelectedItemsButton.title = !hasProject ? "请先选择项目" : (hasSelection ? "" : "请先选择文献"); elements.importSelectedItemsButton.title = !hasProject ? "请先选择项目" : (hasSelection ? "" : "请先选择文献");
elements.selectDescendantsButton.disabled = !state.visibleCollectionItems.length; elements.selectDescendantsButton.disabled = !state.collectionItemsLoaded || !state.visibleCollectionItems.length;
elements.clearSelectionButton.disabled = !state.selectedItemKeys.size; elements.clearSelectionButton.disabled = !state.selectedItemKeys.size;
renderSelectedCount(); renderSelectedCount();
} }
@ -719,6 +723,11 @@
updateImportActionState(); updateImportActionState();
return; return;
} }
if (!state.collectionItemsLoaded) {
elements.collectionItems.innerHTML = '<div class="empty">正在加载当前 collection 文献。</div>';
updateImportActionState();
return;
}
if (!state.visibleCollectionItems.length) { if (!state.visibleCollectionItems.length) {
elements.collectionItems.innerHTML = '<div class="empty">当前目录及子目录下没有可导入文献。</div>'; elements.collectionItems.innerHTML = '<div class="empty">当前目录及子目录下没有可导入文献。</div>';
updateImportActionState(); updateImportActionState();
@ -759,14 +768,21 @@
return; return;
} }
state.visibleCollectionItems = []; state.visibleCollectionItems = [];
state.collectionItemsLoaded = false;
state.loadingCollectionKey = null;
renderCollectionItems(); renderCollectionItems();
setStatus(elements.collectionStatus, ""); setStatus(elements.collectionStatus, "");
} }
async function loadCollectionItems(collectionKey) { async function loadCollectionItems(collectionKey) {
if (!state.collectionItemsLoaded && state.loadingCollectionKey === collectionKey) {
return;
}
state.selectedCollectionKey = collectionKey; state.selectedCollectionKey = collectionKey;
if (normalizeSelectedCollectionKey() !== collectionKey) { if (normalizeSelectedCollectionKey() !== collectionKey) {
state.visibleCollectionItems = []; state.visibleCollectionItems = [];
state.collectionItemsLoaded = false;
state.loadingCollectionKey = null;
renderCollectionTree(); renderCollectionTree();
renderCollectionItems(); renderCollectionItems();
setStatus(elements.collectionStatus, state.selectedCollectionKey ? "已切换到可用 collection。" : ""); setStatus(elements.collectionStatus, state.selectedCollectionKey ? "已切换到可用 collection。" : "");
@ -778,9 +794,20 @@
ensureSelectedCollectionExpanded(); ensureSelectedCollectionExpanded();
persistImportSessionState(); persistImportSessionState();
renderCollectionTree(); renderCollectionTree();
state.collectionItemsLoaded = false;
state.loadingCollectionKey = collectionKey;
const requestToken = state.nextCollectionItemsRequestToken + 1;
state.nextCollectionItemsRequestToken = requestToken;
state.activeCollectionItemsRequestToken = requestToken;
renderCollectionItems();
setStatus(elements.collectionStatus, "正在加载文献..."); setStatus(elements.collectionStatus, "正在加载文献...");
const payload = await api(`/api/zotero/collections/${collectionKey}/items`); const payload = await api(`/api/zotero/collections/${collectionKey}/items`);
if (state.activeCollectionItemsRequestToken !== requestToken || state.selectedCollectionKey !== collectionKey) {
return;
}
state.visibleCollectionItems = Array.isArray(payload.items) ? payload.items : []; state.visibleCollectionItems = Array.isArray(payload.items) ? payload.items : [];
state.collectionItemsLoaded = true;
state.loadingCollectionKey = null;
renderCollectionItems(); renderCollectionItems();
const visibleCount = state.visibleCollectionItems.length; const visibleCount = state.visibleCollectionItems.length;
setStatus( setStatus(
@ -847,7 +874,10 @@
} catch (error) { } catch (error) {
setStatus(elements.collectionStatus, error.message, true); setStatus(elements.collectionStatus, error.message, true);
} }
} else if (state.selectedCollectionKey && !state.visibleCollectionItems.length) { } else if (
state.selectedCollectionKey &&
(!state.collectionItemsLoaded && state.loadingCollectionKey !== state.selectedCollectionKey)
) {
try { try {
await loadCollectionItems(state.selectedCollectionKey); await loadCollectionItems(state.selectedCollectionKey);
} catch (error) { } catch (error) {

View File

@ -42,6 +42,8 @@ def test_index_contains_import_modal_controls(tmp_path) -> None:
assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html
assert 'isImportModalOpen: false' in html assert 'isImportModalOpen: false' in html
assert "currentProjectName: null" in html assert "currentProjectName: null" in html
assert "collectionItemsLoaded: false" in html
assert "nextCollectionItemsRequestToken: 0" in html
assert "function openImportModal()" in html assert "function openImportModal()" in html
assert "function closeImportModal()" in html assert "function closeImportModal()" in html
assert "function updateImportActionState()" in html assert "function updateImportActionState()" in html