1566 lines
38 KiB
JavaScript
1566 lines
38 KiB
JavaScript
/*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (c) 2006 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://chnm.gmu.edu
|
|
|
|
Licensed under the Educational Community License, Version 1.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.opensource.org/licenses/ecl1.php
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// ItemTreeView
|
|
/// -- handles the link between an individual tree and the data layer
|
|
/// -- displays only items (no collections, no hierarchy)
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Constructor for the ItemTreeView object
|
|
*/
|
|
Zotero.ItemTreeView = function(itemGroup, sourcesOnly)
|
|
{
|
|
this._initialized = false;
|
|
|
|
this._itemGroup = itemGroup;
|
|
this._sourcesOnly = sourcesOnly;
|
|
|
|
this._callbacks = [];
|
|
|
|
this._treebox = null;
|
|
this._ownerDocument = null;
|
|
this._needsSort = false;
|
|
|
|
this._dataItems = [];
|
|
this.rowCount = 0;
|
|
|
|
this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item']);
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.addCallback = function(callback) {
|
|
this._callbacks.push(callback);
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype._runCallbacks = function() {
|
|
for each(var cb in this._callbacks) {
|
|
cb();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by the tree itself
|
|
*/
|
|
Zotero.ItemTreeView.prototype.setTree = function(treebox)
|
|
{
|
|
// Try to set the window document if not yet set
|
|
if (treebox && !this._ownerDocument) {
|
|
try {
|
|
this._ownerDocument = treebox.treeBody.ownerDocument;
|
|
}
|
|
catch (e) {}
|
|
}
|
|
|
|
if (this._treebox) {
|
|
if (this._needsSort) {
|
|
this.sort();
|
|
}
|
|
return;
|
|
}
|
|
|
|
this._treebox = treebox;
|
|
|
|
if (this._ownerDocument.defaultView.ZoteroPane) {
|
|
this._ownerDocument.defaultView.ZoteroPane.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
|
|
}
|
|
|
|
// Generate the tree contents in a timer to allow message above to display
|
|
var paneLoader = function(obj) {
|
|
obj.refresh();
|
|
|
|
// Add a keypress listener for expand/collapse
|
|
var expandAllRows = obj.expandAllRows;
|
|
var collapseAllRows = obj.collapseAllRows;
|
|
var tree = obj._treebox.treeBody.parentNode;
|
|
tree.addEventListener('keypress', function(event) {
|
|
var key = String.fromCharCode(event.which);
|
|
|
|
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
|
|
expandAllRows(treebox);
|
|
return;
|
|
}
|
|
else if (key == '-' && !(event.shiftKey || event.ctrlKey ||
|
|
event.altKey || event.metaKey)) {
|
|
collapseAllRows(treebox);
|
|
return;
|
|
}
|
|
}, false);
|
|
|
|
obj.sort();
|
|
|
|
//Zotero.debug('Running callbacks in itemTreeView.setTree()', 4);
|
|
obj._runCallbacks();
|
|
|
|
if (obj._ownerDocument.defaultView.ZoteroPane) {
|
|
obj._ownerDocument.defaultView.ZoteroPane.clearItemsPaneMessage();
|
|
}
|
|
}
|
|
|
|
this._ownerDocument.defaultView.setTimeout(paneLoader, 50, this);
|
|
}
|
|
|
|
|
|
/*
|
|
* Reload the rows from the data access methods
|
|
* (doesn't call the tree.invalidate methods, etc.)
|
|
*/
|
|
Zotero.ItemTreeView.prototype.refresh = function()
|
|
{
|
|
var oldRows = this.rowCount;
|
|
this._dataItems = [];
|
|
this.rowCount = 0;
|
|
|
|
var cacheFields = ['title', 'date'];
|
|
// Cache the visible fields so they don't load individually
|
|
var visibleFields = this.getVisibleFields();
|
|
for each(var field in visibleFields) {
|
|
if (field == 'year') {
|
|
field = 'date';
|
|
}
|
|
if (cacheFields.indexOf(field) == -1) {
|
|
cacheFields = cacheFields.concat(field);
|
|
}
|
|
}
|
|
Zotero.Items.cacheFields(cacheFields);
|
|
|
|
var newRows = this._itemGroup.getChildItems();
|
|
for (var i=0, len=newRows.length; i < len; i++) {
|
|
if (newRows[i] &&
|
|
(!this._sourcesOnly || (!newRows[i].isAttachment() && !newRows[i].isNote()))) {
|
|
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), i+1); //item ref, before row
|
|
}
|
|
}
|
|
|
|
this._refreshHashMap();
|
|
|
|
// Update the treebox's row count
|
|
var diff = this.rowCount - oldRows;
|
|
if (diff != 0) {
|
|
this._treebox.rowCountChanged(0, diff);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by Zotero.Notifier on any changes to items in the data layer
|
|
*/
|
|
Zotero.ItemTreeView.prototype.notify = function(action, type, ids)
|
|
{
|
|
var madeChanges = false;
|
|
var sort = false;
|
|
|
|
this.selection.selectEventsSuppressed = true;
|
|
var savedSelection = this.saveSelection();
|
|
|
|
if (this._treebox && this._treebox.treeBody) {
|
|
// See if we're in the active window
|
|
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
|
.getService(Components.interfaces.nsIWindowMediator);
|
|
if (wm.getMostRecentWindow("navigator:browser") == this._ownerDocument.defaultView){
|
|
var activeWindow = true;
|
|
}
|
|
}
|
|
|
|
var quicksearch = this._ownerDocument.getElementById('zotero-tb-search');
|
|
|
|
// 'collection-item' ids are in the form collectionID-itemID
|
|
if (type == 'collection-item') {
|
|
var splitIDs = [];
|
|
for each(var id in ids) {
|
|
var split = id.split('-');
|
|
// Skip if not collection or not an item in this collection
|
|
if (!this._itemGroup.isCollection() || split[0] != this._itemGroup.ref.getID()) {
|
|
continue;
|
|
}
|
|
splitIDs.push(split[1]);
|
|
}
|
|
ids = splitIDs;
|
|
}
|
|
|
|
if((action == 'remove' && !this._itemGroup.isLibrary()) || action == 'delete')
|
|
{
|
|
//Since a remove involves shifting of rows, we have to do it in order
|
|
|
|
//sort the ids by row
|
|
var rows = new Array();
|
|
for(var i=0, len=ids.length; i<len; i++)
|
|
{
|
|
if (action == 'delete' || !this._itemGroup.ref.hasItem(ids[i])) {
|
|
// Row might already be gone (e.g. if this is a child and
|
|
// 'modify' was sent to parent)
|
|
if (this._itemRowMap[ids[i]] != undefined) {
|
|
rows.push(this._itemRowMap[ids[i]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(rows.length > 0)
|
|
{
|
|
rows.sort(function(a,b) { return a-b });
|
|
|
|
for(var i=0, len=rows.length; i<len; i++)
|
|
{
|
|
var row = rows[i];
|
|
if(row != null)
|
|
{
|
|
this._hideItem(row-i);
|
|
this._treebox.rowCountChanged(row-i,-1);
|
|
}
|
|
}
|
|
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
|
|
}
|
|
else if (action == 'modify')
|
|
{
|
|
// If saved search, just re-run search
|
|
if (this._itemGroup.isSearch())
|
|
{
|
|
this.refresh();
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
|
|
// If no quicksearch, process modifications manually
|
|
else if (!quicksearch || quicksearch.value == '')
|
|
{
|
|
for(var i=0, len=ids.length; i<len; i++)
|
|
{
|
|
var row = this._itemRowMap[ids[i]];
|
|
// Item already exists in this view
|
|
if( row != null)
|
|
{
|
|
var sourceItemID = this._getItemAtRow(row).ref.getSource();
|
|
var parentIndex = this.getParentIndex(row);
|
|
|
|
if (this.isContainer(row) && this.isContainerOpen(row))
|
|
{
|
|
this.toggleOpenState(row);
|
|
this.toggleOpenState(row);
|
|
}
|
|
// If item moved from top-level to under another item,
|
|
// remove the old row
|
|
else if (!this.isContainer(row) && parentIndex == -1
|
|
&& sourceItemID)
|
|
{
|
|
this._hideItem(row);
|
|
this._treebox.rowCountChanged(row+1, -1)
|
|
}
|
|
// If moved from under another item to top level, add row
|
|
else if (!this.isContainer(row) && parentIndex != -1
|
|
&& !sourceItemID)
|
|
{
|
|
var item = Zotero.Items.get(ids[i]);
|
|
this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), this.rowCount);
|
|
this._treebox.rowCountChanged(this.rowCount-1, 1);
|
|
sort = ids[i];
|
|
}
|
|
// If not moved from under one item to another
|
|
else if (parentIndex == -1 || !sourceItemID) {
|
|
sort = ids[i];
|
|
}
|
|
madeChanges = true;
|
|
}
|
|
|
|
else if (this._itemGroup.isLibrary() || this._itemGroup.ref.hasItem(ids[i])) {
|
|
var item = Zotero.Items.get(ids[i]);
|
|
if (!item) {
|
|
// DEBUG: this shouldn't really happen but could if a
|
|
// modify comes in after a delete
|
|
continue;
|
|
}
|
|
if(item.isRegularItem() || !item.getSource())
|
|
{
|
|
//most likely, the note or attachment's parent was removed.
|
|
this._showItem(new Zotero.ItemTreeView.TreeRow(item,0,false),this.rowCount);
|
|
this._treebox.rowCountChanged(this.rowCount-1,1);
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sort && ids.length != 1) {
|
|
sort = true;
|
|
}
|
|
}
|
|
|
|
// If quicksearch, re-run it, since the results may have changed
|
|
else
|
|
{
|
|
quicksearch.doCommand();
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
}
|
|
else if(action == 'add')
|
|
{
|
|
// If saved search, just re-run search
|
|
if (this._itemGroup.isSearch())
|
|
{
|
|
this.refresh();
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
|
|
// If not a quicksearch and not background window saved search,
|
|
// process new items manually
|
|
else if (quicksearch && quicksearch.value == '')
|
|
{
|
|
var items = Zotero.Items.get(ids);
|
|
for (var i in items)
|
|
{
|
|
// if the item belongs in this collection
|
|
if((this._itemGroup.isLibrary() || items[i].inCollection(this._itemGroup.ref.getID()))
|
|
// if we haven't already added it to our hash map
|
|
&& this._itemRowMap[items[i].getID()] == null
|
|
// Regular item or standalone note/attachment
|
|
&& (items[i].isRegularItem() || !items[i].getSource()))
|
|
{
|
|
this._showItem(new Zotero.ItemTreeView.TreeRow(items[i],0,false),this.rowCount);
|
|
this._treebox.rowCountChanged(this.rowCount-1,1);
|
|
|
|
madeChanges = true;
|
|
}
|
|
}
|
|
|
|
if (madeChanges) {
|
|
sort = (ids.length == 1) ? ids[0] : true;
|
|
}
|
|
}
|
|
// Otherwise re-run the search, which refreshes the item list
|
|
else
|
|
{
|
|
if (activeWindow) {
|
|
quicksearch.value = '';
|
|
}
|
|
quicksearch.doCommand();
|
|
madeChanges = true;
|
|
sort = true;
|
|
}
|
|
}
|
|
|
|
if(madeChanges)
|
|
{
|
|
// If adding and this is the active window, select the item
|
|
if(action == 'add' && ids.length===1 && activeWindow)
|
|
{
|
|
if (sort) {
|
|
this.sort(typeof sort == 'number' ? sort : false);
|
|
}
|
|
else {
|
|
this._refreshHashMap();
|
|
}
|
|
|
|
// Reset to Info tab
|
|
this._ownerDocument.getElementById('zotero-view-tabs').selectedIndex = 0;
|
|
this.selectItem(ids[0]);
|
|
}
|
|
// If single item is selected and was modified
|
|
else if (action == 'modify' && ids.length == 1 &&
|
|
savedSelection.length == 1 && savedSelection[0] == ids[0]) {
|
|
// If the item no longer matches the search term, clear the search
|
|
if (quicksearch && this._itemRowMap[ids[0]] == undefined) {
|
|
Zotero.debug('Selected item no longer matches quicksearch -- clearing');
|
|
quicksearch.value = '';
|
|
quicksearch.doCommand();
|
|
}
|
|
|
|
if (sort) {
|
|
this.sort(typeof sort == 'number' ? sort : false);
|
|
}
|
|
else {
|
|
this._refreshHashMap();
|
|
}
|
|
|
|
this.rememberSelection(savedSelection);
|
|
|
|
if (activeWindow) {
|
|
this.selectItem(ids[0]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (sort) {
|
|
this.sort(typeof sort == 'number' ? sort : false);
|
|
}
|
|
else {
|
|
this._refreshHashMap();
|
|
}
|
|
|
|
this.rememberSelection(savedSelection);
|
|
}
|
|
|
|
this._treebox.invalidate();
|
|
}
|
|
this.selection.selectEventsSuppressed = false;
|
|
}
|
|
|
|
/*
|
|
* Unregisters view from Zotero.Notifier (called on window close)
|
|
*/
|
|
Zotero.ItemTreeView.prototype.unregister = function()
|
|
{
|
|
Zotero.Notifier.unregisterObserver(this._unregisterID);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// nsITreeView functions
|
|
/// http://www.xulplanet.com/references/xpcomref/ifaces/nsITreeView.html
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Zotero.ItemTreeView.prototype.getCellText = function(row, column)
|
|
{
|
|
var obj = this._getItemAtRow(row);
|
|
var val;
|
|
|
|
if(column.id == "zotero-items-column-numChildren")
|
|
{
|
|
var c = obj.numChildren();
|
|
if(c) //don't display '0'
|
|
val = c;
|
|
}
|
|
else if(column.id == "zotero-items-column-type")
|
|
{
|
|
val = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(obj.getType()));
|
|
}
|
|
// Year column is just date field truncated
|
|
else if (column.id == "zotero-items-column-year") {
|
|
val = obj.getField('date', true).substr(0, 4)
|
|
}
|
|
else
|
|
{
|
|
val = obj.getField(column.id.substring(20));
|
|
}
|
|
|
|
if(column.id == 'zotero-items-column-dateAdded' || column.id == 'zotero-items-column-dateModified') //this is not so much that we will use this format for date, but a simple template for later revisions.
|
|
{
|
|
val = new Date(Date.parse(val.replace(/-/g,"/"))).toLocaleString();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
|
|
{
|
|
if(col.id == 'zotero-items-column-title')
|
|
{
|
|
return this._getItemAtRow(row).ref.getImageSrc();
|
|
}
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.isContainer = function(row)
|
|
{
|
|
return this._getItemAtRow(row).isRegularItem();
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.isContainerOpen = function(row)
|
|
{
|
|
return this._dataItems[row].isOpen;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.isContainerEmpty = function(row)
|
|
{
|
|
if(this._sourcesOnly) {
|
|
return true;
|
|
} else {
|
|
return (this._getItemAtRow(row).numNotes() == 0 && this._getItemAtRow(row).numAttachments() == 0);
|
|
}
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.getLevel = function(row)
|
|
{
|
|
return this._getItemAtRow(row).level;
|
|
}
|
|
|
|
// Gets the index of the row's container, or -1 if none (top-level)
|
|
Zotero.ItemTreeView.prototype.getParentIndex = function(row)
|
|
{
|
|
if (row==-1)
|
|
{
|
|
return -1;
|
|
}
|
|
var thisLevel = this.getLevel(row);
|
|
if(thisLevel == 0) return -1;
|
|
for(var i = row - 1; i >= 0; i--)
|
|
if(this.getLevel(i) < thisLevel)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex)
|
|
{
|
|
var thisLevel = this.getLevel(row);
|
|
for(var i = afterIndex + 1; i < this.rowCount; i++)
|
|
{
|
|
var nextLevel = this.getLevel(i);
|
|
if(nextLevel == thisLevel) return true;
|
|
else if(nextLevel < thisLevel) return false;
|
|
}
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.toggleOpenState = function(row)
|
|
{
|
|
// Shouldn't happen but does if an item is dragged over a closed
|
|
// container until it opens and then released, since the container
|
|
// is no longer in the same place when the spring-load closes
|
|
if (!this.isContainer(row)) {
|
|
return;
|
|
}
|
|
|
|
var count = 0; //used to tell the tree how many rows were added/removed
|
|
var thisLevel = this.getLevel(row);
|
|
|
|
if(this.isContainerOpen(row))
|
|
{
|
|
while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel))
|
|
{
|
|
this._hideItem(row+1);
|
|
count--; //count is negative when closing a container because we are removing rows
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var item = this._getItemAtRow(row).ref;
|
|
//Get children
|
|
var attachments = item.getAttachments();
|
|
var notes = item.getNotes();
|
|
|
|
var newRows;
|
|
if(attachments && notes)
|
|
newRows = notes.concat(attachments);
|
|
else if(attachments)
|
|
newRows = attachments;
|
|
else if(notes)
|
|
newRows = notes;
|
|
|
|
if (newRows) {
|
|
newRows = Zotero.Items.get(newRows);
|
|
|
|
for(var i = 0; i < newRows.length; i++)
|
|
{
|
|
count++;
|
|
this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], thisLevel + 1, false), row + i + 1); // item ref, before row
|
|
}
|
|
}
|
|
}
|
|
|
|
this._treebox.beginUpdateBatch();
|
|
|
|
this._dataItems[row].isOpen = !this._dataItems[row].isOpen;
|
|
this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
|
|
this._treebox.invalidateRow(row);
|
|
this._treebox.endUpdateBatch();
|
|
this._refreshHashMap();
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.isSorted = function()
|
|
{
|
|
// We sort by the first column if none selected, so return true
|
|
return true;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.cycleHeader = function(column)
|
|
{
|
|
for(var i=0, len=this._treebox.columns.count; i<len; i++)
|
|
{
|
|
col = this._treebox.columns.getColumnAt(i);
|
|
if(column != col)
|
|
{
|
|
col.element.removeAttribute('sortActive');
|
|
col.element.removeAttribute('sortDirection');
|
|
}
|
|
else
|
|
{
|
|
col.element.setAttribute('sortActive',true);
|
|
col.element.setAttribute('sortDirection',col.element.getAttribute('sortDirection') == 'descending' ? 'ascending' : 'descending');
|
|
}
|
|
}
|
|
|
|
this.selection.selectEventsSuppressed = true;
|
|
var savedSelection = this.saveSelection();
|
|
this.sort();
|
|
this.rememberSelection(savedSelection);
|
|
this.selection.selectEventsSuppressed = false;
|
|
this._treebox.invalidate();
|
|
}
|
|
|
|
/*
|
|
* Sort the items by the currently sorted column.
|
|
* Simply uses Array.sort() function, and refreshes the hash map.
|
|
*/
|
|
Zotero.ItemTreeView.prototype.sort = function(itemID)
|
|
{
|
|
// If Zotero pane is hidden, mark tree for sorting later in setTree()
|
|
if (!this._treebox.columns) {
|
|
this._needsSort = true;
|
|
return;
|
|
}
|
|
else {
|
|
this._needsSort = false;
|
|
}
|
|
|
|
var columnField = this.getSortField();
|
|
var order = this.getSortDirection() == 'ascending';
|
|
|
|
// Year is really the date field truncated
|
|
if (columnField == 'year') {
|
|
columnField = 'date';
|
|
}
|
|
|
|
// Some fields (e.g. dates) need to be retrieved unformatted for sorting
|
|
switch (columnField) {
|
|
case 'date':
|
|
var unformatted = true;
|
|
break;
|
|
|
|
default:
|
|
var unformatted = false;
|
|
}
|
|
|
|
// Hash table of fields for which rows with empty values should be displayed last
|
|
var emptyFirst = {
|
|
title: true
|
|
};
|
|
|
|
// Cache primary values while sorting, since base-field-mapped getField()
|
|
// calls are relatively expensive
|
|
var cache = [];
|
|
|
|
function columnSort(a,b) {
|
|
var cmp, fieldA, fieldB;
|
|
|
|
var aItemID = a.ref.getID();
|
|
if (cache[aItemID]) {
|
|
fieldA = cache[aItemID];
|
|
}
|
|
var bItemID = b.ref.getID();
|
|
if (cache[bItemID]) {
|
|
fieldB = cache[bItemID];
|
|
}
|
|
|
|
switch (columnField) {
|
|
case 'type':
|
|
var typeA = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(a.getType()));
|
|
var typeB = Zotero.getString('itemTypes.'+Zotero.ItemTypes.getName(b.getType()));
|
|
|
|
cmp = (typeA > typeB) ? -1 : (typeA < typeB) ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
break;
|
|
|
|
case 'numChildren':
|
|
cmp = b.numChildren() - a.numChildren();
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (fieldA == undefined) {
|
|
fieldA = a.getField(columnField, unformatted, true);
|
|
if (typeof fieldA == 'string') {
|
|
fieldA = fieldA.toLowerCase();
|
|
}
|
|
cache[aItemID] = fieldA;
|
|
}
|
|
|
|
if (fieldB == undefined) {
|
|
fieldB = b.getField(columnField, unformatted, true);
|
|
if (typeof fieldB == 'string') {
|
|
fieldB = fieldB.toLowerCase();
|
|
}
|
|
cache[bItemID] = fieldB;
|
|
}
|
|
|
|
// Display rows with empty values last
|
|
if (!emptyFirst[columnField]) {
|
|
cmp = (fieldA == '' && fieldB != '') ? -1 :
|
|
(fieldA != '' && fieldB == '') ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
if (columnField != 'firstCreator') {
|
|
fieldA = a.getField('firstCreator');
|
|
fieldB = b.getField('firstCreator');
|
|
|
|
// Display rows with empty values last
|
|
cmp = (fieldA == '' && fieldB != '') ? -1 :
|
|
(fieldA != '' && fieldB == '') ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
|
|
cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
if (columnField != 'date') {
|
|
fieldA = a.getField('date', true, true);
|
|
fieldB = b.getField('date', true, true);
|
|
|
|
// Display rows with empty values last
|
|
cmp = (fieldA == '' && fieldB != '') ? -1 :
|
|
(fieldA != '' && fieldB == '') ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
|
|
cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
|
|
if (cmp) {
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
fieldA = a.getField('dateModified');
|
|
fieldB = b.getField('dateModified');
|
|
return (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
|
|
}
|
|
|
|
function doSort(a,b)
|
|
{
|
|
return columnSort(a,b);
|
|
}
|
|
|
|
function reverseSort(a,b)
|
|
{
|
|
return columnSort(a,b) * -1;
|
|
}
|
|
|
|
// Need to close all containers before sorting
|
|
var openRows = new Array();
|
|
for (var i=0; i<this._dataItems.length; i++) {
|
|
if(this.isContainer(i) && this.isContainerOpen(i))
|
|
{
|
|
openRows.push(this._getItemAtRow(i).ref.getID());
|
|
this.toggleOpenState(i);
|
|
}
|
|
}
|
|
|
|
// Single-row sort
|
|
if (itemID) {
|
|
this._refreshHashMap();
|
|
var row = this._itemRowMap[itemID];
|
|
for (var i=0, len=this._dataItems.length; i<len; i++) {
|
|
if (i == row) {
|
|
continue;
|
|
}
|
|
|
|
if (order) {
|
|
var cmp = reverseSort(this._dataItems[i], this._dataItems[row]);
|
|
}
|
|
else {
|
|
var cmp = doSort(this._dataItems[i], this._dataItems[row]);
|
|
}
|
|
|
|
// As soon as we find a value greater (or smaller if reverse sort),
|
|
// insert row at that position
|
|
if (cmp < 0) {
|
|
var rowItem = this._dataItems.splice(row, 1);
|
|
this._dataItems.splice(row < i ? i-1 : i, 0, rowItem[0]);
|
|
this._treebox.invalidate();
|
|
break;
|
|
}
|
|
|
|
// If greater than last row, move to end
|
|
if (i == len-1) {
|
|
var rowItem = this._dataItems.splice(row, 1);
|
|
this._dataItems.splice(i, 0, rowItem[0]);
|
|
this._treebox.invalidate();
|
|
}
|
|
}
|
|
}
|
|
// Full sort
|
|
else {
|
|
if (order) {
|
|
this._dataItems.sort(doSort);
|
|
}
|
|
else {
|
|
this._dataItems.sort(reverseSort);
|
|
}
|
|
}
|
|
|
|
this._refreshHashMap();
|
|
|
|
// Reopen closed containers
|
|
for(var i = 0; i < openRows.length; i++)
|
|
this.toggleOpenState(this._itemRowMap[openRows[i]]);
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Additional functions for managing data in the tree
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Select an item
|
|
*/
|
|
Zotero.ItemTreeView.prototype.selectItem = function(id, expand)
|
|
{
|
|
var row = this._itemRowMap[id];
|
|
|
|
// Get the row of the parent, if there is one
|
|
var parentRow = null;
|
|
var item = Zotero.Items.get(id);
|
|
var parent = item.getSource();
|
|
if (parent && this._itemRowMap[parent]) {
|
|
parentRow = this._itemRowMap[parent];
|
|
}
|
|
|
|
// If row with id not visible, check to see if it's hidden under a parent
|
|
if(row == null)
|
|
{
|
|
if (!parent || !parentRow)
|
|
{
|
|
// No parent -- it's not here
|
|
return false;
|
|
}
|
|
this.toggleOpenState(parentRow); //opens the parent of the item
|
|
row = this._itemRowMap[id];
|
|
}
|
|
|
|
this.selection.select(row);
|
|
// If _expand_, open container
|
|
if (expand && this.isContainer(row) && !this.isContainerOpen(row)) {
|
|
this.toggleOpenState(row);
|
|
}
|
|
this.selection.select(row);
|
|
|
|
// We aim for a row 5 below the target row, since ensureRowIsVisible() does
|
|
// the bare minimum to get the row in view
|
|
for (var v = row + 5; v>=row; v--) {
|
|
if (this._dataItems[v]) {
|
|
this._treebox.ensureRowIsVisible(v);
|
|
if (this._treebox.getFirstVisibleRow() <= row) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the parent row isn't in view and we have enough room, make parent visible
|
|
if (parentRow !== null && this._treebox.getFirstVisibleRow() > parentRow) {
|
|
if ((row - parentRow) < this._treebox.getPageLength()) {
|
|
this._treebox.ensureRowIsVisible(parentRow);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Return an array of Item objects for selected items
|
|
*
|
|
* If asIDs is true, return an array of itemIDs instead
|
|
*/
|
|
Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
|
|
{
|
|
var items = [], start = {}, end = {};
|
|
for (var i=0, len = this.selection.getRangeCount(); i<len; i++)
|
|
{
|
|
this.selection.getRangeAt(i,start,end);
|
|
for (var j=start.value; j<=end.value; j++) {
|
|
if (asIDs) {
|
|
items.push(this._getItemAtRow(j).ref.getID());
|
|
}
|
|
else {
|
|
items.push(this._getItemAtRow(j).ref);
|
|
}
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
|
|
/*
|
|
* Delete the selection
|
|
*
|
|
* _force_ deletes item from DB even if removing from a collection
|
|
*/
|
|
Zotero.ItemTreeView.prototype.deleteSelection = function(eraseChildren, force)
|
|
{
|
|
if(this.selection.count == 0)
|
|
return;
|
|
|
|
//collapse open items
|
|
for(var i=0; i<this.rowCount; i++)
|
|
if(this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i))
|
|
this.toggleOpenState(i);
|
|
this._refreshHashMap();
|
|
|
|
//create an array of selected items
|
|
var ids = [];
|
|
var start = {};
|
|
var end = {};
|
|
for (var i=0, len=this.selection.getRangeCount(); i<len; i++)
|
|
{
|
|
this.selection.getRangeAt(i,start,end);
|
|
for (var j=start.value; j<=end.value; j++)
|
|
ids.push(this._getItemAtRow(j).ref.getID());
|
|
}
|
|
|
|
//iterate and erase...
|
|
this._treebox.beginUpdateBatch();
|
|
|
|
// Erase item(s) from DB
|
|
if (this._itemGroup.isLibrary() || force) {
|
|
Zotero.Items.erase(ids, eraseChildren);
|
|
}
|
|
else if (this._itemGroup.isCollection()) {
|
|
for each(var id in ids) {
|
|
this._itemGroup.ref.removeItem(id);
|
|
}
|
|
}
|
|
this._treebox.endUpdateBatch();
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the tags filter on the view
|
|
*/
|
|
Zotero.ItemTreeView.prototype.setFilter = function(type, data) {
|
|
this.selection.selectEventsSuppressed = true;
|
|
var savedSelection = this.saveSelection();
|
|
var savedOpenState = this.saveOpenState();
|
|
var savedFirstRow = this.saveFirstRow();
|
|
|
|
switch (type) {
|
|
case 'search':
|
|
this._itemGroup.setSearch(data);
|
|
break;
|
|
case 'tags':
|
|
this._itemGroup.setTags(data);
|
|
break;
|
|
default:
|
|
throw ('Invalid filter type in setFilter');
|
|
}
|
|
var oldCount = this.rowCount;
|
|
this.refresh();
|
|
|
|
this.sort();
|
|
|
|
this.rememberOpenState(savedOpenState);
|
|
this.rememberFirstRow(savedFirstRow);
|
|
this.rememberSelection(savedSelection);
|
|
this.selection.selectEventsSuppressed = false;
|
|
this._treebox.invalidate();
|
|
//Zotero.debug('Running callbacks in itemTreeView.setFilter()', 4);
|
|
this._runCallbacks();
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by various view functions to show a row
|
|
*
|
|
* item: reference to the Item
|
|
* beforeRow: row index to insert new row before
|
|
*/
|
|
Zotero.ItemTreeView.prototype._showItem = function(item, beforeRow)
|
|
{
|
|
this._dataItems.splice(beforeRow, 0, item);
|
|
this.rowCount++;
|
|
}
|
|
|
|
/*
|
|
* Called by view to hide specified row
|
|
*/
|
|
Zotero.ItemTreeView.prototype._hideItem = function(row)
|
|
{
|
|
this._dataItems.splice(row,1);
|
|
this.rowCount--;
|
|
}
|
|
|
|
/*
|
|
* Returns a reference to the item at row (see Zotero.Item in data_access.js)
|
|
*/
|
|
Zotero.ItemTreeView.prototype._getItemAtRow = function(row)
|
|
{
|
|
return this._dataItems[row];
|
|
}
|
|
|
|
/*
|
|
* Create hash map of item ids to row indexes
|
|
*/
|
|
Zotero.ItemTreeView.prototype._refreshHashMap = function()
|
|
{
|
|
this._itemRowMap = new Array();
|
|
for(var i=0; i < this.rowCount; i++)
|
|
{
|
|
var row = this._getItemAtRow(i);
|
|
this._itemRowMap[row.ref.getID()] = i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Saves the ids of currently selected items for later
|
|
*/
|
|
Zotero.ItemTreeView.prototype.saveSelection = function()
|
|
{
|
|
var savedSelection = new Array();
|
|
|
|
var start = new Object();
|
|
var end = new Object();
|
|
for (var i=0, len=this.selection.getRangeCount(); i<len; i++)
|
|
{
|
|
this.selection.getRangeAt(i,start,end);
|
|
for (var j=start.value; j<=end.value; j++)
|
|
{
|
|
var item = this._getItemAtRow(j);
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
savedSelection.push(item.ref.getID());
|
|
}
|
|
}
|
|
return savedSelection;
|
|
}
|
|
|
|
/*
|
|
* Sets the selection based on saved selection ids (see above)
|
|
*/
|
|
Zotero.ItemTreeView.prototype.rememberSelection = function(selection)
|
|
{
|
|
// clearSelection() seems to cause a crash in Firefox 2.0.0.2 - 2.0.0.3, at least
|
|
//this.selection.clearSelection();
|
|
// So try this alternative method
|
|
this.selection.selectEventsSuppressed = true;
|
|
this.selection.select(0);
|
|
this.selection.toggleSelect(0);
|
|
this.selection.selectEventsSuppressed = false;
|
|
|
|
for(var i=0; i < selection.length; i++)
|
|
{
|
|
if (this._itemRowMap[selection[i]] != null) {
|
|
this.selection.toggleSelect(this._itemRowMap[selection[i]]);
|
|
}
|
|
// Try the parent
|
|
else {
|
|
var item = Zotero.Items.get(selection[i]);
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
|
|
var parent = item.getSource();
|
|
if (!parent) {
|
|
continue;
|
|
}
|
|
|
|
if (this._itemRowMap[parent] != null) {
|
|
if (this.isContainerOpen(this._itemRowMap[parent])) {
|
|
this.toggleOpenState(this._itemRowMap[parent]);
|
|
}
|
|
this.toggleOpenState(this._itemRowMap[parent]);
|
|
this.selection.toggleSelect(this._itemRowMap[selection[i]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.saveOpenState = function() {
|
|
var ids = [];
|
|
for (var i=0, len=this.rowCount; i<len; i++) {
|
|
if (this.isContainer(i) && this.isContainerOpen(i)) {
|
|
ids.push(this._getItemAtRow(i).ref.getID());
|
|
}
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.rememberOpenState = function(ids) {
|
|
for each(var id in ids) {
|
|
var row = this._itemRowMap[id];
|
|
if (row == undefined || !this.isContainer(row) || this.isContainerOpen(row)) {
|
|
continue;
|
|
}
|
|
this.toggleOpenState(row);
|
|
}
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.saveFirstRow = function() {
|
|
var row = this._treebox.getFirstVisibleRow();
|
|
if (row) {
|
|
return this._getItemAtRow(row).ref.getID();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.rememberFirstRow = function(firstRow) {
|
|
if (firstRow && this._itemRowMap[firstRow]) {
|
|
this._treebox.scrollToRow(this._itemRowMap[firstRow]);
|
|
}
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.expandAllRows = function(treebox) {
|
|
var view = treebox.view;
|
|
treebox.beginUpdateBatch();
|
|
for (var i=0; i<view.rowCount; i++) {
|
|
if (view.isContainer(i) && !view.isContainerOpen(i)) {
|
|
view.toggleOpenState(i);
|
|
}
|
|
}
|
|
treebox.endUpdateBatch();
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.collapseAllRows = function(treebox) {
|
|
var view = treebox.view;
|
|
treebox.beginUpdateBatch();
|
|
for (var i=0; i<view.rowCount; i++) {
|
|
if (view.isContainer(i) && view.isContainerOpen(i)) {
|
|
view.toggleOpenState(i);
|
|
}
|
|
}
|
|
treebox.endUpdateBatch();
|
|
}
|
|
|
|
|
|
Zotero.ItemTreeView.prototype.getVisibleFields = function() {
|
|
var columns = [];
|
|
for (var i=0, len=this._treebox.columns.count; i<len; i++) {
|
|
var col = this._treebox.columns.getColumnAt(i);
|
|
if (col.element.getAttribute('hidden') != 'true') {
|
|
columns.push(col.id.substring(20));
|
|
}
|
|
}
|
|
return columns;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns an array of item ids of visible items in current sort order
|
|
*/
|
|
Zotero.ItemTreeView.prototype.getSortedItems = function() {
|
|
var ids = [];
|
|
for each(var item in this._dataItems) {
|
|
ids.push(item.ref.getID());
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.getSortField = function() {
|
|
var column = this._treebox.columns.getSortedColumn()
|
|
if (!column) {
|
|
column = this._treebox.columns.getFirstColumn()
|
|
}
|
|
// zotero-items-column-_________
|
|
return column.id.substring(20);
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns 'ascending' or 'descending'
|
|
*
|
|
* A-Z == 'descending'
|
|
*/
|
|
Zotero.ItemTreeView.prototype.getSortDirection = function() {
|
|
var column = this._treebox.columns.getSortedColumn();
|
|
if (!column) {
|
|
return 'descending';
|
|
}
|
|
return column.element.getAttribute('sortDirection');
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Command Controller:
|
|
/// for Select All, etc.
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Zotero.ItemTreeCommandController = function(tree)
|
|
{
|
|
this.tree = tree;
|
|
}
|
|
|
|
Zotero.ItemTreeCommandController.prototype.supportsCommand = function(cmd)
|
|
{
|
|
return (cmd == 'cmd_selectAll');
|
|
}
|
|
|
|
Zotero.ItemTreeCommandController.prototype.isCommandEnabled = function(cmd)
|
|
{
|
|
return (cmd == 'cmd_selectAll');
|
|
}
|
|
|
|
Zotero.ItemTreeCommandController.prototype.doCommand = function(cmd)
|
|
{
|
|
if(cmd == 'cmd_selectAll')
|
|
this.tree.view.selection.selectAll();
|
|
}
|
|
|
|
Zotero.ItemTreeCommandController.prototype.onEvent = function(evt)
|
|
{
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Drag-and-drop functions:
|
|
/// for nsDragAndDrop.js + nsTransferable.js
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Begin a drag
|
|
*/
|
|
Zotero.ItemTreeView.prototype.onDragStart = function (evt,transferData,action)
|
|
{
|
|
transferData.data=new TransferData();
|
|
transferData.data.addDataForFlavour("zotero/item", this.saveSelection());
|
|
}
|
|
|
|
/*
|
|
* Called by nsDragAndDrop.js for any sort of drop on the tree
|
|
*/
|
|
Zotero.ItemTreeView.prototype.getSupportedFlavours = function ()
|
|
{
|
|
var flavors = new FlavourSet();
|
|
flavors.appendFlavour("zotero/item");
|
|
flavors.appendFlavour("text/x-moz-url");
|
|
return flavors;
|
|
}
|
|
|
|
Zotero.ItemTreeView.prototype.canDrop = function(row, orient)
|
|
{
|
|
if (row == -1 && orient == -1) {
|
|
return true;
|
|
}
|
|
|
|
try
|
|
{
|
|
var dataSet = nsTransferable.get(this.getSupportedFlavours(),
|
|
nsDragAndDrop.getDragData, true);
|
|
}
|
|
catch (e)
|
|
{
|
|
// A work around a limitation in nsDragAndDrop.js -- the mDragSession
|
|
// is not set until the drag moves over another control.
|
|
// (This will only happen if the first drag is from the item list.)
|
|
nsDragAndDrop.mDragSession = nsDragAndDrop.mDragService.getCurrentSession();
|
|
return false;
|
|
}
|
|
|
|
var data = dataSet.first.first;
|
|
var dataType = data.flavour.contentType;
|
|
|
|
switch (dataType) {
|
|
case 'zotero/item':
|
|
var ids = data.data.split(','); // ids of rows we are dragging in
|
|
break;
|
|
|
|
case 'text/x-moz-url':
|
|
var url = data.data.split("\n")[0];
|
|
break;
|
|
}
|
|
|
|
// workaround... two different services call canDrop
|
|
// (nsDragAndDrop, and the tree) -- this is for the former,
|
|
// used when dragging between windows
|
|
if (typeof row == 'object')
|
|
{
|
|
// If drag to different window
|
|
if (nsDragAndDrop.mDragSession.sourceNode!=row.target)
|
|
{
|
|
if (dataType == 'zotero/item') {
|
|
// Check if at least one item (or parent item for children) doesn't
|
|
// already exist in target
|
|
for each(var id in ids)
|
|
{
|
|
var item = Zotero.Items.get(id);
|
|
|
|
// Skip non-top-level items
|
|
if (!item.isRegularItem() && item.getSource())
|
|
{
|
|
continue;
|
|
}
|
|
// DISABLED: move parent on child drag
|
|
//var source = item.isRegularItem() ? false : item.getSource();
|
|
//if (!this._itemGroup.ref.hasItem(source ? source : id))
|
|
if (this._itemGroup.ref && !this._itemGroup.ref.hasItem(id))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (dataType == 'text/x-moz-url') {
|
|
if (this._itemGroup.isSearch()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//Zotero.debug('row is ' + row);
|
|
//Zotero.debug('orient is ' + orient);
|
|
|
|
// Highlight the rows correctly on drag
|
|
|
|
var rowItem = this._getItemAtRow(row).ref; //the item we are dragging over
|
|
if (dataType == 'zotero/item')
|
|
{
|
|
// Directly on a row
|
|
if (orient == 0)
|
|
{
|
|
for each(var id in ids)
|
|
{
|
|
var item = Zotero.Items.get(id);
|
|
// Only allow dragging of notes and attachments
|
|
// that aren't already children of the item
|
|
if (!item.isRegularItem() && item.getSource()!=rowItem.getID())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// In library, allow children to be dragged out of parent
|
|
else if (this._itemGroup.isLibrary() || this._itemGroup.isCollection())
|
|
{
|
|
for each(var id in ids)
|
|
{
|
|
// Don't allow drag if any top-level items
|
|
var item = Zotero.Items.get(id);
|
|
if (item.isRegularItem() || !item.getSource())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else if (dataType == "text/x-moz-url") {
|
|
// Disallow direct drop on a non-regular item (e.g. note)
|
|
if (orient == 0) {
|
|
if (!rowItem.isRegularItem()) {
|
|
return false;
|
|
}
|
|
}
|
|
else if (this._itemGroup.isSearch()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Called when something's been dropped on or next to a row
|
|
*/
|
|
Zotero.ItemTreeView.prototype.drop = function(row, orient)
|
|
{
|
|
try
|
|
{
|
|
var dataSet = nsTransferable.get(this.getSupportedFlavours(),
|
|
nsDragAndDrop.getDragData, true);
|
|
}
|
|
catch (e)
|
|
{
|
|
// A work around a limitation in nsDragAndDrop.js -- the mDragSession
|
|
// is not set until the drag moves over another control.
|
|
// (This will only happen if the first drag is from the item list.)
|
|
nsDragAndDrop.mDragSession = nsDragAndDrop.mDragService.getCurrentSession();
|
|
var dataSet = nsTransferable.get(this.getSupportedFlavours(),
|
|
nsDragAndDrop.getDragData, true);
|
|
}
|
|
|
|
var data = dataSet.first.first;
|
|
var dataType = data.flavour.contentType;
|
|
|
|
if (!this.canDrop(row, orient)) {
|
|
return false;
|
|
}
|
|
|
|
if (dataType == 'zotero/item') {
|
|
var ids = data.data.split(','); // ids of rows we are dragging in
|
|
|
|
// Dropped directly on a row
|
|
if (orient == 0)
|
|
{
|
|
// If item was a top-level item and it exists in a collection,
|
|
// replace it in collections with the parent item
|
|
var rowItem = this._getItemAtRow(row).ref; // the item we are dragging over
|
|
for each(var id in ids)
|
|
{
|
|
var item = Zotero.Items.get(id);
|
|
item.setSource(rowItem.getID());
|
|
}
|
|
}
|
|
|
|
// Dropped outside of a row
|
|
else
|
|
{
|
|
// Remove from parent and make top-level
|
|
if (this._itemGroup.isLibrary())
|
|
{
|
|
for each(var id in ids)
|
|
{
|
|
var item = Zotero.Items.get(id);
|
|
if (!item.isRegularItem())
|
|
{
|
|
item.setSource();
|
|
}
|
|
}
|
|
}
|
|
// Add to collection
|
|
else
|
|
{
|
|
for each(var id in ids)
|
|
{
|
|
var item = Zotero.Items.get(id);
|
|
var source = item.isRegularItem() ? false : item.getSource();
|
|
|
|
// Top-level item
|
|
if (source) {
|
|
item.setSource();
|
|
}
|
|
this._itemGroup.ref.addItem(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (dataType == 'text/x-moz-url') {
|
|
var url = data.data.split("\n")[0];
|
|
|
|
var sourceItemID = false;
|
|
var parentCollectionID = false;
|
|
|
|
if (orient == 0) {
|
|
var rowItem = this._getItemAtRow(row).ref;
|
|
sourceItemID = rowItem.getID()
|
|
}
|
|
else if (this._itemGroup.isCollection()) {
|
|
var parentCollectionID = this._itemGroup.ref.getID();
|
|
}
|
|
|
|
Zotero.Attachments.importFromURL(url, sourceItemID, false, false, parentCollectionID);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called by nsDragAndDrop.js for any sort of drop on the tree
|
|
*/
|
|
Zotero.ItemTreeView.prototype.onDrop = function (evt,dropdata,session){ }
|
|
|
|
Zotero.ItemTreeView.prototype.onDragOver = function (evt,dropdata,session) { }
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Functions for nsITreeView that we have to stub out.
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Zotero.ItemTreeView.prototype.isSeparator = function(row) { return false; }
|
|
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) { }
|
|
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
|
|
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) { }
|
|
|
|
Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
|
|
{
|
|
this.ref = ref; //the item associated with this
|
|
this.level = level;
|
|
this.isOpen = isOpen;
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.isNote = function()
|
|
{
|
|
return this.ref.isNote();
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.isAttachment = function()
|
|
{
|
|
return this.ref.isAttachment();
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.isRegularItem = function()
|
|
{
|
|
return this.ref.isRegularItem();
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.getField = function(field, unformatted)
|
|
{
|
|
return this.ref.getField(field, unformatted, true);
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.getType = function()
|
|
{
|
|
return this.ref.getType();
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.numChildren = function()
|
|
{
|
|
if(this.isRegularItem())
|
|
return this.ref.numChildren();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.numNotes = function()
|
|
{
|
|
if(this.isRegularItem())
|
|
return this.ref.numNotes();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
Zotero.ItemTreeView.TreeRow.prototype.numAttachments = function()
|
|
{
|
|
if(this.isRegularItem())
|
|
return this.ref.numAttachments();
|
|
else
|
|
return 0;
|
|
} |