From 3f0de027a2c07d98a11cc4d3b853fb25cc16efae Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 7 Apr 2026 10:25:19 -0400 Subject: [PATCH 01/30] Auto-resolve lastRead sync conflicts by picking the most recent value --- chrome/content/zotero/xpcom/sync/syncLocal.js | 15 ++++- test/tests/syncEngineTest.js | 56 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/chrome/content/zotero/xpcom/sync/syncLocal.js b/chrome/content/zotero/xpcom/sync/syncLocal.js index 1d1d8ef234..d5a50384fd 100644 --- a/chrome/content/zotero/xpcom/sync/syncLocal.js +++ b/chrome/content/zotero/xpcom/sync/syncLocal.js @@ -1739,7 +1739,20 @@ Zotero.Sync.Data.Local = { || isAutoMergeType) { continue; } - + + // Auto-resolve lastRead by keeping the most recent value + if (c1.field == 'lastRead') { + if ((c1.value || 0) > (c2.value || 0)) { + // Local is more recent -- drop remote change + changeset2.splice(j--, 1); + } + else { + // Remote is more recent -- apply it + matchedLocalChanges.add(i); + } + continue; + } + // Conflict matchedLocalChanges.add(i); changeset2.splice(j--, 1); diff --git a/test/tests/syncEngineTest.js b/test/tests/syncEngineTest.js index 05ed1e8ae2..5d05f0682e 100644 --- a/test/tests/syncEngineTest.js +++ b/test/tests/syncEngineTest.js @@ -3783,7 +3783,61 @@ describe("Zotero.Sync.Data.Engine", function () { var keys = await Zotero.Sync.Data.Local.getObjectsFromSyncQueue('item', libraryID); assert.lengthOf(keys, 0); }); - + + it("should auto-resolve lastRead conflict by picking the most recent value", async function () { + var libraryID = Zotero.Libraries.userLibraryID; + ({ engine, client, caller } = await setup()); + + // Create a user-library attachment with a lastRead value + let attachment = await importFileAttachment('test.pdf'); + attachment.version = 10; + attachment.synced = true; + attachment.attachmentLastRead = 1700000000; + await attachment.saveTx({ skipAll: true }); + + // Save original version in cache + let jsonData = attachment.toJSON(); + jsonData.key = attachment.key; + jsonData.version = 10; + let json = { + key: attachment.key, + version: 10, + data: jsonData + }; + await Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json]); + + // Modify lastRead locally (older) + attachment.attachmentLastRead = 1700000100; + attachment.synced = false; + await attachment.saveTx({ skipAll: true }); + + // Create remote version with newer lastRead + let remoteJSON = Object.assign({}, jsonData); + remoteJSON.lastRead = 1700000200; + remoteJSON.version = 15; + let responseJSON = { + key: attachment.key, + version: 15, + data: remoteJSON + }; + + setResponse({ + method: "GET", + url: `users/1/items?itemKey=${attachment.key}&includeTrashed=1`, + status: 200, + headers: { + "Last-Modified-Version": 15 + }, + json: [responseJSON] + }); + + // Should not show conflict resolution window + await engine._downloadObjects('item', [attachment.key]); + + // The most recent lastRead value should win + assert.equal(attachment.attachmentLastRead, 1700000200); + }); + it("should show conflict resolution window on note conflicts", async function () { var libraryID = Zotero.Libraries.userLibraryID; ({ engine, client, caller } = await setup()); From a3aa2bb5c7f243d371930cd33e35f8ea5b173cb6 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 7 Apr 2026 10:32:36 -0400 Subject: [PATCH 02/30] Add `general-more-information` FTL string --- chrome/locale/en-US/zotero/zotero.ftl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl index 2dab80efec..6a4807407a 100644 --- a/chrome/locale/en-US/zotero/zotero.ftl +++ b/chrome/locale/en-US/zotero/zotero.ftl @@ -61,6 +61,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = Warning general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -323,7 +324,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = From 5671a724e8db79ced04ce19d575514f0a2cd0619 Mon Sep 17 00:00:00 2001 From: Mi Ramon Date: Tue, 7 Apr 2026 22:38:50 +0800 Subject: [PATCH 03/30] Fix broken Find option under Edit menu in reader (#5877) --- chrome/content/zotero/zoteroPane.xhtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml index e345f06214..ba1049efab 100644 --- a/chrome/content/zotero/zoteroPane.xhtml +++ b/chrome/content/zotero/zoteroPane.xhtml @@ -421,9 +421,12 @@ oncommand="ZoteroStandalone.currentReader.rotatePageRight()" /> - + Date: Tue, 7 Apr 2026 10:44:12 -0400 Subject: [PATCH 04/30] Update English strings for Transifex --- chrome/locale/en-US/zotero/zotero.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chrome/locale/en-US/zotero/zotero.json b/chrome/locale/en-US/zotero/zotero.json index 29286ce9eb..75d8607378 100644 --- a/chrome/locale/en-US/zotero/zotero.json +++ b/chrome/locale/en-US/zotero/zotero.json @@ -128,6 +128,9 @@ "general-learn-more": { "string": "Learn More" }, + "general-more-information": { + "string": "More Information" + }, "general-warning": { "string": "Warning" }, @@ -452,9 +455,6 @@ "import-online-relink-only.label": { "string": "Relink Mendeley Desktop citations" }, - "import-online-relink-kb": { - "string": "More Information" - }, "import-online-connection-error": { "string": "{ app-name } could not connect to { targetApp }. Please check your internet connection and try again." }, From 91e82566ffccda06446f1c376c3f19393e80fa4a Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 7 Apr 2026 10:58:06 -0400 Subject: [PATCH 05/30] Update locales from Transifex --- chrome/locale/af-ZA/zotero/zotero.ftl | 3 +- chrome/locale/ar/zotero/zotero.ftl | 3 +- chrome/locale/bg-BG/zotero/zotero.ftl | 3 +- chrome/locale/br/zotero/integration.ftl | 2 +- chrome/locale/br/zotero/note-editor.ftl | 2 +- chrome/locale/br/zotero/reader.ftl | 10 ++-- chrome/locale/br/zotero/zotero.dtd | 30 +++++------ chrome/locale/br/zotero/zotero.ftl | 7 +-- chrome/locale/br/zotero/zotero.properties | 62 +++++++++++----------- chrome/locale/ca-AD/zotero/zotero.ftl | 3 +- chrome/locale/cs-CZ/zotero/zotero.ftl | 3 +- chrome/locale/da-DK/zotero/zotero.ftl | 3 +- chrome/locale/de/zotero/zotero.ftl | 3 +- chrome/locale/el-GR/zotero/zotero.ftl | 3 +- chrome/locale/en-GB/zotero/zotero.ftl | 3 +- chrome/locale/es-ES/zotero/zotero.ftl | 3 +- chrome/locale/et-EE/zotero/zotero.ftl | 3 +- chrome/locale/eu-ES/zotero/zotero.ftl | 3 +- chrome/locale/fa/zotero/zotero.ftl | 3 +- chrome/locale/fi-FI/zotero/zotero.ftl | 3 +- chrome/locale/fr-FR/zotero/integration.ftl | 6 +-- chrome/locale/fr-FR/zotero/zotero.ftl | 3 +- chrome/locale/gl-ES/zotero/zotero.ftl | 3 +- chrome/locale/he-IL/zotero/zotero.ftl | 3 +- chrome/locale/hr-HR/zotero/zotero.ftl | 3 +- chrome/locale/hu-HU/zotero/zotero.ftl | 3 +- chrome/locale/id-ID/zotero/zotero.ftl | 3 +- chrome/locale/is-IS/zotero/zotero.ftl | 3 +- chrome/locale/it-IT/zotero/zotero.ftl | 3 +- chrome/locale/ja-JP/zotero/zotero.ftl | 3 +- chrome/locale/km/zotero/zotero.ftl | 3 +- chrome/locale/ko-KR/zotero/zotero.ftl | 3 +- chrome/locale/lt-LT/zotero/zotero.ftl | 3 +- chrome/locale/mn-MN/zotero/zotero.ftl | 3 +- chrome/locale/nb-NO/zotero/zotero.ftl | 3 +- chrome/locale/nl-NL/zotero/zotero.ftl | 3 +- chrome/locale/nn-NO/zotero/zotero.ftl | 3 +- chrome/locale/pl-PL/zotero/preferences.ftl | 12 ++--- chrome/locale/pl-PL/zotero/zotero.ftl | 17 +++--- chrome/locale/pt-BR/zotero/zotero.ftl | 3 +- chrome/locale/pt-PT/zotero/zotero.ftl | 3 +- chrome/locale/ro-RO/zotero/zotero.ftl | 3 +- chrome/locale/ru-RU/zotero/zotero.ftl | 3 +- chrome/locale/sk-SK/zotero/zotero.ftl | 3 +- chrome/locale/sl-SI/zotero/zotero.ftl | 3 +- chrome/locale/sr-RS/zotero/zotero.ftl | 3 +- chrome/locale/sv-SE/zotero/zotero.ftl | 3 +- chrome/locale/ta/zotero/zotero.ftl | 3 +- chrome/locale/th-TH/zotero/zotero.ftl | 3 +- chrome/locale/tr-TR/zotero/zotero.ftl | 3 +- chrome/locale/uk-UA/zotero/zotero.ftl | 3 +- chrome/locale/vi-VN/zotero/zotero.ftl | 3 +- chrome/locale/zh-CN/zotero/preferences.ftl | 4 +- chrome/locale/zh-CN/zotero/zotero.ftl | 3 +- chrome/locale/zh-TW/zotero/zotero.ftl | 3 +- 55 files changed, 167 insertions(+), 120 deletions(-) diff --git a/chrome/locale/af-ZA/zotero/zotero.ftl b/chrome/locale/af-ZA/zotero/zotero.ftl index 45100dd1da..c3f9345aee 100644 --- a/chrome/locale/af-ZA/zotero/zotero.ftl +++ b/chrome/locale/af-ZA/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = Warning general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ar/zotero/zotero.ftl b/chrome/locale/ar/zotero/zotero.ftl index ff576ba060..f5aef86ed2 100644 --- a/chrome/locale/ar/zotero/zotero.ftl +++ b/chrome/locale/ar/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = إلخ. general-previous = السابق general-next = التالي general-learn-more = تعرف على المزيد +general-more-information = المزيد من المعلومات general-warning = تحذير general-type-to-continue = اكتب ”{ $text }“ للمتابعة. general-continue = استمر @@ -283,7 +284,7 @@ import-online-wrong-credentials = لقد فشل تسجيل الدخول إلى { import-online-blocked-by-plugin = لا يمكن متابعة الاستيراد مع تثبيت { $plugin }. يرجى تعطيل هذه الإضافة والمحاولة مرة أخرى. import-online-relink-only = .label = إعادة ربط استشهادات مندلي المكتبية -import-online-relink-kb = المزيد من المعلومات +import-online-relink-kb = { general-more-information } import-online-connection-error = تعذر على { -app-name } الاتصال ب { $targetApp }. يرجى التحقق من اتصالك بالإنترنت والمحاولة مرة أخرى. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/bg-BG/zotero/zotero.ftl b/chrome/locale/bg-BG/zotero/zotero.ftl index a37be06569..537439e00a 100644 --- a/chrome/locale/bg-BG/zotero/zotero.ftl +++ b/chrome/locale/bg-BG/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = и др. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = Повече информация general-warning = Предупреждение general-type-to-continue = Type “{ $text }” to continue. general-continue = Продължаване @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Повече информация +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/br/zotero/integration.ftl b/chrome/locale/br/zotero/integration.ftl index 3792faf40d..6e591e05bd 100644 --- a/chrome/locale/br/zotero/integration.ftl +++ b/chrome/locale/br/zotero/integration.ftl @@ -23,7 +23,7 @@ integration-citationDialog = Citation Dialog integration-citationDialog-section-open = Open Documents ({ $count }) integration-citationDialog-section-selected = Selected Items ({ $count }/{ $total }) integration-citationDialog-section-selectedAnnotations = Selected Annotations -integration-citationDialog-section-selectedItems = Selected Items +integration-citationDialog-section-selectedItems = Elfennoù diuzet integration-citationDialog-section-cited = { $count -> [0] Cited Items diff --git a/chrome/locale/br/zotero/note-editor.ftl b/chrome/locale/br/zotero/note-editor.ftl index 664cf3c156..4b74b44015 100644 --- a/chrome/locale/br/zotero/note-editor.ftl +++ b/chrome/locale/br/zotero/note-editor.ftl @@ -16,7 +16,7 @@ note-editor-paragraph = Rannbennad note-editor-monospaced = Monospaset note-editor-bullet-list = Listenn gant merkigoù note-editor-ordered-list = Listenn niverennet -note-editor-block-quote = Block Quote +note-editor-block-quote = Bloc'had arroudenn note-editor-math-block = Matematik bloc'had note-editor-format-text = Stumm an destenn note-editor-highlight-text = Usskediñ an destenn diff --git a/chrome/locale/br/zotero/reader.ftl b/chrome/locale/br/zotero/reader.ftl index 887c612bf5..6003b14333 100644 --- a/chrome/locale/br/zotero/reader.ftl +++ b/chrome/locale/br/zotero/reader.ftl @@ -7,7 +7,7 @@ reader-no-extracted-text = Testenn ebet eztennet reader-add-comment = Ouzhpennañ un evezhiadenn reader-annotation-comment = Evezhiadenn an ennotadur reader-annotation-text = Testenn an ennotadur -reader-manage-tags = Manage tags for this annotation +reader-manage-tags = Merañ an tikedennoù evit an ennotadur-mañ reader-open-menu = Digeriñ lañser an ennotaudurioù reader-thumbnails = Skeudennigoù reader-tag-selector-message = Silañ an ennotadurioù dre an dikedenn-mañ @@ -94,12 +94,12 @@ reader-epub-appearance-word-spacing = Spas etre gerioù reader-epub-appearance-letter-spacing = Spas etre al lizherennoù reader-epub-appearance-page-width = Ledander ar bajenn reader-epub-appearance-use-original-font = Ober gant ar skritur orin -reader-epub-appearance-line-height-revert = Use default line height +reader-epub-appearance-line-height-revert = Ober gant an uhelder linenn dre ziouer reader-epub-appearance-word-spacing-revert = Ober gant ar spas dre ziouer etre ar gerioù reader-epub-appearance-letter-spacing-revert = Ober gant ar spas dre ziouer etre al lizherennoù reader-epub-appearance-page-width-revert = Ober gant al ledander pajenn dre ziouer -reader-convert-to-highlight = Convert to Highlight -reader-convert-to-underline = Convert to Underline +reader-convert-to-highlight = Emdreiñ da usskedet +reader-convert-to-underline = Emdreiñ da islinennet reader-size = Ment reader-merge = Kendeuziñ reader-copy-link = Eilañ al liamm @@ -131,7 +131,7 @@ reader-background = Drekleur: reader-foreground = Rakleur: reader-reading-mode = Reading Mode reader-reading-mode-not-supported = Reading Mode is not supported in this document. -reader-clear-selection = Clear Selection +reader-clear-selection = Dilemel an diuzadenn reader-epub-encrypted = This ebook is encrypted and cannot be opened. reader-move-annotation-start-key = { PLATFORM() -> diff --git a/chrome/locale/br/zotero/zotero.dtd b/chrome/locale/br/zotero/zotero.dtd index e9f653d41e..93503ea584 100644 --- a/chrome/locale/br/zotero/zotero.dtd +++ b/chrome/locale/br/zotero/zotero.dtd @@ -106,8 +106,8 @@ - - + + @@ -133,7 +133,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -297,10 +297,10 @@ - - - - + + + + @@ -311,9 +311,9 @@ - - - + + + @@ -324,6 +324,6 @@ - - - + + + diff --git a/chrome/locale/br/zotero/zotero.ftl b/chrome/locale/br/zotero/zotero.ftl index 95abad575a..00e5da1e04 100644 --- a/chrome/locale/br/zotero/zotero.ftl +++ b/chrome/locale/br/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = hag all hag all general-previous = Kent general-next = Da-heul general-learn-more = Gouzout hiroc'h +general-more-information = Muioc'h a ditouroù general-warning = Kemenn-diwall general-type-to-continue = Type “{ $text }” to continue. general-continue = Kenderc'hel @@ -231,7 +232,7 @@ import-online-intro-title = Digoradur import-source-file = .label = Ur restr (BibTeX, RIS, Zotero RDF, hag all hag all...) import-source-folder = - .label = A folder of PDFs or other files + .label = Un doser gant PDFoù pe restroù all import-source-online = .label = { $targetApp } online import import-options = Dibarzhioù @@ -252,7 +253,7 @@ import-file-handling-link = .label = Liammañ d'ar restroù el lec'hiadur orin import-fileHandling-description = Linked files cannot be synced by { -app-name }. import-online-new = - .label = Download new items only; don’t update previously imported items + .label = Pellgargañ an elfennoù nevez hepken; na hizivaat an elfennoù emporzhiet kent import-mendeley-username = Anv-implijer import-mendeley-password = Ger-tremen general-error = Fazi @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Muioc'h a ditouroù +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/br/zotero/zotero.properties b/chrome/locale/br/zotero/zotero.properties index 24dc95c6f6..ac72e06008 100644 --- a/chrome/locale/br/zotero/zotero.properties +++ b/chrome/locale/br/zotero/zotero.properties @@ -90,7 +90,7 @@ general.default=Dre ziouer general.custom=Personelaet general.loading=O kargañ... general.richText=Testennoù pinvidik -general.clearSelection=Clear Selection +general.clearSelection=Dilemel an diuzadenn general.insert=Enlakaat general.username=Anv-implijer general.password=Ger-tremen @@ -115,7 +115,7 @@ general.operationInProgress=Un oberiadenn a-berzh Zotero a zo war ober er mare-m general.operationInProgress.waitUntilFinished=Gortozit betek ma vo echuet. general.operationInProgress.waitUntilFinishedAndTryAgain=Gortozit betek ma vo echuet ha klaskit en-dro. -networkError.connectionNotSecure=Insecure Connection +networkError.connectionNotSecure=Kevreañ diasur networkError.insecureConnectionTo=%1$S could not make a secure connection to %2$S. networkError.errorViaProxy=Fazi en ur gevreañ dre ur servijer proksi networkError.connectionMonitored=Your connection is likely being monitored, and %S has not been configured to trust the intermediate server. @@ -364,12 +364,12 @@ pane.items.menu.generateReport=Genel un danevell diwar an elfenn... pane.items.menu.generateReport.multiple=Genel un danevell diwar an elfennoù... pane.items.menu.reindexItem=Ad-Indeksiñ an elfenn pane.items.menu.reindexItem.multiple=Ad-Indeksiñ an elfennoù -pane.items.menu.recognizeDocument=Retrieve Metadata +pane.items.menu.recognizeDocument=Adtapout ar metaroadennoù pane.items.menu.createParent=Krouiñ un elfenn kar... pane.items.menu.createParent.multiple=Krouiñ elfennoù kar pane.items.menu.renameAttachments=Adenvel ar restr diwar ar meta-roadenn kar pane.items.menu.renameAttachments.multiple=Adenvel ar restroù diwar ar meta-roadenn kar -pane.items.menu.duplicateAndConvert.toBookSection=Create Book Section +pane.items.menu.duplicateAndConvert.toBookSection=Krouiñ ur chabistr levr pane.items.menu.duplicateAndConvert.toBook=Create Book from Book Section pane.items.menu.showInFeed=Diskouez er froud pane.items.showItemInLibrary=Diskouez an elfenn el levraoueg @@ -426,7 +426,7 @@ pane.item.attachments.autoRelink.text4=%1$S can automatically relink this attach pane.item.attachments.autoRelink.relink=Adliammañ pane.item.attachments.autoRelinkOthers.title=Additional Files Located pane.item.attachments.autoRelinkOthers.text=One other unlinked attachment in this library was found within the same directory. Relink this attachment as well?;%S other unlinked attachments in this library were found within the same directory. Relink all located attachments? -pane.item.attachments.autoRelink.locateManually=Locate Manually… +pane.item.attachments.autoRelink.locateManually=Lec'helaat gant an dorn… pane.item.attachments.autoRelink.relinkAll=Adliammañ pep-tra pane.item.attachments.has=Gant pezhioù stag pane.item.attachments.hasPDF=Gant restroù PDF stag @@ -487,7 +487,7 @@ itemTypes.document=Teuliad itemTypes.encyclopediaArticle=Pennad hollouiziegezh itemTypes.dictionaryEntry=Pennger geriadur itemTypes.preprint=Rakvoullañ -itemTypes.dataset=Dataset +itemTypes.dataset=Diaz-roadennoù itemTypes.standard=Standard itemFields.itemType=Doare elfenn @@ -601,16 +601,16 @@ itemFields.issuingAuthority=Aozadur a embann itemFields.filingDate=Deiziad leuniadur itemFields.genre=Doare itemFields.archive=Diell -itemFields.attachmentPDF=PDF Attachment +itemFields.attachmentPDF=PDF stag itemFields.repository=Kavlec'h -itemFields.repositoryLocation=Repo. Location -itemFields.archiveID=Archive ID -itemFields.citationKey=Citation Key +itemFields.repositoryLocation=Lec'h. an etrepaouez +itemFields.archiveID=ID en diell +itemFields.citationKey=Alc'hwez arroudenn itemFields.identifier=Anaouder itemFields.type=Doare itemFields.format=Stumm itemFields.status=Statud -itemFields.authority=Authority +itemFields.authority=Aotrouniezh itemFields.organization=Aozadur itemFields.feed=Froud @@ -664,7 +664,7 @@ ingester.importFile.intoNewCollection=Emporzhiañ e-barzh un dastumadeg nevez ingester.lookup.performing=O klask... ingester.lookup.error=Ur fazi a zo c'hoarvezet en ur glask an elfenn. -findPDF.searchingForAvailableFiles=Searching for available files… +findPDF.searchingForAvailableFiles=O klask restroù hegerz… findPDF.checkingItems=O wiriañ %1$S elfenn;O wiriañ %1$S elfenn findPDF.openAccessPDF=PDF moned-digor findPDF.pdfWithMethod=PDF (%S) @@ -730,11 +730,11 @@ zotero.preferences.export.quickCopy.citationStyles=Doare arroudenn zotero.preferences.export.quickCopy.exportFormats=Ezporzhiañ ar stummoù zotero.preferences.export.quickCopy.instructions=An Eilañ prim a ro an tu deoc'h da ezporzhiañ buan-tre an elfennoù en ur stumm roet. Gellout a rit eilañ an elfennoù diuzet er golver en ur bouezañ war %S pe rinklit an elfennoù war-eeun e-barzh ur voest testenn e-barzh ur program all. zotero.preferences.export.quickCopy.citationInstructions=Evit ar stiloù arroudenn e c'hallit eilañ an arroudennoù pe an notennoù-traoñ pajenn en ur bouezañ war %S pe en ur zerc'hel war Linkad a-raok rinklañ-stlejañ elfennoù. -zotero.preferences.export.quickCopy.note.htmlOptions.label=Rich Text/HTML +zotero.preferences.export.quickCopy.note.htmlOptions.label=Testenn pinvidik/HTML zotero.preferences.wordProcessors.installationSuccess=Berzh en deus graet ar staliadur. zotero.preferences.wordProcessors.installationError=N'hall ket bezañ klokaet ar staliadur rak ur fazi a zo c'hoarvezet. Bezit asur eo sarr %1$S, hag adloc'hit goude-se %2$S. -zotero.preferences.wordProcessors.manualInstallation.button=Manual Installation +zotero.preferences.wordProcessors.manualInstallation.button=Staliadur gant an dorn zotero.preferences.wordProcessors.installed=Ar vodulenn %S a zo staliet endeo. zotero.preferences.wordProcessors.notInstalled=Neket staliet ar vodulenn %S betek-hen. zotero.preferences.wordProcessors.install=Staliañ ar vodulenn %S @@ -841,7 +841,7 @@ citation.showEditor=Diskouez an aozer... citation.hideEditor=Kuzhat an aozer... citation.citations=Arroudennoù citation.notes=Notennoù -citation.locator.timestamp=Timestamp +citation.locator.timestamp=Eurdeiziad report.title.default=Danevell Zotero report.parentItem=Elfenn kar: @@ -879,7 +879,7 @@ integration.removeBibEntry.body=Ha fellout a ra deoc'h leuskel anezhañ e-maez h integration.cited=Arroudennet integration.cited.loading=O kargañ an elfennoù arroudennet... integration.openTabs=Restroù digor -integration.selectedItems=Selected Items +integration.selectedItems=Elfennoù diuzet integration.ibid=ibid integration.emptyCitationWarning.title=Arroudenn wenn integration.emptyCitationWarning.body=An arroudenn spisaet ganeoc'h a vo goullo gant ar stil diuzet er mare-mañ. Ha sur oc'h e fell deoc'h ouzhpennañ anezhañ? @@ -905,7 +905,7 @@ integration.error.misconfiguredWordStartupFolder.fix=Al lugant na vo ket diskoue integration.error.macWordSBPermissionsMissing.title=Aotreadurioù a vank integration.error.macWordSBPermissionsMissing=Zotero does not have permission to control Word. To grant this permission:\n\n1) Open System Settings\n2) Select “Privacy & Security” in the left column\n3) Select “Automation”\n4) Find “Zotero” and click the arrow to expand it\n5) Make sure “Microsoft Word” is enabled under “Zotero”\n6) Restart Word integration.error.macWordSBPermissionsMissing.pre2016=Ma ne gaver ket «Microsoft Word» dindan «Automation», bezit asur e rit gant Word 2011 stumm 14.7.7 pe nevesoc'h. -integration.error.m1UpgradeOS.title=macOS Upgrade Required +integration.error.m1UpgradeOS.title=Hizivadur macOS rekis integration.error.m1UpgradeOS=Citing in Word on an Apple Silicon Mac with macOS 11.3 or earlier is not supported and may cause %S to freeze. Please upgrade to macOS 11.4 or later. integration.error.viewTroubleshootingInfo=Ha c'hoant ho peus da welet ar c'helennadurioù distankañ? integration.error.tabUnavailable=The operation cannot be completed because the %S tab is no longer available. @@ -1007,7 +1007,7 @@ sync.error.invalidDataError.otherData=Roadennoù all a gendalc'ho da sinkro. account.unlinkWarning=Logging out will prevent %S from syncing your data. account.unlinkWarning.removeData=Dilemel ma roadennoù %S diwar an urzhiataer-mañ da virviken -account.unlinkWarning.button=Log Out +account.unlinkWarning.button=Digevreañ account.warning.emptyLibrary=You are about to sync to an empty %1$S database. This could happen if you accidentally deleted the %2$S file in your %1$S data directory or the location of your data directory changed. account.warning.emptyLibrary.dataWillBeDownloaded=If you continue, data in the ‘%1$S’ account will be downloaded to this computer. Your online library won’t be affected. account.warning.existingDataElsewhere=Ma 'z eus eus ho roadennoù %S en ul lec'h all war hoc'h urzhiataer, rankout a rafec'h lakaat anezho da fiñval betek ar c'havlec'h roadennoù pe cheñch lec'hiadur ar c'havlec'h roadennoù evit ma poentfe war-zu ho roadennoù a zo anezho. @@ -1136,9 +1136,9 @@ proxies.notification.settings.button=Arventennoù proksi... proxies.recognized.message=Ouzhpennañ ar proksi-mañ a roio an aotre da Zotero da anavezout elfennoù adalek e bajennoù hag e vo em-adheñchet ar rekedoù da zont savet ouzh %1$S dre %2$S. proxies.recognized.add=Ouzhpennañ ur proksi -recognizePDF.title=Metadata Retrieval +recognizePDF.title=Oc'h adpakout ar metaroadennoù recognizePDF.noOCR=Ne endalc'h ket ar PDF-mañ un destenn OCRet (OCR = arouezioù an destenn anavezet dre wel) -recognizePDF.couldNotRead=Could not read text from document +recognizePDF.couldNotRead=N'heller ket lenn an destenn diwar ar restr recognizePDF.noMatches=N'eus bet kavet daveenn ebet kenglotus recognizePDF.fileNotFound=Restr nann-kavet recognizePDF.error=Ur fazi dic'hortoz 'zo c'hoarvezet @@ -1176,13 +1176,13 @@ lookup.failureTooMany.description=Re a anaouadurioù. Ebarzhit un anaouader mar locate.online.label=Diskouez enlinenn locate.pdf.label=Digeriñ ar PDF locate.pdfNewWindow.label=Open PDF in New Window -locate.pdfNewTab.label=Open PDF in New Tab +locate.pdfNewTab.label=Digeriñ ur PDF en un ivinell nevez locate.snapshot.label=Diskouez an dapadenn locate.file.label=Diskouez ar restr locate.externalViewer.label=Digeriñ gant ul lenner diavaez locate.internalViewer.label=Digeriñ e-barzh ul lenner diabarzh locate.libraryLookup.label=Klask el levraoueg -locate.libraryLookup.noResolver.title=No OpenURL Resolver +locate.libraryLookup.noResolver.title=Diskoulmer OpenURL ebet locate.manageLocateEngines=Merañ al luskerioù enklask... locate.locateEngineDescription=Lookup engines help you find resources in your %S library on the web. Engines enabled in this list appear in the Locate drop-down in the toolbar. @@ -1269,7 +1269,7 @@ noteEditor.paragraph=Rannbennad noteEditor.monospaced=Monospaset noteEditor.bulletList=Listenn gant merkigoù noteEditor.orderedList=Listenn niverennet -noteEditor.blockquote=Block Quote +noteEditor.blockquote=Bloc'had arroudenn noteEditor.mathBlock=Matematik bloc'had noteEditor.formatText=Stumm an destenn noteEditor.highlightText=Usskediñ an destenn @@ -1334,7 +1334,7 @@ pdfReader.noExtractedText=Testenn ebet eztennet pdfReader.addComment=Ouzhpennañ un evezhiadenn pdfReader.annotationComment=Evezhiadenn an ennotadur pdfReader.annotationText=Testenn an ennotadur -pdfReader.manageTags=Manage tags for this annotation +pdfReader.manageTags=Merañ an tikedennoù evit an ennotadur-mañ pdfReader.openMenu=Digeriñ lañser an ennotaudurioù pdfReader.thumbnails=Skeudennigoù pdfReader.tagSelectorMessage=Silañ an ennotadurioù dre an dikedenn-mañ @@ -1405,12 +1405,12 @@ pdfReader.epubAppearance.wordSpacing=Spas etre ar gerioù pdfReader.epubAppearance.letterSpacing=Spas etre al lizherennoù pdfReader.epubAppearance.pageWidth=Ledander ar bajenn pdfReader.epubAppearance.useOriginalFont=Ober gant ar skritur orin -pdfReader.epubAppearance.lineHeight.revert=Use default line height +pdfReader.epubAppearance.lineHeight.revert=Ober gant an uhelder linenn dre ziouer pdfReader.epubAppearance.wordSpacing.revert=Ober gant ar spas dre ziouer etre ar gerioù pdfReader.epubAppearance.letterSpacing.revert=Ober gant ar spas dre ziouer etre al lizherennoù pdfReader.epubAppearance.pageWidth.revert=Ober gant al ledander pajenn dre ziouer -pdfReader.convertToHighlight=Convert to Highlight -pdfReader.convertToUnderline=Convert to Underline +pdfReader.convertToHighlight=Emdreiñ da usskedet +pdfReader.convertToUnderline=Emdreiñ da islinennet pdfReader.size=Ment pdfReader.merge=Kendeuziñ pdfReader.copyLink=Eilañ al liamm @@ -1450,9 +1450,9 @@ tabs.moveToStart=Fiñval d'an penn-kentañ tabs.moveToEnd=Fiñval d'ar fin tabs.moveToWindow=Move to New Window tabs.duplicate=Eilañ an ivinell -tabs.undoClose=Reopen Closed Tab;Reopen Closed Tabs -tabs.closeOther=Close Other Tabs +tabs.undoClose=Addigeriñ an ivinell serret;Addigeriñ an ivinelloù serret +tabs.closeOther=Serriñ an holl ivinelloù addons.emptyListMessage=Download plugins from the [Zotero plugins directory]. -addons.remove.title=Remove %S? -addons.remove.text=Are you sure you want to remove %1$S from %2$S? +addons.remove.title=Dilemel %S? +addons.remove.text=Ha sur oc'h ho peus c'hoant da zilemel %1$S diwar %2$S? diff --git a/chrome/locale/ca-AD/zotero/zotero.ftl b/chrome/locale/ca-AD/zotero/zotero.ftl index a6f80475d6..67647fcf48 100644 --- a/chrome/locale/ca-AD/zotero/zotero.ftl +++ b/chrome/locale/ca-AD/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Següent general-learn-more = Més informació +general-more-information = Més informació general-warning = Avís general-type-to-continue = Type “{ $text }” to continue. general-continue = Continua @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Més informació +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/cs-CZ/zotero/zotero.ftl b/chrome/locale/cs-CZ/zotero/zotero.ftl index 12ea5af7bc..4b210ecf95 100644 --- a/chrome/locale/cs-CZ/zotero/zotero.ftl +++ b/chrome/locale/cs-CZ/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Předchozí general-next = Následující general-learn-more = Zjistit více +general-more-information = Více informací general-warning = Varování general-type-to-continue = Type “{ $text }” to continue. general-continue = Pokračovat @@ -279,7 +280,7 @@ import-online-wrong-credentials = Přihlášení do { $targetApp } se nezdařilo import-online-blocked-by-plugin = Import nemůže pokračovat, pokud je nainstalován { $plugin }. Vypněte prosím tento doplněk a zkuste to znovu. import-online-relink-only = .label = Znovu propojit citace Mendeley Desktop -import-online-relink-kb = Více informací +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } se nepodařilo připojit k { $targetApp }. Zkontrolujte prosím své internetové připojení a zkuste to znovu. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/da-DK/zotero/zotero.ftl b/chrome/locale/da-DK/zotero/zotero.ftl index 8544ffdcf1..0fb86fb400 100644 --- a/chrome/locale/da-DK/zotero/zotero.ftl +++ b/chrome/locale/da-DK/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = m.fl. general-previous = Previous general-next = Next general-learn-more = Lær mere +general-more-information = Mere information general-warning = Warning general-type-to-continue = Type “{ $text }” to continue. general-continue = Fortsæt @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Mere information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/de/zotero/zotero.ftl b/chrome/locale/de/zotero/zotero.ftl index 89656aba6f..c6af6f94dc 100644 --- a/chrome/locale/de/zotero/zotero.ftl +++ b/chrome/locale/de/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Nächstes general-next = Vorheriges general-learn-more = Mehr erfahren +general-more-information = Weitere Informationen general-warning = Warnung general-type-to-continue = Drücke "{ $text }" um fortzufahren. general-continue = Weiter @@ -279,7 +280,7 @@ import-online-wrong-credentials = Anmeldung bei { $targetApp } fehlgeschlagen. B import-online-blocked-by-plugin = Der Import kann nicht fortgesetzt werden, wenn { $plugin } installiert ist. Bitte deaktivieren Sie dieses Plugin und versuchen Sie es erneut. import-online-relink-only = .label = Zitationen aus Mendeley Desktop erneut vernküfen -import-online-relink-kb = Weitere Informationen +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } konnte sich nicht mit { $targetApp } verbinden. Bitte überprüfen Sie Ihre Internetverbindung und wiederholen Sie den Vorgang. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/el-GR/zotero/zotero.ftl b/chrome/locale/el-GR/zotero/zotero.ftl index 6940101cbd..c5305977b8 100644 --- a/chrome/locale/el-GR/zotero/zotero.ftl +++ b/chrome/locale/el-GR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = κ.α. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = Περισσότερες πληροφορίες general-warning = Προειδοποίηση general-type-to-continue = Type “{ $text }” to continue. general-continue = Συνέχεια @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Περισσότερες πληροφορίες +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/en-GB/zotero/zotero.ftl b/chrome/locale/en-GB/zotero/zotero.ftl index c913dedb93..31acf093e4 100644 --- a/chrome/locale/en-GB/zotero/zotero.ftl +++ b/chrome/locale/en-GB/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = Warning general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/es-ES/zotero/zotero.ftl b/chrome/locale/es-ES/zotero/zotero.ftl index a33305fa96..1bd96ab0fd 100644 --- a/chrome/locale/es-ES/zotero/zotero.ftl +++ b/chrome/locale/es-ES/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Siguiente general-learn-more = Más información +general-more-information = Más información general-warning = Advertencia general-type-to-continue = Introduzca “{ $text }” para continuar. general-continue = Continuar @@ -280,7 +281,7 @@ import-online-wrong-credentials = Error al iniciar sesión en { $targetApp }. Vu import-online-blocked-by-plugin = La importación no puede continuar con { $plugin } instalado. Por favor, desactive esta extensión e inténtelo de nuevo. import-online-relink-only = .label = Volver a vincular las citas de Mendeley Desktop -import-online-relink-kb = Más información +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } no ha podido conectarse a { $targetApp }. Compruebe su conexión a Internet e inténtelo de nuevo. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/et-EE/zotero/zotero.ftl b/chrome/locale/et-EE/zotero/zotero.ftl index d69fb0e14b..a0a53c0393 100644 --- a/chrome/locale/et-EE/zotero/zotero.ftl +++ b/chrome/locale/et-EE/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Eelmine general-next = Järgmine general-learn-more = Õpi lisa +general-more-information = Lisainfo general-warning = Hoiatus general-type-to-continue = Type “{ $text }” to continue. general-continue = Jätka @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Lisainfo +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/eu-ES/zotero/zotero.ftl b/chrome/locale/eu-ES/zotero/zotero.ftl index e9ee160ced..c1cdfa537d 100644 --- a/chrome/locale/eu-ES/zotero/zotero.ftl +++ b/chrome/locale/eu-ES/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Aurrekoa general-next = Hurrengoa general-learn-more = Gehiago jakin +general-more-information = Informazio gehiago general-warning = Kontuz! general-type-to-continue = Type “{ $text }” to continue. general-continue = Jarraitu @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Informazio gehiago +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/fa/zotero/zotero.ftl b/chrome/locale/fa/zotero/zotero.ftl index b023dfb562..33e956e729 100644 --- a/chrome/locale/fa/zotero/zotero.ftl +++ b/chrome/locale/fa/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = اطلاعات بیشتر general-warning = اخطار general-type-to-continue = Type “{ $text }” to continue. general-continue = ادامه @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = اطلاعات بیشتر +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/fi-FI/zotero/zotero.ftl b/chrome/locale/fi-FI/zotero/zotero.ftl index 3fa3004cb0..e1f35464a2 100644 --- a/chrome/locale/fi-FI/zotero/zotero.ftl +++ b/chrome/locale/fi-FI/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = ym. general-previous = Edellinen general-next = Seuraava general-learn-more = Lisätietoja +general-more-information = Lisätietoja general-warning = Varoitus general-type-to-continue = Kirjoita “{ $text }” jatkaaksesi. general-continue = Jatka @@ -279,7 +280,7 @@ import-online-wrong-credentials = Kirjautuminen sovellukseen { $targetApp } epä import-online-blocked-by-plugin = Tuontia ei voida jatkaa kun { $plugin } on asennettu. Kytke tämä lisäosa pois päältä ja yritä uudelleen. import-online-relink-only = .label = Linkitä uudelleen Mendeley Desktopin viittaukset -import-online-relink-kb = Lisätietoja +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } ei saanut yhteyttä sovellukseen { $targetApp }. Tarkista internet-yhteytesi ja yritä uudelleen. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/fr-FR/zotero/integration.ftl b/chrome/locale/fr-FR/zotero/integration.ftl index 0f6f335d70..b3919e9320 100644 --- a/chrome/locale/fr-FR/zotero/integration.ftl +++ b/chrome/locale/fr-FR/zotero/integration.ftl @@ -41,7 +41,7 @@ integration-citationDialog-settings-title = Paramètres de citation integration-citationDialog-lib-message-citation = { $search -> [true] Aucun document sélectionné, ouvert ou cité ne correspond à la recherche actuelle - *[other] Aucun document sélectionné ou ouvert + *[other] Aucun document n'est ouvert ou sélectionné } integration-citationDialog-lib-message-add-note = { $search -> @@ -50,8 +50,8 @@ integration-citationDialog-lib-message-add-note = } integration-citationDialog-lib-message-annotations = { $search -> - [true] No items with annotations match the current search - *[other] No selected or open items with annotations + [true] Aucun document avec annotations ne correspond à la recherche actuelle + *[other] Aucun document avec annotations n'est ouvert ou sélectionné } integration-citationDialog-settings-keepSorted = Trier les sources automatiquement integration-citationDialog-btn-settings = diff --git a/chrome/locale/fr-FR/zotero/zotero.ftl b/chrome/locale/fr-FR/zotero/zotero.ftl index c2214cbae5..4a82e8a532 100644 --- a/chrome/locale/fr-FR/zotero/zotero.ftl +++ b/chrome/locale/fr-FR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Précédent general-next = Suivant general-learn-more = En savoir plus +general-more-information = Plus d'informations general-warning = Avertissement general-type-to-continue = Tapez “{ $text }” pour continuer. general-continue = Continuer @@ -280,7 +281,7 @@ import-online-wrong-credentials = Connexion à { $targetApp } refusée. Veuillez import-online-blocked-by-plugin = L'importation ne peut pas aboutir avec l'extension { $plugin } installée. Veuillez désactiver cette extension et réessayer. import-online-relink-only = .label = Lier à nouveau les citations de Mendeley Desktop -import-online-relink-kb = Plus d'informations +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } n'a pas pu se connecter à { $targetApp }. Veuillez vérifier votre connexion Internet et réessayer. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/gl-ES/zotero/zotero.ftl b/chrome/locale/gl-ES/zotero/zotero.ftl index 10d965dab1..1d8433e05f 100644 --- a/chrome/locale/gl-ES/zotero/zotero.ftl +++ b/chrome/locale/gl-ES/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Seguinte general-learn-more = Saber máis +general-more-information = Máis información general-warning = Advertencia general-type-to-continue = Type “{ $text }” to continue. general-continue = Continuar @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Volver a ligar as citas de Mendeley Desktop -import-online-relink-kb = Máis información +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } non puido conectar con { $targetApp }. Por favor, comprobe a súa conexión a internet e vólvao intentar. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/he-IL/zotero/zotero.ftl b/chrome/locale/he-IL/zotero/zotero.ftl index aa5f6ffe5b..657b6aa213 100644 --- a/chrome/locale/he-IL/zotero/zotero.ftl +++ b/chrome/locale/he-IL/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = ועוד general-previous = הקודם general-next = הבא general-learn-more = מידע נוסף +general-more-information = פרטים נוספים general-warning = אזהרה general-type-to-continue = Type “{ $text }” to continue. general-continue = להמשיך @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = פרטים נוספים +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/hr-HR/zotero/zotero.ftl b/chrome/locale/hr-HR/zotero/zotero.ftl index 636c9ac05e..92161f1d8b 100644 --- a/chrome/locale/hr-HR/zotero/zotero.ftl +++ b/chrome/locale/hr-HR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = i dr. general-previous = Prethodno general-next = Sljedeće general-learn-more = Saznajte više +general-more-information = Još informacija general-warning = Upozorenje general-type-to-continue = Type “{ $text }” to continue. general-continue = Nastavi @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Još informacija +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/hu-HU/zotero/zotero.ftl b/chrome/locale/hu-HU/zotero/zotero.ftl index f7c1a66517..1f06ee04a3 100644 --- a/chrome/locale/hu-HU/zotero/zotero.ftl +++ b/chrome/locale/hu-HU/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = és mtsai. general-previous = Előző general-next = Következő general-learn-more = Bővebben +general-more-information = További információ general-warning = Figyelmeztetés general-type-to-continue = Type “{ $text }” to continue. general-continue = Folytatás @@ -279,7 +280,7 @@ import-online-wrong-credentials = A { $targetApp } bejelentkezés sikertelen. K import-online-blocked-by-plugin = Az importálás nem folytatható a telepített { $plugin } esetén. Kérjük, tiltsa le ezt a plugint, és próbálja meg újra. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = További információ +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } nem tudott csatlakozni { $targetApp }. Kérjük, ellenőrizze az internetkapcsolatot, és próbálja meg újra. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/id-ID/zotero/zotero.ftl b/chrome/locale/id-ID/zotero/zotero.ftl index 6cd346ee55..64476f11d9 100644 --- a/chrome/locale/id-ID/zotero/zotero.ftl +++ b/chrome/locale/id-ID/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Sebelum general-next = Lanjut general-learn-more = Baca lebih lanjut +general-more-information = Informasi lebih lanjut general-warning = Peringatan general-type-to-continue = Type “{ $text }” to continue. general-continue = Lanjut @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Informasi lebih lanjut +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/is-IS/zotero/zotero.ftl b/chrome/locale/is-IS/zotero/zotero.ftl index 733fdffc38..dd762c4cc7 100644 --- a/chrome/locale/is-IS/zotero/zotero.ftl +++ b/chrome/locale/is-IS/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = og fl. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = Frekari upplýsingar general-warning = Viðvörun general-type-to-continue = Type “{ $text }” to continue. general-continue = Halda áfram @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Frekari upplýsingar +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/it-IT/zotero/zotero.ftl b/chrome/locale/it-IT/zotero/zotero.ftl index 565fa2910b..f915f4aa56 100644 --- a/chrome/locale/it-IT/zotero/zotero.ftl +++ b/chrome/locale/it-IT/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Precedente general-next = Successivo general-learn-more = Approfondisci +general-more-information = Ulteriori informazioni general-warning = Attenzione general-type-to-continue = Digita “{ $text }” per continuare. general-continue = Continua @@ -280,7 +281,7 @@ import-online-wrong-credentials = L'autenticazione in { $targetApp } è fallita. import-online-blocked-by-plugin = L'importazione non può proseguire se { $plugin } è installato. Si prega di disabilitare questo plugin e riprovare. import-online-relink-only = .label = Ricollega le citazioni di Mendeley Desktop -import-online-relink-kb = Ulteriori informazioni +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } non è riuscito a connettersi a { $targetApp }. Si prega di verificare la connessione a Internet e riprovare. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ja-JP/zotero/zotero.ftl b/chrome/locale/ja-JP/zotero/zotero.ftl index d72608e3a0..939526f8eb 100644 --- a/chrome/locale/ja-JP/zotero/zotero.ftl +++ b/chrome/locale/ja-JP/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = 詳細情報の表示 +general-more-information = さらに詳しく general-warning = 警告 general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/km/zotero/zotero.ftl b/chrome/locale/km/zotero/zotero.ftl index b85913fa12..442acce6b6 100644 --- a/chrome/locale/km/zotero/zotero.ftl +++ b/chrome/locale/km/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = ព័ត៌មានបន្ថែម general-warning = បម្រាម general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = ព័ត៌មានបន្ថែម +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ko-KR/zotero/zotero.ftl b/chrome/locale/ko-KR/zotero/zotero.ftl index 8796d2284c..0f2ff586a6 100644 --- a/chrome/locale/ko-KR/zotero/zotero.ftl +++ b/chrome/locale/ko-KR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = 등 general-previous = 이전 general-next = 다음 general-learn-more = 더 알아보기 +general-more-information = 정보 더 보기 general-warning = 경고 general-type-to-continue = Type “{ $text }” to continue. general-continue = 계속 @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = 정보 더 보기 +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/lt-LT/zotero/zotero.ftl b/chrome/locale/lt-LT/zotero/zotero.ftl index b2b1dcf090..c66dc5bcc7 100644 --- a/chrome/locale/lt-LT/zotero/zotero.ftl +++ b/chrome/locale/lt-LT/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = ir kt. general-previous = Ankstesnis general-next = Tolesnis general-learn-more = Parodyti išsamiau +general-more-information = Daugiau informacijos general-warning = Įspėjimas general-type-to-continue = Type “{ $text }” to continue. general-continue = Tęsti @@ -279,7 +280,7 @@ import-online-wrong-credentials = Nepavyko prisijungti prie { $targetApp }. Iš import-online-blocked-by-plugin = Importavimo negalima tęsti, jei veikia { $plugin }. Prašome išjungti šį papildinį, tada bandykite iš naujo. import-online-relink-only = .label = Iš naujo susieti Mendeley Desktop citatas -import-online-relink-kb = Daugiau informacijos +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } negali prisijungti { $targetApp }. Prašome patikrinti interneto ryšį ir bandykite vėl. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/mn-MN/zotero/zotero.ftl b/chrome/locale/mn-MN/zotero/zotero.ftl index eea0b4605e..40e5c68b4d 100644 --- a/chrome/locale/mn-MN/zotero/zotero.ftl +++ b/chrome/locale/mn-MN/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = гэх мэт. general-previous = Previous general-next = Next general-learn-more = Илүү ихийг мэдэж аваарай +general-more-information = Дэлгэрэнгүй мэдээлэл general-warning = Анхааруулга general-type-to-continue = Type “{ $text }” to continue. general-continue = Үргэлжлүүлэх @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Дэлгэрэнгүй мэдээлэл +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/nb-NO/zotero/zotero.ftl b/chrome/locale/nb-NO/zotero/zotero.ftl index 39de6bedc5..4f52186ddb 100644 --- a/chrome/locale/nb-NO/zotero/zotero.ftl +++ b/chrome/locale/nb-NO/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Forrige general-next = Neste general-learn-more = Lær mer +general-more-information = Mer informasjon general-warning = Advarsel general-type-to-continue = Skriv “{ $text }” for å fortsette. general-continue = Fortsett @@ -279,7 +280,7 @@ import-online-wrong-credentials = Innlogging til { $targetApp } mislyktes. Vennl import-online-blocked-by-plugin = Importen kan ikke fortsette med { $plugin } installert. Deaktiver dette programtillegget og prøv på nytt. import-online-relink-only = .label = Koble sammen igjen Mendeley Desktop henvisninger -import-online-relink-kb = Mer informasjon +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } kunne ikke koble til { $targetApp }. Kontroller Internett-tilkoblingen din og prøv igjen. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/nl-NL/zotero/zotero.ftl b/chrome/locale/nl-NL/zotero/zotero.ftl index 8d23738aac..d89f3c4e37 100644 --- a/chrome/locale/nl-NL/zotero/zotero.ftl +++ b/chrome/locale/nl-NL/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = e.a. general-previous = Previous general-next = Next general-learn-more = Meer info +general-more-information = Meer informatie general-warning = Waarschuwing general-type-to-continue = Type “{ $text }” to continue. general-continue = Doorgaan @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Meer informatie +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/nn-NO/zotero/zotero.ftl b/chrome/locale/nn-NO/zotero/zotero.ftl index 0639afd6ff..91352814b0 100644 --- a/chrome/locale/nn-NO/zotero/zotero.ftl +++ b/chrome/locale/nn-NO/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = Åtvaring general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/pl-PL/zotero/preferences.ftl b/chrome/locale/pl-PL/zotero/preferences.ftl index 821a4174b5..baa5d25f6a 100644 --- a/chrome/locale/pl-PL/zotero/preferences.ftl +++ b/chrome/locale/pl-PL/zotero/preferences.ftl @@ -6,7 +6,7 @@ preferences-auto-recognize-files = preferences-file-renaming-title = Zmiana nazw plików preferences-file-renaming-intro = { -app-name } może automatycznie zmienić nazwy plików na podstawie szczegółów elementu nadrzędnego (tytuł, autor, etc.) i utrzymać nazwy plików zsynchronizowane podczas dokonywania przez Ciebie zmian. Pobrane pliki są zawsze początkowo nazywane na podstawie elementu nadrzędnego. preferences-file-renaming-configure-button = - .label = Configure File Renaming… + .label = Konfiguruj zmianę nazw plików... preferences-attachment-titles-title = Nazwy załączników preferences-attachment-titles-intro = Nazwy załączników są . Aby wspierać różne style pracy, { -app-name } może pokazywać nazwy plików zamiast nazw załączników na liście elementów. preferences-attachment-titles-show-filenames = @@ -108,15 +108,15 @@ preferences-sync-reset-restore-to-server-yes = Zamień dane w zdalnej bibliotece preferences-account-log-in = .label = Zaloguj się preferences-account-waiting-for-login = - .value = Waiting for login… + .value = Czekanie na zalogowanie... preferences-account-cancel-button = .label = { general-cancel } preferences-account-logged-out-status = - .value = (logged out) + .value = (wylogowano) preferences-account-email-label = .value = Email: preferences-account-switch-accounts = - .label = Switch Accounts… + .label = Przełącz konta… preferences-account-switch-text = Switching to a different account will remove all { -app-name } data on this computer. Before continuing, make sure all data and files you wish to keep have been synced with the “{ $username }” account or you have a backup of your { -app-name } data directory. -preferences-account-switch-confirmation-text = remove local data -preferences-account-switch-accept = Remove Data and Restart +preferences-account-switch-confirmation-text = usuń dane lokalne +preferences-account-switch-accept = Usuń dane i uruchom ponownie diff --git a/chrome/locale/pl-PL/zotero/zotero.ftl b/chrome/locale/pl-PL/zotero/zotero.ftl index 70f2b997fc..777b52a9d1 100644 --- a/chrome/locale/pl-PL/zotero/zotero.ftl +++ b/chrome/locale/pl-PL/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Poprzedni general-next = Następny general-learn-more = Dowiedz się więcej +general-more-information = Więcej informacji general-warning = Ostrzeżenie general-type-to-continue = Wpisz “{ $text }”, aby kontynuować. general-continue = Dalej @@ -158,11 +159,11 @@ zotero-toolbar-tabs-scroll-backwards = .title = Przewiń wstecz toolbar-add-attachment = .tooltiptext = { add-attachment } -recently-read = Recently Read +recently-read = Ostatnio czytane collections-menu-show-recently-read = - .label = Show { recently-read } + .label = Pokaż { recently-read } item-menu-remove-from-recently-read = - .label = Remove from { recently-read }… + .label = Usuń z { recently-read }… collections-menu-rename-collection = .label = Zmień nazwę kolekcji collections-menu-edit-saved-search = @@ -281,7 +282,7 @@ import-online-wrong-credentials = Logowanie do { $targetApp } nie powiodło się import-online-blocked-by-plugin = Importowanie nie może być kontynuowane z zainstalowaną wtyczką { $plugin }. Proszę, wyłącz tę wtyczkę i spróbuj ponownie. import-online-relink-only = .label = Zlinkuj ponownie cytowania Mendeley Desktop -import-online-relink-kb = Więcej informacji +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } nie może się połączyć z { $targetApp }. Proszę, sprawdź swoje połączenie internetowe i spróbuj ponownie. items-table-cell-notes = .aria-label = @@ -291,9 +292,9 @@ items-table-cell-notes = [many] { $count } notatek *[other] { $count } notatek } -items-column-added-by = Added By -items-column-modified-by = Modified By -items-column-last-read = Last Read +items-column-added-by = Dodany przez +items-column-modified-by = Zmodyfikowany przez +items-column-last-read = Ostatnio czytany report-error = .label = Zgłoś błąd... rtfScan-wizard = @@ -806,6 +807,6 @@ menu-normalize-attachment-titles = normalize-attachment-titles-title = Normalizuj nazwy załączników normalize-attachment-titles-text = { -app-name } automatycznie zmienia nazwy plików na dysku z użyciem metadanych elementu nadrzędnego, ale używa osobnych, prostszych nazw jak “Full Text PDF”, “Preprint PDF”, or “PDF” dla głównych załączników, aby utrzymać bardziej klarowną listę elementów i zapobiec duplikowaniu informacji.W starszych wersjach { -app-name }, podobnie jak używając różnych wtyczek, nazwy plików załączników mogą być niepotrzebnie zmieniane, aby odpowiadały nazwom plików.Czy chcesz zaktualizować wybrane załączniki, aby użyć ich prostszych nazw? Zostaną zmienione tylko główne załączniki z nazwami, które odpowiadają nazwie pliku. banner-close-button = - .aria-label = Dismiss notification + .aria-label = Odrzuć powiadomienie plugins-blocked-plugin = .message = Ta wtyczka została wyłączona przez { -app-name }. diff --git a/chrome/locale/pt-BR/zotero/zotero.ftl b/chrome/locale/pt-BR/zotero/zotero.ftl index 4b94147237..077188fed0 100644 --- a/chrome/locale/pt-BR/zotero/zotero.ftl +++ b/chrome/locale/pt-BR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Próximo general-learn-more = Saber Mais +general-more-information = Mais informações general-warning = Aviso general-type-to-continue = Digite “{ $text }” para continuar. general-continue = Continuar @@ -280,7 +281,7 @@ import-online-wrong-credentials = Login para { $targetApp } falhou. Por favor, r import-online-blocked-by-plugin = A importação não pode continuar com o { $plugin } instalado. Por favor, desabilite a extensão e tente novamente. import-online-relink-only = .label = Religar citações do Mendeley Desktop -import-online-relink-kb = Mais informações +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } não conseguiu se conectar ao { $targetApp }. Por favor, verifique sua conexão com a internet e tente novamente. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/pt-PT/zotero/zotero.ftl b/chrome/locale/pt-PT/zotero/zotero.ftl index 5ffc76259d..7286b567a3 100644 --- a/chrome/locale/pt-PT/zotero/zotero.ftl +++ b/chrome/locale/pt-PT/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Seguinte general-learn-more = Saber Mais +general-more-information = Mais Informação general-warning = Aviso general-type-to-continue = Digite “{ $text }” para continuar. general-continue = Continuar @@ -280,7 +281,7 @@ import-online-wrong-credentials = Login para { $targetApp } falhou. Por favor, r import-online-blocked-by-plugin = A importação não pode continuar com o { $plugin } instalado. Por favor, desabilite a extensão e tente novamente. import-online-relink-only = .label = Religar citações do Mendeley Desktop -import-online-relink-kb = Mais Informação +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } não conseguiu se conectar ao { $targetApp }. Por favor, verifique sua conexão com a internet e tente novamente. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ro-RO/zotero/zotero.ftl b/chrome/locale/ro-RO/zotero/zotero.ftl index c7be75d859..236b666a97 100644 --- a/chrome/locale/ro-RO/zotero/zotero.ftl +++ b/chrome/locale/ro-RO/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Anterior general-next = Următoarea general-learn-more = Învață mai multe +general-more-information = Mai multe informații general-warning = Avertisment general-type-to-continue = Type “{ $text }” to continue. general-continue = Continuă @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Mai multe informații +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ru-RU/zotero/zotero.ftl b/chrome/locale/ru-RU/zotero/zotero.ftl index a64bfcbdff..58ebe9fbb6 100644 --- a/chrome/locale/ru-RU/zotero/zotero.ftl +++ b/chrome/locale/ru-RU/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = и др. general-previous = Предыдущий general-next = Следующий general-learn-more = Узнать больше +general-more-information = Дополнительные сведения general-warning = Внимание general-type-to-continue = Введите “{ $text }”, чтобы продолжить. general-continue = Продолжить @@ -281,7 +282,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Заменить ссылки на цитирования из Mendeley Desktop -import-online-relink-kb = Дополнительные сведения +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } не смог подсоединиться к { $targetApp }. Пожалуйста, проверьте своё подключение к интернету и попробуйте снова. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/sk-SK/zotero/zotero.ftl b/chrome/locale/sk-SK/zotero/zotero.ftl index 5ae8addab3..fe2c01a7de 100644 --- a/chrome/locale/sk-SK/zotero/zotero.ftl +++ b/chrome/locale/sk-SK/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = Viac informácií general-warning = Upozornenie general-type-to-continue = Type “{ $text }” to continue. general-continue = Pokračovať @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Viac informácií +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/sl-SI/zotero/zotero.ftl b/chrome/locale/sl-SI/zotero/zotero.ftl index 96989bb58e..c64cd8b39d 100644 --- a/chrome/locale/sl-SI/zotero/zotero.ftl +++ b/chrome/locale/sl-SI/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = idr. general-previous = Nazaj general-next = Naprej general-learn-more = Več o tem +general-more-information = Podrobnosti general-warning = Opozorilo general-type-to-continue = Type “{ $text }” to continue. general-continue = Nadaljuj @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Ponovno poveži citate Mendeley Desktop -import-online-relink-kb = Podrobnosti +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/sr-RS/zotero/zotero.ftl b/chrome/locale/sr-RS/zotero/zotero.ftl index 9749e57b7d..8281057c7f 100644 --- a/chrome/locale/sr-RS/zotero/zotero.ftl +++ b/chrome/locale/sr-RS/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = и сар. general-previous = Претходно general-next = Следеће general-learn-more = Сазнајте више +general-more-information = Више података general-warning = Упозорење general-type-to-continue = Type “{ $text }” to continue. general-continue = Настави @@ -279,7 +280,7 @@ import-online-wrong-credentials = Није успела пријава на { $t import-online-blocked-by-plugin = Не можете да наставите увоз док је покренут прикључак { $plugin }. Искључите овај прикључак и покушајте поново. import-online-relink-only = .label = Поново повежи цитате из Мендељејева -import-online-relink-kb = Више података +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } не може да се повеже на { $targetApp }. Проверите везу са интернетом и покушајте поново. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/sv-SE/zotero/zotero.ftl b/chrome/locale/sv-SE/zotero/zotero.ftl index 53168a4ac0..c68def7b18 100644 --- a/chrome/locale/sv-SE/zotero/zotero.ftl +++ b/chrome/locale/sv-SE/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = m. fl. general-previous = Föregående general-next = Nästa general-learn-more = Läs mer +general-more-information = Mer information general-warning = Varning general-type-to-continue = Type “{ $text }” to continue. general-continue = Fortsätt @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Mer information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/ta/zotero/zotero.ftl b/chrome/locale/ta/zotero/zotero.ftl index 67fd87c4fc..08919735c2 100644 --- a/chrome/locale/ta/zotero/zotero.ftl +++ b/chrome/locale/ta/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = மற்றும் பலர். general-previous = முந்தையது general-next = அடுத்தது general-learn-more = மேலும் அறிக +general-more-information = மேலும் செய்தி general-warning = முன்னறிவிப்பு general-type-to-continue = தொடர “{ $text }” எனத் தட்டச்சு செய். general-continue = தொடரவும் @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = மேலும் செய்தி +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/th-TH/zotero/zotero.ftl b/chrome/locale/th-TH/zotero/zotero.ftl index 250fce7950..4e67979d5b 100644 --- a/chrome/locale/th-TH/zotero/zotero.ftl +++ b/chrome/locale/th-TH/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = คำเตือน general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/tr-TR/zotero/zotero.ftl b/chrome/locale/tr-TR/zotero/zotero.ftl index d96f051993..622d321eb4 100644 --- a/chrome/locale/tr-TR/zotero/zotero.ftl +++ b/chrome/locale/tr-TR/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = vd. general-previous = Önceki general-next = Sonraki general-learn-more = Daha Öğren +general-more-information = Daha Fazla Bilgi general-warning = Uyarı general-type-to-continue = Devam etmek için “{ $text }” yazın. general-continue = Devam et @@ -279,7 +280,7 @@ import-online-wrong-credentials = { $targetApp } için oturum açılamadı. Lüt import-online-blocked-by-plugin = { $plugin } kuruluyken içeri aktarma işlemi sürdürülemez. Lütfen bu eklentiyi devre dışı bırakın ve yeniden deneyin. import-online-relink-only = .label = Mendeley Desktop alıntılarını yeniden bağlantıla -import-online-relink-kb = Daha Fazla Bilgi +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name }, { $targetApp } uygulamasına bağlanamadı. Lütfen internet bağlantınızı kontrol edip tekrar deneyin. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/uk-UA/zotero/zotero.ftl b/chrome/locale/uk-UA/zotero/zotero.ftl index 2eddd841c4..c74620a6e3 100644 --- a/chrome/locale/uk-UA/zotero/zotero.ftl +++ b/chrome/locale/uk-UA/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = Додаткова інформація general-warning = Попередження general-type-to-continue = Type “{ $text }” to continue. general-continue = Продовжити @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = Додаткова інформація +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/vi-VN/zotero/zotero.ftl b/chrome/locale/vi-VN/zotero/zotero.ftl index d260a419fb..ee41f02394 100644 --- a/chrome/locale/vi-VN/zotero/zotero.ftl +++ b/chrome/locale/vi-VN/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = et al. general-previous = Previous general-next = Next general-learn-more = Learn More +general-more-information = More Information general-warning = Cảnh báo general-type-to-continue = Type “{ $text }” to continue. general-continue = Continue @@ -279,7 +280,7 @@ import-online-wrong-credentials = Login to { $targetApp } failed. Please re-ente import-online-blocked-by-plugin = The import cannot continue with { $plugin } installed. Please disable this plugin and try again. import-online-relink-only = .label = Relink Mendeley Desktop citations -import-online-relink-kb = More Information +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } could not connect to { $targetApp }. Please check your internet connection and try again. items-table-cell-notes = .aria-label = diff --git a/chrome/locale/zh-CN/zotero/preferences.ftl b/chrome/locale/zh-CN/zotero/preferences.ftl index 2c109f9695..bb9a5ce9a6 100644 --- a/chrome/locale/zh-CN/zotero/preferences.ftl +++ b/chrome/locale/zh-CN/zotero/preferences.ftl @@ -4,7 +4,7 @@ preferences-appearance-title = 外观和语言 preferences-auto-recognize-files = .label = 自动检索 PDF 和电子书的元数据 preferences-file-renaming-title = 文件重命名 -preferences-file-renaming-intro = { -app-name } 可以根据上级条目的元数据(如标题、作者等)自动重命名文件,并在你进行更改时保持文件名同步。下载的文件始终会根据父项进行初始命名。 +preferences-file-renaming-intro = { -app-name } 可以根据上级条目的元数据(如标题、作者等)自动重命名文件,并在你进行更改时保持文件名同步。下载的文件始终会根据上级条目进行初始命名。 preferences-file-renaming-configure-button = .label = 配置文件重命名… preferences-attachment-titles-title = 附件标题 @@ -111,7 +111,7 @@ preferences-account-cancel-button = preferences-account-logged-out-status = .value = (已退出登录) preferences-account-email-label = - .value = 邮箱: + .value = 邮箱: preferences-account-switch-accounts = .label = 切换账户… preferences-account-switch-text = 切换到其他账户将删除此计算机中所有的 { -app-name } 数据。在继续之前,请确保您想要保留的所有数据和文件都已与 “{ $username }” 账户同步,或者您已经备份 { -app-name } 数据目录。 diff --git a/chrome/locale/zh-CN/zotero/zotero.ftl b/chrome/locale/zh-CN/zotero/zotero.ftl index 92bf2c9e02..2fdb3ea2f1 100644 --- a/chrome/locale/zh-CN/zotero/zotero.ftl +++ b/chrome/locale/zh-CN/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = 等 general-previous = 前一页 general-next = 下一步 general-learn-more = 了解更多 +general-more-information = 更多信息 general-warning = 警告 general-type-to-continue = 输入 “{ $text }” 以继续 general-continue = 继续 @@ -278,7 +279,7 @@ import-online-wrong-credentials = 登录 { $targetApp } 失败。请重新输入 import-online-blocked-by-plugin = 安装了 { $plugin } 后导入无法继续。请禁用此插件并重试。 import-online-relink-only = .label = 重新链接 Mendeley 桌面程序参考文献 -import-online-relink-kb = 更多信息 +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } 无法连接到 { $targetApp }。请检查您的网络连接并重试。 items-table-cell-notes = .aria-label = diff --git a/chrome/locale/zh-TW/zotero/zotero.ftl b/chrome/locale/zh-TW/zotero/zotero.ftl index 8ae8308086..5e3092a299 100644 --- a/chrome/locale/zh-TW/zotero/zotero.ftl +++ b/chrome/locale/zh-TW/zotero/zotero.ftl @@ -59,6 +59,7 @@ general-et-al = …等 general-previous = 上一步 general-next = 下一步 general-learn-more = 了解更多 +general-more-information = 更多資訊 general-warning = 警告 general-type-to-continue = Type “{ $text }” to continue. general-continue = 繼續 @@ -279,7 +280,7 @@ import-online-wrong-credentials = 登入 { $targetApp } 失敗。請重新輸入 import-online-blocked-by-plugin = 在 { $plugin } 裝著的時候無法繼續匯入,請停用這個附加元件,然後再試一次。 import-online-relink-only = .label = 重新連結Mendeley桌面應用程式引文 -import-online-relink-kb = 更多資訊 +import-online-relink-kb = { general-more-information } import-online-connection-error = { -app-name } 無法連結到 { $targetApp }。請檢查您的網路連線後再試一次。 items-table-cell-notes = .aria-label = From 4b14ca9af48ad85c3dfc116f64a3f76e5f6257a8 Mon Sep 17 00:00:00 2001 From: Abe Jellinek <1770299+AbeJellinek@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:10:18 -0400 Subject: [PATCH 06/30] Set Recently Read threshold relative to most recent item (#5878) And persist cutoff within session --- chrome/content/zotero/collectionTree.jsx | 16 +- .../content/zotero/xpcom/collectionTreeRow.js | 19 +- chrome/content/zotero/xpcom/data/items.js | 49 ++ chrome/content/zotero/xpcom/data/search.js | 10 +- test/content/support.js | 4 + test/tests/itemTreeTest.js | 429 +++++++++++------- test/tests/itemsTest.js | 111 +++++ 7 files changed, 460 insertions(+), 178 deletions(-) diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index 03c161b0fb..5a264f19c0 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -3022,14 +3022,16 @@ var CollectionTree = class CollectionTree extends LibraryTree { // Recently Read if (showRecentlyRead && this._isFilterEmpty()) { - let s = new Zotero.Search(); - s.libraryID = libraryID; - s.name = Zotero.getString('recently-read'); - s.addCondition('libraryID', 'is', libraryID); - s.addCondition('lastRead', 'isInTheLast', '14 days'); - s.addCondition('includeParents', 'true'); rows.splice(row + 1 + newRows, 0, - new Zotero.CollectionTreeRow(this, 'recentlyRead', s, level + 1)); + new Zotero.CollectionTreeRow(this, + 'recentlyRead', + { + libraryID, + name: Zotero.getString('recently-read'), + }, + level + 1 + ) + ); newRows++; } diff --git a/chrome/content/zotero/xpcom/collectionTreeRow.js b/chrome/content/zotero/xpcom/collectionTreeRow.js index e5a826c760..a9e75151c8 100644 --- a/chrome/content/zotero/xpcom/collectionTreeRow.js +++ b/chrome/content/zotero/xpcom/collectionTreeRow.js @@ -364,14 +364,25 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = async function () { return Zotero.CollectionTreeCache.lastSearch; } + var s; var includeScopeChildren = false; // Create/load the inner search - if (this.ref instanceof Zotero.Search) { - var s = this.ref; + if (this.isRecentlyRead()) { + let ids = await Zotero.Items.getLastRead(this.ref.libraryID); + let tmpTable = await Zotero.Search.idsToTempTable(ids, { idColumn: 'id' }); + s = new Zotero.Search(); + s.libraryID = this.ref.libraryID; + s.addCondition('tempTable', 'is', tmpTable); + this.onUnload = async function () { + await Zotero.DB.queryAsync(`DROP TABLE IF EXISTS ${tmpTable}`, false, { noCache: true }); + }; + } + else if (this.ref instanceof Zotero.Search) { + s = this.ref; } else if (this.isDuplicates()) { - var s = await this.ref.getSearchObject(); + s = await this.ref.getSearchObject(); let tmpTable; for (let id in s.conditions) { let c = s.conditions[id]; @@ -386,7 +397,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = async function () { }; } else { - var s = new Zotero.Search(); + s = new Zotero.Search(); if (!this.isFeeds()) { s.libraryID = this.ref.libraryID; } diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index 87f878cd1e..07487c95ef 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -139,7 +139,56 @@ Zotero.Items = function () { return this.getAsync(ids); }; + this._lastReadCutoffs = new Map(); // libraryID -> cutoff timestamp + /** + * Get items with recently read attachments in a library. + * + * "Recently read" means read within two weeks of the most recently read + * attachment in the library (cutoff fixed within each session). + * + * @param {Integer} libraryID + * @return {Promise} Item IDs (parent items, or standalone attachments) + */ + this.getLastRead = async function (libraryID) { + let twoWeeksInSeconds = 14 * 24 * 60 * 60; + + // Freeze the cutoff on first load so that reading something new + // doesn't cause old items to vanish mid-session + let cutoff; + if (this._lastReadCutoffs.has(libraryID)) { + cutoff = this._lastReadCutoffs.get(libraryID); + } + else { + let maxSQL = "SELECT MAX(IA.lastRead) FROM itemAttachments IA " + + "JOIN items I ON (I.itemID = IA.itemID) " + + "WHERE I.libraryID = ? " + + "AND IA.lastRead IS NOT NULL " + + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) " + + "AND COALESCE(IA.parentItemID, IA.itemID) NOT IN (SELECT itemID FROM deletedItems)"; + let maxLastRead = await Zotero.DB.valueQueryAsync(maxSQL, [libraryID]); + if (maxLastRead) { + cutoff = maxLastRead - twoWeeksInSeconds; + this._lastReadCutoffs.set(libraryID, cutoff); + } + else { + // No items with lastRead. + // Don't persist this - we want to calculate a new cutoff if items come in later. + cutoff = 0; + } + } + + let sql = "SELECT DISTINCT COALESCE(IA.parentItemID, IA.itemID) " + + "FROM itemAttachments IA " + + "JOIN items I ON (I.itemID = COALESCE(IA.parentItemID, IA.itemID)) " + + "WHERE IA.lastRead >= ? " + + "AND I.libraryID = ? " + + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) " + + "AND COALESCE(IA.parentItemID, IA.itemID) NOT IN (SELECT itemID FROM deletedItems)"; + return Zotero.DB.columnQueryAsync(sql, [cutoff, libraryID]); + }; + + // // Bulk data loading functions // diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js index 594d1cbede..d66ee0d3d9 100644 --- a/chrome/content/zotero/xpcom/data/search.js +++ b/chrome/content/zotero/xpcom/data/search.js @@ -923,25 +923,25 @@ Zotero.Search.prototype.getSQLParams = async function () { /* * Batch insert */ -Zotero.Search.idsToTempTable = async function (ids) { +Zotero.Search.idsToTempTable = async function (ids, { idColumn = 'itemID' } = {}) { var tmpTable = "tmpSearchResults_" + Zotero.randomString(8); - + Zotero.debug(`Creating ${tmpTable} with ${ids.length} item${ids.length != 1 ? 's' : ''}`); var sql = "CREATE TEMPORARY TABLE " + tmpTable; if (ids.length) { sql += " AS " - + "WITH cte(itemID) AS (" + + `WITH cte(${idColumn}) AS (` + "VALUES " + ids.map(id => "(" + parseInt(id) + ")").join(',') + ") " + "SELECT * FROM cte"; } else { - sql += " (itemID INTEGER PRIMARY KEY)"; + sql += ` (${idColumn} INTEGER PRIMARY KEY)`; } await Zotero.DB.queryAsync(sql, false, { debug: false, noCache: true }); if (ids.length) { await Zotero.DB.queryAsync( - `CREATE UNIQUE INDEX ${tmpTable}_itemID ON ${tmpTable}(itemID)`, + `CREATE UNIQUE INDEX ${tmpTable}_${idColumn} ON ${tmpTable}(${idColumn})`, false, { noCache: true diff --git a/test/content/support.js b/test/content/support.js index e2386dcb70..8b51d7df1c 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -1126,6 +1126,7 @@ function importTextAttachment(parentItem, options = {}) { return importFileAttachment('test.txt', { contentType: 'text/plain', charset: 'utf-8', + libraryID: options.libraryID, parentID: parentItem ? parentItem.id : null, title: options.title }); @@ -1136,6 +1137,7 @@ function importHTMLAttachment(parentItem, options = {}) { return importFileAttachment('test.html', { contentType: 'text/html', charset: 'utf-8', + libraryID: options.libraryID, parentID: parentItem ? parentItem.id : null, title: options.title }); @@ -1151,6 +1153,7 @@ function importSnapshotAttachment(parentItem, options = {}) { file, contentType: 'text/html', charset: 'utf-8', + libraryID: options.libraryID, parentItemID: parentItem ? parentItem.id : null, }); } @@ -1161,6 +1164,7 @@ async function importPDFAttachment(parentItem, options = {}) { 'test.pdf', { contentType: 'application/pdf', + libraryID: options.libraryID, parentID: parentItem ? parentItem.id : null, title: options.title } diff --git a/test/tests/itemTreeTest.js b/test/tests/itemTreeTest.js index 9a8cde060b..b2b533df1c 100644 --- a/test/tests/itemTreeTest.js +++ b/test/tests/itemTreeTest.js @@ -1107,209 +1107,314 @@ describe("Zotero.ItemTree", function () { }); }); - it("should re-sort by Last Read when child attachmentLastRead is updated in the user library", async function () { - let userLibraryID = Zotero.Libraries.userLibraryID; - let item1 = await createDataObject('item', { libraryID: userLibraryID }); - let attachment1 = await importPDFAttachment(item1); - let item2 = await createDataObject('item', { libraryID: userLibraryID }); - let attachment2 = await importPDFAttachment(item2); - assert.notOk(item1.getItemLastRead()); - assert.notOk(item2.getItemLastRead()); - - // attachment2 is more recently opened - attachment1.attachmentLastRead = Math.round(Date.now() / 1000) - 5; - attachment2.attachmentLastRead = Math.round(Date.now() / 1000); - await attachment1.saveTx(); - await attachment2.saveTx(); + describe("Recently Read", function () { + before(async function () { + await resetData(); + }); + afterEach(function () { + Zotero.Items._lastReadCutoffs.clear(); + }); - await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); - assert.equal(zp.getCollectionTreeRow().id, 'Y' + userLibraryID); - await waitForItemsLoad(win); - assert.isAbove(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); + it("should re-sort by Last Read when child attachmentLastRead is updated in the user library", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let item1 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment1 = await importPDFAttachment(item1); + let item2 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment2 = await importPDFAttachment(item2); + assert.notOk(item1.getItemLastRead()); + assert.notOk(item2.getItemLastRead()); - // Now make attachment2 much less recently opened - attachment2.attachmentLastRead = Math.round(Date.now() / 1000) - 60; - await attachment2.saveTx(); + // attachment2 is more recently opened + attachment1.attachmentLastRead = Math.round(Date.now() / 1000) - 5; + attachment2.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment1.saveTx(); + await attachment2.saveTx(); - assert.isBelow(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - }); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + assert.equal(zp.getCollectionTreeRow().id, 'Y' + userLibraryID); + await waitForItemsLoad(win); + assert.isAbove(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - it("should re-sort by Last Read when child attachmentLastRead is updated in a group library", async function () { - let groupLibraryID = (await createGroup()).libraryID; - let item1 = await createDataObject('item', { libraryID: groupLibraryID }); - let attachment1 = await importPDFAttachment(item1); - let item2 = await createDataObject('item', { libraryID: groupLibraryID }); - let attachment2 = await importPDFAttachment(item2); - assert.notOk(item1.getItemLastRead()); - assert.notOk(item2.getItemLastRead()); + // Now make attachment2 much less recently opened + attachment2.attachmentLastRead = Math.round(Date.now() / 1000) - 60; + await attachment2.saveTx(); - // attachment2 is more recently opened - attachment1.attachmentLastRead = Math.round(Date.now() / 1000) - 5; - attachment2.attachmentLastRead = Math.round(Date.now() / 1000); - await attachment1.saveTx(); - await attachment2.saveTx(); + assert.isBelow(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); - assert.equal(zp.getCollectionTreeRow().id, 'Y' + groupLibraryID); - await waitForItemsLoad(win); - assert.isAbove(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); + await item1.eraseTx(); + await item2.eraseTx(); + }); - // Now make attachment2 much less recently opened - attachment2.attachmentLastRead = Math.round(Date.now() / 1000) - 60; - await attachment2.saveTx(); + it("should re-sort by Last Read when child attachmentLastRead is updated in a group library", async function () { + let groupLibraryID = (await createGroup()).libraryID; + let item1 = await createDataObject('item', { libraryID: groupLibraryID }); + let attachment1 = await importPDFAttachment(item1); + let item2 = await createDataObject('item', { libraryID: groupLibraryID }); + let attachment2 = await importPDFAttachment(item2); + assert.notOk(item1.getItemLastRead()); + assert.notOk(item2.getItemLastRead()); - assert.isBelow(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - }); - - describe("Recently Read quicksearch", function () { - let quicksearch; + // attachment2 is more recently opened + attachment1.attachmentLastRead = Math.round(Date.now() / 1000) - 5; + attachment2.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment1.saveTx(); + await attachment2.saveTx(); - before(() => { - quicksearch = win.document.getElementById('zotero-tb-search-textbox'); - }); - afterEach(async () => { - quicksearch.value = ""; - quicksearch.doCommand(); - await zp.itemsView._refreshPromise; - }); + await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); + assert.equal(zp.getCollectionTreeRow().id, 'Y' + groupLibraryID); + await waitForItemsLoad(win); + assert.isAbove(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - it("should find parent item by title", async function () { - let userLibraryID = Zotero.Libraries.userLibraryID; - let item = await createDataObject('item', { title: 'Unique Parent Title ZZZ' }); - let attachment = await importPDFAttachment(item); - attachment.attachmentLastRead = Math.round(Date.now() / 1000); - await attachment.saveTx(); + // Now make attachment2 much less recently opened + attachment2.attachmentLastRead = Math.round(Date.now() / 1000) - 60; + await attachment2.saveTx(); - await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); - assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + assert.isBelow(zp.itemsView.getRowIndexByID(item1.id), zp.itemsView.getRowIndexByID(item2.id)); - quicksearch.value = "Unique Parent Title ZZZ"; - quicksearch.doCommand(); - await zp.itemsView._refreshPromise; + await item1.eraseTx(); + await item2.eraseTx(); + }); - assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); - }); + it("should show empty Recently Read when no items have been read", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let item = await createDataObject('item', { libraryID: userLibraryID }); - it("should not show non-matching parent item", async function () { - let userLibraryID = Zotero.Libraries.userLibraryID; - let matchItem = await createDataObject('item', { title: 'Matching Item AAA' }); - let matchAttachment = await importPDFAttachment(matchItem); - matchAttachment.attachmentLastRead = Math.round(Date.now() / 1000); - await matchAttachment.saveTx(); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); - let otherItem = await createDataObject('item', { title: 'Other Item BBB' }); - let otherAttachment = await importPDFAttachment(otherItem); - otherAttachment.attachmentLastRead = Math.round(Date.now() / 1000); - await otherAttachment.saveTx(); + assert.equal(zp.itemsView.rowCount, 0); - await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); - assert.isNumber(zp.itemsView.getRowIndexByID(matchItem.id)); - assert.isNumber(zp.itemsView.getRowIndexByID(otherItem.id)); + await item.eraseTx(); + }); - quicksearch.value = "Matching Item AAA"; - quicksearch.doCommand(); - await zp.itemsView._refreshPromise; + it("should show items read more than 14 days ago if within 14 days of the most recently read item", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let threeMonthsAgo = Math.round(Date.now() / 1000) - (90 * 24 * 60 * 60); - assert.isNumber(zp.itemsView.getRowIndexByID(matchItem.id)); - assert.isFalse(zp.itemsView.getRowIndexByID(otherItem.id)); - }); - }); + let item1 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment1 = await importPDFAttachment(item1); + attachment1.attachmentLastRead = threeMonthsAgo; + await attachment1.saveTx(); - describe("Remove from Recently Read", function () { - it("should remove a parent item when the parent is selected", async function () { - let userLibraryID = Zotero.Libraries.userLibraryID; - let item = await createDataObject('item'); - let attachment = await importPDFAttachment(item); + let item2 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment2 = await importPDFAttachment(item2); + attachment2.attachmentLastRead = threeMonthsAgo - (5 * 24 * 60 * 60); + await attachment2.saveTx(); - attachment.attachmentLastRead = Math.round(Date.now() / 1000); - await attachment.saveTx(); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); - await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); - assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + // Both should appear -- within 14 days of each other + assert.isNumber(zp.itemsView.getRowIndexByID(item1.id)); + assert.isNumber(zp.itemsView.getRowIndexByID(item2.id)); - await zp.itemsView.selectItem(item.id); - await zp.itemsView.deleteSelection(); + await item1.eraseTx(); + await item2.eraseTx(); + }); - assert.isNull(attachment.attachmentLastRead); - assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); - }); + it("should not show items read more than 14 days before the most recently read item", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let threeMonthsAgo = Math.round(Date.now() / 1000) - (90 * 24 * 60 * 60); - it("should clear only the selected child attachment's lastRead", async function () { - let userLibraryID = Zotero.Libraries.userLibraryID; - let item = await createDataObject('item'); - let attachment1 = await importPDFAttachment(item); - let attachment2 = await importPDFAttachment(item); + let item1 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment1 = await importPDFAttachment(item1); + attachment1.attachmentLastRead = threeMonthsAgo; + await attachment1.saveTx(); - let lastRead = Math.round(Date.now() / 1000); - attachment1.attachmentLastRead = lastRead; - attachment2.attachmentLastRead = lastRead; - await attachment1.saveTx(); - await attachment2.saveTx(); + let item2 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment2 = await importPDFAttachment(item2); + attachment2.attachmentLastRead = threeMonthsAgo - (20 * 24 * 60 * 60); + await attachment2.saveTx(); - await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); - // Expand the parent and select the first child attachment - let parentRow = zp.itemsView.getRowIndexByID(item.id); - if (!zp.itemsView.isContainerOpen(parentRow)) { - await zp.itemsView.toggleOpenState(parentRow); - } - await zp.itemsView.selectItem(attachment1.id); + // item1 should appear, item2 should not -- read 20 days before most recent + assert.isNumber(zp.itemsView.getRowIndexByID(item1.id)); + assert.isFalse(zp.itemsView.getRowIndexByID(item2.id)); - await zp.itemsView.deleteSelection(); + await item1.eraseTx(); + await item2.eraseTx(); + }); - assert.isNull(attachment1.attachmentLastRead); - assert.equal(attachment2.attachmentLastRead, lastRead); - }); + it("should not remove old items when a new item is read", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let threeMonthsAgo = Math.round(Date.now() / 1000) - (90 * 24 * 60 * 60); - it("should remove an item from a read-only group library", async function () { - let group = await createGroup(); - let groupLibraryID = group.libraryID; - let item = await createDataObject('item', { libraryID: groupLibraryID }); - let attachment = await importPDFAttachment(item); + let item1 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment1 = await importPDFAttachment(item1); + attachment1.attachmentLastRead = threeMonthsAgo; + await attachment1.saveTx(); - attachment.attachmentLastRead = Math.round(Date.now() / 1000); - await attachment.saveTx(); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(item1.id)); - // Make the group read-only after creating test data - group.editable = false; - await group.saveTx(); + // Read something new, so it shifts the window far away from item1 + let item2 = await createDataObject('item', { libraryID: userLibraryID }); + let attachment2 = await importPDFAttachment(item2); + attachment2.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment2.saveTx(); + await waitForItemsLoad(win); - await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); - assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + // item1 should still be visible + assert.isNumber(zp.itemsView.getRowIndexByID(item1.id)); + assert.isNumber(zp.itemsView.getRowIndexByID(item2.id)); - await zp.itemsView.selectItem(item.id); - await zp.itemsView.deleteSelection(); + await item1.eraseTx(); + await item2.eraseTx(); + }); - assert.isNull(attachment.attachmentLastRead); - assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); - }); + describe("With Quick Search", function () { + let quicksearch; - it("should remove a group item when its lastRead synced setting is cleared", async function () { - let group = await createGroup(); - let groupLibraryID = group.libraryID; - let item = await createDataObject('item', { libraryID: groupLibraryID }); - let attachment = await importPDFAttachment(item); - let key = attachment._getLastReadSettingKey(); + before(() => { + quicksearch = win.document.getElementById('zotero-tb-search-textbox'); + }); + afterEach(async () => { + quicksearch.value = ""; + quicksearch.doCommand(); + await zp.itemsView._refreshPromise; + }); - await Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, key, Math.round(Date.now() / 1000)); + it("should find parent item by title", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let item = await createDataObject('item', { title: 'Unique Parent Title ZZZ' }); + let attachment = await importPDFAttachment(item); + attachment.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment.saveTx(); - await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); - await waitForItemsLoad(win); - assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); - // Simulate clearing via sync (as if another device removed it) - await Zotero.SyncedSettings.clear(Zotero.Libraries.userLibraryID, key, { skipDeleteLog: true }); + quicksearch.value = "Unique Parent Title ZZZ"; + quicksearch.doCommand(); + await zp.itemsView._refreshPromise; - // Wait for the notify to propagate and the view to refresh - await zp.itemsView._refreshPromise; + assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + }); - assert.isNull(attachment.attachmentLastRead); - assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); + it("should not show non-matching parent item", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let matchItem = await createDataObject('item', { title: 'Matching Item AAA' }); + let matchAttachment = await importPDFAttachment(matchItem); + matchAttachment.attachmentLastRead = Math.round(Date.now() / 1000); + await matchAttachment.saveTx(); + + let otherItem = await createDataObject('item', { title: 'Other Item BBB' }); + let otherAttachment = await importPDFAttachment(otherItem); + otherAttachment.attachmentLastRead = Math.round(Date.now() / 1000); + await otherAttachment.saveTx(); + + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(matchItem.id)); + assert.isNumber(zp.itemsView.getRowIndexByID(otherItem.id)); + + quicksearch.value = "Matching Item AAA"; + quicksearch.doCommand(); + await zp.itemsView._refreshPromise; + + assert.isNumber(zp.itemsView.getRowIndexByID(matchItem.id)); + assert.isFalse(zp.itemsView.getRowIndexByID(otherItem.id)); + }); + }); + + describe("After Remove from Recently Read", function () { + it("should remove a parent item when the parent is selected", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let item = await createDataObject('item'); + let attachment = await importPDFAttachment(item); + + attachment.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment.saveTx(); + + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + + await zp.itemsView.selectItem(item.id); + await zp.itemsView.deleteSelection(); + + assert.isNull(attachment.attachmentLastRead); + assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); + }); + + it("should clear only the selected child attachment's lastRead", async function () { + let userLibraryID = Zotero.Libraries.userLibraryID; + let item = await createDataObject('item'); + let attachment1 = await importPDFAttachment(item); + let attachment2 = await importPDFAttachment(item); + + let lastRead = Math.round(Date.now() / 1000); + attachment1.attachmentLastRead = lastRead; + attachment2.attachmentLastRead = lastRead; + await attachment1.saveTx(); + await attachment2.saveTx(); + + await zp.setVirtual(userLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + + // Expand the parent and select the first child attachment + let parentRow = zp.itemsView.getRowIndexByID(item.id); + if (!zp.itemsView.isContainerOpen(parentRow)) { + await zp.itemsView.toggleOpenState(parentRow); + } + await zp.itemsView.selectItem(attachment1.id); + + await zp.itemsView.deleteSelection(); + + assert.isNull(attachment1.attachmentLastRead); + assert.equal(attachment2.attachmentLastRead, lastRead); + }); + + it("should remove an item from a read-only group library", async function () { + let group = await createGroup(); + let groupLibraryID = group.libraryID; + let item = await createDataObject('item', { libraryID: groupLibraryID }); + let attachment = await importPDFAttachment(item); + + attachment.attachmentLastRead = Math.round(Date.now() / 1000); + await attachment.saveTx(); + + // Make the group read-only after creating test data + group.editable = false; + await group.saveTx(); + + await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + + await zp.itemsView.selectItem(item.id); + await zp.itemsView.deleteSelection(); + + assert.isNull(attachment.attachmentLastRead); + assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); + }); + + it("should remove a group item when its lastRead synced setting is cleared", async function () { + let group = await createGroup(); + let groupLibraryID = group.libraryID; + let item = await createDataObject('item', { libraryID: groupLibraryID }); + let attachment = await importPDFAttachment(item); + let key = attachment._getLastReadSettingKey(); + + await Zotero.SyncedSettings.set(Zotero.Libraries.userLibraryID, key, Math.round(Date.now() / 1000)); + + await zp.setVirtual(groupLibraryID, 'recentlyRead', true, true); + await waitForItemsLoad(win); + assert.isNumber(zp.itemsView.getRowIndexByID(item.id)); + + // Simulate clearing via sync (as if another device removed it) + await Zotero.SyncedSettings.clear(Zotero.Libraries.userLibraryID, key, { skipDeleteLog: true }); + + // Wait for the notify to propagate and the view to refresh + await zp.itemsView._refreshPromise; + + assert.isNull(attachment.attachmentLastRead); + assert.isFalse(zp.itemsView.getRowIndexByID(item.id)); + }); }); }); diff --git a/test/tests/itemsTest.js b/test/tests/itemsTest.js index b7d5281d7f..2f101ed204 100644 --- a/test/tests/itemsTest.js +++ b/test/tests/itemsTest.js @@ -655,4 +655,115 @@ describe("Zotero.Items", function () { } }); }); + + describe("#getLastRead()", function () { + it("should return items read within two weeks of the most recently read", async function () { + let group = await createGroup(); + let libraryID = group.libraryID; + let now = Math.round(Date.now() / 1000); + + let item1 = await createDataObject('item', { libraryID }); + let att1 = await importPDFAttachment(item1); + att1.attachmentLastRead = now; + await att1.saveTx(); + + let item2 = await createDataObject('item', { libraryID }); + let att2 = await importPDFAttachment(item2); + att2.attachmentLastRead = now - (10 * 24 * 60 * 60); // 10 days before + await att2.saveTx(); + + let item3 = await createDataObject('item', { libraryID }); + let att3 = await importPDFAttachment(item3); + att3.attachmentLastRead = now - (20 * 24 * 60 * 60); // 20 days before + await att3.saveTx(); + + let ids = await Zotero.Items.getLastRead(libraryID); + assert.include(ids, item1.id); + assert.include(ids, item2.id); + assert.notInclude(ids, item3.id); + }); + + it("should return empty array when no items have been read", async function () { + let group = await createGroup(); + let ids = await Zotero.Items.getLastRead(group.libraryID); + assert.lengthOf(ids, 0); + }); + + it("should exclude deleted items", async function () { + let group = await createGroup(); + let libraryID = group.libraryID; + let now = Math.round(Date.now() / 1000); + + let item = await createDataObject('item', { libraryID }); + let att = await importPDFAttachment(item); + att.attachmentLastRead = now; + await att.saveTx(); + item.deleted = true; + await item.saveTx(); + + let ids = await Zotero.Items.getLastRead(libraryID); + assert.notInclude(ids, item.id); + }); + + it("should return items read months ago if within two weeks of each other", async function () { + let group = await createGroup(); + let libraryID = group.libraryID; + let threeMonthsAgo = Math.round(Date.now() / 1000) - (90 * 24 * 60 * 60); + + let item1 = await createDataObject('item', { libraryID }); + let att1 = await importPDFAttachment(item1); + att1.attachmentLastRead = threeMonthsAgo; + await att1.saveTx(); + + let item2 = await createDataObject('item', { libraryID }); + let att2 = await importPDFAttachment(item2); + att2.attachmentLastRead = threeMonthsAgo - (5 * 24 * 60 * 60); + await att2.saveTx(); + + let ids = await Zotero.Items.getLastRead(libraryID); + assert.include(ids, item1.id); + assert.include(ids, item2.id); + }); + + it("should return standalone attachments without parents", async function () { + let group = await createGroup(); + let libraryID = group.libraryID; + let now = Math.round(Date.now() / 1000); + + let att = await importPDFAttachment(null, { libraryID }); + att.attachmentLastRead = now; + await att.saveTx(); + + let ids = await Zotero.Items.getLastRead(libraryID); + assert.include(ids, att.id); + }); + + it("should freeze cutoff so old items persist when a new item is read", async function () { + let group = await createGroup(); + let libraryID = group.libraryID; + let threeMonthsAgo = Math.round(Date.now() / 1000) - (90 * 24 * 60 * 60); + let now = Math.round(Date.now() / 1000); + + // Read an item 3 months ago + let item1 = await createDataObject('item', { libraryID }); + let att1 = await importPDFAttachment(item1); + att1.attachmentLastRead = threeMonthsAgo; + await att1.saveTx(); + + // First call freezes the cutoff based on this item + let ids = await Zotero.Items.getLastRead(libraryID); + assert.include(ids, item1.id); + + // Read something new today -- would shift the window if not frozen + let item2 = await createDataObject('item', { libraryID }); + let att2 = await importPDFAttachment(item2); + att2.attachmentLastRead = now; + await att2.saveTx(); + + // item1 should still appear because the cutoff is frozen + ids = await Zotero.Items.getLastRead(libraryID); + assert.include(ids, item1.id); + assert.include(ids, item2.id); + }); + }); }); From 117fa1dddbc18925d84536fc9493b1d6cb294afb Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 8 Apr 2026 11:18:34 -0400 Subject: [PATCH 07/30] Update citeproc.js to include Juris-M/citeproc-js#275 --- chrome/content/zotero/xpcom/citeproc.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/citeproc.js b/chrome/content/zotero/xpcom/citeproc.js index 013529d0bd..033978a589 100644 --- a/chrome/content/zotero/xpcom/citeproc.js +++ b/chrome/content/zotero/xpcom/citeproc.js @@ -21128,7 +21128,14 @@ CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) { if (begin > 100 && begin % 100 && parseInt((begin / 100), 10) === parseInt((end / 100), 10)) { m[3] = "" + (end % 100); } else if (begin >= 10000) { - m[3] = "" + (end % 1000); + e = "" + end; + for (var i = 3; i < e.length; i++) { + var divisor = Math.pow(10, i); + if (Math.floor(begin / divisor) === Math.floor(end / divisor)) { + m[3] = "" + (end % divisor); + break; + } + } } } if (m[2].slice(1) === m[0]) { From c3d80587944874f2a04f20e460672fc960a2e290 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 8 Apr 2026 14:49:39 -0400 Subject: [PATCH 08/30] Update locales from Transifex --- chrome/locale/it-IT/zotero/fileRenaming.ftl | 10 +++++----- chrome/locale/it-IT/zotero/integration.ftl | 16 ++++++++-------- chrome/locale/it-IT/zotero/preferences.ftl | 14 +++++++------- chrome/locale/it-IT/zotero/reader.ftl | 6 +++--- chrome/locale/it-IT/zotero/zotero.ftl | 18 +++++++++--------- chrome/locale/pl-PL/zotero/fileRenaming.ftl | 10 +++++----- chrome/locale/pl-PL/zotero/integration.ftl | 10 +++++----- chrome/locale/pl-PL/zotero/reader.ftl | 6 +++--- chrome/locale/pl-PL/zotero/zotero.ftl | 6 +++--- 9 files changed, 48 insertions(+), 48 deletions(-) diff --git a/chrome/locale/it-IT/zotero/fileRenaming.ftl b/chrome/locale/it-IT/zotero/fileRenaming.ftl index 838b9e9be0..324b6b8674 100644 --- a/chrome/locale/it-IT/zotero/fileRenaming.ftl +++ b/chrome/locale/it-IT/zotero/fileRenaming.ftl @@ -1,6 +1,6 @@ file-renaming-settings-window = - .title = Configure File Renaming -file-renaming-settings-library-label = Library: + .title = Configura rinominazione dei file +file-renaming-settings-library-label = Biblioteca: file-renaming-auto-rename-files = .label = Rinomina file automaticamente file-renaming-file-types = Rinomina file di questi tipi: @@ -20,7 +20,7 @@ file-renaming-rename-linked = .label = Rinomina i file collegati file-renaming-format-title = Formato del nome dei file file-renaming-format-instructions = È possibile personalizzare lo schema con cui { -app-name } rinomina i file allegati dai metadati dell'elemento genitore. -file-renaming-format-instructions-example = For example, "{ $example }" in this template will be replaced with the title of the parent item, truncated at 50 characters. +file-renaming-format-instructions-example = Ad esempio, "{ $example }" in questo modello sarà sostituito dal titolo dell'elemento genitore, troncato dopo 50 caratteri. file-renaming-format-instructions-more = Vedi la per ulteriori informazioni (in inglese). file-renaming-format-template = Schema di rinominazione: file-renaming-format-preview = Anteprima: @@ -33,13 +33,13 @@ file-renaming-cancel-button = .label = { general-cancel } file-renaming-auto-rename-prompt-title = Modificate le impostazioni di auto-rinominazione file-renaming-auto-rename-prompt-body = Vuoi rinominare i file nella tua biblioteca in accordo con le nuove impostazioni? -file-renaming-auto-rename-prompt-body-library = Would you like to rename existing files in library "{ $library }" to match the new settings? +file-renaming-auto-rename-prompt-body-library = Vuoi rinominare i file nella biblioteca "{ $library }" in accordo con le nuove impostazioni? file-renaming-auto-rename-prompt-yes = { file-renaming-preview-changes } file-renaming-auto-rename-prompt-no = Mantieni i nomi correnti dei file file-renaming-preview-window = .title = Rinomina file file-renaming-preview-window-loading = Caricamento in corso… file-renaming-preview-window-intro = { -app-name } rinominerà i seguenti file nella tua libreria secondo i metadati del rispettivo elemento genitore: -file-renaming-preview-window-intro-library = { -app-name } will rename the following files in "{ $library }" to match their parent items: +file-renaming-preview-window-intro-library = { -app-name } rinominerà i seguenti file nella libreria "{ $library }" secondo i metadati del rispettivo elemento genitore: file-renaming-preview-window-renaming = Rinominazione in corso... file-renaming-preview-window-no-files = Tutti i file corrispondono già al proprio elemento genitore. Nessuna modifica richiesta. diff --git a/chrome/locale/it-IT/zotero/integration.ftl b/chrome/locale/it-IT/zotero/integration.ftl index 2468919352..59f1279bcf 100644 --- a/chrome/locale/it-IT/zotero/integration.ftl +++ b/chrome/locale/it-IT/zotero/integration.ftl @@ -22,7 +22,7 @@ integration-editBibliography-wrapper = integration-citationDialog = Finestra di citazione integration-citationDialog-section-open = Apri documenti ({ $count }) integration-citationDialog-section-selected = Elementi selezionati ({ $count }/{ $total }) -integration-citationDialog-section-selectedAnnotations = Selected Annotations +integration-citationDialog-section-selectedAnnotations = Annotazioni selezionate integration-citationDialog-section-selectedItems = Elementi selezionati integration-citationDialog-section-cited = { $count -> @@ -32,7 +32,7 @@ integration-citationDialog-section-cited = integration-citationDialog-details-suffix = Suffisso integration-citationDialog-details-prefix = Prefisso integration-citationDialog-details-suppressAuthor = Ometti l'autore -integration-citationDialog-details-includeComments = Include Comments +integration-citationDialog-details-includeComments = Includi i commenti integration-citationDialog-details-remove = { general-remove } integration-citationDialog-details-done = .label = { general-done } @@ -50,8 +50,8 @@ integration-citationDialog-lib-message-add-note = } integration-citationDialog-lib-message-annotations = { $search -> - [true] No items with annotations match the current search - *[other] No selected or open items with annotations + [true] Non ci sono elementi con annotazioni che corrispondono alla ricerca corrente + *[other] Nessun elemento con annotazioni selezionato o aperto } integration-citationDialog-settings-keepSorted = Mantieni l'ordinamento delle fonti integration-citationDialog-btn-settings = @@ -63,7 +63,7 @@ integration-citationDialog-btn-type-citation = integration-citationDialog-btn-type-add-note = .title = Aggiungi nota integration-citationDialog-btn-type-annotations = - .title = Add Annotations + .title = Aggiungi annotazioni integration-citationDialog-btn-accept = .title = { general-accept } integration-citationDialog-btn-cancel = @@ -82,7 +82,7 @@ integration-citationDialog-input-citation = integration-citationDialog-single-input-add-note = .placeholder = Cerca una nota da inserire nel documento integration-citationDialog-single-input-annotations = - .placeholder = Search for annotations to insert into the document + .placeholder = Cerca annotazioni da inserire nel documento integration-citationDialog-aria-item-list = .aria-description = Usa le frecce su/giù per cambiare la selezione degli elementi. { integration-citationDialog-enter-to-add-item } integration-citationDialog-aria-item-library = @@ -100,8 +100,8 @@ integration-citationDialog-collapse-section = integration-citationDialog-bubble-empty = (nessun titolo) integration-citationDialog-add-to-citation = Aggiungi alla citazione integration-citationDialog-annotations-filter = - .placeholder = Filter annotations -integration-citationDialog-annotations-empty = Select an item, attachment, or annotation to view annotation details + .placeholder = Filtra annotazioni +integration-citationDialog-annotations-empty = Seleziona un elemento, allegato o annotazione per vedere i dettagli dell'annotazione integration-prefs-displayAs-label = Mostra citazioni come: integration-prefs-footnotes = .label = Note a piè di pagina diff --git a/chrome/locale/it-IT/zotero/preferences.ftl b/chrome/locale/it-IT/zotero/preferences.ftl index 3b860849e4..bc6edec6b5 100644 --- a/chrome/locale/it-IT/zotero/preferences.ftl +++ b/chrome/locale/it-IT/zotero/preferences.ftl @@ -6,7 +6,7 @@ preferences-auto-recognize-files = preferences-file-renaming-title = Rinominazione dei file preferences-file-renaming-intro = { -app-name } può rinominare automaticamente i file in accordo con i metadati dell'elemento genitore (titolo, autore, ecc.) e mantenerli sincronizzati con eventuali modifiche. I file scaricati sono sempre rinominati in accordo con l'elemento genitore. preferences-file-renaming-configure-button = - .label = Configure File Renaming… + .label = Configura rinominazione dei file... preferences-attachment-titles-title = Titoli degli allegati preferences-attachment-titles-intro = I titoli degli allegati sono . A supporto di certi flussi di lavoro, { -app-name } può mostrare i nomi dei file al posto dei titoli degli allegati nella lista degli elementi. preferences-attachment-titles-show-filenames = @@ -107,15 +107,15 @@ preferences-sync-reset-restore-to-server-yes = Sostituisci i dati nella bibliote preferences-account-log-in = .label = Accedi preferences-account-waiting-for-login = - .value = Waiting for login… + .value = In attesa dell'accesso… preferences-account-cancel-button = .label = { general-cancel } preferences-account-logged-out-status = - .value = (logged out) + .value = (disconnesso) preferences-account-email-label = .value = Email: preferences-account-switch-accounts = - .label = Switch Accounts… -preferences-account-switch-text = Switching to a different account will remove all { -app-name } data on this computer. Before continuing, make sure all data and files you wish to keep have been synced with the “{ $username }” account or you have a backup of your { -app-name } data directory. -preferences-account-switch-confirmation-text = remove local data -preferences-account-switch-accept = Remove Data and Restart + .label = Cambia account… +preferences-account-switch-text = Cambiare account eliminerà tutti i dati di { -app-name } su questo computer. Prima di continuare, assicurati che tutti i dati e i file che vuoi mantenere siano sincronizzati con l'account “{ $username }” o di avere un backup della cartella dei dati di { -app-name }. +preferences-account-switch-confirmation-text = elimina i dati locali +preferences-account-switch-accept = Rimuovi i dati e riavvia diff --git a/chrome/locale/it-IT/zotero/reader.ftl b/chrome/locale/it-IT/zotero/reader.ftl index 018939c2b7..29154b23a8 100644 --- a/chrome/locale/it-IT/zotero/reader.ftl +++ b/chrome/locale/it-IT/zotero/reader.ftl @@ -197,12 +197,12 @@ reader-selected-pages = } reader-page-options = Opzioni pagina reader-read-aloud = Leggi ad alta voce -reader-read-aloud-from-here = Read Aloud from Here +reader-read-aloud-from-here = Leggi ad alta voce da qui reader-read-aloud-options = Opzioni reader-read-aloud-skip-back = Indietro -reader-read-aloud-skip-back-sentence = Skip Back by Sentence +reader-read-aloud-skip-back-sentence = Indietro di una frase reader-read-aloud-skip-ahead = Avanti -reader-read-aloud-skip-ahead-sentence = Skip Ahead by Sentence +reader-read-aloud-skip-ahead-sentence = Avanti di una frase reader-read-aloud-add-annotation = Annota frase ({ $key1 }/{ $key2 }) reader-read-aloud-play = Riproduci reader-read-aloud-pause = Pausa diff --git a/chrome/locale/it-IT/zotero/zotero.ftl b/chrome/locale/it-IT/zotero/zotero.ftl index f915f4aa56..92d7e8db30 100644 --- a/chrome/locale/it-IT/zotero/zotero.ftl +++ b/chrome/locale/it-IT/zotero/zotero.ftl @@ -158,11 +158,11 @@ zotero-toolbar-tabs-scroll-backwards = .title = Scorri indietro toolbar-add-attachment = .tooltiptext = { add-attachment } -recently-read = Recently Read +recently-read = Letti di recente collections-menu-show-recently-read = - .label = Show { recently-read } + .label = Mostra { recently-read } item-menu-remove-from-recently-read = - .label = Remove from { recently-read }… + .label = Rimuovi da { recently-read }… collections-menu-rename-collection = .label = Rinomina collezione collections-menu-edit-saved-search = @@ -290,9 +290,9 @@ items-table-cell-notes = [many] { $count } Note *[other] { $count } Note } -items-column-added-by = Added By -items-column-modified-by = Modified By -items-column-last-read = Last Read +items-column-added-by = Aggiunto da +items-column-modified-by = Modificato da +items-column-last-read = Ultima lettura report-error = .label = Segnala un errore… rtfScan-wizard = @@ -612,8 +612,8 @@ attachment-preview-placeholder = Nessun allegato da mostrare attachment-rename-from-parent = .tooltiptext = Rinominare il file in base ai metadati dell'elemento genitore account-log-in = Accedi -account-not-logged-in-text = Log in to your Zotero account to sync your data. -account-error-login-session-expired = Your login session has expired. Please try again. +account-not-logged-in-text = Accedi al tuo account Zotero per sincronizzare i dati. +account-error-login-session-expired = La sessione di accesso è scaduta. Riprova. toggle-preview = .label = { $type -> @@ -726,7 +726,7 @@ search-conditions-dateModified = Data ultima modifica search-conditions-fulltextContent = Contenuto dell'allegato search-conditions-programmingLanguage = Linguaggio di programmazione search-conditions-fileTypeID = Tipo di allegato -search-conditions-lastRead = Attachment Last Read +search-conditions-lastRead = Ultima lettura dell'allegato search-conditions-annotationText = Testo dell'annotazione search-conditions-annotationComment = Commento all'annotazione search-conditions-anyField = Qualsiasi campo diff --git a/chrome/locale/pl-PL/zotero/fileRenaming.ftl b/chrome/locale/pl-PL/zotero/fileRenaming.ftl index 749b1a8e94..5ceb6ddb73 100644 --- a/chrome/locale/pl-PL/zotero/fileRenaming.ftl +++ b/chrome/locale/pl-PL/zotero/fileRenaming.ftl @@ -1,6 +1,6 @@ file-renaming-settings-window = - .title = Configure File Renaming -file-renaming-settings-library-label = Library: + .title = Konfiguruj zmianę nazw plików +file-renaming-settings-library-label = Biblioteka: file-renaming-auto-rename-files = .label = Automatycznie zmień nazwy plików file-renaming-file-types = Zmień nazwy plików następujących typów: @@ -20,7 +20,7 @@ file-renaming-rename-linked = .label = Zmień nazwy powiązanych plików file-renaming-format-title = Format nazwy pliku file-renaming-format-instructions = Możesz dostosować wzorzec zmiany nazwy plik, który zostanie użyty przez { -app-name } aby zmienić nazwy plików załączników w taki sposób, aby użyte zostały metadane elementu nadrzędnego. -file-renaming-format-instructions-example = For example, "{ $example }" in this template will be replaced with the title of the parent item, truncated at 50 characters. +file-renaming-format-instructions-example = Na przykład “{ $example }” w tym szablonie zostanie zastąpione tytułem elementu nadrzędnego, skróconym do 50 znaków. file-renaming-format-instructions-more = Zobacz , aby uzyskać więcej informacji. file-renaming-format-template = Szablon nazwy pliku: file-renaming-format-preview = Podgląd: @@ -33,13 +33,13 @@ file-renaming-cancel-button = .label = { general-cancel } file-renaming-auto-rename-prompt-title = Zmieniono ustawienia nazw plików file-renaming-auto-rename-prompt-body = Czy chcesz zmienić nazwy istniejących plików w twojej bibliotece, aby zgadzały się z nowymi ustawieniami? -file-renaming-auto-rename-prompt-body-library = Would you like to rename existing files in library "{ $library }" to match the new settings? +file-renaming-auto-rename-prompt-body-library = Czy chcesz zmienić nazwy istniejących plików w bibliotece "{ $library }", aby zgadzały się z nowymi ustawieniami? file-renaming-auto-rename-prompt-yes = { file-renaming-preview-changes } file-renaming-auto-rename-prompt-no = Zachowaj istniejące nazwy plików file-renaming-preview-window = .title = Zmień nazwy plików file-renaming-preview-window-loading = Wczytywanie... file-renaming-preview-window-intro = { -app-name } zmieni nazwy następujących plików w Twojej bibliotece tak, aby odpowiadały nazwom ich elementów nadrzędnych: -file-renaming-preview-window-intro-library = { -app-name } will rename the following files in "{ $library }" to match their parent items: +file-renaming-preview-window-intro-library = { -app-name } zmieni nazwy następujących plików w bibliotece "{ $library }" tak, aby odpowiadały nazwom ich elementów nadrzędnych: file-renaming-preview-window-renaming = Zmiana nazw... file-renaming-preview-window-no-files = Wszystkie nazwy plików obecnie odpowiadają elementom nadrzędnym. Nie są wymagane żadne zmiany. diff --git a/chrome/locale/pl-PL/zotero/integration.ftl b/chrome/locale/pl-PL/zotero/integration.ftl index 0c76fcd55a..b828d91dc5 100644 --- a/chrome/locale/pl-PL/zotero/integration.ftl +++ b/chrome/locale/pl-PL/zotero/integration.ftl @@ -19,7 +19,7 @@ integration-editBibliography-wrapper = integration-citationDialog = Okno cytowania integration-citationDialog-section-open = Open Documents ({ $count }) integration-citationDialog-section-selected = Wybrane elementy ({ $count }/{ $total }) -integration-citationDialog-section-selectedAnnotations = Selected Annotations +integration-citationDialog-section-selectedAnnotations = Wybrane adnotacje integration-citationDialog-section-selectedItems = Wybrane elementy integration-citationDialog-section-cited = { $count -> @@ -29,7 +29,7 @@ integration-citationDialog-section-cited = integration-citationDialog-details-suffix = Przyrostek integration-citationDialog-details-prefix = Przedrostek integration-citationDialog-details-suppressAuthor = Pomiń autora -integration-citationDialog-details-includeComments = Include Comments +integration-citationDialog-details-includeComments = Dołącz komentarze integration-citationDialog-details-remove = { general-remove } integration-citationDialog-details-done = .label = { general-done } @@ -60,7 +60,7 @@ integration-citationDialog-btn-type-citation = integration-citationDialog-btn-type-add-note = .title = Dodaj notatkę integration-citationDialog-btn-type-annotations = - .title = Add Annotations + .title = Dodaj adnotacje integration-citationDialog-btn-accept = .title = { general-accept } integration-citationDialog-btn-cancel = @@ -79,7 +79,7 @@ integration-citationDialog-input-citation = integration-citationDialog-single-input-add-note = .placeholder = Wyszukaj notatkę, aby wstawić ją do dokumentu integration-citationDialog-single-input-annotations = - .placeholder = Search for annotations to insert into the document + .placeholder = Wyszukaj adnotację, aby wstawić ją do dokumentu integration-citationDialog-aria-item-list = .aria-description = Użyj klawiszy strzałki w górę/w dół, aby zmienić wybór elementu. { integration-citationDialog-enter-to-add-item } integration-citationDialog-aria-item-library = @@ -97,7 +97,7 @@ integration-citationDialog-collapse-section = integration-citationDialog-bubble-empty = (brak tytułu) integration-citationDialog-add-to-citation = Dodaj do cytowania integration-citationDialog-annotations-filter = - .placeholder = Filter annotations + .placeholder = Filtruj adnotacje integration-citationDialog-annotations-empty = Select an item, attachment, or annotation to view annotation details integration-prefs-displayAs-label = Wyświetl cytowania jako: integration-prefs-footnotes = diff --git a/chrome/locale/pl-PL/zotero/reader.ftl b/chrome/locale/pl-PL/zotero/reader.ftl index a942f2b06f..779810654e 100644 --- a/chrome/locale/pl-PL/zotero/reader.ftl +++ b/chrome/locale/pl-PL/zotero/reader.ftl @@ -230,7 +230,7 @@ reader-read-aloud-quota-exceeded-message = reader-read-aloud-error = { general-error } reader-read-aloud-error-unknown = Wystąpił nieznany błąd. reader-read-aloud-error-connection = Nie można się połączyć z usługą Read Aloud. Sprawdź swoje połączenie internetowe. -reader-read-aloud-error-daily-limit-exceeded = You have exceeded your daily limit for { -app-name } Voices. +reader-read-aloud-error-daily-limit-exceeded = Osiągnięto twój dzienny limit dla głosów { -app-name }. reader-read-aloud-retry = Ponów reader-read-aloud-first-run-title = Wybierz swój preferowany głos Read Aloud: reader-read-aloud-first-run-voice-tier-local-bullet-os-provided = Głosy dostarczone przez Twój system operacyjny @@ -252,9 +252,9 @@ reader-read-aloud-first-run-voice-tier-premium-bullet-multilingual = Wsparcie wi reader-read-aloud-first-run-voice-tier-premium-bullet-external-processing = Source text is processed by external text-to-speech providers reader-read-aloud-first-run-voice-tier-premium-bullet-subscription-minutes = { -subscription-name } plans include monthly Premium Voice minutes reader-read-aloud-first-run-voice-tier-premium-bullet-beta-credits = Request credits for additional minutes during beta -reader-read-aloud-sample-text = I am the local voice { $name } +reader-read-aloud-sample-text = Jestem głosem lokalnym { $name } reader-read-aloud-voices-none-available = Brak dostępnych głosów -reader-read-aloud-first-run-no-voices-for-language = { $tier } Voices do not support { $language }. +reader-read-aloud-first-run-no-voices-for-language = { $tier } Głosy nie obsługują { $language }. reader-read-aloud-region = Region reader-read-aloud-region-auto = Automatycznie reader-read-aloud-annotation-popup-move = Move annotation by sentence diff --git a/chrome/locale/pl-PL/zotero/zotero.ftl b/chrome/locale/pl-PL/zotero/zotero.ftl index 777b52a9d1..3217823a01 100644 --- a/chrome/locale/pl-PL/zotero/zotero.ftl +++ b/chrome/locale/pl-PL/zotero/zotero.ftl @@ -619,8 +619,8 @@ attachment-preview-placeholder = Brak załącznika do pokazania w podglądzie attachment-rename-from-parent = .tooltiptext = Zmień nazwę pliku tak, aby odpowiadał elementowi nadrzędnemu account-log-in = Zaloguj się -account-not-logged-in-text = Log in to your Zotero account to sync your data. -account-error-login-session-expired = Your login session has expired. Please try again. +account-not-logged-in-text = Zaloguj się do swojego konta Zotero, aby zsynchronizować swoje dane. +account-error-login-session-expired = Twoja sesja logowania wygasła. Proszę spróbuj ponownie. toggle-preview = .label = { $type -> @@ -737,7 +737,7 @@ search-conditions-dateModified = Data modyfikacji search-conditions-fulltextContent = Zawartość załącznika search-conditions-programmingLanguage = Język programowania search-conditions-fileTypeID = Typ pliku załącznika -search-conditions-lastRead = Attachment Last Read +search-conditions-lastRead = Ostatnio czytany załącznik search-conditions-annotationText = Tekst adnotacji search-conditions-annotationComment = Komentarz adnotacji search-conditions-anyField = Dowolne pole From ea230faa26aae0d82e6dfcc681e1ba187fa0ce92 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 8 Apr 2026 14:57:59 -0400 Subject: [PATCH 09/30] Add deployment script for 9.0 --- app/scripts/9.0_release_build_and_deploy | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 app/scripts/9.0_release_build_and_deploy diff --git a/app/scripts/9.0_release_build_and_deploy b/app/scripts/9.0_release_build_and_deploy new file mode 100755 index 0000000000..0143809654 --- /dev/null +++ b/app/scripts/9.0_release_build_and_deploy @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")" +ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +. "$APP_ROOT_DIR/config.sh" + +CHANNEL="release" +BRANCH="main" +if [ "`uname`" = "Darwin" ]; then + export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/release/ZoteroSafariExtension.appex" +fi + +cd "$SCRIPT_DIR" +./check_requirements + +hash=`./get_repo_branch_hash $BRANCH` +source_dir=`./get_commit_files $hash` +build_dir=`mktemp -d` + +function cleanup { + rm -rf "$source_dir" + rm -rf "$build_dir" +} +trap cleanup EXIT + +./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash +./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL From f82efc766d68e44827ad7dd5ef0bd6487335f1ee Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 9 Apr 2026 11:13:16 -0400 Subject: [PATCH 10/30] Fix Added/Modified By visibility when switching between library types The `_getColumns()` column cache key didn't account for library type, so switching between personal and group libraries reused cached columns from the previous library type. This caused `groupLibrariesOnly` columns (Added By, Modified By) to be missing in group libraries or incorrectly shown in personal libraries. Additionally, `VirtualizedTable.componentDidUpdate()` only recreated its internal `Columns` object when the id prop changed, not when the columns prop changed, so even with correct column rebuilding in `ItemTree`, the table wouldn't pick up the new column list. https://forums.zotero.org/discussion/130521/added-by-column-missing --- .../zotero/components/virtualized-table.jsx | 3 +- chrome/content/zotero/itemTree.jsx | 10 ++++-- test/tests/itemTreeTest.js | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/chrome/content/zotero/components/virtualized-table.jsx b/chrome/content/zotero/components/virtualized-table.jsx index 5f833c417c..89a01191cc 100644 --- a/chrome/content/zotero/components/virtualized-table.jsx +++ b/chrome/content/zotero/components/virtualized-table.jsx @@ -1066,7 +1066,8 @@ class VirtualizedTable extends React.Component { } componentDidUpdate(prevProps) { - if (this.props.id !== prevProps.id) { + if (this.props.id !== prevProps.id + || this.props.columns !== prevProps.columns) { this._columns = new Columns(this); this.forceUpdate(); } diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index 95ec6b8d87..bd8a3d6b1f 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -3810,11 +3810,15 @@ var ItemTree = class ItemTree extends LibraryTree { const visibilityGroup = this.collectionTreeRow.visibilityGroup; const prefKey = this.id; - if (this._columnsId == prefKey) { + // Include group status in cache key so groupLibrariesOnly columns + // are recalculated when switching between personal and group libraries + let cacheKey = prefKey + + (this.collectionTreeRow.isWithinGroup?.() ? '-group' : ''); + if (this._columnsId == cacheKey) { return this._columns; } - - this._columnsId = prefKey; + + this._columnsId = cacheKey; this._columns = []; let columnsSettings = this._getColumnPrefs(); diff --git a/test/tests/itemTreeTest.js b/test/tests/itemTreeTest.js index b2b533df1c..b2604dc5fe 100644 --- a/test/tests/itemTreeTest.js +++ b/test/tests/itemTreeTest.js @@ -2397,6 +2397,37 @@ describe("Zotero.ItemTree", function () { }); }); + describe("Added By column", function () { + it("should show in a group library and hide in a personal library", async function () { + let getTableColumns = () => itemsView.tree._columns.getAsArray(); + + // Start in personal library + await selectLibrary(win); + itemsView = zp.itemsView; + assert.notOk( + getTableColumns().find(c => c.dataKey === 'addedBy'), + "addedBy column should not be present in personal library" + ); + + // Switch to a group library + let group = await createGroup(); + await selectLibrary(win, group.libraryID); + itemsView = zp.itemsView; + assert.ok( + getTableColumns().find(c => c.dataKey === 'addedBy'), + "addedBy column should be present in group library" + ); + + // Switch back to personal library + await selectLibrary(win); + itemsView = zp.itemsView; + assert.notOk( + getTableColumns().find(c => c.dataKey === 'addedBy'), + "addedBy column should not be present after switching back to personal library" + ); + }); + }); + describe('Advanced Search', function () { describe('#notify', function () { it('should resolve the returned promise when an item is selected', async function() { From a1baa68006a9a8a4bddeee0d42957ac44b23a650 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 9 Apr 2026 11:30:42 -0400 Subject: [PATCH 11/30] Update reader submodule --- reader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reader b/reader index 7732d4b999..a3ab660769 160000 --- a/reader +++ b/reader @@ -1 +1 @@ -Subproject commit 7732d4b99967901b51a61ed9deeb4d7062634f37 +Subproject commit a3ab660769013b8d26590ade93f942cc8fdd4b3e From 1a89239a13ac6048c936e7c5f5be23a7c2ef6ed9 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 9 Apr 2026 11:32:54 -0400 Subject: [PATCH 12/30] Separate build and deploy for release scripts Split build_and_deploy into build_for_deploy (builds, uploads to S3, rsyncs manifests) and a deploy script in client-downloads (updates updates JSON, updates incrementals, runs deploy command). Release scripts support -b flag for build-only (deploy later manually). Beta/dev/test scripts build and deploy automatically via SSH. Move incrementals files from S3 to deploy server. Update DEPLOY_PATH to root of client-downloads so manifests and deploy script paths are derived from it. --- app/config.sh | 3 +- app/scripts/8.0_release_build_and_deploy | 22 ++++++++- app/scripts/9.0_release_build_and_deploy | 22 ++++++++- app/scripts/beta_build_and_deploy | 4 +- .../{build_and_deploy => build_for_deploy} | 48 ++++--------------- app/scripts/dev_build_and_deploy | 4 +- app/scripts/manage_incrementals | 10 ++-- app/scripts/test_build_and_deploy | 4 +- 8 files changed, 67 insertions(+), 50 deletions(-) rename app/scripts/{build_and_deploy => build_for_deploy} (79%) diff --git a/app/config.sh b/app/config.sh index a9bbcff93f..43ecdc3f0b 100644 --- a/app/config.sh +++ b/app/config.sh @@ -45,8 +45,7 @@ S3_CI_ZIP_PATH="ci/client" S3_DIST_PATH="client" DEPLOY_HOST="deploy.zotero" -DEPLOY_PATH="www/www-production/public/download/client/manifests" -DEPLOY_CMD="ssh $DEPLOY_HOST update-site-files" +DEPLOY_PATH="www/www-production/public/download/client" BUILD_PLATFORMS="" NUM_INCREMENTALS=6 diff --git a/app/scripts/8.0_release_build_and_deploy b/app/scripts/8.0_release_build_and_deploy index dde18615c9..3f18495ce0 100755 --- a/app/scripts/8.0_release_build_and_deploy +++ b/app/scripts/8.0_release_build_and_deploy @@ -8,10 +8,20 @@ ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" CHANNEL="release" BRANCH="8.0" +BUILD_ONLY=0 if [ "`uname`" = "Darwin" ]; then export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/release/ZoteroSafariExtension.appex" fi +while getopts "b" opt; do + case $opt in + b) + BUILD_ONLY=1 + ;; + esac + shift $((OPTIND-1)); OPTIND=1 +done + cd "$SCRIPT_DIR" ./check_requirements @@ -26,4 +36,14 @@ function cleanup { trap cleanup EXIT ./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash -./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL +VERSION="`cat \"$build_dir/version\"`" +./build_for_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL + +if [ $BUILD_ONLY -eq 1 ]; then + echo + echo "Build only -- skipping deploy." + echo "To deploy, run on the deploy server:" + echo " $DEPLOY_PATH/deploy $CHANNEL $VERSION $BUILD_PLATFORMS" +else + ssh $DEPLOY_HOST "$DEPLOY_PATH/deploy" $CHANNEL "$VERSION" $BUILD_PLATFORMS +fi diff --git a/app/scripts/9.0_release_build_and_deploy b/app/scripts/9.0_release_build_and_deploy index 0143809654..68abf35a25 100755 --- a/app/scripts/9.0_release_build_and_deploy +++ b/app/scripts/9.0_release_build_and_deploy @@ -8,10 +8,20 @@ ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" CHANNEL="release" BRANCH="main" +BUILD_ONLY=0 if [ "`uname`" = "Darwin" ]; then export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/release/ZoteroSafariExtension.appex" fi +while getopts "b" opt; do + case $opt in + b) + BUILD_ONLY=1 + ;; + esac + shift $((OPTIND-1)); OPTIND=1 +done + cd "$SCRIPT_DIR" ./check_requirements @@ -26,4 +36,14 @@ function cleanup { trap cleanup EXIT ./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash -./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL +VERSION="`cat \"$build_dir/version\"`" +./build_for_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL + +if [ $BUILD_ONLY -eq 1 ]; then + echo + echo "Build only -- skipping deploy." + echo "To deploy, run on the deploy server:" + echo " $DEPLOY_PATH/deploy $CHANNEL $VERSION $BUILD_PLATFORMS" +else + ssh $DEPLOY_HOST "$DEPLOY_PATH/deploy" $CHANNEL "$VERSION" $BUILD_PLATFORMS +fi diff --git a/app/scripts/beta_build_and_deploy b/app/scripts/beta_build_and_deploy index 079d67fe79..92676beeee 100755 --- a/app/scripts/beta_build_and_deploy +++ b/app/scripts/beta_build_and_deploy @@ -27,4 +27,6 @@ function cleanup { trap cleanup EXIT ./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash -./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL +VERSION="`cat \"$build_dir/version\"`" +./build_for_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL +ssh $DEPLOY_HOST "$DEPLOY_PATH/deploy" $CHANNEL "$VERSION" $BUILD_PLATFORMS diff --git a/app/scripts/build_and_deploy b/app/scripts/build_for_deploy similarity index 79% rename from app/scripts/build_and_deploy rename to app/scripts/build_for_deploy index e5bf235fb5..aafce72cbd 100755 --- a/app/scripts/build_and_deploy +++ b/app/scripts/build_for_deploy @@ -1,6 +1,7 @@ #!/bin/bash # -# Builds and deploys Zotero with full and incremental updates +# Builds Zotero and uploads to S3 and deploy server, but does not make the build live. +# To make the build live, run the deploy script on the deploy server (or via SSH). # set -euo pipefail @@ -107,13 +108,13 @@ do usage ;; esac - + echo echo "Getting $platform_name incrementals" INCREMENTALS="`\"$SCRIPT_DIR/manage_incrementals\" -c $CHANNEL -p ${PLATFORMS:i:1} -n $NUM_INCREMENTALS`" echo "$INCREMENTALS" echo - + if [ -n "$INCREMENTALS" ]; then for from in $INCREMENTALS; do echo "Building incremental update for $platform_name from $from to $VERSION" @@ -127,7 +128,7 @@ done "$SCRIPT_DIR/upload_builds" $CHANNEL $VERSION # Upload file lists and build info for each platform -channel_deploy_path="$DEPLOY_PATH/$CHANNEL" +channel_deploy_path="$DEPLOY_PATH/manifests/$CHANNEL" mkdir "$TEMP_DIR/version_info" chmod g+ws "$TEMP_DIR/version_info" cp "$DIST_DIR"/files-* "$TEMP_DIR/version_info" @@ -148,38 +149,9 @@ do done rsync -rv "$TEMP_DIR/version_info/" $DEPLOY_HOST:"$channel_deploy_path/$VERSION/" -# Download updates JSON for each platform, update it, and reupload it -for i in `seq 0 1 $((${#PLATFORMS}-1))` -do - case ${PLATFORMS:i:1} in - m) - architectures="mac" - ;; - w) - architectures="win32 win-x64 win-arm64" - ;; - l) - architectures="linux-i686 linux-x86_64 linux-arm64" - ;; - esac - - for arch in $architectures; - do - jsonfile="updates-$arch.json" - - scp $DEPLOY_HOST:"$channel_deploy_path/$jsonfile" "$TEMP_DIR/$jsonfile" - "$SCRIPT_DIR/add_version_info" -f "$TEMP_DIR/$jsonfile" -v $VERSION -b $BUILD_ID - scp "$TEMP_DIR/$jsonfile" $DEPLOY_HOST:"$channel_deploy_path/$jsonfile" - done -done - -# Add version to incremental lists -echo -for i in `seq 0 1 $((${#PLATFORMS}-1))` -do - "$SCRIPT_DIR/manage_incrementals" -c $CHANNEL -p ${PLATFORMS:i:1} -a $VERSION -done - -$DEPLOY_CMD - rm -rf "$STAGE_DIR"/* + +echo +echo "Build $VERSION uploaded successfully." +echo "To deploy, run on the deploy server:" +echo " ./deploy $CHANNEL $VERSION $PLATFORMS" diff --git a/app/scripts/dev_build_and_deploy b/app/scripts/dev_build_and_deploy index 7e9cca3006..0bc3cc01cd 100755 --- a/app/scripts/dev_build_and_deploy +++ b/app/scripts/dev_build_and_deploy @@ -24,4 +24,6 @@ function cleanup { trap cleanup EXIT ./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash -./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 +VERSION="`cat \"$build_dir/version\"`" +./build_for_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 +ssh $DEPLOY_HOST "$DEPLOY_PATH/deploy" $CHANNEL "$VERSION" $BUILD_PLATFORMS diff --git a/app/scripts/manage_incrementals b/app/scripts/manage_incrementals index a01382dfb9..2fbcf4b9ee 100755 --- a/app/scripts/manage_incrementals +++ b/app/scripts/manage_incrementals @@ -64,22 +64,22 @@ if [[ "$VERSION" ]] && [[ "$NUM_VERSIONS" ]]; then usage fi -INCR_FILENAME="incrementals-$CHANNEL-$PLATFORM" -S3_URL="s3://$S3_BUCKET/$S3_DIST_PATH/$CHANNEL/incrementals-$PLATFORM" +INCR_FILENAME="incrementals-$PLATFORM" +REMOTE_PATH="$DEPLOY_PATH/manifests/$CHANNEL/$INCR_FILENAME" INCR_PATH="$DIST_DIR/$INCR_FILENAME" if [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then - INCR_PATH=$(cygpath -w "$INCR_PATH") + INCR_PATH=$(cygpath -w "$INCR_PATH") fi mkdir -p "$DIST_DIR" -aws s3 cp $S3_URL "$INCR_PATH" >&2 +scp $DEPLOY_HOST:"$REMOTE_PATH" "$INCR_PATH" >&2 # Add version to file and reupload if [ "$VERSION" ]; then echo "Adding $VERSION to incrementals-$PLATFORM" echo $VERSION >> "$INCR_PATH" - aws s3 cp "$INCR_PATH" $S3_URL + scp "$INCR_PATH" $DEPLOY_HOST:"$REMOTE_PATH" # Show last n versions elif [ "$NUM_VERSIONS" -gt 0 ]; then # TEMP: Don't include 6.0 versions diff --git a/app/scripts/test_build_and_deploy b/app/scripts/test_build_and_deploy index a2864e80bb..e2d8b2df11 100755 --- a/app/scripts/test_build_and_deploy +++ b/app/scripts/test_build_and_deploy @@ -24,4 +24,6 @@ function cleanup { trap cleanup EXIT ./prepare_build -s "$source_dir" -o "$build_dir" -c $CHANNEL -m $hash -./build_and_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 +VERSION="`cat \"$build_dir/version\"`" +./build_for_deploy -d "$build_dir" -p $BUILD_PLATFORMS -c $CHANNEL -i 1 +ssh $DEPLOY_HOST "$DEPLOY_PATH/deploy" $CHANNEL "$VERSION" $BUILD_PLATFORMS From e96d65f0eda3de6c1927c7bb4b8789e573172c39 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 9 Apr 2026 12:24:47 -0400 Subject: [PATCH 13/30] Fix manage_incrementals on Cygwin Remove Windows path conversion that was needed for aws s3 cp but breaks scp (which interprets the drive letter colon as a hostname). --- app/scripts/manage_incrementals | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/scripts/manage_incrementals b/app/scripts/manage_incrementals index 2fbcf4b9ee..0517c96a52 100755 --- a/app/scripts/manage_incrementals +++ b/app/scripts/manage_incrementals @@ -68,9 +68,6 @@ INCR_FILENAME="incrementals-$PLATFORM" REMOTE_PATH="$DEPLOY_PATH/manifests/$CHANNEL/$INCR_FILENAME" INCR_PATH="$DIST_DIR/$INCR_FILENAME" -if [ "`uname -o 2> /dev/null`" = "Cygwin" ]; then - INCR_PATH=$(cygpath -w "$INCR_PATH") -fi mkdir -p "$DIST_DIR" scp $DEPLOY_HOST:"$REMOTE_PATH" "$INCR_PATH" >&2 From a0198e25715847cc834a9e71ca2b368cfc6b04bd Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 9 Apr 2026 14:31:29 -0400 Subject: [PATCH 14/30] Support pinned incrementals from deploy server Fetch pinned-incrementals-{platform} from the deploy server and merge with the normal last-N incrementals list. This ensures incremental MARs are always built from key versions (e.g., last release of each major version) regardless of how many newer versions have been released. --- app/scripts/build_for_deploy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/scripts/build_for_deploy b/app/scripts/build_for_deploy index aafce72cbd..f61591f0cc 100755 --- a/app/scripts/build_for_deploy +++ b/app/scripts/build_for_deploy @@ -112,6 +112,16 @@ do echo echo "Getting $platform_name incrementals" INCREMENTALS="`\"$SCRIPT_DIR/manage_incrementals\" -c $CHANNEL -p ${PLATFORMS:i:1} -n $NUM_INCREMENTALS`" + + # Fetch pinned incrementals from deploy server and merge + PINNED_FILE="$DEPLOY_PATH/manifests/$CHANNEL/pinned-incrementals-$platform" + PINNED="" + if ssh $DEPLOY_HOST "test -f $PINNED_FILE"; then + PINNED="`ssh $DEPLOY_HOST cat $PINNED_FILE`" + fi + + # Combine and deduplicate + INCREMENTALS="`echo -e "$INCREMENTALS\n$PINNED" | sort -u | grep -v '^$'`" echo "$INCREMENTALS" echo From 3e04f5e3e77f3a7554efe33451bc6c1ae4b710ff Mon Sep 17 00:00:00 2001 From: Abe Jellinek <1770299+AbeJellinek@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:35:02 -0400 Subject: [PATCH 15/30] Enable Read Aloud in stable builds --- chrome/content/zotero/xpcom/reader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 71ee8b83ff..08e8590e9d 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -246,7 +246,7 @@ class ReaderInstance { autoDisableTextTool: Zotero.Prefs.get('reader.autoDisableTool.text'), autoDisableImageTool: Zotero.Prefs.get('reader.autoDisableTool.image'), sidebarView: Zotero.Prefs.get('reader.lastSidebarTab'), - enableReadAloud: Zotero.isBetaBuild || Zotero.isDevBuild || Zotero.isSourceBuild, + enableReadAloud: true, readAloudVoices: this._getReadAloudVoices(), readAloudEnabledVoices: await this._getReadAloudEnabledVoices(), readAloudRemoteInterface: this._getReadAloudRemoteInterface(this._iframeWindow), From e22e2cd9dd3c322314b7e772564c59d09a30eb2b Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Apr 2026 11:20:25 -0400 Subject: [PATCH 16/30] Fix hardcoded version in post-upgrade banner --- chrome/content/zotero/zoteroPane.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 627b70dcc1..e1c22ec6bc 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -6701,7 +6701,7 @@ var ZoteroPane = new function () { // Set message and link to current version let div = document.getElementById('post-upgrade-message'); - document.l10n.setArgs(div, { version: "8" }); + document.l10n.setArgs(div, { version: majorVersion }); let link = document.getElementById('post-upgrade-new-features-link'); link.href = ZOTERO_CONFIG.NEW_FEATURES_URL.replace('{version}', majorVersion); document.getElementById('post-upgrade-container').removeAttribute('collapsed'); From b27c4cb0237b39ca4611686a5f06462e082d67a3 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Apr 2026 12:32:22 -0400 Subject: [PATCH 17/30] Enable SQLite WAL mode and add periodic VACUUM INTO - Switch journal mode from DELETE to WAL for better write performance. With EXCLUSIVE locking mode, SQLite uses heap memory for the WAL index, avoiding an -shm file. Set synchronous=NORMAL (matching what Mozilla uses for Places). Checkpoint WAL on database close so the .sqlite file has all data (for copies or backups). - Add periodic database compaction on idle (after DB backup) using VACUUM INTO and do an atomic file swap back to zotero.sqlite if no writes occurred during the operation. Check if vacuuming is needed based on time interval (default 14 days) and freelist ratio (default 10% threshold). - Disable auto_vacuum, which causes fragmentation and is unnecessary with periodic VACUUM - Remove the VACUUM call from the integrity check, which was always just an awkward hack to let people trigger a VACUUM without having an explicit button Closes #652 --- .../preferences/preferences_advanced.js | 7 - chrome/content/zotero/xpcom/db.js | 161 ++++++++++++++++-- chrome/content/zotero/xpcom/schema.js | 4 +- defaults/preferences/zotero.js | 4 + test/tests/dbTest.js | 33 ++++ 5 files changed, 186 insertions(+), 23 deletions(-) diff --git a/chrome/content/zotero/preferences/preferences_advanced.js b/chrome/content/zotero/preferences/preferences_advanced.js index c8012ce04a..7c831f471c 100644 --- a/chrome/content/zotero/preferences/preferences_advanced.js +++ b/chrome/content/zotero/preferences/preferences_advanced.js @@ -237,13 +237,6 @@ Zotero_Preferences.Advanced = { return; } - try { - await Zotero.DB.vacuum(); - } - catch (e) { - Zotero.logError(e); - ok = false; - } } var str = ok ? 'passed' : 'failed'; diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js index 4553876936..841179a702 100644 --- a/chrome/content/zotero/xpcom/db.js +++ b/chrome/content/zotero/xpcom/db.js @@ -94,6 +94,7 @@ Zotero.DBConnection = function (dbNameOrPath) { this._lastTransactionDate = null; this._transactionRollback = false; this._transactionNestingLevel = 0; + this._commitCount = 0; this._callbacks = { begin: [], commit: [], @@ -481,6 +482,7 @@ Zotero.DBConnection.prototype.executeTransaction = async function (func, options } result = await conn.executeTransaction(func); + this._commitCount++; Zotero.debug(`Committed DB transaction ${id}`, 4); } finally { @@ -500,12 +502,6 @@ Zotero.DBConnection.prototype.executeTransaction = async function (func, options this._transactionID = null; - if (options.vacuumOnCommit) { - Zotero.debug('Vacuuming database'); - await this.queryAsync('VACUUM'); - Zotero.debug('Done vacuuming'); - } - // Function to run once transaction has been committed but before any // permanent callbacks if (options.onCommit) { @@ -906,10 +902,16 @@ Zotero.DBConnection.prototype.executeSQLFile = async function (sql) { /* * Implements nsIObserver */ -Zotero.DBConnection.prototype.observe = function (subject, topic, data) { +Zotero.DBConnection.prototype.observe = async function (subject, topic, data) { switch (topic) { case 'idle': - this.backUpDatabase({ online: true }); + try { + await this.backUpDatabase({ online: true }); + await this.vacuum(); + } + catch (e) { + Zotero.logError(e); + } break; } } @@ -925,16 +927,129 @@ Zotero.DBConnection.prototype.getCachedStatements = function () { }; -// TEMP -Zotero.DBConnection.prototype.vacuum = function () { - return this.executeTransaction(async function () {}, { vacuumOnCommit: true }); +/** + * Vacuum the database using VACUUM INTO and perform an atomic file swap + * + * Creates a compacted copy of the database without blocking writes during + * the copy phase, then closes the connection and atomically replaces the + * original file if no writes occurred during compaction. + * + * @param {Object} [options] + * @param {Boolean} [options.force] - Skip time/freelist/disk-space checks + * @return {Promise} - Whether vacuum was performed + */ +Zotero.DBConnection.prototype.vacuum = async function ({ force } = {}) { + if (this._externalDB) { + return false; + } + + if (this.inTransaction()) { + await this.waitForTransaction(); + } + + if (!force) { + // Check time threshold + let lastVacuum = Zotero.Prefs.get('vacuum.lastTime') || 0; + let intervalDays = Zotero.Prefs.get('vacuum.interval') || 14; + let intervalMs = intervalDays * 24 * 60 * 60 * 1000; + if ((Date.now() - lastVacuum) < intervalMs) { + Zotero.debug("Database was vacuumed recently -- skipping"); + return false; + } + + // Check freelist threshold + let freelistCount = await this.valueQueryAsync("PRAGMA freelist_count"); + let pageCount = await this.valueQueryAsync("PRAGMA page_count"); + let threshold = Zotero.Prefs.get('vacuum.freelistThreshold') || 10; + if (pageCount > 0 && (freelistCount / pageCount * 100) < threshold) { + Zotero.debug(`Database freelist is ${freelistCount}/${pageCount} pages ` + + `(${(freelistCount / pageCount * 100).toFixed(1)}%) ` + + `-- below ${threshold}% threshold, skipping`); + return false; + } + + // Check disk space + let dbFile = Zotero.File.pathToFile(this._dbPath); + let dbSize = (await IOUtils.stat(this._dbPath)).size; + let freeSpace = dbFile.diskSpaceAvailable; + if (freeSpace < dbSize) { + Zotero.debug(`Not enough disk space to vacuum database ` + + `(${freeSpace} available, ${dbSize} needed) -- skipping`); + return false; + } + } + + let tmpFile = this._dbPath + '.vacuum.tmp'; + + try { + // Clean up any leftover temp file from a previous failed attempt + if (await IOUtils.exists(tmpFile)) { + await IOUtils.remove(tmpFile); + } + + Zotero.debug("Vacuuming database"); + let t = new Date(); + + let commitCountBefore = this._commitCount; + + // Disable auto_vacuum for the output file if previously enabled -- periodic VACUUM handles + // compaction, and auto_vacuum causes fragmentation + await this.queryAsync("PRAGMA auto_vacuum=0"); + + // VACUUM INTO creates a compacted copy as a snapshot of committed data. Concurrent writes + // in WAL are NOT included in the output. + await this.queryAsync(`VACUUM INTO '${tmpFile.replace(/'/g, "''")}'`); + + // Block other code from reopening the connection during the swap + let resolveVacuumPromise; + this._offlineBackupPromise = new Promise(function () { + resolveVacuumPromise = arguments[0]; + }); + + try { + // Close the database -- this waits for any in-flight transactions and checkpoints WAL + await this.closeDatabase(); + + // If any writes happened between VACUUM INTO start and close, the compacted copy is + // stale -- abort + if (this._commitCount !== commitCountBefore) { + Zotero.debug("Database was modified during vacuum -- aborting swap", 1); + await IOUtils.remove(tmpFile); + return false; + } + + // Atomic swap + await IOUtils.move(tmpFile, this._dbPath); + + Zotero.Prefs.set('vacuum.lastTime', Date.now()); + Zotero.debug("Vacuumed database in " + (new Date() - t) + " ms"); + + return true; + } + finally { + this._offlineBackupPromise = null; + resolveVacuumPromise(); + } + } + catch (e) { + Zotero.logError(e); + try { + if (await IOUtils.exists(tmpFile)) { + await IOUtils.remove(tmpFile); + } + } + catch (e2) { + Zotero.logError(e2); + } + return false; + } }; // TEMP Zotero.DBConnection.prototype.info = async function () { var info = {}; - var pragmas = ['auto_vacuum', 'cache_size', 'main.locking_mode', 'page_size']; + var pragmas = ['auto_vacuum', 'cache_size', 'journal_mode', 'main.locking_mode', 'page_size', 'synchronous']; for (let p of pragmas) { info[p] = await Zotero.DB.valueQueryAsync(`PRAGMA ${p}`); } @@ -978,6 +1093,18 @@ Zotero.DBConnection.prototype.closeDatabase = async function (permanent) { } Zotero.debug("Closing database"); + + // Checkpoint WAL before closing so all data is in the main file + // and -wal file is truncated. Use _connection.execute() directly + // to avoid deadlocking with _offlineBackupPromise in queryAsync. + try { + Zotero.debug("PRAGMA wal_checkpoint(TRUNCATE)"); + await this._connection.execute("PRAGMA wal_checkpoint(TRUNCATE)"); + } + catch (e) { + Zotero.logError(e); + } + this.closed = true; await this._connection.close(); this._connection = undefined; @@ -1285,7 +1412,15 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function () { else { await this.queryAsync("PRAGMA main.locking_mode=NORMAL"); } - + + // Enable WAL mode for better write performance. With locking_mode=EXCLUSIVE + // set first, SQLite uses heap memory for the WAL index instead of shared + // memory, so no -shm file is created on disk. + await this.queryAsync("PRAGMA journal_mode=WAL"); + // NORMAL synchronous is safe with WAL -- only risks losing the last + // transaction on power loss, not corruption + await this.queryAsync("PRAGMA synchronous=NORMAL"); + // Set page cache size to 8MB let pageSize = await this.valueQueryAsync("PRAGMA page_size"); let cacheSize = 8192000 / pageSize; diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 9e82bfdb99..25912c8ca0 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -2206,11 +2206,9 @@ Zotero.Schema = new function () { try { var userLibraryID = 1; - // Enable auto-vacuuming await Zotero.DB.queryAsync("PRAGMA page_size = 4096"); await Zotero.DB.queryAsync("PRAGMA encoding = 'UTF-8'"); - await Zotero.DB.queryAsync("PRAGMA auto_vacuum = 1"); - + var sql = await _getSchemaSQL('system'); await Zotero.DB.executeSQLFile(sql); diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index 6e3edba9ff..fedb6edd69 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -68,6 +68,10 @@ pref("extensions.zotero.feeds.defaultCleanupUnreadAfter", 30); pref("extensions.zotero.backup.numBackups", 2); pref("extensions.zotero.backup.interval", 1440); +pref("extensions.zotero.vacuum.lastTime", 0); +pref("extensions.zotero.vacuum.interval", 14); // days +pref("extensions.zotero.vacuum.freelistThreshold", 10); // percentage of free pages to trigger + pref("extensions.zotero.lastCreatorFieldMode",0); pref("extensions.zotero.lastAbstractExpand", true); pref("extensions.zotero.lastRenameAssociatedFile", false); diff --git a/test/tests/dbTest.js b/test/tests/dbTest.js index 63bd7afcf0..73da326d75 100644 --- a/test/tests/dbTest.js +++ b/test/tests/dbTest.js @@ -453,4 +453,37 @@ describe("Zotero.DB", function () { assert.isTrue(await IOUtils.exists(bakFile)); }); }); + + + describe("#vacuum()", function () { + it("should vacuum the database with force option", async function () { + let result = await Zotero.DB.vacuum({ force: true }); + assert.isTrue(result); + + // DB should still be functional + let count = await Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM items"); + assert.isNumber(count); + + // Vacuum timestamp should be updated + assert.isAbove(Zotero.Prefs.get('vacuum.lastTime'), 0); + + // Temp file should be cleaned up + assert.isFalse(await IOUtils.exists(Zotero.DB.path + '.vacuum.tmp')); + }); + + it("should skip vacuum when recently vacuumed", async function () { + Zotero.Prefs.set('vacuum.lastTime', Date.now()); + let result = await Zotero.DB.vacuum(); + assert.isFalse(result); + Zotero.Prefs.clear('vacuum.lastTime'); + }); + + it("should skip vacuum when freelist is below threshold", async function () { + Zotero.Prefs.clear('vacuum.lastTime'); + Zotero.Prefs.set('vacuum.freelistThreshold', 99); + let result = await Zotero.DB.vacuum(); + assert.isFalse(result); + Zotero.Prefs.clear('vacuum.freelistThreshold'); + }); + }); }); From 16b63a97ddd1e5c387a6a9cba6a3090385a947fe Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Apr 2026 13:34:01 -0400 Subject: [PATCH 18/30] Remove concurrent backup tests These tested racing an offline backup against an in-progress online backup, which wouldn't happen in practice (schema backups run at startup before the idle observer, and someone is very unlikely to perform a DB integrity check immediately after returning from an idle that triggered a backup). The tests relied on fragile timing and a shared tmp file, causing failures on Linux after the WAL checkpoint change. The tests were already skipped on macOS after switching to APFS clones. --- test/tests/dbTest.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/test/tests/dbTest.js b/test/tests/dbTest.js index 73da326d75..13689f1787 100644 --- a/test/tests/dbTest.js +++ b/test/tests/dbTest.js @@ -430,28 +430,6 @@ describe("Zotero.DB", function () { assert.isFalse(await IOUtils.exists(bakFile2)); }); - it("should perform an offline backup if an online backup is already in progress", async function () { - // On APFS, online backups use cloning (offline path), so this doesn't apply - if (Zotero.File.isAPFS(Zotero.DB.path)) this.skip(); - var promise = Zotero.DB.backUpDatabase({ suffix: 'test', online: true }); - var result = await Zotero.DB.backUpDatabase({ suffix: 'test2' }); - // The online backup fails - assert.ok(await getPromiseError(promise)); - assert.isFalse(await IOUtils.exists(bakFile)); - assert.isTrue(result); - assert.isTrue(await IOUtils.exists(bakFile2)); - }); - - it("shouldn't perform an online backup if one is already in progress", async function () { - // On APFS, online backups use cloning (offline path), so this doesn't apply - if (Zotero.File.isAPFS(Zotero.DB.path)) this.skip(); - var promise = Zotero.DB.backUpDatabase({ suffix: 'test', online: true }); - var result = await Zotero.DB.backUpDatabase({ suffix: 'test2', online: true }); - await promise; - assert.isFalse(result); - assert.isFalse(await IOUtils.exists(bakFile2)); - assert.isTrue(await IOUtils.exists(bakFile)); - }); }); From 1ed1fab55bc5408290aeecc4472d2ec63c921913 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Apr 2026 13:40:01 -0400 Subject: [PATCH 19/30] Switch to 9.0 branch for 9.0 releases --- app/scripts/9.0_release_build_and_deploy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/9.0_release_build_and_deploy b/app/scripts/9.0_release_build_and_deploy index 68abf35a25..72c7b0db09 100755 --- a/app/scripts/9.0_release_build_and_deploy +++ b/app/scripts/9.0_release_build_and_deploy @@ -7,7 +7,7 @@ ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" . "$APP_ROOT_DIR/config.sh" CHANNEL="release" -BRANCH="main" +BRANCH="9.0" BUILD_ONLY=0 if [ "`uname`" = "Darwin" ]; then export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/release/ZoteroSafariExtension.appex" From 62815bdd002b99390983938cb9fade526c55bdd5 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Fri, 10 Apr 2026 13:40:54 -0400 Subject: [PATCH 20/30] Update version to 10.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 3205fc9e46..697a1131b7 100644 --- a/version +++ b/version @@ -1 +1 @@ -9.0.SOURCE +10.0.SOURCE From f20eb5b566c466b3a422f8d3bd197242002f5a39 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 11 Apr 2026 09:58:47 -0400 Subject: [PATCH 21/30] Fix crash when annotation item is missing itemAnnotations row Don't throw when updateDisplayTitle() retry fails after loading annotation data for a corrupt item. Also add an integrity check to delete annotation items without itemAnnotations rows. Fixes https://forums.zotero.org/discussion/130872 --- chrome/content/zotero/xpcom/data/items.js | 7 ++++++- chrome/content/zotero/xpcom/schema.js | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index 07487c95ef..0f869f7bbc 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -317,7 +317,12 @@ Zotero.Items = function () { else if (item.isAnnotation()) { await item.loadDataType('annotation'); } - item.updateDisplayTitle() + try { + item.updateDisplayTitle(); + } + catch (e2) { + Zotero.logError(e2); + } } else { throw e; diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 25912c8ca0..05ef593c52 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -1936,6 +1936,11 @@ Zotero.Schema = new function () { `SELECT COUNT(*) > 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`, `INSERT INTO itemAttachments (itemID, linkMode) SELECT itemID, 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`, ], + // Missing itemAnnotations rows + [ + `SELECT COUNT(*) > 0 FROM items WHERE itemTypeID=${annotationID} AND itemID NOT IN (SELECT itemID FROM itemAnnotations)`, + `DELETE FROM items WHERE itemTypeID=${annotationID} AND itemID NOT IN (SELECT itemID FROM itemAnnotations)`, + ], // Attachments with note parents, unless they're embedded-image attachments [ `SELECT COUNT(*) > 0 FROM itemAttachments ` From 56ef61e45b965ef970d2da1f69858beeabb3ab44 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Mon, 13 Apr 2026 12:38:02 -0400 Subject: [PATCH 22/30] Store vacuum.lastTime as seconds, not milliseconds Also drop the default pref, since this is just internal state --- chrome/content/zotero/xpcom/db.js | 7 ++++--- defaults/preferences/zotero.js | 1 - test/tests/dbTest.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js index 841179a702..246f8c93cf 100644 --- a/chrome/content/zotero/xpcom/db.js +++ b/chrome/content/zotero/xpcom/db.js @@ -951,8 +951,9 @@ Zotero.DBConnection.prototype.vacuum = async function ({ force } = {}) { // Check time threshold let lastVacuum = Zotero.Prefs.get('vacuum.lastTime') || 0; let intervalDays = Zotero.Prefs.get('vacuum.interval') || 14; - let intervalMs = intervalDays * 24 * 60 * 60 * 1000; - if ((Date.now() - lastVacuum) < intervalMs) { + let intervalSeconds = intervalDays * 24 * 60 * 60; + let nowSeconds = Math.floor(Date.now() / 1000); + if ((nowSeconds - lastVacuum) < intervalSeconds) { Zotero.debug("Database was vacuumed recently -- skipping"); return false; } @@ -1021,7 +1022,7 @@ Zotero.DBConnection.prototype.vacuum = async function ({ force } = {}) { // Atomic swap await IOUtils.move(tmpFile, this._dbPath); - Zotero.Prefs.set('vacuum.lastTime', Date.now()); + Zotero.Prefs.set('vacuum.lastTime', Math.floor(Date.now() / 1000)); Zotero.debug("Vacuumed database in " + (new Date() - t) + " ms"); return true; diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index fedb6edd69..79a4abfb98 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -68,7 +68,6 @@ pref("extensions.zotero.feeds.defaultCleanupUnreadAfter", 30); pref("extensions.zotero.backup.numBackups", 2); pref("extensions.zotero.backup.interval", 1440); -pref("extensions.zotero.vacuum.lastTime", 0); pref("extensions.zotero.vacuum.interval", 14); // days pref("extensions.zotero.vacuum.freelistThreshold", 10); // percentage of free pages to trigger diff --git a/test/tests/dbTest.js b/test/tests/dbTest.js index 13689f1787..7dae666351 100644 --- a/test/tests/dbTest.js +++ b/test/tests/dbTest.js @@ -450,7 +450,7 @@ describe("Zotero.DB", function () { }); it("should skip vacuum when recently vacuumed", async function () { - Zotero.Prefs.set('vacuum.lastTime', Date.now()); + Zotero.Prefs.set('vacuum.lastTime', Math.floor(Date.now() / 1000)); let result = await Zotero.DB.vacuum(); assert.isFalse(result); Zotero.Prefs.clear('vacuum.lastTime'); From 683ecf1620b00588103c7402b50274d3fbf618e0 Mon Sep 17 00:00:00 2001 From: abaevbog Date: Mon, 13 Apr 2026 10:09:53 -0700 Subject: [PATCH 23/30] fix related item sometimes not selected on click (#5885) https://forums.zotero.org/discussion/130887/clicking-related-fails-zotero-report-id-1418809492 --- chrome/content/zotero/itemTree.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index bd8a3d6b1f..abb5c3f3aa 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -1304,7 +1304,7 @@ var ItemTree = class ItemTree extends LibraryTree { // Clear the quick search and tag selection and try again (once) if (!noRecurse && window.ZoteroPane) { let hasQuickSearch = !!this.collectionTreeRow.searchText; - let hasTagFilters = this.collectionTreeRow.tags.size > 0; + let hasTagFilters = this.collectionTreeRow.tags?.size > 0; if (hasQuickSearch || hasTagFilters) { // Clear all searches set on the collection tree row directly on // collectionTreeRow (vs using ZoteroPane functions) to avoid From ad4bb17db91137dd89d2f62a584a9c109c483423 Mon Sep 17 00:00:00 2001 From: Tom Najdek Date: Tue, 14 Apr 2026 11:36:15 +0200 Subject: [PATCH 24/30] Fix CSS specificity causing tag swatches to render in reverse order --- scss/components/_item-tree.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scss/components/_item-tree.scss b/scss/components/_item-tree.scss index 93a5420b25..f16bf2974e 100644 --- a/scss/components/_item-tree.scss +++ b/scss/components/_item-tree.scss @@ -152,7 +152,7 @@ // RTL: Parent is flipped, so margin can always be on logical left margin-left: -0.38461538em; - @include focus-states using($color) { + @include focus-states('.row.selected', '.virtualized-table:focus-within') using($color) { @if $color =="white" { background: url("chrome://zotero/skin/tag-crescent-border.svg") no-repeat center/contain; } From 676d609d0d91723ae74025149f93c4be15ffe5eb Mon Sep 17 00:00:00 2001 From: Mi Ramon Date: Wed, 15 Apr 2026 03:46:54 +0800 Subject: [PATCH 25/30] Add a icon for "Relate Items" (#5884) --- chrome/skin/default/zotero/16/universal/related.svg | 5 +++++ scss/components/_menu.scss | 1 + 2 files changed, 6 insertions(+) create mode 100644 chrome/skin/default/zotero/16/universal/related.svg diff --git a/chrome/skin/default/zotero/16/universal/related.svg b/chrome/skin/default/zotero/16/universal/related.svg new file mode 100644 index 0000000000..80c7c13683 --- /dev/null +++ b/chrome/skin/default/zotero/16/universal/related.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scss/components/_menu.scss b/scss/components/_menu.scss index 6d50a7acbe..2771d641ac 100644 --- a/scss/components/_menu.scss +++ b/scss/components/_menu.scss @@ -69,6 +69,7 @@ $menu-icons: ( duplicate-saved-search: 'duplicate', copy-collection: 'copy-collection', move-collection: 'move-collection', + relate-items: 'related', ); @each $cls, $icon in $menu-icons { From c89abbc5be760721c783f528d34500c81d0db96b Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 14 Apr 2026 15:39:23 -0400 Subject: [PATCH 26/30] Rename Sync preferences pane to Account - Restructure the pane layout: account info (username, email, login/logout) at the top, followed by a separate "Sync" section with an intro description and "Learn more" link, data sync options, and file syncing - Migrate hardcoded "Zotero storage" strings to FTL and fix capitalization - Rename the "Zotero" storage dropdown to "Zotero Storage". --- .../zotero/preferences/preferences_sync.jsx | 4 +- .../zotero/preferences/preferences_sync.xhtml | 187 +++++++++--------- .../preferences/preferences_sync_reset.xhtml | 2 +- .../content/zotero/xpcom/preferencePanes.js | 4 +- chrome/locale/en-US/zotero/preferences.dtd | 4 +- chrome/locale/en-US/zotero/preferences.ftl | 8 + .../default/zotero/20/universal/account.svg | 3 + scss/preferences/_sync.scss | 13 +- 8 files changed, 109 insertions(+), 116 deletions(-) create mode 100644 chrome/skin/default/zotero/20/universal/account.svg diff --git a/chrome/content/zotero/preferences/preferences_sync.jsx b/chrome/content/zotero/preferences/preferences_sync.jsx index 2dc15a58db..1bf615a43f 100644 --- a/chrome/content/zotero/preferences/preferences_sync.jsx +++ b/chrome/content/zotero/preferences/preferences_sync.jsx @@ -122,15 +122,13 @@ Zotero_Preferences.Sync = { document.getElementById('sync-unauthorized').hidden = linked; document.getElementById('account-linked').hidden = !linked; + document.getElementById('sync-settings-section').hidden = !loggedIn; document.getElementById('sync-reset').hidden = !loggedIn; // Toggle logged-in vs logged-out elements within the linked container document.getElementById('account-log-out-button').hidden = !loggedIn; document.querySelector('.account-logged-out-status').hidden = !loggedOutLinked; document.getElementById('account-logged-out-actions').hidden = !loggedOutLinked; - for (let elem of document.querySelectorAll('.sync-settings')) { - elem.hidden = !loggedIn; - } let displayUsername = loggedIn ? username : linkedUsername; document.getElementById('account-username').value = displayUsername; diff --git a/chrome/content/zotero/preferences/preferences_sync.xhtml b/chrome/content/zotero/preferences/preferences_sync.xhtml index 3bf398b625..775a1fca30 100644 --- a/chrome/content/zotero/preferences/preferences_sync.xhtml +++ b/chrome/content/zotero/preferences/preferences_sync.xhtml @@ -25,91 +25,13 @@ - - - + - - -