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:
parent
6709555dd9
commit
eb79a0f659
16 changed files with 359 additions and 57 deletions
|
@ -146,13 +146,17 @@
|
|||
|
||||
var parentbox = this._id('citeLabel');
|
||||
var textbox = this._id('noteField');
|
||||
var textboxReadOnly = this._id('noteFieldReadOnly');
|
||||
var button = this._id('goButton');
|
||||
|
||||
if (this.editable) {
|
||||
textbox.removeAttribute('readonly');
|
||||
textbox.hidden = false;
|
||||
textboxReadOnly.hidden = true;
|
||||
}
|
||||
else {
|
||||
textbox.setAttribute('readonly', 'true');
|
||||
textbox.hidden = true;
|
||||
textboxReadOnly.hidden = false;
|
||||
textbox = textboxReadOnly;
|
||||
}
|
||||
|
||||
//var scrollPos = textbox.inputField.scrollTop;
|
||||
|
@ -361,7 +365,10 @@
|
|||
<content>
|
||||
<xul:vbox xbl:inherits="flex">
|
||||
<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:linksbox id="links" flex="1"/>
|
||||
</xul:hbox>
|
||||
|
|
|
@ -58,6 +58,7 @@ var ZoteroItemPane = new function() {
|
|||
return;
|
||||
}
|
||||
|
||||
_deck = document.getElementById('zotero-view-item');
|
||||
_itemBox = document.getElementById('zotero-editpane-item-box');
|
||||
_notesList = document.getElementById('zotero-editpane-dynamic-notes');
|
||||
_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
|
||||
// pane, since for some reason it's not being called automatically
|
||||
if (_itemBeingEdited && _itemBeingEdited != thisItem) {
|
||||
switch (_tabs.selectedIndex) {
|
||||
switch (_deck.selectedIndex) {
|
||||
// Info
|
||||
case 0:
|
||||
// TODO: fix
|
||||
|
@ -100,7 +101,7 @@ var ZoteroItemPane = new function() {
|
|||
_itemBeingEdited = thisItem;
|
||||
_loaded = {};
|
||||
|
||||
loadPane(_tabs.selectedIndex, mode);
|
||||
loadPane(_deck.selectedIndex, mode);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,6 @@ var ZoteroPane = new function()
|
|||
this.itemSelected = itemSelected;
|
||||
this.reindexItem = reindexItem;
|
||||
this.duplicateSelectedItem = duplicateSelectedItem;
|
||||
this.deleteSelectedItem = deleteSelectedItem;
|
||||
this.deleteSelectedCollection = deleteSelectedCollection;
|
||||
this.editSelectedCollection = editSelectedCollection;
|
||||
this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
|
||||
|
@ -552,7 +551,7 @@ var ZoteroPane = new function()
|
|||
event.keyCode == event.DOM_VK_DELETE) {
|
||||
// If Cmd or Ctrl delete, delete from Library (with prompt)
|
||||
var fromDB = event.metaKey || (!Zotero.isMac && event.ctrlKey);
|
||||
ZoteroPane.deleteSelectedItem(fromDB);
|
||||
ZoteroPane.deleteSelectedItems(fromDB);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -827,11 +826,21 @@ var ZoteroPane = new function()
|
|||
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)
|
||||
{
|
||||
var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
|
||||
|
||||
if(item.ref.isNote()) {
|
||||
tabs.hidden = true;
|
||||
|
||||
var noteEditor = document.getElementById('zotero-note-editor');
|
||||
noteEditor.mode = this.itemsView.readOnly ? 'view' : 'edit';
|
||||
|
||||
|
@ -846,19 +855,27 @@ var ZoteroPane = new function()
|
|||
|
||||
noteEditor.enableUndo();
|
||||
|
||||
document.getElementById('zotero-view-note-button').setAttribute('noteID',item.ref.id);
|
||||
if(item.ref.getSource())
|
||||
{
|
||||
document.getElementById('zotero-view-note-button').setAttribute('sourceID',item.ref.getSource());
|
||||
var viewButton = document.getElementById('zotero-view-note-button');
|
||||
if (this.itemsView.readOnly) {
|
||||
viewButton.hidden = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById('zotero-view-note-button').removeAttribute('sourceID');
|
||||
else {
|
||||
viewButton.hidden = false;
|
||||
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;
|
||||
}
|
||||
|
||||
else if(item.ref.isAttachment()) {
|
||||
tabs.hidden = true;
|
||||
|
||||
var attachmentBox = document.getElementById('zotero-attachment-box');
|
||||
attachmentBox.mode = this.itemsView.readOnly ? 'view' : 'edit';
|
||||
attachmentBox.item = item.ref;
|
||||
|
@ -869,12 +886,22 @@ var ZoteroPane = new function()
|
|||
// Regular item
|
||||
else
|
||||
{
|
||||
ZoteroItemPane.viewItem(item.ref, this.itemsView.readOnly ? 'view' : false);
|
||||
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
|
||||
{
|
||||
tabs.hidden = true;
|
||||
document.getElementById('zotero-item-pane-content').selectedIndex = 0;
|
||||
|
||||
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
|
||||
*/
|
||||
function deleteSelectedItem(force)
|
||||
{
|
||||
this.deleteSelectedItems = function (force) {
|
||||
if (this.itemsView && this.itemsView.selection.count > 0) {
|
||||
if (!force){
|
||||
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()
|
||||
{
|
||||
if (this.collectionsView.selection.count > 0) {
|
||||
|
@ -1253,14 +1313,14 @@ var ZoteroPane = new function()
|
|||
exportCollection: 7,
|
||||
createBibCollection: 8,
|
||||
exportFile: 9,
|
||||
loadReport: 10
|
||||
loadReport: 10,
|
||||
emptyTrash: 11
|
||||
};
|
||||
|
||||
// Collection
|
||||
if (this.collectionsView.selection.count == 1 &&
|
||||
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,
|
||||
m.sep2, m.exportCollection, m.createBibCollection, m.loadReport];
|
||||
if (this.itemsView.rowCount>0) {
|
||||
|
@ -1275,6 +1335,7 @@ var ZoteroPane = new function()
|
|||
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.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.collection'));
|
||||
menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.collection'));
|
||||
|
@ -1284,7 +1345,6 @@ var ZoteroPane = new function()
|
|||
// Saved Search
|
||||
else if (this.collectionsView.selection.count == 1 &&
|
||||
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,
|
||||
m.createBibCollection, m.loadReport];
|
||||
|
||||
|
@ -1296,17 +1356,21 @@ var ZoteroPane = new function()
|
|||
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.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.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.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
|
||||
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];
|
||||
}
|
||||
|
||||
|
@ -1320,9 +1384,9 @@ var ZoteroPane = new function()
|
|||
menu.childNodes[enable[i]].setAttribute('disabled', false);
|
||||
}
|
||||
|
||||
for (var i in hide)
|
||||
{
|
||||
menu.childNodes[hide[i]].setAttribute('hidden', true);
|
||||
// Hide all items by default
|
||||
for each(var pos in m) {
|
||||
menu.childNodes[pos].setAttribute('hidden', true);
|
||||
}
|
||||
|
||||
for (var i in show)
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
<menuitem oncommand="Zotero_File_Interface.bibliographyFromCollection();"/>
|
||||
<menuitem label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile()"/>
|
||||
<menuitem oncommand="Zotero_Report_Interface.loadCollectionReport()"/>
|
||||
<menuitem label="&zotero.toolbar.emptyTrash.label;" oncommand="ZoteroPane.emptyTrash();"/>
|
||||
</popup>
|
||||
<popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();">
|
||||
<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'));"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane.duplicateSelectedItem();"/>
|
||||
<menuitem oncommand="ZoteroPane.deleteSelectedItem();"/>
|
||||
<menuitem oncommand="ZoteroPane.deleteSelectedItem(true);"/>
|
||||
<menuitem oncommand="ZoteroPane.deleteSelectedItems();"/>
|
||||
<menuitem oncommand="ZoteroPane.deleteSelectedItems(true);"/>
|
||||
<menuseparator/>
|
||||
<menuitem oncommand="Zotero_File_Interface.exportItems();"/>
|
||||
<menuitem oncommand="Zotero_File_Interface.bibliographyFromItems();"/>
|
||||
|
@ -358,6 +359,9 @@
|
|||
<toolbarbutton id="zotero-tb-fullscreen" tooltiptext="&zotero.toolbar.fullscreen.tooltip;" oncommand="ZoteroPane.fullScreen();"/>
|
||||
<toolbarbutton class="tabs-closebutton" oncommand="ZoteroPane.toggleDisplay()"/>
|
||||
</hbox>
|
||||
<!-- TODO: localize -->
|
||||
<button id="zotero-item-restore-button" label="Restore to Library"
|
||||
oncommand="ZoteroPane.restoreSelectedItems()" hidden="true"/>
|
||||
<groupbox flex="1">
|
||||
<caption>
|
||||
<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;"/>
|
||||
</tabs>
|
||||
</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">
|
||||
<label id="zotero-view-selected-label"/>
|
||||
</box>
|
||||
|
|
|
@ -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();
|
||||
|
||||
// Update the treebox's row count
|
||||
|
@ -1037,6 +1042,12 @@ Zotero.ItemGroup.prototype.isShare = function()
|
|||
return this.type == 'share';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isTrash = function()
|
||||
{
|
||||
return this.type == 'trash';
|
||||
}
|
||||
|
||||
|
||||
Zotero.ItemGroup.prototype.getName = function()
|
||||
{
|
||||
if (this.isCollection()) {
|
||||
|
@ -1051,6 +1062,9 @@ Zotero.ItemGroup.prototype.getName = function()
|
|||
else if (this.isShare()) {
|
||||
return this.ref.name;
|
||||
}
|
||||
else if (this.isTrash()) {
|
||||
return Zotero.getString('pane.collections.trash');
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
|
@ -1098,12 +1112,18 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
|
|||
}
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isTrash()) {
|
||||
s.addCondition('deleted', 'true');
|
||||
}
|
||||
else if (!this.isSearch()) {
|
||||
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
|
||||
}
|
||||
|
||||
// Create the outer (filter) search
|
||||
var s2 = new Zotero.Search();
|
||||
if (this.isTrash()) {
|
||||
s2.addCondition('deleted', 'true');
|
||||
}
|
||||
s2.setScope(s, includeScopeChildren);
|
||||
|
||||
if (this.searchText) {
|
||||
|
|
|
@ -75,12 +75,14 @@ Zotero.Item.prototype._init = function () {
|
|||
this._changedPrimaryData = false;
|
||||
this._changedItemData = false;
|
||||
this._changedCreators = false;
|
||||
this._changedDeleted = false;
|
||||
this._changedNote = false;
|
||||
this._changedSource = false;
|
||||
this._changedAttachmentData = false;
|
||||
|
||||
this._previousData = null;
|
||||
|
||||
this._deleted = null;
|
||||
this._noteTitle = null;
|
||||
this._noteText = null;
|
||||
this._noteAccessTime = null;
|
||||
|
@ -341,8 +343,9 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
|
|||
Zotero.Item.prototype.hasChanged = function() {
|
||||
return !!(this._changed
|
||||
|| this._changedPrimaryData
|
||||
|| this._changedCreators
|
||||
|| this._changedItemData
|
||||
|| this._changedCreators
|
||||
|| this._changedDeleted
|
||||
|| this._changedNote
|
||||
|| this._changedSource
|
||||
|| 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) {
|
||||
var parsedInt = parseInt(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 fulltextItemWords 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 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
|
||||
if (this.isNote() || this._changedNote) {
|
||||
sql = "INSERT INTO itemNotes "
|
||||
|
@ -1393,6 +1446,7 @@ Zotero.Item.prototype.save = function() {
|
|||
|
||||
Zotero.DB.query(sql, sqlValues);
|
||||
|
||||
|
||||
//
|
||||
// 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
|
||||
if (this._changedNote) {
|
||||
if (this._noteText === null || this._noteTitle === null) {
|
||||
|
@ -1790,6 +1855,13 @@ Zotero.Item.prototype.save = function() {
|
|||
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);
|
||||
|
||||
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 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 itemNotes 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()) {
|
||||
// Creators
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
|
|
@ -232,7 +232,7 @@ Zotero.ItemTreeView.prototype.refresh = function()
|
|||
|
||||
|
||||
Zotero.ItemTreeView.prototype.__defineGetter__('readOnly', function () {
|
||||
if (this._itemGroup.isShare()) {
|
||||
if (this._itemGroup.isTrash() || this._itemGroup.isShare()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -309,7 +309,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
}
|
||||
|
||||
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
|
||||
if (action == 'id-change') {
|
||||
|
@ -323,7 +323,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
var rows = [];
|
||||
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])) {
|
||||
// Row might already be gone (e.g. if this is a child and
|
||||
// 'modify' was sent to parent)
|
||||
|
@ -350,12 +350,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
madeChanges = true;
|
||||
sort = true;
|
||||
}
|
||||
|
||||
}
|
||||
else if (action == 'modify')
|
||||
{
|
||||
// If saved search, just re-run search
|
||||
if (this._itemGroup.isSearch())
|
||||
// If trash or saved search, just re-run search
|
||||
if (this._itemGroup.isTrash() || this._itemGroup.isSearch())
|
||||
{
|
||||
this.refresh();
|
||||
madeChanges = true;
|
||||
|
@ -410,6 +409,11 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
// modify comes in after a delete
|
||||
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())
|
||||
{
|
||||
//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')
|
||||
{
|
||||
// If saved search, just re-run search
|
||||
if (this._itemGroup.isSearch())
|
||||
{
|
||||
// If saved search or trash, just re-run search
|
||||
if (this._itemGroup.isSearch() || this._itemGroup.isTrash()) {
|
||||
this.refresh();
|
||||
madeChanges = true;
|
||||
sort = true;
|
||||
|
@ -1167,10 +1170,11 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
|
@ -1201,11 +1205,14 @@ Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
|
|||
|
||||
// Erase item(s) from DB
|
||||
if (this._itemGroup.isLibrary() || force) {
|
||||
Zotero.Items.erase(ids, eraseChildren);
|
||||
Zotero.Items.trash(ids);
|
||||
}
|
||||
else if (this._itemGroup.isCollection()) {
|
||||
this._itemGroup.ref.removeItems(ids);
|
||||
}
|
||||
else if (this._itemGroup.isTrash()) {
|
||||
Zotero.Items.erase(ids, eraseChildren);
|
||||
}
|
||||
this._treebox.endUpdateBatch();
|
||||
}
|
||||
|
||||
|
|
|
@ -2146,7 +2146,7 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
// // 1.5 Sync Preview 3.6
|
||||
// 1.5 Sync Preview 3.6
|
||||
if (i==47) {
|
||||
Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
|
||||
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("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);
|
||||
|
|
|
@ -928,6 +928,10 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
// Handle special conditions
|
||||
else {
|
||||
switch (data['name']){
|
||||
case 'deleted':
|
||||
var deleted = this._conditions[i].operator == 'true';
|
||||
continue;
|
||||
|
||||
case 'noChildren':
|
||||
var noChildren = this._conditions[i]['operator']=='true';
|
||||
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){
|
||||
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 "
|
||||
+ "(SELECT itemID FROM itemAttachments "
|
||||
+ "WHERE sourceItemID IS NOT NULL))";
|
||||
}
|
||||
|
||||
if (this._hasPrimaryConditions) {
|
||||
if (noChildren){
|
||||
sql += " AND ";
|
||||
}
|
||||
else {
|
||||
sql += " WHERE ";
|
||||
}
|
||||
|
||||
for each(var condition in conditions){
|
||||
var skipOperators = false;
|
||||
|
@ -1441,12 +1444,10 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
}
|
||||
// Keep non-required conditions separate if in ANY mode
|
||||
else if (!condition['required'] && joinMode == 'ANY') {
|
||||
var nonQSConditions = true;
|
||||
anySQL += condSQL + ' OR ';
|
||||
anySQLParams = anySQLParams.concat(condSQLParams);
|
||||
}
|
||||
else {
|
||||
var nonQSConditions = true;
|
||||
condSQL += ' AND ';
|
||||
sql += condSQL;
|
||||
sqlParams = sqlParams.concat(condSQLParams);
|
||||
|
@ -1460,11 +1461,8 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
sql = sql.substring(0, sql.length-4); // remove last ' OR '
|
||||
sql += ')';
|
||||
}
|
||||
else if (nonQSConditions) {
|
||||
sql = sql.substring(0, sql.length-5); // remove last ' AND '
|
||||
}
|
||||
else {
|
||||
sql = sql.substring(0, sql.length-7); // remove ' WHERE '
|
||||
sql = sql.substring(0, sql.length-5); // remove last ' AND '
|
||||
}
|
||||
|
||||
// Add on quicksearch conditions
|
||||
|
@ -1601,6 +1599,15 @@ Zotero.SearchConditions = new function(){
|
|||
// Special conditions
|
||||
//
|
||||
|
||||
|
||||
{
|
||||
name: 'deleted',
|
||||
operators: {
|
||||
true: true,
|
||||
false: true
|
||||
}
|
||||
},
|
||||
|
||||
// Don't include child items
|
||||
{
|
||||
name: 'noChildren',
|
||||
|
|
|
@ -2535,6 +2535,11 @@ Zotero.Sync.Server.Data = new function() {
|
|||
xml.field += newField;
|
||||
}
|
||||
|
||||
// Deleted item flag
|
||||
if (item.deleted) {
|
||||
xml.@deleted = '1';
|
||||
}
|
||||
|
||||
if (item.primary.itemType == 'note' || item.primary.itemType == 'attachment') {
|
||||
if (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
|
||||
var i = 0;
|
||||
for each(var creator in xmlItem.creator) {
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<!ENTITY zotero.toolbar.newCollection.label "New Collection...">
|
||||
<!ENTITY zotero.toolbar.newSubcollection.label "New Subcollection...">
|
||||
<!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.actions.label "Actions">
|
||||
<!ENTITY zotero.toolbar.import.label "Import...">
|
||||
|
|
|
@ -14,6 +14,7 @@ general.errorHasOccurred = An error has occurred.
|
|||
general.restartFirefox = Please restart Firefox.
|
||||
general.restartFirefoxAndTryAgain = Please restart Firefox and try again.
|
||||
general.checkForUpdate = Check for update
|
||||
general.actionCannotBeUndone = This action cannot be undone.
|
||||
general.install = Install
|
||||
general.updateAvailable = Update Available
|
||||
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.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.name = Enter a name for this collection:
|
||||
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.trash = Trash
|
||||
pane.collections.untitled = Untitled
|
||||
|
||||
pane.collections.menu.rename.collection = Rename Collection...
|
||||
|
|
BIN
chrome/skin/default/zotero/treesource-trash.png
Normal file
BIN
chrome/skin/default/zotero/treesource-trash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 476 B |
37
triggers.sql
37
triggers.sql
|
@ -1,4 +1,4 @@
|
|||
-- 3
|
||||
-- 4
|
||||
|
||||
-- Triggers to validate 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;
|
||||
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
|
||||
DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
|
||||
CREATE TRIGGER fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- 47
|
||||
-- 48
|
||||
|
||||
-- This file creates tables containing user-specific data -- any changes made
|
||||
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
|
||||
|
@ -171,6 +171,11 @@ CREATE TABLE savedSearchConditions (
|
|||
FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID)
|
||||
);
|
||||
|
||||
CREATE TABLE deletedItems (
|
||||
itemID INTEGER PRIMARY KEY,
|
||||
dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE fulltextItems (
|
||||
itemID INTEGER PRIMARY KEY,
|
||||
version INT,
|
||||
|
|
Loading…
Reference in a new issue