fx-compat: Convert import wizard to use CE

This commit is contained in:
Tom Najdek 2022-08-15 12:04:52 +02:00
parent 2820add3d1
commit 5ea43bd65c
No known key found for this signature in database
GPG key ID: EEC61A7B4C667D77
14 changed files with 683 additions and 570 deletions

View file

@ -389,7 +389,7 @@ var Zotero_File_Interface = new function() {
};
args.wrappedJSObject = args;
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xul",
Services.ww.openWindow(null, "chrome://zotero/content/import/importWizard.xhtml",
"importFile", "chrome,dialog=yes,centerscreen,width=600,height=400,modal", args);
};
@ -985,71 +985,6 @@ var Zotero_File_Interface = new function() {
}
}
this.authenticateMendeleyOnlinePoll = function (win) {
if (win && win[0] && win[0].location) {
const matchResult = win[0].location.toString().match(/mendeley_oauth_redirect.html(?:.*?)(?:\?|&)code=(.*?)(?:&|$)/i);
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
// the moment, since we're not purposely using cookies for anything else.
//
// TODO: Switch to removeAllSince() once >Fx60
try {
Cc["@mozilla.org/cookiemanager;1"]
.getService(Ci.nsICookieManager)
.removeAll();
}
catch (e) {
Zotero.logError(e);
}
win.close();
return;
}
}
if (win && !win.closed) {
Zotero.getMainWindow().setTimeout(this.authenticateMendeleyOnlinePoll.bind(this, win), 200);
}
};
this.authenticateMendeleyOnline = function () {
const uri = `https://api.mendeley.com/oauth/authorize?client_id=5907&redirect_uri=https%3A%2F%2Fzotero-static.s3.amazonaws.com%2Fmendeley_oauth_redirect.html&response_type=code&state=&scope=all`;
var win = Services.wm.getMostRecentWindow("zotero:basicViewer");
if (win) {
win.loadURI(uri);
}
else {
const ww = Services.ww;
const arg = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
arg.data = uri;
win = ww.openWindow(null, "chrome://zotero/content/standalone/basicViewer.xhtml",
"basicViewer", "chrome,dialog=yes,resizable,centerscreen,menubar,scrollbars", arg);
}
let browser;
let func = function () {
win.removeEventListener("load", func);
browser = win.document.documentElement.getElementsByTagName('browser')[0];
browser.addEventListener("pageshow", innerFunc);
};
let innerFunc = function () {
browser.removeEventListener("pageshow", innerFunc);
win.outerWidth = Math.max(640, Math.min(1000, win.screen.availHeight));
win.outerHeight = Math.max(480, Math.min(800, win.screen.availWidth));
};
win.addEventListener("load", func);
// polling executed by the main window because current (wizard) window will be closed
Zotero.getMainWindow().setTimeout(this.authenticateMendeleyOnlinePoll.bind(this, win), 200);
};
/**
* Generate an error string reporting a translation failure. Includes the
* label of the running translator if available.

View file

@ -2,7 +2,22 @@ var EXPORTED_SYMBOLS = ["Zotero_Import_Folder"]; // eslint-disable-line no-unuse
Components.utils.import("resource://gre/modules/Services.jsm");
Services.scriptloader.loadSubScript("chrome://zotero/content/include.js");
const multimatch = require('multimatch');
// matches "*" and "?" wildcards of a glob pattern, case-insensitive
function simpleGlobMatch(filename, patterns) {
for (const pattern of patterns) {
// Convert glob pattern to regex pattern
const regexPattern = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex characters
.replace(/\*/g, '.*') // Replace * with regex equivalent
.replace(/\?/g, '.'); // Replace ? with regex equivalent
if (new RegExp(`^${regexPattern}$`, 'i').test(filename)) {
return true;
}
}
return false;
}
const collectFilesRecursive = async (dirPath, parents = [], files = []) => {
await Zotero.File.iterateDirectory(dirPath, async ({ isDir, _isSymlink, name, path }) => {
@ -97,7 +112,7 @@ class Zotero_Import_Folder { // eslint-disable-line camelcase,no-unused-vars
async ({ name, path }, index) => {
const contentType = mimeTypes[index];
this._progress++;
if (!(this.types.includes(contentType) || multimatch(name, this.fileTypes, { nocase: true }).length > 0)) {
if (!(this.types.includes(contentType) || simpleGlobMatch(name, this.fileTypes))) {
// don't bother calculating a hash for file that will be ignored
return null;
}
@ -165,12 +180,12 @@ class Zotero_Import_Folder { // eslint-disable-line camelcase,no-unused-vars
let attachmentItem = null;
if ((this.types.includes(mimeType) || multimatch(name, this.fileTypes, { nocase: true }).length > 0)) {
if ((this.types.includes(mimeType) || simpleGlobMatch(name, this.fileTypes))) {
const existingItem = await findItemByHash(libraryID, hash);
if (existingItem) {
existingItem.setCollections([...existingItem.getCollections(), ...parentCollectionIDs]);
existingItem.saveTx({ skipSelect: true });
await existingItem.saveTx({ skipSelect: true });
}
else {
if (linkFiles) {
@ -233,9 +248,9 @@ class Zotero_Import_Folder { // eslint-disable-line camelcase,no-unused-vars
finally {
recognizeQueue.removeListener('rowupdated', processRecognizedItem);
}
await Zotero.Promise.all(
itemsToSavePostRecognize.map(async item => item.saveTx({ skipSelect: true }))
);
for (const item of itemsToSavePostRecognize) {
await item.saveTx({ skipSelect: true });
}
}
}

View file

@ -1,211 +1,249 @@
import FilePicker from 'zotero/modules/filePicker';
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2022 Corporation for Digital Scholarship
Vienna, Virginia, USA
https://www.zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
var Zotero_Import_Wizard = {
_wizard: null,
_dbs: null,
_file: null,
_translation: null,
_mendeleyOnlineRedirectURLWithCode: null,
_mendeleyCode: null,
init: async function () {
this._wizard = document.getElementById('import-wizard');
var dbs = await Zotero_File_Interface.findMendeleyDatabases();
if (dbs.length) {
// Local import disabled
//document.getElementById('radio-import-source-mendeley').hidden = false;
import FilePicker from 'zotero/filePicker';
import React from 'react';
import ReactDOM from 'react-dom';
import ProgressQueueTable from 'components/progressQueueTable';
/* eslint camelcase: ["error", {allow: ["Zotero_File_Interface", "Zotero_Import_Wizard"]} ] */
/* global Zotero_File_Interface: false */
const Zotero_Import_Wizard = { // eslint-disable-line no-unused-vars
wizard: null,
folder: null,
file: null,
mendeleyCode: null,
libraryID: null,
translation: null,
async getShouldCreateCollection() {
const sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
+ "UNION "
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
// Not in trash
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
// And not a child item (which doesn't necessarily show up in the trash)
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
+ "LIMIT 1";
return Zotero.DB.valueQueryAsync(sql, this.libraryID);
},
async init() {
const { mendeleyCode, libraryID } = window.arguments[0].wrappedJSObject ?? {};
this.libraryID = libraryID;
this.mendeleyCode = mendeleyCode;
this.wizard = document.getElementById('import-wizard');
this.wizard.getPageById('page-start')
.addEventListener('pageadvanced', this.onImportSourceAdvance.bind(this));
this.wizard.getPageById('page-mendeley-online-intro')
.addEventListener('pagerewound', this.goToStart.bind(this));
this.wizard.getPageById('page-mendeley-online-intro')
.addEventListener('pageadvanced', this.openMendeleyAuthWindow.bind(this));
this.wizard.getPageById('page-options')
.addEventListener('pageshow', this.onOptionsPageShow.bind(this));
this.wizard.getPageById('page-options')
.addEventListener('pageadvanced', this.startImport.bind(this));
this.wizard.getPageById('page-progress')
.addEventListener('pageshow', this.onProgressPageShow.bind(this));
document
.getElementById('other-files')
.addEventListener('keyup', (ev) => {
document.getElementById('import-other').checked = ev.currentTarget.value.length > 0;
});
document
.querySelector('#page-done-error-mendeley > a')
.addEventListener('click', this.onURLInteract.bind(this));
document
.querySelector('#page-done-error-mendeley > a')
.addEventListener('keydown', this.onURLInteract.bind(this));
document
.querySelector('#page-done-error > button')
.addEventListener('click', this.onReportErrorInteract.bind(this));
document
.querySelector('#page-done-error > button')
.addEventListener('keydown', this.onReportErrorInteract.bind(this));
this.wizard.addEventListener('pageshow', this.updateFocus.bind(this));
this.wizard.addEventListener('wizardcancel', this.onCancel.bind(this));
const shouldCreateCollection = await this.getShouldCreateCollection();
document.getElementById('create-collection').checked = shouldCreateCollection;
// wizard.shadowRoot content isn't exposed to our css
this.wizard.shadowRoot
.querySelector('.wizard-header-label').style.fontSize = '16px';
if (mendeleyCode) {
this.wizard.goTo('page-options');
}
// If no existing collections or non-trash items in the library, don't create a new
// collection by default
var args = window.arguments[0].wrappedJSObject;
if (args && args.libraryID) {
let sql = "SELECT ROWID FROM collections WHERE libraryID=?1 "
+ "UNION "
+ "SELECT ROWID FROM items WHERE libraryID=?1 "
// Not in trash
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
// And not a child item (which doesn't necessarily show up in the trash)
+ "AND itemID NOT IN (SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
+ "AND itemID NOT IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL) "
+ "LIMIT 1";
if (!await Zotero.DB.valueQueryAsync(sql, args.libraryID)) {
document.getElementById('create-collection-checkbox').removeAttribute('checked');
},
skipToDonePage(label, description, showReportErrorButton = false, isMendeleyError = false) {
this.wizard.getPageById('page-done').dataset.headerLabelId = label;
if (!isMendeleyError) {
if (Array.isArray(description)) {
document.getElementById('page-done-description').dataset.l10nId = description[0];
document.getElementById('page-done-description').dataset.l10nArgs = JSON.stringify(description[1]);
}
else {
document.getElementById('page-done-description').dataset.l10nId = description;
}
}
if (args && args.mendeleyCode) {
this._mendeleyCode = args.mendeleyCode;
this._wizard.goTo('page-options');
}
// Update labels
document.getElementById('radio-import-source-mendeley-online').label
= `Mendeley Reference Manager (${Zotero.getString('import.onlineImport')})`;
document.getElementById('radio-import-source-mendeley').label
= `Mendeley Desktop (${Zotero.getString('import.localImport')})`;
document.getElementById('file-handling-store').label = Zotero.getString(
'import.fileHandling.store',
Zotero.appName
);
document.getElementById('file-handling-link').label = Zotero.getString('import.fileHandling.link');
document.getElementById('file-handling-description').textContent = Zotero.getString(
'import.fileHandling.description',
Zotero.appName
);
Zotero.Translators.init(); // async
},
document.getElementById('page-done-error-mendeley').style.display = isMendeleyError ? 'block' : 'none';
document.getElementById('page-done-error').style.display = showReportErrorButton ? 'block' : 'none';
onCancel: function () {
if (this._translation && this._translation.interrupt) {
this._translation.interrupt();
}
},
onModeChosen: async function () {
var wizard = this._wizard;
const doneQueueContainer = document.getElementById('done-queue-container');
const doneQueue = document.getElementById('done-queue');
var mode = document.getElementById('import-source').selectedItem.id;
try {
switch (mode) {
case 'radio-import-source-file':
await this.chooseFile();
break;
case 'radio-import-source-mendeley-online':
wizard.goTo('mendeley-online-explanation');
wizard.canRewind = true;
break;
case 'radio-import-source-mendeley':
this._dbs = await Zotero_File_Interface.findMendeleyDatabases();
// This shouldn't happen, because we only show the wizard if there are databases
if (!this._dbs.length) {
throw new Error("No databases found");
}
this._populateFileList(this._dbs);
document.getElementById('file-options-header').textContent
= Zotero.getString('fileInterface.chooseAppDatabaseToImport', 'Mendeley')
wizard.goTo('page-file-list');
wizard.canRewind = true;
this._enableCancel();
break;
default:
throw new Error(`Unknown mode ${mode}`);
}
}
catch (e) {
this._onDone(
Zotero.getString('general.error'),
Zotero.getString('fileInterface.importError'),
true
if (this.folder && !showReportErrorButton) {
doneQueueContainer.style.display = 'flex';
ReactDOM.render(
<ProgressQueueTable progressQueue={ Zotero.ProgressQueues.get('recognize') } />,
doneQueue
);
throw e;
}
else {
doneQueueContainer.style.display = 'none';
}
this.wizard.goTo('page-done');
this.wizard.canRewind = false;
},
onMendeleyOnlineShow: async function () {
document.getElementById('mendeley-online-description').textContent = Zotero.getString(
'import.online.intro', [Zotero.appName, 'Mendeley Reference Manager', 'Mendeley']
);
document.getElementById('mendeley-online-description2').textContent = Zotero.getString(
'import.online.intro2', [Zotero.appName, 'Mendeley']
);
goToStart() {
this.wizard.goTo('page-start');
this.wizard.canAdvance = true;
},
onMendeleyOnlineAdvance: function () {
if (!this._mendeleyOnlineRedirectURLWithCode) {
Zotero_File_Interface.authenticateMendeleyOnline();
window.close();
}
},
goToStart: function () {
this._wizard.goTo('page-start');
this._wizard.canAdvance = true;
return false;
},
chooseFile: async function (translation) {
var translation = new Zotero.Translate.Import();
var translators = await translation.getTranslators();
var fp = new FilePicker();
async chooseFile() {
const translation = new Zotero.Translate.Import();
const translators = await translation.getTranslators();
const fp = new FilePicker();
fp.init(window, Zotero.getString("fileInterface.import"), fp.modeOpen);
fp.appendFilters(fp.filterAll);
var collation = Zotero.getLocaleCollation();
// Add Mendeley DB, which isn't a translator
var mendeleyFilter = {
const mendeleyFilter = {
label: "Mendeley Database", // TODO: Localize
target: "*.sqlite"
};
var filters = [...translators];
const filters = [...translators];
filters.push(mendeleyFilter);
filters.sort((a, b) => collation.compareString(1, a.label, b.label));
for (let filter of filters) {
fp.appendFilter(filter.label, "*." + filter.target);
}
var rv = await fp.show();
const rv = await fp.show();
if (rv !== fp.returnOK && rv !== fp.returnReplace) {
return false;
return;
}
Zotero.debug(`File is ${fp.file}`);
this._file = fp.file;
this._wizard.canAdvance = true;
this._wizard.goTo('page-options');
this.file = fp.file;
this.wizard.canAdvance = true;
this.wizard.goTo('page-options');
},
/**
* When a file is clicked on in the file list
*/
onFileSelected: async function () {
var index = document.getElementById('file-list').selectedIndex;
if (index != -1) {
this._file = this._dbs[index].path;
this._wizard.canAdvance = true;
async chooseFolder() {
const fp = new FilePicker();
fp.init(window, Zotero.getString('attachmentBasePath.selectDir'), fp.modeGetFolder);
fp.appendFilters(fp.filterAll);
const rv = await fp.show();
if (rv !== fp.returnOK && rv !== fp.returnReplace) {
return;
}
else {
this._file = null;
this._wizard.canAdvance = false;
Zotero.debug(`Folder is ${fp.file}`);
this.folder = fp.file;
this.wizard.canAdvance = true;
this.wizard.goTo('page-options');
},
async onImportSourceAdvance(ev) {
const selectedMode = document.getElementById('import-source-group').selectedItem.value;
ev.preventDefault();
ev.stopPropagation();
try {
switch (selectedMode) {
case 'file':
this.folder = null;
await this.chooseFile();
break;
case 'folder':
this.file = null;
await this.chooseFolder();
break;
case 'mendeleyOnline':
this.file = null;
this.folder = null;
this.wizard.goTo('page-mendeley-online-intro');
this.wizard.canRewind = true;
break;
default:
throw new Error(`Unknown mode ${selectedMode}`);
}
}
catch (e) {
this.skipToDonePage('general-error', 'file-interface-import-error', true);
throw e;
}
},
/**
* When the user clicks "Other…" to choose a file not in the list
*/
chooseMendeleyDB: async function () {
document.getElementById('file-list').selectedIndex = -1;
var fp = new FilePicker();
fp.init(window, Zotero.getString('fileInterface.import'), fp.modeOpen);
fp.appendFilter("Mendeley Database", "*.sqlite"); // TODO: Localize
var rv = await fp.show();
if (rv != fp.returnOK) {
return false;
}
this._file = fp.file;
this._wizard.canAdvance = true;
this._wizard.advance();
onOptionsPageShow() {
document.getElementById('page-options-folder-import').style.display = this.folder ? 'block' : 'none';
document.getElementById('page-options-file-handling').style.display = this.mendeleyCode ? 'none' : 'block';
this.wizard.canRewind = false;
},
onOptionsShown: function () {
document.getElementById('file-handling-options').hidden = !!this._mendeleyCode;
openMendeleyAuthWindow(ev) {
ev.preventDefault();
const arg = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
arg.data = 'mendeleyImport';
window.close();
Services.ww.openWindow(null, "chrome://zotero/content/standalone/basicViewer.xhtml",
"basicViewer", "chrome,dialog=yes,centerscreen,width=1000,height=700,modal", arg);
},
onBeforeImport: async function (translation) {
async onBeforeImport(translation) {
// Unrecognized translator
if (!translation) {
// Allow error dialog to be displayed, and then close window
@ -214,159 +252,112 @@ var Zotero_Import_Wizard = {
});
return;
}
this._translation = translation;
this.translation = translation;
// Switch to progress pane
this._wizard.goTo('page-progress');
var pm = document.getElementById('import-progressmeter');
translation.setHandler('itemDone', function () {
pm.value = translation.getProgress();
this.wizard.goTo('page-progress');
translation.setHandler('itemDone', () => {
document.getElementById('import-progress').value = translation.getProgress();
});
},
onImportStart: async function () {
if (!this._file && !this._mendeleyCode) {
let index = document.getElementById('file-list').selectedIndex;
this._file = this._dbs[index].path;
async onProgressPageShow() {
this.wizard.canAdvance = false;
this.wizard.canRewind = false;
const progressQueueContainer = document.getElementById('progress-queue-container');
const progressQueue = document.getElementById('progress-queue');
if (this.folder) {
progressQueueContainer.style.display = 'flex';
ReactDOM.render(
<ProgressQueueTable progressQueue={Zotero.ProgressQueues.get('recognize')} />,
progressQueue
);
}
this._disableCancel();
this._wizard.canRewind = false;
this._wizard.canAdvance = false;
else {
progressQueueContainer.style.display = 'none';
}
},
onURLInteract(ev) {
if (ev.type === 'click' || (ev.type === 'keydown' && ev.key === ' ')) {
Zotero.launchURL(ev.currentTarget.getAttribute('href'));
window.close();
ev.preventDefault();
}
},
onReportErrorInteract(ev) {
if (ev.type === 'click' || (ev.type === 'keydown' && ev.key === ' ')) {
Zotero.getActiveZoteroPane().reportErrors();
window.close();
}
},
onCancel() {
if (this.translation && this.translation.interrupt) {
this.translation.interrupt();
}
},
updateFocus() {
(this.wizard.currentPage.querySelector('radiogroup:not([disabled]),checkbox:not([disabled])') ?? this.wizard.currentPage).focus();
},
async startImport() {
this.wizard.canAdvance = false;
this.wizard.canRewind = false;
const linkFiles = document.getElementById('file-handling').selectedItem.id === 'link';
const recreateStructure = document.getElementById('recreate-structure').checked;
const shouldCreateCollection = document.getElementById('create-collection').checked;
const mimeTypes = document.getElementById('import-pdf').checked
? ['application/pdf']
: [];
const fileTypes = document.getElementById('import-other').checked
? document.getElementById('other-files').value
: null;
try {
let result = await Zotero_File_Interface.importFile({
file: this._file,
const result = await Zotero_File_Interface.importFile({
addToLibraryRoot: !shouldCreateCollection,
file: this.file,
fileTypes,
folder: this.folder,
linkFiles,
mendeleyCode: this.mendeleyCode,
mimeTypes,
onBeforeImport: this.onBeforeImport.bind(this),
addToLibraryRoot: !document.getElementById('create-collection-checkbox')
.hasAttribute('checked'),
linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1,
mendeleyCode: this._mendeleyCode
recreateStructure
});
// Cancelled by user or due to error
if (!result) {
window.close();
return;
}
let numItems = this._translation.newItems.length;
this._onDone(
Zotero.getString('fileInterface.importComplete'),
Zotero.getString(`fileInterface.itemsWereImported`, numItems, numItems)
const numItems = this.translation.newItems.length;
this.skipToDonePage(
'file-interface-import-complete',
['file-interface-items-were-imported', { numItems }]
);
}
catch (e) {
if (e.message == 'Encrypted Mendeley database') {
let url = 'https://www.zotero.org/support/kb/mendeley_import';
let elem = document.createElement('div');
elem.innerHTML = `The selected Mendeley database cannot be read, likely because it `
+ `is encrypted. See <a href="${url}" class="text-link">How do I import a `
+ `Mendeley library into Zotero?</a> for more information.`
this._onDone(Zotero.getString('general.error'), elem);
this.skipToDonePage('general.error', [], false, true);
}
else {
this._onDone(
Zotero.getString('general.error'),
Zotero_File_Interface.makeImportErrorString(this._translation),
const translatorLabel = this.translation?.translator?.[0]?.label;
this.skipToDonePage(
'general.error',
translatorLabel
? ['file-interface-import-error-translator', { translator: translatorLabel }]
: 'file-interface-import-error',
true
);
}
throw e;
}
},
reportError: function () {
Zotero.getActiveZoteroPane().reportErrors();
window.close();
},
_populateFileList: async function (files) {
var listbox = document.getElementById('file-list');
// Remove existing entries
var items = listbox.getElementsByTagName('listitem');
for (let item of items) {
listbox.removeChild(item);
}
for (let file of files) {
let li = document.createElement('listitem');
let name = document.createElement('listcell');
// Simply filenames
let nameStr = file.name
.replace(/\.sqlite$/, '')
.replace(/@www\.mendeley\.com$/, '');
if (nameStr == 'online') {
nameStr = Zotero.getString('dataDir.default', 'online.sqlite');
}
name.setAttribute('label', nameStr + ' ');
li.appendChild(name);
let lastModified = document.createElement('listcell');
lastModified.setAttribute('label', file.lastModified.toLocaleString() + ' ');
li.appendChild(lastModified);
let size = document.createElement('listcell');
size.setAttribute(
'label',
Zotero.getString('general.nMegabytes', (file.size / 1024 / 1024).toFixed(1)) + ' '
);
li.appendChild(size);
listbox.appendChild(li);
}
if (files.length == 1) {
listbox.selectedIndex = 0;
}
},
_enableCancel: function () {
this._wizard.getButton('cancel').disabled = false;
},
_disableCancel: function () {
this._wizard.getButton('cancel').disabled = true;
},
_onDone: function (label, description, showReportErrorButton) {
var wizard = this._wizard;
wizard.getPageById('page-done').setAttribute('label', label);
var xulElem = document.getElementById('result-description');
var htmlElem = document.getElementById('result-description-html');
if (description instanceof HTMLElement) {
htmlElem.appendChild(description);
Zotero.Utilities.Internal.updateHTMLInXUL(htmlElem, { callback: () => window.close() });
xulElem.hidden = true;
htmlElem.setAttribute('display', 'block');
}
else {
xulElem.textContent = description;
xulElem.hidden = false;
htmlElem.setAttribute('display', 'none');
}
document.getElementById('result-description');
if (showReportErrorButton) {
let button = document.getElementById('result-report-error');
button.setAttribute('label', Zotero.getString('errorReport.reportError'));
button.hidden = false;
}
// When done, move to last page and allow closing
wizard.canAdvance = true;
wizard.goTo('page-done');
wizard.canRewind = false;
}
};

View file

@ -0,0 +1,92 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="Zotero_Import_Wizard.init()"
>
<linkset>
<html:link rel="localization" href="zotero.ftl" />
</linkset>
<script src="chrome://global/content/customElements.js"/>
<wizard id="import-wizard" class="import-wizard" data-l10n-id="import-wizard" width="600" height="400">
<wizardpage pageid="page-start" data-header-label-id="import-where-from">
<radiogroup id="import-source-group" align="start">
<radio value="file" data-l10n-id="import-source-file" />
<radio value="folder" data-l10n-id="import-source-folder" />
<radio value="mendeleyOnline" data-l10n-id="import-source-online"
data-l10n-args='{"targetApp": "Mendeley Reference Manager"}' />
</radiogroup>
</wizardpage>
<wizardpage
pageid="page-mendeley-online-intro"
data-header-label-id="import-online-intro-title"
>
<div
class="mendeley-online-intro"
data-l10n-id="import-online-intro"
data-l10n-args='{"targetAppOnline": "Mendeley Reference Manager", "targetApp": "Mendeley"}'></div>
<div
class="mendeley-online-intro"
data-l10n-id="import-online-intro2"
data-l10n-args='{"targetApp": "Mendeley"}'></div>
</wizardpage>
<wizardpage
pageid="page-options"
data-header-label-id="import-options"
>
<div class="options-group" id="page-options-common">
<checkbox native="true" id="create-collection" data-l10n-id="import-create-collection" />
</div>
<div class="options-group" id="page-options-folder-import">
<checkbox native="true" id="recreate-structure" data-l10n-id="import-recreate-structure" checked="true" />
<h2 class="import-file-types-header" data-l10n-id="import-fileTypes-header"></h2>
<fieldset>
<div class="page-options-file-type">
<checkbox native="true" id="import-pdf" data-l10n-id="import-fileTypes-pdf" checked="true" />
</div>
<div class="page-options-file-type">
<checkbox native="true" id="import-other" />
<html:input type="text" id="other-files" data-l10n-id="import-fileTypes-other" />
</div>
</fieldset>
</div>
<div class="options-group" id="page-options-file-handling">
<h2 data-l10n-id="import-file-handling"></h2>
<radiogroup id="file-handling" align="start">
<radio id="store" data-l10n-id="import-file-handling-store" />
<radio id="link" data-l10n-id="import-file-handling-link" />
</radiogroup>
<div class="page-options-file-handling-description" data-l10n-id="import-fileHandling-description"></div>
</div>
</wizardpage>
<wizardpage pageid="page-progress" data-header-label-id="import-importing">
<html:progress id="import-progress" max="100" value="0" />
<div id="progress-queue-container" class="progress-queue table-container">
<div id="progress-queue"></div>
</div>
</wizardpage>
<wizardpage pageid="page-done">
<div id="page-done-description"></div>
<div id="page-done-error-mendeley" data-l10n-id="import-mendeley-encrypted">
<html:a data-l10n-name="mendeley-import-kb" href="https://www.zotero.org/support/kb/mendeley_import" />
</div>
<div id="page-done-error">
<button data-l10n-id="report-error" />
</div>
<div id="page-done-progress-queue"></div>
<div id="done-queue-container" class="done-queue table-container">
<div id="done-queue"></div>
</div>
</wizardpage>
</wizard>
<script src="chrome://zotero/content/include.js"/>
<script src="../fileInterface.js"/>
<script src="./importWizard.js" />
</window>

View file

@ -1,98 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/importWizard.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<wizard id="import-wizard"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&zotero.import;"
onwizardcancel="Zotero_Import_Wizard.onCancel()"
onload="Zotero_Import_Wizard.init()">
<script src="../include.js"/>
<script src="../fileInterface.js"/>
<script src="importWizard.js"/>
<wizardpage pageid="page-start"
label="&zotero.import.whereToImportFrom;"
next="page-options"
onpageadvanced="Zotero_Import_Wizard.onModeChosen(); return false;">
<radiogroup id="import-source">
<radio id="radio-import-source-file" label="&zotero.import.source.file;"/>
<radio id="radio-import-source-mendeley-online"/>
<radio id="radio-import-source-mendeley" hidden="true"/>
</radiogroup>
</wizardpage>
<wizardpage
next="page-options"
pageid="mendeley-online-explanation"
onpageshow="Zotero_Import_Wizard.onMendeleyOnlineShow()"
onpageadvanced="Zotero_Import_Wizard.onMendeleyOnlineAdvance(); return false;"
onpagerewound="return Zotero_Import_Wizard.goToStart()"
>
<description id="mendeley-online-description" />
<description id="mendeley-online-description2" />
</wizardpage>
<wizardpage pageid="page-file-list"
next="page-options"
onpagerewound="return Zotero_Import_Wizard.goToStart()">
<description id="file-options-header"/>
<listbox id="file-list" onselect="Zotero_Import_Wizard.onFileSelected()">
<listhead>
<listheader label="&zotero.import.database;"/>
<listheader label="&zotero.import.lastModified;"/>
<listheader label="&zotero.import.size;"/>
</listhead>
<listcols>
<listcol flex="1"/>
<listcol/>
<listcol/>
</listcols>
</listbox>
<hbox>
<button label="&zotero.general.other;" oncommand="Zotero_Import_Wizard.chooseMendeleyDB()"/>
</hbox>
</wizardpage>
<wizardpage pageid="page-options"
label="&zotero.general.options;"
next="page-progress"
onpageshow="Zotero_Import_Wizard.onOptionsShown()"
onpagerewound="return Zotero_Import_Wizard.goToStart()"
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
<checkbox id="create-collection-checkbox" label="&zotero.import.createCollection;" checked="true" />
<vbox id="file-handling-options">
<label control="file-handling-radio" value="&zotero.import.fileHandling;"/>
<radiogroup id="file-handling-radio">
<radio id="file-handling-store" selected="true"/>
<radio id="file-handling-link"/>
</radiogroup>
<description id="file-handling-description"/>
</vbox>
</wizardpage>
<wizardpage pageid="page-progress"
label="&zotero.import.importing;"
onpageshow="document.getElementById('import-wizard').canRewind = false;"
next="page-done">
<progressmeter id="import-progressmeter" mode="determined"/>
</wizardpage>
<wizardpage pageid="page-done">
<description id="result-description"/>
<html:div id="result-description-html"/>
<hbox>
<button id="result-report-error"
oncommand="Zotero_Import_Wizard.reportError()"
hidden="true"/>
</hbox>
</wizardpage>
</wizard>

View file

@ -0,0 +1,47 @@
Services.scriptloader.loadSubScript("chrome://zotero/content/fileInterface.js", window);
const { E10SUtils } = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm");
const URI = `https://api.mendeley.com/oauth/authorize?client_id=5907&redirect_uri=https%3A%2F%2Fzotero-static.s3.amazonaws.com%2Fmendeley_oauth_redirect.html&response_type=code&state=&scope=all`;
var startTime;
const tryExtractAuthCode = browser => {
const matchResult = browser.webNavigation.currentURI.spec
.match(/mendeley_oauth_redirect.html(?:.*?)(?:\?|&)code=(.*?)(?:&|$)/i);
return matchResult ? matchResult[1] : false;
}
const clearCookies = since => {
const cookieManager = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
const sinceμs = since * 1000;
const cookiesSince = cookieManager.getCookiesSince(sinceμs);
Zotero.debug(`Deleting ${cookiesSince.length} cookies created during Mendeley Auth (last ${(Date.now() - since)}ms)`);
cookieManager.removeAllSince(sinceμs);
}
window.addEventListener('unload', () => {
clearCookies(startTime);
});
window.addEventListener("load", () => {
// basicViewer uses remote="false" browser, we need to re-construct the browser so it's
// capable of loading a mendeley auth website
const browser = document.querySelector('browser');
browser.setAttribute('remote', 'true');
browser.changeRemoteness({ remoteType: E10SUtils.DEFAULT_REMOTE_TYPE });
browser.construct();
startTime = Date.now();
browser.addEventListener("pagetitlechanged", event => {
const mendeleyCode = tryExtractAuthCode(browser);
if (mendeleyCode) {
window.close();
Zotero_File_Interface.showImportWizard({ mendeleyCode })
}
document.title = browser.contentTitle;
});
browser.loadURI(URI, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
}, false);

View file

@ -60,8 +60,13 @@
if (Zotero.isMac) {
Services.scriptloader.loadSubScript("chrome://global/content/macWindowMenu.js", this);
}
if(window.arguments?.[0] === 'mendeleyImport') {
Services.scriptloader.loadSubScript("chrome://zotero/content/import/mendeley/authViewer.js", this);
} else {
Services.scriptloader.loadSubScript("chrome://zotero/content/standalone/basicViewer.js", this);
}
Services.scriptloader.loadSubScript("chrome://zotero/content/standalone/basicViewer.js", this);
</script>
<commandset id="mainCommandSet">
@ -165,6 +170,13 @@
</toolbox>
<hbox flex="1" id="browser">
<vbox id="appcontent" flex="1"/>
<vbox id="appcontent" flex="1">
<browser
type="content"
flex="1"
remote="false"
disableglobalhistory="true"
maychangeremoteness="true"/>
</vbox>
</hbox>
</window>

View file

@ -180,6 +180,8 @@
<!ENTITY zotero.import "Import">
<!ENTITY zotero.import.whereToImportFrom "Where do you want to import from?">
<!ENTITY zotero.import.source.file "A file (BibTeX, RIS, Zotero RDF, etc.)">
<!ENTITY zotero.import.source.folder "A folder of PDFs or other files">
<!ENTITY zotero.import.onlineImport "online import">
<!ENTITY zotero.import.importing "Importing…">
<!ENTITY zotero.import.database "Database">
<!ENTITY zotero.import.lastModified "Last Modified">

View file

@ -0,0 +1,64 @@
-app-name = Zotero
import-wizard =
.title = "Import"
import-where-from = Where do you want to import from?
import-online-intro-title = Introduction
import-source-file =
.label = A file (BibTeX, RIS, Zotero RDF, etc.)
import-source-folder =
.label = A folder of PDFs or other files
import-source-online =
.label = { $targetApp } online import
import-options = Options
import-importing = Importing…
import-create-collection =
.label = Place imported collections and items into new collection
import-recreate-structure =
.label = Recreate folder structure as collections
import-fileTypes-header = File Types to Import:
import-fileTypes-pdf =
.label = PDFs
import-fileTypes-other =
.placeholder = Other files by pattern, comma-separated (e.g., *.jpg,*.png)
import-file-handling = File Handling
import-file-handling-store =
.label = Copy files to the { -app-name } storage folder
import-file-handling-link =
.label = Link to files in original location
import-fileHandling-description = Linked files cannot be synced by { -app-name }.
general-error = Error
file-interface-import-error = An error occurred while trying to import the selected file. Please ensure that the file is valid and try again.
file-interface-import-complete = Import Complete
file-interface-items-were-imported = { $numItems ->
[one] item was imported
*[other] { $numItems } items were imported
}
import-mendeley-encrypted = The selected Mendeley database cannot be read, likely because it is encrypted.
See <a data-l10n-name="mendeley-import-kb">How do I import a Mendeley library into Zotero?</a> for more information.
file-interface-import-error = = An error occurred while trying to import the selected file. Please ensure that the file is valid and try again.
file-interface-import-error-translator = An error occurred importing the selected file with “{ $translator }”. Please ensure that the file is valid and try again.
# Variables:
# $targetAppOnline (String)
# $targetApp (String)
import-online-intro=In the next step you will be asked to log in to { $targetAppOnline } and grant { -app-name } access. This is necessary to import your { $targetApp } library into { -app-name }.
import-online-intro2={ -app-name } will never see or store your { $targetApp } password.
report-error =
.label = Report Error…

View file

@ -808,14 +808,6 @@ fileInterface.exportError = An error occurred while trying to export the selecte
fileInterface.importOPML = Import Feeds from OPML
fileInterface.OPMLFeedFilter = OPML Feed List
import.onlineImport = online import
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.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.
quickCopy.copyAs = Copy as %S
quickSearch.mode.titleCreatorYear = Title, Creator, Year

View file

@ -1,82 +0,0 @@
.wizard-header-label {
font-size: 16px;
font-weight: bold;
}
/* Start */
wizard[currentpageid="page-start"] .wizard-header-label {
padding-top: 24px;
}
wizard[currentpageid="page-start"] .wizard-page-box {
margin-top: -2px;
padding-top: 0;
}
radiogroup {
font-size: 14px;
margin-top: 4px;
}
radio {
padding-top: 5px;
}
/* File list */
wizard[currentpageid="page-file-list"] .wizard-header {
display: none;
}
#file-options-header {
font-size: 15px;
font-weight: bold;
margin-bottom: 6px;
}
#file-handling-options {
margin-top: 2em;
}
#file-handling-options > label {
font-size: 14px;
}
#file-handling-options radiogroup {
font-size: 13px;
margin-left: 1em;
}
#file-handling-options description {
margin-top: .6em;
font-size: 11px;
}
listbox, #result-description, #result-description-html {
font-size: 13px;
}
#result-description-html {
line-height: 1.5;
}
#mendeley-online-description, #mendeley-online-description2 {
font-size: 13px;
line-height: 1.5;
}
#mendeley-online-description2 {
margin-top: 1em;
}
#result-description-html a {
text-decoration: underline;
}
button, checkbox {
font-size: 13px;
}
#result-report-error {
margin-top: 13px;
margin-left: 0;
}

View file

@ -31,6 +31,7 @@
@import "components/editable";
@import "components/exportOptions";
@import "components/icons";
@import "components/import-wizard";
@import "components/item-tree";
@import "components/longTagFixer";
@import "components/mainWindow";

View file

@ -0,0 +1,142 @@
.import-wizard {
font-size: 12px;
radiogroup {
radio {
padding-top: 5px;
}
}
wizardpage {
display: flex;
flex-direction: column;
> div {
display: block;
}
}
a {
display: inline;
color: -moz-nativehyperlinktext;
cursor: pointer;
&:hover, &:active, &:focus {
outline:none;
text-decoration: $link-hover-decoration;
}
}
checkbox {
margin-left: 4px; // indent required for correct focus ring rendering
}
button {
font: menu;
}
h2 {
font-size: 14px;
font-weight: normal;
margin-bottom: 6px;
margin: 0;
}
#other-files {
margin-left: -1px;
width: 400px;
}
#page-start {
radiogroup {
margin-top: 4px;
}
}
#page-options-folder-import {
fieldset {
display: block;
border: none;
margin-left: 1em;
}
#recreate-structure {
margin-top: .25em;
}
.import-file-types-header {
margin-top: 1em;
}
.page-options-file-type {
display: flex;
margin-top: .25em;
&:first-child {
margin-top: 1em;
}
}
}
#page-options-file-handling {
padding-top: 1.5em;
.radioset {
// font-size: 13px;
margin-left: 1em;
}
}
.page-options-file-handling-description {
margin-top: .6em;
font-size: 11px;
}
#page-done-error {
margin-top: 2em;
text-align: right;
}
.mendeley-online-intro {
// font-size: 13px;
& +.mendeley-online-intro {
margin-top: 1em;
}
}
#import-progress {
margin-top: 1em;
}
#page-progress {
display: flex;
flex-direction: column;
}
// TODO: deduplicate with rtfscan
.table-container {
display: flex;
height: 0;
flex-direction: column;
flex: 1 0 auto;
margin-top: 1.5em;
> div {
display: flex;
flex: 1 0 auto;
background-color: -moz-field;
overflow: hidden;
position: relative;
}
.virtualized-table-body {
display: flex;
.windowed-list {
flex: 1 0 auto;
}
}
}
}

View file

@ -2,7 +2,7 @@
describe('Zotero_Import_Folder', function () {
var tmpDir;
const uc = (name) => 'Zotero_Import_Folder_' + name;
const uc = name => 'Zotero_Import_Folder_' + name;
before(async () => {
tmpDir = await getTempDirectory();
@ -111,7 +111,7 @@ describe('Zotero_Import_Folder', function () {
const importer = new Zotero_Import_Folder({
folder: tmpDir,
recreateStructure: false,
fileTypes: '*.png,*.txt',
fileTypes: '*.png,*.TXT', // should match case-insensitively
mimeTypes: []
});