mirror of
https://github.com/zotero/zotero.git
synced 2026-10-14 04:08:17 +00:00
Compare commits
56 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6b7829501 | ||
|
|
1bad050d7f | ||
|
|
239530c515 | ||
|
|
828e0c942a | ||
|
|
cf37dfb330 | ||
|
|
6af5a484fd | ||
|
|
7de623accf | ||
|
|
10b3b5f915 | ||
|
|
051280fc43 | ||
|
|
264f55545a | ||
|
|
184671f919 | ||
|
|
24c7f146b3 | ||
|
|
707c71506f | ||
|
|
569bd1ffae | ||
|
|
62d46d337a | ||
|
|
ce0f7c0378 | ||
|
|
d2c9a310a8 | ||
|
|
b4bf86915b | ||
|
|
8804effba4 | ||
|
|
dfde08852a | ||
|
|
006a787272 | ||
|
|
0424faee42 | ||
|
|
3c9dcc94bc | ||
|
|
4bc61273a0 | ||
|
|
59b3268d01 | ||
|
|
ddb1daabbc | ||
|
|
d0debc0bed | ||
|
|
b5b59062a7 | ||
|
|
65461de4f5 | ||
|
|
b9e46ec660 | ||
|
|
22541ff8d8 | ||
|
|
f7b4cf907b | ||
|
|
36838e6a9a | ||
|
|
5b427ac332 | ||
|
|
65c77ddaae | ||
|
|
489d747b44 | ||
|
|
09813be7e0 | ||
|
|
85b111de4a | ||
|
|
788c48cc26 | ||
|
|
e3ae991bb3 | ||
|
|
e4a21b174f | ||
|
|
06fe4ad346 | ||
|
|
2019ac39a1 | ||
|
|
02b48318d7 | ||
|
|
f836e74996 | ||
|
|
1321447374 | ||
|
|
16221ac348 | ||
|
|
c0c00752e7 | ||
|
|
9fc00782de | ||
|
|
b9d1f50559 | ||
|
|
77bdb0b4cb | ||
|
|
320399dca9 | ||
|
|
8c54b98483 | ||
|
|
a0009f0fa3 | ||
|
|
cb0b2f9d58 | ||
|
|
a74117d824 |
78 changed files with 1172 additions and 424 deletions
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
|
@ -19,6 +19,9 @@ jobs:
|
|||
node-version: 18
|
||||
#cache: npm
|
||||
|
||||
- name: Install dbus-glib
|
||||
run: sudo apt-get update && sudo apt-get install -y libdbus-glib-1-2
|
||||
|
||||
# On GitHub
|
||||
- name: Install xvfb
|
||||
if: env.ACT != 'true'
|
||||
|
|
@ -57,18 +60,17 @@ jobs:
|
|||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
# Somehow forces dpl v1 to be installed, even though v2 is installed without
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.3' # Not needed with a `.ruby-version` or `.tool-versions`
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
|
||||
|
||||
- name: Upload deployment ZIP
|
||||
if: |
|
||||
env.ACT != 'true'
|
||||
&& github.repository == 'zotero/zotero'
|
||||
&& github.event_name == 'push'
|
||||
&& (github.ref == 'refs/heads/main' || endsWith(github.ref, '-hotfix'))
|
||||
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/7.0' || endsWith(github.ref, '-hotfix'))
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
|
@ -77,11 +79,8 @@ jobs:
|
|||
cd build
|
||||
zip -r ../build-zip/$GITHUB_SHA.zip *
|
||||
cd ..
|
||||
# Once dpl 2 works:
|
||||
#sudo gem install --no-document dpl
|
||||
#dpl s3 --bucket zotero-download --local_dir build-zip --upload_dir ci/client --acl public_read
|
||||
sudo gem install --no-document dpl dpl-s3
|
||||
dpl --provider=s3 --bucket=zotero-download --local-dir=build-zip --upload-dir=ci/client --acl=public-read --skip_cleanup=true
|
||||
gem install --no-document dpl -v '>= 2.0'
|
||||
dpl s3 --bucket zotero-download --local_dir build-zip --upload_dir ci/client --acl public_read
|
||||
|
||||
- name: Run tests
|
||||
run: xvfb-run test/runtests.sh -f
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 13c92c090c12f6555bf6bab98780025e93d75077
|
||||
Subproject commit 01b4a2abfcdaa35f478168af805df627cfedcb6a
|
||||
|
|
@ -7,7 +7,7 @@ ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|||
. "$APP_ROOT_DIR/config.sh"
|
||||
|
||||
CHANNEL="release"
|
||||
BRANCH="main"
|
||||
BRANCH="7.0"
|
||||
if [ "`uname`" = "Darwin" ]; then
|
||||
export SAFARI_APPEX="$ROOT_DIR/../safari-app-extension-builds/release/ZoteroSafariExtension.appex"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
title="Scaffold"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="zotero:scaffold"
|
||||
style="display: flex;"
|
||||
drawintitlebar-platforms="win, linux">
|
||||
|
||||
|
|
|
|||
|
|
@ -736,9 +736,9 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
return numSelected;
|
||||
}
|
||||
|
||||
// If there's a single item and it's in the trash, switch to that
|
||||
if (items.length == 1 && items[0].deleted) {
|
||||
Zotero.debug("Item is deleted; switching to trash");
|
||||
// If there's a single item and it or an ancestor are in the trash, switch to that
|
||||
if (items.length == 1 && items[0].isInTrash()) {
|
||||
Zotero.debug("Item is in trash; switching to trash");
|
||||
await this.selectTrash(libraryID);
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
this._abstractField.setAttribute('aria-label', Zotero.ItemFields.getLocalizedString('abstractNote'));
|
||||
}
|
||||
|
||||
async _ensureFeedAbstractBrowserExists() {
|
||||
_ensureFeedAbstractBrowserExists = Zotero.Utilities.Internal.serial(async () => {
|
||||
if (!this._feedAbstractBrowser) {
|
||||
// dynamically create a browser element to avoid spawning a process for every tab (#4530)
|
||||
this._feedAbstractBrowser = document.createXULElement("browser");
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
_discardFeedAbstractBrowser() {
|
||||
if (this._feedAbstractBrowser) {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
let imageAnnotationRendered = false;
|
||||
for (let annotation of this._annotationItems) {
|
||||
if (!imageAnnotationRendered
|
||||
&& annotation.annotationType === 'image'
|
||||
&& ['image', 'ink'].includes(annotation.annotationType)
|
||||
&& !await Zotero.Annotations.hasCacheImage(annotation)) {
|
||||
try {
|
||||
await Zotero.PDFWorker.renderAttachmentAnnotations(annotation.parentID);
|
||||
|
|
|
|||
|
|
@ -322,7 +322,6 @@
|
|||
|
||||
async asyncRender() {
|
||||
if (!this.item) return;
|
||||
if (this._asyncRendering) return;
|
||||
if (!this._section.open) return;
|
||||
if (this._isAlreadyRendered("async")) {
|
||||
if (this._previewDiscarded) {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
this._selectFieldSelection = null;
|
||||
this._addCreatorRow = false;
|
||||
this._switchedModeOfCreator = null;
|
||||
this._popupNode = null;
|
||||
|
||||
this._lastUpdateCustomRows = "";
|
||||
// Keep in sync with itemPaneManager.js
|
||||
|
|
@ -125,7 +126,7 @@
|
|||
init() {
|
||||
this.initCollapsibleSection();
|
||||
this._creatorTypeMenu.addEventListener('command', async (event) => {
|
||||
var typeBox = document.popupNode;
|
||||
var typeBox = this._popupNode;
|
||||
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
|
||||
|
||||
var typeID = event.explicitOriginalTarget.getAttribute('typeid');
|
||||
|
|
@ -144,7 +145,7 @@
|
|||
});
|
||||
|
||||
this._id('zotero-creator-transform-menu').addEventListener('popupshowing', (_event) => {
|
||||
var row = document.popupNode.closest('.meta-row');
|
||||
var row = this._popupNode.closest('.meta-row');
|
||||
var typeBox = row.querySelector('.creator-type-label').parentNode;
|
||||
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
|
||||
var item = this.item;
|
||||
|
|
@ -182,7 +183,7 @@
|
|||
});
|
||||
|
||||
this._id('zotero-creator-transform-menu').addEventListener('command', async (event) => {
|
||||
var row = document.popupNode.closest('.meta-row');
|
||||
var row = this._popupNode.closest('.meta-row');
|
||||
var typeBox = row.querySelector('.creator-type-label').parentNode;
|
||||
var index = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
|
||||
|
||||
|
|
@ -666,12 +667,13 @@
|
|||
}
|
||||
if (addLinkContextMenu) {
|
||||
rowData.oncontextmenu = (event) => {
|
||||
this._linkMenu.dataset.link = link;
|
||||
this._linkMenu.dataset.val = val;
|
||||
document.popupNode = rowLabel.parentElement;
|
||||
let menupopup = this._linkMenu;
|
||||
|
||||
menupopup.dataset.link = link;
|
||||
menupopup.dataset.val = val;
|
||||
this._popupNode = rowLabel.parentElement;
|
||||
|
||||
let menupopup = this._id('zotero-link-menu');
|
||||
Zotero.Utilities.Internal.updateEditContextMenu(menupopup, event.target.closest('input'));
|
||||
Zotero.Utilities.Internal.updateEditContextMenu(menupopup, event.target);
|
||||
this.handlePopupOpening(event, menupopup);
|
||||
};
|
||||
}
|
||||
|
|
@ -1189,7 +1191,7 @@
|
|||
span.className = 'creator-type-dropmarker';
|
||||
labelWrapper.appendChild(span);
|
||||
labelWrapper.addEventListener('click', (e) => {
|
||||
document.popupNode = rowLabel;
|
||||
this._popupNode = rowLabel;
|
||||
this._creatorTypeMenu.openPopup(rowLabel);
|
||||
// If the creator menu is opened via mouse-click, add a special attribute to
|
||||
// blur the focused field so that icons do not show up after the menu is closed.
|
||||
|
|
@ -1296,7 +1298,7 @@
|
|||
optionsButton.setAttribute('id', `creator-${rowIndex}-options`);
|
||||
optionsButton.setAttribute('data-l10n-id', "itembox-button-options");
|
||||
let triggerPopup = (e) => {
|
||||
document.popupNode = firstlast;
|
||||
this._popupNode = firstlast;
|
||||
|
||||
let menupopup = this._id('zotero-creator-transform-menu');
|
||||
Zotero.Utilities.Internal.updateEditContextMenu(menupopup, e.target.closest('input'));
|
||||
|
|
@ -2330,6 +2332,7 @@
|
|||
row.querySelector(".zotero-clicky-plus").hidden = true;
|
||||
row.querySelector(".zotero-clicky-minus").hidden = true;
|
||||
row.querySelector(".zotero-clicky-options").hidden = true;
|
||||
row.querySelector(".zotero-clicky-switch-type").hidden = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -2389,7 +2392,7 @@
|
|||
* @return {Promise}
|
||||
*/
|
||||
async swapNames(_event) {
|
||||
var row = document.popupNode.closest('.meta-row');
|
||||
var row = this._popupNode.closest('.meta-row');
|
||||
var typeBox = row.querySelector('[fieldname]');
|
||||
var creatorIndex = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
|
||||
var fields = this.getCreatorFields(row);
|
||||
|
|
@ -2414,7 +2417,7 @@
|
|||
* @return {Promise}
|
||||
*/
|
||||
async capitalizeCreatorName(_event) {
|
||||
var row = document.popupNode.closest('.meta-row');
|
||||
var row = this._popupNode.closest('.meta-row');
|
||||
let label = row.querySelector('.meta-label');
|
||||
var creatorIndex = parseInt(label.getAttribute('fieldname').split('-')[1]);
|
||||
let [lastName, firstName] = [...row.querySelectorAll("editable-text")];
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@
|
|||
}
|
||||
};
|
||||
|
||||
const waitDOMUpdate = async (timeout = 50) => {
|
||||
return new Promise((resolve) => {
|
||||
requestIdleCallback(resolve, { timeout });
|
||||
});
|
||||
};
|
||||
|
||||
const waitNoLongerThan = async (promise, ms = 1000) => {
|
||||
return Promise.race([
|
||||
promise,
|
||||
|
|
@ -269,6 +275,11 @@
|
|||
this._restorePinnedPane();
|
||||
|
||||
let panes = this.getPanes();
|
||||
|
||||
// Unobserve intersection to prevent unwanted rendering.
|
||||
// Checking flags in _handleIntersection is not reliable because it's async.
|
||||
this._toggleIntersectionObserver(false);
|
||||
|
||||
for (let box of [this._header, ...panes]) {
|
||||
box.editable = this.editable;
|
||||
box.tabID = this.tabID;
|
||||
|
|
@ -291,7 +302,7 @@
|
|||
this._paneParent.style.paddingBottom = '';
|
||||
if (pinnedPaneElem) {
|
||||
let paneID = pinnedPaneElem.dataset.pane;
|
||||
this.scrollToPane(paneID, 'instant');
|
||||
await this.scrollToPane(paneID, 'instant');
|
||||
this.pinnedPane = paneID;
|
||||
}
|
||||
else {
|
||||
|
|
@ -310,10 +321,17 @@
|
|||
continue;
|
||||
}
|
||||
await waitNoLongerThan(box.asyncRender(), 500);
|
||||
// Make sure the layout is updated for next isPaneVisible check
|
||||
await waitDOMUpdate();
|
||||
}
|
||||
|
||||
if (this.item.id == item.id) {
|
||||
this._isRendering = false;
|
||||
}
|
||||
|
||||
// Re-enable intersection observer
|
||||
this._toggleIntersectionObserver(true);
|
||||
|
||||
if (Zotero.test) {
|
||||
resolve();
|
||||
}
|
||||
|
|
@ -439,7 +457,9 @@
|
|||
}
|
||||
|
||||
async scrollToPane(paneID, behavior = 'smooth') {
|
||||
let pane = this.getEnabledPane(paneID);
|
||||
let panes = this.getEnabledPanes();
|
||||
let paneIndex = panes.findIndex(elem => elem.dataset.pane == paneID);
|
||||
let pane = panes[paneIndex];
|
||||
if (!pane) return null;
|
||||
|
||||
let scrollPromise;
|
||||
|
|
@ -450,6 +470,9 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
// Temporarily disable intersection observer to prevent unwanted rendering
|
||||
this._toggleIntersectionObserver(false);
|
||||
|
||||
// The pane should always be at the very top
|
||||
// If there isn't enough stuff below it for it to be at the top, we add padding
|
||||
// We use a ::before pseudo-element for this so that we don't need to add another level to the DOM
|
||||
|
|
@ -459,9 +482,31 @@
|
|||
scrollPromise = this._waitForScroll();
|
||||
scrollPromise.then(() => this._disableScrollHandler = false);
|
||||
}
|
||||
else {
|
||||
// Wait for the next DOM update to make sure the height is updated before rendering
|
||||
scrollPromise = waitDOMUpdate();
|
||||
}
|
||||
pane.scrollIntoView({ block: 'start', behavior });
|
||||
pane.focus();
|
||||
return scrollPromise;
|
||||
await scrollPromise;
|
||||
|
||||
// Check current and following panes for async render
|
||||
for (let i = paneIndex; i < panes.length; i++) {
|
||||
let nextPane = panes[i];
|
||||
// Stop if the pane is not visible anymore
|
||||
if (!this.isPaneVisible(nextPane.dataset.pane)) {
|
||||
break;
|
||||
}
|
||||
if (nextPane.asyncRender) {
|
||||
await nextPane.asyncRender();
|
||||
// Wait for the next DOM update to make sure the layout is updated
|
||||
await waitDOMUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable intersection observer
|
||||
this._toggleIntersectionObserver(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
_makeSpaceForPane(pane) {
|
||||
|
|
@ -528,7 +573,17 @@
|
|||
this._intersectionOb.disconnect();
|
||||
}
|
||||
this._intersectionOb = new IntersectionObserver(this._handleIntersection);
|
||||
this.getPanes().forEach(elem => this._intersectionOb.observe(elem));
|
||||
this._toggleIntersectionObserver(true);
|
||||
}
|
||||
|
||||
_toggleIntersectionObserver(enabled) {
|
||||
let panes = this.getPanes();
|
||||
if (enabled) {
|
||||
panes.forEach(elem => this._intersectionOb.observe(elem));
|
||||
}
|
||||
else {
|
||||
panes.forEach(elem => this._intersectionOb.unobserve(elem));
|
||||
}
|
||||
}
|
||||
|
||||
_handleContainerScroll = () => {
|
||||
|
|
|
|||
|
|
@ -151,8 +151,17 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
renderItemPane(item) {
|
||||
async renderItemPane(item) {
|
||||
let previousMode = this.mode;
|
||||
this.mode = "item";
|
||||
|
||||
// Fix https://forums.zotero.org/discussion/115450/zotero-7-beta-wrong-vertical-position-in-the-item-pane-after-switching-from-a-note
|
||||
if (previousMode === "note") {
|
||||
// Wait for DOM to update and then trigger item-details render
|
||||
await new Promise((resolve) => {
|
||||
requestIdleCallback(resolve, { timeout: 50 });
|
||||
});
|
||||
}
|
||||
|
||||
this._itemDetails.editable = this.editable;
|
||||
this._itemDetails.tabID = "zotero-pane";
|
||||
|
|
|
|||
|
|
@ -129,8 +129,6 @@
|
|||
|
||||
_contextMenuTarget = null;
|
||||
|
||||
_disableScrollHandler = false;
|
||||
|
||||
get container() {
|
||||
return this._container;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ import ReactDOM from "react-dom";
|
|||
|
||||
|
||||
choosePane(pane) {
|
||||
Zotero.debug(new Error().stack);
|
||||
let otherPane;
|
||||
if (pane.id == 'left-pane') {
|
||||
otherPane = this._rightPane;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@
|
|||
if (this._item == val) {
|
||||
return;
|
||||
}
|
||||
// Blur events do not fire when item is changed by clicking on an itemTree row.
|
||||
// Make sure any changes to the tags get saved before item is updated.
|
||||
this.blurOpenField();
|
||||
this._item = val;
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +135,7 @@
|
|||
|
||||
this._tagColors = Zotero.Tags.getColors(this.item.libraryID);
|
||||
|
||||
let focusedTag = this._id('rows').querySelector('editable-text:focus')?.value;
|
||||
let focusedTag = this._id('rows').querySelector('editable-text:focus-within')?.value;
|
||||
|
||||
let tagRows = this._id('rows');
|
||||
tagRows.replaceChildren();
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
|
|||
|
||||
const apiFetch = async (tokens, endPoint, params = {}, headers = {}, options = {}) => {
|
||||
const stringParams = Object.entries(params).map(p => p.join('=')).join('&');
|
||||
const url = MENDELEY_API_URL + '/' + endPoint + '?' + stringParams;
|
||||
const url = `${MENDELEY_API_URL}/${endPoint}${stringParams.length ? '?' + stringParams : ''}`;
|
||||
return apiFetchUrl(tokens, url, headers, options);
|
||||
};
|
||||
|
||||
|
|
@ -183,8 +183,8 @@ const obtainReferenceManagerToken = async (login, password) => {
|
|||
hasEnteredLogin = await browser.browsingContext.currentWindowGlobal
|
||||
.getActor("MendeleyAuth")
|
||||
.sendQuery("login", { login });
|
||||
Zotero.debug(`hasEnteredLogin: ${hasEnteredLogin}`);
|
||||
if (!hasEnteredLogin) {
|
||||
browser.destroy();
|
||||
reject(new Error("Failed to enter login"));
|
||||
}
|
||||
}
|
||||
|
|
@ -195,8 +195,8 @@ const obtainReferenceManagerToken = async (login, password) => {
|
|||
hasEnteredPassword = await browser.browsingContext.currentWindowGlobal
|
||||
.getActor("MendeleyAuth")
|
||||
.sendQuery("password", { password });
|
||||
Zotero.debug(`hasEnteredPassword: ${hasEnteredPassword}`);
|
||||
if (!hasEnteredPassword) {
|
||||
browser.destroy();
|
||||
reject(new Error("Failed to enter password"));
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +205,7 @@ const obtainReferenceManagerToken = async (login, password) => {
|
|||
const cookies = cookieSandbox.getCookiesForURI(
|
||||
Services.io.newURI("https://www.mendeley.com/reference-manager/library")
|
||||
);
|
||||
browser.destroy();
|
||||
if (!cookies.accessToken) {
|
||||
reject(new Error("Failed to obtain Mendeley access token"));
|
||||
}
|
||||
|
|
@ -218,6 +219,7 @@ const obtainReferenceManagerToken = async (login, password) => {
|
|||
|
||||
browser.load("https://www.mendeley.com/sign-in?routeTo=https://www.mendeley.com/reference-manager/library/");
|
||||
Zotero.Promise.delay(ACCESS_TOKEN_TIMEOUT).then(() => {
|
||||
browser.destroy();
|
||||
reject(new Error("Timed out while obtaining Mendeley access token"));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ var Zotero_Import_Mendeley = function () {
|
|||
|
||||
this._tokens = null;
|
||||
this._credentials = null;
|
||||
this._refManagerToken = null;
|
||||
this._db = null;
|
||||
this._file = null;
|
||||
this._saveOptions = null;
|
||||
|
|
@ -327,15 +328,18 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
}
|
||||
|
||||
if (this._credentials && !this.skipNotebooks) {
|
||||
const token = await obtainReferenceManagerTokenWithRetry(this._credentials.username, this._credentials.password);
|
||||
this._progress += 1; // progress one arbitrary "tick" assigned to importing notebooks task, we have 4 more left
|
||||
if (token) {
|
||||
if (!this._refManagerToken) {
|
||||
const token = await obtainReferenceManagerTokenWithRetry(this._credentials.username, this._credentials.password);
|
||||
this._refManagerToken = {
|
||||
kind: 'referenceManager',
|
||||
accessToken: token,
|
||||
username: this._credentials.username,
|
||||
password: this._credentials.password
|
||||
};
|
||||
}
|
||||
|
||||
this._progress += 1; // progress one arbitrary "tick" assigned to importing notebooks task, we have 4 more left
|
||||
if (this._refManagerToken) {
|
||||
const notebooks = await this._getNotebooksAPI();
|
||||
const notesContent = await Promise.all(notebooks.map(notebook => this._translateNotebookToNoteContent(libraryID, notebook)));
|
||||
this._progress += 1; // progress one arbitrary "tick" assigned to importing notebooks task, we have 1 more left
|
||||
|
|
@ -353,7 +357,7 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
|
|||
|
||||
const isMappedToExisting = !!existingItem;
|
||||
Zotero.debug(isMappedToExisting ? `Updating existing notebook "${uuid}" -> "${existingItem.key}"` : `Importing new notebook "${uuid}"`, 5);
|
||||
let item = existingItem ?? new Zotero.Item('note');
|
||||
let item = isMappedToExisting ? existingItem : new Zotero.Item('note');
|
||||
item.libraryID = libraryID;
|
||||
item.setNote(noteContent);
|
||||
item.addRelation(predicate, uuid);
|
||||
|
|
@ -1085,14 +1089,15 @@ Zotero_Import_Mendeley.prototype._translateNotebookToNoteContent = async functio
|
|||
'mendeleyDB:annotationUUID',
|
||||
annotationUUID
|
||||
);
|
||||
|
||||
let attachmentItem = Zotero.Items.get(annotation.parentID);
|
||||
let jsonAnnotation = await Zotero.Annotations.toJSON(annotation);
|
||||
jsonAnnotation.attachmentItemID = attachmentItem.id;
|
||||
jsonAnnotation.id = annotation.key;
|
||||
if (annotation) {
|
||||
let attachmentItem = Zotero.Items.get(annotation.parentID);
|
||||
let jsonAnnotation = await Zotero.Annotations.toJSON(annotation);
|
||||
jsonAnnotation.attachmentItemID = attachmentItem.id;
|
||||
jsonAnnotation.id = annotation.key;
|
||||
|
||||
const { html } = Zotero.EditorInstanceUtilities.serializeAnnotations([jsonAnnotation]);
|
||||
return html;
|
||||
const { html } = Zotero.EditorInstanceUtilities.serializeAnnotations([jsonAnnotation]);
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1744,6 +1749,11 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
|
|||
// do not attempt to import annotations for non-PDF files
|
||||
return;
|
||||
}
|
||||
|
||||
// Mendeley produces empty, type-less annotations that confuse the PDF worker
|
||||
annotations = annotations.filter(
|
||||
annotation => ['highlight', 'note', 'image', 'ink', 'underline', 'text'].includes(annotation.type)
|
||||
);
|
||||
|
||||
let annotationMap = new Map();
|
||||
for (let annotation of annotations) {
|
||||
|
|
@ -1766,6 +1776,7 @@ Zotero_Import_Mendeley.prototype._saveAnnotations = async function (annotations,
|
|||
for (let annotation of annotations) {
|
||||
// Ignore empty highlights
|
||||
if (annotation.type == 'highlight' && !annotation.text) {
|
||||
Zotero.debug(`Skipping empty highlight with uuid ${annotation.uuid}`, 5);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2287,7 +2287,8 @@ var Zotero_QuickFormat = new function () {
|
|||
}
|
||||
}
|
||||
// Arrow up closes the popup
|
||||
if (event.key === "ArrowUp") {
|
||||
// (except on locator type dropdown to allow for default handling)
|
||||
if (event.key === "ArrowUp" && event.target.tagName !== "menulist") {
|
||||
skipInputRefocus = true;
|
||||
itemPopover.hidePopup();
|
||||
return stopEvent();
|
||||
|
|
|
|||
|
|
@ -345,25 +345,13 @@ var Zotero_LocateMenu = new function() {
|
|||
* Get the first 50 selected items
|
||||
*/
|
||||
function _getSelectedItems() {
|
||||
if (Zotero_Tabs.selectedID == 'zotero-pane') {
|
||||
var allSelectedItems = ZoteroPane_Local.getSelectedItems();
|
||||
var selectedItems = [];
|
||||
while (selectedItems.length < 50 && allSelectedItems.length) {
|
||||
var item = allSelectedItems.shift();
|
||||
if (!item.isNote()) selectedItems.push(item);
|
||||
}
|
||||
return selectedItems;
|
||||
}
|
||||
else {
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
let item = Zotero.Items.get(reader.itemID);
|
||||
if (item.parentItem) {
|
||||
return [item.parentItem];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
var allSelectedItems = ZoteroPane.getSelectedItems();
|
||||
var selectedItems = [];
|
||||
while (selectedItems.length < 50 && allSelectedItems.length) {
|
||||
var item = allSelectedItems.shift();
|
||||
if (!item.isNote()) selectedItems.push(item);
|
||||
}
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
var ViewOptions = {};
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ Zotero_Preferences.Sync = {
|
|||
noChar: '\uD83D\uDEAB',
|
||||
|
||||
init: async function () {
|
||||
this.storeLastStorageSettings();
|
||||
this.updateStorageSettingsUI();
|
||||
this.updateStorageSettingsGroupsUI();
|
||||
|
||||
|
|
@ -426,7 +427,17 @@ Zotero_Preferences.Sync = {
|
|||
addRow(group.data.name, "G" + group.id, librariesToSkip.indexOf("G" + group.id) == -1);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
_lastStorageProtocol: null,
|
||||
_lastStorageURL: null,
|
||||
|
||||
storeLastStorageSettings: function () {
|
||||
this._lastStorageProtocol = Zotero.Prefs.get('sync.storage.protocol');
|
||||
this._lastStorageURL = Zotero.Prefs.get('sync.storage.url');
|
||||
},
|
||||
|
||||
|
||||
updateStorageSettingsUI: Zotero.Promise.coroutine(function* () {
|
||||
this.unverifyStorageServer();
|
||||
|
||||
|
|
@ -480,17 +491,19 @@ Zotero_Preferences.Sync = {
|
|||
|
||||
|
||||
onStorageSettingsChange: Zotero.Promise.coroutine(function* () {
|
||||
// Clean URL
|
||||
Zotero.Prefs.set('sync.storage.url', Zotero.Prefs.get('sync.storage.url').trim()
|
||||
.replace(/(^https?:\/\/|\/zotero\/?$|\/$)/g, ''));
|
||||
|
||||
var oldProtocol = Zotero.Prefs.get('sync.storage.protocol');
|
||||
var oldProtocol = this._lastStorageProtocol;
|
||||
var oldURL = this._lastStorageURL;
|
||||
|
||||
// Necessary for pref to update
|
||||
yield Zotero.Promise.delay(1);
|
||||
|
||||
var newProtocol = Zotero.Prefs.get('sync.storage.protocol');
|
||||
|
||||
if (oldProtocol != newProtocol) {
|
||||
var newURL = Zotero.Prefs.get('sync.storage.url').trim()
|
||||
// Strip scheme, leading '://' or '//' (#3483), and trailing '/zotero'
|
||||
.replace(/(^https?:\/\/|^:?\/\/|\/zotero\/?$|\/$)/g, '')
|
||||
Zotero.Prefs.set('sync.storage.url', newURL);
|
||||
|
||||
if (oldProtocol != newProtocol || oldURL != newURL) {
|
||||
yield Zotero.Sync.Storage.Local.resetAllSyncStates(Zotero.Libraries.userLibraryID);
|
||||
}
|
||||
|
||||
|
|
@ -547,6 +560,7 @@ Zotero_Preferences.Sync = {
|
|||
}
|
||||
|
||||
this.updateStorageSettingsUI();
|
||||
this.storeLastStorageSettings();
|
||||
}),
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<html id="run-js">
|
||||
<html id="run-js" windowtype="zotero:run-js">
|
||||
<head>
|
||||
<title data-l10n-id="runJS-title"></title>
|
||||
<link href="chrome://zotero-platform/content/zotero.css" rel="stylesheet"/>
|
||||
|
|
|
|||
|
|
@ -309,23 +309,12 @@ const ZoteroStandalone = new function() {
|
|||
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
if (win.Zotero_Tabs.selectedID == 'zotero-pane') {
|
||||
try {
|
||||
selected = win.ZoteroPane.getSelectedItems();
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
win.ZoteroPane.updateQuickCopyCommands(selected);
|
||||
try {
|
||||
selected = win.ZoteroPane.getSelectedItems();
|
||||
}
|
||||
else {
|
||||
let reader = Zotero.Reader.getByTabID(win.Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
let item = Zotero.Items.get(reader.itemID);
|
||||
selected = item.parentItem && [item.parentItem] || [];
|
||||
item = item.parentItem || item;
|
||||
win.ZoteroPane.updateQuickCopyCommands([item]);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
win.ZoteroPane.updateQuickCopyCommands(selected);
|
||||
}
|
||||
|
||||
var format = Zotero.QuickCopy.getFormatFromURL(Zotero.QuickCopy.lastActiveURL);
|
||||
|
|
|
|||
|
|
@ -399,7 +399,8 @@ var Zotero_Tabs = new function () {
|
|||
null,
|
||||
{
|
||||
tabIndex: tab.index,
|
||||
openInBackground: true
|
||||
openInBackground: true,
|
||||
allowDuplicate: true
|
||||
}
|
||||
));
|
||||
if (tab.index > maxIndex) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
id="csl-edit"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="zotero:style-editor"
|
||||
onload="Zotero_CSL_Editor.init();"
|
||||
title="&styles.editor;">
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const { HiddenBrowser } = ChromeUtils.import("chrome://zotero/content/HiddenBrow
|
|||
Zotero.BrowserDownload = {
|
||||
HANDLED_URLS: {
|
||||
'https://zotero-static.s3.amazonaws.com/test-pdf-redirect.html': "html",
|
||||
'://www.sciencedirect.com': "#challenge-form"
|
||||
'://www.sciencedirect.com': "#captcha-box"
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -343,11 +343,15 @@ Zotero.CookieSandbox.Observer = new function() {
|
|||
// Zotero.debug(`CookieSandbox: Found the browser via doc of load group for ${channelURI}`, 5);
|
||||
trackedBy = this.trackedBrowsers.get(browser);
|
||||
}
|
||||
else if (notificationCallbacks instanceof XMLHttpRequest) {
|
||||
// Zotero.debug(`CookieSandbox: Found the browser via XHR for ${channelURI}`, 5);
|
||||
tested = true;
|
||||
}
|
||||
else {
|
||||
// try getting as an XHR or nsIWBP
|
||||
// try getting as an nsIWBP
|
||||
try {
|
||||
notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
|
||||
// Zotero.debug(`CookieSandbox: Found the browser via XHR or nsIWBP for ${channelURI}`, 5);
|
||||
notificationCallbacks.QueryInterface(Components.interfaces.nsIWebBrowserPersist);
|
||||
// Zotero.debug(`CookieSandbox: Found the browser via nsIWBP for ${channelURI}`, 5);
|
||||
tested = true;
|
||||
}
|
||||
catch (e) {}
|
||||
|
|
|
|||
|
|
@ -2183,6 +2183,22 @@ Zotero.Item.prototype.setSourceKey = function(sourceItemKey) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether the item or any of its ancestors is in the trash
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Zotero.Item.prototype.isInTrash = function () {
|
||||
if (this.deleted) {
|
||||
return true;
|
||||
}
|
||||
if (this.isTopLevelItem()) {
|
||||
return false;
|
||||
}
|
||||
return this.parentItem.isInTrash();
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
|
|
@ -2540,12 +2556,15 @@ Zotero.Item.prototype.numNonHTMLFileAttachments = function () {
|
|||
};
|
||||
|
||||
|
||||
Zotero.Item.prototype.numFileAttachmentsWithContentType = function (contentType) {
|
||||
Zotero.Item.prototype.getFileAttachmentsWithContentType = function (contentType) {
|
||||
this._requireData('childItems');
|
||||
return this.getAttachments()
|
||||
.map(itemID => Zotero.Items.get(itemID))
|
||||
.filter(item => item.isFileAttachment() && item.attachmentContentType == contentType)
|
||||
.length;
|
||||
return Zotero.Items.get(this.getAttachments())
|
||||
.filter(item => item.isFileAttachment() && item.attachmentContentType == contentType);
|
||||
};
|
||||
|
||||
|
||||
Zotero.Item.prototype.numFileAttachmentsWithContentType = function (contentType) {
|
||||
return this.getFileAttachmentsWithContentType(contentType).length;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -3958,7 +3977,8 @@ Zotero.Item.prototype.setAutoAttachmentTitle = function ({ ignoreAutoRenamePrefs
|
|||
// If this is the only attachment of its type on the parent item and the
|
||||
// file is being renamed, give it a default title ("PDF", "Webpage", etc.)
|
||||
let isFirstOfType = this.parentItemID
|
||||
&& this.parentItem.numFileAttachmentsWithContentType(this.attachmentContentType) <= 1;
|
||||
&& this.parentItem.getFileAttachmentsWithContentType(this.attachmentContentType)
|
||||
.every(item => item === this);
|
||||
let isBeingRenamed = ignoreAutoRenamePrefs || Zotero.Attachments.shouldAutoRenameAttachment(this);
|
||||
if (isFirstOfType && isBeingRenamed) {
|
||||
let defaultTitle = this._getDefaultTitleForAttachmentContentType();
|
||||
|
|
@ -5124,6 +5144,7 @@ Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
|
|||
*/
|
||||
Zotero.Item.prototype.fromJSON = function (json, options = {}) {
|
||||
var strict = !!options.strict;
|
||||
var migrateExtra = !strict;
|
||||
|
||||
if (!json.itemType && !this._itemTypeID) {
|
||||
throw new Error("itemType property not provided");
|
||||
|
|
@ -5139,29 +5160,34 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
|
|||
|
||||
var isValidForType = {};
|
||||
var setFields = new Set();
|
||||
var { itemType, fields: extraFields, creators: extraCreators, extra } =
|
||||
Zotero.Utilities.Internal.extractExtraFields(
|
||||
json.extra || '',
|
||||
this,
|
||||
Object.keys(json)
|
||||
// TEMP until we move creator lines to real creators
|
||||
.concat('creators')
|
||||
);
|
||||
// If a different item type was parsed out of Extra, use that instead
|
||||
if (itemType && json.itemType != itemType) {
|
||||
itemTypeID = Zotero.ItemTypes.getID(itemType);
|
||||
this.setType(itemTypeID);
|
||||
|
||||
var extraFields = new Map();
|
||||
var extraCreators = [];
|
||||
var extra = json.extra || '';
|
||||
// Transfer valid fields from Extra to regular fields
|
||||
if (migrateExtra) {
|
||||
let itemType;
|
||||
({ itemType, fields: extraFields, creators: extraCreators, extra } =
|
||||
Zotero.Utilities.Internal.extractExtraFields(
|
||||
extra,
|
||||
this,
|
||||
Object.keys(json)
|
||||
// TEMP until we move creator lines to real creators
|
||||
.concat('creators')
|
||||
));
|
||||
// If a different item type was parsed out of Extra, use that instead
|
||||
if (itemType && json.itemType != itemType) {
|
||||
itemTypeID = Zotero.ItemTypes.getID(itemType);
|
||||
this.setType(itemTypeID);
|
||||
}
|
||||
for (let [field, value] of extraFields) {
|
||||
this.setField(field, value);
|
||||
setFields.add(field);
|
||||
extraFields.delete(field);
|
||||
}
|
||||
}
|
||||
var invalidFieldLogLines = new Map();
|
||||
|
||||
// Transfer valid fields from Extra to regular fields
|
||||
// Currently disabled
|
||||
/*for (let [field, value] of extraFields) {
|
||||
this.setField(field, value);
|
||||
setFields.add(field);
|
||||
extraFields.delete(field);
|
||||
}*/
|
||||
|
||||
for (let field in json) {
|
||||
let val = json[field];
|
||||
|
||||
|
|
|
|||
|
|
@ -291,10 +291,12 @@ Zotero.Library.prototype._set = function(prop, val) {
|
|||
if (newVal != val) {
|
||||
throw new Error(`${prop} must be an integer (${typeof val} '${val}' given)`);
|
||||
}
|
||||
// Ensure that it is never decreasing, unless it is being set to -1
|
||||
// by Reset File Sync History
|
||||
if (val != -1 && val < this._libraryStorageVersion) {
|
||||
throw new Error(prop + ' cannot decrease');
|
||||
}
|
||||
val = newVal;
|
||||
|
||||
// Ensure that it is never decreasing
|
||||
if (val < this._libraryStorageVersion) throw new Error(prop + ' cannot decrease');
|
||||
break;
|
||||
|
||||
case '_libraryLastSync':
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ Zotero.FileHandlers = {
|
|||
handler = handler || systemHandler;
|
||||
if (handler) {
|
||||
if (Zotero.isMac) {
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
try {
|
||||
await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', handler, path]);
|
||||
return true;
|
||||
|
|
@ -156,6 +157,9 @@ Zotero.FileHandlers = {
|
|||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
finally {
|
||||
Zotero.Utilities.Internal.Environment.restoreMozillaVariables();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -498,8 +502,13 @@ Zotero.FileHandlers = {
|
|||
if (!Zotero.File.pathToFile(command).isExecutable()) {
|
||||
throw new Error(`${command} is not an executable`);
|
||||
}
|
||||
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
|
||||
// Do not await
|
||||
Zotero.Utilities.Internal.exec(command, args);
|
||||
var promise = Zotero.Utilities.Internal.exec(command, args);
|
||||
|
||||
promise.finally(() => Zotero.Utilities.Internal.Environment.restoreMozillaVariables());
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1086,9 +1086,16 @@ Zotero.HTTP = new function() {
|
|||
};
|
||||
|
||||
|
||||
this.getDisplayURI = function (uri) {
|
||||
this.getDisplayURI = function (uri, noCredentials) {
|
||||
if (!uri.password) return uri;
|
||||
return uri.mutate().setPassword('********').finalize();
|
||||
uri = uri.mutate();
|
||||
if (noCredentials) {
|
||||
uri.setUserPass('');
|
||||
}
|
||||
else {
|
||||
uri.setPassword('********');
|
||||
}
|
||||
return uri.finalize();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ Zotero.Integration = new function() {
|
|||
if (document) {
|
||||
try {
|
||||
await document.cleanup();
|
||||
if (!Zotero.Integration.currentSession._dontActivateDocument) {
|
||||
if (!Zotero.Integration.currentSession?._dontActivateDocument) {
|
||||
await document.activate();
|
||||
}
|
||||
|
||||
|
|
@ -898,7 +898,8 @@ Zotero.Integration.Interface.prototype.setDocPrefs = Zotero.Promise.coroutine(fu
|
|||
|
||||
// Perform noteType or fieldType conversion
|
||||
let fields = yield this._session.getFields();
|
||||
|
||||
|
||||
const styleHasChanged = oldData.style.styleID != this._session.data.style.styleID;
|
||||
var convertBibliographies = oldData.prefs.fieldType != this._session.data.prefs.fieldType;
|
||||
var convertItems = convertBibliographies
|
||||
|| oldData.prefs.noteType != this._session.data.prefs.noteType;
|
||||
|
|
@ -931,7 +932,7 @@ Zotero.Integration.Interface.prototype.setDocPrefs = Zotero.Promise.coroutine(fu
|
|||
this._session._fields = null;
|
||||
this._session.ignoreEmptyBibliography = true;
|
||||
|
||||
if (this._session.data.prefs.delayCitationUpdates && !fieldsToConvert.length) return;
|
||||
if (this._session.data.prefs.delayCitationUpdates && !fieldsToConvert.length && !styleHasChanged) return;
|
||||
|
||||
yield this._session.updateFromDocument(FORCE_CITATIONS_RESET_TEXT);
|
||||
return this._session.updateDocument(FORCE_CITATIONS_RESET_TEXT, true, true);
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ Zotero.RecognizeDocument = new function () {
|
|||
throw new Error();
|
||||
}
|
||||
|
||||
let res = await _processItem(item);
|
||||
_progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, item.getField('title'));
|
||||
let parentItem = await _processItem(item);
|
||||
_progressQueue.updateRow(itemID, Zotero.ProgressQueue.ROW_SUCCEEDED, parentItem.getField('title'));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
|
@ -250,7 +250,7 @@ Zotero.RecognizeDocument = new function () {
|
|||
/**
|
||||
* Processes the item and places it as a children of the new item
|
||||
* @param itemID
|
||||
* @return {Promise}
|
||||
* @return {Promise} A promise that resolves to a newly created, recognized parent item
|
||||
*/
|
||||
async function _processItem(attachment) {
|
||||
// Make sure the attachment still doesn't have a parent
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Zotero.Sync.Storage = new function () {
|
|||
* @param {String} libraryKey
|
||||
* @param {Number|NULL}
|
||||
*/
|
||||
this.setItemDownloadPercentage = function (libraryKey, percentage) {
|
||||
this.setItemDownloadPercentage = Zotero.Utilities.throttle(function (libraryKey, percentage) {
|
||||
Zotero.debug("Setting image download percentage to " + percentage
|
||||
+ " for item " + libraryKey);
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ Zotero.Sync.Storage = new function () {
|
|||
}
|
||||
Zotero.Notifier.trigger('redraw', 'item', parentItem.id, { column: "hasAttachment" });
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
|
||||
function error(e) {
|
||||
|
|
|
|||
|
|
@ -149,7 +149,9 @@ Zotero.Sync.Storage.Engine.prototype.start = Zotero.Promise.coroutine(function*
|
|||
// full check of this library, check only files that were previously modified or opened
|
||||
// recently
|
||||
else if (this.background
|
||||
&& !this.firstInSession
|
||||
// TEMP: Don't check all files at startup
|
||||
// https://github.com/zotero/zotero/issues/5025
|
||||
//&& !this.firstInSession
|
||||
&& this.local.lastFullFileCheck[libraryID]
|
||||
&& (this.local.lastFullFileCheck[libraryID]
|
||||
+ (this.maxCheckAge * 1000)) > new Date().getTime()) {
|
||||
|
|
|
|||
|
|
@ -550,6 +550,10 @@ Zotero.Sync.Storage.Local = {
|
|||
}
|
||||
sql = "UPDATE itemAttachments SET syncState=? WHERE itemID IN (" + sql + ")";
|
||||
await Zotero.DB.queryAsync(sql, [this.SYNC_STATE_TO_UPLOAD].concat(params));
|
||||
|
||||
var library = Zotero.Libraries.get(libraryID);
|
||||
library.storageVersion = -1;
|
||||
await library.save();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ if (!Zotero.Sync.Storage.Mode) {
|
|||
Zotero.Sync.Storage.Mode.WebDAV = function (options) {
|
||||
this.options = options;
|
||||
|
||||
this.VerificationError = function (error, uri) {
|
||||
this.VerificationError = function (error, url) {
|
||||
this.message = `WebDAV verification error (${error})`;
|
||||
this.error = error;
|
||||
this.uri = uri;
|
||||
this.url = url;
|
||||
}
|
||||
this.VerificationError.prototype = Object.create(Error.prototype);
|
||||
}
|
||||
|
|
@ -196,14 +196,23 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
throw new this.VerificationError("NO_PASSWORD");
|
||||
}
|
||||
|
||||
url = scheme + '://'
|
||||
var spec = scheme + '://'
|
||||
+ encodeURIComponent(username) + ':' + encodeURIComponent(password) + '@'
|
||||
+ url
|
||||
+ (url.endsWith('/') ? '' : '/');
|
||||
|
||||
var io = Services.io;
|
||||
this._parentURI = io.newURI(url, null, null);
|
||||
this._rootURI = io.newURI(url + "zotero/", null, null);
|
||||
try {
|
||||
this._parentURI = io.newURI(spec, null, null);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message.includes('NS_ERROR_MALFORMED_URI')) {
|
||||
let displayURL = scheme + '://' + url + (url.endsWith('/') ? '' : '/');
|
||||
throw new this.VerificationError("INVALID_URL", displayURL);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
this._rootURI = io.newURI(spec + "zotero/", null, null);
|
||||
Zotero.HTTP.CookieBlocker.addURL(this._rootURI.spec);
|
||||
},
|
||||
|
||||
|
|
@ -641,7 +650,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
|
||||
var dav = req.getResponseHeader("DAV");
|
||||
if (dav == null) {
|
||||
throw new this.VerificationError("NOT_DAV", uri);
|
||||
throw new this.VerificationError("NOT_DAV", Zotero.HTTP.getDisplayURI(uri, true).spec);
|
||||
}
|
||||
|
||||
var headers = { Depth: 0 };
|
||||
|
|
@ -683,7 +692,10 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
catch (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
if (e.status >= 200 && e.status < 300) {
|
||||
throw new this.VerificationError("NONEXISTENT_FILE_NOT_MISSING", uri);
|
||||
throw new this.VerificationError(
|
||||
"NONEXISTENT_FILE_NOT_MISSING",
|
||||
Zotero.HTTP.getDisplayURI(uri, true).spec
|
||||
);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
@ -730,7 +742,10 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
// This can also be from IIS 6+, which is configured not to serve .prop files.
|
||||
// http://support.microsoft.com/kb/326965
|
||||
else if (req.status == 404) {
|
||||
throw new this.VerificationError("FILE_MISSING_AFTER_UPLOAD", uri);
|
||||
throw new this.VerificationError(
|
||||
"FILE_MISSING_AFTER_UPLOAD",
|
||||
Zotero.HTTP.getDisplayURI(uri, true).spec
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (req.status == 404) {
|
||||
|
|
@ -751,10 +766,16 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
});
|
||||
|
||||
if (req.status == 207) {
|
||||
throw new this.VerificationError("ZOTERO_DIR_NOT_FOUND", uri);
|
||||
throw new this.VerificationError(
|
||||
"ZOTERO_DIR_NOT_FOUND",
|
||||
Zotero.HTTP.getDisplayURI(uri, true).spec
|
||||
);
|
||||
}
|
||||
else if (req.status == 404) {
|
||||
throw new this.VerificationError("PARENT_DIR_NOT_FOUND", uri);
|
||||
throw new this.VerificationError(
|
||||
"PARENT_DIR_NOT_FOUND",
|
||||
Zotero.HTTP.getDisplayURI(uri, true).spec
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -805,10 +826,6 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
}
|
||||
}
|
||||
else if (err instanceof this.VerificationError) {
|
||||
let spec;
|
||||
if (err.uri) {
|
||||
spec = err.uri.scheme + '://' + err.uri.hostPort + err.uri.pathQueryRef;
|
||||
}
|
||||
switch (err.error) {
|
||||
case "NO_URL":
|
||||
errorMsg = Zotero.getString('sync.storage.error.webdav.enterURL');
|
||||
|
|
@ -822,21 +839,22 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
|
|||
errorMsg = Zotero.getString('sync.error.enterPassword');
|
||||
break;
|
||||
|
||||
case "INVALID_URL":
|
||||
case "NOT_DAV":
|
||||
errorMsg = Zotero.getString('sync.storage.error.webdav.invalidURL', spec);
|
||||
errorMsg = Zotero.getString('sync.storage.error.webdav.invalidURL', err.url);
|
||||
break;
|
||||
|
||||
case "PARENT_DIR_NOT_FOUND":
|
||||
errorTitle = Zotero.getString('sync.storage.error.directoryNotFound');
|
||||
var parentSpec = spec.replace(/zotero\/$/, "");
|
||||
errorMsg = Zotero.getString('sync.storage.error.doesNotExist', parentSpec);
|
||||
var parentURL = err.url.replace(/zotero\/$/, "");
|
||||
errorMsg = Zotero.getString('sync.storage.error.doesNotExist', parentURL);
|
||||
break;
|
||||
|
||||
case "ZOTERO_DIR_NOT_FOUND":
|
||||
var create = promptService.confirmEx(
|
||||
window,
|
||||
Zotero.getString('sync.storage.error.directoryNotFound'),
|
||||
Zotero.getString('sync.storage.error.doesNotExist', spec) + "\n\n"
|
||||
Zotero.getString('sync.storage.error.doesNotExist', err.url) + "\n\n"
|
||||
+ Zotero.getString('sync.storage.error.createNow'),
|
||||
promptService.BUTTON_POS_0
|
||||
* promptService.BUTTON_TITLE_IS_STRING
|
||||
|
|
|
|||
|
|
@ -1076,7 +1076,9 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
|
|||
var filename = item.attachmentFilename;
|
||||
var fileSize = (await OS.File.stat(item.getFilePath())).size;
|
||||
|
||||
text += "\n\n" + filename + " (" + Math.round(fileSize / 1024) + " KB)";
|
||||
text += "\n\n" + filename
|
||||
// TODO: Format more intelligently (e.g., use MB or GB for large files)
|
||||
+ " (" + Zotero.Utilities.numberFormat(Math.round(fileSize / 1024), 0) + " KB)";
|
||||
|
||||
var e = new Zotero.Error(
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -933,6 +933,22 @@ Zotero.Sync.Data.Local = {
|
|||
['mtime', 'md5', 'dateAdded', 'dateModified']
|
||||
);
|
||||
|
||||
// If local object became a child item and remote was added to any
|
||||
// collections, we need to remove the 'collections' changes and add
|
||||
// the parent item to those collections instead
|
||||
if (objectType == 'item'
|
||||
&& !obj.isTopLevelItem()
|
||||
&& (obj.isNote() || obj.isAttachment())) {
|
||||
let collections = result.changes
|
||||
.filter(x => x.field == 'collections' && x.op == 'member-add')
|
||||
.map(x => x.value);
|
||||
if (collections.length) {
|
||||
result.changes = result.changes
|
||||
.filter(x => !(x.field == 'collections' && x.op == 'member-add'));
|
||||
saveOptions.newParentItemCollections = collections;
|
||||
}
|
||||
}
|
||||
|
||||
// If no changes, just update local version number and mark as synced
|
||||
if (!result.changes.length && !result.conflicts.length) {
|
||||
Zotero.debug("No remote changes to apply to local "
|
||||
|
|
@ -1542,6 +1558,24 @@ Zotero.Sync.Data.Local = {
|
|||
Zotero.Libraries.get(obj.libraryID).storageDownloadNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// See explanation in processObjectsFromJSON()
|
||||
if (options.newParentItemCollections) {
|
||||
let parentItem = obj.parentItem;
|
||||
for (let c of options.newParentItemCollections) {
|
||||
parentItem.addToCollection(c);
|
||||
}
|
||||
yield parentItem.save({
|
||||
skipEditCheck: true,
|
||||
skipDateModifiedUpdate: true,
|
||||
skipSelect: true,
|
||||
notifierQueue: options.notifierQueue,
|
||||
// Errors are logged elsewhere, so skip in DataObject.save()
|
||||
errorHandler: function (e) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// For now, allow sync to proceed after all errors
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
}
|
||||
}.bind(this),
|
||||
background: !!options.background,
|
||||
firstInSession: _firstInSession,
|
||||
firstInSession: options.firstInSession,
|
||||
resetMode: options.resetMode
|
||||
};
|
||||
|
||||
|
|
@ -913,14 +913,25 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
}
|
||||
|
||||
|
||||
this.end = Zotero.Promise.coroutine(function* (options) {
|
||||
this.end = async function (options) {
|
||||
_syncInProgress = false;
|
||||
yield this.checkErrors(_errors, options);
|
||||
await this.checkErrors(_errors, options);
|
||||
if (!options.restartSync) {
|
||||
this.updateIcons(_errors);
|
||||
|
||||
// If foreground sync, trigger dialog button immediately for some errors
|
||||
// (e.g., long tag fixer)
|
||||
if (!options.background && _errors.length) {
|
||||
if (_errors[0].dialogButtonImmediate) {
|
||||
let maybePromise = _errors[0].dialogButtonCallback();
|
||||
if (maybePromise && maybePromise.then) {
|
||||
await maybePromise;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_errors = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -1253,6 +1264,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
|
||||
options.restartSync = true;
|
||||
});
|
||||
e.dialogButtonImmediate = true;
|
||||
}
|
||||
else {
|
||||
// Note too long
|
||||
|
|
@ -1296,14 +1308,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If not a background sync, show dialog immediately
|
||||
if (!options.background && e.dialogButtonCallback) {
|
||||
let maybePromise = e.dialogButtonCallback();
|
||||
if (maybePromise && maybePromise.then) {
|
||||
yield maybePromise;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Show warning for unknown data that couldn't be saved
|
||||
|
|
|
|||
|
|
@ -938,11 +938,13 @@ Zotero.Utilities.Internal = {
|
|||
}
|
||||
|
||||
// Tags
|
||||
for (let i = 0; i < item.tags.length; i++) {
|
||||
if (!item.tags[i].type) {
|
||||
item.tags[i].type = 0;
|
||||
if (item.tags) {
|
||||
for (let i = 0; i < item.tags.length; i++) {
|
||||
if (!item.tags[i].type) {
|
||||
item.tags[i].type = 0;
|
||||
}
|
||||
// No translator ever used "primary", "fields", or "linkedItems" objects
|
||||
}
|
||||
// No translator ever used "primary", "fields", or "linkedItems" objects
|
||||
}
|
||||
|
||||
// "related" was never used (array of itemIDs)
|
||||
|
|
@ -2906,6 +2908,85 @@ Zotero.Utilities.Internal.getProcessID = function () {
|
|||
};
|
||||
|
||||
|
||||
Zotero.Utilities.Internal.Environment = {
|
||||
/**
|
||||
* Unset an environment variable
|
||||
*
|
||||
* Since nsIEnvironment doesn't have an unset method
|
||||
*/
|
||||
unset: function (varName) {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
let lib;
|
||||
let result;
|
||||
let success;
|
||||
// Windows
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
if (Zotero.isWin) {
|
||||
lib = ctypes.open("kernel32.dll");
|
||||
let SetEnvironmentVariable = lib.declare(
|
||||
"SetEnvironmentVariableW",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.int, // Return type
|
||||
ctypes.char16_t.ptr, // Argument type: LPCTSTR (variable name)
|
||||
ctypes.char16_t.ptr // Argument type: LPCTSTR (value, NULL to unset)
|
||||
);
|
||||
result = SetEnvironmentVariable(varName, null);
|
||||
success = result != 0; // "If the function succeeds, the return value is nonzero."
|
||||
}
|
||||
// macOS or Linux
|
||||
else {
|
||||
lib = ctypes.open(Zotero.isMac ? "/usr/lib/libSystem.dylib" : "libc.so.6");
|
||||
let unsetenv = lib.declare(
|
||||
"unsetenv",
|
||||
ctypes.default_abi,
|
||||
ctypes.int, // Return type
|
||||
ctypes.char.ptr // Argument type: const char *
|
||||
);
|
||||
result = unsetenv(varName);
|
||||
success = result == 0;
|
||||
}
|
||||
|
||||
// Check the result
|
||||
if (success) {
|
||||
//Zotero.debug(`Unset environment variable ${varName}`);
|
||||
}
|
||||
else {
|
||||
Zotero.logError(`Failed to unset environment variable ${varName} (${result})`);
|
||||
}
|
||||
|
||||
lib.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the Mozilla environment variables that we changed in the launcher
|
||||
*
|
||||
* Call this before launching another process that might be Firefox so that we don't end up
|
||||
* using the wrong Firefox profile when launching URLs or PDFs. On Windows, it's not necessary
|
||||
* to call this when launching URLs, only processes.
|
||||
*
|
||||
* https://github.com/zotero/zotero/issues/4981
|
||||
*/
|
||||
clearMozillaVariables: function () {
|
||||
this.unset("MOZ_ALLOW_DOWNGRADE");
|
||||
this.unset("MOZ_LEGACY_PROFILES");
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-set the Mozilla environment variables that we changed in the launcher
|
||||
*
|
||||
* Call this in a finally() after using unsetMozillaVariables(). This mostly isn't necessary,
|
||||
* since most new launches of Zotero would use the modified launcher, but a restart on Linux
|
||||
* skips our shell script where we set these variables.
|
||||
*/
|
||||
restoreMozillaVariables: function () {
|
||||
var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
env.set("MOZ_ALLOW_DOWNGRADE", "1");
|
||||
env.set("MOZ_LEGACY_PROFILES", "1");
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Base64 encode / decode
|
||||
* From http://www.webtoolkit.info/
|
||||
|
|
@ -3048,9 +3129,18 @@ Zotero.Utilities.Internal.OpenURL = {
|
|||
);
|
||||
if (co) {
|
||||
let base = Zotero.Prefs.get("openURL.resolver");
|
||||
// Add & if there's already a ?
|
||||
let splice = base.indexOf("?") == -1 ? "?" : "&";
|
||||
return base + splice + co;
|
||||
if (!base.endsWith('?')) {
|
||||
if (base.includes('login?url=')) {
|
||||
base += '?';
|
||||
}
|
||||
else if (base.includes('?')) {
|
||||
base += '&';
|
||||
}
|
||||
else {
|
||||
base += '?';
|
||||
}
|
||||
}
|
||||
return base + co;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -996,6 +996,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
*/
|
||||
this.launchFile = function (file) {
|
||||
file = Zotero.File.pathToFile(file);
|
||||
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
|
||||
try {
|
||||
Zotero.debug("Launching " + file.path);
|
||||
file.launch();
|
||||
|
|
@ -1038,6 +1041,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Zotero.Utilities.Internal.Environment.restoreMozillaVariables();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1062,8 +1068,12 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
args = [filePath];
|
||||
}
|
||||
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
|
||||
// Async, but we don't want to block
|
||||
Zotero.Utilities.Internal.exec(applicationPath, args);
|
||||
var promise = Zotero.Utilities.Internal.exec(applicationPath, args);
|
||||
|
||||
promise.finally(() => Zotero.Utilities.Internal.Environment.restoreMozillaVariables());
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1093,12 +1103,29 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
if (!found.value) {
|
||||
throw new Error(`Handler not found for '${scheme}' URLs`);
|
||||
}
|
||||
svc.loadURI(Services.io.newURI(url, null, null));
|
||||
try {
|
||||
if (!Zotero.isWin) {
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
}
|
||||
|
||||
svc.loadURI(Services.io.newURI(url, null, null));
|
||||
}
|
||||
finally {
|
||||
if (!Zotero.isWin) {
|
||||
Zotero.Utilities.Internal.Environment.restoreMozillaVariables();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var mozCleared = false;
|
||||
try {
|
||||
if (!Zotero.isWin) {
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
mozCleared = true;
|
||||
}
|
||||
|
||||
var uri = Services.io.newURI(url, null, null);
|
||||
var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1']
|
||||
.getService(Components.interfaces.nsIExternalProtocolService)
|
||||
|
|
@ -1123,6 +1150,11 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
+ "check extensions.zotero." + pref + " in about:config");
|
||||
}
|
||||
|
||||
if (!mozCleared) {
|
||||
Zotero.Utilities.Internal.Environment.clearMozillaVariables();
|
||||
mozCleared = true;
|
||||
}
|
||||
|
||||
var proc = Components.classes["@mozilla.org/process/util;1"]
|
||||
.createInstance(Components.interfaces.nsIProcess);
|
||||
proc.init(exec);
|
||||
|
|
@ -1130,6 +1162,11 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
var args = [url];
|
||||
proc.runw(false, args, args.length);
|
||||
}
|
||||
finally {
|
||||
if (mozCleared) {
|
||||
Zotero.Utilities.Internal.Environment.restoreMozillaVariables();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ var ZoteroPane = new function()
|
|||
this.handleKeyPress = handleKeyPress;
|
||||
this.getSelectedCollection = getSelectedCollection;
|
||||
this.getSelectedSavedSearch = getSelectedSavedSearch;
|
||||
this.getSelectedItems = getSelectedItems;
|
||||
this.getSortedItems = getSortedItems;
|
||||
this.getSortField = getSortField;
|
||||
this.getSortDirection = getSortDirection;
|
||||
this.setItemsPaneMessage = setItemsPaneMessage;
|
||||
|
|
@ -1936,7 +1934,7 @@ var ZoteroPane = new function()
|
|||
|
||||
// Tab selection observer in standalone.js makes sure that
|
||||
// updateQuickCopyCommands is called
|
||||
if (Zotero_Tabs.selectedID == 'zotero-pane') {
|
||||
if (Zotero_Tabs.selectedType == 'library') {
|
||||
this.updateQuickCopyCommands(selectedItems);
|
||||
}
|
||||
|
||||
|
|
@ -2654,21 +2652,10 @@ var ZoteroPane = new function()
|
|||
|
||||
this.copySelectedItemsToClipboard = function (asCitations) {
|
||||
var items = [];
|
||||
if (Zotero_Tabs.selectedID == 'zotero-pane') {
|
||||
let itemIDs = this.getSelectedItems(true);
|
||||
// Get selected item IDs in the item tree order
|
||||
itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id));
|
||||
items = Zotero.Items.get(itemIDs);
|
||||
}
|
||||
else {
|
||||
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
let item = Zotero.Items.get(reader.itemID);
|
||||
if (item.parentItem) {
|
||||
items = [item.parentItem];
|
||||
}
|
||||
}
|
||||
}
|
||||
let itemIDs = this.getSelectedItems(true);
|
||||
// Get selected item IDs in the item tree order
|
||||
itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id));
|
||||
items = Zotero.Items.get(itemIDs);
|
||||
|
||||
if (!items.length) {
|
||||
return;
|
||||
|
|
@ -3070,14 +3057,28 @@ var ZoteroPane = new function()
|
|||
*
|
||||
* If asIDs is true, return an array of itemIDs instead
|
||||
*/
|
||||
function getSelectedItems(asIDs)
|
||||
{
|
||||
if (!this.itemsView) {
|
||||
return [];
|
||||
this.getSelectedItems = function (asIDs) {
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library':
|
||||
if (!this.itemsView) {
|
||||
return [];
|
||||
}
|
||||
return this.itemsView.getSelectedItems(asIDs);
|
||||
case 'reader': {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
if (reader) {
|
||||
let item = Zotero.Items.get(reader.itemID);
|
||||
if (item.parentItem) {
|
||||
item = item.parentItem;
|
||||
}
|
||||
return asIDs ? [item.id] : [item];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.itemsView.getSelectedItems(asIDs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -3085,13 +3086,18 @@ var ZoteroPane = new function()
|
|||
*
|
||||
* If asIDs is true, return an array of itemIDs instead
|
||||
*/
|
||||
function getSortedItems(asIDs) {
|
||||
if (!this.itemsView) {
|
||||
return [];
|
||||
this.getSortedItems = function (asIDs) {
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library':
|
||||
if (!this.itemsView) {
|
||||
return [];
|
||||
}
|
||||
return this.itemsView.getSortedItems(asIDs);
|
||||
default:
|
||||
// ALl non-library tabs: Visible items == "selected" items
|
||||
return this.getSelectedItems(asIDs);
|
||||
}
|
||||
|
||||
return this.itemsView.getSortedItems(asIDs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getSortField() {
|
||||
|
|
@ -4510,22 +4516,14 @@ var ZoteroPane = new function()
|
|||
/**
|
||||
* @param {Boolean} [link]
|
||||
* @param {Number} [parentItemID]
|
||||
* @param {nsIFile[]} [files] Used instead of showing a file picker - for tests
|
||||
* @param {String[]} [files] Used instead of showing a file picker - for tests
|
||||
* @returns {Promise<Zotero.Item[] | null>}
|
||||
*/
|
||||
this.addAttachmentFromDialog = async function (link, parentItemID, files = null) {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return null;
|
||||
}
|
||||
|
||||
var collectionTreeRow = this.getCollectionTreeRow();
|
||||
if (link) {
|
||||
if (collectionTreeRow.isWithinGroup()) {
|
||||
Zotero.alert(null, "", "Linked files cannot be added to group libraries.");
|
||||
return null;
|
||||
}
|
||||
else if (collectionTreeRow.isPublications()) {
|
||||
var libraryID;
|
||||
if (Zotero_Tabs.selectedType === 'library') {
|
||||
let collectionTreeRow = this.getCollectionTreeRow();
|
||||
if (link && collectionTreeRow.isPublications()) {
|
||||
Zotero.alert(
|
||||
null,
|
||||
Zotero.getString('general.error'),
|
||||
|
|
@ -4533,15 +4531,25 @@ var ZoteroPane = new function()
|
|||
);
|
||||
return null;
|
||||
}
|
||||
libraryID = collectionTreeRow.ref.libraryID;
|
||||
}
|
||||
else {
|
||||
libraryID = Zotero.Items.get(parentItemID).libraryID;
|
||||
}
|
||||
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return null;
|
||||
}
|
||||
// TODO: disable in menu
|
||||
if (!this.canEditFiles()) {
|
||||
this.displayCannotEditLibraryFilesMessage();
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (link && Zotero.Libraries.get(libraryID).isGroup) {
|
||||
Zotero.alert(null, "", "Linked files cannot be added to group libraries.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var libraryID = collectionTreeRow.ref.libraryID;
|
||||
|
||||
if (!files) {
|
||||
var fp = new FilePicker();
|
||||
|
|
@ -5369,36 +5377,48 @@ var ZoteroPane = new function()
|
|||
/**
|
||||
* Test if the user can edit the currently selected view
|
||||
*
|
||||
* @param {Integer} [row]
|
||||
*
|
||||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
* @param {Integer} [row] Row index - ignored if not in library tab
|
||||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
*/
|
||||
this.canEdit = function (row) {
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library':
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
}
|
||||
return this.collectionsView.getRow(row).editable;
|
||||
case 'reader': {
|
||||
let itemID = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)?.itemID;
|
||||
if (!itemID) {
|
||||
throw new Error('Reader tab has no itemID');
|
||||
}
|
||||
return Zotero.Items.get(itemID).library.editable;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
var collectionTreeRow = this.collectionsView.getRow(row);
|
||||
return collectionTreeRow.editable;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test if the user can edit the parent library of the selected view
|
||||
*
|
||||
* @param {Integer} [row]
|
||||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
* @param {Integer} [row] Row index - ignored if not in library tab
|
||||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
*/
|
||||
this.canEditLibrary = function (row) {
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library': // Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
}
|
||||
return Zotero.Libraries.get(this.collectionsView.getRow(row).ref.libraryID).editable;
|
||||
default:
|
||||
// All non-library tabs: canEditLibrary() == canEdit()
|
||||
return this.canEdit(row);
|
||||
}
|
||||
|
||||
var collectionTreeRow = this.collectionsView.getRow(row);
|
||||
return Zotero.Libraries.get(collectionTreeRow.ref.libraryID).editable;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -5409,14 +5429,24 @@ var ZoteroPane = new function()
|
|||
* @return {Boolean} TRUE if user can edit, FALSE if not
|
||||
*/
|
||||
this.canEditFiles = function (row) {
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
switch (Zotero_Tabs.selectedType) {
|
||||
case 'library':
|
||||
// Currently selected row
|
||||
if (row === undefined) {
|
||||
row = this.collectionsView.selection.focused;
|
||||
}
|
||||
return this.collectionsView.getRow(row).filesEditable;
|
||||
case 'reader': {
|
||||
let itemID = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID)?.itemID;
|
||||
if (!itemID) {
|
||||
throw new Error('Reader tab has no itemID');
|
||||
}
|
||||
return Zotero.Items.get(itemID).library.filesEditable;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
var collectionTreeRow = this.collectionsView.getRow(row);
|
||||
return collectionTreeRow.filesEditable;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.displayCannotEditLibraryMessage = function () {
|
||||
|
|
@ -6814,13 +6844,14 @@ var ZoteroPane = new function()
|
|||
};
|
||||
|
||||
this.buildFieldTransformMenu = function ({ target, onTransform }) {
|
||||
let doc = target.ownerDocument;
|
||||
let value = target.value;
|
||||
let valueTitleCased = Zotero.Utilities.capitalizeTitle(value.toLowerCase(), true);
|
||||
let valueSentenceCased = Zotero.Utilities.sentenceCase(value);
|
||||
|
||||
let menupopup = document.createXULElement('menupopup');
|
||||
let menupopup = doc.createXULElement('menupopup');
|
||||
|
||||
let titleCase = document.createXULElement('menuitem');
|
||||
let titleCase = doc.createXULElement('menuitem');
|
||||
titleCase.setAttribute('label', Zotero.getString('zotero.item.textTransform.titlecase'));
|
||||
titleCase.addEventListener('command', () => {
|
||||
onTransform(valueTitleCased);
|
||||
|
|
@ -6828,7 +6859,7 @@ var ZoteroPane = new function()
|
|||
titleCase.disabled = valueTitleCased == value;
|
||||
menupopup.append(titleCase);
|
||||
|
||||
let sentenceCase = document.createXULElement('menuitem');
|
||||
let sentenceCase = doc.createXULElement('menuitem');
|
||||
sentenceCase.setAttribute('label', Zotero.getString('zotero.item.textTransform.sentencecase'));
|
||||
sentenceCase.addEventListener('command', () => {
|
||||
onTransform(valueSentenceCased);
|
||||
|
|
|
|||
|
|
@ -418,6 +418,7 @@
|
|||
command="cmd_find"/>
|
||||
<menuitem id="menu_advancedSearch"
|
||||
label="&zotero.toolbar.advancedSearch;"
|
||||
key="key_advancedSearch"
|
||||
command="cmd_zotero_advancedSearch"/>
|
||||
<menuseparator hidden="true" id="textfieldDirection-separator"/>
|
||||
<menuitem id="textfieldDirection-swap"
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ preferences-advanced-language-and-region-title = Kieli ja alue
|
|||
preferences-advanced-enable-bidi-ui =
|
||||
.label = Käytä kaksisuuntaisen tekstin editointiohjelmia
|
||||
preferences-advanced-reset-data-dir =
|
||||
.label = Revert to Default Location…
|
||||
.label = Palauta vakiosijaintiin…
|
||||
preferences-advanced-custom-data-dir =
|
||||
.label = Use Custom Location…
|
||||
.label = Käytä omaa sijaintia…
|
||||
preferences-advanced-default-data-dir =
|
||||
.value = (Default: { $directory })
|
||||
.value = (Oletus: { $directory })
|
||||
.aria-label = Oletussijainti
|
||||
|
|
|
|||
|
|
@ -48,18 +48,18 @@ pdfReader-import-from-epub =
|
|||
.label = Tuo e-kirjan huomautukset...
|
||||
pdfReader-import-from-epub-prompt-title = Tuo e-kirjan huomautukset
|
||||
pdfReader-import-from-epub-prompt-text =
|
||||
{ -app-name } found { $count ->
|
||||
[1] { $count } { $tool } annotation
|
||||
*[other] { $count } { $tool } annotations
|
||||
}, last edited { $lastModifiedRelative }.
|
||||
{ -app-name } löysi { $count ->
|
||||
[1] { $count } { $tool } huomautuksen
|
||||
*[other] { $count } { $tool } huomautusta
|
||||
}, muokattu viimeksi { $lastModifiedRelative }.
|
||||
|
||||
Any { -app-name } annotations that were previously imported from this ebook will be updated.
|
||||
Kaikki { -app-name }-huomautukset jotka on tuotu aiemmin tästä e-kirjasta päivitetään.
|
||||
pdfReader-import-from-epub-no-annotations-current-file =
|
||||
This ebook does not appear to contain any importable annotations.
|
||||
Tämä e-kirja ei vaikuta sisältävän tuotavia huomautuksia.
|
||||
|
||||
{ -app-name } can import ebook annotations created in Calibre and KOReader.
|
||||
{ -app-name } voi tuoda e-kirjan huomautukset jotka on luotu Calibrella tai KOReaderilla.
|
||||
pdfReader-import-from-epub-no-annotations-other-file =
|
||||
“{ $filename }” does not appear to contain any Calibre or KOReader annotations.
|
||||
“{ $filename }” ei vaikuta sisältävän Calibrella tai KOReaderilla luotuja huomautuksia.
|
||||
|
||||
If this ebook has been annotated with KOReader, try selecting a “metadata.epub.lua” file directly.
|
||||
pdfReader-import-from-epub-select-other = Select Other File…
|
||||
Jos tämän e-kirjan huomautukset on tehty KOReaderilla, koita valita “metadata.epub.lua”-tiedosto suoraan.
|
||||
pdfReader-import-from-epub-select-other = Valitse toinen tiedosto…
|
||||
|
|
|
|||
|
|
@ -285,11 +285,11 @@ integration-editBibliography-remove-button =
|
|||
.aria-label = { general-remove }
|
||||
integration-editBibliography-editor =
|
||||
.aria-label = Vuokkaa viitettä
|
||||
-integration-editBibliography-include-uncited = To include an uncited item in your bibliography, select it from the items list and press { general-add }.
|
||||
-integration-editBibliography-exclude-cited = You can also exclude a cited item by selecting it from the list of references and pressing { general-remove }.
|
||||
-integration-editBibliography-edit-reference = To change how a reference is formatted, use the text editor.
|
||||
-integration-editBibliography-include-uncited = Sisällyttääksesi viittaamattoman nimikkeen lähdeluetteloosi, valitse se nimikelistalta ja paina { general-add }.
|
||||
-integration-editBibliography-exclude-cited = Voit myös jättää pois viitatun nimikkeen lähdeluettelosta valitsemalla sen ja painamalla { general-remove }.
|
||||
-integration-editBibliography-edit-reference = Muuttaaksesi viitteen muotoilua, käytä tekstieditoria.
|
||||
integration-editBibliography-wrapper =
|
||||
.aria-label = Edit Bibliography dialog
|
||||
.aria-label = Muokkaa lähdeluetteloa -valintaikkuna
|
||||
.aria-description =
|
||||
{ -integration-editBibliography-include-uncited }
|
||||
{ -integration-editBibliography-exclude-cited }
|
||||
|
|
@ -391,7 +391,7 @@ menu-ui-density-comfortable =
|
|||
.label = Väljempi
|
||||
menu-ui-density-compact =
|
||||
.label = Tiivis
|
||||
pane-item-details = Item Details
|
||||
pane-item-details = Nimikkeen tiedot
|
||||
pane-info = Tiedot
|
||||
pane-abstract = Tiivistelmä
|
||||
pane-attachments = Liitteet
|
||||
|
|
@ -641,7 +641,7 @@ find-pdf-files-added =
|
|||
select-items-dialog =
|
||||
.buttonlabelaccept = Valitse
|
||||
select-items-convertToStandalone =
|
||||
.label = Convert to Standalone
|
||||
.label = Muuta erilliseksi
|
||||
select-items-convertToStandaloneAttachment =
|
||||
.label =
|
||||
{ $count ->
|
||||
|
|
@ -651,8 +651,8 @@ select-items-convertToStandaloneAttachment =
|
|||
select-items-convertToStandaloneNote =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] Convert to Standalone Note
|
||||
*[other] Convert to Standalone Notes
|
||||
[one] Muuta erilliseksi muistiinpanoksi
|
||||
*[other] Muuta erillisiksi muistiinpanoiksi
|
||||
}
|
||||
file-type-webpage = Verkkosivu
|
||||
file-type-image = Kuva
|
||||
|
|
|
|||
|
|
@ -1,96 +1,96 @@
|
|||
<!ENTITY preferencesCmdMac.label "Preferences...">
|
||||
<!ENTITY preferencesCmdMac.label "Postavke…">
|
||||
<!ENTITY preferencesCmdMac.commandkey ",">
|
||||
<!ENTITY servicesMenuMac.label "Services">
|
||||
<!ENTITY hideThisAppCmdMac.label "Hide &brandShortName;">
|
||||
<!ENTITY servicesMenuMac.label "Servisi">
|
||||
<!ENTITY hideThisAppCmdMac.label "Sakrij &brandShortName;">
|
||||
<!ENTITY hideThisAppCmdMac.commandkey "H">
|
||||
<!ENTITY hideOtherAppsCmdMac.label "Hide Others">
|
||||
<!ENTITY hideOtherAppsCmdMac.label "Sakrij ostalo">
|
||||
<!ENTITY hideOtherAppsCmdMac.commandkey "H">
|
||||
<!ENTITY showAllAppsCmdMac.label "Show All">
|
||||
<!ENTITY quitApplicationCmdMac.label "Quit Zotero">
|
||||
<!ENTITY showAllAppsCmdMac.label "Prikaži sve">
|
||||
<!ENTITY quitApplicationCmdMac.label "Prekini Zotero">
|
||||
<!ENTITY quitApplicationCmdMac.key "Q">
|
||||
|
||||
<!ENTITY fileMenu.label "File">
|
||||
<!ENTITY fileMenu.label "Datoteka">
|
||||
<!ENTITY fileMenu.accesskey "F">
|
||||
<!ENTITY saveCmd.label "Save…">
|
||||
<!ENTITY saveCmd.label "Spremi…">
|
||||
<!ENTITY saveCmd.key "S">
|
||||
<!ENTITY saveCmd.accesskey "A">
|
||||
<!ENTITY pageSetupCmd.label "Page Setup…">
|
||||
<!ENTITY pageSetupCmd.label "Postava stranice…">
|
||||
<!ENTITY pageSetupCmd.accesskey "U">
|
||||
<!ENTITY printCmd.label "Print…">
|
||||
<!ENTITY printCmd.label "Printaj…">
|
||||
<!ENTITY printCmd.key "P">
|
||||
<!ENTITY printCmd.accesskey "P">
|
||||
<!ENTITY closeCmd.label "Close">
|
||||
<!ENTITY closeCmd.label "Zatvori">
|
||||
<!ENTITY closeCmd.key "W">
|
||||
<!ENTITY closeCmd.accesskey "C">
|
||||
<!ENTITY manageAttachments.label "Manage Attachments">
|
||||
<!ENTITY convertToStored.label "Convert Linked Files to Stored Files…">
|
||||
<!ENTITY importCmd.label "Import...">
|
||||
<!ENTITY manageAttachments.label "Upravljanje prilozima">
|
||||
<!ENTITY convertToStored.label "Pretvori povezane u pohranjene datoteke…">
|
||||
<!ENTITY importCmd.label "Uvoz…">
|
||||
<!ENTITY importCmd.key "I">
|
||||
<!ENTITY importFromClipboardCmd.label "Import from Clipboard">
|
||||
<!ENTITY quitApplicationCmdWin.label "Exit">
|
||||
<!ENTITY importFromClipboardCmd.label "Uvoz iz međuspremnika">
|
||||
<!ENTITY quitApplicationCmdWin.label "Izlaz">
|
||||
<!ENTITY quitApplicationCmdWin.accesskey "x">
|
||||
<!ENTITY quitApplicationCmd.label "Quit">
|
||||
<!ENTITY quitApplicationCmd.label "Prekid">
|
||||
<!ENTITY quitApplicationCmd.accesskey "Q">
|
||||
|
||||
<!ENTITY copyCitationCmd.label "Copy Citation">
|
||||
<!ENTITY copyBibliographyCmd.label "Copy Bibliography">
|
||||
<!ENTITY copyNoteCmd.label "Copy Note">
|
||||
<!ENTITY copyCitationCmd.label "Kopiraj citat">
|
||||
<!ENTITY copyBibliographyCmd.label "Kopiraj biblioteku">
|
||||
<!ENTITY copyNoteCmd.label "Kopiraj bilješku">
|
||||
|
||||
<!ENTITY bidiSwitchPageDirectionItem.label "Switch Page Direction">
|
||||
<!ENTITY bidiSwitchPageDirectionItem.label "Promjena smjera stranice">
|
||||
<!ENTITY bidiSwitchPageDirectionItem.accesskey "g">
|
||||
<!ENTITY bidiSwitchTextDirectionItem.label "Switch Text Direction">
|
||||
<!ENTITY bidiSwitchTextDirectionItem.label "Promjena smjera teksta">
|
||||
<!ENTITY bidiSwitchTextDirectionItem.accesskey "w">
|
||||
<!ENTITY bidiSwitchTextDirectionItem.commandkey "X">
|
||||
|
||||
<!ENTITY goMenu.label "Go">
|
||||
<!ENTITY goMenu.label "Idi">
|
||||
<!ENTITY goMenu.accesskey "G">
|
||||
|
||||
<!ENTITY viewMenu.label "View">
|
||||
<!ENTITY viewMenu.label "Pogled">
|
||||
<!ENTITY viewMenu.accesskey "V">
|
||||
<!ENTITY layout.label "Layout">
|
||||
<!ENTITY standardView.label "Standard View">
|
||||
<!ENTITY stackedView.label "Stacked View">
|
||||
<!ENTITY collectionsPane.label "Collections Pane">
|
||||
<!ENTITY itemPane.label "Item Pane">
|
||||
<!ENTITY tagSelector.label "Tag Selector">
|
||||
<!ENTITY recursiveCollections.label "Show Items from Subcollections">
|
||||
<!ENTITY fontSize.label "Font Size">
|
||||
<!ENTITY noteFontSize.label "Note Font Size">
|
||||
<!ENTITY columns.label "Columns">
|
||||
<!ENTITY sortBy.label "Sort By">
|
||||
<!ENTITY layout.label "Izgled">
|
||||
<!ENTITY standardView.label "Standardni pogled">
|
||||
<!ENTITY stackedView.label "Složeni prikaz">
|
||||
<!ENTITY collectionsPane.label "Okno zbirke">
|
||||
<!ENTITY itemPane.label "Okno stavke">
|
||||
<!ENTITY tagSelector.label "Odabir oznaka">
|
||||
<!ENTITY recursiveCollections.label "Prikaži stavke iz podzbirki">
|
||||
<!ENTITY fontSize.label "Veličina slova">
|
||||
<!ENTITY noteFontSize.label "Veličina slova bilješke">
|
||||
<!ENTITY columns.label "Stupac">
|
||||
<!ENTITY sortBy.label "Sortiraj po">
|
||||
|
||||
<!ENTITY toolsMenu.label "Tools">
|
||||
<!ENTITY toolsMenu.label "Alati">
|
||||
<!ENTITY toolsMenu.accesskey "T">
|
||||
<!ENTITY installConnector.label "Install Browser Connector">
|
||||
<!ENTITY installConnector.label "Instalirajte Browser Connector…">
|
||||
<!ENTITY installConnector.accesskey "C">
|
||||
<!ENTITY addons.label "Add-ons">
|
||||
<!ENTITY developer.label "Developer">
|
||||
<!ENTITY addons.label "Dodaci">
|
||||
<!ENTITY developer.label "Programer">
|
||||
|
||||
<!ENTITY bringAllToFront.label "Bring All to Front">
|
||||
<!ENTITY bringAllToFront.label "Dovedite sve naprijed">
|
||||
|
||||
<!ENTITY helpMenu.label "Help">
|
||||
<!ENTITY helpMenu.label "Pomoć">
|
||||
<!ENTITY helpMenu.accesskey "H">
|
||||
<!ENTITY helpMenuWin.label "Help">
|
||||
<!ENTITY helpMenuWin.label "Pomoć">
|
||||
<!ENTITY helpMenuWin.accesskey "H">
|
||||
<!ENTITY helpMac.commandkey "?">
|
||||
<!ENTITY aboutProduct.label "About &brandShortName;">
|
||||
<!ENTITY aboutProduct.label "O &brandShortName;">
|
||||
<!ENTITY aboutProduct.accesskey "A">
|
||||
<!ENTITY productHelp.label "Support and Documentation">
|
||||
<!ENTITY productHelp.label "Potpora i dokumentacija">
|
||||
<!ENTITY productHelp.accesskey "D">
|
||||
<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
|
||||
<!ENTITY helpTroubleshootingInfo.label "Informacije o rješavanju problema">
|
||||
<!ENTITY helpTroubleshootingInfo.accesskey "T">
|
||||
<!ENTITY helpFeedbackPage.label "Discussion Forums">
|
||||
<!ENTITY helpFeedbackPage.label "Forumi za raspravu">
|
||||
<!ENTITY helpFeedbackPage.accesskey "F">
|
||||
|
||||
<!ENTITY helpReportErrors.label "Report Errors...">
|
||||
<!ENTITY helpReportErrors.label "Prijavi pogreške…">
|
||||
<!ENTITY helpReportErrors.accesskey "R">
|
||||
|
||||
<!ENTITY debugOutputLogging.label "Debug Output Logging">
|
||||
<!ENTITY debugOutputLogging.label "Zapisivanje izlaza otklanjanja pogrešaka">
|
||||
<!ENTITY debugOutputLogging.accesskey "L">
|
||||
<!ENTITY debugOutputLogging.submit "Submit Output">
|
||||
<!ENTITY debugOutputLogging.view "View Output">
|
||||
<!ENTITY debugOutputLogging.clear "Clear Output">
|
||||
<!ENTITY debugOutputLogging.restartWithLoggingEnabled "Restart with Logging Enabled…">
|
||||
<!ENTITY debugOutputLogging.submit "Pošalji izlaz">
|
||||
<!ENTITY debugOutputLogging.view "Pregled izlaza">
|
||||
<!ENTITY debugOutputLogging.clear "Očisti izlaz">
|
||||
<!ENTITY debugOutputLogging.restartWithLoggingEnabled "Ponovo pokreni s omogućenim zapisivanjem…">
|
||||
|
||||
<!ENTITY helpCheckForUpdates.label "Check for Updates…">
|
||||
<!ENTITY helpCheckForUpdates.label "Provjera nadopuna…">
|
||||
<!ENTITY helpCheckForUpdates.accesskey "U">
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ preferences-advanced-language-and-region-title = Kalba ir regonas
|
|||
preferences-advanced-enable-bidi-ui =
|
||||
.label = Įgalinti abikrypčius teksto redagavimo įrankius
|
||||
preferences-advanced-reset-data-dir =
|
||||
.label = Revert to Default Location…
|
||||
.label = Atstatyti numatytąją vietą…
|
||||
preferences-advanced-custom-data-dir =
|
||||
.label = Use Custom Location…
|
||||
.label = Naudoti savitą vietą...
|
||||
preferences-advanced-default-data-dir =
|
||||
.value = (Default: { $directory })
|
||||
.aria-label = Default location
|
||||
.value = (Numatytoji: { $directory })
|
||||
.aria-label = Numatytoji vieta
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ pdfReader-selectArea = Pažymėti sritį
|
|||
pdfReader-draw = Piešti
|
||||
pdfReader-highlightAnnotation = Paryškinimas
|
||||
pdfReader-underlineAnnotation = Pabraukimas
|
||||
pdfReader-noteAnnotation = Note Annotation
|
||||
pdfReader-textAnnotation = Text Annotation
|
||||
pdfReader-noteAnnotation = Pastaba
|
||||
pdfReader-textAnnotation = Tekstinė anotacija
|
||||
pdfReader-imageAnnotation = Image Annotation
|
||||
pdfReader-find-in-document = Paieška dokumente
|
||||
pdfReader-move-annotation-start-key =
|
||||
|
|
@ -14,13 +14,13 @@ pdfReader-move-annotation-start-key =
|
|||
[macos] { general-key-command }
|
||||
*[other] { general-key-alt }
|
||||
}
|
||||
pdfReader-a11yMoveAnnotation = Use the arrow keys to move the annotation.
|
||||
pdfReader-a11yEditTextAnnotation = To move the end of the text annotation, hold { general-key-shift } and use the left/right arrow keys. To move the start of the annotation, hold { general-key-shift }-{ pdfReader-move-annotation-start-key } and use the arrow keys.
|
||||
pdfReader-a11yResizeAnnotation = To resize the annotation, hold { general-key-shift } and use the arrow keys.
|
||||
pdfReader-a11yMoveAnnotation = Spausdami rodyklių klavišus galite pastumti anotaciją.
|
||||
pdfReader-a11yEditTextAnnotation = Norėdami pakeisti tekstinės anotacijos plotį pagal dešinįjį kraštą (pabaigą), laikykite { general-key-shift } ir spauskite rodyklių klavišus kairėn arba dešinėn. Norėdami pakeisti tekstinės anotacijos plotį pagal kairįjį kraštą (pradžią), laikykite { general-key-shift }-{ pdfReader-move-annotation-start-key } ir spauskite rodyklių klavišus.
|
||||
pdfReader-a11yResizeAnnotation = Norėdami pakeisti tekstinės anotacijos dydį, laikykite { general-key-shift } ir spauskite rodyklių klavišus aukštyn arba žemyn.
|
||||
pdfReader-a11yAnnotationPopupAppeared = Use Tab to navigate the annotation popup.
|
||||
pdfReader-a11yAnnotationCreated = { $type } created.
|
||||
pdfReader-a11yAnnotationSelected = { $type } selected.
|
||||
-pdfReader-a11yTextualAnnotationInstruction = To annotate text via the keyboard, first use “{ pdfReader-find-in-document }” to locate the phrase, and then press { general-key-control }-{ option-or-alt }-{ $number } to turn the search result into an annotation.
|
||||
pdfReader-a11yAnnotationCreated = { $type } sukurtas.
|
||||
pdfReader-a11yAnnotationSelected = { $type } pasirinktas.
|
||||
-pdfReader-a11yTextualAnnotationInstruction = Norėdami sukurti tekstinę anotaciją naudodamiesi klaviatūra, pirmiausia raskite tekstą naudodamiesi „{ pdfReader-find-in-document }“, tuomet spauskite { general-key-control }-{ option-or-alt }-{ $number } pažymėtam tekstui paversti anotacija.
|
||||
-pdfReader-a11yAnnotationInstruction = To add this annotation into the document, focus the document and press { general-key-control }-{ option-or-alt }-{ $number }.
|
||||
pdfReader-toolbar-highlight =
|
||||
.aria-description = { -pdfReader-a11yTextualAnnotationInstruction(number: 1) }
|
||||
|
|
@ -38,15 +38,15 @@ pdfReader-toolbar-area =
|
|||
.aria-description = { -pdfReader-a11yAnnotationInstruction(number: 5) }
|
||||
.title = { pdfReader-selectArea }
|
||||
pdfReader-toolbar-draw =
|
||||
.aria-description = This annotation type cannot be created via the keyboard.
|
||||
.aria-description = Šio anotacijų tipo negalite sukurti naudodamiesi klaviatūra.
|
||||
.title = { pdfReader-draw }
|
||||
pdfReader-findInDocumentInput =
|
||||
.title = Ieškoti
|
||||
.placeholder = { pdfReader-find-in-document }
|
||||
.aria-description = To turn a search result into a highlight annotation, press { general-key-control }-{ option-or-alt }-1. To turn a search result into an underline annotation, press { general-key-control }-{ option-or-alt }-2.
|
||||
pdfReader-import-from-epub =
|
||||
.label = Import Ebook Annotations…
|
||||
pdfReader-import-from-epub-prompt-title = Import Ebook Annotations
|
||||
.label = Importuoti el. knygos anotacijas…
|
||||
pdfReader-import-from-epub-prompt-title = Importuoti el. knygos anotacijas
|
||||
pdfReader-import-from-epub-prompt-text =
|
||||
{ -app-name } found { $count ->
|
||||
[1] { $count } { $tool } annotation
|
||||
|
|
@ -62,4 +62,4 @@ pdfReader-import-from-epub-no-annotations-other-file =
|
|||
“{ $filename }” does not appear to contain any Calibre or KOReader annotations.
|
||||
|
||||
If this ebook has been annotated with KOReader, try selecting a “metadata.epub.lua” file directly.
|
||||
pdfReader-import-from-epub-select-other = Select Other File…
|
||||
pdfReader-import-from-epub-select-other = Pasirinkti kitą rinkmeną…
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<!ENTITY saveCmd.label "Įrašyti...">
|
||||
<!ENTITY saveCmd.key "S">
|
||||
<!ENTITY saveCmd.accesskey "r">
|
||||
<!ENTITY pageSetupCmd.label "Puslapio nuostatos...">
|
||||
<!ENTITY pageSetupCmd.label "Puslapio nuostatos…">
|
||||
<!ENTITY pageSetupCmd.accesskey "n">
|
||||
<!ENTITY printCmd.label "Spausdinti...">
|
||||
<!ENTITY printCmd.key "P">
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
<!ENTITY closeCmd.key "W">
|
||||
<!ENTITY closeCmd.accesskey "U">
|
||||
<!ENTITY manageAttachments.label "Tvarkyti pridėtas rinkmenas">
|
||||
<!ENTITY convertToStored.label "Nuorodas pakeisti tikromis rinkmenomis...">
|
||||
<!ENTITY importCmd.label "Importuoti...">
|
||||
<!ENTITY convertToStored.label "Nuorodas pakeisti tikromis rinkmenomis…">
|
||||
<!ENTITY importCmd.label "Importuoti…">
|
||||
<!ENTITY importCmd.key "I">
|
||||
<!ENTITY importFromClipboardCmd.label "Importuoti iškarpinės turinį">
|
||||
<!ENTITY quitApplicationCmdWin.label "Išeiti">
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
<!ENTITY toolsMenu.label "Priemonės">
|
||||
<!ENTITY toolsMenu.accesskey "P">
|
||||
<!ENTITY installConnector.label "Install Browser Connector…">
|
||||
<!ENTITY installConnector.label "Įdiegti papildinį naršyklei…">
|
||||
<!ENTITY installConnector.accesskey "C">
|
||||
<!ENTITY addons.label "Priedai">
|
||||
<!ENTITY developer.label "Programuotojams">
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
<!ENTITY helpFeedbackPage.label "Diskusijų forumai">
|
||||
<!ENTITY helpFeedbackPage.accesskey "I">
|
||||
|
||||
<!ENTITY helpReportErrors.label "Pranešti apie klaidas...">
|
||||
<!ENTITY helpReportErrors.label "Pranešti apie klaidas…">
|
||||
<!ENTITY helpReportErrors.accesskey "r">
|
||||
|
||||
<!ENTITY debugOutputLogging.label "Derinimo pranešimų išvedimas">
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<!ENTITY debugOutputLogging.submit "Siųsti išvedimą">
|
||||
<!ENTITY debugOutputLogging.view "Rodyti išvedimą">
|
||||
<!ENTITY debugOutputLogging.clear "Išvalyti išvedimą">
|
||||
<!ENTITY debugOutputLogging.restartWithLoggingEnabled "Paleisti iš naujo įgalinus derinimo pranešimus...">
|
||||
<!ENTITY debugOutputLogging.restartWithLoggingEnabled "Paleisti iš naujo įgalinus derinimo pranešimus…">
|
||||
|
||||
<!ENTITY helpCheckForUpdates.label "Tikrinti, ar yra atnaujinimų...">
|
||||
<!ENTITY helpCheckForUpdates.label "Tikrinti, ar yra atnaujinimų…">
|
||||
<!ENTITY helpCheckForUpdates.accesskey "T">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
general-key-control = Control
|
||||
general-key-shift = Shift
|
||||
general-key-control = Vald
|
||||
general-key-shift = Lyg2
|
||||
general-key-alt = Alt
|
||||
general-key-option = Option
|
||||
general-key-option = Parinktis
|
||||
general-key-command = Command
|
||||
option-or-alt =
|
||||
{ PLATFORM() ->
|
||||
|
|
@ -24,7 +24,7 @@ general-help = Žinynas
|
|||
general-tag = Gairė
|
||||
general-done = Atlikta
|
||||
general-view-troubleshooting-instructions = Peržiūrėti nesklandumų sprendimo instrukcijas
|
||||
general-go-back = Go Back
|
||||
general-go-back = Atgal
|
||||
citation-style-label = Citavimo stilius:
|
||||
language-label = Kalba:
|
||||
menu-file-show-in-finder =
|
||||
|
|
@ -66,9 +66,9 @@ menu-deletePermanently =
|
|||
menu-tools-plugins =
|
||||
.label = Papildiniai
|
||||
menu-view-columns-move-left =
|
||||
.label = Move Column Left
|
||||
.label = Perkelti stulpelį kairėn
|
||||
menu-view-columns-move-right =
|
||||
.label = Move Column Right
|
||||
.label = Perkelti stulpelį dešinėn
|
||||
main-window-command =
|
||||
.label = Library
|
||||
main-window-key =
|
||||
|
|
@ -91,9 +91,9 @@ collections-menu-rename-collection =
|
|||
collections-menu-edit-saved-search =
|
||||
.label = Taisyti įsimintąją paiešką
|
||||
collections-menu-move-collection =
|
||||
.label = Move To
|
||||
.label = Perkelti į
|
||||
collections-menu-copy-collection =
|
||||
.label = Copy To
|
||||
.label = Kopijuoti į
|
||||
item-creator-moveDown =
|
||||
.label = Nuleisti žemyn
|
||||
item-creator-moveToTop =
|
||||
|
|
@ -129,7 +129,7 @@ item-menu-add-linked-file =
|
|||
item-menu-add-url =
|
||||
.label = Tinklalapio nuoroda
|
||||
item-menu-change-parent-item =
|
||||
.label = Change Parent Item…
|
||||
.label = Priskirti kitam aukštesniam įrašui…
|
||||
view-online = Atverti tinklalapį
|
||||
item-menu-option-view-online =
|
||||
.label = { view-online }
|
||||
|
|
@ -286,7 +286,7 @@ integration-editBibliography-add-button =
|
|||
integration-editBibliography-remove-button =
|
||||
.aria-label = { general-remove }
|
||||
integration-editBibliography-editor =
|
||||
.aria-label = Edit reference
|
||||
.aria-label = Keisti nuorodą
|
||||
-integration-editBibliography-include-uncited = To include an uncited item in your bibliography, select it from the items list and press { general-add }.
|
||||
-integration-editBibliography-exclude-cited = You can also exclude a cited item by selecting it from the list of references and pressing { general-remove }.
|
||||
-integration-editBibliography-edit-reference = To change how a reference is formatted, use the text editor.
|
||||
|
|
@ -304,7 +304,7 @@ styleEditor-locatorInput = Locator input
|
|||
styleEditor-citationStyle = { citation-style-label }
|
||||
styleEditor-locale = { language-label }
|
||||
styleEditor-editor =
|
||||
.aria-label = Style editor
|
||||
.aria-label = Stilių rengyklė
|
||||
styleEditor-preview =
|
||||
.aria-label = Peržiūra
|
||||
integration-prefs-displayAs-label = Citavimo rodymo pavidalas:
|
||||
|
|
@ -620,9 +620,11 @@ item-pane-message-unselected =
|
|||
}
|
||||
item-pane-message-objects-unselected =
|
||||
{ $count ->
|
||||
[0] No objects in this view
|
||||
[one] { $count } object in this view
|
||||
*[other] { $count } objects in this view
|
||||
[0] Šiame rodinyje objektų nėra
|
||||
[one] { $count } objektas šiame rodinyje
|
||||
[few] { $count } objektai šiame rodinyje
|
||||
[many] { $count } objektų šiame rodinyje
|
||||
*[other] { $count } objektas šiame rodinyje
|
||||
}
|
||||
item-pane-duplicates-merge-items =
|
||||
.label =
|
||||
|
|
@ -696,9 +698,9 @@ post-upgrade-done =
|
|||
.label = { general-done }
|
||||
text-action-paste-and-search =
|
||||
.label = Įdėti ir ieškoti
|
||||
mac-word-plugin-install-message = Zotero needs access to Word data to install the Word plugin.
|
||||
mac-word-plugin-install-message = Zotero turi galėti prieiti prie „Word“ duomenų tam, kad galėtų įdiegti papildinį „Word“ programai.
|
||||
mac-word-plugin-install-action-button =
|
||||
.label = Install Word plugin
|
||||
.label = Įdiegti papildinį „Word“ programai
|
||||
mac-word-plugin-install-remind-later-button =
|
||||
.label = { general-remind-me-later }
|
||||
mac-word-plugin-install-dont-ask-again-button =
|
||||
|
|
|
|||
|
|
@ -1358,7 +1358,7 @@ noteEditor.deleteTable=Ištrinti lentelę
|
|||
pdfReader.annotations=Anotacijos
|
||||
pdfReader.showAnnotations=Rodyti anotacijas
|
||||
pdfReader.searchAnnotations=Ieškoti anotacijų
|
||||
pdfReader.searchOutline=Search Outline
|
||||
pdfReader.searchOutline=Ieškoti žymelės
|
||||
pdfReader.noAnnotations=Sukurkite anotaciją ir ją galėsite matyti šoniniame skydelyje.
|
||||
pdfReader.noExtractedText=Tekstas neišgautas
|
||||
pdfReader.addComment=Pridėti komentarą
|
||||
|
|
@ -1448,8 +1448,8 @@ pdfReader.epubAppearance.pageWidth.revert=Naudoti numatytąjį puslapio plotį
|
|||
pdfReader.convertToHighlight=Paversti paryškinimu
|
||||
pdfReader.convertToUnderline=Paversti pabraukimu
|
||||
pdfReader.size=Dydis
|
||||
pdfReader.merge=Merge
|
||||
pdfReader.copyLink=Copy Link
|
||||
pdfReader.merge=Apjungti
|
||||
pdfReader.copyLink=Kopijuoti nuorodą
|
||||
|
||||
spellCheck.checkSpelling=Rašybos tikrinimas
|
||||
spellCheck.addRemoveDictionaries=Pridėti / šalinti žodynus...
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
<!ENTITY zotero.preferences.default "Standard:">
|
||||
<!ENTITY zotero.preferences.items "items">
|
||||
<!ENTITY zotero.preferences.period ".">
|
||||
<!ENTITY zotero.preferences.custom "Custom…">
|
||||
<!ENTITY zotero.preferences.custom "Eigendefinert…">
|
||||
|
||||
<!ENTITY zotero.preferences.prefpane.general "Generelt">
|
||||
|
||||
<!ENTITY zotero.preferences.interface "Interface">
|
||||
<!ENTITY zotero.preferences.interface "Grensesnitt">
|
||||
<!ENTITY zotero.preferences.fileHandling "File Handling">
|
||||
<!ENTITY zotero.preferences.automaticSnapshots "Ta snapshot automatisk når du genererer element frå nettsider">
|
||||
<!ENTITY zotero.preferences.downloadAssociatedFiles "Lagra tilknytte PDF-filer og andre filer ved lagring av element">
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<!ENTITY zotero.preferences.reader.tabsTitle.label "Show tabs as">
|
||||
<!ENTITY zotero.preferences.reader.tabsTitle.creatorYearTitle "Creator - Year - Title">
|
||||
<!ENTITY zotero.preferences.reader.tabsTitle.titleCreatorYear "Title - Creator - Year">
|
||||
<!ENTITY zotero.preferences.reader.tabsTitle.filename "Filename">
|
||||
<!ENTITY zotero.preferences.reader.tabsTitle.filename "Filnamn">
|
||||
|
||||
<!ENTITY zotero.preferences.groups "Grupper">
|
||||
<!ENTITY zotero.preferences.groups.whenCopyingInclude "Ta med når element vert kopierte mellom bibliotek:">
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
<!ENTITY zotero.preferences.sync.reset.resetFileSyncHistory.desc "Compare all attachment files with the storage service">
|
||||
<!ENTITY zotero.preferences.sync.reset "Nullstill">
|
||||
<!ENTITY zotero.preferences.sync.reset.showResetOptions "Show Reset Options…">
|
||||
<!ENTITY zotero.preferences.sync.reset.button "Reset…">
|
||||
<!ENTITY zotero.preferences.sync.reset.button "Nullstill…">
|
||||
|
||||
|
||||
<!ENTITY zotero.preferences.prefpane.search "Søk">
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
<!ENTITY zotero.preferences.quickCopy.copyAsHTML "Kopier som HTML">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.setings "Innstillingar">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.domainPath "Domene/stig">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.domainPath.example "(e.g., wikipedia.org)">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.domainPath.example "(t.d. wikipedia.org)">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.format "Format">
|
||||
<!ENTITY zotero.preferences.quickCopy.siteEditor.locale "Språk">
|
||||
<!ENTITY zotero.preferences.quickCopy.dragLimit "Slå av rask kopiering når du drar fleire enn">
|
||||
|
|
@ -126,7 +126,7 @@
|
|||
<!ENTITY zotero.preferences.cite.styles.styleManager.title "Tittel">
|
||||
<!ENTITY zotero.preferences.cite.styles.styleManager.updated "Oppdatert">
|
||||
<!ENTITY zotero.preferences.cite.styles.styleManager.csl "CSL">
|
||||
<!ENTITY zotero.preferences.export.getAdditionalStyles "Get additional styles…">
|
||||
<!ENTITY zotero.preferences.export.getAdditionalStyles "Hent fleire stilar…">
|
||||
|
||||
<!ENTITY zotero.preferences.keys.saveToZotero "Save to Zotero (address bar icon)">
|
||||
<!ENTITY zotero.preferences.keys.focusLibrariesPane "Focus Libraries Pane">
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ new-collection-dialog =
|
|||
new-collection-name = Namn:
|
||||
new-collection-create-in = Create in:
|
||||
attachment-info-title = Tittel
|
||||
attachment-info-filename = Filename
|
||||
attachment-info-filename = Filnamn
|
||||
attachment-info-accessed = Lest
|
||||
attachment-info-pages = Sidetal
|
||||
attachment-info-modified = Sist endra
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ pane.item.attachments.hasLink=Has link
|
|||
pane.item.attachments.select=Vel ei fil
|
||||
pane.item.attachments.PDF.installTools.title=PDF Tools Not Installed
|
||||
pane.item.attachments.PDF.installTools.text=To use this feature, you must first install the PDF tools in the Search pane of the Zotero preferences.
|
||||
pane.item.attachments.filename=Filename
|
||||
pane.item.attachments.filename=Filnamn
|
||||
pane.item.noteEditor.clickHere=Klikk her
|
||||
pane.item.tags.icon.user=Brukerdefinert tagg:
|
||||
pane.item.tags.icon.automatic=Automatisk tagg:
|
||||
|
|
@ -1344,7 +1344,7 @@ noteEditor.removeCitations=Hide Annotation Citations
|
|||
noteEditor.image=Bilete
|
||||
noteEditor.math=Math
|
||||
noteEditor.table=Table
|
||||
noteEditor.citation=Citation
|
||||
noteEditor.citation=Sitering
|
||||
noteEditor.copyImage=Copy Image
|
||||
noteEditor.saveImageAs=Save Image As…
|
||||
noteEditor.insertRowBefore=Insert Row Above
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ preferences-advanced-enable-bidi-ui =
|
|||
preferences-advanced-reset-data-dir =
|
||||
.label = Revert to Default Location…
|
||||
preferences-advanced-custom-data-dir =
|
||||
.label = Use Custom Location…
|
||||
.label = 使用自定义位置
|
||||
preferences-advanced-default-data-dir =
|
||||
.value = (Default: { $directory })
|
||||
.aria-label = Default location
|
||||
.aria-label = 默认位置
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ pdfReader-findInDocumentInput =
|
|||
.placeholder = { pdfReader-find-in-document }
|
||||
.aria-description = 要将搜索结果转换为高亮注释,请按 { general-key-control }-{ option-or-alt }-1。要将搜索结果转换为下划线注释,请按 { general-key-control }-{ option-or-alt }-2。
|
||||
pdfReader-import-from-epub =
|
||||
.label = Import Ebook Annotations…
|
||||
pdfReader-import-from-epub-prompt-title = Import Ebook Annotations
|
||||
.label = 导入电子书注释…
|
||||
pdfReader-import-from-epub-prompt-title = 导入电子书注释
|
||||
pdfReader-import-from-epub-prompt-text =
|
||||
{ -app-name } found { $count ->
|
||||
[1] { $count } { $tool } annotation
|
||||
|
|
@ -62,4 +62,4 @@ pdfReader-import-from-epub-no-annotations-other-file =
|
|||
“{ $filename }” does not appear to contain any Calibre or KOReader annotations.
|
||||
|
||||
If this ebook has been annotated with KOReader, try selecting a “metadata.epub.lua” file directly.
|
||||
pdfReader-import-from-epub-select-other = Select Other File…
|
||||
pdfReader-import-from-epub-select-other = 选择其他文件…
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
<!ENTITY toolsMenu.label "工具">
|
||||
<!ENTITY toolsMenu.accesskey "T">
|
||||
<!ENTITY installConnector.label "Install Browser Connector…">
|
||||
<!ENTITY installConnector.label "安装浏览器扩展…">
|
||||
<!ENTITY installConnector.accesskey "C">
|
||||
<!ENTITY addons.label "插件">
|
||||
<!ENTITY developer.label "开发者">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ general-help = 帮助
|
|||
general-tag = 标签
|
||||
general-done = 完成
|
||||
general-view-troubleshooting-instructions = 查看故障排除说明
|
||||
general-go-back = Go Back
|
||||
general-go-back = 返回
|
||||
citation-style-label = 参考文献样式:
|
||||
language-label = 语言:
|
||||
menu-file-show-in-finder =
|
||||
|
|
@ -66,9 +66,9 @@ menu-deletePermanently =
|
|||
menu-tools-plugins =
|
||||
.label = 插件
|
||||
menu-view-columns-move-left =
|
||||
.label = Move Column Left
|
||||
.label = 将列移至左侧
|
||||
menu-view-columns-move-right =
|
||||
.label = Move Column Right
|
||||
.label = 将列移至右侧
|
||||
main-window-command =
|
||||
.label = Library
|
||||
main-window-key =
|
||||
|
|
@ -283,7 +283,7 @@ integration-editBibliography-add-button =
|
|||
integration-editBibliography-remove-button =
|
||||
.aria-label = { general-remove }
|
||||
integration-editBibliography-editor =
|
||||
.aria-label = Edit reference
|
||||
.aria-label = 编辑参考文献表
|
||||
-integration-editBibliography-include-uncited = To include an uncited item in your bibliography, select it from the items list and press { general-add }.
|
||||
-integration-editBibliography-exclude-cited = You can also exclude a cited item by selecting it from the list of references and pressing { general-remove }.
|
||||
-integration-editBibliography-edit-reference = To change how a reference is formatted, use the text editor.
|
||||
|
|
|
|||
|
|
@ -1358,7 +1358,7 @@ noteEditor.deleteTable=删除表格
|
|||
pdfReader.annotations=注释
|
||||
pdfReader.showAnnotations=显示注释
|
||||
pdfReader.searchAnnotations=搜索注释
|
||||
pdfReader.searchOutline=Search Outline
|
||||
pdfReader.searchOutline=搜索大纲
|
||||
pdfReader.noAnnotations=创建注释使其在侧栏可见
|
||||
pdfReader.noExtractedText=无提取的文本
|
||||
pdfReader.addComment=添加评论
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 0f4f218980ceed99639ae887b084c9b059ebd4d0
|
||||
Subproject commit cd609cafd3049f4d922c891641ffc7a76eb8ccdf
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 64c6acb0c06ed9f1d7f090052132292378c5cfe5
|
||||
Subproject commit f9857be7000bdd90997e7a28abad2383f8d2f88b
|
||||
2
reader
2
reader
|
|
@ -1 +1 @@
|
|||
Subproject commit 533ccd38ca0f2f5ab3c5be277516a4dba581629c
|
||||
Subproject commit c36c76d184c8b581dbfdfd5169a462ee9019baf2
|
||||
|
|
@ -1 +1 @@
|
|||
2024-11-26 07:33:38
|
||||
2025-02-19 07:01:30
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ async function waitNoLongerThan(promise, ms = 1000) {
|
|||
|
||||
async function waitForScrollToPane(itemDetails, paneID) {
|
||||
await itemDetails._renderPromise;
|
||||
itemDetails.scrollToPane(paneID, "instant");
|
||||
await itemDetails.scrollToPane(paneID, "instant");
|
||||
// Wait for some frames or up to 150ms to ensure the pane is visible
|
||||
await waitForFrames(5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -691,6 +691,23 @@ describe("Zotero.CollectionTree", function() {
|
|||
assert.equal(cv.selection.focused, 0);
|
||||
assert.sameMembers(zp.itemsView.getSelectedItems(true), [item1.id, item2.id]);
|
||||
});
|
||||
|
||||
it("should switch to trash if item is in trash", async function () {
|
||||
var item = await createDataObject('item', { deleted: true });
|
||||
await cv.selectItems([item.id]);
|
||||
await waitForItemsLoad(win);
|
||||
assert.isTrue(zp.getCollectionTreeRow().isTrash());
|
||||
assert.sameMembers(zp.itemsView.getSelectedItems(true), [item.id]);
|
||||
});
|
||||
|
||||
it("should switch to trash if parent of child note is in trash", async function () {
|
||||
var item = await createDataObject('item', { deleted: true });
|
||||
var note = await createDataObject('item', { itemType: 'note', parentItemID: item.id });
|
||||
await cv.selectItems([note.id]);
|
||||
await waitForItemsLoad(win);
|
||||
assert.isTrue(zp.getCollectionTreeRow().isTrash());
|
||||
assert.sameMembers(zp.itemsView.getSelectedItems(true), [note.id]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onDrop()", function () {
|
||||
|
|
|
|||
|
|
@ -2,25 +2,43 @@
|
|||
{
|
||||
"id": "339d0202-d99f-48a2-aa0d-9b0c5631af26",
|
||||
"type": "highlight",
|
||||
"color":
|
||||
{
|
||||
"color": {
|
||||
"r": 255,
|
||||
"g": 245,
|
||||
"b": 173
|
||||
},
|
||||
"profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
|
||||
"positions":
|
||||
[
|
||||
"positions": [
|
||||
{
|
||||
"top_left":
|
||||
{
|
||||
"x": 429.74257247464135,
|
||||
"y": 770.1238821199208
|
||||
"top_left": {
|
||||
"x": 191.5049,
|
||||
"y": 588.5418
|
||||
},
|
||||
"bottom_right":
|
||||
{
|
||||
"x": 572.5401236420382,
|
||||
"y": 680.1292839565452
|
||||
"bottom_right": {
|
||||
"x": 461.6047999999994,
|
||||
"y": 576.1542
|
||||
},
|
||||
"page": 1
|
||||
},
|
||||
{
|
||||
"top_left": {
|
||||
"x": 146.3,
|
||||
"y": 576.8651
|
||||
},
|
||||
"bottom_right": {
|
||||
"x": 461.3467999999992,
|
||||
"y": 564.6542
|
||||
},
|
||||
"page": 1
|
||||
},
|
||||
{
|
||||
"top_left": {
|
||||
"x": 146.3,
|
||||
"y": 565.3651
|
||||
},
|
||||
"bottom_right": {
|
||||
"x": 220.06760000000003,
|
||||
"y": 553.1542
|
||||
},
|
||||
"page": 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
],
|
||||
"created": "2024-11-04T13:49:03.274Z",
|
||||
"modified": "2024-11-04T13:49:03.274Z",
|
||||
"id": "https://api.mendeley.com/annotations/v2/84f12446-3b49-4052-bbdc-832d28e1e072",
|
||||
"id": "https://api.mendeley.com/annotations/v2/339d0202-d99f-48a2-aa0d-9b0c5631af26",
|
||||
"body": [
|
||||
{
|
||||
"purpose": "highlighting",
|
||||
|
|
|
|||
8
test/tests/data/mendeleyMock/notebooks.json
Normal file
8
test/tests/data/mendeleyMock/notebooks.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"id": "8041a43b-f740-41ec-be42-a24cc5106d68",
|
||||
"created": "2024-07-09T09:56:04.349Z",
|
||||
"modified": "2024-12-06T17:41:37.200Z",
|
||||
"title": "TEST"
|
||||
}
|
||||
]
|
||||
|
|
@ -269,7 +269,7 @@ describe("Item pane", function () {
|
|||
|
||||
var menupopup = itemBox.querySelector('#zotero-creator-transform-menu');
|
||||
// Fake a right-click
|
||||
doc.popupNode = parent;
|
||||
itemBox._popupNode = parent;
|
||||
menupopup.openPopup(
|
||||
parent, "after_start", 0, 0, true, false, new MouseEvent('click', { button: 2 })
|
||||
);
|
||||
|
|
@ -298,7 +298,7 @@ describe("Item pane", function () {
|
|||
var firstlast = label.closest('.creator-type-value');
|
||||
var menupopup = itemBox.querySelector('#zotero-creator-transform-menu');
|
||||
// Fake a right-click
|
||||
doc.popupNode = firstlast;
|
||||
itemBox._popupNode = firstlast;
|
||||
menupopup.openPopup(
|
||||
firstlast, "after_start", 0, 0, true, false, new MouseEvent('click', { button: 2 })
|
||||
);
|
||||
|
|
@ -1795,4 +1795,47 @@ describe("Item pane", function () {
|
|||
assert.isTrue(ZoteroContextPane.splitter.hidden);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Libraries pane", function () {
|
||||
let paneID = "libraries-collections";
|
||||
|
||||
beforeEach(function () {
|
||||
Zotero.Prefs.set("panes.libraries-collections.open", true);
|
||||
Zotero_Tabs.select("zotero-pane");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Zotero_Tabs.select("zotero-pane");
|
||||
Zotero_Tabs.closeAll();
|
||||
});
|
||||
|
||||
it("should scroll to pinned pane after selection moves from note to item", async function () {
|
||||
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||
let pane = itemDetails.getPane(paneID);
|
||||
|
||||
let item = await createDataObject('item');
|
||||
await importPDFAttachment(item);
|
||||
|
||||
let note = await createDataObject('item', { itemType: 'note' });
|
||||
|
||||
await select(win, item);
|
||||
await waitForScrollToPane(itemDetails, paneID);
|
||||
|
||||
itemDetails.pinnedPane = paneID;
|
||||
|
||||
await select(win, note);
|
||||
|
||||
await Zotero.Promise.delay(10);
|
||||
|
||||
await select(win, item);
|
||||
await waitForScrollToPane(itemDetails, paneID);
|
||||
|
||||
// Should scroll to pinned pane
|
||||
assert.isTrue(
|
||||
Math.abs(pane.getBoundingClientRect().top - pane.parentElement.getBoundingClientRect().top) < 3
|
||||
);
|
||||
|
||||
itemDetails.pinnedPane = "";
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2731,7 +2731,7 @@ describe("Zotero.Item", function () {
|
|||
});
|
||||
|
||||
describe("not-strict mode", function () {
|
||||
it("should handle Extra in non-strict mode", function () {
|
||||
it("should preserve Extra", function () {
|
||||
var json = {
|
||||
itemType: "journalArticle",
|
||||
title: "Test",
|
||||
|
|
@ -2834,6 +2834,45 @@ describe("Zotero.Item", function () {
|
|||
assert.equal(item.getField('extra'), '');
|
||||
});
|
||||
|
||||
it("should use a Zotero item type stored in Extra", async function () {
|
||||
var json = {
|
||||
itemType: "document",
|
||||
title: "Test",
|
||||
extra: "Type: dataset\nCitation Key: abc123\nFoo: Bar"
|
||||
};
|
||||
var item = new Zotero.Item;
|
||||
item.fromJSON(json);
|
||||
assert.equal(Zotero.ItemTypes.getName(item.itemTypeID), 'dataset');
|
||||
// Move a valid field out of Extra
|
||||
assert.equal(item.getField('citationKey'), 'abc123');
|
||||
assert.equal(item.getField('extra'), 'Foo: Bar');
|
||||
});
|
||||
|
||||
it("should use a CSL item type stored in Extra", async function () {
|
||||
var json = {
|
||||
itemType: "journalArticle",
|
||||
title: "Test",
|
||||
extra: "Type: song"
|
||||
};
|
||||
var item = new Zotero.Item;
|
||||
item.fromJSON(json);
|
||||
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('audioRecording'));
|
||||
});
|
||||
|
||||
it("should move a now-invalid existing field to Extra when using Type from Extra", async function () {
|
||||
var json = {
|
||||
itemType: "journalArticle",
|
||||
title: "Test",
|
||||
pages: "123",
|
||||
extra: "Type: audioRecording"
|
||||
};
|
||||
var item = new Zotero.Item;
|
||||
item.fromJSON(json);
|
||||
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('audioRecording'));
|
||||
// 'pages' should've been moved to Extra, since it's not valid for the new type
|
||||
assert.equal(item.getField('extra'), 'Pages: 123');
|
||||
});
|
||||
|
||||
it("should ignore some redundant fields from RDF translator (temporary)", function () {
|
||||
var json = {
|
||||
itemType: "book",
|
||||
|
|
@ -2941,6 +2980,28 @@ describe("Zotero.Item", function () {
|
|||
};
|
||||
assert.throws(f, /^Invalid creator type/);
|
||||
});
|
||||
|
||||
it("should ignore item type in Extra", function () {
|
||||
var json = {
|
||||
itemType: "document",
|
||||
title: "",
|
||||
extra: "Type: preprint"
|
||||
};
|
||||
var item = new Zotero.Item;
|
||||
item.fromJSON(json, { strict: true });
|
||||
assert.equal(item.getField('extra'), "Type: preprint");
|
||||
});
|
||||
|
||||
it("should ignore valid field in Extra", function () {
|
||||
var json = {
|
||||
itemType: "journalArticle",
|
||||
title: "",
|
||||
extra: "DOI: 10.1234/abcd"
|
||||
};
|
||||
var item = new Zotero.Item;
|
||||
item.fromJSON(json, { strict: true });
|
||||
assert.equal(item.getField('extra'), "DOI: 10.1234/abcd");
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept ISO 8601 dates", function* () {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,18 @@
|
|||
describe('Zotero_Import_Mendeley', function () {
|
||||
var server, httpd, httpdURL, importers;
|
||||
|
||||
const getImporter = () => {
|
||||
const getImporter = ({ importNotes = false } = {}) => {
|
||||
const importer = new Zotero_Import_Mendeley();
|
||||
importer.mendeleyAuth = { kind: 'direct', tokens: { accessToken: 'access_token', refreshToken: 'refresh_token' } };
|
||||
importer.skipNotebooks = true;
|
||||
importer.skipNotebooks = !importNotes;
|
||||
if (importNotes) {
|
||||
importer._refManagerToken = {
|
||||
kind: 'referenceManager',
|
||||
accessToken: 'refman_token',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
};
|
||||
}
|
||||
importers.push(importer);
|
||||
return importer;
|
||||
};
|
||||
|
|
@ -14,7 +22,7 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
before(async () => {
|
||||
Components.utils.import('chrome://zotero/content/import/mendeley/mendeleyImport.js');
|
||||
|
||||
// real http server is used to deliver an empty pdf so that annotations can be processed during import
|
||||
// A real HTTP server is used to deliver a Bitcoin PDF so that annotations can be processed during import.
|
||||
Components.utils.import("resource://zotero-unit/httpd.js");
|
||||
const port = 16213;
|
||||
httpd = new HttpServer();
|
||||
|
|
@ -22,7 +30,7 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
httpd.start(port);
|
||||
httpd.registerFile(
|
||||
'/file1.pdf',
|
||||
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'empty.pdf'))
|
||||
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'recognizePDF_test_title.pdf'))
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -84,7 +92,7 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
|
||||
setHTTPResponse(server, 'https://api.mendeley.com/', {
|
||||
method: 'GET',
|
||||
url: `profiles/v2/me?`,
|
||||
url: `profiles/v2/me`,
|
||||
status: 200,
|
||||
headers: {},
|
||||
json: JSON.parse(
|
||||
|
|
@ -104,7 +112,7 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
|
||||
setHTTPResponse(server, 'https://api.mendeley.com/', {
|
||||
method: 'GET',
|
||||
url: `files/19fb5e5b-1a39-4851-b513-d48441a670e1?`,
|
||||
url: `files/19fb5e5b-1a39-4851-b513-d48441a670e1`,
|
||||
status: 200, // ideally would be 303 but mock http doesn't like it
|
||||
headers: {
|
||||
Location: `${httpdURL}/file1.pdf`
|
||||
|
|
@ -526,7 +534,7 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
annotation.annotationText = 'Highlight text';
|
||||
annotation.annotationComment = 'Highlight comment';
|
||||
annotation.annotationPageLabel = '57';
|
||||
annotation.addRelation('mendeleyDB:annotationUUID', '84f12446-3b49-4052-bbdc-832d28e1e072');
|
||||
annotation.addRelation('mendeleyDB:annotationUUID', '339d0202-d99f-48a2-aa0d-9b0c5631af26');
|
||||
await annotation.saveTx();
|
||||
|
||||
let mendeleyNotebook = JSON.parse(
|
||||
|
|
@ -544,5 +552,85 @@ describe('Zotero_Import_Mendeley', function () {
|
|||
await attachment.eraseTx();
|
||||
await item.eraseTx();
|
||||
});
|
||||
|
||||
it('should import a notebook from Mendeley', async () => {
|
||||
const importer = getImporter({ importNotes: true });
|
||||
|
||||
setHTTPResponse(server, 'https://api.mendeley.com/', {
|
||||
method: 'GET',
|
||||
url: `notes/v1?limit=500`,
|
||||
status: 200,
|
||||
headers: {},
|
||||
json: JSON.parse(
|
||||
await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/notebooks.json')
|
||||
)
|
||||
});
|
||||
|
||||
setHTTPResponse(server, 'https://api.mendeley.com/', {
|
||||
method: 'GET',
|
||||
url: 'notes/v1/8041a43b-f740-41ec-be42-a24cc5106d68',
|
||||
status: 200,
|
||||
headers: {},
|
||||
json: JSON.parse(
|
||||
await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/notebook.json')
|
||||
)
|
||||
});
|
||||
|
||||
await importer.translate({
|
||||
libraryID: Zotero.Libraries.userLibraryID,
|
||||
collections: null,
|
||||
linkFiles: false,
|
||||
});
|
||||
|
||||
const note = (await Zotero.Relations
|
||||
.getByPredicateAndObject('item', 'mendeleyDB:notebookUUID', '8041a43b-f740-41ec-be42-a24cc5106d68'))
|
||||
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
|
||||
.shift();
|
||||
|
||||
assert.match(
|
||||
note.note,
|
||||
/^<h1>TEST<\/h1>\n<p><span class="highlight" data-annotation="((?:(?:%[0-9A-F]{2}|[^<>'" %])+))">“A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution”<\/span> <span class="citation" data-citation="((?:(?:%[0-9A-F]{2}|[^<>'" %])+))">\(<span class="citation-item">“Item with PDF”, 2005, p. 1<\/span>\)<\/span><\/p>\n<p>Lorem Ipsum<\/p>$/i
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle incorrect annotations', async () => {
|
||||
let annotationsResponse = JSON.parse(
|
||||
await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/annotations.json')
|
||||
);
|
||||
delete annotationsResponse.find(a => a.id == '885615a7-170e-4613-af80-0227ea76ae55').type;
|
||||
delete annotationsResponse.find(a => a.id == 'bfbdb972-171d-4b21-8ae6-f156ac9a2b41').color;
|
||||
annotationsResponse.push({ type: 'note', id: 'invalid' });
|
||||
setHTTPResponse(server, 'https://api.mendeley.com/', {
|
||||
method: 'GET',
|
||||
url: `annotations?limit=200`,
|
||||
status: 200,
|
||||
headers: {},
|
||||
json: annotationsResponse
|
||||
});
|
||||
|
||||
const importer = getImporter();
|
||||
await importer.translate({
|
||||
libraryID: Zotero.Libraries.userLibraryID,
|
||||
collections: null,
|
||||
linkFiles: false,
|
||||
});
|
||||
|
||||
const pdf = (await Zotero.Relations
|
||||
.getByPredicateAndObject('item', 'mendeleyDB:fileHash', 'cc22c6611277df346ff8dc7386ba3880b2bafa15'))
|
||||
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
|
||||
.shift();
|
||||
|
||||
const annotations = await pdf.getAnnotations();
|
||||
// Typeless notes and invalid annotations should be discarded.
|
||||
assert.lengthOf(annotations, 5);
|
||||
assert.lengthOf(await Zotero.Relations.getByPredicateAndObject('item', 'mendeleyDB:annotationUUID', '885615a7-170e-4613-af80-0227ea76ae55'), 0);
|
||||
assert.lengthOf(await Zotero.Relations.getByPredicateAndObject('item', 'mendeleyDB:annotationUUID', 'invalid'), 0);
|
||||
// Colorless note annotations should have a default colour.
|
||||
const colorlessNote = (await Zotero.Relations
|
||||
.getByPredicateAndObject('item', 'mendeleyDB:annotationUUID', 'bfbdb972-171d-4b21-8ae6-f156ac9a2b41'))
|
||||
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
|
||||
.shift();
|
||||
assert.equal(colorlessNote.annotationColor, '#ffd400');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -718,6 +718,33 @@ describe("Zotero.Sync.Data.Local", function() {
|
|||
}
|
||||
});
|
||||
|
||||
it("should handle local item becoming child and remote being added to a collection", async function () {
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
|
||||
var collection = await createDataObject('collection');
|
||||
var parentItem = await createDataObject('item');
|
||||
var childItem = await createDataObject('item', { itemType: 'note' });
|
||||
var data = childItem.toJSON();
|
||||
await Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [data]);
|
||||
|
||||
// Add to parent locally
|
||||
childItem.parentID = parentItem.id;
|
||||
await childItem.saveTx();
|
||||
|
||||
// Add to collection remotely
|
||||
data.collections = [collection.key];
|
||||
var json = {
|
||||
key: childItem.key,
|
||||
version: 10,
|
||||
data
|
||||
};
|
||||
await Zotero.Sync.Data.Local.processObjectsFromJSON(
|
||||
'item', libraryID, [json], { stopOnError: true }
|
||||
);
|
||||
|
||||
assert.isTrue(collection.hasItem(parentItem));
|
||||
});
|
||||
|
||||
it("should automatically resolve collection name conflict", async function () {
|
||||
var libraryID = Zotero.Libraries.userLibraryID;
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,31 @@ describe("Item Tags Box", function () {
|
|||
// New empty tag should have focus
|
||||
assert.exists(doc.activeElement.closest("[isNew]"));
|
||||
});
|
||||
|
||||
it("should save tag edits when another item is selected", async function () {
|
||||
let notSelectedItem = await createDataObject('item');
|
||||
var tag = Zotero.Utilities.randomString();
|
||||
var updatedTag = Zotero.Utilities.randomString();
|
||||
|
||||
let selectedItem = await createDataObject('item', { tags: [{ tag }] });
|
||||
var tagsbox = doc.querySelector('#zotero-editpane-tags');
|
||||
var rows = tagsbox.querySelectorAll('.row editable-text');
|
||||
assert.equal(rows.length, 1);
|
||||
|
||||
// type something
|
||||
var firstRow = rows[0];
|
||||
firstRow.focus();
|
||||
firstRow.ref.value = updatedTag;
|
||||
firstRow.ref.dispatchEvent(new Event('input'));
|
||||
|
||||
// change the item by clicking on another row in itemTree
|
||||
let promise = waitForItemEvent('modify');
|
||||
win.ZoteroPane.selectItem(notSelectedItem.id);
|
||||
// selectedItem should be modified
|
||||
await promise;
|
||||
// make sure that the tag was actually updated
|
||||
assert.equal(selectedItem.getTags()[0].tag, updatedTag);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -738,4 +738,42 @@ describe("Zotero.Utilities.Internal", function () {
|
|||
assert.equal(html, 'yes1yes2 yes3');
|
||||
});
|
||||
});
|
||||
|
||||
describe("OpenURL", function () {
|
||||
var item;
|
||||
|
||||
before(async function () {
|
||||
item = await createDataObject('item', { title: 'Foo Bar', date: '2024-12-19' });
|
||||
})
|
||||
|
||||
after(function () {
|
||||
Zotero.Prefs.clear('openURL.resolver');
|
||||
});
|
||||
|
||||
describe("#resolve()", function () {
|
||||
it("should add trailing '?' if no query string", async function () {
|
||||
Zotero.Prefs.set("openURL.resolver", "https://resolver.ebsco.com/c/abcdef/result");
|
||||
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
|
||||
assert.include(url, 'result?url_ver=Z39.88-2004');
|
||||
});
|
||||
|
||||
it("should add trailing '&' if already a query string", async function () {
|
||||
Zotero.Prefs.set("openURL.resolver", "https://resolver.ebscohost.com/openurl?custid=abcdef&groupid=main&profile=ftf&authtype=ip,uid");
|
||||
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
|
||||
assert.include(url, 'authtype=ip,uid&url_ver=Z39.88-2004');
|
||||
});
|
||||
|
||||
it("should add trailing '?' after /login?url=", async function () {
|
||||
Zotero.Prefs.set("openURL.resolver", "https://proxy.school.edu/login?url=https://resolver.ebscohost.com/openurl");
|
||||
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
|
||||
assert.include(url, 'openurl?url_ver=Z39.88-2004');
|
||||
});
|
||||
|
||||
it("shouldn't add trailing '?' after /login?url= if URL already ends in '?'", async function () {
|
||||
Zotero.Prefs.set("openURL.resolver", "https://proxy.school.edu/login?url=https://resolver.ebscohost.com/openurl?");
|
||||
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
|
||||
assert.include(url, 'openurl?url_ver=Z39.88-2004');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -768,6 +768,50 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
|
|||
win.close();
|
||||
});
|
||||
|
||||
it("should show an error for a non-DAV URL", async function () {
|
||||
Zotero.HTTP.mock = null;
|
||||
Zotero.Prefs.set("sync.storage.url", davHostPath);
|
||||
|
||||
httpd.registerPathHandler(
|
||||
`${davBasePath}zotero/`,
|
||||
{
|
||||
handle: function (request, response) {
|
||||
// Force Basic Auth
|
||||
if (!request.hasHeader('Authorization')) {
|
||||
response.setStatusLine(null, 401, null);
|
||||
response.setHeader('WWW-Authenticate', 'Basic realm="WebDAV"', false);
|
||||
return;
|
||||
}
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write("<html><body><p>This is a non-DAV URL.</p></body></html>");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Begin install procedure
|
||||
var win = await loadPrefPane('sync');
|
||||
var button = win.document.getElementById('storage-verify');
|
||||
|
||||
var spy = sinon.spy(win.Zotero_Preferences.Sync, "verifyStorageServer");
|
||||
var promise1 = waitForDialog(function (dialog) {
|
||||
assert.include(
|
||||
dialog.document.documentElement.textContent,
|
||||
Zotero.getString(
|
||||
'sync.storage.error.webdav.invalidURL',
|
||||
davScheme + '://' + davHostPath + 'zotero/'
|
||||
)
|
||||
);
|
||||
});
|
||||
button.click();
|
||||
await promise1;
|
||||
|
||||
var promise2 = spy.returnValues[0];
|
||||
spy.restore();
|
||||
await promise2;
|
||||
|
||||
win.close();
|
||||
});
|
||||
|
||||
it("should show an error for a 403", function* () {
|
||||
Zotero.HTTP.mock = null;
|
||||
httpd.registerPathHandler(
|
||||
|
|
|
|||
|
|
@ -1692,6 +1692,38 @@ describe("ZoteroPane", function() {
|
|||
assert.equal(parentItem.getAttachments().length, 4);
|
||||
assert.equal(epubAttachment.getField('title'), Zotero.getString('file-type-ebook'));
|
||||
});
|
||||
|
||||
describe("Linked file renaming", function () {
|
||||
before(() => {
|
||||
Zotero.Prefs.set('autoRenameFiles.linked', true);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
Zotero.Prefs.clear('autoRenameFiles.linked');
|
||||
});
|
||||
|
||||
it("should only rename and change the title of the first PDF attachment", async function () {
|
||||
let testFile = getTestDataDirectory();
|
||||
testFile.append('test.pdf');
|
||||
|
||||
let tempDir = await getTempDirectory();
|
||||
let copy1 = PathUtils.join(tempDir, 'copy1.pdf');
|
||||
let copy2 = PathUtils.join(tempDir, 'copy2.pdf');
|
||||
|
||||
await IOUtils.copy(testFile.path, copy1);
|
||||
await IOUtils.copy(testFile.path, copy2);
|
||||
|
||||
let parentItem = await createDataObject('item', { title: 'Foo' });
|
||||
|
||||
let [attachment1] = await zp.addAttachmentFromDialog(false, parentItem.id, [copy1]);
|
||||
assert.equal(attachment1.getField('title'), Zotero.getString('file-type-pdf'));
|
||||
assert.equal(attachment1.attachmentFilename, 'Foo.pdf');
|
||||
|
||||
let [attachment2] = await zp.addAttachmentFromDialog(false, parentItem.id, [copy2]);
|
||||
assert.equal(attachment2.getField('title'), 'copy2');
|
||||
assert.equal(attachment2.attachmentFilename, 'copy2.pdf');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createParentItemsFromSelected()", function () {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4cadb3e1455e6438b0b7c530d509cddca5f3e1c6
|
||||
Subproject commit 60a7cb79e90a5fc0ed6328f01330230f44704728
|
||||
2
version
2
version
|
|
@ -1 +1 @@
|
|||
7.0.11.SOURCE
|
||||
7.0.13.SOURCE
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue