diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js index 58fefa44c..183ca8523 100644 --- a/chrome/content/zotero/overlay.js +++ b/chrome/content/zotero/overlay.js @@ -58,6 +58,7 @@ var ZoteroPane = new function() this.onDoubleClick = onDoubleClick; this.contextPopupShowing = contextPopupShowing; this.openNoteWindow = openNoteWindow; + this.toggleAbstractForSelectedItem = toggleAbstractForSelectedItem this.newNote = newNote; this.addTextToNote = addTextToNote; this.addItemFromPage = addItemFromPage; @@ -731,52 +732,69 @@ var ZoteroPane = new function() if(itemsView && itemsView.selection.count > 0) { - enable.push(0,1,2,4,5,6,7,8,9); + enable.push(0,1,2,4,5,7,8,9,10); // Multiple items selected if (itemsView.selection.count > 1) { var multiple = '.multiple'; - hide.push(0,1,2,3); + hide.push(0,1,2,3,4); } // Single item selected else { - var item = itemsView._getItemAtRow(itemsView.selection.currentIndex); - if (item.ref.isRegularItem()) + var item = itemsView._getItemAtRow(itemsView.selection.currentIndex).ref; + if (item.isRegularItem()) { - var itemID = item.ref.getID(); + var itemID = item.getID(); menu.setAttribute('itemID', itemID); - show.push(0,1,2,3); + show.push(0,1,2,4); + hide.push(3); // abstract } else { - hide.push(0,1,2,3); + hide.push(0,1,2); + + // Abstract + if (item.isNote() && item.getSource()) { + show.push(3,4); + if (item.isAbstract()) { + menu.childNodes[3].setAttribute('label', Zotero.getString('pane.items.menu.abstract.unset')); + } + else { + menu.childNodes[3].setAttribute('label', Zotero.getString('pane.items.menu.abstract.set')); + } + } + else { + hide.push(3,4); + } } } } else { - disable.push(0,1,2,4,5,7,8,9); + disable.push(0,1,2,5,6,8,9,10); + hide.push(3); // abstract + show.push(4); // separator } // Remove from collection if (itemsView._itemGroup.isCollection()) { - menu.childNodes[4].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple)); - show.push(4); + menu.childNodes[5].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple)); + show.push(5); } else { - hide.push(4); + hide.push(5); } // Plural if necessary - menu.childNodes[5].setAttribute('label', Zotero.getString('pane.items.menu.erase' + multiple)); - menu.childNodes[7].setAttribute('label', Zotero.getString('pane.items.menu.export' + multiple)); - menu.childNodes[8].setAttribute('label', Zotero.getString('pane.items.menu.createBib' + multiple)); - menu.childNodes[9].setAttribute('label', Zotero.getString('pane.items.menu.generateReport' + multiple)); + menu.childNodes[6].setAttribute('label', Zotero.getString('pane.items.menu.erase' + multiple)); + menu.childNodes[8].setAttribute('label', Zotero.getString('pane.items.menu.export' + multiple)); + menu.childNodes[9].setAttribute('label', Zotero.getString('pane.items.menu.createBib' + multiple)); + menu.childNodes[10].setAttribute('label', Zotero.getString('pane.items.menu.generateReport' + multiple)); for (var i in disable) { @@ -936,6 +954,20 @@ var ZoteroPane = new function() window.open('chrome://zotero/content/note.xul?v=1'+(id ? '&id='+id : '')+(parent ? '&coll='+parent : ''),'','chrome,resizable,centerscreen'); } + + function toggleAbstractForSelectedItem() { + var items = getSelectedItems(); + if (itemsView.selection.count == 1 && items[0] && items[0].isNote() + && items[0].getSource()) { + + items[0].setAbstract(!items[0].isAbstract()) + return true; + } + + return false; + } + + function addAttachmentFromDialog(link, id) { var nsIFilePicker = Components.interfaces.nsIFilePicker; diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul index c4c84b121..38060be2c 100644 --- a/chrome/content/zotero/overlay.xul +++ b/chrome/content/zotero/overlay.xul @@ -80,6 +80,7 @@ + diff --git a/chrome/content/zotero/xpcom/data_access.js b/chrome/content/zotero/xpcom/data_access.js index f004a6764..ed121ab17 100644 --- a/chrome/content/zotero/xpcom/data_access.js +++ b/chrome/content/zotero/xpcom/data_access.js @@ -49,8 +49,9 @@ Zotero.Item.prototype._init = function(){ this._changedCreators = new Zotero.Hash(); this._changedItemData = new Zotero.Hash(); - this._noteData = null; - this._noteDataAccessTime = null; + this._noteText = null; + this._noteIsAbstract = null + this._noteAccessTime = null; this._fileLinkMode = null; } @@ -966,20 +967,17 @@ Zotero.Item.prototype.updateNote = function(text){ Zotero.DB.beginTransaction(); if (this.isNote()){ - var sourceID = this.getSource(); - if (sourceID) - { - var sql = "REPLACE INTO itemNotes (note, sourceItemID, itemID) " - + "VALUES (?,?,?)"; - var bindParams = [{string:text}, sourceID, this.getID()]; - } - else - { - var sql = "REPLACE INTO itemNotes (note, itemID) VALUES (?,?)"; - var bindParams = [{string:text}, this.getID()]; - } + var sourceItemID = this.getSource(); } - else { + + if (sourceItemID) + { + var sql = "REPLACE INTO itemNotes (note, sourceItemID, itemID, isAbstract) " + + "VALUES (?,?,?,?)"; + var bindParams = [{string:text}, sourceItemID, this.getID(), this.isAbstract() ? 1 : null]; + } + else + { var sql = "REPLACE INTO itemNotes (note, itemID) VALUES (?,?)"; var bindParams = [{string:text}, this.getID()]; } @@ -988,7 +986,7 @@ Zotero.Item.prototype.updateNote = function(text){ if (updated){ this.updateDateModified(); Zotero.DB.commitTransaction(); - this.updateNoteCache(text); + this.updateNoteCache(text, this.isAbstract()); Zotero.Notifier.trigger('modify', 'item', this.getID()); } @@ -998,10 +996,11 @@ Zotero.Item.prototype.updateNote = function(text){ } -Zotero.Item.prototype.updateNoteCache = function(text){ +Zotero.Item.prototype.updateNoteCache = function(text, isAbstract){ // Update cached values - this._noteData = text ? text : ''; + this._noteText = text ? text : ''; if (this.isNote()){ + this._noteIsAbstract = !!isAbstract; this.setField('title', this._noteToTitle(), true); } } @@ -1065,6 +1064,14 @@ Zotero.Item.prototype.setSource = function(sourceItemID){ } } + if (this.isAbstract()) { + // If making an independent note or if new item already has an + // abstract, clear abstract status + if (!sourceItemID || newItem.getAbstract()) { + this.setAbstract(false); + } + } + var sql = "UPDATE item" + Type + "s SET sourceItemID=? WHERE itemID=?"; var bindParams = [sourceItemID ? {int:sourceItemID} : null, this.getID()]; Zotero.DB.query(sql, bindParams); @@ -1125,16 +1132,16 @@ Zotero.Item.prototype.getNote = function(){ throw ("getNote() can only be called on notes and attachments"); } - if (this._noteData !== null){ + if (this._noteText !== null){ // Store access time for later garbage collection - this._noteDataAccessTime = new Date(); - return this._noteData; + this._noteAccessTime = new Date(); + return this._noteText; } var sql = "SELECT note FROM itemNotes WHERE itemID=" + this.getID(); var note = Zotero.DB.valueQuery(sql); - this._noteData = note ? note : ''; + this._noteText = note ? note : ''; return note ? note : ''; } @@ -1172,11 +1179,105 @@ Zotero.Item.prototype.getNotes = function(){ } var sql = "SELECT itemID FROM itemNotes NATURAL JOIN items " - + "WHERE sourceItemID=" + this.getID() + " ORDER BY dateAdded"; + + "WHERE sourceItemID=" + this.getID() + " ORDER BY isAbstract IS NULL, dateAdded"; return Zotero.DB.columnQuery(sql); } +/* + * Return true if a note item is an abstract, false otherwise + */ +Zotero.Item.prototype.isAbstract = function() { + if (!this.isNote()) { + throw ("getAbstract() can only be called on note items"); + } + + if (!this.getID()) { + throw ("Cannot call isAbstract() on unsaved item"); + } + + if (this._noteIsAbstract !== null) { + return this._noteIsAbstract; + } + + var sql = "SELECT isAbstract FROM itemNotes WHERE itemID=?"; + var isAbstract = !!Zotero.DB.valueQuery(sql, this.getID()); + + this._noteIsAbstract = isAbstract; + return isAbstract; +} + + +/* + * Make a note item an abstract or clear abstract status + */ +Zotero.Item.prototype.setAbstract = function(set) { + if (!this.isNote()) { + throw ("setAbstract() can only be called on note items"); + } + + if (!this.getID()) { + throw ("Cannot call setAbstract() on unsaved item"); + } + + if (!!set == !!this.isAbstract()) { + Zotero.debug('Abstract status has not changed', 4); + return; + } + + Zotero.DB.beginTransaction(); + + var sourceItemID = this.getSource(); + + if (!sourceItemID) { + Zotero.DB.rollbackTransaction(); + throw ("Cannot make a non-child note an abstract"); + } + + if (set) { + // If existing abstract, clear it + var oldAbstractID = Zotero.Items.get(sourceItemID).getAbstract(); + if (oldAbstractID) { + var oldAbstractItem = Zotero.Items.get(oldAbstractID); + oldAbstractItem.setAbstract(false); + } + } + + var sql = "UPDATE itemNotes SET isAbstract=NULL WHERE sourceItemID=?"; + Zotero.DB.query(sql, sourceItemID); + + var sql = "UPDATE itemNotes SET isAbstract=? WHERE itemID=?"; + Zotero.DB.valueQuery(sql, [set ? 1 : null, this.getID()]); + + Zotero.DB.commitTransaction(); + + this._noteIsAbstract = !!set; + + Zotero.Notifier.trigger('modify', 'item', [this.getID(), sourceItemID]); +} + + +/* + * Return the itemID of a parent item's abstract note, or false if none + */ +Zotero.Item.prototype.getAbstract = function() { + if (!this.isRegularItem()) { + throw ("getAbstract() can only be called on regular items"); + } + + if (!this.getID()) { + throw ("Cannot call getAbstract() on unsaved item"); + } + + var sql = "SELECT itemID FROM itemNotes WHERE sourceItemID=? AND isAbstract=1"; + return Zotero.DB.valueQuery(sql, this.getID()); +} + + + + + + //////////////////////////////////////////////////////// // @@ -2142,7 +2243,7 @@ Zotero.Notes = new function(){ * * Returns the itemID of the new note item **/ - function add(text, sourceItemID){ + function add(text, sourceItemID, isAbstract){ Zotero.DB.beginTransaction(); if (sourceItemID){ @@ -2157,21 +2258,32 @@ Zotero.Notes = new function(){ } } + // If creating abstract, clear abstract status of existing abstract + // for source item + if (isAbstract && sourceItemID) { + var oldAbstractID = Zotero.Items.get(sourceItemID).getAbstract(); + if (oldAbstractID) { + var oldAbstractItem = Zotero.Items.get(oldAbstractID); + oldAbstractItem.setAbstract(false); + } + } + var note = Zotero.Items.getNewItemByType(Zotero.ItemTypes.getID('note')); note.save(); - var sql = "INSERT INTO itemNotes VALUES (?,?,?)"; + var sql = "INSERT INTO itemNotes VALUES (?,?,?,?)"; var bindParams = [ note.getID(), (sourceItemID ? {int:sourceItemID} : null), - {string:text} + {string:text}, + isAbstract ? 1 : null, ]; Zotero.DB.query(sql, bindParams); Zotero.DB.commitTransaction(); // Switch to Zotero.Items version var note = Zotero.Items.get(note.getID()); - note.updateNoteCache(text); + note.updateNoteCache(text, isAbstract); if (sourceItemID){ sourceItem.incrementNoteCount(); diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js index b448466ef..aaf1ab13d 100644 --- a/chrome/content/zotero/xpcom/itemTreeView.js +++ b/chrome/content/zotero/xpcom/itemTreeView.js @@ -347,6 +347,10 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col) } } + if (itemType == 'note' && item.ref.isAbstract()) { + itemType = 'note-abstract'; + } + // DEBUG: only have icons for some types so far switch (itemType) { @@ -371,6 +375,7 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col) case 'map': case 'newspaperArticle': case 'note': + case 'note-abstract': case 'podcast': case 'radioBroadcast': case 'report': @@ -458,7 +463,7 @@ Zotero.ItemTreeView.prototype.toggleOpenState = function(row) var newRows; if(attachments && notes) - newRows = attachments.concat(notes); + newRows = notes.concat(attachments); else if(attachments) newRows = attachments; else if(notes) diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 3290080ba..5418dce5a 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -681,6 +681,16 @@ Zotero.Schema = new function(){ Zotero.DB.query("CREATE INDEX translators_type ON translators(translatorType)"); Zotero.DB.query("DROP TABLE translatorsTemp"); } + + if (i==13) { + Zotero.DB.query("CREATE TABLE itemNotesTemp (itemID INT, sourceItemID INT, note TEXT, PRIMARY KEY (itemID), FOREIGN KEY (itemID) REFERENCES items(itemID), FOREIGN KEY (sourceItemID) REFERENCES items(itemID))"); + Zotero.DB.query("INSERT INTO itemNotesTemp SELECT * FROM itemNotes"); + Zotero.DB.query("DROP TABLE itemNotes"); + Zotero.DB.query("CREATE TABLE itemNotes (\n itemID INT,\n sourceItemID INT,\n note TEXT,\n isAbstract INT DEFAULT NULL,\n PRIMARY KEY (itemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID),\n FOREIGN KEY (sourceItemID) REFERENCES items(itemID)\n);"); + Zotero.DB.query("INSERT INTO itemNotes SELECT itemID, sourceItemID, note, NULL FROM itemNotesTemp"); + Zotero.DB.query("CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID)"); + Zotero.DB.query("DROP TABLE itemNotesTemp"); + } } _updateSchema('userdata'); diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 39c9aa636..36a9c8123 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -15,6 +15,8 @@ pane.items.menu.remove = Remove Selected Item pane.items.menu.remove.multiple = Remove Selected Items pane.items.menu.erase = Delete Selected Item from Library... pane.items.menu.erase.multiple = Delete Selected Items from Library... +pane.items.menu.abstract.set = Set Note as Abstract +pane.items.menu.abstract.unset = Unset Note as Abstract pane.items.menu.export = Export Selected Item... pane.items.menu.export.multiple = Export Selected Items... pane.items.menu.createBib = Create Bibliography from Selected Item... diff --git a/chrome/skin/default/zotero/treeitem-note-abstract.png b/chrome/skin/default/zotero/treeitem-note-abstract.png new file mode 100644 index 000000000..fbaa15606 Binary files /dev/null and b/chrome/skin/default/zotero/treeitem-note-abstract.png differ