From e3fc1e62a01c89834a340a19ceffb136ea2277ee Mon Sep 17 00:00:00 2001 From: Tom Najdek Date: Thu, 26 Jun 2025 15:30:35 +0200 Subject: [PATCH] Display the number of items that would be deleted when replacing the online library (#5359) And require checkbox or text challenge depending on the severity of the action --------- Co-authored-by: Dan Stillman --- .../zotero/preferences/preferences_sync.jsx | 98 ++++++++++++------- chrome/locale/en-US/zotero/preferences.ftl | 16 +++ chrome/locale/en-US/zotero/zotero.ftl | 4 + chrome/locale/en-US/zotero/zotero.properties | 2 - 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/chrome/content/zotero/preferences/preferences_sync.jsx b/chrome/content/zotero/preferences/preferences_sync.jsx index 92aac11259..fe41c11080 100644 --- a/chrome/content/zotero/preferences/preferences_sync.jsx +++ b/chrome/content/zotero/preferences/preferences_sync.jsx @@ -831,44 +831,74 @@ Zotero_Preferences.Sync = { } break;*/ - case 'restore-to-server': - var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) - + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL) - + ps.BUTTON_POS_1_DEFAULT; - var index = ps.confirmEx( - null, - Zotero.getString('general.warning'), - Zotero.getString( - 'zotero.preferences.sync.reset.restoreToServer', - [Zotero.clientName, library.name, ZOTERO_CONFIG.DOMAIN_NAME] - ), - buttonFlags, - Zotero.getString('zotero.preferences.sync.reset.restoreToServer.button'), - null, null, null, {} - ); + case 'restore-to-server': { + const CHECKBOX_THRESHOLD = 10; + const CONFIRMATION_TEXT_MAX_ITEMS = 5; - switch (index) { - case 0: - var resetButton = document.getElementById('sync-reset-button'); - resetButton.disabled = true; - try { - await Zotero.Sync.Runner.sync({ - libraries: [libraryID], - resetMode: Zotero.Sync.Runner.RESET_MODE_TO_SERVER - }); - } - finally { - resetButton.disabled = false; - } - break; - - // Cancel - case 1: - return; + let apiKey = await Zotero.Sync.Data.Local.getAPIKey(); + let client = Zotero.Sync.Runner.getAPIClient({ apiKey }); + var keyInfo = await Zotero.Sync.Runner.checkAccess(client, { timeout: 5000 }); + let { keys: remoteKeysArray } = await client.getKeys('user', keyInfo.userID, { target: 'items', itemType: '-annotation' }); + let remoteKeys = new Set(remoteKeysArray); + let localItems = await Zotero.Items.getAll(Zotero.Libraries.userLibraryID, false, false, false); + let localItemsCount = localItems.length; + let localKeys = new Set(localItems + .filter(item => item.isRegularItem() || item.isNote() || item.isAttachment()) + .map(item => item.key)); + let remoteButNotLocal = remoteKeys.difference(localKeys); // NOTE: `difference` requires FF 127 + let remoteItemsDeletedCount = remoteButNotLocal.size; + + let [title, text, warning1, warning2, checkboxLabel, yes] = await document.l10n.formatValues([ + 'general-warning', + { id: 'preferences-sync-reset-restore-to-server-body', args: { libraryName: library.name, domain: ZOTERO_CONFIG.DOMAIN_NAME } }, + { id: 'preferences-sync-reset-restore-to-server-deleted-items-text', args: { remoteItemsDeletedCount } }, + { id: 'preferences-sync-reset-restore-to-server-remaining-items-text', args: { localItemsCount } }, + { id: 'preferences-sync-reset-restore-to-server-checkbox-label', args: { remoteItemsDeletedCount } }, + 'preferences-sync-reset-restore-to-server-yes', + ]); + let confirmationText; + + text = remoteItemsDeletedCount > 0 ? `${text}\n\n${warning1}` : text; + + if (remoteItemsDeletedCount < CHECKBOX_THRESHOLD) { + checkboxLabel = null; } + else if (localItemsCount < CONFIRMATION_TEXT_MAX_ITEMS) { + text += warning2; + checkboxLabel = null; + confirmationText = await document.l10n.formatValue( + 'preferences-sync-reset-restore-to-server-confirmation-text', + ) + text += "\n\n" + await document.l10n.formatValue( + 'general-type-to-continue', + { text: confirmationText} + ); + } + var io = { + title, + text, + acceptLabel: yes, + checkboxLabel, + confirmationText + }; + window.openDialog("chrome://zotero/content/hardConfirmationDialog.xhtml", "", + "chrome,dialog,dependent,modal,centerscreen", io); + if (io.accept) { + let resetButton = document.getElementById('sync-reset-button'); + resetButton.disabled = true; + try { + await Zotero.Sync.Runner.sync({ + libraries: [libraryID], + resetMode: Zotero.Sync.Runner.RESET_MODE_TO_SERVER + }); + } + finally { + resetButton.disabled = false; + } + } break; - + } case 'reset-file-sync-history': var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING diff --git a/chrome/locale/en-US/zotero/preferences.ftl b/chrome/locale/en-US/zotero/preferences.ftl index 64eb9291a4..4c9704a050 100644 --- a/chrome/locale/en-US/zotero/preferences.ftl +++ b/chrome/locale/en-US/zotero/preferences.ftl @@ -103,3 +103,19 @@ preferences-advanced-custom-data-dir = preferences-advanced-default-data-dir = .value = (Default: { $directory }) .aria-label = Default location + +preferences-sync-reset-restore-to-server-body = { -app-name } will replace “{ $libraryName }” on { $domain } with data from this computer. +preferences-sync-reset-restore-to-server-deleted-items-text = { $remoteItemsDeletedCount } { $remoteItemsDeletedCount -> + [one] item + *[other] items + } in the online library will be permanently deleted. +preferences-sync-reset-restore-to-server-remaining-items-text = { general-sentence-separator }{ $localItemsCount -> + [0] The library on this computer and the online library will be empty. + [one] 1 item will remain on this computer and in the online library. + *[other] { $localItemsCount } items will remain on this computer and in the online library. + } +preferences-sync-reset-restore-to-server-checkbox-label = { $remoteItemsDeletedCount -> + *[other] Delete { $remoteItemsDeletedCount } items + } +preferences-sync-reset-restore-to-server-confirmation-text = delete online library +preferences-sync-reset-restore-to-server-yes = Replace Data in Online Library \ No newline at end of file diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl index fe97c630ac..03a5e94c59 100644 --- a/chrome/locale/en-US/zotero/zotero.ftl +++ b/chrome/locale/en-US/zotero/zotero.ftl @@ -1,3 +1,5 @@ +general-sentence-separator = { " " } + general-key-control = Control general-key-shift = Shift general-key-alt = Alt @@ -58,6 +60,8 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-warning = Warning +general-type-to-continue = Type “{ $text }” to continue. general-red = Red general-orange = Orange diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index af92e9f8ce..ed0655fcc1 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -718,8 +718,6 @@ zotero.preferences.sync.reset.userInfoMissing = You must enter a username and zotero.preferences.sync.reset.restoreFromServer = All data in this copy of Zotero will be erased and replaced with data belonging to user '%S' on the Zotero server. zotero.preferences.sync.reset.replaceLocalData = Replace Local Data zotero.preferences.sync.reset.restartToComplete = Firefox must be restarted to complete the restore process. -zotero.preferences.sync.reset.restoreToServer = %1$S will replace data in “%2$S” on %3$S with data from this computer. -zotero.preferences.sync.reset.restoreToServer.button = Replace Data in Online Library zotero.preferences.sync.reset.fileSyncHistory = On the next sync, %1$S will check all attachment files in “%2$S” against the storage service. Any remote attachment files that are missing locally will be downloaded, and local attachment files missing remotely will be uploaded.\n\nThis option is not necessary during normal usage. zotero.preferences.sync.reset.fileSyncHistory.cleared = The file sync history for “%S” has been cleared.