diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js
index f4d7ec0725..0509f5374e 100644
--- a/chrome/content/zotero/fileInterface.js
+++ b/chrome/content/zotero/fileInterface.js
@@ -296,6 +296,7 @@ var Zotero_File_Interface = new function() {
* @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker
* @param {Boolean} [options.addToLibraryRoot=false]
* @param {Boolean} [options.createNewCollection=true] - Put items in a new collection
+ * @param {Boolean} [options.linkFiles=false] - Link to files instead of storing them
* @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful
* for displaying progress in a different way. This also causes an error to be throw
* instead of shown in the main window.
@@ -315,6 +316,7 @@ var Zotero_File_Interface = new function() {
var file = options.file ? Zotero.File.pathToFile(options.file) : null;
var createNewCollection = options.createNewCollection;
var addToLibraryRoot = options.addToLibraryRoot;
+ var linkFiles = options.linkFiles;
var onBeforeImport = options.onBeforeImport;
if (createNewCollection === undefined && !addToLibraryRoot) {
@@ -359,6 +361,7 @@ var Zotero_File_Interface = new function() {
translation,
createNewCollection,
addToLibraryRoot,
+ linkFiles,
defaultNewCollectionPrefix,
onBeforeImport
});
@@ -412,6 +415,7 @@ var Zotero_File_Interface = new function() {
var translation = options.translation;
var addToLibraryRoot = options.addToLibraryRoot;
var createNewCollection = options.createNewCollection;
+ var linkFiles = options.linkFiles;
var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix;
var onBeforeImport = options.onBeforeImport;
@@ -518,7 +522,8 @@ var Zotero_File_Interface = new function() {
try {
yield translation.translate({
libraryID,
- collections: importCollection ? [importCollection.id] : null
+ collections: importCollection ? [importCollection.id] : null,
+ linkFiles
});
} catch(e) {
if (!showProgressWindow) {
diff --git a/chrome/content/zotero/import/importWizard.js b/chrome/content/zotero/import/importWizard.js
index ccc86de34e..facb7d0d45 100644
--- a/chrome/content/zotero/import/importWizard.js
+++ b/chrome/content/zotero/import/importWizard.js
@@ -31,6 +31,17 @@ var Zotero_Import_Wizard = {
}
}
+ // Update labels
+ 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
},
@@ -160,20 +171,6 @@ var Zotero_Import_Wizard = {
},
- onImportStart: async function () {
- if (!this._file) {
- let index = document.getElementById('file-list').selectedIndex;
- this._file = this._dbs[index].path;
- }
- this._disableCancel();
- this._wizard.canRewind = false;
- this._wizard.canAdvance = false;
- await this.doImport({
- createNewCollection: document.getElementById('create-collection-checkbox').hasAttribute('checked')
- });
- },
-
-
onBeforeImport: async function (translation) {
// Unrecognized translator
if (!translation) {
@@ -196,12 +193,22 @@ var Zotero_Import_Wizard = {
},
- doImport: async function (options) {
+ onImportStart: async function () {
+ if (!this._file) {
+ let index = document.getElementById('file-list').selectedIndex;
+ this._file = this._dbs[index].path;
+ }
+ this._disableCancel();
+ this._wizard.canRewind = false;
+ this._wizard.canAdvance = false;
+
try {
let result = await Zotero_File_Interface.importFile({
file: this._file,
onBeforeImport: this.onBeforeImport.bind(this),
- addToLibraryRoot: !options.createNewCollection
+ addToLibraryRoot: !document.getElementById('create-collection-checkbox')
+ .hasAttribute('checked'),
+ linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1
});
// Cancelled by user or due to error
diff --git a/chrome/content/zotero/import/importWizard.xul b/chrome/content/zotero/import/importWizard.xul
index c4e6240a28..8721aed14f 100644
--- a/chrome/content/zotero/import/importWizard.xul
+++ b/chrome/content/zotero/import/importWizard.xul
@@ -54,6 +54,16 @@
onpagerewound="return Zotero_Import_Wizard.goToStart()"
onpageadvanced="Zotero_Import_Wizard.onImportStart()">
+
+
+
+
+
+
+
+
+
+
}
*/
@@ -161,6 +163,8 @@ Zotero.Attachments = new function(){
var file = Zotero.File.pathToFile(options.file);
var parentItemID = options.parentItemID;
var collections = options.collections;
+ var contentType = options.contentType || (yield Zotero.MIME.getMIMETypeFromFile(file));
+ var charset = options.charset;
var saveOptions = options.saveOptions;
if (parentItemID && collections) {
@@ -168,12 +172,12 @@ Zotero.Attachments = new function(){
}
var title = file.leafName;
- var contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
var item = yield _addToDB({
file,
title,
linkMode: this.LINK_MODE_LINKED_FILE,
contentType,
+ charset,
parentItemID,
collections,
saveOptions
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
index 5b0e2a7f99..e742df5484 100644
--- a/chrome/content/zotero/xpcom/translation/translate.js
+++ b/chrome/content/zotero/xpcom/translation/translate.js
@@ -1282,6 +1282,7 @@ Zotero.Translate.Base.prototype = {
* or NULL for default library;
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
+ * @param {Boolean} [linkFiles=false] - Save linked files instead of stored files
* @returns {Promise} Promise resolved with saved items
* when translation complete
*/
@@ -1321,6 +1322,7 @@ Zotero.Translate.Base.prototype = {
}
this._collections = options.collections;
this._saveAttachments = options.saveAttachments === undefined || options.saveAttachments;
+ this._linkFiles = options.linkFiles;
this._forceTagType = options.forceTagType;
this._saveOptions = options.saveOptions;
@@ -2431,6 +2433,7 @@ Zotero.Translate.Import.prototype._prepareTranslation = Zotero.Promise.method(fu
collections: this._collections,
forceTagType: this._forceTagType,
attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
+ linkFiles: this._linkFiles,
baseURI,
saveOptions: Object.assign(
{
diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js
index 1d7b786c9e..b78af7518f 100644
--- a/chrome/content/zotero/xpcom/translation/translate_item.js
+++ b/chrome/content/zotero/xpcom/translation/translate_item.js
@@ -31,6 +31,7 @@
* libraryID - ID of library in which items should be saved
* collections - New collections to create (used during Import translation
* attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved
+ * linkFiles - Save attachments as linked files instead of stored files
* forceTagType - Force tags to specified tag type
* cookieSandbox - Cookie sandbox for attachment requests
* proxy - A proxy to deproxify item URLs
@@ -53,6 +54,7 @@ Zotero.Translate.ItemSaver = function(options) {
// If group filesEditable==false, don't save attachments
this.attachmentMode = Zotero.Libraries.get(this._libraryID).filesEditable ? options.attachmentMode :
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
+ this._linkFiles = options.linkFiles;
this._forceTagType = options.forceTagType;
this._referrer = options.referrer;
this._cookieSandbox = options.cookieSandbox;
@@ -605,7 +607,24 @@ Zotero.Translate.ItemSaver.prototype = {
title: attachment.title || undefined,
collections: !parentItemID ? this._collections : undefined
});
- } else {
+ }
+ else if (this._linkFiles
+ // Don't link if it's a path to the current storage directory
+ && !Zotero.File.directoryContains(Zotero.DataDirectory.getSubdirectory('storage'), file.path)) {
+ attachment.linkMode = "linked_file";
+ newItem = yield Zotero.Attachments.linkFromFile({
+ file,
+ parentItemID,
+ collections: !parentItemID ? this._collections : undefined
+ });
+ if (attachment.title) {
+ newItem.setField("title", attachment.title);
+ }
+ if (attachment.url) {
+ newItem.setNote(attachment.url);
+ }
+ }
+ else {
if (attachment.url) {
attachment.linkMode = "imported_url";
newItem = yield Zotero.Attachments.importSnapshotFromFile({
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index 2981687f2b..e215ad5c20 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -203,6 +203,7 @@
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index cf0474ca45..e01c5909d7 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -722,6 +722,10 @@ fileInterface.exportError = An error occurred while trying to export the selecte
fileInterface.importOPML = Import Feeds from OPML
fileInterface.OPMLFeedFilter = OPML Feed List
+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.
+
quickCopy.copyAs = Copy as %S
quickSearch.mode.titleCreatorYear = Title, Creator, Year
diff --git a/chrome/skin/default/zotero/importWizard.css b/chrome/skin/default/zotero/importWizard.css
index ecc432fd7b..a690158aac 100644
--- a/chrome/skin/default/zotero/importWizard.css
+++ b/chrome/skin/default/zotero/importWizard.css
@@ -33,10 +33,32 @@ wizard[currentpageid="page-file-list"] .wizard-header {
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;
+}
+
#result-description-html a {
text-decoration: underline;
}
diff --git a/test/tests/translateTest.js b/test/tests/translateTest.js
index 0e19cbb312..30bd5a7ce7 100644
--- a/test/tests/translateTest.js
+++ b/test/tests/translateTest.js
@@ -505,6 +505,87 @@ describe("Zotero.Translate", function() {
assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
});
+ it("import translators should save linked-file attachments with linkFiles: true", async function () {
+ var testDir = getTestDataDirectory().path;
+ var file1 = OS.Path.join(testDir, 'test.pdf');
+ var file2 = OS.Path.join(testDir, 'test.html');
+ var file2URL = "http://example.com";
+ var json = [
+ {
+ itemType: "journalArticle",
+ title: "Parent Item",
+ attachments: [
+ {
+ title: "PDF",
+ mimeType: "application/pdf",
+ path: file1
+ },
+ {
+ title: "Snapshot",
+ mimeType: "text/html",
+ charset: "utf-8",
+ url: file2URL,
+ path: file2
+ }
+ ]
+ }
+ ];
+
+ var newItems = itemsArrayToObject(
+ await saveItemsThroughTranslator(
+ "import",
+ json,
+ {
+ linkFiles: true
+ }
+ )
+ );
+ var attachmentIDs = newItems["Parent Item"].getAttachments();
+ assert.lengthOf(attachmentIDs, 2);
+ var attachments = await Zotero.Items.getAsync(attachmentIDs);
+ assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE);
+ assert.equal(attachments[0].attachmentContentType, 'application/pdf');
+ assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE);
+ assert.equal(attachments[1].attachmentContentType, 'text/html');
+ assert.equal(attachments[1].attachmentCharset, 'utf-8');
+ assert.equal(attachments[1].getNote(), file2URL);
+ });
+
+ it("import translators shouldn't save linked-file attachment with linkFiles: true if path is within current storage directory", async function () {
+ var attachment = await importFileAttachment('test.png');
+ var path = attachment.getFilePath();
+ var json = [
+ {
+ itemType: "journalArticle",
+ title: "Parent Item",
+ attachments: [
+ {
+ title: "PDF",
+ mimeType: "application/pdf",
+ path
+ }
+ ]
+ }
+ ];
+
+ var newItems = itemsArrayToObject(
+ await saveItemsThroughTranslator(
+ "import",
+ json,
+ {
+ linkFiles: true
+ }
+ )
+ );
+ var attachmentIDs = newItems["Parent Item"].getAttachments();
+ assert.lengthOf(attachmentIDs, 1);
+ var attachments = await Zotero.Items.getAsync(attachmentIDs);
+ assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE);
+ var newPath = attachments[0].getFilePath();
+ assert.ok(newPath);
+ assert.notEqual(newPath, path);
+ });
+
it('web translators should set accessDate to current date', function* () {
let myItem = {
"itemType":"webpage",