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