Addresses #513, Deleted Items folder

- Still experimental, but committing for testing
- Sync conflicts with deleted items aren't yet supported

Unrelated: deprecated ZoteroPane.deleteSelectedItem() in favor of more accurately named deleteSelectedItems()
This commit is contained in:
Dan Stillman 2009-01-28 21:25:06 +00:00
parent 6709555dd9
commit eb79a0f659
16 changed files with 359 additions and 57 deletions

View File

@ -146,13 +146,17 @@
var parentbox = this._id('citeLabel'); var parentbox = this._id('citeLabel');
var textbox = this._id('noteField'); var textbox = this._id('noteField');
var textboxReadOnly = this._id('noteFieldReadOnly');
var button = this._id('goButton'); var button = this._id('goButton');
if (this.editable) { if (this.editable) {
textbox.removeAttribute('readonly'); textbox.hidden = false;
textboxReadOnly.hidden = true;
} }
else { else {
textbox.setAttribute('readonly', 'true'); textbox.hidden = true;
textboxReadOnly.hidden = false;
textbox = textboxReadOnly;
} }
//var scrollPos = textbox.inputField.scrollTop; //var scrollPos = textbox.inputField.scrollTop;
@ -361,7 +365,10 @@
<content> <content>
<xul:vbox xbl:inherits="flex"> <xul:vbox xbl:inherits="flex">
<xul:label id="citeLabel"/> <xul:label id="citeLabel"/>
<xul:textbox id="noteField" type="styled" mode="note" timeout="1000" flex="1"/> <xul:textbox id="noteField" type="styled" mode="note"
timeout="1000" flex="1" hidden="true"/>
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
readonly="true" flex="1" hidden="true"/>
<xul:hbox id="linksbox" hidden="true"> <xul:hbox id="linksbox" hidden="true">
<xul:linksbox id="links" flex="1"/> <xul:linksbox id="links" flex="1"/>
</xul:hbox> </xul:hbox>

View File

@ -58,6 +58,7 @@ var ZoteroItemPane = new function() {
return; return;
} }
_deck = document.getElementById('zotero-view-item');
_itemBox = document.getElementById('zotero-editpane-item-box'); _itemBox = document.getElementById('zotero-editpane-item-box');
_notesList = document.getElementById('zotero-editpane-dynamic-notes'); _notesList = document.getElementById('zotero-editpane-dynamic-notes');
_notesLabel = document.getElementById('zotero-editpane-notes-label'); _notesLabel = document.getElementById('zotero-editpane-notes-label');
@ -76,7 +77,7 @@ var ZoteroItemPane = new function() {
// Force blur() when clicking off a textbox to another item in middle // Force blur() when clicking off a textbox to another item in middle
// pane, since for some reason it's not being called automatically // pane, since for some reason it's not being called automatically
if (_itemBeingEdited && _itemBeingEdited != thisItem) { if (_itemBeingEdited && _itemBeingEdited != thisItem) {
switch (_tabs.selectedIndex) { switch (_deck.selectedIndex) {
// Info // Info
case 0: case 0:
// TODO: fix // TODO: fix
@ -100,7 +101,7 @@ var ZoteroItemPane = new function() {
_itemBeingEdited = thisItem; _itemBeingEdited = thisItem;
_loaded = {}; _loaded = {};
loadPane(_tabs.selectedIndex, mode); loadPane(_deck.selectedIndex, mode);
} }

View File

@ -52,7 +52,6 @@ var ZoteroPane = new function()
this.itemSelected = itemSelected; this.itemSelected = itemSelected;
this.reindexItem = reindexItem; this.reindexItem = reindexItem;
this.duplicateSelectedItem = duplicateSelectedItem; this.duplicateSelectedItem = duplicateSelectedItem;
this.deleteSelectedItem = deleteSelectedItem;
this.deleteSelectedCollection = deleteSelectedCollection; this.deleteSelectedCollection = deleteSelectedCollection;
this.editSelectedCollection = editSelectedCollection; this.editSelectedCollection = editSelectedCollection;
this.copySelectedItemsToClipboard = copySelectedItemsToClipboard; this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
@ -552,7 +551,7 @@ var ZoteroPane = new function()
event.keyCode == event.DOM_VK_DELETE) { event.keyCode == event.DOM_VK_DELETE) {
// If Cmd or Ctrl delete, delete from Library (with prompt) // If Cmd or Ctrl delete, delete from Library (with prompt)
var fromDB = event.metaKey || (!Zotero.isMac && event.ctrlKey); var fromDB = event.metaKey || (!Zotero.isMac && event.ctrlKey);
ZoteroPane.deleteSelectedItem(fromDB); ZoteroPane.deleteSelectedItems(fromDB);
event.preventDefault(); event.preventDefault();
return; return;
} }
@ -827,11 +826,21 @@ var ZoteroPane = new function()
return; return;
} }
// Display restore button if items selected in Trash
if (this.itemsView && this.itemsView.selection.count) {
document.getElementById('zotero-item-restore-button').hidden
= !this.itemsView._itemGroup.isTrash();
}
var tabs = document.getElementById('zotero-view-tabs');
if (this.itemsView && this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1) if (this.itemsView && this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1)
{ {
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex); var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
if(item.ref.isNote()) { if(item.ref.isNote()) {
tabs.hidden = true;
var noteEditor = document.getElementById('zotero-note-editor'); var noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = this.itemsView.readOnly ? 'view' : 'edit'; noteEditor.mode = this.itemsView.readOnly ? 'view' : 'edit';
@ -846,19 +855,27 @@ var ZoteroPane = new function()
noteEditor.enableUndo(); noteEditor.enableUndo();
document.getElementById('zotero-view-note-button').setAttribute('noteID',item.ref.id); var viewButton = document.getElementById('zotero-view-note-button');
if(item.ref.getSource()) if (this.itemsView.readOnly) {
{ viewButton.hidden = true;
document.getElementById('zotero-view-note-button').setAttribute('sourceID',item.ref.getSource());
} }
else else {
{ viewButton.hidden = false;
document.getElementById('zotero-view-note-button').removeAttribute('sourceID'); viewButton.setAttribute('noteID', item.ref.id);
if (item.ref.getSource()) {
viewButton.setAttribute('sourceID', item.ref.getSource());
} }
else {
viewButton.removeAttribute('sourceID');
}
}
document.getElementById('zotero-item-pane-content').selectedIndex = 2; document.getElementById('zotero-item-pane-content').selectedIndex = 2;
} }
else if(item.ref.isAttachment()) { else if(item.ref.isAttachment()) {
tabs.hidden = true;
var attachmentBox = document.getElementById('zotero-attachment-box'); var attachmentBox = document.getElementById('zotero-attachment-box');
attachmentBox.mode = this.itemsView.readOnly ? 'view' : 'edit'; attachmentBox.mode = this.itemsView.readOnly ? 'view' : 'edit';
attachmentBox.item = item.ref; attachmentBox.item = item.ref;
@ -869,12 +886,22 @@ var ZoteroPane = new function()
// Regular item // Regular item
else else
{ {
ZoteroItemPane.viewItem(item.ref, this.itemsView.readOnly ? 'view' : false);
document.getElementById('zotero-item-pane-content').selectedIndex = 1; document.getElementById('zotero-item-pane-content').selectedIndex = 1;
if (this.itemsView.readOnly) {
document.getElementById('zotero-view-item').selectedIndex = 0;
ZoteroItemPane.viewItem(item.ref, 'view');
tabs.hidden = true;
}
else {
ZoteroItemPane.viewItem(item.ref);
tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
tabs.hidden = false;
}
} }
} }
else else
{ {
tabs.hidden = true;
document.getElementById('zotero-item-pane-content').selectedIndex = 0; document.getElementById('zotero-item-pane-content').selectedIndex = 0;
var label = document.getElementById('zotero-view-selected-label'); var label = document.getElementById('zotero-view-selected-label');
@ -927,11 +954,15 @@ var ZoteroPane = new function()
} }
this.deleteSelectedItem = function () {
Zotero.debug("ZoteroPane.deleteSelectedItem() is deprecated -- use ZoteroPane.deleteSelectedItems()");
this.deleteSelectedItems();
}
/* /*
* _force_ deletes item from DB even if removing from a collection or search * _force_ deletes item from DB even if removing from a collection or search
*/ */
function deleteSelectedItem(force) this.deleteSelectedItems = function (force) {
{
if (this.itemsView && this.itemsView.selection.count > 0) { if (this.itemsView && this.itemsView.selection.count > 0) {
if (!force){ if (!force){
if (this.itemsView._itemGroup.isCollection()) { if (this.itemsView._itemGroup.isCollection()) {
@ -997,6 +1028,35 @@ var ZoteroPane = new function()
} }
} }
this.restoreSelectedItems = function () {
var items = this.getSelectedItems();
if (!items) {
return;
}
Zotero.DB.beginTransaction();
for (var i=0; i<items.length; i++) {
items[i].deleted = false;
items[i].save();
}
Zotero.DB.commitTransaction();
}
this.emptyTrash = function () {
var prompt = Components.classes["@mozilla.org/network/default-prompt;1"]
.getService(Components.interfaces.nsIPrompt);
var result = prompt.confirm("",
Zotero.getString('pane.collections.emptyTrash') + "\n\n" +
Zotero.getString('general.actionCannotBeUndone'));
if (result) {
Zotero.Items.emptyTrash();
}
}
function editSelectedCollection() function editSelectedCollection()
{ {
if (this.collectionsView.selection.count > 0) { if (this.collectionsView.selection.count > 0) {
@ -1253,14 +1313,14 @@ var ZoteroPane = new function()
exportCollection: 7, exportCollection: 7,
createBibCollection: 8, createBibCollection: 8,
exportFile: 9, exportFile: 9,
loadReport: 10 loadReport: 10,
emptyTrash: 11
}; };
// Collection // Collection
if (this.collectionsView.selection.count == 1 && if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isCollection()) this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isCollection())
{ {
var hide = [m.newCollection, m.newSavedSearch, m.exportFile];
var show = [m.newSubcollection, m.sep1, m.editSelectedCollection, m.removeCollection, var show = [m.newSubcollection, m.sep1, m.editSelectedCollection, m.removeCollection,
m.sep2, m.exportCollection, m.createBibCollection, m.loadReport]; m.sep2, m.exportCollection, m.createBibCollection, m.loadReport];
if (this.itemsView.rowCount>0) { if (this.itemsView.rowCount>0) {
@ -1275,6 +1335,7 @@ var ZoteroPane = new function()
var disable = [m.exportCollection, m.createBibCollection, m.loadReport]; var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
} }
// Adjust labels
menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.rename.collection')); menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.rename.collection'));
menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.collection')); menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.collection'));
menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.collection')); menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.collection'));
@ -1284,7 +1345,6 @@ var ZoteroPane = new function()
// Saved Search // Saved Search
else if (this.collectionsView.selection.count == 1 && else if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isSearch()) { this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isSearch()) {
var hide = [m.newCollection, m.newSavedSearch, m.newSubcollection, m.sep1, m.exportFile]
var show = [m.editSelectedCollection, m.removeCollection, m.sep2, m.exportCollection, var show = [m.editSelectedCollection, m.removeCollection, m.sep2, m.exportCollection,
m.createBibCollection, m.loadReport]; m.createBibCollection, m.loadReport];
@ -1296,17 +1356,21 @@ var ZoteroPane = new function()
var disable = [m.exportCollection, m.createBibCollection, m.loadReport]; var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
} }
// Adjust labels
menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch')); menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch'));
menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch')); menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch'));
menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.savedSearch')); menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.savedSearch'));
menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch')); menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch'));
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch')); menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
} }
// Trash
else if (this.collectionsView.selection.count == 1 &&
this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isTrash()) {
var show = [m.emptyTrash];
}
// Library // Library
else else
{ {
var hide = [m.newSubcollection, m.editSelectedCollection, m.removeCollection, m.sep2,
m.exportCollection, m.createBibCollection, m.loadReport];
var show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile]; var show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile];
} }
@ -1320,9 +1384,9 @@ var ZoteroPane = new function()
menu.childNodes[enable[i]].setAttribute('disabled', false); menu.childNodes[enable[i]].setAttribute('disabled', false);
} }
for (var i in hide) // Hide all items by default
{ for each(var pos in m) {
menu.childNodes[hide[i]].setAttribute('hidden', true); menu.childNodes[pos].setAttribute('hidden', true);
} }
for (var i in show) for (var i in show)

View File

@ -90,6 +90,7 @@
<menuitem oncommand="Zotero_File_Interface.bibliographyFromCollection();"/> <menuitem oncommand="Zotero_File_Interface.bibliographyFromCollection();"/>
<menuitem label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile()"/> <menuitem label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile()"/>
<menuitem oncommand="Zotero_Report_Interface.loadCollectionReport()"/> <menuitem oncommand="Zotero_Report_Interface.loadCollectionReport()"/>
<menuitem label="&zotero.toolbar.emptyTrash.label;" oncommand="ZoteroPane.emptyTrash();"/>
</popup> </popup>
<popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();"> <popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();">
<menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/> <menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/>
@ -99,8 +100,8 @@
<menuitem label="&zotero.items.menu.attach.link;" oncommand="ZoteroPane.addAttachmentFromPage(true, this.parentNode.getAttribute('itemID'));"/> <menuitem label="&zotero.items.menu.attach.link;" oncommand="ZoteroPane.addAttachmentFromPage(true, this.parentNode.getAttribute('itemID'));"/>
<menuseparator/> <menuseparator/>
<menuitem label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane.duplicateSelectedItem();"/> <menuitem label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane.duplicateSelectedItem();"/>
<menuitem oncommand="ZoteroPane.deleteSelectedItem();"/> <menuitem oncommand="ZoteroPane.deleteSelectedItems();"/>
<menuitem oncommand="ZoteroPane.deleteSelectedItem(true);"/> <menuitem oncommand="ZoteroPane.deleteSelectedItems(true);"/>
<menuseparator/> <menuseparator/>
<menuitem oncommand="Zotero_File_Interface.exportItems();"/> <menuitem oncommand="Zotero_File_Interface.exportItems();"/>
<menuitem oncommand="Zotero_File_Interface.bibliographyFromItems();"/> <menuitem oncommand="Zotero_File_Interface.bibliographyFromItems();"/>
@ -358,6 +359,9 @@
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/> <toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/> <toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>
</hbox> </hbox>
<!-- TODO: localize -->
<button id="zotero-item-restore-button" label="Restore to Library"
oncommand="ZoteroPane.restoreSelectedItems()" hidden="true"/>
<groupbox flex="1"> <groupbox flex="1">
<caption> <caption>
<tabs id="zotero-view-tabs" onselect="document.getElementById('zotero-view-item').selectedIndex = this.selectedIndex;" hidden="true"> <tabs id="zotero-view-tabs" onselect="document.getElementById('zotero-view-item').selectedIndex = this.selectedIndex;" hidden="true">
@ -368,7 +372,7 @@
<tab label="&zotero.tabs.related.label;"/> <tab label="&zotero.tabs.related.label;"/>
</tabs> </tabs>
</caption> </caption>
<deck id="zotero-item-pane-content" selectedIndex="0" flex="1" onselect="document.getElementById('zotero-view-tabs').setAttribute('hidden',(this.selectedIndex != 1));"> <deck id="zotero-item-pane-content" selectedIndex="0" flex="1">
<box pack="center" align="center"> <box pack="center" align="center">
<label id="zotero-view-selected-label"/> <label id="zotero-view-selected-label"/>
</box> </box>

View File

@ -164,6 +164,11 @@ Zotero.CollectionTreeView.prototype.refresh = function()
} }
} }
var deletedItems = Zotero.Items.getDeleted();
if (deletedItems) {
this._showItem(new Zotero.ItemGroup('trash', null), 0, this._dataItems.length);
}
this._refreshHashMap(); this._refreshHashMap();
// Update the treebox's row count // Update the treebox's row count
@ -1037,6 +1042,12 @@ Zotero.ItemGroup.prototype.isShare = function()
return this.type == 'share'; return this.type == 'share';
} }
Zotero.ItemGroup.prototype.isTrash = function()
{
return this.type == 'trash';
}
Zotero.ItemGroup.prototype.getName = function() Zotero.ItemGroup.prototype.getName = function()
{ {
if (this.isCollection()) { if (this.isCollection()) {
@ -1051,6 +1062,9 @@ Zotero.ItemGroup.prototype.getName = function()
else if (this.isShare()) { else if (this.isShare()) {
return this.ref.name; return this.ref.name;
} }
else if (this.isTrash()) {
return Zotero.getString('pane.collections.trash');
}
else { else {
return ""; return "";
} }
@ -1098,12 +1112,18 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
} }
includeScopeChildren = true; includeScopeChildren = true;
} }
else if (this.isTrash()) {
s.addCondition('deleted', 'true');
}
else if (!this.isSearch()) { else if (!this.isSearch()) {
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()'); throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
} }
// Create the outer (filter) search // Create the outer (filter) search
var s2 = new Zotero.Search(); var s2 = new Zotero.Search();
if (this.isTrash()) {
s2.addCondition('deleted', 'true');
}
s2.setScope(s, includeScopeChildren); s2.setScope(s, includeScopeChildren);
if (this.searchText) { if (this.searchText) {

View File

@ -75,12 +75,14 @@ Zotero.Item.prototype._init = function () {
this._changedPrimaryData = false; this._changedPrimaryData = false;
this._changedItemData = false; this._changedItemData = false;
this._changedCreators = false; this._changedCreators = false;
this._changedDeleted = false;
this._changedNote = false; this._changedNote = false;
this._changedSource = false; this._changedSource = false;
this._changedAttachmentData = false; this._changedAttachmentData = false;
this._previousData = null; this._previousData = null;
this._deleted = null;
this._noteTitle = null; this._noteTitle = null;
this._noteText = null; this._noteText = null;
this._noteAccessTime = null; this._noteAccessTime = null;
@ -341,8 +343,9 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
Zotero.Item.prototype.hasChanged = function() { Zotero.Item.prototype.hasChanged = function() {
return !!(this._changed return !!(this._changed
|| this._changedPrimaryData || this._changedPrimaryData
|| this._changedCreators
|| this._changedItemData || this._changedItemData
|| this._changedCreators
|| this._changedDeleted
|| this._changedNote || this._changedNote
|| this._changedSource || this._changedSource
|| this._changedAttachmentData); || this._changedAttachmentData);
@ -919,6 +922,44 @@ Zotero.Item.prototype.removeCreator = function(orderIndex) {
} }
Zotero.Item.prototype.__defineGetter__('deleted', function () {
if (this._deleted !== null) {
return this._deleted;
}
if (!this.id) {
return '';
}
var sql = "SELECT COUNT(*) FROM deletedItems WHERE itemID=?";
var deleted = !!Zotero.DB.valueQuery(sql, this.id);
this._deleted = deleted;
return deleted;
});
Zotero.Item.prototype.__defineSetter__('deleted', function (val) {
Zotero.debug('setting deleted');
Zotero.debug(val);
if (!this.id) {
Zotero.debug("Deleted state not set on item without id");
return;
}
var deleted = !!val;
if (this.deleted == deleted) {
Zotero.debug("Deleted state hasn't changed for item " + this.id);
return;
}
if (!this._changedDeleted) {
this._changedDeleted = true;
}
this._deleted = deleted;
});
Zotero.Item.prototype.addRelatedItem = function (itemID) { Zotero.Item.prototype.addRelatedItem = function (itemID) {
var parsedInt = parseInt(itemID); var parsedInt = parseInt(itemID);
if (parsedInt != itemID) { if (parsedInt != itemID) {
@ -1033,6 +1074,7 @@ Zotero.Item.prototype.save = function() {
Zotero.DB.query("UPDATE itemTags SET itemID=? WHERE itemID=?", params); Zotero.DB.query("UPDATE itemTags SET itemID=? WHERE itemID=?", params);
Zotero.DB.query("UPDATE fulltextItemWords SET itemID=? WHERE itemID=?", params); Zotero.DB.query("UPDATE fulltextItemWords SET itemID=? WHERE itemID=?", params);
Zotero.DB.query("UPDATE fulltextItems SET itemID=? WHERE itemID=?", params); Zotero.DB.query("UPDATE fulltextItems SET itemID=? WHERE itemID=?", params);
Zotero.DB.query("UPDATE deletedItems SET itemID=? WHERE itemID=?", params);
Zotero.DB.query("UPDATE annotations SET itemID=? WHERE itemID=?", params); Zotero.DB.query("UPDATE annotations SET itemID=? WHERE itemID=?", params);
Zotero.DB.query("UPDATE highlights SET itemID=? WHERE itemID=?", params); Zotero.DB.query("UPDATE highlights SET itemID=? WHERE itemID=?", params);
@ -1237,6 +1279,17 @@ Zotero.Item.prototype.save = function() {
} }
if (this._changedDeleted) {
if (this.deleted) {
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
}
else {
sql = "DELETE FROM deletedItems WHERE itemID=?";
}
Zotero.DB.query(sql, itemID);
}
// Note // Note
if (this.isNote() || this._changedNote) { if (this.isNote() || this._changedNote) {
sql = "INSERT INTO itemNotes " sql = "INSERT INTO itemNotes "
@ -1393,6 +1446,7 @@ Zotero.Item.prototype.save = function() {
Zotero.DB.query(sql, sqlValues); Zotero.DB.query(sql, sqlValues);
// //
// ItemData // ItemData
// //
@ -1573,6 +1627,17 @@ Zotero.Item.prototype.save = function() {
} }
if (this._changedDeleted) {
if (this.deleted) {
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
}
else {
sql = "DELETE FROM deletedItems WHERE itemID=?";
}
Zotero.DB.query(sql, this.id);
}
// Note // Note
if (this._changedNote) { if (this._changedNote) {
if (this._noteText === null || this._noteTitle === null) { if (this._noteText === null || this._noteTitle === null) {
@ -1790,6 +1855,13 @@ Zotero.Item.prototype.save = function() {
this._key = key; this._key = key;
} }
if (this._changedDeleted) {
Zotero.Notifier.trigger('refresh', 'collection', 0);
if (this._deleted) {
Zotero.Notifier.trigger('trash', 'item', this.id);
}
}
Zotero.Items.reload(this.id); Zotero.Items.reload(this.id);
if (isNew) { if (isNew) {
@ -3244,6 +3316,7 @@ Zotero.Item.prototype.erase = function(deleteChildren) {
Zotero.DB.query('DELETE FROM annotations WHERE itemID=?', this.id); Zotero.DB.query('DELETE FROM annotations WHERE itemID=?', this.id);
Zotero.DB.query('DELETE FROM highlights WHERE itemID=?', this.id); Zotero.DB.query('DELETE FROM highlights WHERE itemID=?', this.id);
Zotero.DB.query('DELETE FROM deletedItems WHERE itemID=?', this.id);
Zotero.DB.query('DELETE FROM itemCreators WHERE itemID=?', this.id); Zotero.DB.query('DELETE FROM itemCreators WHERE itemID=?', this.id);
Zotero.DB.query('DELETE FROM itemNotes WHERE itemID=?', this.id); Zotero.DB.query('DELETE FROM itemNotes WHERE itemID=?', this.id);
Zotero.DB.query('DELETE FROM itemAttachments WHERE itemID=?', this.id); Zotero.DB.query('DELETE FROM itemAttachments WHERE itemID=?', this.id);
@ -3467,6 +3540,10 @@ Zotero.Item.prototype.serialize = function(mode) {
} }
} }
// Deleted items flag
if (this.deleted) {
arr.deleted = true;
}
if (this.isRegularItem()) { if (this.isRegularItem()) {
// Creators // Creators

View File

@ -106,6 +106,24 @@ Zotero.Items = new function() {
} }
/**
* Return items marked as deleted
*
* @param {Boolean} asIDs Return itemIDs instead of
* Zotero.Item objects
* @return {Zotero.Item[]|Integer[]}
*/
this.getDeleted = function (asIDs) {
var sql = "SELECT itemID FROM deletedItems";
var ids = Zotero.DB.columnQuery(sql);
if (asIDs) {
return ids;
}
return this.get(ids);
}
/* /*
* Returns all items in the database * Returns all items in the database
* *
@ -320,6 +338,45 @@ Zotero.Items = new function() {
} }
this.trash = function (ids) {
ids = Zotero.flattenArguments(ids);
Zotero.UnresponsiveScriptIndicator.disable();
try {
Zotero.DB.beginTransaction();
for each(var id in ids) {
var item = this.get(id);
if (!item) {
Zotero.debug('Item ' + id + ' does not exist in Items.trash()!', 1);
Zotero.Notifier.trigger('delete', 'item', id);
continue;
}
item.deleted = true;
item.save();
}
Zotero.DB.commitTransaction();
}
catch (e) {
Zotero.DB.rollbackTransaction();
throw (e);
}
finally {
Zotero.UnresponsiveScriptIndicator.enable();
}
}
this.emptyTrash = function () {
Zotero.DB.beginTransaction();
var deletedIDs = this.getDeleted(true);
if (deletedIDs) {
this.erase(deletedIDs, true);
}
Zotero.Notifier.trigger('refresh', 'collection', 0);
Zotero.DB.commitTransaction();
}
/** /**
* Delete item(s) from database and clear from internal array * Delete item(s) from database and clear from internal array
* *

View File

@ -232,7 +232,7 @@ Zotero.ItemTreeView.prototype.refresh = function()
Zotero.ItemTreeView.prototype.__defineGetter__('readOnly', function () { Zotero.ItemTreeView.prototype.__defineGetter__('readOnly', function () {
if (this._itemGroup.isShare()) { if (this._itemGroup.isTrash() || this._itemGroup.isShare()) {
return true; return true;
} }
return false; return false;
@ -309,7 +309,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
} }
if ((action == 'remove' && !this._itemGroup.isLibrary()) if ((action == 'remove' && !this._itemGroup.isLibrary())
|| action == 'delete' || action == 'id-change') { || action == 'delete' || action == 'id-change' || action == 'trash') {
// We only care about the old ids // We only care about the old ids
if (action == 'id-change') { if (action == 'id-change') {
@ -323,7 +323,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
var rows = []; var rows = [];
for(var i=0, len=ids.length; i<len; i++) for(var i=0, len=ids.length; i<len; i++)
{ {
if (action == 'delete' || action == 'id-change' || if (action == 'delete' || action == 'trash' || action == 'id-change' ||
!this._itemGroup.ref.hasItem(ids[i])) { !this._itemGroup.ref.hasItem(ids[i])) {
// Row might already be gone (e.g. if this is a child and // Row might already be gone (e.g. if this is a child and
// 'modify' was sent to parent) // 'modify' was sent to parent)
@ -350,12 +350,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
madeChanges = true; madeChanges = true;
sort = true; sort = true;
} }
} }
else if (action == 'modify') else if (action == 'modify')
{ {
// If saved search, just re-run search // If trash or saved search, just re-run search
if (this._itemGroup.isSearch()) if (this._itemGroup.isTrash() || this._itemGroup.isSearch())
{ {
this.refresh(); this.refresh();
madeChanges = true; madeChanges = true;
@ -410,6 +409,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// modify comes in after a delete // modify comes in after a delete
continue; continue;
} }
// Deleted items get a modify that we have to ignore when
// not viewing the trash
if (item.deleted) {
continue;
}
if(item.isRegularItem() || !item.getSource()) if(item.isRegularItem() || !item.getSource())
{ {
//most likely, the note or attachment's parent was removed. //most likely, the note or attachment's parent was removed.
@ -436,9 +440,8 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
} }
else if(action == 'add') else if(action == 'add')
{ {
// If saved search, just re-run search // If saved search or trash, just re-run search
if (this._itemGroup.isSearch()) if (this._itemGroup.isSearch() || this._itemGroup.isTrash()) {
{
this.refresh(); this.refresh();
madeChanges = true; madeChanges = true;
sort = true; sort = true;
@ -1167,10 +1170,11 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
} }
/* /**
* Delete the selection * Delete the selection
* *
* _force_ deletes item from DB even if removing from a collection * @param {Boolean} eraseChildren
* @param {Boolean} force Delete item even if removing from a collection
*/ */
Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force) Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
{ {
@ -1201,11 +1205,14 @@ Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
// Erase item(s) from DB // Erase item(s) from DB
if (this._itemGroup.isLibrary() || force) { if (this._itemGroup.isLibrary() || force) {
Zotero.Items.erase(ids, eraseChildren); Zotero.Items.trash(ids);
} }
else if (this._itemGroup.isCollection()) { else if (this._itemGroup.isCollection()) {
this._itemGroup.ref.removeItems(ids); this._itemGroup.ref.removeItems(ids);
} }
else if (this._itemGroup.isTrash()) {
Zotero.Items.erase(ids, eraseChildren);
}
this._treebox.endUpdateBatch(); this._treebox.endUpdateBatch();
} }

View File

@ -2146,7 +2146,7 @@ Zotero.Schema = new function(){
} }
} }
// // 1.5 Sync Preview 3.6 // 1.5 Sync Preview 3.6
if (i==47) { if (i==47) {
Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld"); Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
Zotero.DB.query("DROP INDEX syncDeleteLog_timestamp"); Zotero.DB.query("DROP INDEX syncDeleteLog_timestamp");
@ -2155,6 +2155,11 @@ Zotero.Schema = new function(){
Zotero.DB.query("INSERT OR IGNORE INTO syncDeleteLog SELECT syncObjectTypeID, key, timestamp FROM syncDeleteLogOld ORDER BY timestamp DESC"); Zotero.DB.query("INSERT OR IGNORE INTO syncDeleteLog SELECT syncObjectTypeID, key, timestamp FROM syncDeleteLogOld ORDER BY timestamp DESC");
Zotero.DB.query("DROP TABLE syncDeleteLogOld"); Zotero.DB.query("DROP TABLE syncDeleteLogOld");
} }
//
if (i==48) {
Zotero.DB.query("CREATE TABLE deletedItems (\n itemID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL\n);");
}
} }
_updateDBVersion('userdata', toVersion); _updateDBVersion('userdata', toVersion);

View File

@ -928,6 +928,10 @@ Zotero.Search.prototype._buildQuery = function(){
// Handle special conditions // Handle special conditions
else { else {
switch (data['name']){ switch (data['name']){
case 'deleted':
var deleted = this._conditions[i].operator == 'true';
continue;
case 'noChildren': case 'noChildren':
var noChildren = this._conditions[i]['operator']=='true'; var noChildren = this._conditions[i]['operator']=='true';
continue; continue;
@ -971,20 +975,19 @@ Zotero.Search.prototype._buildQuery = function(){
} }
} }
// Exclude deleted items by default
sql += " WHERE itemID " + (deleted ? "" : "NOT ") + "IN "
+ "(SELECT itemID FROM deletedItems)";
if (noChildren){ if (noChildren){
sql += " WHERE (itemID NOT IN (SELECT itemID FROM itemNotes " sql += " AND (itemID NOT IN (SELECT itemID FROM itemNotes "
+ "WHERE sourceItemID IS NOT NULL) AND itemID NOT IN " + "WHERE sourceItemID IS NOT NULL) AND itemID NOT IN "
+ "(SELECT itemID FROM itemAttachments " + "(SELECT itemID FROM itemAttachments "
+ "WHERE sourceItemID IS NOT NULL))"; + "WHERE sourceItemID IS NOT NULL))";
} }
if (this._hasPrimaryConditions) { if (this._hasPrimaryConditions) {
if (noChildren){
sql += " AND "; sql += " AND ";
}
else {
sql += " WHERE ";
}
for each(var condition in conditions){ for each(var condition in conditions){
var skipOperators = false; var skipOperators = false;
@ -1441,12 +1444,10 @@ Zotero.Search.prototype._buildQuery = function(){
} }
// Keep non-required conditions separate if in ANY mode // Keep non-required conditions separate if in ANY mode
else if (!condition['required'] && joinMode == 'ANY') { else if (!condition['required'] && joinMode == 'ANY') {
var nonQSConditions = true;
anySQL += condSQL + ' OR '; anySQL += condSQL + ' OR ';
anySQLParams = anySQLParams.concat(condSQLParams); anySQLParams = anySQLParams.concat(condSQLParams);
} }
else { else {
var nonQSConditions = true;
condSQL += ' AND '; condSQL += ' AND ';
sql += condSQL; sql += condSQL;
sqlParams = sqlParams.concat(condSQLParams); sqlParams = sqlParams.concat(condSQLParams);
@ -1460,11 +1461,8 @@ Zotero.Search.prototype._buildQuery = function(){
sql = sql.substring(0, sql.length-4); // remove last ' OR ' sql = sql.substring(0, sql.length-4); // remove last ' OR '
sql += ')'; sql += ')';
} }
else if (nonQSConditions) {
sql = sql.substring(0, sql.length-5); // remove last ' AND '
}
else { else {
sql = sql.substring(0, sql.length-7); // remove ' WHERE ' sql = sql.substring(0, sql.length-5); // remove last ' AND '
} }
// Add on quicksearch conditions // Add on quicksearch conditions
@ -1601,6 +1599,15 @@ Zotero.SearchConditions = new function(){
// Special conditions // Special conditions
// //
{
name: 'deleted',
operators: {
true: true,
false: true
}
},
// Don't include child items // Don't include child items
{ {
name: 'noChildren', name: 'noChildren',

View File

@ -2535,6 +2535,11 @@ Zotero.Sync.Server.Data = new function() {
xml.field += newField; xml.field += newField;
} }
// Deleted item flag
if (item.deleted) {
xml.@deleted = '1';
}
if (item.primary.itemType == 'note' || item.primary.itemType == 'attachment') { if (item.primary.itemType == 'note' || item.primary.itemType == 'attachment') {
if (item.sourceItemID) { if (item.sourceItemID) {
xml.@sourceItemID = item.sourceItemID; xml.@sourceItemID = item.sourceItemID;
@ -2665,6 +2670,10 @@ Zotero.Sync.Server.Data = new function() {
} }
} }
// Deleted item flag
var deleted = xmlItem.@deleted.toString();
item.deleted = (deleted == 'true' || deleted == "1");
// Item creators // Item creators
var i = 0; var i = 0;
for each(var creator in xmlItem.creator) { for each(var creator in xmlItem.creator) {

View File

@ -61,6 +61,7 @@
<!ENTITY zotero.toolbar.newCollection.label "New Collection..."> <!ENTITY zotero.toolbar.newCollection.label "New Collection...">
<!ENTITY zotero.toolbar.newSubcollection.label "New Subcollection..."> <!ENTITY zotero.toolbar.newSubcollection.label "New Subcollection...">
<!ENTITY zotero.toolbar.newSavedSearch.label "New Saved Search..."> <!ENTITY zotero.toolbar.newSavedSearch.label "New Saved Search...">
<!ENTITY zotero.toolbar.emptyTrash.label "Empty Trash">
<!ENTITY zotero.toolbar.tagSelector.label "Show/Hide Tag Selector"> <!ENTITY zotero.toolbar.tagSelector.label "Show/Hide Tag Selector">
<!ENTITY zotero.toolbar.actions.label "Actions"> <!ENTITY zotero.toolbar.actions.label "Actions">
<!ENTITY zotero.toolbar.import.label "Import..."> <!ENTITY zotero.toolbar.import.label "Import...">

View File

@ -14,6 +14,7 @@ general.errorHasOccurred = An error has occurred.
general.restartFirefox = Please restart Firefox. general.restartFirefox = Please restart Firefox.
general.restartFirefoxAndTryAgain = Please restart Firefox and try again. general.restartFirefoxAndTryAgain = Please restart Firefox and try again.
general.checkForUpdate = Check for update general.checkForUpdate = Check for update
general.actionCannotBeUndone = This action cannot be undone.
general.install = Install general.install = Install
general.updateAvailable = Update Available general.updateAvailable = Update Available
general.upgrade = Upgrade general.upgrade = Upgrade
@ -50,12 +51,14 @@ startupError = There was an error starting Zotero.
pane.collections.delete = Are you sure you want to delete the selected collection? pane.collections.delete = Are you sure you want to delete the selected collection?
pane.collections.deleteSearch = Are you sure you want to delete the selected search? pane.collections.deleteSearch = Are you sure you want to delete the selected search?
pane.collections.emptyTrash = Are you sure you want to permanently remove items in the Trash?
pane.collections.newCollection = New Collection pane.collections.newCollection = New Collection
pane.collections.name = Enter a name for this collection: pane.collections.name = Enter a name for this collection:
pane.collections.newSavedSeach = New Saved Search pane.collections.newSavedSeach = New Saved Search
pane.collections.savedSearchName = Enter a name for this saved search: pane.collections.savedSearchName = Enter a name for this saved search:
pane.collections.rename = Rename collection: pane.collections.rename = Rename collection:
pane.collections.library = My Library pane.collections.library = My Library
pane.collections.trash = Trash
pane.collections.untitled = Untitled pane.collections.untitled = Untitled
pane.collections.menu.rename.collection = Rename Collection... pane.collections.menu.rename.collection = Rename Collection...

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

View File

@ -1,4 +1,4 @@
-- 3 -- 4
-- Triggers to validate date field -- Triggers to validate date field
DROP TRIGGER IF EXISTS insert_date_field; DROP TRIGGER IF EXISTS insert_date_field;
@ -807,6 +807,41 @@ CREATE TRIGGER fku_savedSearches_savedSearchID_savedSearchConditions_savedSearch
UPDATE savedSearchConditions SET savedSearchID=NEW.savedSearchID WHERE savedSearchID=OLD.savedSearchID; UPDATE savedSearchConditions SET savedSearchID=NEW.savedSearchID WHERE savedSearchID=OLD.savedSearchID;
END; END;
-- deletedItems/itemID
-- savedSearchConditions/savedSearchID
DROP TRIGGER IF EXISTS fki_deletedItems_itemID_items_itemID;
CREATE TRIGGER fki_deletedItems_itemID_items_itemID
BEFORE INSERT ON deletedItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "deletedItems" violates foreign key constraint "fki_deletedItems_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fku_deletedItems_itemID_items_itemID;
CREATE TRIGGER fku_deletedItems_itemID_items_itemID
BEFORE UPDATE OF itemID ON deletedItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "deletedItems" violates foreign key constraint "fku_deletedItems_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;
END;
DROP TRIGGER IF EXISTS fkd_deletedItems_itemID_items_itemID;
CREATE TRIGGER fkd_deletedItems_itemID_items_itemID
BEFORE DELETE ON items
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_deletedItems_itemID_items_itemID"')
WHERE (SELECT COUNT(*) FROM deletedItems WHERE itemID = OLD.itemID) > 0;
END;
DROP TRIGGER IF EXISTS fku_items_itemID_deletedItems_itemID;
CREATE TRIGGER fku_items_itemID_deletedItems_itemID
AFTER UPDATE OF itemID ON items
FOR EACH ROW BEGIN
UPDATE deletedItems SET itemID=NEW.itemID WHERE itemID=OLD.itemID;
END;
-- syncDeleteLog/syncObjectTypeID -- syncDeleteLog/syncObjectTypeID
DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID; DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
CREATE TRIGGER fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID CREATE TRIGGER fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID

View File

@ -1,4 +1,4 @@
-- 47 -- 48
-- This file creates tables containing user-specific data -- any changes made -- This file creates tables containing user-specific data -- any changes made
-- here must be mirrored in transition steps in schema.js::_migrateSchema() -- here must be mirrored in transition steps in schema.js::_migrateSchema()
@ -171,6 +171,11 @@ CREATE TABLE savedSearchConditions (
FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID)
); );
CREATE TABLE deletedItems (
itemID INTEGER PRIMARY KEY,
dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE TABLE fulltextItems ( CREATE TABLE fulltextItems (
itemID INTEGER PRIMARY KEY, itemID INTEGER PRIMARY KEY,
version INT, version INT,