diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js
index ebbf22be55..59eedb6d91 100644
--- a/chrome/content/zotero/fileInterface.js
+++ b/chrome/content/zotero/fileInterface.js
@@ -444,10 +444,12 @@ var Zotero_File_Interface = new function() {
var translation;
- if (options.mendeleyCode) {
+ if (options.mendeleyAuth || options.mendeleyCode) {
translation = yield _getMendeleyTranslation();
translation.createNewCollection = createNewCollection;
+ translation.mendeleyAuth = options.mendeleyAuth;
translation.mendeleyCode = options.mendeleyCode;
+ translation.newItemsOnly = options.newItemsOnly;
}
else {
// Check if the file is an SQLite database
@@ -983,7 +985,7 @@ var Zotero_File_Interface = new function() {
if (matchResult) {
const mendeleyCode = matchResult[1];
Zotero.getMainWindow().setTimeout(() => this.showImportWizard({ mendeleyCode }), 0);
-
+
// Clear all cookies to remove access
//
// This includes unrelated cookies in the central cookie store, but that's fine for
@@ -998,7 +1000,7 @@ var Zotero_File_Interface = new function() {
catch (e) {
Zotero.logError(e);
}
-
+
win.close();
return;
}
diff --git a/chrome/content/zotero/import/importWizard.js b/chrome/content/zotero/import/importWizard.js
index b8625d06f7..04a4706bf3 100644
--- a/chrome/content/zotero/import/importWizard.js
+++ b/chrome/content/zotero/import/importWizard.js
@@ -1,13 +1,18 @@
+/* global mendeleyAPIUtils:false */
import FilePicker from 'zotero/modules/filePicker';
+Components.utils.import("resource://gre/modules/Services.jsm");
+Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleyAPIUtils.js");
+const { directAuth } = mendeleyAPIUtils;
var Zotero_Import_Wizard = {
_wizard: null,
_dbs: null,
_file: null,
_translation: null,
- _mendeleyOnlineRedirectURLWithCode: null,
_mendeleyCode: null,
-
+ _mendeleyAuth: null,
+ _mendeleyHasPreviouslyImported: false,
+ _isZotfileInstalled: false,
init: async function () {
this._wizard = document.getElementById('import-wizard');
@@ -16,6 +21,16 @@ var Zotero_Import_Wizard = {
// Local import disabled
//document.getElementById('radio-import-source-mendeley').hidden = false;
}
+
+ const extensions = await Zotero.getInstalledExtensions();
+ this._isZotfileInstalled = !!extensions.find(extName => extName.match(/^ZotFile((?!disabled).)*$/));
+
+ const predicateID = Zotero.RelationPredicates.getID('mendeleyDB:documentUUID');
+
+ if (predicateID) {
+ const relSQL = 'SELECT ROWID FROM itemRelations WHERE predicateID = ? LIMIT 1';
+ this._mendeleyHasPreviouslyImported = !!(await Zotero.DB.valueQueryAsync(relSQL, predicateID));
+ }
// If no existing collections or non-trash items in the library, don't create a new
// collection by default
@@ -35,7 +50,7 @@ var Zotero_Import_Wizard = {
}
}
- if (args && args.mendeleyCode) {
+ if (args && args.mendeleyCode && Zotero.Prefs.get("import.mendeleyUseOAuth")) {
this._mendeleyCode = args.mendeleyCode;
this._wizard.goTo('page-options');
}
@@ -54,6 +69,14 @@ var Zotero_Import_Wizard = {
'import.fileHandling.description',
Zotero.appName
);
+
+ // Set up Mendeley username/password fields
+ document.querySelector('label[for="mendeley-username"]').textContent
+ = Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.username'));
+ document.querySelector('label[for="mendeley-password"]').textContent
+ = Zotero.Utilities.Internal.stringWithColon(Zotero.getString('general.password'));
+ document.getElementById('mendeley-username').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
+ document.getElementById('mendeley-password').addEventListener('keyup', this.onMendeleyAuthKeyUp.bind(this));
Zotero.Translators.init(); // async
},
@@ -75,6 +98,14 @@ var Zotero_Import_Wizard = {
break;
case 'radio-import-source-mendeley-online':
+ if (this._isZotfileInstalled) {
+ this._onDone(
+ Zotero.getString('general.error'),
+ Zotero.getString('import.online.blockedByPlugin', 'ZotFile'),
+ false
+ );
+ return;
+ }
wizard.goTo('mendeley-online-explanation');
wizard.canRewind = true;
break;
@@ -108,19 +139,48 @@ var Zotero_Import_Wizard = {
},
onMendeleyOnlineShow: async function () {
- document.getElementById('mendeley-online-description').textContent = Zotero.getString(
- 'import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']
- );
+ document.getElementById('mendeley-online-description').textContent = Zotero.Prefs.get("import.mendeleyUseOAuth")
+ ? Zotero.getString('import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley'])
+ : Zotero.getString('import.online.formIntro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']);
document.getElementById('mendeley-online-description2').textContent = Zotero.getString(
'import.online.intro2', [Zotero.appName, 'Mendeley']
);
+ document.getElementById('mendeley-login').style.display = Zotero.Prefs.get("import.mendeleyUseOAuth") ? 'none' : '';
+ document.getElementById('mendeley-online-login-feedback').style.display = 'none';
+ this._wizard.canAdvance = Zotero.Prefs.get("import.mendeleyUseOAuth");
},
- onMendeleyOnlineAdvance: function () {
- if (!this._mendeleyOnlineRedirectURLWithCode) {
+ onMendeleyOnlineAdvance: async function () {
+ if (Zotero.Prefs.get("import.mendeleyUseOAuth")) {
Zotero_File_Interface.authenticateMendeleyOnline();
window.close();
}
+ else {
+ const userNameEl = document.getElementById('mendeley-username');
+ const passwordEl = document.getElementById('mendeley-password');
+ userNameEl.disabled = true;
+ passwordEl.disabled = true;
+ try {
+ this._mendeleyAuth = await directAuth(userNameEl.value, passwordEl.value);
+ this._wizard.goTo('page-options');
+ }
+ catch (e) {
+ const feedbackEl = document.getElementById('mendeley-online-login-feedback');
+ feedbackEl.textContent = Zotero.getString('import.online.wrongCredentials', ['Mendeley']);
+ feedbackEl.style.display = '';
+ this._wizard.canAdvance = false; // change to either of the inputs will reset thi
+ }
+ finally {
+ userNameEl.disabled = false;
+ passwordEl.disabled = false;
+ }
+ }
+ },
+
+ onMendeleyAuthKeyUp: function () {
+ document.getElementById('mendeley-online-login-feedback').style.display = 'none';
+ this._wizard.canAdvance = document.getElementById('mendeley-username').value.length > 0
+ && document.getElementById('mendeley-password').value.length > 0;
},
goToStart: function () {
@@ -201,7 +261,12 @@ var Zotero_Import_Wizard = {
onOptionsShown: function () {
- document.getElementById('file-handling-options').hidden = !!this._mendeleyCode;
+ document.getElementById('file-handling-options').hidden = !!(this._mendeleyAuth || this._mendeleyCode);
+ const hideExtraMendeleyOptions = !this._mendeleyHasPreviouslyImported || !(this._mendeleyAuth || this._mendeleyCode);
+ document.getElementById('mendeley-options').hidden = hideExtraMendeleyOptions;
+ if (hideExtraMendeleyOptions) {
+ document.getElementById('new-items-only-checkbox').removeAttribute('checked');
+ }
},
@@ -228,7 +293,7 @@ var Zotero_Import_Wizard = {
onImportStart: async function () {
- if (!this._file && !this._mendeleyCode) {
+ if (!this._file && !(this._mendeleyAuth || this._mendeleyCode)) {
let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path;
}
@@ -243,7 +308,9 @@ var Zotero_Import_Wizard = {
addToLibraryRoot: !document.getElementById('create-collection-checkbox')
.hasAttribute('checked'),
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1,
- mendeleyCode: this._mendeleyCode
+ mendeleyAuth: this._mendeleyAuth,
+ mendeleyCode: this._mendeleyCode,
+ newItemsOnly: document.getElementById('new-items-only-checkbox').hasAttribute('checked')
});
// Cancelled by user or due to error
diff --git a/chrome/content/zotero/import/importWizard.xul b/chrome/content/zotero/import/importWizard.xul
index 4fc12adaf7..17788ecb19 100644
--- a/chrome/content/zotero/import/importWizard.xul
+++ b/chrome/content/zotero/import/importWizard.xul
@@ -37,6 +37,17 @@
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
- const options = {
- body: isRefresh
- ? `grant_type=refresh_token&refresh_token=${codeOrRefreshToken}`
- : `grant_type=authorization_code&code=${codeOrRefreshToken}`,
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- }
- };
- const response = await Zotero.HTTP.request('POST', OAUTH_URL, options);
+const getTokens = async (url, bodyProps, headers = {}, options = {}) => {
+ const body = Object.entries(bodyProps)
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
+ .join('&');
+ headers = { ...headers, 'Content-Type': 'application/x-www-form-urlencoded' };
+
+ if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
+ headers['User-Agent'] = USER_AGENT;
+ }
+
+ options = { ...options, body, headers, timeout: 30000 };
+ const response = await Zotero.HTTP.request('POST', url, options);
return JSON.parse(response.responseText);
};
+const directAuth = async (username, password, headers = {}, options = {}) => {
+ const bodyProps = {
+ client_id: CLIENT_ID, // eslint-disable-line camelcase
+ client_secret: CLIENT_NOT_VERY_SECRET, // eslint-disable-line camelcase
+ grant_type: 'password', // eslint-disable-line camelcase
+ password,
+ scope: 'all',
+ username
+ };
+
+ return getTokens(OAUTH_URL, bodyProps, headers, options);
+};
+
+const codeAuth = async (code, headers = {}, options = {}) => {
+ const bodyProps = {
+ grant_type: 'authorization_code', // eslint-disable-line camelcase
+ code,
+ };
+
+ return getTokens(ZOTERO_OAUTH_URL, bodyProps, headers, options);
+};
+
+const refreshAuth = async (refreshToken, headers = {}, options = {}) => {
+ const bodyProps = {
+ grant_type: 'refresh_token', // eslint-disable-line camelcase
+ refresh_token: refreshToken, // eslint-disable-line camelcase
+ };
+
+ if (!Zotero.Prefs.get('import.mendeleyUseOAuth')) {
+ bodyProps.client_id = CLIENT_ID; // eslint-disable-line camelcase
+ bodyProps.client_secret = CLIENT_NOT_VERY_SECRET; // eslint-disable-line camelcase
+ }
+
+ return getTokens(
+ Zotero.Prefs.get('import.mendeleyUseOAuth') ? ZOTERO_OAUTH_URL : OAUTH_URL,
+ bodyProps, headers, options
+ );
+};
+
const getNextLinkFromResponse = (response) => {
let next = null;
let links = response.getResponseHeader('link');
@@ -42,7 +87,7 @@ const apiFetchUrl = async (tokens, url, headers = {}, options = {}) => {
}
catch (e) {
if (e.status === 401 || e.status === 403) {
- const newTokens = await getTokens(tokens.refresh_token, true);
+ const newTokens = await refreshAuth(tokens.refresh_token);
// update tokens in the tokens object and in the header for next request
tokens.access_token = newTokens.access_token; // eslint-disable-line camelcase
tokens.refresh_token = newTokens.refresh_token; // eslint-disable-line camelcase
@@ -81,6 +126,5 @@ const getAll = async (tokens, endPoint, params = {}, headers = {}, options = {},
return data;
};
-
-return { getNextLinkFromResponse, getTokens, apiFetch, apiFetchUrl, get, getAll };
+return { codeAuth, directAuth, getNextLinkFromResponse, apiFetch, apiFetchUrl, get, getAll };
})();
diff --git a/chrome/content/zotero/import/mendeley/mendeleyImport.js b/chrome/content/zotero/import/mendeley/mendeleyImport.js
index e7f0346324..a67c4917eb 100644
--- a/chrome/content/zotero/import/mendeley/mendeleyImport.js
+++ b/chrome/content/zotero/import/mendeley/mendeleyImport.js
@@ -10,7 +10,7 @@ Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/men
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/mendeleySchemaMap.js");
const { apiTypeToDBType, apiFieldToDBField } = mendeleyOnlineMappings;
-const { apiFetch, get, getAll, getTokens } = mendeleyAPIUtils;
+const { apiFetch, codeAuth, directAuth, get, getAll } = mendeleyAPIUtils;
const colorMap = new Map();
colorMap.set('rgb(255, 245, 173)', '#ffd400');
@@ -28,7 +28,9 @@ var Zotero_Import_Mendeley = function () {
this.createNewCollection = null;
this.linkFiles = null;
this.newItems = [];
- this.mendeleyCode = null;
+ this.newCollections = [];
+ this.mendeleyAuth = null;
+ this.newItemsOnly = false;
this._tokens = null;
this._db = null;
@@ -115,8 +117,11 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
throw new Error("Not a valid Mendeley database");
}
- if (this.mendeleyCode) {
- this._tokens = await getTokens(this.mendeleyCode);
+ if (this.mendeleyAuth) {
+ this._tokens = this.mendeleyAuth;
+ }
+ else if (this.mendeleyCode) {
+ this._tokens = await codeAuth(this.mendeleyCode);
}
if (!this._file && !this._tokens) {
@@ -303,9 +308,13 @@ Zotero_Import_Mendeley.prototype.translate = async function (options = {}) {
annotations.get(document.id)
);
}
- this.newItems.push(Zotero.Items.get(documentIDMap.get(document.id)));
this._interruptChecker(true);
}
+ if (this.newItemsOnly && rootCollectionKey && this.newItems.length === 0) {
+ Zotero.debug(`Mendeley Import detected no new items, removing import collection containing ${this.newCollections.length} collections created during the import`);
+ const rootCollection = await Zotero.Collections.getAsync(options.collections[0]);
+ await rootCollection.eraseTx(this._saveOptions);
+ }
Zotero.debug(`Completed Mendeley import in ${Math.round((Date.now() - this._started) / 1000)}s. (Started: ${this._started})`);
}
catch (e) {
@@ -480,6 +489,7 @@ Zotero_Import_Mendeley.prototype._saveCollections = async function (libraryID, j
collection.key = collectionJSON.key;
await collection.loadPrimaryData();
}
+ this.newCollections.push(collection);
}
// Remove external ids before saving
@@ -541,11 +551,19 @@ Zotero_Import_Mendeley.prototype._getDocumentsAPI = async function (groupID) {
}
- return (await getAll(this._tokens, 'documents', params, headers, {}, this._interruptChecker)).map(d => ({
- ...d,
- uuid: d.id,
- remoteUuid: d.id
- }));
+ return (await getAll(this._tokens, 'documents', params, headers, {}, this._interruptChecker)).map(d => {
+ const processedDocument = { ...d, remoteUuid: d.id };
+
+ try {
+ const clientData = JSON.parse(d.client_data);
+ processedDocument.uuid = clientData.desktop_id ? clientData.desktop_id : d.id;
+ }
+ catch (_) {
+ processedDocument.uuid = d.id;
+ }
+
+ return processedDocument;
+ });
};
/**
@@ -1279,10 +1297,12 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
var lastExistingParentItem;
for (let i = 0; i < json.length; i++) {
let itemJSON = json[i];
+ let isMappedToExisting = false;
// Check if the item has been previously imported
let item = await this._findExistingItem(libraryID, itemJSON, lastExistingParentItem);
if (item) {
+ isMappedToExisting = true;
if (item.isRegularItem()) {
lastExistingParentItem = item;
@@ -1310,8 +1330,25 @@ Zotero_Import_Mendeley.prototype._saveItems = async function (libraryID, json) {
// Remove external id before save
let toSave = Object.assign({}, itemJSON);
delete toSave.documentID;
-
- item.fromJSON(toSave);
+
+ if ((this.newItemsOnly && !isMappedToExisting) || !this.newItemsOnly) {
+ if (isMappedToExisting) {
+ // dateAdded shouldn't change on an updated item. See #2881
+ delete toSave.dateAdded;
+ }
+ item.fromJSON(toSave);
+ this.newItems.push(item);
+ }
+ else if (isMappedToExisting && toSave.relations) {
+ const predicate = 'mendeleyDB:documentUUID';
+ const existingRels = item.getRelationsByPredicate(predicate);
+ const newRel = toSave.relations[predicate];
+ if (existingRels.length && newRel && existingRels[0] !== newRel) {
+ Zotero.debug(`Migrating relation ${predicate} for existing item ${item.key} from ${existingRels[0]} to ${newRel}`);
+ item.removeRelation(predicate, existingRels[0]);
+ item.addRelation(predicate, newRel);
+ }
+ }
await item.saveTx({
skipDateModifiedUpdate: true,
...this._saveOptions
diff --git a/chrome/content/zotero/import/mendeley/mendeleyOnlineMappings.js b/chrome/content/zotero/import/mendeley/mendeleyOnlineMappings.js
index 102437f1fd..9aa138dd4a 100644
--- a/chrome/content/zotero/import/mendeley/mendeleyOnlineMappings.js
+++ b/chrome/content/zotero/import/mendeley/mendeleyOnlineMappings.js
@@ -28,6 +28,7 @@ var mendeleyOnlineMappings = {
arxiv: 'arxivId',
accessed: 'dateAccessed',
authors: false, // all author types handled separately
+ client_data: false, // desktop_id extraction handled separately
citation_key: 'citationKey',
created: 'added',
edition: 'edition',
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index 21e4835e30..065ab13062 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -186,6 +186,7 @@
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index b390274418..de6936fbd8 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -94,6 +94,8 @@ general.loading = Loading…
general.richText = Rich Text
general.clearSelection = Clear Selection
general.insert = Insert
+general.username = Username
+general.password = Password
general.yellow = Yellow
general.red = Red
@@ -807,8 +809,11 @@ import.localImport = local import
import.fileHandling.store = Copy files to the %S storage folder
import.fileHandling.link = Link to files in original location
import.fileHandling.description = Linked files cannot be synced by %S.
+import.online.formIntro = Please enter your credentials to log in to %2$S. This is necessary to import your %3$S library into %1$S.
import.online.intro = In the next step you will be asked to log in to %2$S and grant %1$S access. This is necessary to import your %3$S library into %1$S.
import.online.intro2 = %1$S will never see or store your %2$S password.
+import.online.wrongCredentials = Login to %1$S failed. Please re-enter credentials and try again.
+import.online.blockedByPlugin = The import cannot continue with "%1$S" installed. Please disable this plugin and try again.
quickCopy.copyAs = Copy as %S
diff --git a/chrome/skin/default/zotero/importWizard.css b/chrome/skin/default/zotero/importWizard.css
index ec8d6d27a8..99cd5cf261 100644
--- a/chrome/skin/default/zotero/importWizard.css
+++ b/chrome/skin/default/zotero/importWizard.css
@@ -3,6 +3,40 @@
font-weight: bold;
}
+#mendeley-login {
+ padding: 1.5em 1em 0;
+ font-size: 13px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ border: none;
+}
+
+#mendeley-login .field {
+ display: flex;
+ max-width: 300px;
+ line-height: 1.5em;
+ align-items: center;
+}
+
+#mendeley-login .field + .field {
+ margin-top: .5em;
+}
+
+#mendeley-login label {
+ flex: 0 0 90px;
+ width: 90px;
+ margin-right: 8px;
+ text-align: right;
+}
+
+#mendeley-login input {
+ flex: 1 1 auto;
+ font-size: 13px;
+}
+
+
+
/* Start */
wizard[currentpageid="page-start"] .wizard-header-label {
padding-top: 24px;
@@ -80,3 +114,11 @@ button, checkbox {
margin-top: 13px;
margin-left: 0;
}
+
+#mendeley-online-login-feedback {
+ text-align: center;
+ font-size: 13px;
+ margin-top: 1.3em;
+ color: red;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/test/tests/data/mendeleyMock/items-simple-no-desktop-id.json b/test/tests/data/mendeleyMock/items-simple-no-desktop-id.json
new file mode 100644
index 0000000000..ff3aa22494
--- /dev/null
+++ b/test/tests/data/mendeleyMock/items-simple-no-desktop-id.json
@@ -0,0 +1,88 @@
+[
+ {
+ "authored": false,
+ "confirmed": true,
+ "created": "2021-11-02T09:54:28.353Z",
+ "file_attached": false,
+ "hidden": false,
+ "id": "7fea3cb3-f97d-3f16-8fad-f59caaa71688",
+ "last_modified": "2021-11-02T12:26:30.025Z",
+ "private_publication": false,
+ "profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
+ "read": false,
+ "source": "lorem ipsum",
+ "identifiers":
+ {
+ "doi": "10.1111",
+ "pmid": "PMID: 11111111",
+ "arxiv": "1111.2222"
+ },
+ "starred": false,
+ "title": "Foo Bar",
+ "authors":
+ [
+ {
+ "first_name": "Tom",
+ "last_name": "Najdek"
+ },
+ {
+ "first_name": "Lorem",
+ "last_name": "Ipsum"
+ }
+ ],
+ "type": "journal",
+ "folder_uuids": [
+ "8d2f262d-49b3-4dfc-8968-0bb71bcd92ea"
+ ],
+ "year": 1987
+ }, {
+ "authored": false,
+ "confirmed": true,
+ "created": "2021-11-04T11:53:10.353Z",
+ "file_attached": false,
+ "hidden": false,
+ "id": "07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef",
+ "last_modified": "2021-11-04T11:53:10.353Z",
+ "private_publication": false,
+ "profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
+ "read": false,
+ "starred": false,
+ "title": "Sample Report",
+ "type": "report",
+ "year": 2002
+ }, {
+ "title": "Item with PDF",
+ "type": "journal",
+ "year": 2005,
+ "source": "Zotero",
+ "pages": "1-11",
+ "websites":
+ [
+ "https://zotero.org"
+ ],
+ "id": "c54b0c6f-c4ce-4706-8742-bc7d032df862",
+ "created": "2021-11-09T10:26:15.201Z",
+ "file_attached": true,
+ "profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
+ "last_modified": "2021-11-09T10:26:16.303Z",
+ "read": false,
+ "starred": false,
+ "authored": false,
+ "confirmed": true,
+ "hidden": false,
+ "private_publication": false,
+ "abstract": "Lorem Ipsum. Nostrud elit ullamco laborum cillum.",
+ "files":
+ [
+ {
+ "id": "19fb5e5b-1a39-4851-b513-d48441a670e1",
+ "document_id": "c54b0c6f-c4ce-4706-8742-bc7d032df862",
+ "mime_type": "application/pdf",
+ "file_name": "item.pdf",
+ "size": 123456,
+ "created": "2021-11-09T10:26:16.292Z",
+ "filehash": "cc22c6611277df346ff8dc7386ba3880b2bafa15"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/test/tests/data/mendeleyMock/items-simple.json b/test/tests/data/mendeleyMock/items-simple.json
index ff3aa22494..4e0da03e3f 100644
--- a/test/tests/data/mendeleyMock/items-simple.json
+++ b/test/tests/data/mendeleyMock/items-simple.json
@@ -6,6 +6,7 @@
"file_attached": false,
"hidden": false,
"id": "7fea3cb3-f97d-3f16-8fad-f59caaa71688",
+ "client_data": "{\"desktop_id\":\"b5f57b1a-f083-486c-aec7-5d5edd366dd2\"}",
"last_modified": "2021-11-02T12:26:30.025Z",
"private_publication": false,
"profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
@@ -42,6 +43,7 @@
"file_attached": false,
"hidden": false,
"id": "07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef",
+ "client_data": "{\"desktop_id\":\"616ec6d1-8d23-4414-8b6e-7bb129677577\"}",
"last_modified": "2021-11-04T11:53:10.353Z",
"private_publication": false,
"profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
@@ -61,6 +63,7 @@
"https://zotero.org"
],
"id": "c54b0c6f-c4ce-4706-8742-bc7d032df862",
+ "client_data": "{\"desktop_id\":\"3630a4bf-d97e-46c4-8611-61ec50f840c6\"}",
"created": "2021-11-09T10:26:15.201Z",
"file_attached": true,
"profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
diff --git a/test/tests/data/mendeleyMock/items-updated.json b/test/tests/data/mendeleyMock/items-updated.json
index 2492ac5477..4165e33e37 100644
--- a/test/tests/data/mendeleyMock/items-updated.json
+++ b/test/tests/data/mendeleyMock/items-updated.json
@@ -2,10 +2,11 @@
{
"authored": false,
"confirmed": true,
- "created": "2021-11-04T11:53:10.353Z",
+ "created": "2021-12-05T12:00:00.000Z",
"file_attached": false,
"hidden": false,
"id": "07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef",
+ "client_data": "{\"desktop_id\":\"616ec6d1-8d23-4414-8b6e-7bb129677577\"}",
"last_modified": "2021-11-05T11:53:10.353Z",
"private_publication": false,
"profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
@@ -15,5 +16,23 @@
"type": "journal",
"source": "lorem ipsum",
"year": 2002
+ },
+ {
+ "authored": false,
+ "confirmed": true,
+ "created": "2021-11-05T12:33:18.353Z",
+ "file_attached": false,
+ "hidden": false,
+ "id": "31a8251f-88b0-497b-9d30-1b2516771057",
+ "client_data": "{\"desktop_id\":\"86e56a00-5ae5-4fe8-a977-9298a03b16d6\"}",
+ "last_modified": "2021-11-05T12:33:18.353Z",
+ "private_publication": false,
+ "profile_id": "8dbf0832-8723-4c48-b532-20c0b7f6e01a",
+ "read": false,
+ "starred": true,
+ "title": "Completely new item",
+ "type": "book",
+ "source": "lorem ipsum",
+ "year": 1999
}
]
\ No newline at end of file
diff --git a/test/tests/mendeleyImportTest.js b/test/tests/mendeleyImportTest.js
index 8eb7936656..d176806ff0 100644
--- a/test/tests/mendeleyImportTest.js
+++ b/test/tests/mendeleyImportTest.js
@@ -1,12 +1,17 @@
/* global setHTTPResponse:false, sinon: false, Zotero_Import_Mendeley: false, HttpServer: false */
describe('Zotero_Import_Mendeley', function () {
- var server, importer, httpd, httpdURL;
+ var server, httpd, httpdURL, importers;
+
+ const getImporter = () => {
+ const importer = new Zotero_Import_Mendeley();
+ importer.mendeleyAuth = { access_token: 'access_token', refresh_token: 'refresh_token' };// eslint-disable-line camelcase
+ importers.push(importer);
+ return importer;
+ };
before(async () => {
Components.utils.import('chrome://zotero/content/import/mendeley/mendeleyImport.js');
- importer = new Zotero_Import_Mendeley();
- importer.mendeleyCode = 'CODE';
// real http server is used to deliver an empty pdf so that annotations can be processed during import
Components.utils.import("resource://zotero-unit/httpd.js");
@@ -25,12 +30,15 @@ describe('Zotero_Import_Mendeley', function () {
});
beforeEach(async () => {
+ importers = [];
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
- server = sinon.fakeServer.create();
+ server = sinon.fakeServer.create({
+ unsafeHeadersEnabled: false
+ });
server.autoRespond = true;
- setHTTPResponse(server, 'https://www.zotero.org/', {
+ setHTTPResponse(server, 'https://api.mendeley.com/', {
method: 'POST',
- url: `utils/mendeley/oauth`,
+ url: `oauth/token`,
status: 200,
headers: {},
json: {
@@ -127,12 +135,21 @@ describe('Zotero_Import_Mendeley', function () {
});
});
- afterEach(() => {
+ afterEach(async () => {
+ await Promise.all(
+ importers
+ .map(importer => ([
+ Zotero.Items.erase(Array.from(new Set(importer.newItems)).map(i => i.id)),
+ Zotero.Collections.erase(Array.from(new Set(importer.newCollections)).map(c => c.id))
+ ]))
+ .reduce((prev, a) => ([...prev, ...a]), []) // .flat() in >= FF62
+ );
Zotero.HTTP.mock = null;
});
describe('#import', () => {
it("should import collections, items, attachments & annotations", async () => {
+ const importer = getImporter();
await importer.translate({
libraryID: Zotero.Libraries.userLibraryID,
collections: null,
@@ -140,17 +157,17 @@ describe('Zotero_Import_Mendeley', function () {
});
const journal = (await Zotero.Relations
- .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '7fea3cb3-f97d-3f16-8fad-f59caaa71688'))
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', 'b5f57b1a-f083-486c-aec7-5d5edd366dd2'))
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
.shift();
const report = (await Zotero.Relations
- .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef'))
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '616ec6d1-8d23-4414-8b6e-7bb129677577'))
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
.shift();
const withpdf = (await Zotero.Relations
- .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', 'c54b0c6f-c4ce-4706-8742-bc7d032df862'))
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '3630a4bf-d97e-46c4-8611-61ec50f840c6'))
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
.shift();
@@ -159,10 +176,14 @@ describe('Zotero_Import_Mendeley', function () {
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
.shift();
+
+ assert.equal(journal.getRelations()['mendeleyDB:remoteDocumentUUID'], '7fea3cb3-f97d-3f16-8fad-f59caaa71688');
assert.equal(journal.getField('title'), 'Foo Bar');
assert.equal(journal.itemTypeID, Zotero.ItemTypes.getID('journalArticle'));
+ assert.equal(report.getRelations()['mendeleyDB:remoteDocumentUUID'], '07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef');
assert.equal(report.getField('title'), 'Sample Report');
assert.equal(report.itemTypeID, Zotero.ItemTypes.getID('report'));
+ assert.equal(withpdf.getRelations()['mendeleyDB:remoteDocumentUUID'], 'c54b0c6f-c4ce-4706-8742-bc7d032df862');
assert.equal(withpdf.getField('title'), 'Item with PDF');
assert.equal(withpdf.itemTypeID, Zotero.ItemTypes.getID('journalArticle'));
@@ -237,22 +258,23 @@ describe('Zotero_Import_Mendeley', function () {
assert.equal(parentCollection.name, 'folder1');
});
- it("should update previously imported item", async () => {
- const importer = new Zotero_Import_Mendeley();
- importer.mendeleyCode = 'CODE';
- await importer.translate({
+ it("should update previously imported item, based on config", async () => {
+ const importer1 = getImporter();
+ await importer1.translate({
libraryID: Zotero.Libraries.userLibraryID,
collections: null,
linkFiles: false,
});
const report = (await Zotero.Relations
- .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef'))
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '616ec6d1-8d23-4414-8b6e-7bb129677577'))
.filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
.shift();
+
assert.equal(report.getField('title'), 'Sample Report');
assert.equal(report.getField('year'), '2002');
+ assert.equal(report.getField('dateAdded'), '2021-11-04 11:53:10');
assert.equal(report.itemTypeID, Zotero.ItemTypes.getID('report'));
assert.lengthOf(report.getTags(), 0);
@@ -266,7 +288,9 @@ describe('Zotero_Import_Mendeley', function () {
)
});
- await importer.translate({
+ const importer2 = getImporter();
+ importer2.newItemsOnly = false;
+ await importer2.translate({
libraryID: Zotero.Libraries.userLibraryID,
collections: null,
linkFiles: false,
@@ -276,6 +300,107 @@ describe('Zotero_Import_Mendeley', function () {
assert.equal(report.itemTypeID, Zotero.ItemTypes.getID('journalArticle'));
assert.equal(report.getField('year'), '2002');
assert.sameMembers(report.getTags().map(t => t.tag), ['\u2605']);
+ // dateAdded shouldn't change on an updated item. See #2881
+ assert.equal(report.getField('dateAdded'), '2021-11-04 11:53:10');
+ });
+
+ it("shouldn't update previously imported item, based on config", async () => {
+ const importer1 = getImporter();
+ await importer1.translate({
+ libraryID: Zotero.Libraries.userLibraryID,
+ collections: null,
+ linkFiles: false,
+ });
+
+ const report = (await Zotero.Relations
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '616ec6d1-8d23-4414-8b6e-7bb129677577'))
+ .filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
+ .shift();
+
+ const noNewItemHere = await Zotero.Relations.getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '86e56a00-5ae5-4fe8-a977-9298a03b16d6');
+
+
+ assert.equal(report.getField('title'), 'Sample Report');
+ assert.equal(report.getField('year'), '2002');
+ assert.equal(report.itemTypeID, Zotero.ItemTypes.getID('report'));
+ assert.lengthOf(report.getTags(), 0);
+ assert.lengthOf(noNewItemHere, 0);
+
+ setHTTPResponse(server, 'https://api.mendeley.com/', {
+ method: 'GET',
+ url: `documents?view=all&limit=500`,
+ status: 200,
+ headers: {},
+ json: JSON.parse(
+ await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/items-updated.json')
+ )
+ });
+
+ const importer2 = getImporter();
+ importer2.newItemsOnly = true;
+ await importer2.translate({
+ libraryID: Zotero.Libraries.userLibraryID,
+ collections: null,
+ linkFiles: false,
+ });
+
+ assert.equal(report.getField('title'), 'Sample Report');
+ assert.equal(report.itemTypeID, Zotero.ItemTypes.getID('report'));
+ assert.equal(report.getField('year'), '2002');
+ assert.lengthOf(report.getTags(), 0);
+
+ const newItem = (await Zotero.Relations
+ .getByPredicateAndObject('item', 'mendeleyDB:documentUUID', '86e56a00-5ae5-4fe8-a977-9298a03b16d6'))
+ .filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
+ .shift();
+
+ assert.equal(newItem.getField('title'), 'Completely new item');
+ });
+
+ it("should correct IDs if available on subsequent import", async () => {
+ setHTTPResponse(server, 'https://api.mendeley.com/', {
+ method: 'GET',
+ url: `documents?view=all&limit=500`,
+ status: 200,
+ headers: {},
+ json: JSON.parse(
+ await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/items-simple-no-desktop-id.json')
+ )
+ });
+ const importer = getImporter();
+ importer.newItemsOnly = true;
+ await importer.translate({
+ libraryID: Zotero.Libraries.userLibraryID,
+ collections: null,
+ linkFiles: false,
+ });
+
+ const report = (await Zotero.Relations
+ .getByPredicateAndObject('item', 'mendeleyDB:remoteDocumentUUID', '07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef'))
+ .filter(item => item.libraryID == Zotero.Libraries.userLibraryID && !item.deleted)
+ .shift();
+
+ assert.equal(report.getField('title'), 'Sample Report');
+ assert.equal(report.getRelations()['mendeleyDB:documentUUID'], '07a74c26-28d1-4d9f-a60d-3f3bc5ef76ef');
+
+ setHTTPResponse(server, 'https://api.mendeley.com/', {
+ method: 'GET',
+ url: `documents?view=all&limit=500`,
+ status: 200,
+ headers: {},
+ json: JSON.parse(
+ await Zotero.File.getContentsFromURLAsync('resource://zotero-unit-tests/data/mendeleyMock/items-simple.json')
+ )
+ });
+
+ await importer.translate({
+ libraryID: Zotero.Libraries.userLibraryID,
+ collections: null,
+ linkFiles: false,
+ });
+
+ assert.equal(report.getField('title'), 'Sample Report');
+ assert.equal(report.getRelations()['mendeleyDB:documentUUID'], '616ec6d1-8d23-4414-8b6e-7bb129677577');
});
});
});