Initial My Publications support

Adds a "My Publications" source after "My Library", implemented as a
separate library. Top-level items can be dragged in and removed.

(This doesn't currently work without disabling Quick Copy.)

Also:

- Make "Group Libraries" an unselectable header instead of a container,
  and don't indent group libraries
- Fix relation purging, which maybe never worked
- Pass only libraryID/key on deletes (which should speed them up)
- Fix async item cloning/copying
- Fix miscellaneous other bugs

To-do:

- Confirmation dialog on drag
- API support
This commit is contained in:
Dan Stillman 2015-03-06 12:21:33 -07:00
parent a9f010d547
commit bf36a988e4
24 changed files with 405 additions and 223 deletions

View file

@ -101,6 +101,9 @@ var ZoteroOverlay = new function()
observerService.addObserver(zoteroObserver, "browser-delayed-startup-finished", false);
// Set a flag for hi-res displays
Zotero.hiDPI = window.devicePixelRatio > 1;
// Add a listener for toolbar change events
window.addEventListener("customizationchange", onToolbarChange, false);

View file

@ -1064,7 +1064,8 @@ Zotero.Attachments = new function(){
throw ("Attachment is already in library " + libraryID);
}
var newAttachment = attachment.clone(libraryID);
attachment.loadItemData();
var newAttachment = yield attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;

View file

@ -42,8 +42,21 @@ Zotero.CollectionTreeView = function()
this.hideSources = [];
this._highlightedRows = {};
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket'], 'collectionTreeView');
this._unregisterID = Zotero.Notifier.registerObserver(
this,
[
'collection',
'search',
'publications',
'share',
'group',
'trash',
'bucket'
],
'collectionTreeView'
);
this._containerState = {};
this._publicationsRow;
this._duplicateLibraries = [];
this._unfiledLibraries = [];
this._trashNotEmpty = {};
@ -164,37 +177,34 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function*
libraryID: Zotero.Libraries.userLibraryID
};
// treeRow, level, beforeRow, startOpen
//
// Add "My Library"
//
// addRow(treeRow, level, beforeRow, startOpen)
this._addRow(newRows, new Zotero.CollectionTreeRow('library', library), 0, 1);
yield this._expandRow(newRows, 0);
// Add "My Publications"
this._addRow(newRows, new Zotero.CollectionTreeRow('separator', false));
this._addRow(newRows, new Zotero.CollectionTreeRow('publications', {
libraryID: Zotero.Libraries.publicationsLibraryID
}));
this._publicationsRow = newRows.length - 1;
// Add groups
var groups = yield Zotero.Groups.getAll();
if (groups.length) {
this._addRow(newRows, new Zotero.CollectionTreeRow('separator', false));
var header = {
var row = this._addRow(newRows, new Zotero.CollectionTreeRow('header', {
id: "group-libraries-header",
label: Zotero.getString('pane.collections.groupLibraries'),
libraryID: -1,
expand: Zotero.Promise.coroutine(function* (rows, beforeRow, groups) {
if (!groups) {
groups = yield Zotero.Groups.getAll();
}
var newRows = 0;
for (var i = 0, len = groups.length; i < len; i++) {
var row = self._addRow(
rows,
new Zotero.CollectionTreeRow('group', groups[i]),
1,
beforeRow ? beforeRow + newRows : null
);
newRows += 1 + ( yield self._expandRow(rows, row) );
}
return newRows;
})
}
var row = this._addRow(newRows, new Zotero.CollectionTreeRow('header', header));
if (this._containerState.HG) {
newRows[row][1] = true;
yield header.expand(newRows, null, groups);
libraryID: -1
}, 0));
for (let i = 0, len = groups.length; i < len; i++) {
var row = this._addRow(
newRows,
new Zotero.CollectionTreeRow('group', groups[i])
);
}
}
@ -325,7 +335,8 @@ Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function*
this.rememberSelection(savedSelection);
}
else if (action == 'modify' || action == 'refresh') {
if (type != 'bucket') {
if (type != 'bucket'
&& (type != 'publications' || this.selectedTreeRow.isPublications())) {
yield this.reload();
}
this.rememberSelection(savedSelection);
@ -392,10 +403,21 @@ Zotero.CollectionTreeView.prototype.setHighlightedRows = Zotero.Promise.coroutin
this._highlightedRows = {};
this._treebox.invalidate();
for each(var id in ids) {
yield this.expandToCollection(id);
this._highlightedRows[this._collectionRowMap[id]] = true;
this._treebox.invalidateRow(this._collectionRowMap[id]);
if (!ids) return;
for (let id of ids) {
var row = null;
if (id[0] == 'C') {
id = id.substr(1);
yield this.expandToCollection(id);
row = this._collectionRowMap[id];
}
else if (id == 'P') {
row = this._publicationsRow;
}
if (row) {
this._highlightedRows[row] = true;
this._treebox.invalidateRow(row);
}
}
});
@ -429,6 +451,8 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
var suffix = Zotero.hiDPI ? "@2x" : "";
var treeRow = this.getRow(row);
var collectionType = treeRow.type;
@ -467,6 +491,9 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
case 'collection':
case 'search':
return "chrome://zotero-platform/content/treesource-" + collectionType + ".png";
case 'publications':
return "chrome://zotero/skin/treeitem-journalArticle" + suffix + ".png";
}
return "chrome://zotero/skin/treesource-" + collectionType + ".png";
@ -475,7 +502,7 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
Zotero.CollectionTreeView.prototype.isContainer = function(row)
{
var treeRow = this.getRow(row);
return treeRow.isLibrary(true) || treeRow.isCollection() || treeRow.isHeader() || treeRow.isBucket();
return treeRow.isLibrary(true) || treeRow.isCollection() || treeRow.isPublications() || treeRow.isBucket();
}
Zotero.CollectionTreeView.prototype.isContainerOpen = function(row)
@ -492,9 +519,6 @@ Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row)
if (treeRow.isLibrary()) {
return false;
}
if (treeRow.isHeader()) {
return false;
}
if (treeRow.isBucket()) {
return true;
}
@ -559,10 +583,7 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(f
}
else {
var treeRow = this.getRow(row);
if (treeRow.type == 'header') {
count = yield treeRow.ref.expand(this._rows, row + 1);
}
else if (treeRow.isLibrary(true) || treeRow.isCollection()) {
if (treeRow.isLibrary(true) || treeRow.isCollection()) {
count = yield this._expandRow(this._rows, row, true);
}
this.rowCount += count;
@ -581,8 +602,9 @@ Zotero.CollectionTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(f
Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) {
var treeRow = this.getRow(row);
switch (treeRow.type) {
case 'separator':
return false;
case 'separator':
case 'header':
return false;
}
return true;
}
@ -741,14 +763,6 @@ Zotero.CollectionTreeView.prototype.selectLibrary = Zotero.Promise.coroutine(fun
// Find library
for (var i = 0; i < this.rowCount; i++) {
var treeRow = this.getRow(i);
// If group header is closed, open it
if (treeRow.isHeader() && treeRow.ref.id == 'group-libraries-header'
&& !this.isContainerOpen(i)) {
yield this.toggleOpenState(i);
continue;
}
if (treeRow.ref && treeRow.ref.libraryID == libraryID) {
this._treebox.ensureRowIsVisible(i);
this.selection.select(i);
@ -900,6 +914,10 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi
var isCollection = treeRow.isCollection();
var libraryID = treeRow.ref.libraryID;
if (treeRow.isPublications()) {
return false;
}
if (isGroup) {
var group = yield Zotero.Groups.getByLibraryID(libraryID);
var collections = yield group.getCollections();
@ -1259,7 +1277,6 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
var ids = data;
var items = Zotero.Items.get(ids);
var skip = true;
Zotero.debug(ids);
for each(var item in items) {
// Can only drag top-level items
if (!item.isTopLevelItem()) {
@ -1281,6 +1298,15 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
continue;
}
if (treeRow.isPublications() && treeRow.ref.libraryID != item.libraryID) {
if (item.isAttachment() || item.isNote()) {
Zotero.debug("Standalone attachments and notes cannot be added to My Publications");
return false;
}
skip = false;
continue;
}
// Cross-library drag
if (treeRow.ref.libraryID != item.libraryID) {
// Only allow cross-library drag to root library and collections
@ -1313,7 +1339,7 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
return true;
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
if (treeRow.isSearch()) {
if (treeRow.isSearch() || treeRow.isPublications()) {
return false;
}
if (dataType == 'application/x-moz-file') {
@ -1330,6 +1356,10 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
return true;
}
else if (dataType == 'zotero/collection') {
if (treeRow.isPublications()) {
return false;
}
let draggedCollectionID = data[0];
let draggedCollection = Zotero.Collections.get(draggedCollectionID);
@ -1400,16 +1430,22 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
if (linkedItem && !linkedItem.deleted) {
// For drag to root, skip if linked item exists
if (treeRow.isLibrary(true)) {
Zotero.debug("Linked item " + linkedItem.key + " already exists "
+ "in library " + treeRow.ref.libraryID);
continue;
}
// For drag to collection
else if (treeRow.isCollection()) {
// skip if linked item is already in it
if (treeRow.ref.hasItem(linkedItem.id)) {
Zotero.debug("Linked item " + linkedItem.key + " already exists "
+ "in collection");
continue;
}
// or if linked item is a child item
else if (!linkedItem.isTopLevelItem()) {
Zotero.debug("Linked item " + linkedItem.key + " already exists "
+ "as child item");
continue;
}
}
@ -1420,8 +1456,7 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
// Intra-library drag
// Make sure there's at least one item that's not already
// in this collection
// Make sure there's at least one item that's not already in this destination
if (treeRow.isCollection()) {
if (treeRow.ref.hasItem(item.id)) {
Zotero.debug("Item " + item.id + " already exists in collection");
@ -1563,7 +1598,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
// Create new clone item in target library
var newItem = item.clone(targetLibraryID, false, !Zotero.Prefs.get('groups.copyTags'));
var newItem = yield item.clone(targetLibraryID, false, !Zotero.Prefs.get('groups.copyTags'));
var newItemID = yield newItem.save();
// Record link
@ -1577,10 +1612,11 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Child notes
if (Zotero.Prefs.get('groups.copyChildNotes')) {
yield item.loadChildItems();
var noteIDs = item.getNotes();
var notes = yield Zotero.Items.getAsync(noteIDs);
for each(var note in notes) {
let newNote = note.clone(targetLibraryID);
let newNote = yield note.clone(targetLibraryID);
newNote.parentID = newItemID;
yield newNote.save()
@ -1617,11 +1653,11 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
}
Zotero.Attachments.copyAttachmentToLibrary(attachment, targetLibraryID, newItem.id);
Zotero.Attachments.copyAttachmentToLibrary(attachment, targetLibraryID, newItemID);
}
}
return newID;
return newItemID;
});
var targetLibraryID = targetTreeRow.ref.libraryID;
@ -1791,9 +1827,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Add items to target collection
if (targetCollectionID) {
var collection = yield Zotero.Collections.getAsync(targetCollectionID);
Zotero.debug('adding');
yield collection.addItems(newIDs);
Zotero.debug('added');
}
// If moving, remove items from source collection
@ -1902,24 +1936,34 @@ Zotero.CollectionTreeView.prototype.isSorted = function() { return false;
Zotero.CollectionTreeView.prototype.getRowProperties = function(row, prop) {
var props = [];
if (this._highlightedRows[row]) {
// <=Fx21
if (prop) {
var aServ = Components.classes["@mozilla.org/atom-service;1"].
getService(Components.interfaces.nsIAtomService);
prop.AppendElement(aServ.getAtom("highlighted"));
}
// Fx22+
else {
props.push("highlighted");
}
var treeRow = this.getRow(row);
if (treeRow.isHeader()) {
props.push("header");
}
else if (this._highlightedRows[row]) {
props.push("highlighted");
}
return props.join(" ");
}
Zotero.CollectionTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop) { }
Zotero.CollectionTreeView.prototype.getColumnProperties = function(col, prop) {}
Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop) {
var props = [];
var treeRow = this.getRow(row);
if (treeRow.isHeader()) {
props.push("header");
props.push("notwisty");
}
else if (treeRow.isPublications()) {
props.push("notwisty");
}
return props.join(" ");
}
Zotero.CollectionTreeView.prototype.isSeparator = function(index) {
var source = this.getRow(index);
return source.type == 'separator';
@ -1965,6 +2009,9 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
case 'search':
return 'S' + this.ref.id;
case 'publications':
return 'P';
case 'duplicates':
return 'D' + this.ref.libraryID;
@ -1990,7 +2037,7 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
Zotero.CollectionTreeRow.prototype.isLibrary = function (includeGlobal)
{
if (includeGlobal) {
return this.type == 'library' || this.type == 'group';
return this.type == 'library' || this.type == 'publications' || this.type == 'group';
}
return this.type == 'library';
}
@ -2022,6 +2069,10 @@ Zotero.CollectionTreeRow.prototype.isHeader = function () {
return this.type == 'header';
}
Zotero.CollectionTreeRow.prototype.isPublications = function() {
return this.type == 'publications';
}
Zotero.CollectionTreeRow.prototype.isGroup = function() {
return this.type == 'group';
}
@ -2059,7 +2110,7 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () {
if (this.isTrash() || this.isShare() || this.isBucket()) {
return false;
}
if (!this.isWithinGroup()) {
if (this.isPublications || !this.isWithinGroup()) {
return true;
}
var libraryID = this.ref.libraryID;
@ -2107,6 +2158,9 @@ Zotero.CollectionTreeRow.prototype.getName = function()
case 'library':
return Zotero.getString('pane.collections.library');
case 'publications':
return Zotero.getString('pane.collections.publications');
case 'trash':
return Zotero.getString('pane.collections.trash');
@ -2130,9 +2184,6 @@ Zotero.CollectionTreeRow.prototype.getItems = Zotero.Promise.coroutine(function*
case 'bucket':
return this.ref.getItems();
case 'header':
return [];
}
var ids = yield this.getSearchResults();
@ -2220,7 +2271,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
yield s.addCondition('deleted', 'true');
}
else {
throw ('Invalid search mode in Zotero.CollectionTreeRow.getSearchObject()');
throw new Error('Invalid search mode in Zotero.CollectionTreeRow.getSearchObject()');
}
}
@ -2263,9 +2314,6 @@ Zotero.CollectionTreeRow.prototype.getChildTags = Zotero.Promise.method(function
case 'bucket':
return false;
case 'header':
return false;
}
return Zotero.Tags.getAllWithinSearchResults(this.getSearchResults(true));

View file

@ -606,7 +606,10 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
return Zotero.DB.executeTransaction(function* () {
var descendents = yield this.getDescendents(false, null, true);
var items = [];
notifierData[this.id] = { old: this.toJSON() };
notifierData[this.id] = {
libraryID: this.libraryID,
key: this.key
};
var del = [];
for(var i=0, len=descendents.length; i<len; i++) {
@ -615,7 +618,10 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
collections.push(descendents[i].id);
var c = yield this.ObjectsClass.getAsync(descendents[i].id);
if (c) {
notifierData[c.id] = { old: c.toJSON() };
notifierData[c.id] = {
libraryID: c.libraryID,
key: c.key
};
}
}
// Descendent items

View file

@ -292,6 +292,7 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function
var predicate = Zotero.Relations.linkedObjectPredicate;
var uri = Zotero.URI['get' + this._ObjectType + 'URI'](this);
// Get all relations with this object as the subject or object
var links = yield Zotero.Promise.all([
Zotero.Relations.getSubject(false, predicate, uri),
Zotero.Relations.getObject(uri, predicate, false)
@ -311,7 +312,7 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function
for (let i=0; i<links.length; i++) {
let link = links[i];
if (link.indexOf(libraryObjectPrefix) == 0) {
if (link.startsWith(libraryObjectPrefix)) {
var obj = yield Zotero.URI['getURI' + this._ObjectType](link);
if (!obj) {
Zotero.debug("Referenced linked " + this._objectType + " '" + link + "' not found "
@ -599,6 +600,7 @@ Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* () {
yield this._erasePreCommit(env);
}.bind(this))
.catch(e => {
Zotero.debug(e);
return this._eraseRecoverFromFailure(env);
});

View file

@ -77,6 +77,7 @@ Zotero.Item = function(itemTypeOrID) {
this._fileExists = null;
this._deleted = null;
this._publication = null;
this._hasNote = null;
this._noteAccessTime = null;
@ -1037,28 +1038,35 @@ Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) {
return true;
}
Zotero.defineProperty(Zotero.Item.prototype, 'deleted', {
get: function() {
if (!this.id) {
return false;
// Define 'deleted' and 'publication' properties
for (let name of ['deleted', 'publication']) {
let prop = '_' + name;
Zotero.defineProperty(Zotero.Item.prototype, name, {
get: function() {
if (!this.id) {
return false;
}
if (this[prop] !== null) {
return this[prop];
}
this._requireData('primaryData');
},
set: function(val) {
val = !!val;
if (this[prop] == val) {
Zotero.debug(Zotero.Utilities.capitalize(name)
+ " state hasn't changed for item " + this.id);
return;
}
this._markFieldChange('publication', !!this[prop]);
this._changed[name] = true;
this[prop] = val;
}
if (this._deleted !== null) {
return this._deleted;
}
this._requireData('primaryData');
},
set: function(val) {
var deleted = !!val;
if (this._deleted == deleted) {
Zotero.debug("Deleted state hasn't changed for item " + this.id);
return;
}
this._markFieldChange('deleted', !!this._deleted);
this._changed.deleted = true;
this._deleted = deleted;
}
});
});
}
Zotero.Item.prototype.addRelatedItem = Zotero.Promise.coroutine(function* (itemID) {
@ -1407,7 +1415,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
// Trashed status
if (this._changed.deleted) {
if (this.deleted) {
if (this._deleted) {
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
}
else {
@ -3710,14 +3718,14 @@ Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems
* @param {Number} [libraryID] - libraryID of the new item, or the same as original if omitted
* @param {Boolean} [skipTags=false] - Skip tags
*/
Zotero.Item.prototype.clone = function(libraryID, skipTags) {
Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, skipTags) {
Zotero.debug('Cloning item ' + this.id);
if (libraryID !== undefined && libraryID !== null && typeof libraryID !== 'number') {
throw new Error("libraryID must be null or an integer");
}
this._requireData('primaryData');
yield this.loadPrimaryData();
if (libraryID === undefined || libraryID === null) {
libraryID = this.libraryID;
@ -3728,6 +3736,7 @@ Zotero.Item.prototype.clone = function(libraryID, skipTags) {
newItem.libraryID = libraryID;
newItem.setType(this.itemTypeID);
yield this.loadItemData();
var fieldIDs = this.getUsedFields();
for (let i = 0; i < fieldIDs.length; i++) {
let fieldID = fieldIDs[i];
@ -3736,9 +3745,11 @@ Zotero.Item.prototype.clone = function(libraryID, skipTags) {
// Regular item
if (this.isRegularItem()) {
yield this.loadCreators();
newItem.setCreators(newItem.getCreators());
}
else {
yield this.loadNote();
newItem.setNote(this.getNote());
if (sameLibrary) {
var parent = this.parentKey;
@ -3763,16 +3774,18 @@ Zotero.Item.prototype.clone = function(libraryID, skipTags) {
}
if (!skipTags) {
yield this.loadTags();
newItem.setTags(this.getTags());
}
if (sameLibrary) {
// DEBUG: this will add reverse-only relateds too
yield this.loadRelations();
newItem.setRelations(this.getRelations());
}
return newItem;
}
});
/**
@ -3791,7 +3804,10 @@ Zotero.Item.prototype._eraseInit = Zotero.Promise.coroutine(function* (env) {
if (!proceed) return false;
env.deletedItemNotifierData = {};
env.deletedItemNotifierData[this.id] = { old: this.toJSON() };
env.deletedItemNotifierData[this.id] = {
libraryID: this.libraryID,
key: this.key
};
return true;
});
@ -4572,9 +4588,9 @@ Zotero.Item.prototype._getRelatedItems = function () {
// Pull out object values from related-item relations, turn into items, and pull out keys
var keys = [];
for (let i=0; i<relatedItemURIs.length; i++) {
item = Zotero.URI.getURIItem(relatedItemURIs[i]);
if (item) {
keys.push(item.key);
let {libraryID, key} = Zotero.URI.getURIItemLibraryKey(relatedItemURIs[i]);
if (key) {
keys.push(key);
}
}
return keys;

View file

@ -26,6 +26,7 @@
Zotero.Libraries = new function () {
let _libraryData = {},
_userLibraryID,
_publicationsLibraryID,
_libraryDataLoaded = false;
Zotero.defineProperty(this, 'userLibraryID', {
@ -37,6 +38,15 @@ Zotero.Libraries = new function () {
}
});
Zotero.defineProperty(this, 'publicationsLibraryID', {
get: function() {
if (!_libraryDataLoaded) {
throw new Error("Library data not yet loaded");
}
return _publicationsLibraryID;
}
});
this.init = Zotero.Promise.coroutine(function* () {
// Library data
var sql = "SELECT * FROM libraries";
@ -47,6 +57,9 @@ Zotero.Libraries = new function () {
if (row.libraryType == 'user') {
_userLibraryID = row.libraryID;
}
else if (row.libraryType == 'publications') {
_publicationsLibraryID = row.libraryID;
}
}
_libraryDataLoaded = true;
});
@ -82,6 +95,10 @@ Zotero.Libraries = new function () {
switch (type) {
case 'user':
return Zotero.getString('pane.collections.library');
case 'publications':
return Zotero.getString('pane.collections.publications');
case 'group':
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
var group = Zotero.Groups.get(groupID);
@ -147,6 +164,7 @@ Zotero.Libraries = new function () {
var type = this.getType(libraryID);
switch (type) {
case 'user':
case 'publications':
return true;
case 'group':
@ -164,6 +182,7 @@ Zotero.Libraries = new function () {
var type = this.getType(libraryID);
switch (type) {
case 'user':
case 'publications':
return true;
case 'group':
@ -184,7 +203,6 @@ Zotero.Libraries = new function () {
return this.getType(libraryID) == 'group';
}
function parseDBRow(row) {
return {
type: row.libraryType,

View file

@ -51,8 +51,8 @@ Zotero.Relation.prototype.__defineSetter__('object', function (val) { this._set(
Zotero.Relation.prototype._get = function (field) {
if (this._id && !this._loaded) {
this.load();
if (!this._loaded) {
throw new Error("Data not loaded for relation " + this._id);
}
return this['_' + field];
}
@ -214,7 +214,8 @@ Zotero.Relation.prototype.erase = Zotero.Promise.coroutine(function* () {
var deleteData = {};
deleteData[this.id] = {
old: this.serialize()
libraryID: this.libraryID,
key: Zotero.Utilities.Internal.md5(this.subject + "_" + this.predicate + "_" + this.object)
}
var sql = "DELETE FROM relations WHERE ROWID=?";
@ -249,15 +250,11 @@ Zotero.Relation.prototype.serialize = function () {
var key = Zotero.Utilities.Internal.md5(this.subject + "_" + this.predicate + "_" + this.object);
var obj = {
primary: {
libraryID: this.libraryID,
key: key,
},
fields: {
subject: this.subject,
predicate: this.predicate,
object: this.object
}
libraryID: this.libraryID,
key: key,
subject: this.subject,
predicate: this.predicate,
object: this.object
};
return obj;
}

View file

@ -183,7 +183,8 @@ Zotero.Relations = function () {
var ids = yield Zotero.DB.columnQueryAsync(sql, params);
for (let i=0; i<ids.length; i++) {
let relation = yield this.get(ids[i]);
let relation = this.get(ids[i]);
yield relation.load();
yield relation.erase();
}
}.bind(this));
@ -206,7 +207,8 @@ Zotero.Relations = function () {
var ids = yield Zotero.DB.columnQueryAsync(sql, params);
for (let i=0; i<ids.length; i++) {
let relation = yield this.get(ids[i]);
let relation = this.get(ids[i]);
yield relation.load();
yield relation.erase();
}
}.bind(this));
@ -214,6 +216,8 @@ Zotero.Relations = function () {
this.purge = Zotero.Promise.coroutine(function* () {
Zotero.debug("Purging relations");
var t = new Date;
var sql = "SELECT subject FROM relations WHERE predicate != ? "
+ "UNION SELECT object FROM relations WHERE predicate != ?";
var uris = yield Zotero.DB.columnQueryAsync(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
@ -226,14 +230,15 @@ Zotero.Relations = function () {
if (uri.indexOf(prefix) == -1) {
continue;
}
if (uri.indexOf(/\/items\//) != -1 && !Zotero.URI.getURIItem(uri)) {
this.eraseByURI(uri);
if (uri.indexOf("/items/") != -1 && !Zotero.URI.getURIItemID(uri)) {
yield this.eraseByURI(uri);
}
if (uri.indexOf(/\/collections\//) != -1 && !Zotero.URI.getURICollection(uri)) {
this.eraseByURI(uri);
if (uri.indexOf("/collections/") != -1 && !Zotero.URI.getURICollectionID(uri)) {
yield this.eraseByURI(uri);
}
}
}.bind(this));
Zotero.debug("Purged relations in " + ((new Date) - t) + "ms");
}
});

View file

@ -3100,6 +3100,8 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = function(uris) {
// Next try getting URI directly
try {
// TEMP
throw new Error("getURIItem() is now async");
zoteroItem = Zotero.URI.getURIItem(uri);
if(zoteroItem) {
// Ignore items in the trash
@ -3134,6 +3136,8 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = function(uris) {
seen.push(uri);
try {
// TEMP
throw new Error("getURIItem() is now async");
zoteroItem = Zotero.URI.getURIItem(uri);
if(zoteroItem) {
// Ignore items in the trash

View file

@ -476,6 +476,11 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
yield this.refresh();
}
}
else if (type == 'publications') {
if (collectionTreeRow.isPublications()) {
yield this.refresh();
}
}
// 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._itemRowMap[ids[0]];
@ -545,7 +550,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
var rows = [];
for (var i=0, len=ids.length; i<len; i++) {
let push = false;
if (action == 'delete' && action == 'trash') {
if (action == 'delete' || action == 'trash') {
push = true;
}
else {
@ -627,8 +632,15 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
var parentItemID = this.getRow(row).ref.parentItemID;
var parentIndex = this.getParentIndex(row);
if (this.isContainer(row))
{
// Top-level item
if (this.isContainer(row)) {
// If removed from My Publications, remove row
if (collectionTreeRow.isPublications() && !item.publication) {
this._removeRow(row);
this._treebox.rowCountChanged(row, -1)
continue;
}
//yield this.toggleOpenState(row);
//yield this.toggleOpenState(row);
sort = id;
@ -1753,7 +1765,7 @@ Zotero.ItemTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(functio
if (collectionTreeRow.isBucket()) {
collectionTreeRow.ref.deleteItems(ids);
}
else if (collectionTreeRow.isTrash()) {
else if (collectionTreeRow.isTrash() || collectionTreeRow.isPublications()) {
Zotero.Items.erase(ids);
}
else if (collectionTreeRow.isLibrary(true) || force) {

View file

@ -105,6 +105,7 @@ Zotero.LibraryTreeView.prototype = {
else {
throw new Error("Invalid tree id '" + tree.id + "'");
}
if (!view.canDropCheck(row.value, Zotero.DragDrop.currentOrientation, event.dataTransfer)) {
this._setDropEffect(event, "none");
return;

View file

@ -28,7 +28,8 @@ Zotero.Notifier = new function(){
var _disabled = false;
var _types = [
'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'publications',
'bucket', 'relation'
];
var _inTransaction;
var _locked = false;
@ -50,7 +51,7 @@ Zotero.Notifier = new function(){
for (var i=0; i<types.length; i++){
if (_types.indexOf(types[i]) == -1){
throw ('Invalid type ' + types[i] + ' in registerObserver()');
throw new Error('Invalid type ' + types[i] + ' in registerObserver()');
}
}
}

View file

@ -143,7 +143,7 @@ Zotero.Report.HTML = new function () {
}
for (let i=0; i<rels.length; i++) {
let rel = rels[i];
let relItem = Zotero.URI.getURIItem(rel);
let relItem = yield Zotero.URI.getURIItem(rel);
if (relItem) {
content += '\t\t\t\t\t<li id="item_' + relItem.key + '">';
content += escapeXML(relItem.getDisplayTitle());

View file

@ -1420,6 +1420,7 @@ Zotero.Schema = new function(){
yield _updateDBVersion('compatibility', _maxCompatibility);
yield Zotero.DB.queryAsync("INSERT INTO libraries (libraryID, libraryType) VALUES (?, 'user')", userLibraryID);
yield Zotero.DB.queryAsync("INSERT INTO libraries (libraryID, libraryType) VALUES (2, 'publications')");
if (!Zotero.Schema.skipDefaultData) {
// Quick Start Guide web page item
@ -1775,6 +1776,7 @@ Zotero.Schema = new function(){
yield _updateDBVersion('compatibility', 1);
yield Zotero.DB.queryAsync("INSERT INTO libraries VALUES (1, 'user')");
yield Zotero.DB.queryAsync("INSERT INTO libraries VALUES (2, 'publications')");
let oldUserLibraryID = yield Zotero.DB.valueQueryAsync("SELECT value FROM settings WHERE setting='account' AND key='libraryID'");

View file

@ -1007,7 +1007,6 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
continue;
case 'unfiled':
this._conditions[i]['operator']
var unfiled = this._conditions[i]['operator'] == 'true';
continue;
@ -1704,7 +1703,10 @@ Zotero.Searches = function() {
search.id = id;
yield search.loadPrimaryData();
yield search.loadConditions();
notifierData[id] = { old: search.serialize() };
notifierData[id] = {
libraryID: this.libraryID,
old: search.serialize() // TODO: replace with toJSON()
};
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
yield Zotero.DB.queryAsync(sql, id);
@ -1712,7 +1714,7 @@ Zotero.Searches = function() {
var sql = "DELETE FROM savedSearches WHERE savedSearchID=?";
yield Zotero.DB.queryAsync(sql, id);
}
});
}.bind(this));
Zotero.Notifier.trigger('delete', 'search', ids, notifierData);
});
@ -1784,8 +1786,6 @@ Zotero.SearchConditions = new function(){
//
// Special conditions
//
{
name: 'deleted',
operators: {

View file

@ -301,14 +301,14 @@ Zotero.Sync.Storage.QueueManager = new function () {
}
function _reconcileConflicts(conflicts) {
var _reconcileConflicts = Zotero.Promise.coroutine(function* (conflicts) {
var objectPairs = [];
for each(var conflict in conflicts) {
var item = Zotero.Sync.Storage.getItemFromRequestName(conflict.name);
var item1 = item.clone(false, false, true);
var item1 = yield item.clone(false, false, true);
item1.setField('dateModified',
Zotero.Date.dateToSQL(new Date(conflict.localData.modTime), true));
var item2 = item.clone(false, false, true);
var item2 = yield item.clone(false, false, true);
item2.setField('dateModified',
Zotero.Date.dateToSQL(new Date(conflict.remoteData.modTime), true));
objectPairs.push([item1, item2]);
@ -342,7 +342,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
}
return io.dataOut;
}
});
function _processMergeData(data) {

View file

@ -86,10 +86,15 @@ Zotero.URI = new function () {
switch (libraryType) {
case 'user':
case 'publications':
var id = Zotero.Users.getCurrentUserID();
if (!id) {
throw new Exception("User id not available in Zotero.URI.getLibraryPath()");
}
if (libraryType == 'publications') {
return "users/" + id + "/publications";
}
break;
case 'group':
@ -171,49 +176,92 @@ Zotero.URI = new function () {
*
* @param {String} itemURI
* @param {Zotero.Item|FALSE}
* @return {Promise<Zotero.Item|FALSE>}
*/
this.getURIItem = function (itemURI) {
this.getURIItem = Zotero.Promise.method(function (itemURI) {
var {libraryID, key} = this._getURIObject(itemURI, 'item');
if (!key) return false;
return Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
});
/**
* @param {String} itemURI
* @return {Object|FALSE} - Object with 'libraryID' and 'key', or FALSE if item not found
*/
this.getURIItemLibraryKey = function (itemURI) {
return this._getURIObject(itemURI, 'item');
}
/**
* @param {String} itemURI
* @return {Integer|FALSE} - itemID of matching item, or FALSE if none
*/
this.getURIItemID = function (itemURI) {
var {libraryID, key} = this._getURIObject(itemURI, 'item');
if (!key) return false;
return Zotero.Items.getIDFromLibraryAndKey(libraryID, key);
}
/**
* Convert a collection URI into a collection
*
* @param {String} collectionURI
* @param {Zotero.Collection|FALSE}
* @return {Promise<Zotero.Collection|FALSE>}
*/
this.getURICollection = function (collectionURI) {
this.getURICollection = Zotero.Promise.method(function (collectionURI) {
var {libraryID, key} = this._getURIObject(collectionURI, 'collection');
if (!key) return false;
return Zotero.Collections.getByLibraryAndKeyAsync(libraryID, key);
});
/**
* @param {String} collectionURI
* @return {Object|FALSE} - Object with 'libraryID' and 'key', or FALSE if item not found
*/
this.getURICollectionLibraryKey = function (collectionURI) {
return this._getURIObject(collectionURI, 'collection');
}
/**
* @param {String} collectionURI
* @return {Integer|FALSE} - collectionID of matching collection, or FALSE if none
*/
this.getURICollectionID = function (collectionURI) {
var {libraryID, key} = this._getURIObject(collectionURI, 'item');
if (!key) return false;
return Zotero.Collections.getIDFromLibraryAndKey(libraryID, key);
}
/**
* Convert a library URI into a libraryID
*
* @param {String} libraryURI
* @return {Zotero.Collection|FALSE}
* @return {Integer|FALSE} - libraryID, or FALSE if no matching library
*/
this.getURILibrary = function (libraryURI) {
return this._getURIObject(libraryURI, "library");
var {libraryID} = this._getURIObject(libraryURI, "library");
return libraryID !== undefined ? libraryID : false;
}
/**
* Convert an object URI into an object (item, collection, etc.)
*
* @param {String} objectURI
* @param {"item"|"collection"|"library"} [type] The type of object to return. If the object
* is valid but not available, returns "false". Note that if type is "library", this
* this function may return null for the default library, which is distinct from false.
*
* @return {Zotero.Item|Zotero.Collection|Integer|NULL|FALSE}
* @param {String} objectURI
* @param {'library'|'collection'|'item'} - The type of URI to expect
* @return {Object|FALSE} - An object containing 'libraryID' and, if applicable, 'key',
* or FALSE if library not found
*/
this._getURIObject = function (objectURI, type) {
var Types = type[0].toUpperCase() + type.substr(1) + 's';
var types = Types.toLowerCase();
var libraryType = null;
var libraryType;
var libraryTypeID;
// If this is a local URI, compare to the local user key
if (objectURI.match(/\/users\/local\//)) {
@ -229,72 +277,82 @@ Zotero.URI = new function () {
}
}
*/
var libraryType = 'user';
var id = null;
libraryType = 'user';
libraryTypeID = null;
}
// If not found, try global URI
if (!libraryType) {
if (objectURI.indexOf(_baseURI) != 0) {
throw ("Invalid base URI '" + objectURI + "' in Zotero.URI._getURIObject()");
if (!objectURI.startsWith(_baseURI)) {
throw new Error("Invalid base URI '" + objectURI + "'");
}
objectURI = objectURI.substr(_baseURI.length);
var typeRE = /^(users|groups)\/([0-9]+)(?:\/|$)/;
var matches = objectURI.match(typeRE);
let typeRE = /^(users|groups)\/([0-9]+)(?:\/|$)/;
let matches = objectURI.match(typeRE);
if (!matches) {
throw ("Invalid library URI '" + objectURI + "' in Zotero.URI._getURIObject()");
throw new Error("Invalid library URI '" + objectURI + "'");
}
var libraryType = matches[1].substr(0, matches[1].length-1);
var id = matches[2];
libraryType = matches[1].substr(0, matches[1].length-1);
libraryTypeID = matches[2];
objectURI = objectURI.replace(typeRE, '');
}
if (libraryType == 'group') {
if (!Zotero.Groups.get(id)) {
if (libraryType == 'user' && objectURI.startsWith('publications/')) {
libraryType = 'publications';
}
if (libraryType == 'user') {
var libraryID = Zotero.Libraries.userLibraryID;
}
else if (libraryType == 'group') {
if (!Zotero.Groups.exists(libraryTypeID)) {
return false;
}
var libraryID = Zotero.Groups.getLibraryIDFromGroupID(id);
var libraryID = Zotero.Groups.getLibraryIDFromGroupID(libraryTypeID);
}
else if (libraryType == 'publications') {
var libraryID = Zotero.Libraries.publicationsLibraryID;
}
if(type === 'library') {
if (libraryType == 'user') {
if(id === null) {
if (libraryTypeID) {
if (libraryTypeID == Zotero.Users.getCurrentUserID()) {
return {
libraryID: libraryID
};
}
}
else {
var localUserURI = this.getLocalUserURI();
if (localUserURI) {
localUserURI += "/";
if (objectURI.indexOf(localUserURI) == 0) {
objectURI = objectURI.substr(localUserURI.length);
return null;
if (objectURI.startsWith(localUserURI)) {
return {
libraryID: Zotero.Libraries.userLibraryID
};
}
}
} else {
if(id == Zotero.Users.getCurrentUserID()) {
return null;
}
}
return false;
}
if (libraryType == 'group') {
return libraryID;
return {
libraryID: libraryID
};
}
} else {
// TODO: objectID-based URI?
var re = new RegExp(types + "\/([A-Z0-9]{8})");
var re = /(?:items|collections)\/([A-Z0-9]{8})/;
var matches = objectURI.match(re);
if (!matches) {
throw ("Invalid object URI '" + objectURI + "' in Zotero.URI._getURIObject()");
}
var objectKey = matches[1];
if (libraryType == 'user') {
return Zotero[Types].getByLibraryAndKey(null, objectKey);
}
if (libraryType == 'group') {
return Zotero[Types].getByLibraryAndKey(libraryID, objectKey);
}
let objectKey = matches[1];
return {
libraryID: libraryID,
key: objectKey
};
}
}
}

View file

@ -847,6 +847,10 @@ Zotero.Utilities = {
return newString;
},
"capitalize": function (str) {
return str[0].toUpperCase() + str.substr(1);
},
/**
* Replaces accented characters in a string with ASCII equivalents
*

View file

@ -2079,7 +2079,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
//Zotero.Fulltext.purgeUnusedWords();
yield Zotero.Items.purge();
// DEBUG: this might not need to be permanent
Zotero.Relations.purge();
yield Zotero.Relations.purge();
yield Zotero.CharacterSets.purge();
});
@ -2679,7 +2679,7 @@ Zotero.DragDrop = {
getDragSource: function (dataTransfer) {
if (!dataTransfer) {
Zotero.debug("Drag data not available", 2);
//Zotero.debug("Drag data not available", 2);
return false;
}

View file

@ -523,13 +523,17 @@ var ZoteroPane = new function()
function setHighlightedRowsCallback() {
var itemIDs = ZoteroPane_Local.getSelectedItems(true);
if (itemIDs && itemIDs.length) {
Zotero.Collections.getCollectionsContainingItems(itemIDs, true)
.then(function (collectionIDs) {
if (collectionIDs) {
ZoteroPane_Local.collectionsView.setHighlightedRows(collectionIDs);
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");
}
})
.done();
if (ids.length) {
ZoteroPane_Local.collectionsView.setHighlightedRows(ids);
}
})();
}
}
@ -1543,7 +1547,7 @@ var ZoteroPane = new function()
var id = yield newItem.save();
var newItem = yield Zotero.Items.getAsync(id);
item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
yield item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
yield newItem.save();
if (self.collectionsView.selectedTreeRow.isCollection() && newItem.isTopLevelItem()) {
@ -1591,7 +1595,10 @@ var ZoteroPane = new function()
)
};
if (collectionTreeRow.isLibrary(true)) {
if (collectionTreeRow.isPublications()) {
var prompt = toDelete;
}
else if (collectionTreeRow.isLibrary(true)) {
// In library, don't prompt if meta key was pressed
var prompt = (force && !fromMenu) ? false : toTrash;
}

View file

@ -163,6 +163,7 @@ pane.collections.newSavedSeach = New Saved Search
pane.collections.savedSearchName = Enter a name for this saved search:
pane.collections.rename = Rename collection:
pane.collections.library = My Library
pane.collections.publications = My Publications
pane.collections.groupLibraries = Group Libraries
pane.collections.trash = Trash
pane.collections.untitled = Untitled

View file

@ -10,6 +10,12 @@
overflow: hidden;
}
/* Why is this necessary? */
#zotero-collections-tree treechildren::-moz-tree-image,
#zotero-items-tree treechildren::-moz-tree-image {
margin-right: 5px;
}
#zotero-collections-pane
{
min-width: 150px;
@ -20,17 +26,17 @@
min-height: 5.2em;
}
#zotero-collections-tree treechildren::-moz-tree-image(primary)
{
margin-right: 5px;
#zotero-collections-tree treechildren::-moz-tree-row {
height: 1.7em;
}
#zotero-collections-tree #zotero-collections-sync-status-column {
width: 35px;
}
/*#zotero-collections-tree treechildren::-moz-tree-separator {
border: none;
}*/
#zotero-collections-tree[hidevscroll] #zotero-collections-sync-status-column {
width: 21px;
#zotero-collections-tree treechildren::-moz-tree-twisty(notwisty),
#zotero-collections-tree treechildren::-moz-tree-twisty(header) {
width: 0;
}
/* Set by setHighlightedRows() and getRowProperties() in collectionTreeView.js) */
@ -39,11 +45,6 @@
background: #FFFF99 !important;
}
/*#zotero-collections-tree treechildren::-moz-tree-row(separator)
{
height: .25em;
}*/
#zotero-pane splitter
{
border: 0;
@ -60,11 +61,6 @@
text-align: center;
}
#zotero-items-tree treechildren::-moz-tree-image
{
margin-right: 5px;
}
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie)
{
margin: 1px 0 0;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB