diff --git a/chrome/content/zotero/itemPane.xul b/chrome/content/zotero/itemPane.xul
index 7a040c49ce..ff3baa2952 100644
--- a/chrome/content/zotero/itemPane.xul
+++ b/chrome/content/zotero/itemPane.xul
@@ -33,6 +33,11 @@
+
+
+
+
+
+ &zotero.publications.include.adjustAtAnyTime;
+
diff --git a/chrome/content/zotero/xpcom/collectionTreeRow.js b/chrome/content/zotero/xpcom/collectionTreeRow.js
index b1573a7221..05b32479df 100644
--- a/chrome/content/zotero/xpcom/collectionTreeRow.js
+++ b/chrome/content/zotero/xpcom/collectionTreeRow.js
@@ -37,7 +37,6 @@ Zotero.CollectionTreeRow = function(type, ref, level, isOpen)
Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
switch (this.type) {
case 'library':
- case 'publications':
case 'group':
case 'feed':
return 'L' + this.ref.libraryID;
@@ -54,6 +53,9 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
case 'unfiled':
return 'U' + this.ref.libraryID;
+ case 'publications':
+ return 'P' + this.ref.libraryID;
+
case 'trash':
return 'T' + this.ref.libraryID;
@@ -73,7 +75,7 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
Zotero.CollectionTreeRow.prototype.isLibrary = function (includeGlobal)
{
if (includeGlobal) {
- var global = ['library', 'publications', 'group', 'feed'];
+ var global = ['library', 'group', 'feed'];
return global.indexOf(this.type) != -1;
}
return this.type == 'library';
@@ -315,6 +317,9 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
}
includeScopeChildren = true;
}
+ else if (this.isPublications()) {
+ s.addCondition('publications', 'true');
+ }
else if (this.isTrash()) {
s.addCondition('deleted', 'true');
}
@@ -383,6 +388,7 @@ Zotero.CollectionTreeRow.prototype.setTags = function (tags) {
Zotero.CollectionTreeRow.prototype.isSearchMode = function() {
switch (this.type) {
case 'search':
+ case 'publications':
case 'trash':
return true;
}
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
index 831d72a01d..e3f3121904 100644
--- a/chrome/content/zotero/xpcom/collectionTreeView.js
+++ b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -47,7 +47,6 @@ Zotero.CollectionTreeView = function()
[
'collection',
'search',
- 'publications',
'feed',
'share',
'group',
@@ -183,18 +182,6 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function*
);
added += yield this._expandRow(newRows, 0);
- this._addRowToArray(newRows, new Zotero.CollectionTreeRow('separator', false), added++);
-
- // Add "My Publications"
- this._addRowToArray(
- newRows,
- new Zotero.CollectionTreeRow('publications', {
- libraryID: Zotero.Libraries.publicationsLibraryID,
- treeViewID: "L" + Zotero.Libraries.publicationsLibraryID
- }),
- added++
- );
-
// TODO: Unify feed and group adding code
// Add groups
@@ -1308,12 +1295,14 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi
var showDuplicates = this.hideSources.indexOf('duplicates') == -1
&& this._virtualCollectionLibraries.duplicates[libraryID] !== false;
var showUnfiled = this._virtualCollectionLibraries.unfiled[libraryID] !== false;
+ var showPublications = libraryID == Zotero.Libraries.userLibraryID;
var showTrash = this.hideSources.indexOf('trash') == -1;
}
else {
var savedSearches = [];
var showDuplicates = false;
var showUnfiled = false;
+ var showPublications = false;
var showTrash = false;
}
@@ -1367,6 +1356,23 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi
newRows++;
}
+ if (showPublications) {
+ // Add "My Publications"
+ this._addRowToArray(
+ rows,
+ new Zotero.CollectionTreeRow(
+ 'publications',
+ {
+ libraryID,
+ treeViewID: "P" + libraryID
+ },
+ level + 1
+ ),
+ row + 1 + newRows
+ );
+ newRows++
+ }
+
// Duplicate items
if (showDuplicates) {
let d = new Zotero.Duplicates(libraryID);
@@ -1624,7 +1630,7 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
continue;
}
- if (treeRow.isPublications() && treeRow.ref.libraryID != item.libraryID) {
+ if (treeRow.isPublications()) {
if (item.isAttachment() || item.isNote()) {
Zotero.debug("Top-level attachments and notes cannot be added to My Publications");
return false;
@@ -1790,6 +1796,16 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
skip = false;
continue;
}
+
+ // Make sure there's at least one item that's not already in My Publications
+ if (treeRow.isPublications()) {
+ if (item.inPublications) {
+ Zotero.debug("Item " + item.id + " already exists in My Publications");
+ continue;
+ }
+ skip = false;
+ continue;
+ }
}
if (skip) {
Zotero.debug("Drag skipped");
@@ -1929,13 +1945,6 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Create new clone item in target library
var newItem = item.clone(targetLibraryID, { skipTags: !options.tags });
- // Set Rights field for My Publications
- if (options.license) {
- if (!options.keepRights || !newItem.getField('rights')) {
- newItem.setField('rights', options.licenseName);
- }
- }
-
var newItemID = yield newItem.save({
skipSelect: true
});
@@ -2121,42 +2130,34 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
copyOptions.childNotes = io.includeNotes;
copyOptions.childFileAttachments = io.includeFiles;
copyOptions.childLinks = true;
- copyOptions.tags = true; // TODO: add checkbox
['keepRights', 'license', 'licenseName'].forEach(function (field) {
copyOptions[field] = io[field];
});
}
- yield Zotero.DB.executeTransaction(function* () {
- var newItems = [];
- var newIDs = [];
- var toMove = [];
- // TODO: support items coming from different sources?
- if (items[0].libraryID == targetLibraryID) {
- var sameLibrary = true;
- }
- else {
- var sameLibrary = false;
+ let newItems = [];
+ let newIDs = [];
+ let toMove = [];
+ // TODO: support items coming from different sources?
+ let sameLibrary = items[0].libraryID == targetLibraryID
+
+ for (let item of items) {
+ if (!item.isTopLevelItem()) {
+ continue;
}
- for (let item of items) {
- if (!item.isTopLevelItem()) {
- continue;
- }
-
- if (sameLibrary) {
- newIDs.push(item.id);
- toMove.push(item.id);
- }
- else {
- newItems.push(item);
- }
- }
+ newItems.push(item);
- if (!sameLibrary) {
- var toReconcile = [];
-
- var newIDs = [];
+ if (sameLibrary) {
+ newIDs.push(item.id);
+ toMove.push(item.id);
+ }
+ }
+
+ if (!sameLibrary) {
+ let toReconcile = [];
+
+ yield Zotero.DB.executeTransaction(function* () {
for (let item of newItems) {
var id = yield copyItem(item, targetLibraryID, copyOptions)
// Standalone attachments might not get copied
@@ -2165,64 +2166,70 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
newIDs.push(id);
}
+ });
+
+ if (toReconcile.length) {
+ let sourceName = Zotero.Libraries.getName(items[0].libraryID);
+ let targetName = Zotero.Libraries.getName(targetLibraryID);
- if (toReconcile.length) {
- var sourceName = Zotero.Libraries.getName(items[0].libraryID);
- var targetName = Zotero.Libraries.getName(targetLibraryID);
-
- var io = {
- dataIn: {
- type: "item",
- captions: [
- // TODO: localize
- sourceName,
- targetName,
- "Merged Item"
- ],
- objects: toReconcile
- }
- };
-
- /*
- if (type == 'item') {
- if (!Zotero.Utilities.isEmpty(changedCreators)) {
- io.dataIn.changedCreators = changedCreators;
- }
+ let io = {
+ dataIn: {
+ type: "item",
+ captions: [
+ // TODO: localize
+ sourceName,
+ targetName,
+ "Merged Item"
+ ],
+ objects: toReconcile
}
- */
-
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var lastWin = wm.getMostRecentWindow("navigator:browser");
- lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
-
+ };
+
+ /*
+ if (type == 'item') {
+ if (!Zotero.Utilities.isEmpty(changedCreators)) {
+ io.dataIn.changedCreators = changedCreators;
+ }
+ }
+ */
+
+ let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ let lastWin = wm.getMostRecentWindow("navigator:browser");
+ lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
+
+ yield Zotero.DB.executeTransaction(function* () {
for (let obj of io.dataOut) {
yield obj.ref.save();
}
- }
- }
-
- // Add items to target collection
- if (targetCollectionID) {
- let ids = newIDs.filter(function (itemID) {
- var item = Zotero.Items.get(itemID);
- return item.isTopLevelItem();
});
- var collection = yield Zotero.Collections.getAsync(targetCollectionID);
+ }
+ }
+
+ // Add items to target collection
+ if (targetCollectionID) {
+ let ids = newIDs.filter(itemID => Zotero.Items.get(itemID).isTopLevelItem());
+ yield Zotero.DB.executeTransaction(function* () {
+ let collection = yield Zotero.Collections.getAsync(targetCollectionID);
yield collection.addItems(ids);
+ }.bind(this));
+ }
+ else if (targetTreeRow.isPublications()) {
+ yield Zotero.Items.addToPublications(newItems, copyOptions);
+ }
+
+ // If moving, remove items from source collection
+ if (dropEffect == 'move' && toMove.length) {
+ if (!sameLibrary) {
+ throw new Error("Cannot move items between libraries");
}
-
- // If moving, remove items from source collection
- if (dropEffect == 'move' && toMove.length) {
- if (!sameLibrary) {
- throw new Error("Cannot move items between libraries");
- }
- if (!sourceTreeRow || !sourceTreeRow.isCollection()) {
- throw new Error("Drag source must be a collection for move action");
- }
+ if (!sourceTreeRow || !sourceTreeRow.isCollection()) {
+ throw new Error("Drag source must be a collection for move action");
+ }
+ yield Zotero.DB.executeTransaction(function* () {
yield sourceTreeRow.ref.removeItems(toMove);
- }
- });
+ }.bind(this));
+ }
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
var targetLibraryID = targetTreeRow.ref.libraryID;
@@ -2332,9 +2339,7 @@ Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop)
props.push("header");
props.push("notwisty");
}
- else if (treeRow.isPublications()) {
- props.push("notwisty");
- } else if (treeRow.ref && treeRow.ref.unreadCount) {
+ else if (treeRow.ref && treeRow.ref.unreadCount) {
props.push('unread');
}
diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js
index b43a3ae87e..3fa2920b6b 100644
--- a/chrome/content/zotero/xpcom/data/collections.js
+++ b/chrome/content/zotero/xpcom/data/collections.js
@@ -137,11 +137,6 @@ Zotero.Collections = function() {
this.getCollectionsContainingItems = function (itemIDs, asIDs) {
- // If an unreasonable number of items, don't try
- if (itemIDs.length > 100) {
- return Zotero.Promise.resolve([]);
- }
-
var sql = "SELECT collectionID FROM collections WHERE ";
var sqlParams = [];
for (let id of itemIDs) {
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
index 4c17139759..f36124b82b 100644
--- a/chrome/content/zotero/xpcom/data/item.js
+++ b/chrome/content/zotero/xpcom/data/item.js
@@ -374,6 +374,7 @@ Zotero.Item.prototype._parseRowData = function(row) {
// Boolean
case 'synced':
case 'deleted':
+ case 'inPublications':
val = !!val;
break;
@@ -1133,9 +1134,11 @@ Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) {
}
-// Define 'deleted' property (and any others that follow the same pattern in the future)
-for (let name of ['deleted']) {
+// Define boolean properties
+for (let name of ['deleted', 'inPublications']) {
let prop = '_' + name;
+ // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=449811 (Fixed in Fx51)
+ let tmpName = name;
Zotero.defineProperty(Zotero.Item.prototype, name, {
get: function() {
@@ -1151,12 +1154,12 @@ for (let name of ['deleted']) {
val = !!val;
if (this[prop] == val) {
- Zotero.debug(Zotero.Utilities.capitalize(name)
+ Zotero.debug(Zotero.Utilities.capitalize(tmpName)
+ " state hasn't changed for item " + this.id);
return;
}
- this._markFieldChange(name, !!this[prop]);
- this._changed[name] = true;
+ this._markFieldChange(tmpName, !!this[prop]);
+ this._changed[tmpName] = true;
this[prop] = val;
}
});
@@ -1510,17 +1513,13 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
}
}
- if (libraryType == 'publications' && !this.isRegularItem() && !parentItemID) {
+ if (this._inPublications && !this.isRegularItem() && !parentItemID) {
throw new Error("Top-level attachments and notes cannot be added to My Publications");
}
// Trashed status
if (this._changed.deleted) {
if (this._deleted) {
- if (libraryType == 'publications') {
- throw new Error("Items in My Publications cannot be moved to trash");
- }
-
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
}
else {
@@ -1554,6 +1553,16 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
}
}
+ if (this._changed.inPublications) {
+ if (this._inPublications) {
+ sql = "INSERT OR IGNORE INTO publicationsItems (itemID) VALUES (?)";
+ }
+ else {
+ sql = "DELETE FROM publicationsItems WHERE itemID=?";
+ }
+ yield Zotero.DB.queryAsync(sql, itemID);
+ }
+
// Note
if ((isNew && this.isNote()) || this._changed.note) {
if (!isNew) {
@@ -3624,6 +3633,28 @@ Zotero.DataObject.prototype.setDeleted = Zotero.Promise.coroutine(function* (del
});
+/**
+ * Update item publications state without marking as changed or modifying DB
+ *
+ * This is used by Zotero.Items.addToPublications()/removeFromPublications()
+ *
+ * Database state must be set separately!
+ *
+ * @param {Boolean} inPublications
+ */
+Zotero.DataObject.prototype.setPublications = Zotero.Promise.coroutine(function* (inPublications) {
+ if (!this.id) {
+ throw new Error("Cannot update publications state of unsaved item");
+ }
+
+ this._inPublications = !!inPublications;
+
+ if (this._changed.inPublications) {
+ delete this._changed.inPublications;
+ }
+});
+
+
Zotero.Item.prototype.getImageSrc = function() {
var itemType = Zotero.ItemTypes.getName(this.itemTypeID);
if (itemType == 'attachment') {
@@ -4086,7 +4117,8 @@ Zotero.Item.prototype.fromJSON = function (json) {
break;
case 'deleted':
- this.deleted = !!val;
+ case 'inPublications':
+ this[field] = !!val;
break;
case 'creators':
@@ -4262,15 +4294,21 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
}.bind(this));
}
- // Relations
- obj.relations = this.getRelations()
+ // My Publications
+ if (this._inPublications || mode == 'full') {
+ obj.inPublications = this._inPublications;
+ }
// Deleted
let deleted = this.deleted;
if (deleted || mode == 'full') {
+ // Match what APIv3 returns, though it would be good to change this
obj.deleted = deleted ? 1 : 0;
}
+ // Relations
+ obj.relations = this.getRelations()
+
if (obj.accessDate) obj.accessDate = Zotero.Date.sqlToISO8601(obj.accessDate);
if (this.dateAdded) {
diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js
index 2c1b48dd0c..397b31648d 100644
--- a/chrome/content/zotero/xpcom/data/items.js
+++ b/chrome/content/zotero/xpcom/data/items.js
@@ -50,6 +50,7 @@ Zotero.Items = function() {
sortCreator: _getSortCreatorSQL(),
deleted: "DI.itemID IS NOT NULL AS deleted",
+ inPublications: "PI.itemID IS NOT NULL AS inPublications",
numNotes: "(SELECT COUNT(*) FROM itemNotes INo "
+ "WHERE parentItemID=O.itemID AND "
@@ -99,6 +100,7 @@ Zotero.Items = function() {
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
+ "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
+ + "LEFT JOIN publicationsItems PI ON (O.itemID=PI.itemID) "
+ "LEFT JOIN charsets CS ON (IA.charsetID=CS.charsetID)";
this._relationsTable = "itemRelations";
@@ -976,6 +978,107 @@ Zotero.Items = function() {
}
+ this.addToPublications = function (items, options = {}) {
+ if (!items.length) return;
+
+ return Zotero.DB.executeTransaction(function* () {
+ var timestamp = Zotero.DB.transactionTimestamp;
+
+ var allItems = [...items];
+
+ if (options.license) {
+ for (let item of items) {
+ if (!options.keepRights || !item.getField('rights')) {
+ item.setField('rights', options.licenseName);
+ }
+ }
+ }
+
+ if (options.childNotes) {
+ for (let item of items) {
+ item.getNotes().forEach(id => allItems.push(Zotero.Items.get(id)));
+ }
+ }
+
+ if (options.childFileAttachments || options.childLinks) {
+ for (let item of items) {
+ item.getAttachments().forEach(id => {
+ var attachment = Zotero.Items.get(id);
+ var linkMode = attachment.attachmentLinkMode;
+
+ if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
+ Zotero.debug("Skipping child linked file attachment on drag");
+ return;
+ }
+ if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ if (!options.childLinks) {
+ Zotero.debug("Skipping child link attachment on drag");
+ return;
+ }
+ }
+ else if (!options.childFileAttachments) {
+ Zotero.debug("Skipping child file attachment on drag");
+ return;
+ }
+ allItems.push(attachment);
+ });
+ }
+ }
+
+ yield Zotero.Utilities.Internal.forEachChunkAsync(allItems, 250, Zotero.Promise.coroutine(function* (chunk) {
+ for (let item of chunk) {
+ item.setPublications(true);
+ item.synced = false;
+ }
+ let ids = chunk.map(item => item.id);
+ yield Zotero.DB.queryAsync(
+ `UPDATE items SET synced=0, clientDateModified=? WHERE itemID IN (${ids.join(", ")})`,
+ timestamp
+ );
+ yield Zotero.DB.queryAsync(
+ `INSERT OR IGNORE INTO publicationsItems VALUES (${ids.join("), (")})`
+ );
+ }.bind(this)));
+ Zotero.Notifier.queue('modify', 'item', allItems.map(item => item.id));
+ }.bind(this));
+ };
+
+
+ this.removeFromPublications = function (items) {
+ return Zotero.DB.executeTransaction(function* () {
+ let allItems = [];
+ for (let item of items) {
+ if (!item.inPublications) {
+ throw new Error(`Item ${item.libraryKey} is not in My Publications`);
+ }
+
+ // Remove all child items too
+ if (item.isRegularItem()) {
+ allItems.push(...this.get(item.getNotes(true).concat(item.getAttachments(true))));
+ }
+
+ allItems.push(item);
+ }
+
+ allItems.forEach(item => {
+ item.setPublications(false);
+ item.synced = false;
+ });
+
+ var timestamp = Zotero.DB.transactionTimestamp;
+ yield Zotero.Utilities.Internal.forEachChunkAsync(allItems, 250, Zotero.Promise.coroutine(function* (chunk) {
+ let idStr = chunk.map(item => item.id).join(", ");
+ yield Zotero.DB.queryAsync(
+ `UPDATE items SET synced=0, clientDateModified=? WHERE itemID IN (${idStr})`,
+ timestamp
+ );
+ yield Zotero.DB.queryAsync(`DELETE FROM publicationsItems WHERE itemID IN (${idStr})`);
+ }.bind(this)));
+ Zotero.Notifier.queue('modify', 'item', items.map(item => item.id));
+ }.bind(this));
+ };
+
+
/**
* Purge unused data values
*/
diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js
index 39cc8ea1c2..e51d5bceb9 100644
--- a/chrome/content/zotero/xpcom/data/libraries.js
+++ b/chrome/content/zotero/xpcom/data/libraries.js
@@ -40,16 +40,6 @@ Zotero.Libraries = new function () {
}
})
- let _publicationsLibraryID;
- Zotero.defineProperty(this, 'publicationsLibraryID', {
- get: function() {
- if (_publicationsLibraryID === undefined) {
- throw new Error("Library data not yet loaded");
- }
- return _publicationsLibraryID;
- }
- });
-
/**
* Manage cache
*/
@@ -105,7 +95,6 @@ Zotero.Libraries = new function () {
let library;
switch (row._libraryType) {
case 'user':
- case 'publications':
library = new Zotero.Library();
library._loadDataFromRow(row); // Does not call save()
break;
@@ -116,9 +105,6 @@ Zotero.Libraries = new function () {
if (library.libraryType == 'user') {
_userLibraryID = library.libraryID;
}
- else if (library.libraryType == 'publications') {
- _publicationsLibraryID = library.libraryID;
- }
this._addToCache(newCaches.library, library);
}
@@ -169,12 +155,10 @@ Zotero.Libraries = new function () {
if (!this._cache) throw new Error("Zotero.Libraries cache is not initialized");
var libraries = Object.keys(this._cache).map(v => Zotero.Libraries.get(parseInt(v)));
var collation = Zotero.getLocaleCollation();
- // Sort My Library, My Publications, then others by name
+ // Sort My Library, then others by name
libraries.sort(function (a, b) {
if (a.libraryID == _userLibraryID) return -1;
if (b.libraryID == _userLibraryID) return 1;
- if (a.libraryID == _publicationsLibraryID) return -1;
- if (b.libraryID == _publicationsLibraryID) return 1;
return collation.compareString(1, a.name, b.name);
}.bind(this))
return libraries;
diff --git a/chrome/content/zotero/xpcom/data/library.js b/chrome/content/zotero/xpcom/data/library.js
index 0f16b3b18b..d0b0b21e69 100644
--- a/chrome/content/zotero/xpcom/data/library.js
+++ b/chrome/content/zotero/xpcom/data/library.js
@@ -103,12 +103,12 @@ Zotero.defineProperty(Zotero.Library.prototype, '_childObjectTypes', {
// Valid library types
Zotero.defineProperty(Zotero.Library.prototype, 'libraryTypes', {
- value: Object.freeze(['user', 'publications'])
+ value: Object.freeze(['user'])
});
// Immutable libraries
Zotero.defineProperty(Zotero.Library.prototype, 'fixedLibraries', {
- value: Object.freeze(['user', 'publications'])
+ value: Object.freeze(['user'])
});
Zotero.defineProperty(Zotero.Library.prototype, 'libraryID', {
@@ -136,7 +136,6 @@ Zotero.defineProperty(Zotero.Library.prototype, 'libraryTypeID', {
get: function () {
switch (this._libraryType) {
case 'user':
- case 'publications':
return Zotero.Users.getCurrentUserID();
case 'group':
@@ -170,10 +169,6 @@ Zotero.defineProperty(Zotero.Library.prototype, 'name', {
return Zotero.getString('pane.collections.library');
}
- if (this._libraryType == 'publications') {
- return Zotero.getString('pane.collections.publications');
- }
-
throw new Error('Unhandled library type "' + this._libraryType + '"');
}
});
@@ -254,7 +249,7 @@ Zotero.Library.prototype._set = function(prop, val) {
case '_libraryEditable':
case '_libraryFilesEditable':
- if (['user', 'publications'].indexOf(this._libraryType) != -1) {
+ if (['user'].indexOf(this._libraryType) != -1) {
throw new Error('Cannot change ' + prop + ' for ' + this._libraryType + ' library');
}
val = !!val;
@@ -303,7 +298,7 @@ Zotero.Library.prototype._set = function(prop, val) {
break;
case '_libraryArchived':
- if (['user', 'publications', 'feeds'].indexOf(this._libraryType) != -1) {
+ if (['user', 'feeds'].indexOf(this._libraryType) != -1) {
throw new Error('Cannot change ' + prop + ' for ' + this._libraryType + ' library');
}
if (val && this._libraryEditable) {
diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js
index a0b5ed35c0..8e413aa4c1 100644
--- a/chrome/content/zotero/xpcom/data/search.js
+++ b/chrome/content/zotero/xpcom/data/search.js
@@ -989,6 +989,10 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
var unfiled = condition.operator == 'true';
continue;
+ case 'publications':
+ var publications = condition.operator == 'true';
+ continue;
+
// Search subcollections
case 'recursive':
var recursive = condition.operator == 'true';
@@ -1045,6 +1049,10 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
+ ")";
}
+ if (publications) {
+ sql += " AND (itemID IN (SELECT itemID FROM publicationsItems))";
+ }
+
// Limit to library search belongs to
//
// This is equivalent to adding libraryID as a search condition,
diff --git a/chrome/content/zotero/xpcom/data/searchConditions.js b/chrome/content/zotero/xpcom/data/searchConditions.js
index 41394d3de7..6c4905f380 100644
--- a/chrome/content/zotero/xpcom/data/searchConditions.js
+++ b/chrome/content/zotero/xpcom/data/searchConditions.js
@@ -99,6 +99,14 @@ Zotero.SearchConditions = new function(){
}
},
+ {
+ name: 'publications',
+ operators: {
+ true: true,
+ false: true
+ }
+ },
+
{
name: 'includeParentsAndChildren',
operators: {
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
index 75855e5381..ba60032a71 100644
--- a/chrome/content/zotero/xpcom/itemTreeView.js
+++ b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -439,6 +439,11 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
this.selection.selectEventsSuppressed = false;
}
+ // Clear My Publications intro text on a refresh with items
+ if (this.collectionTreeRow.isPublications() && this.rowCount) {
+ this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
+ }
+
yield this.runListeners('refresh');
setTimeout(function () {
@@ -557,12 +562,6 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
refreshed = true;
}
}
- else if (type == 'publications') {
- if (collectionTreeRow.isPublications()) {
- yield this.refresh();
- refreshed = true;
- }
- }
// If refreshing a single item, clear caches and then unselect and reselect row
else if (savedSelection.length == 1 && savedSelection[0] == ids[0]) {
let row = this._rowMap[ids[0]];
@@ -667,9 +666,10 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
delete this._cellTextCache[id];
}
- // If trash or saved search, just re-run search
- if (collectionTreeRow.isTrash() || collectionTreeRow.isSearch())
- {
+ // If saved search, publications, or trash, just re-run search
+ if (collectionTreeRow.isSearch()
+ || collectionTreeRow.isPublications()
+ || collectionTreeRow.isTrash()) {
yield this.refresh();
refreshed = true;
madeChanges = true;
@@ -789,7 +789,10 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
let items = Zotero.Items.get(ids);
// In some modes, just re-run search
- if (collectionTreeRow.isSearch() || collectionTreeRow.isTrash() || collectionTreeRow.isUnfiled()) {
+ if (collectionTreeRow.isSearch()
+ || collectionTreeRow.isPublications()
+ || collectionTreeRow.isTrash()
+ || collectionTreeRow.isUnfiled()) {
yield this.refresh();
refreshed = true;
madeChanges = true;
@@ -1887,7 +1890,7 @@ Zotero.ItemTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(functio
if (collectionTreeRow.isBucket()) {
collectionTreeRow.ref.deleteItems(ids);
}
- else if (collectionTreeRow.isTrash() || collectionTreeRow.isPublications()) {
+ if (collectionTreeRow.isTrash()) {
yield Zotero.Items.erase(ids);
}
else if (collectionTreeRow.isLibrary(true) || force) {
@@ -1898,6 +1901,10 @@ Zotero.ItemTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(functio
yield collectionTreeRow.ref.removeItems(ids);
});
}
+ else if (collectionTreeRow.isPublications()) {
+ yield Zotero.Items.removeFromPublications(ids.map(id => Zotero.Items.get(id)));
+ }
+
//this._treebox.endUpdateBatch();
});
diff --git a/chrome/content/zotero/xpcom/notifier.js b/chrome/content/zotero/xpcom/notifier.js
index 93691b6ef1..42c2473aee 100644
--- a/chrome/content/zotero/xpcom/notifier.js
+++ b/chrome/content/zotero/xpcom/notifier.js
@@ -29,7 +29,7 @@ Zotero.Notifier = new function(){
var _observers = {};
var _types = [
'collection', 'search', 'share', 'share-items', 'item', 'file',
- 'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'publications',
+ 'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key'
];
var _inTransaction;
diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js
index 791762f55e..0a8d743191 100644
--- a/chrome/content/zotero/xpcom/schema.js
+++ b/chrome/content/zotero/xpcom/schema.js
@@ -34,7 +34,7 @@ Zotero.Schema = new function(){
var _dbVersions = [];
var _schemaVersions = [];
// Update when adding _updateCompatibility() line to schema update step
- var _maxCompatibility = 4;
+ var _maxCompatibility = 5;
var _repositoryTimer;
var _remoteUpdateInProgress = false, _localUpdateInProgress = false;
@@ -1356,8 +1356,7 @@ Zotero.Schema = new function(){
var sql = "INSERT INTO libraries (libraryID, type, editable, filesEditable) "
+ "VALUES "
- + "(?, 'user', 1, 1), "
- + "(4, 'publications', 1, 1)"
+ + "(?, 'user', 1, 1)";
yield Zotero.DB.queryAsync(sql, userLibraryID);
/*if (!Zotero.Schema.skipDefaultData) {
@@ -2370,6 +2369,28 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("UPDATE itemRelations SET object='http://zotero.org/users/' || ? || SUBSTR(object, 39) WHERE object LIKE ?", [userID, 'http://zotero.org/users/local/%']);
}
}
+
+ else if (i == 93) {
+ yield _updateCompatibility(5);
+ yield Zotero.DB.queryAsync("CREATE TABLE publicationsItems (\n itemID INTEGER PRIMARY KEY\n);");
+ yield Zotero.DB.queryAsync("INSERT INTO publicationsItems SELECT itemID FROM items WHERE libraryID=4");
+ yield Zotero.DB.queryAsync("UPDATE OR IGNORE items SET libraryID=1, synced=0 WHERE libraryID=4");
+ yield Zotero.DB.queryAsync("DELETE FROM itemRelations WHERE object LIKE ? AND object LIKE ?", ['http://zotero.org/users/%', '%/publications/items%']);
+ yield Zotero.DB.queryAsync("DELETE FROM libraries WHERE libraryID=4");
+
+ let rows = yield Zotero.DB.queryAsync("SELECT itemID, data FROM syncCache JOIN items USING (libraryID, key, version) WHERE syncObjectTypeID=3");
+ let ids = [];
+ for (let row of rows) {
+ let json = JSON.parse(row.data);
+ if (json.data && json.data.inPublications) {
+ ids.push(row.itemID);
+ }
+ }
+ if (ids.length) {
+ yield Zotero.DB.queryAsync("INSERT INTO publicationsItems (itemID) VALUES "
+ + ids.map(id => `(${id})`).join(', '));
+ }
+ }
}
yield _updateDBVersion('userdata', toVersion);
diff --git a/chrome/content/zotero/xpcom/sync/syncRunner.js b/chrome/content/zotero/xpcom/sync/syncRunner.js
index 8ff5bf3593..96ed573052 100644
--- a/chrome/content/zotero/xpcom/sync/syncRunner.js
+++ b/chrome/content/zotero/xpcom/sync/syncRunner.js
@@ -319,7 +319,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
if (syncAllLibraries) {
if (access.user && access.user.library) {
- libraries = [Zotero.Libraries.userLibraryID, Zotero.Libraries.publicationsLibraryID];
+ libraries = [Zotero.Libraries.userLibraryID];
// If syncing all libraries, remove skipped libraries
libraries = Zotero.Utilities.arrayDiff(
libraries, Zotero.Sync.Data.Local.getSkippedLibraries()
@@ -330,7 +330,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
// Check access to specified libraries
for (let libraryID of libraries) {
let type = Zotero.Libraries.get(libraryID).libraryType;
- if (type == 'user' || type == 'publications') {
+ if (type == 'user') {
if (!access.user || !access.user.library) {
// TODO: Alert
throw new Error("Key does not have access to library " + libraryID);
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 68db36ba01..21702afaf8 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -546,19 +546,23 @@ var ZoteroPane = new function()
*/
function setHighlightedRowsCallback() {
var itemIDs = ZoteroPane_Local.getSelectedItems(true);
- if (itemIDs && itemIDs.length) {
- Zotero.Promise.coroutine(function* () {
- var collectionIDs = yield Zotero.Collections.getCollectionsContainingItems(itemIDs, true);
- var ids = collectionIDs.map(id => "C" + id);
- Zotero.debug(Zotero.Items.get(itemIDs).some(item => !item.publication));
- if (!Zotero.Items.get(itemIDs).some(item => !item.publication)) {
- ids.push("P");
- }
- if (ids.length) {
- ZoteroPane_Local.collectionsView.setHighlightedRows(ids);
- }
- })();
- }
+ // If no items or an unreasonable number, don't try
+ if (!itemIDs || !itemIDs.length || itemIDs.length > 100) return;
+
+ Zotero.Promise.coroutine(function* () {
+ var collectionIDs = yield Zotero.Collections.getCollectionsContainingItems(itemIDs, true);
+ var ids = collectionIDs.map(id => "C" + id);
+ var userLibraryID = Zotero.Libraries.userLibraryID;
+ var allInPublications = Zotero.Items.get(itemIDs).every((item) => {
+ return item.libraryID == userLibraryID && item.inPublications;
+ })
+ if (allInPublications) {
+ ids.push("P" + Zotero.Libraries.userLibraryID);
+ }
+ if (ids.length) {
+ ZoteroPane_Local.collectionsView.setHighlightedRows(ids);
+ }
+ })();
}
@@ -1233,6 +1237,9 @@ var ZoteroPane = new function()
this._updateToolbarIconsForRow(collectionTreeRow);
this.itemsView = new Zotero.ItemTreeView(collectionTreeRow);
+ if (collectionTreeRow.isPublications()) {
+ this.itemsView.expandAll = true;
+ }
this.itemsView.onError = function () {
// Don't reload last folder, in case that's the problem
Zotero.Prefs.clear('lastViewedFolder');
@@ -1391,10 +1398,15 @@ var ZoteroPane = new function()
return false;
}
+ var selectedItems = this.itemsView.getSelectedItems();
+
+ // Display buttons at top of item pane depending on context. This needs to run even if the
+ // selection hasn't changed, because the selected items might have been modified.
+ this.updateItemPaneButtons(selectedItems);
+
// Check if selection has actually changed. The onselect event that calls this
// can be called in various situations where the selection didn't actually change,
// such as whenever selectEventsSuppressed is set to false.
- var selectedItems = this.itemsView.getSelectedItems();
var ids = selectedItems.map(item => item.id);
ids.sort();
if (ids.length && Zotero.Utilities.arrayEquals(_lastSelectedItems, ids)) {
@@ -1402,20 +1414,6 @@ var ZoteroPane = new function()
}
_lastSelectedItems = ids;
- // Display restore/delete buttons depending on context
- if (this.itemsView.selection.count) {
- document.getElementById('zotero-item-pane-top-buttons-trash').hidden
- = !this.getCollectionTreeRow().isTrash()
- || _nonDeletedItemsSelected(this.itemsView);
-
- document.getElementById('zotero-item-pane-top-buttons-feed').hidden
- = !this.getCollectionTreeRow().isFeed()
- }
- else {
- document.getElementById('zotero-item-pane-top-buttons-trash').hidden = true;
- document.getElementById('zotero-item-pane-top-buttons-feed').hidden = true;
- }
-
var tabs = document.getElementById('zotero-view-tabbox');
// save note when switching from a note
@@ -1595,33 +1593,47 @@ var ZoteroPane = new function()
/**
- * Check if any selected items in the passed (trash) treeview are not deleted
+ * Display buttons at top of item pane depending on context
*
- * @param {nsITreeView}
- * @return {Boolean}
+ * @param {Zotero.Item[]}
*/
- function _nonDeletedItemsSelected(itemsView) {
- var start = {};
- var end = {};
- for (var i=0, len=itemsView.selection.getRangeCount(); i x.hidden = true);
+ return;
}
- return false;
- }
+
+ // My Publications buttons
+ let isPublications = this.getCollectionTreeRow().isPublications();
+ let myPublicationsButtons = document.getElementById('zotero-item-pane-top-buttons-my-publications');
+ let regularItemsSelected = selectedItems.some(item => item.isRegularItem());
+ let myPublicationsShown = isPublications && !regularItemsSelected;
+ myPublicationsButtons.hidden = !myPublicationsShown;
+ if (myPublicationsShown) {
+ let button = myPublicationsButtons.firstChild;
+ let hiddenItemsSelected = selectedItems.some(item => !item.inPublications);
+ let str, onclick;
+ if (hiddenItemsSelected) {
+ str = 'showInMyPublications';
+ onclick = () => Zotero.Items.addToPublications(selectedItems);
+ }
+ else {
+ str = 'hideFromMyPublications';
+ onclick = () => Zotero.Items.removeFromPublications(selectedItems);
+ }
+ button.label = Zotero.getString('pane.item.' + str);
+ button.onclick = onclick;
+ }
+
+ // Trash button
+ let nonDeletedItemsSelected = selectedItems.some(item => !item.deleted);
+ document.getElementById('zotero-item-pane-top-buttons-trash').hidden
+ = !this.getCollectionTreeRow().isTrash() || nonDeletedItemsSelected;
+
+ // Feed buttons
+ document.getElementById('zotero-item-pane-top-buttons-feed').hidden
+ = !this.getCollectionTreeRow().isFeed()
+ };
/**
@@ -1805,7 +1817,13 @@ var ZoteroPane = new function()
};
if (collectionTreeRow.isPublications()) {
- var prompt = toDelete;
+ let toRemoveFromPublications = {
+ title: Zotero.getString('pane.items.removeFromPublications.title'),
+ text: Zotero.getString(
+ 'pane.items.removeFromPublications' + (this.itemsView.selection.count > 1 ? '.multiple' : '')
+ )
+ };
+ var prompt = force ? toTrash : toRemoveFromPublications;
}
else if (collectionTreeRow.isLibrary(true)) {
// In library, don't prompt if meta key was pressed
@@ -2558,8 +2576,6 @@ var ZoteroPane = new function()
}
else if (collectionTreeRow.isPublications()) {
show = [
- 'sync',
- 'sep1',
'exportFile'
];
}
@@ -2647,7 +2663,7 @@ var ZoteroPane = new function()
'sep2',
'toggleRead',
'duplicateItem',
- 'deleteItem',
+ 'removeItems',
'restoreToLibrary',
'moveToTrash',
'deleteFromLibrary',
@@ -2688,9 +2704,6 @@ var ZoteroPane = new function()
show.push(m.deleteFromLibrary);
show.push(m.restoreToLibrary);
}
- else if (collectionTreeRow.isPublications()) {
- show.push(m.deleteFromLibrary);
- }
else if (!collectionTreeRow.isFeed()) {
show.push(m.moveToTrash);
}
@@ -2852,7 +2865,7 @@ var ZoteroPane = new function()
menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsRead'));
}
}
- else {
+ else if (!collectionTreeRow.isPublications()) {
show.push(m.duplicateItem);
}
}
@@ -2877,14 +2890,15 @@ var ZoteroPane = new function()
show.push(m.showInLibrary, m.sep1);
}
- disable.push(m.showInLibrary, m.duplicateItem, m.deleteItem,
+ disable.push(m.showInLibrary, m.duplicateItem, m.removeItems,
m.moveToTrash, m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport);
}
if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeed()) {
for (let i in m) {
- // Still allow export/bib/report/read for non-editable views
+ // Still allow some options for non-editable views
switch (i) {
+ case 'showInLibrary':
case 'exportItems':
case 'createBib':
case 'loadReport':
@@ -2901,7 +2915,8 @@ var ZoteroPane = new function()
else if (collectionTreeRow.isPublications()) {
switch (i) {
case 'addNote':
- case 'deleteFromLibrary':
+ case 'removeItems':
+ case 'moveToTrash':
continue;
}
}
@@ -2910,10 +2925,13 @@ var ZoteroPane = new function()
}
// Remove from collection
- if (collectionTreeRow.isCollection() && !(item && !item.isTopLevelItem()))
- {
- menu.childNodes[m.deleteItem].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple));
- show.push(m.deleteItem);
+ if (collectionTreeRow.isCollection() && (!item || item.isTopLevelItem())) {
+ menu.childNodes[m.removeItems].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple));
+ show.push(m.removeItems);
+ }
+ else if (collectionTreeRow.isPublications()) {
+ menu.childNodes[m.removeItems].setAttribute('label', Zotero.getString('pane.items.menu.removeFromPublications' + multiple));
+ show.push(m.removeItems);
}
// Set labels, plural if necessary
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
index 2bfd461c94..99a6759ab2 100644
--- a/chrome/content/zotero/zoteroPane.xul
+++ b/chrome/content/zotero/zoteroPane.xul
@@ -295,7 +295,7 @@
-
+
diff --git a/chrome/locale/en-US/zotero/publications.dtd b/chrome/locale/en-US/zotero/publications.dtd
index 11844e6e68..721d033d27 100644
--- a/chrome/locale/en-US/zotero/publications.dtd
+++ b/chrome/locale/en-US/zotero/publications.dtd
@@ -3,6 +3,7 @@
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index c3452564b0..855709c9aa 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -255,8 +255,13 @@ pane.items.delete.multiple = Are you sure you want to delete the selected items
pane.items.remove.title = Remove from Collection
pane.items.remove = Are you sure you want to remove the selected item from this collection?
pane.items.remove.multiple = Are you sure you want to remove the selected items from this collection?
+pane.items.removeFromPublications.title = Remove from My Publications
+pane.items.removeFromPublications = Are you sure you want to remove the selected item from My Publications?
+pane.items.removeFromPublications.multiple = Are you sure you want to remove the selected items from My Publications?
pane.items.menu.remove = Remove Item from Collection…
pane.items.menu.remove.multiple = Remove Items from Collection…
+pane.items.menu.removeFromPublications = Remove Item from My Publications…
+pane.items.menu.removeFromPublications.multiple = Remove Items from My Publications…
pane.items.menu.moveToTrash = Move Item to Trash…
pane.items.menu.moveToTrash.multiple = Move Items to Trash…
pane.items.menu.delete = Delete Item…
@@ -301,6 +306,8 @@ pane.item.duplicates.onlySameItemType = Merged items must all be of the same it
pane.item.markAsRead = Mark As Read
pane.item.markAsUnread = Mark As Unread
pane.item.addTo = Add to “%S”
+pane.item.showInMyPublications = Show in My Publications
+pane.item.hideFromMyPublications = Hide from My Publications
pane.item.changeType.title = Change Item Type
pane.item.changeType.text = Are you sure you want to change the item type?\n\nThe following fields will be lost:
pane.item.defaultFirstName = first
@@ -536,7 +543,7 @@ save.link = Saving Link…
save.link.error = An error occurred while saving this link.
save.error.cannotMakeChangesToCollection = You cannot make changes to the currently selected collection.
save.error.cannotAddFilesToCollection = You cannot add files to the currently selected collection.
-save.error.cannotAddToMyPublications = You cannot save items directly to My Publications. To add items to My Publications, drag them from another library.
+save.error.cannotAddToMyPublications = You cannot save items directly to My Publications. To add items, drag them from elsewhere in your library.
save.error.cannotAddToFeed = You cannot save items to feeds.
ingester.saveToZotero = Save to Zotero
@@ -1096,7 +1103,7 @@ styles.editor.output.singleCitation = Single Citation (with position "first")
styles.preview.instructions = Select one or more items in Zotero and click the "Refresh" button to see how these items are rendered by the installed CSL citation styles.
publications.intro.text1 = My Publications allows you to create a list of your own work and share it on your profile page on %S. You can add notes about each item and even share PDFs or other files under a license you specify.
-publications.intro.text2 = To add items, drag them from another library. You’ll be able to choose whether to include attached notes and files.
+publications.intro.text2 = To add items, drag them from elsewhere in your library. You’ll be able to choose whether to include attached notes and files.
publications.intro.text3 = Only add work you yourself have created, and only include files if you have the rights to distribute them publicly and wish to do so.
publications.intro.authorship = I created this work.
publications.intro.authorship.files = I created this work and have the rights to distribute included files.
diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css
index e426725392..f8f6a20700 100644
--- a/chrome/skin/default/zotero/overlay.css
+++ b/chrome/skin/default/zotero/overlay.css
@@ -421,8 +421,7 @@
list-style-image: url('chrome://zotero/skin/arrow_refresh.png');
}
-.zotero-menuitem-delete-collection
-{
+.zotero-menuitem-delete-collection, .zotero-menuitem-remove-items {
list-style-image: url('chrome://zotero/skin/toolbar-collection-delete.png');
}
diff --git a/resource/schema/userdata.sql b/resource/schema/userdata.sql
index 6d8d0ccbe8..54d1bd03ff 100644
--- a/resource/schema/userdata.sql
+++ b/resource/schema/userdata.sql
@@ -1,4 +1,4 @@
--- 92
+-- 93
-- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA
@@ -282,6 +282,10 @@ CREATE TABLE groupItems (
FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID) ON DELETE SET NULL
);
+CREATE TABLE publicationsItems (
+ itemID INTEGER PRIMARY KEY
+);
+
CREATE TABLE fulltextItems (
itemID INTEGER PRIMARY KEY,
indexedPages INT,
diff --git a/test/tests/collectionTreeViewTest.js b/test/tests/collectionTreeViewTest.js
index e83b999dab..0a6814889a 100644
--- a/test/tests/collectionTreeViewTest.js
+++ b/test/tests/collectionTreeViewTest.js
@@ -608,6 +608,157 @@ describe("Zotero.CollectionTreeView", function() {
assert.equal(treeRow.ref.id, item.id);
});
+ describe("My Publications", function () {
+ it("should add an item to My Publications", function* () {
+ var item = yield createDataObject('item', false, { skipSelect: true });
+ var libraryID = item.libraryID;
+
+ var stub = sinon.stub(zp, "showPublicationsWizard")
+ .returns({
+ includeNotes: false,
+ includeFiles: false,
+ keepRights: true
+ });
+
+ // Add observer to wait for item modification
+ var deferred = Zotero.Promise.defer();
+ var observerID = Zotero.Notifier.registerObserver({
+ notify: function (event, type, ids, extraData) {
+ if (type == 'item' && event == 'modify' && ids[0] == item.id) {
+ setTimeout(function () {
+ deferred.resolve();
+ });
+ }
+ }
+ }, 'item', 'test');
+
+ yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
+
+ Zotero.Notifier.unregisterObserver(observerID);
+ stub.restore();
+
+ // Select publications and check for item
+ yield cv.selectByID("P" + libraryID);
+ yield waitForItemsLoad(win);
+ var itemsView = win.ZoteroPane.itemsView
+ assert.equal(itemsView.rowCount, 1);
+ var treeRow = itemsView.getRow(0);
+ assert.equal(treeRow.ref.id, item.id);
+ });
+
+ it("should add an item with a file attachment to My Publications", function* () {
+ var item = yield createDataObject('item', false, { skipSelect: true });
+ var attachment = yield importFileAttachment('test.png', { parentItemID: item.id });
+ var libraryID = item.libraryID;
+
+ var stub = sinon.stub(zp, "showPublicationsWizard")
+ .returns({
+ includeNotes: false,
+ includeFiles: true,
+ keepRights: true
+ });
+
+ // Add observer to wait for modify
+ var deferred = Zotero.Promise.defer();
+ var observerID = Zotero.Notifier.registerObserver({
+ notify: function (event, type, ids, extraData) {
+ if (type == 'item' && event == 'modify' && ids[0] == item.id) {
+ setTimeout(function () {
+ deferred.resolve();
+ });
+ }
+ }
+ }, 'item', 'test');
+
+ yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
+
+ Zotero.Notifier.unregisterObserver(observerID);
+ stub.restore();
+
+ assert.isTrue(item.inPublications);
+ // File attachment should be in My Publications
+ assert.isTrue(attachment.inPublications);
+ });
+
+ it("should add an item with a linked URL attachment to My Publications", function* () {
+ var item = yield createDataObject('item', false, { skipSelect: true });
+ var attachment = yield Zotero.Attachments.linkFromURL({
+ parentItemID: item.id,
+ title: 'Test',
+ url: 'http://127.0.0.1/',
+ contentType: 'text/html'
+ });
+ var libraryID = item.libraryID;
+
+ var stub = sinon.stub(zp, "showPublicationsWizard")
+ .returns({
+ includeNotes: false,
+ includeFiles: false,
+ keepRights: true
+ });
+
+ // Add observer to wait for modify
+ var deferred = Zotero.Promise.defer();
+ var observerID = Zotero.Notifier.registerObserver({
+ notify: function (event, type, ids, extraData) {
+ if (type == 'item' && event == 'modify' && ids[0] == item.id) {
+ setTimeout(function () {
+ deferred.resolve();
+ });
+ }
+ }
+ }, 'item', 'test');
+
+ yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
+
+ Zotero.Notifier.unregisterObserver(observerID);
+ stub.restore();
+
+ assert.isTrue(item.inPublications);
+ // Link attachment should be in My Publications
+ assert.isTrue(attachment.inPublications);
+ });
+
+ it("shouldn't add linked file attachment to My Publications", function* () {
+ var item = yield createDataObject('item', false, { skipSelect: true });
+ var attachment = yield Zotero.Attachments.linkFromFile({
+ parentItemID: item.id,
+ title: 'Test',
+ file: OS.Path.join(getTestDataDirectory().path, 'test.png'),
+ contentType: 'image/png'
+ });
+ var libraryID = item.libraryID;
+
+ var stub = sinon.stub(zp, "showPublicationsWizard")
+ .returns({
+ includeNotes: false,
+ includeFiles: false,
+ keepRights: true
+ });
+
+ // Add observer to wait for modify
+ var deferred = Zotero.Promise.defer();
+ var observerID = Zotero.Notifier.registerObserver({
+ notify: function (event, type, ids, extraData) {
+ if (type == 'item' && event == 'modify' && ids[0] == item.id) {
+ setTimeout(function () {
+ deferred.resolve();
+ });
+ }
+ }
+ }, 'item', 'test');
+
+ yield drop('item', 'P' + libraryID, [item.id], deferred.promise);
+
+ Zotero.Notifier.unregisterObserver(observerID);
+ stub.restore();
+
+ assert.isTrue(item.inPublications);
+ // Linked URL attachment shouldn't be in My Publications
+ assert.isFalse(attachment.inPublications);
+ });
+ });
+
it("should copy an item with an attachment to a group", function* () {
var group = yield createGroup();
diff --git a/test/tests/itemTest.js b/test/tests/itemTest.js
index 5d26e5fa35..f7abd99cab 100644
--- a/test/tests/itemTest.js
+++ b/test/tests/itemTest.js
@@ -336,6 +336,36 @@ describe("Zotero.Item", function () {
})
})
+ describe("#inPublications", function () {
+ it("should add item to publications table", function* () {
+ var item = yield createDataObject('item');
+ item.inPublications = true;
+ yield item.saveTx();
+ assert.ok(item.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
+ 1
+ );
+ })
+
+ it("should be set to false after save", function* () {
+ var collection = yield createDataObject('collection');
+ var item = createUnsavedDataObject('item');
+ item.inPublications = false;
+ yield item.saveTx();
+
+ item.inPublications = false;
+ yield item.saveTx();
+ assert.isFalse(item.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
+ 0
+ );
+ })
+ });
+
describe("#parentID", function () {
it("should create a child note", function* () {
var item = new Zotero.Item('book');
@@ -1168,6 +1198,25 @@ describe("Zotero.Item", function () {
assert.notProperty(json, "filename");
assert.notProperty(json, "path");
});
+
+ it("should include inPublications=true for items in My Publications", function* () {
+ var item = createUnsavedDataObject('item');
+ item.inPublications = true;
+ var json = item.toJSON();
+ assert.propertyVal(json, "inPublications", true);
+ });
+
+ it("shouldn't include inPublications for items not in My Publications in patch mode", function* () {
+ var item = createUnsavedDataObject('item');
+ var json = item.toJSON();
+ assert.notProperty(json, "inPublications");
+ });
+
+ it("should include inPublications=false for items not in My Publications in full mode", function* () {
+ var item = createUnsavedDataObject('item');
+ var json = item.toJSON({ mode: 'full' });
+ assert.property(json, "inPublications", false);
+ });
})
describe("'full' mode", function () {
diff --git a/test/tests/itemTreeViewTest.js b/test/tests/itemTreeViewTest.js
index 254835ccd0..b2471813e0 100644
--- a/test/tests/itemTreeViewTest.js
+++ b/test/tests/itemTreeViewTest.js
@@ -529,8 +529,85 @@ describe("Zotero.ItemTreeView", function() {
});
assert.isFalse(zp.itemsView.getRowIndexByID(item.id));
});
+
+ describe("My Publications", function () {
+ before(function* () {
+ var libraryID = Zotero.Libraries.userLibraryID;
+
+ var s = new Zotero.Search;
+ s.libraryID = libraryID;
+ s.addCondition('publications', 'true');
+ var ids = yield s.search();
+
+ yield Zotero.Items.erase(ids);
+
+ yield zp.collectionsView.selectByID("P" + libraryID);
+ yield waitForItemsLoad(win);
+
+ // Make sure we're showing the intro text
+ var deck = win.document.getElementById('zotero-items-pane-content');
+ assert.equal(deck.selectedIndex, 1);
+ });
+
+ it("should replace My Publications intro text with items list on item add", function* () {
+ var item = yield createDataObject('item');
+
+ yield zp.collectionsView.selectByID("P" + item.libraryID);
+ yield waitForItemsLoad(win);
+ var iv = zp.itemsView;
+
+ item.inPublications = true;
+ yield item.saveTx();
+
+ var deck = win.document.getElementById('zotero-items-pane-content');
+ assert.equal(deck.selectedIndex, 0);
+
+ assert.isNumber(iv.getRowIndexByID(item.id));
+ });
+
+ it("should add new item to My Publications items list", function* () {
+ var item1 = createUnsavedDataObject('item');
+ item1.inPublications = true;
+ yield item1.saveTx();
+
+ yield zp.collectionsView.selectByID("P" + item1.libraryID);
+ yield waitForItemsLoad(win);
+ var iv = zp.itemsView;
+
+ var deck = win.document.getElementById('zotero-items-pane-content');
+ assert.equal(deck.selectedIndex, 0);
+
+ var item2 = createUnsavedDataObject('item');
+ item2.inPublications = true;
+ yield item2.saveTx();
+
+ assert.isNumber(iv.getRowIndexByID(item2.id));
+ });
+
+ it("should add modified item to My Publications items list", function* () {
+ var item1 = createUnsavedDataObject('item');
+ item1.inPublications = true;
+ yield item1.saveTx();
+ var item2 = yield createDataObject('item');
+
+ yield zp.collectionsView.selectByID("P" + item1.libraryID);
+ yield waitForItemsLoad(win);
+ var iv = zp.itemsView;
+
+ var deck = win.document.getElementById('zotero-items-pane-content');
+ assert.equal(deck.selectedIndex, 0);
+
+ assert.isFalse(iv.getRowIndexByID(item2.id));
+
+ item2.inPublications = true;
+ yield item2.saveTx();
+
+ assert.isNumber(iv.getRowIndexByID(item2.id));
+ });
+ });
})
+
describe("#drop()", function () {
var httpd;
var port = 16213;
diff --git a/test/tests/itemsTest.js b/test/tests/itemsTest.js
index fee5c07c11..1f09fc9cb8 100644
--- a/test/tests/itemsTest.js
+++ b/test/tests/itemsTest.js
@@ -15,6 +15,144 @@ describe("Zotero.Items", function () {
})
+ describe("#addToPublications", function () {
+ it("should add an item to My Publications", function* () {
+ var item = yield createDataObject('item');
+ yield Zotero.Items.addToPublications([item]);
+ assert.isTrue(item.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
+ 1
+ );
+ });
+
+ describe("#license", function () {
+ it("should set a license if specified", function* () {
+ var item = createUnsavedDataObject('item');
+ item.setField('rights', 'Test');
+ yield item.saveTx();
+ yield Zotero.Items.addToPublications(
+ [item],
+ {
+ license: 'reserved',
+ licenseName: 'All Rights Reserved',
+ keepRights: false
+ }
+ );
+ assert.equal(item.getField('rights'), 'All Rights Reserved');
+ });
+
+ it("should keep existing Rights field if .keepRights is true", function* () {
+ var item1 = createUnsavedDataObject('item');
+ item1.setField('rights', 'Test');
+ yield item1.saveTx();
+ var item2 = yield createDataObject('item');
+ yield Zotero.Items.addToPublications(
+ [item1, item2],
+ {
+ license: 'reserved',
+ licenseName: 'All Rights Reserved',
+ keepRights: true
+ }
+ );
+ assert.equal(item1.getField('rights'), 'Test');
+ assert.equal(item2.getField('rights'), 'All Rights Reserved');
+ });
+
+ it("shouldn't set a license if not specified", function* () {
+ var item = createUnsavedDataObject('item');
+ item.setField('rights', 'Test');
+ yield item.saveTx();
+ yield Zotero.Items.addToPublications([item]);
+ assert.equal(item.getField('rights'), 'Test');
+ });
+ });
+
+ it("should add child notes if .childNotes is true", function* () {
+ var item = yield createDataObject('item');
+ var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
+ var attachment = yield Zotero.Attachments.linkFromURL({
+ url: "http://example.com",
+ parentItemID: item.id,
+ title: "Example"
+ });
+
+ yield Zotero.Items.addToPublications([item], { childNotes: true });
+ assert.isTrue(note.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", note.id)),
+ 1
+ );
+ assert.isFalse(attachment.inPublications);
+ });
+
+ it("should add child link attachments if .childLinks is true", function* () {
+ var item = yield createDataObject('item');
+ var attachment1 = yield Zotero.Attachments.linkFromURL({
+ url: "http://example.com",
+ parentItemID: item.id,
+ title: "Example"
+ });
+ var attachment2 = yield importFileAttachment('test.png', { parentItemID: item.id });
+ var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
+
+ yield Zotero.Items.addToPublications([item], { childLinks: true });
+ assert.isTrue(attachment1.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", attachment1.id)),
+ 1
+ );
+ assert.isFalse(attachment2.inPublications);
+ assert.isFalse(note.inPublications);
+ });
+
+ it("should add child file attachments if .childFileAttachments is true", function* () {
+ var item = yield createDataObject('item');
+ var attachment1 = yield importFileAttachment('test.png', { parentItemID: item.id });
+ var attachment2 = yield Zotero.Attachments.linkFromURL({
+ url: "http://example.com",
+ parentItemID: item.id,
+ title: "Example"
+ });
+ var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
+
+ yield Zotero.Items.addToPublications([item], { childFileAttachments: true });
+ assert.isTrue(attachment1.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", attachment1.id)),
+ 1
+ );
+ assert.isFalse(attachment2.inPublications);
+ assert.isFalse(note.inPublications);
+ });
+ });
+
+
+ describe("#removeFromPublications", function () {
+ it("should remove an item from My Publications", function* () {
+ var item = yield createDataObject('item');
+ item.inPublications = true;
+ yield item.saveTx();
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
+ 1
+ );
+ yield Zotero.Items.removeFromPublications([item]);
+ assert.isFalse(item.inPublications);
+ assert.equal(
+ (yield Zotero.DB.valueQueryAsync(
+ "SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
+ 0
+ );
+ });
+ });
+
+
describe("#merge()", function () {
it("should merge two items", function* () {
var item1 = yield createDataObject('item');
diff --git a/test/tests/syncLocalTest.js b/test/tests/syncLocalTest.js
index eabd65b9ca..d2b6ccc4d2 100644
--- a/test/tests/syncLocalTest.js
+++ b/test/tests/syncLocalTest.js
@@ -1406,6 +1406,43 @@ describe("Zotero.Sync.Data.Local", function() {
]
);
});
+
+ it("should automatically apply inPublications setting from remote", function () {
+ var cacheJSON = {
+ key: "AAAAAAAA",
+ version: 1234,
+ title: "Title 1",
+ dateModified: "2017-04-02 12:34:56"
+ };
+ var json1 = {
+ key: "AAAAAAAA",
+ version: 1234,
+ title: "Title 1",
+ dateModified: "2017-04-02 12:34:56"
+ };
+ var json2 = {
+ key: "AAAAAAAA",
+ version: 1235,
+ title: "Title 1",
+ inPublications: true,
+ dateModified: "2017-04-03 12:34:56"
+ };
+ var ignoreFields = ['dateAdded', 'dateModified'];
+ var result = Zotero.Sync.Data.Local._reconcileChanges(
+ 'item', cacheJSON, json1, json2, ignoreFields
+ );
+ assert.lengthOf(result.changes, 1);
+ assert.sameDeepMembers(
+ result.changes,
+ [
+ {
+ field: "inPublications",
+ op: "add",
+ value: true
+ }
+ ]
+ );
+ });
})
diff --git a/test/tests/zoteroPaneTest.js b/test/tests/zoteroPaneTest.js
index 4d3b370965..073b223a1f 100644
--- a/test/tests/zoteroPaneTest.js
+++ b/test/tests/zoteroPaneTest.js
@@ -230,6 +230,80 @@ describe("ZoteroPane", function() {
})
})
+
+ describe("#deleteSelectedItems()", function () {
+ it("should remove an item from My Publications", function* () {
+ var item = createUnsavedDataObject('item');
+ item.inPublications = true;
+ yield item.saveTx();
+
+ yield zp.collectionsView.selectByID("P" + userLibraryID);
+ yield waitForItemsLoad(win);
+ var iv = zp.itemsView;
+
+ var selected = iv.selectItem(item.id);
+ assert.ok(selected);
+
+ var tree = doc.getElementById('zotero-items-tree');
+ tree.focus();
+
+ yield Zotero.Promise.delay(1);
+
+ var promise = waitForDialog();
+ var modifyPromise = waitForItemEvent('modify');
+
+ var event = doc.createEvent("KeyboardEvent");
+ event.initKeyEvent("keypress", true, true, window, false, false, false, false, 46, 0);
+ tree.dispatchEvent(event);
+ yield promise;
+ yield modifyPromise;
+
+ assert.isFalse(item.inPublications);
+ assert.isFalse(item.deleted);
+ });
+
+ it("should move an item to trash from My Publications", function* () {
+ var item = createUnsavedDataObject('item');
+ item.inPublications = true;
+ yield item.saveTx();
+
+ yield zp.collectionsView.selectByID("P" + userLibraryID);
+ yield waitForItemsLoad(win);
+ var iv = zp.itemsView;
+
+ var selected = iv.selectItem(item.id);
+ assert.ok(selected);
+
+ var tree = doc.getElementById('zotero-items-tree');
+ tree.focus();
+
+ yield Zotero.Promise.delay(1);
+
+ var promise = waitForDialog();
+ var modifyPromise = waitForItemEvent('modify');
+
+ var event = doc.createEvent("KeyboardEvent");
+ event.initKeyEvent(
+ "keypress",
+ true,
+ true,
+ window,
+ false,
+ false,
+ !Zotero.isMac, // shift
+ Zotero.isMac, // meta
+ 46,
+ 0
+ );
+ tree.dispatchEvent(event);
+ yield promise;
+ yield modifyPromise;
+
+ assert.isTrue(item.inPublications);
+ assert.isTrue(item.deleted);
+ });
+ });
+
describe("#deleteSelectedCollection()", function () {
it("should delete collection but not descendant items by default", function* () {
var collection = yield createDataObject('collection');