diff --git a/chrome/content/zotero/components/createParent/createParent.jsx b/chrome/content/zotero/components/createParent/createParent.jsx
new file mode 100644
index 0000000000..7ce4ba0c27
--- /dev/null
+++ b/chrome/content/zotero/components/createParent/createParent.jsx
@@ -0,0 +1,91 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2020 Corporation for Digital Scholarship
+ Vienna, Virginia, USA
+ https://digitalscholar.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 .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+'use strict';
+
+import React, { memo } from 'react';
+import ReactDOM from "react-dom";
+import PropTypes from 'prop-types';
+import cx from 'classnames';
+import { IntlProvider } from "react-intl";
+
+function CreateParent({ loading, item, toggleAccept }) {
+ // When the input has/does not have characters toggle the accept button on the dialog
+ const handleInput = (e) => {
+ if (e.target.value.trim() !== '') {
+ toggleAccept(true);
+ }
+ else {
+ toggleAccept(false);
+ }
+ };
+
+ return (
+
+
+
+ { item.attachmentFilename }
+
+
+
+
+ );
+}
+
+
+CreateParent.propTypes = {
+ loading: PropTypes.bool,
+ item: PropTypes.object,
+ toggleAccept: PropTypes.func
+};
+
+Zotero.CreateParent = memo(CreateParent);
+
+
+Zotero.CreateParent.destroy = (domEl) => {
+ ReactDOM.unmountComponentAtNode(domEl);
+};
+
+
+Zotero.CreateParent.render = (domEl, props) => {
+ ReactDOM.render(, domEl);
+};
diff --git a/chrome/content/zotero/createParentDialog.js b/chrome/content/zotero/createParentDialog.js
new file mode 100644
index 0000000000..2253f7da05
--- /dev/null
+++ b/chrome/content/zotero/createParentDialog.js
@@ -0,0 +1,80 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://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 .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+"use strict";
+
+var io;
+let createParent;
+
+function toggleAccept(enabled) {
+ document.documentElement.getButton("accept").disabled = !enabled;
+}
+
+function doLoad() {
+ // Set font size from pref
+ let sbc = document.getElementById('zotero-create-parent-container');
+ Zotero.setFontSize(sbc);
+
+ io = window.arguments[0];
+
+ createParent = document.getElementById('create-parent');
+ Zotero.CreateParent.render(createParent, {
+ loading: false,
+ item: io.dataIn.item,
+ toggleAccept
+ });
+}
+
+function doUnload() {
+ Zotero.CreateParent.destroy(createParent);
+}
+
+async function doAccept() {
+ let textBox = document.getElementById('parent-item-identifier');
+ let childItem = io.dataIn.item;
+ let newItems = await Zotero_Lookup.addItemsFromIdentifier(
+ textBox,
+ childItem,
+ (on) => {
+ // Render react again with correct loading value
+ Zotero.CreateParent.render(createParent, {
+ loading: on,
+ item: childItem,
+ toggleAccept
+ });
+ }
+ );
+
+ // If we successfully created a parent, return it
+ if (newItems) {
+ io.dataOut = { parent: newItems[0] };
+ window.close();
+ }
+}
+
+function doManualEntry() {
+ io.dataOut = { parent: false };
+ window.close();
+}
diff --git a/chrome/content/zotero/createParentDialog.xul b/chrome/content/zotero/createParentDialog.xul
new file mode 100644
index 0000000000..e92707d6cf
--- /dev/null
+++ b/chrome/content/zotero/createParentDialog.xul
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/lookup.js b/chrome/content/zotero/lookup.js
index c341b13a82..f2be269710 100644
--- a/chrome/content/zotero/lookup.js
+++ b/chrome/content/zotero/lookup.js
@@ -29,9 +29,19 @@
*/
var Zotero_Lookup = new function () {
/**
- * Performs a lookup by DOI, PMID, or ISBN
+ * Performs a lookup by DOI, PMID, or ISBN on the given textBox value
+ * and adds any items it can.
+ *
+ * If a childItem is passed, then only one identifier is allowed, the
+ * child's library/collection information is used and no attachments are
+ * saved for the parent.
+ *
+ * @param textBox {HTMLElement} - Textbox containing identifiers
+ * @param childItem {Zotero.Item|false} - Child item (optional)
+ * @param toggleProgress {function} - Callback to toggle progress on/off
+ * @returns {Promise}
*/
- this.accept = Zotero.Promise.coroutine(function* (textBox) {
+ this.addItemsFromIdentifier = async function (textBox, childItem, toggleProgress) {
var identifiers = Zotero.Utilities.Internal.extractIdentifiers(textBox.value);
if (!identifiers.length) {
Zotero.alert(
@@ -41,58 +51,87 @@ var Zotero_Lookup = new function () {
);
return false;
}
-
- var libraryID = false;
- var collection = false;
- try {
- libraryID = ZoteroPane_Local.getSelectedLibraryID();
- collection = ZoteroPane_Local.getSelectedCollection();
- } catch(e) {
- /** TODO: handle this **/
+ else if (childItem && identifiers.length > 1) {
+ // Only allow one identifier when creating a parent for a child
+ Zotero.alert(
+ window,
+ Zotero.getString("lookup.failure.title"),
+ Zotero.getString("lookup.failureTooMany.description")
+ );
+ return false;
}
- var successful = 0; //counter for successful retrievals
+ var libraryID = false;
+ var collections = false;
+ if (childItem) {
+ libraryID = childItem.libraryID;
+ collections = childItem.collections;
+ }
+ else {
+ try {
+ libraryID = ZoteroPane.getSelectedLibraryID();
+ let collection = ZoteroPane.getSelectedCollection();
+ collections = collection ? [collection.id] : false;
+ }
+ catch (e) {
+ /** TODO: handle this **/
+ }
+ }
- Zotero_Lookup.toggleProgress(true);
+ let newItems = false;
+ toggleProgress(true);
- for (let identifier of identifiers) {
+ await Zotero.Promise.all(identifiers.map(async (identifier) => {
var translate = new Zotero.Translate.Search();
translate.setIdentifier(identifier);
// be lenient about translators
- let translators = yield translate.getTranslators();
+ let translators = await translate.getTranslators();
translate.setTranslator(translators);
try {
- let newItems = yield translate.translate({
+ newItems = await translate.translate({
libraryID,
- collections: collection ? [collection.id] : false
+ collections,
+ saveAttachments: !childItem
});
- successful++;
}
// Continue with other ids on failure
catch (e) {
Zotero.logError(e);
}
- }
-
- Zotero_Lookup.toggleProgress(false);
- // TODO: Give indication if some failed
- if (successful) {
- document.getElementById("zotero-lookup-panel").hidePopup();
- }
- else {
+ }));
+
+ toggleProgress(false);
+ if (!newItems) {
Zotero.alert(
window,
Zotero.getString("lookup.failure.title"),
Zotero.getString("lookup.failure.description")
);
}
-
+ // TODO: Give indication if some, but not all failed
+
+ return newItems;
+ };
+
+ /**
+ * Try a lookup and hide popup if successful
+ */
+ this.accept = async function (textBox) {
+ let newItems = await Zotero_Lookup.addItemsFromIdentifier(
+ textBox,
+ false,
+ on => Zotero_Lookup.toggleProgress(on)
+ );
+
+ if (newItems) {
+ document.getElementById("zotero-lookup-panel").hidePopup();
+ }
return false;
- });
-
-
+ };
+
+
this.showPanel = function (button) {
var panel = document.getElementById('zotero-lookup-panel');
panel.openPopup(button, "after_start", 16, -2, false, false);
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index e35d91c20d..a0ea1e1445 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -2821,24 +2821,12 @@ var ZoteroPane = new function()
show.push(m.findPDF, m.sep3);
}
- var canCreateParent = true;
- for (let i = 0; i < items.length; i++) {
- let item = items[i];
- if (!item.isTopLevelItem() || !item.isAttachment() || item.isFeedItem) {
- canCreateParent = false;
- break;
- }
- }
- if (canCreateParent) {
- show.push(m.createParent);
- }
-
if (canRename) {
show.push(m.renameAttachments);
}
// Add in attachment separator
- if (canCreateParent || canRecognize || canUnrecognize || canRename || canIndex) {
+ if (canRecognize || canUnrecognize || canRename || canIndex) {
show.push(m.sep5);
}
@@ -2849,7 +2837,6 @@ var ZoteroPane = new function()
if (item.isFileAttachment()) {
disable.push(
m.moveToTrash,
- m.createParent,
m.renameAttachments
);
break;
@@ -4517,20 +4504,33 @@ var ZoteroPane = new function()
};
- this.createParentItemsFromSelected = Zotero.Promise.coroutine(function* () {
+ this.createParentItemsFromSelected = async function () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
- var items = this.getSelectedItems();
- for (var i=0; i
+
+
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index a78d90f898..69c45057af 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -1148,6 +1148,9 @@ file.error.cannotAddShortcut = Shortcut files cannot be added directly. Pl
lookup.failure.title = Lookup Failed
lookup.failure.description = Zotero could not find a record for the specified identifier. Please verify the identifier and try again.
lookup.failureToID.description = Zotero could not find any identifiers in your input. Please verify your input and try again.
+lookup.failureTooMany.description = Too many identifiers. Please enter one identifier and try again.
+
+createParent.prompt = Enter a DOI, ISBN, PMID, or arXiv ID to identify this file
locate.online.label = View Online
locate.online.tooltip = Go to this item online
diff --git a/scss/_zotero-react-client.scss b/scss/_zotero-react-client.scss
index c45fffc2d2..54c74b6657 100644
--- a/scss/_zotero-react-client.scss
+++ b/scss/_zotero-react-client.scss
@@ -22,8 +22,10 @@
@import "components/autosuggest";
@import "components/button";
+@import "components/createParent";
@import "components/editable";
@import "components/icons";
+@import "components/progressMeter";
@import "components/search";
@import "components/tagsBox";
@import "components/tagSelector";
diff --git a/scss/components/_createParent.scss b/scss/components/_createParent.scss
new file mode 100644
index 0000000000..aa2673df6e
--- /dev/null
+++ b/scss/components/_createParent.scss
@@ -0,0 +1,35 @@
+.create-parent-container {
+ .title {
+ font-size: 1.4em;
+ font-weight: 700;
+ }
+
+ .body {
+ margin: 1em 0;
+ position: relative;
+ display: flex;
+
+ input {
+ flex: 1;
+ font-size: 14px;
+ }
+
+ .downloadProgress {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ right: -1em;
+ transform: translateY(-100%);
+
+ &.hidden {
+ display: none;
+ }
+
+ .progress-bar {
+ width: 100%;
+ height: 100%;
+ opacity: 0.5;
+ }
+ }
+ }
+}
diff --git a/scss/components/_progressMeter.scss b/scss/components/_progressMeter.scss
new file mode 100644
index 0000000000..20a80e3ff4
--- /dev/null
+++ b/scss/components/_progressMeter.scss
@@ -0,0 +1,72 @@
+// From https://dxr.mozilla.org/mozilla-esr60/source/browser/themes/shared/downloads/progressmeter.inc.css
+
+/*** Common-styled progressmeter ***/
+.downloadProgress {
+ height: 8px;
+ border-radius: 1px;
+ margin: 4px 0 0;
+ margin-inline-end: 12px;
+
+ /* for overriding rules in progressmeter.css */
+ -moz-appearance: none;
+ border-style: none;
+ background-color: transparent;
+ min-width: initial;
+ min-height: initial;
+}
+
+.downloadProgress[mode="undetermined"] {
+ /* for overriding rules on global.css in Linux. */
+ -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter");
+}
+
+.downloadProgress > .progress-bar {
+ background-color: Highlight;
+
+ /* for overriding rules in progressmeter.css */
+ -moz-appearance: none;
+}
+
+.downloadProgress[paused="true"] > .progress-bar {
+ background-color: GrayText;
+}
+
+.downloadProgress[mode="undetermined"] > .progress-bar {
+ /* Make a white reflecting animation.
+ Create a gradient with 2 identical pattern, and enlarge the size to 200%.
+ This allows us to animate background-position with percentage. */
+ background-image: linear-gradient(90deg, transparent 0%,
+ rgba(255,255,255,0.5) 25%,
+ transparent 50%,
+ rgba(255,255,255,0.5) 75%,
+ transparent 100%);
+ background-blend-mode: lighten;
+ background-size: 200% 100%;
+ animation: downloadProgressSlideX 1.5s linear infinite;
+}
+
+.downloadProgress > .progress-remainder {
+ border: solid ButtonShadow;
+ border-block-start-width: 1px;
+ border-block-end-width: 1px;
+ border-inline-start-width: 0;
+ border-inline-end-width: 1px;
+ background-color: ButtonFace;
+}
+
+.downloadProgress[value="0"] > .progress-remainder {
+ border-width: 1px;
+}
+
+.downloadProgress > .progress-remainder[mode="undetermined"] {
+ border: none;
+}
+
+@keyframes downloadProgressSlideX {
+ 0% {
+ background-position: 0 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+}
diff --git a/scss/linux/_createParent.scss b/scss/linux/_createParent.scss
new file mode 100644
index 0000000000..fa4953f91c
--- /dev/null
+++ b/scss/linux/_createParent.scss
@@ -0,0 +1,4 @@
+.create-parent-container {
+ // This matches the margin on the buttons in the dialog box, which are platform dependent
+ padding: 0 5px;
+}
diff --git a/scss/mac/_createParent.scss b/scss/mac/_createParent.scss
new file mode 100644
index 0000000000..077003960a
--- /dev/null
+++ b/scss/mac/_createParent.scss
@@ -0,0 +1,5 @@
+.create-parent-container {
+ // This matches the margin on the buttons in the dialog box, which are platform dependent
+ padding-left: 8px;
+ padding-right: 6px;
+}
diff --git a/scss/win/_createParent.scss b/scss/win/_createParent.scss
new file mode 100644
index 0000000000..fa4953f91c
--- /dev/null
+++ b/scss/win/_createParent.scss
@@ -0,0 +1,4 @@
+.create-parent-container {
+ // This matches the margin on the buttons in the dialog box, which are platform dependent
+ padding: 0 5px;
+}
diff --git a/scss/zotero-react-client-mac.scss b/scss/zotero-react-client-mac.scss
index e44cd7245a..2678caa9c9 100644
--- a/scss/zotero-react-client-mac.scss
+++ b/scss/zotero-react-client-mac.scss
@@ -5,6 +5,7 @@
// --------------------------------------------------
@import "mac/button";
+@import "mac/createParent";
@import "mac/editable";
@import "mac/search";
@import "mac/tag-selector";
diff --git a/scss/zotero-react-client-unix.scss b/scss/zotero-react-client-unix.scss
index 21bd289fe6..d3709baeef 100644
--- a/scss/zotero-react-client-unix.scss
+++ b/scss/zotero-react-client-unix.scss
@@ -4,6 +4,7 @@
// Linux specific
// --------------------------------------------------
+@import "linux/createParent";
@import "linux/editable";
@import "linux/search";
@import "linux/tagsBox";
diff --git a/scss/zotero-react-client-win.scss b/scss/zotero-react-client-win.scss
index 8bb667a9c4..4a78be0a85 100644
--- a/scss/zotero-react-client-win.scss
+++ b/scss/zotero-react-client-win.scss
@@ -4,5 +4,6 @@
// Windows specific
// --------------------------------------------------
+@import "win/createParent";
@import "win/search";
@import "win/tag-selector";