Duplicate detection:
- Adds a per-library "Duplicate Items" virtual search to the source list -- shows up by default for "My Library" but can be added to and removed from all libraries - Current matching algorithm is very basic: finds exact title matches (after normalizing case/diacritics/punctuation/spacing) and DOI/ISBN matches (untested) - In duplicates view, sets are selected automatically; in other views, duplicate items can be selected manually and the merge interface can be brought up with "Merge Items" in the context menu - Can select a master item and individual fields to merge from other versions - Word processor integration code will automatically find mapped replacements and update documents with new item keys Possible future improvements: - Improved detection algorithms - UI tweaks - Currently if any items differ, all available versions will be shown as master item options, even if only one item is different; probably the earliest equivalent item should be shown for each distinct version - Caching of results for performance - Confidence scale - Creator version selection (currently the creators from the chosen master item are kept) - Merging of matching child items - Better sorting of duplicates if not clustered together by the selected sort column - Relation path compression when merging items that are already mapped to previously removed duplicates Other changes in this commit: - Don't show Trash in word processor integration windows - Consider items in trash to be missing in word processor documents - Selection of special views (Trash, Unfiled, Duplicates) is now restored properly in new windows - Disabled field transform context menu when item isn't editable - Left/right arrow now expands/collapses all selected items instead of just the last-selected row - Relation deletions are now synced - The same items row is now reselected after item deletion - (dev) Zotero.Item.getNotes(), Zotero.Item.getAttachments(), and Zotero.Item.getTags() now return empty arrays rather than FALSE if no matches -- tests on those return values in third-party code will need to be changed - (dev) New function Zotero.Utilities.removeDiacritics(str, lowercaseOnly) -- could be used to generate ASCII BibTeX keys - (dev) New 'tempTable' search condition can take a table to join against -- useful for implementing virtual source lists - (dev) Significant UI code cleanup - (dev) Moved all item pane content into itemPane.xul - Probably various other things Needless to say, this needs testing.
This commit is contained in:
parent
e945b84b5f
commit
56c7afc47e
29 changed files with 1856 additions and 849 deletions
|
@ -111,7 +111,7 @@
|
|||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#zotero-view-selected-label {
|
||||
#zotero-item-pane-message {
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
break;
|
||||
|
||||
case 'merge':
|
||||
//this.hideEmptyFields = true;
|
||||
this.clickByItem = true;
|
||||
break;
|
||||
|
||||
|
@ -92,6 +91,11 @@
|
|||
this.blurHandler = this.hideEditor;
|
||||
break;
|
||||
|
||||
case 'fieldmerge':
|
||||
this.hideEmptyFields = true;
|
||||
this._fieldAlternatives = {};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid mode '" + val + "' in itembox.xml");
|
||||
}
|
||||
|
@ -103,15 +107,22 @@
|
|||
</property>
|
||||
|
||||
<field name="_item"/>
|
||||
<property name="item"
|
||||
onget="return this._item;"
|
||||
onset="this._item = val; this.refresh();">
|
||||
<property name="item" onget="return this._item;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (!(val instanceof Zotero.Item)) {
|
||||
throw ("<zoteroitembox>.item must be a Zotero.Item");
|
||||
}
|
||||
this._item = val;
|
||||
this.refresh();
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<!-- .ref is an alias for .item -->
|
||||
<property name="ref"
|
||||
onget="return this._item;"
|
||||
onset="this._item = val; this.refresh();">
|
||||
onset="this.item = val; this.refresh();">
|
||||
</property>
|
||||
|
||||
|
||||
|
@ -132,6 +143,22 @@
|
|||
</setter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
An array of field names that should be hidden
|
||||
-->
|
||||
<field name="_hiddenFields">[]</field>
|
||||
<property name="hiddenFields">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val.constructor.name != 'Array') {
|
||||
throw ('hiddenFields must be an array in <itembox>.visibleFields');
|
||||
}
|
||||
|
||||
this._hiddenFields = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
An array of field names that should be clickable
|
||||
even if this.clickable is false
|
||||
|
@ -166,6 +193,26 @@
|
|||
</setter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
An object of alternative values for keyed fields
|
||||
|
||||
-->
|
||||
<field name="_fieldAlternatives">{}</field>
|
||||
<property name="fieldAlternatives">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (val.constructor.name != 'Object') {
|
||||
throw ('fieldAlternatives must be an Object in <itembox>.fieldAlternatives');
|
||||
}
|
||||
|
||||
if (this.mode != 'fieldmerge') {
|
||||
throw ('fieldAlternatives is valid only in fieldmerge mode in <itembox>.fieldAlternatives');
|
||||
}
|
||||
|
||||
this._fieldAlternatives = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
An array of field names in the order they should appear
|
||||
|
@ -209,7 +256,6 @@
|
|||
onget="return '(' + Zotero.getString('pane.item.defaultLastName') + ')'"/>
|
||||
<property name="_defaultFullName"
|
||||
onget="return '(' + Zotero.getString('pane.item.defaultFullName') + ')'"/>
|
||||
|
||||
<method name="refresh">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -285,6 +331,10 @@
|
|||
}
|
||||
|
||||
if (fieldName) {
|
||||
if (this._hiddenFields.indexOf(fieldName) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// createValueElement() adds the itemTypeID as an attribute
|
||||
// and converts it to a localized string for display
|
||||
if (fieldName == 'itemType') {
|
||||
|
@ -294,13 +344,14 @@
|
|||
val = this.item.getField(fieldName);
|
||||
}
|
||||
|
||||
var fieldIsClickable = this._fieldIsClickable(fieldName);
|
||||
|
||||
if (!val && this.hideEmptyFields
|
||||
&& this._visibleFields.indexOf(fieldName) == -1) {
|
||||
&& this._visibleFields.indexOf(fieldName) == -1
|
||||
&& (this.mode != 'fieldmerge' || typeof this._fieldAlternatives[fieldName] == 'undefined')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldIsClickable = this._fieldIsClickable(fieldName);
|
||||
|
||||
// Start tabindex at 1001 after creators
|
||||
var tabindex = fieldIsClickable
|
||||
? (i>0 ? this._tabIndexMinFields + i : 1) : 0;
|
||||
|
@ -365,11 +416,39 @@
|
|||
"if (this.nextSibling.inputField) { this.nextSibling.inputField.blur(); }");
|
||||
}
|
||||
|
||||
this.addDynamicRow(label, valueElement);
|
||||
var row = this.addDynamicRow(label, valueElement);
|
||||
|
||||
if (fieldName && this._selectField == fieldName) {
|
||||
this.showEditor(valueElement);
|
||||
}
|
||||
|
||||
// In field merge mode, add a button to switch field versions
|
||||
else if (this.mode == 'fieldmerge' && typeof this._fieldAlternatives[fieldName] != 'undefined') {
|
||||
var button = document.createElement("toolbarbutton");
|
||||
button.className = 'zotero-field-version-button';
|
||||
button.setAttribute('image', 'chrome://zotero/skin/treesource-duplicates.png');
|
||||
button.setAttribute('type', 'menu');
|
||||
|
||||
var popup = button.appendChild(document.createElement("menupopup"));
|
||||
|
||||
for each(var v in this._fieldAlternatives[fieldName]) {
|
||||
var menuitem = document.createElement("menuitem");
|
||||
menuitem.setAttribute('label', Zotero.Utilities.ellipsize(v, 40));
|
||||
menuitem.setAttribute('fieldName', fieldName);
|
||||
menuitem.setAttribute('originalValue', v);
|
||||
menuitem.setAttribute(
|
||||
'oncommand',
|
||||
"var binding = document.getBindingParent(this); "
|
||||
+ "var item = binding.item; "
|
||||
+ "item.setField(this.getAttribute('fieldName'), this.getAttribute('originalValue')); "
|
||||
+ "var row = Zotero.getAncestorByTagName(this, 'row'); "
|
||||
+ "binding.refresh();"
|
||||
);
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
|
||||
row.appendChild(button);
|
||||
}
|
||||
}
|
||||
this._selectField = false;
|
||||
|
||||
|
@ -446,13 +525,11 @@
|
|||
this.addCreatorRow(false, false, true, true);
|
||||
}
|
||||
|
||||
|
||||
// Move to next or previous field if (shift-)tab was pressed
|
||||
if (this._lastTabIndex && this._tabDirection)
|
||||
{
|
||||
this._focusNextField('info', this._dynamicFields, this._lastTabIndex, this._tabDirection == -1);
|
||||
}
|
||||
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -534,6 +611,8 @@
|
|||
else {
|
||||
this._dynamicFields.appendChild(row);
|
||||
}
|
||||
|
||||
return row;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -1140,10 +1219,10 @@
|
|||
}
|
||||
|
||||
// Display a context menu for certain fields
|
||||
if (fieldName == 'seriesTitle' || fieldName == 'shortTitle' ||
|
||||
if (this.editable && (fieldName == 'seriesTitle' || fieldName == 'shortTitle' ||
|
||||
Zotero.ItemFields.isFieldOfBase(fieldID, 'title') ||
|
||||
Zotero.ItemFields.isFieldOfBase(fieldID, 'publicationTitle')) {
|
||||
valueElement.setAttribute('contextmenu', 'field-menu');
|
||||
Zotero.ItemFields.isFieldOfBase(fieldID, 'publicationTitle'))) {
|
||||
valueElement.setAttribute('contextmenu', 'zotero-field-transform-menu');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2261,7 +2340,7 @@
|
|||
);
|
||||
typeBox.setAttribute('typeid', typeID);
|
||||
document.getBindingParent(this).modifyCreator(index, fields);"/>
|
||||
<menupopup id="field-menu">
|
||||
<menupopup id="zotero-field-transform-menu">
|
||||
<menu label="&zotero.item.textTransform;">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.item.textTransform.titlecase;" class="menuitem-non-iconic"
|
||||
|
|
|
@ -467,6 +467,20 @@
|
|||
<parameter name="ids"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var itemGroup = ZoteroPane_Local.getItemGroup();
|
||||
|
||||
// Ignore anything other than deletes in duplicates view
|
||||
if (itemGroup.isDuplicates()) {
|
||||
switch (event) {
|
||||
case 'delete':
|
||||
case 'trash':
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a selected tag no longer exists, deselect it
|
||||
if (event == 'delete') {
|
||||
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
|
||||
|
|
156
chrome/content/zotero/duplicatesMerge.js
Normal file
156
chrome/content/zotero/duplicatesMerge.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
var Zotero_Duplicates_Pane = new function () {
|
||||
_items = [];
|
||||
_otherItems = [];
|
||||
_ignoreFields = ['dateAdded', 'dateModified', 'accessDate'];
|
||||
|
||||
this.setItems = function (items, displayNumItemsOnTypeError) {
|
||||
var itemTypeID, oldestItem, otherItems = [];
|
||||
for each(var item in items) {
|
||||
// Find the oldest item
|
||||
if (!oldestItem) {
|
||||
oldestItem = item;
|
||||
}
|
||||
else if (item.dateAdded < oldestItem.dateAdded) {
|
||||
otherItems.push(oldestItem);
|
||||
oldestItem = item;
|
||||
}
|
||||
else {
|
||||
otherItems.push(item);
|
||||
}
|
||||
|
||||
if (!item.isRegularItem() || [1,14].indexOf(item.itemTypeID) != -1) {
|
||||
// TODO: localize
|
||||
var msg = "Only top-level full items can be merged.";
|
||||
ZoteroPane_Local.setItemPaneMessage(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure all items are of the same type
|
||||
if (itemTypeID) {
|
||||
if (itemTypeID != item.itemTypeID) {
|
||||
if (displayNumItemsOnTypeError) {
|
||||
var msg = Zotero.getString('pane.item.selected.multiple', items.length);
|
||||
}
|
||||
else {
|
||||
// TODO: localize
|
||||
var msg = "Merged items must all be of the same item type.";
|
||||
}
|
||||
ZoteroPane_Local.setItemPaneMessage(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
itemTypeID = item.itemTypeID;
|
||||
}
|
||||
}
|
||||
|
||||
_items = items;
|
||||
|
||||
_items.sort(function (a, b) {
|
||||
return a.dateAdded > b.dateAdded ? 1 : a.dateAdded == b.dateAdded ? 0 : -1;
|
||||
});
|
||||
|
||||
//
|
||||
// Update the UI
|
||||
//
|
||||
|
||||
var diff = oldestItem.multiDiff(otherItems, _ignoreFields);
|
||||
|
||||
var button = document.getElementById('zotero-duplicates-merge-button');
|
||||
var versionSelect = document.getElementById('zotero-duplicates-merge-version-select');
|
||||
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
|
||||
var fieldSelect = document.getElementById('zotero-duplicates-merge-field-select');
|
||||
|
||||
versionSelect.hidden = !diff;
|
||||
if (diff) {
|
||||
// Populate menulist with Date Added values from all items
|
||||
var dateList = document.getElementById('zotero-duplicates-merge-original-date');
|
||||
|
||||
while (dateList.itemCount) {
|
||||
dateList.removeItemAt(0);
|
||||
}
|
||||
|
||||
var numRows = 0;
|
||||
for each(var item in _items) {
|
||||
var date = Zotero.Date.sqlToDate(item.dateAdded, true);
|
||||
dateList.appendItem(date.toLocaleString());
|
||||
numRows++;
|
||||
}
|
||||
|
||||
dateList.setAttribute('rows', numRows);
|
||||
|
||||
// If we set this inline, the selection doesn't take on the first
|
||||
// selection after unhiding versionSelect (when clicking
|
||||
// from a set with no differences) -- tested in Fx5.0.1
|
||||
setTimeout(function () {
|
||||
dateList.selectedIndex = 0;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
button.label = "Merge " + (otherItems.length + 1) + " items";
|
||||
itembox.hiddenFields = diff ? [] : ['dateAdded', 'dateModified'];
|
||||
fieldSelect.hidden = !diff;
|
||||
|
||||
this.setMaster(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
this.setMaster = function (pos) {
|
||||
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
|
||||
itembox.mode = 'fieldmerge';
|
||||
|
||||
_otherItems = _items.concat();
|
||||
var item = _otherItems.splice(pos, 1)[0];
|
||||
|
||||
// Add master item's values to the beginning of each set of
|
||||
// alternative values so that they're still available if the item box
|
||||
// modifies the item
|
||||
var diff = item.multiDiff(_otherItems, _ignoreFields);
|
||||
if (diff) {
|
||||
var itemValues = item.serialize()
|
||||
for (var i in diff) {
|
||||
diff[i].unshift(itemValues.fields[i]);
|
||||
}
|
||||
itembox.fieldAlternatives = diff;
|
||||
}
|
||||
|
||||
itembox.item = item.clone(true);
|
||||
}
|
||||
|
||||
|
||||
this.merge = function () {
|
||||
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
|
||||
// Work around item.clone() weirdness -- the cloned item can't safely be
|
||||
// used after it's saved, because it's not the version in memory and
|
||||
// doesn't get reloaded properly in item.save()
|
||||
var item = Zotero.Items.get(itembox.item.id);
|
||||
Zotero.Items.merge(item, _otherItems);
|
||||
}
|
||||
}
|
|
@ -28,39 +28,96 @@
|
|||
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<overlay
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="include.js"/>
|
||||
<script src="itemPane.js"/>
|
||||
|
||||
<tabpanels id="zotero-view-item" flex="1">
|
||||
<tabpanel>
|
||||
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
|
||||
</tabpanel>
|
||||
<vbox id="zotero-item-pane" zotero-persist="width">
|
||||
<!-- Trash -->
|
||||
<!-- TODO: localize -->
|
||||
<!-- TODO: Make look less awful -->
|
||||
<button id="zotero-item-restore-button" label="Restore to Library"
|
||||
oncommand="ZoteroPane_Local.restoreSelectedItems()" hidden="true"/>
|
||||
|
||||
<tabpanel flex="1" orient="vertical">
|
||||
<vbox flex="1">
|
||||
<hbox align="center">
|
||||
<label id="zotero-editpane-notes-label"/>
|
||||
<button id="zotero-editpane-notes-add" label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote(event.shiftKey);"/>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
||||
</grid>
|
||||
<!-- Commons -->
|
||||
<button id="zotero-item-show-original" label="Show Original"
|
||||
oncommand="ZoteroPane_Local.showOriginalItem()" hidden="true"/>
|
||||
|
||||
<deck id="zotero-item-pane-content" selectedIndex="0" flex="1">
|
||||
<!-- Center label (for zero or multiple item selection) -->
|
||||
<vbox pack="center" align="center">
|
||||
<label id="zotero-item-pane-message"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel>
|
||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel>
|
||||
<seealsobox id="zotero-editpane-related" flex="1"/>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
|
||||
<!-- Regular item -->
|
||||
<tabbox id="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
|
||||
<tabs>
|
||||
<tab label="&zotero.tabs.info.label;"/>
|
||||
<tab label="&zotero.tabs.notes.label;"/>
|
||||
<tab label="&zotero.tabs.tags.label;"/>
|
||||
<tab label="&zotero.tabs.related.label;"/>
|
||||
</tabs>
|
||||
<tabpanels id="zotero-view-item" flex="1">
|
||||
<tabpanel>
|
||||
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel flex="1" orient="vertical">
|
||||
<vbox flex="1">
|
||||
<hbox align="center">
|
||||
<label id="zotero-editpane-notes-label"/>
|
||||
<button id="zotero-editpane-notes-add" label="&zotero.item.add;" oncommand="ZoteroItemPane.addNote(event.shiftKey);"/>
|
||||
</hbox>
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column flex="1"/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows id="zotero-editpane-dynamic-notes" flex="1"/>
|
||||
</grid>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel>
|
||||
<tagsbox id="zotero-editpane-tags" flex="1"/>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel>
|
||||
<seealsobox id="zotero-editpane-related" flex="1"/>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
|
||||
<!-- Note item -->
|
||||
<groupbox id="zotero-view-note" flex="1">
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"/>
|
||||
<button id="zotero-view-note-button" label="&zotero.notes.separate;" oncommand="ZoteroPane_Local.openNoteWindow(this.getAttribute('noteID')); if(this.hasAttribute('sourceID')) ZoteroPane_Local.selectItem(this.getAttribute('sourceID'));"/>
|
||||
</groupbox>
|
||||
|
||||
<!-- Attachment item -->
|
||||
<zoteroattachmentbox id="zotero-attachment-box" flex="1"/>
|
||||
|
||||
<!-- Duplicate merging -->
|
||||
<!-- TODO: localize -->
|
||||
<vbox id="zotero-duplicates-merge-pane" flex="1">
|
||||
<groupbox>
|
||||
<button id="zotero-duplicates-merge-button" oncommand="Zotero_Duplicates_Pane.merge()"/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-duplicates-merge-version-select">
|
||||
<description>Choose the version of the item to use as the master item:</description>
|
||||
<hbox>
|
||||
<listbox id="zotero-duplicates-merge-original-date" onselect="Zotero_Duplicates_Pane.setMaster(this.selectedIndex)"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox flex="1">
|
||||
<description id="zotero-duplicates-merge-field-select">
|
||||
Select fields to keep from other versions of the item:
|
||||
</description>
|
||||
<zoteroitembox id="zotero-duplicates-merge-item-box" flex="1"/>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
</overlay>
|
|
@ -45,7 +45,7 @@ function doLoad()
|
|||
|
||||
collectionsView = new Zotero.CollectionTreeView();
|
||||
// Don't show Commons when citing
|
||||
collectionsView.showCommons = false;
|
||||
collectionsView.hideSources = ['duplicates', 'trash', 'commons'];
|
||||
document.getElementById('zotero-collections-tree').view = collectionsView;
|
||||
if(io.select) itemsView.selectItem(io.select);
|
||||
|
||||
|
|
|
@ -40,8 +40,7 @@ Zotero.CollectionTreeView = function()
|
|||
this.itemToSelect = null;
|
||||
this._highlightedRows = {};
|
||||
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']);
|
||||
this.showDuplicates = false;
|
||||
this.showCommons = true;
|
||||
this.hideSources = [];
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -78,6 +77,12 @@ Zotero.CollectionTreeView.prototype.setTree = function(treebox)
|
|||
|
||||
var row = this.getLastViewedRow();
|
||||
this.selection.select(row);
|
||||
|
||||
// TODO: make better
|
||||
var tb = this._treebox;
|
||||
setTimeout(function () {
|
||||
tb.ensureRowIsVisible(row);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
|
||||
|
@ -102,6 +107,17 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
this._dataItems = [];
|
||||
this.rowCount = 0;
|
||||
|
||||
if (this.hideSources.indexOf('duplicates') == -1) {
|
||||
try {
|
||||
var duplicateLibraries = Zotero.Prefs.get('duplicateLibraries').split(',');
|
||||
}
|
||||
catch (e) {
|
||||
// Add to personal library by default
|
||||
Zotero.Prefs.set('duplicateLibraries', '0');
|
||||
duplicateLibraries = ['0'];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(',');
|
||||
}
|
||||
|
@ -136,24 +152,31 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
|
||||
// Unfiled items
|
||||
if (unfiledLibraries.indexOf('0') != -1) {
|
||||
var s = new Zotero.Search;
|
||||
// Give virtual search an id so it can be reselected automatically
|
||||
s.id = 86345330000; // 'UNFILED' + '000' + libraryID
|
||||
s.name = Zotero.getString('pane.collections.unfiled');
|
||||
s.addCondition('libraryID', 'is', null);
|
||||
s.addCondition('unfiled', 'true');
|
||||
self._showItem(new Zotero.ItemGroup('search', s), 1, newRows+1);
|
||||
// Duplicate items
|
||||
if (self.hideSources.indexOf('duplicates') == -1 && duplicateLibraries.indexOf('0') != -1) {
|
||||
var d = new Zotero.Duplicates(0);
|
||||
self._showItem(new Zotero.ItemGroup('duplicates', d), 1, newRows+1);
|
||||
newRows++;
|
||||
}
|
||||
|
||||
var deletedItems = Zotero.Items.getDeleted();
|
||||
if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) {
|
||||
self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1);
|
||||
// Unfiled items
|
||||
if (unfiledLibraries.indexOf('0') != -1) {
|
||||
var s = new Zotero.Search;
|
||||
s.name = Zotero.getString('pane.collections.unfiled');
|
||||
s.addCondition('libraryID', 'is', null);
|
||||
s.addCondition('unfiled', 'true');
|
||||
self._showItem(new Zotero.ItemGroup('unfiled', s), 1, newRows+1);
|
||||
newRows++;
|
||||
}
|
||||
self.trashNotEmpty = !!deletedItems;
|
||||
|
||||
if (self.hideSources.indexOf('trash') == -1) {
|
||||
var deletedItems = Zotero.Items.getDeleted();
|
||||
if (deletedItems || Zotero.Prefs.get("showTrashWhenEmpty")) {
|
||||
self._showItem(new Zotero.ItemGroup('trash', false), 1, newRows+1);
|
||||
newRows++;
|
||||
}
|
||||
self.trashNotEmpty = !!deletedItems;
|
||||
}
|
||||
|
||||
return newRows;
|
||||
}
|
||||
|
@ -195,15 +218,22 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
|
||||
// Duplicate items
|
||||
if (self.hideSources.indexOf('duplicates') == -1
|
||||
&& duplicateLibraries.indexOf(groups[i].libraryID + '') != -1) {
|
||||
var d = new Zotero.Duplicates(groups[i].libraryID);
|
||||
self._showItem(new Zotero.ItemGroup('duplicates', d), 2);
|
||||
newRows++;
|
||||
}
|
||||
|
||||
// Unfiled items
|
||||
if (unfiledLibraries.indexOf(groups[i].libraryID + '') != -1) {
|
||||
var s = new Zotero.Search;
|
||||
s.id = parseInt('8634533000' + groups[i].libraryID); // 'UNFILED' + '000' + libraryID
|
||||
s.libraryID = groups[i].libraryID;
|
||||
s.name = Zotero.getString('pane.collections.unfiled');
|
||||
s.addCondition('libraryID', 'is', groups[i].libraryID);
|
||||
s.addCondition('unfiled', 'true');
|
||||
self._showItem(new Zotero.ItemGroup('search', s), 2);
|
||||
self._showItem(new Zotero.ItemGroup('unfiled', s), 2);
|
||||
newRows++;
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +251,7 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
|
||||
if (this.showCommons && Zotero.Commons.enabled) {
|
||||
if (this.hideSources.indexOf('commons') == -1 && Zotero.Commons.enabled) {
|
||||
this._showItem(new Zotero.ItemGroup('separator', false));
|
||||
var header = {
|
||||
id: "commons-header",
|
||||
|
@ -246,7 +276,14 @@ Zotero.CollectionTreeView.prototype.refresh = function()
|
|||
}
|
||||
}
|
||||
|
||||
this._refreshHashMap();
|
||||
try {
|
||||
this._refreshHashMap();
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug(e);
|
||||
throw (e);
|
||||
}
|
||||
|
||||
// Update the treebox's row count
|
||||
var diff = this.rowCount - oldCount;
|
||||
|
@ -274,7 +311,7 @@ Zotero.CollectionTreeView.prototype.reload = function()
|
|||
for(var i = 0; i < openCollections.length; i++)
|
||||
{
|
||||
var row = this._collectionRowMap[openCollections[i]];
|
||||
if (row != null) {
|
||||
if (typeof row != 'undefined') {
|
||||
this.toggleOpenState(row);
|
||||
}
|
||||
}
|
||||
|
@ -313,22 +350,20 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
|
|||
switch (type)
|
||||
{
|
||||
case 'collection':
|
||||
if(this._collectionRowMap[ids[i]] != null)
|
||||
{
|
||||
rows.push(this._collectionRowMap[ids[i]]);
|
||||
if (typeof this._rowMap['C' + ids[i]] != 'undefined') {
|
||||
rows.push(this._rowMap['C' + ids[i]]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
if(this._searchRowMap[ids[i]] != null)
|
||||
{
|
||||
rows.push(this._searchRowMap[ids[i]]);
|
||||
if (typeof this._rowMap['S' + ids[i]] != 'undefined') {
|
||||
rows.push(this._rowMap['S' + ids[i]]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
//if (this._groupRowMap[ids[i]] != null) {
|
||||
// rows.push(this._groupRowMap[ids[i]]);
|
||||
//if (this._rowMap['G' + ids[i]] != null) {
|
||||
// rows.push(this._rowMap['G' + ids[i]]);
|
||||
//}
|
||||
|
||||
// For now, just reload if a group is removed, since otherwise
|
||||
|
@ -410,7 +445,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
|
|||
this.rememberSelection(savedSelection);
|
||||
break;
|
||||
}
|
||||
this.selection.select(this._searchRowMap[ids]);
|
||||
this.selection.select(this._rowMap['S' + ids]);
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
|
@ -454,21 +489,6 @@ Zotero.CollectionTreeView.prototype.unregister = function()
|
|||
Zotero.Notifier.unregisterObserver(this._unregisterID);
|
||||
}
|
||||
|
||||
Zotero.CollectionTreeView.prototype.isLibrary = function(row)
|
||||
{
|
||||
return this._getItemAtRow(row).isLibrary();
|
||||
}
|
||||
|
||||
Zotero.CollectionTreeView.prototype.isCollection = function(row)
|
||||
{
|
||||
return this._getItemAtRow(row).isCollection();
|
||||
}
|
||||
|
||||
Zotero.CollectionTreeView.prototype.isSearch = function(row)
|
||||
{
|
||||
return this._getItemAtRow(row).isSearch();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
|
@ -498,17 +518,6 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
|
|||
}
|
||||
break;
|
||||
|
||||
case 'collection':
|
||||
// TODO: group collection
|
||||
return "chrome://zotero-platform/content/treesource-collection.png";
|
||||
|
||||
case 'search':
|
||||
if ((source.ref.id + "").match(/^8634533000/)) { // 'UNFILED000'
|
||||
collectionType = "search-virtual";
|
||||
break;
|
||||
}
|
||||
return "chrome://zotero-platform/content/treesource-search.png";
|
||||
|
||||
case 'header':
|
||||
if (source.ref.id == 'group-libraries-header') {
|
||||
collectionType = 'groups';
|
||||
|
@ -521,7 +530,12 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
|
|||
case 'group':
|
||||
collectionType = 'library';
|
||||
break;
|
||||
|
||||
case 'collection':
|
||||
case 'search':
|
||||
return "chrome://zotero-platform/content/treesource-" + collectionType + ".png";
|
||||
}
|
||||
|
||||
return "chrome://zotero/skin/treesource-" + collectionType + ".png";
|
||||
}
|
||||
|
||||
|
@ -752,12 +766,13 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the last-viewed source
|
||||
*/
|
||||
Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
|
||||
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
|
||||
var matches = lastViewedFolder.match(/^(?:(C|S|G)([0-9]+)|L)$/);
|
||||
var matches = lastViewedFolder.match(/^([A-Z])([0-9]+)?$/);
|
||||
var select = 0;
|
||||
if (matches) {
|
||||
if (matches[1] == 'C') {
|
||||
|
@ -813,11 +828,11 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (matches[1] == 'S' && this._searchRowMap[matches[2]]) {
|
||||
select = this._searchRowMap[matches[2]];
|
||||
}
|
||||
else if (matches[1] == 'G' && this._groupRowMap[matches[2]]) {
|
||||
select = this._groupRowMap[matches[2]];
|
||||
else {
|
||||
var id = matches[1] + (matches[2] ? matches[2] : "");
|
||||
if (this._rowMap[id]) {
|
||||
select = this._rowMap[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,20 +944,12 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
|
|||
for (var i=0, len=this.rowCount; i<len; i++) {
|
||||
if (this.selection.isSelected(i)) {
|
||||
var itemGroup = this._getItemAtRow(i);
|
||||
if (itemGroup.isLibrary()) {
|
||||
return 'L';
|
||||
var id = itemGroup.id;
|
||||
if (id) {
|
||||
return id;
|
||||
}
|
||||
else if (itemGroup.isCollection()) {
|
||||
return 'C' + itemGroup.ref.id;
|
||||
}
|
||||
else if (itemGroup.isSearch()) {
|
||||
return 'S' + itemGroup.ref.id;
|
||||
}
|
||||
else if (itemGroup.isTrash()) {
|
||||
return 'T';
|
||||
}
|
||||
else if (itemGroup.isGroup()) {
|
||||
return 'G' + itemGroup.ref.id;
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -954,49 +961,8 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
|
|||
*/
|
||||
Zotero.CollectionTreeView.prototype.rememberSelection = function(selection)
|
||||
{
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = selection.substr(1);
|
||||
switch (selection.substr(0, 1)) {
|
||||
// Library
|
||||
case 'L':
|
||||
this.selection.select(0);
|
||||
break;
|
||||
|
||||
// Collection
|
||||
case 'C':
|
||||
// This only selects the collection if it's still visible,
|
||||
// so we open the parent in notify()
|
||||
if (this._collectionRowMap[id] != undefined) {
|
||||
this.selection.select(this._collectionRowMap[id]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Saved search
|
||||
case 'S':
|
||||
if (this._searchRowMap[id] != undefined) {
|
||||
this.selection.select(this._searchRowMap[id]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Trash
|
||||
case 'T':
|
||||
if (this._getItemAtRow(this.rowCount-1).isTrash()){
|
||||
this.selection.select(this.rowCount-1);
|
||||
}
|
||||
else {
|
||||
this.selection.select(0);
|
||||
}
|
||||
break;
|
||||
|
||||
// Group
|
||||
case 'G':
|
||||
if (this._groupRowMap[id] != undefined) {
|
||||
this.selection.select(this._groupRowMap[i]);
|
||||
}
|
||||
break;
|
||||
if (selection && this._rowMap[selection] != 'undefined') {
|
||||
this.selection.select(this._rowMap[selection]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1015,26 +981,22 @@ Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) {
|
|||
|
||||
|
||||
|
||||
/*
|
||||
* Creates hash map of collection and search ids to row indexes
|
||||
* e.g., var rowForID = this._collectionRowMap[]
|
||||
/**
|
||||
* Creates mapping of item group ids to tree rows
|
||||
*/
|
||||
Zotero.CollectionTreeView.prototype._refreshHashMap = function()
|
||||
{
|
||||
this._collectionRowMap = [];
|
||||
this._searchRowMap = [];
|
||||
this._groupRowMap = [];
|
||||
for(var i=0; i < this.rowCount; i++){
|
||||
this._rowMap = [];
|
||||
for(var i = 0, len = this.rowCount; i < len; i++) {
|
||||
var itemGroup = this._getItemAtRow(i);
|
||||
if (itemGroup.isCollection(i)) {
|
||||
|
||||
// Collections get special treatment for now
|
||||
if (itemGroup.isCollection()) {
|
||||
this._collectionRowMap[itemGroup.ref.id] = i;
|
||||
}
|
||||
else if (itemGroup.isSearch(i)) {
|
||||
this._searchRowMap[itemGroup.ref.id] = i;
|
||||
}
|
||||
else if (itemGroup.isGroup(i)) {
|
||||
this._groupRowMap[itemGroup.ref.id] = i;
|
||||
}
|
||||
|
||||
this._rowMap[itemGroup.id] = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1663,7 +1625,36 @@ Zotero.ItemGroup = function(type, ref)
|
|||
this.ref = ref;
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isLibrary = function(includeGlobal)
|
||||
|
||||
Zotero.ItemGroup.prototype.__defineGetter__('id', function () {
|
||||
switch (this.type) {
|
||||
case 'library':
|
||||
return 'L';
|
||||
|
||||
case 'collection':
|
||||
return 'C' + this.ref.id;
|
||||
|
||||
case 'search':
|
||||
return 'S' + this.ref.id;
|
||||
|
||||
case 'duplicates':
|
||||
return 'D' + (this.ref.libraryID ? this.ref.libraryID : 0);
|
||||
|
||||
case 'unfiled':
|
||||
return 'U' + (this.ref.libraryID ? this.ref.libraryID : 0);
|
||||
|
||||
case 'trash':
|
||||
return 'T';
|
||||
|
||||
case 'group':
|
||||
return 'G' + this.ref.id;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.ItemGroup.prototype.isLibrary = function (includeGlobal)
|
||||
{
|
||||
if (includeGlobal) {
|
||||
return this.type == 'library' || this.type == 'group';
|
||||
|
@ -1681,14 +1672,12 @@ Zotero.ItemGroup.prototype.isSearch = function()
|
|||
return this.type == 'search';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isShare = function()
|
||||
{
|
||||
return this.type == 'share';
|
||||
Zotero.ItemGroup.prototype.isDuplicates = function () {
|
||||
return this.type == 'duplicates';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isBucket = function()
|
||||
{
|
||||
return this.type == 'bucket';
|
||||
Zotero.ItemGroup.prototype.isUnfiled = function () {
|
||||
return this.type == 'unfiled';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isTrash = function()
|
||||
|
@ -1696,18 +1685,29 @@ Zotero.ItemGroup.prototype.isTrash = function()
|
|||
return this.type == 'trash';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isGroup = function() {
|
||||
return this.type == 'group';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isHeader = function () {
|
||||
return this.type == 'header';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isGroup = function() {
|
||||
return this.type == 'group';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isSeparator = function () {
|
||||
return this.type == 'separator';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isBucket = function()
|
||||
{
|
||||
return this.type == 'bucket';
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.isShare = function()
|
||||
{
|
||||
return this.type == 'share';
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Special
|
||||
Zotero.ItemGroup.prototype.isWithinGroup = function () {
|
||||
|
@ -1725,7 +1725,7 @@ Zotero.ItemGroup.prototype.__defineGetter__('editable', function () {
|
|||
if (this.isGroup()) {
|
||||
return this.ref.editable;
|
||||
}
|
||||
if (this.isCollection() || this.isSearch()) {
|
||||
if (this.isCollection() || this.isSearch() || this.isDuplicates() || this.isUnfiled()) {
|
||||
var type = Zotero.Libraries.getType(libraryID);
|
||||
if (type == 'group') {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
|
||||
|
@ -1763,42 +1763,30 @@ Zotero.ItemGroup.prototype.__defineGetter__('filesEditable', function () {
|
|||
Zotero.ItemGroup.prototype.getName = function()
|
||||
{
|
||||
switch (this.type) {
|
||||
case 'collection':
|
||||
return this.ref.name;
|
||||
|
||||
case 'library':
|
||||
return Zotero.getString('pane.collections.library');
|
||||
|
||||
case 'search':
|
||||
return this.ref.name;
|
||||
|
||||
case 'share':
|
||||
return this.ref.name;
|
||||
|
||||
case 'bucket':
|
||||
return this.ref.name;
|
||||
|
||||
case 'trash':
|
||||
return Zotero.getString('pane.collections.trash');
|
||||
|
||||
case 'group':
|
||||
return this.ref.name;
|
||||
|
||||
case 'header':
|
||||
return this.ref.label;
|
||||
|
||||
default:
|
||||
case 'separator':
|
||||
return "";
|
||||
|
||||
default:
|
||||
return this.ref.name;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemGroup.prototype.getChildItems = function()
|
||||
Zotero.ItemGroup.prototype.getItems = function()
|
||||
{
|
||||
switch (this.type) {
|
||||
// Fake results if this is a shared library
|
||||
case 'share':
|
||||
return this.ref.getAll();
|
||||
|
||||
|
||||
case 'bucket':
|
||||
return this.ref.getItems();
|
||||
|
||||
|
@ -1823,16 +1811,7 @@ Zotero.ItemGroup.prototype.getChildItems = function()
|
|||
}
|
||||
|
||||
try {
|
||||
var ids;
|
||||
if (this.showDuplicates) {
|
||||
var duplicates = new Zotero.Duplicate;
|
||||
var tmpTable = s.search(true);
|
||||
ids = duplicates.getIDs(tmpTable);
|
||||
Zotero.DB.query("DROP TABLE " + tmpTable);
|
||||
}
|
||||
else {
|
||||
ids = s.search();
|
||||
}
|
||||
var ids = s.search();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackAllTransactions();
|
||||
|
@ -1853,33 +1832,38 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
|
|||
var includeScopeChildren = false;
|
||||
|
||||
// Create/load the inner search
|
||||
var s = new Zotero.Search();
|
||||
if (this.isLibrary()) {
|
||||
s.addCondition('libraryID', 'is', null);
|
||||
s.addCondition('noChildren', 'true');
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isGroup()) {
|
||||
s.addCondition('libraryID', 'is', this.ref.libraryID);
|
||||
s.addCondition('noChildren', 'true');
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isCollection()) {
|
||||
s.addCondition('noChildren', 'true');
|
||||
s.addCondition('collectionID', 'is', this.ref.id);
|
||||
if (Zotero.Prefs.get('recursiveCollections')) {
|
||||
s.addCondition('recursive', 'true');
|
||||
}
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isTrash()) {
|
||||
s.addCondition('deleted', 'true');
|
||||
}
|
||||
else if (this.isSearch()) {
|
||||
if (this.ref instanceof Zotero.Search) {
|
||||
var s = this.ref;
|
||||
}
|
||||
else if (this.isDuplicates()) {
|
||||
var s = this.ref.getSearchObject();
|
||||
}
|
||||
else {
|
||||
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
|
||||
var s = new Zotero.Search();
|
||||
if (this.isLibrary()) {
|
||||
s.addCondition('libraryID', 'is', null);
|
||||
s.addCondition('noChildren', 'true');
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isGroup()) {
|
||||
s.addCondition('libraryID', 'is', this.ref.libraryID);
|
||||
s.addCondition('noChildren', 'true');
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isCollection()) {
|
||||
s.addCondition('noChildren', 'true');
|
||||
s.addCondition('collectionID', 'is', this.ref.id);
|
||||
if (Zotero.Prefs.get('recursiveCollections')) {
|
||||
s.addCondition('recursive', 'true');
|
||||
}
|
||||
includeScopeChildren = true;
|
||||
}
|
||||
else if (this.isTrash()) {
|
||||
s.addCondition('deleted', 'true');
|
||||
}
|
||||
else {
|
||||
throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
|
||||
}
|
||||
}
|
||||
|
||||
// Create the outer (filter) search
|
||||
|
@ -1914,14 +1898,13 @@ Zotero.ItemGroup.prototype.getChildTags = function() {
|
|||
// TODO: implement?
|
||||
case 'share':
|
||||
return false;
|
||||
|
||||
|
||||
case 'bucket':
|
||||
return false;
|
||||
|
||||
case 'header':
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var s = this.getSearchObject();
|
||||
return Zotero.Tags.getAllWithinSearch(s);
|
||||
|
|
|
@ -106,10 +106,10 @@ Zotero.Item.prototype.__defineGetter__('dateAdded', function () { return this.ge
|
|||
Zotero.Item.prototype.__defineGetter__('dateModified', function () { return this.getField('dateModified'); });
|
||||
Zotero.Item.prototype.__defineGetter__('firstCreator', function () { return this.getField('firstCreator'); });
|
||||
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItems', function () { var ids = this._getRelatedItems(true); return ids ? ids : []; });
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItems', function () { var ids = this._getRelatedItems(true); return ids; });
|
||||
Zotero.Item.prototype.__defineSetter__('relatedItems', function (arr) { this._setRelatedItems(arr); });
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItemsReverse', function () { var ids = this._getRelatedItemsReverse(); return ids ? ids : []; });
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItemsBidirectional', function () { var ids = this._getRelatedItemsBidirectional(); return ids ? ids : []; });
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItemsReverse', function () { var ids = this._getRelatedItemsReverse(); return ids; });
|
||||
Zotero.Item.prototype.__defineGetter__('relatedItemsBidirectional', function () { var ids = this._getRelatedItemsBidirectional(); return ids; });
|
||||
|
||||
|
||||
Zotero.Item.prototype.getID = function() {
|
||||
|
@ -608,8 +608,10 @@ Zotero.Item.prototype.getFieldsNotInType = function (itemTypeID, allowBaseConver
|
|||
* Return an array of collectionIDs for all collections the item belongs to
|
||||
**/
|
||||
Zotero.Item.prototype.getCollections = function() {
|
||||
return Zotero.DB.columnQuery("SELECT collectionID FROM collectionItems "
|
||||
+ "WHERE itemID=" + this.id);
|
||||
var ids = Zotero.DB.columnQuery(
|
||||
"SELECT collectionID FROM collectionItems WHERE itemID=?", this.id
|
||||
);
|
||||
return ids ? ids : [];
|
||||
}
|
||||
|
||||
|
||||
|
@ -1105,7 +1107,7 @@ Zotero.Item.prototype.addRelatedItem = function (itemID) {
|
|||
}
|
||||
|
||||
var current = this._getRelatedItems(true);
|
||||
if (current && current.indexOf(itemID) != -1) {
|
||||
if (current.indexOf(itemID) != -1) {
|
||||
Zotero.debug("Item " + this.id + " already related to item "
|
||||
+ itemID + " in Zotero.Item.addItem()");
|
||||
return false;
|
||||
|
@ -1139,11 +1141,9 @@ Zotero.Item.prototype.removeRelatedItem = function (itemID) {
|
|||
itemID = parsedInt;
|
||||
|
||||
var current = this._getRelatedItems(true);
|
||||
if (current) {
|
||||
var index = current.indexOf(itemID);
|
||||
}
|
||||
var index = current.indexOf(itemID);
|
||||
|
||||
if (!current || index == -1) {
|
||||
if (index == -1) {
|
||||
Zotero.debug("Item " + this.id + " isn't related to item "
|
||||
+ itemID + " in Zotero.Item.removeRelatedItem()");
|
||||
return false;
|
||||
|
@ -1487,9 +1487,6 @@ Zotero.Item.prototype.save = function() {
|
|||
var removed = [];
|
||||
var newids = [];
|
||||
var currentIDs = this._getRelatedItems(true);
|
||||
if (!currentIDs) {
|
||||
currentIDs = [];
|
||||
}
|
||||
|
||||
if (this._previousData && this._previousData.related) {
|
||||
for each(var id in this._previousData.related) {
|
||||
|
@ -1763,6 +1760,16 @@ Zotero.Item.prototype.save = function() {
|
|||
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
|
||||
}
|
||||
else {
|
||||
// If undeleting, remove any merge-tracking relations
|
||||
var relations = Zotero.Relations.getByURIs(
|
||||
Zotero.URI.getItemURI(this),
|
||||
Zotero.Relations.deletedItemPredicate,
|
||||
false
|
||||
);
|
||||
for each(var relation in relations) {
|
||||
relation.erase();
|
||||
}
|
||||
|
||||
sql = "DELETE FROM deletedItems WHERE itemID=?";
|
||||
}
|
||||
Zotero.DB.query(sql, this.id);
|
||||
|
@ -1952,9 +1959,6 @@ Zotero.Item.prototype.save = function() {
|
|||
var removed = [];
|
||||
var newids = [];
|
||||
var currentIDs = this._getRelatedItems(true);
|
||||
if (!currentIDs) {
|
||||
currentIDs = [];
|
||||
}
|
||||
|
||||
if (this._previousData && this._previousData.related) {
|
||||
for each(var id in this._previousData.related) {
|
||||
|
@ -2418,7 +2422,7 @@ Zotero.Item.prototype.setNote = function(text) {
|
|||
* Returns child notes of this item
|
||||
*
|
||||
* @param {Boolean} includeTrashed Include trashed child items
|
||||
* @return {Integer[]} Array of itemIDs, or FALSE if none
|
||||
* @return {Integer[]} Array of itemIDs
|
||||
*/
|
||||
Zotero.Item.prototype.getNotes = function(includeTrashed) {
|
||||
if (this.isNote()) {
|
||||
|
@ -2442,7 +2446,7 @@ Zotero.Item.prototype.getNotes = function(includeTrashed) {
|
|||
|
||||
var notes = Zotero.DB.query(sql, this.id);
|
||||
if (!notes) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort by title
|
||||
|
@ -3204,7 +3208,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
|
|||
* Returns child attachments of this item
|
||||
*
|
||||
* @param {Boolean} includeTrashed Include trashed child items
|
||||
* @return {Integer[]} Array of itemIDs, or FALSE if none
|
||||
* @return {Integer[]} Array of itemIDs
|
||||
*/
|
||||
Zotero.Item.prototype.getAttachments = function(includeTrashed) {
|
||||
if (this.isAttachment()) {
|
||||
|
@ -3232,7 +3236,7 @@ Zotero.Item.prototype.getAttachments = function(includeTrashed) {
|
|||
|
||||
var attachments = Zotero.DB.query(sql, this.id);
|
||||
if (!attachments) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort by title
|
||||
|
@ -3322,7 +3326,7 @@ Zotero.Item.prototype.addTag = function(name, type) {
|
|||
|
||||
var matchingTags = Zotero.Tags.getIDs(name, this.libraryID);
|
||||
var itemTags = this.getTags();
|
||||
if (matchingTags && itemTags) {
|
||||
if (matchingTags && itemTags.length) {
|
||||
for each(var id in matchingTags) {
|
||||
if (itemTags.indexOf(id) != -1) {
|
||||
var tag = Zotero.Tags.get(id);
|
||||
|
@ -3422,17 +3426,17 @@ Zotero.Item.prototype.hasTags = function(tagIDs) {
|
|||
/**
|
||||
* Returns all tags assigned to an item
|
||||
*
|
||||
* @return array Array of Zotero.Tag objects
|
||||
* @return Array Array of Zotero.Tag objects
|
||||
*/
|
||||
Zotero.Item.prototype.getTags = function() {
|
||||
if (!this.id) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
var sql = "SELECT tagID, name FROM tags WHERE tagID IN "
|
||||
+ "(SELECT tagID FROM itemTags WHERE itemID=?)";
|
||||
var tags = Zotero.DB.query(sql, this.id);
|
||||
if (!tags) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
|
@ -3755,6 +3759,50 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare multiple items against this item and return fields that differ
|
||||
*
|
||||
* Currently compares only item data, not primary fields
|
||||
*/
|
||||
Zotero.Item.prototype.multiDiff = function (otherItems, ignoreFields) {
|
||||
var thisData = this.serialize();
|
||||
|
||||
var alternatives = {};
|
||||
var hasDiffs = false;
|
||||
|
||||
for each(var otherItem in otherItems) {
|
||||
var diff = [];
|
||||
var otherData = otherItem.serialize();
|
||||
var numDiffs = Zotero.Items.diff(thisData, otherData, diff);
|
||||
|
||||
if (numDiffs) {
|
||||
for (var field in diff[1].fields) {
|
||||
if (ignoreFields && ignoreFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = diff[1].fields[field];
|
||||
|
||||
if (!alternatives[field]) {
|
||||
hasDiffs = true;
|
||||
alternatives[field] = [value];
|
||||
}
|
||||
else if (alternatives[field].indexOf(value) == -1) {
|
||||
hasDiffs = true;
|
||||
alternatives[field].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDiffs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an unsaved copy of the item
|
||||
*
|
||||
|
@ -3781,13 +3829,14 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved) {
|
|||
var sameLibrary = newItem.libraryID == this.libraryID;
|
||||
}
|
||||
else {
|
||||
var newItem = new Zotero.Item(itemTypeID);
|
||||
var newItem = new Zotero.Item;
|
||||
var sameLibrary = true;
|
||||
|
||||
if (includePrimary) {
|
||||
newItem.id = this.id;
|
||||
newItem.libraryID = this.libraryID;
|
||||
newItem.key = this.key;
|
||||
newItem.itemTypeID = itemTypeID;
|
||||
for (var field in obj.primary) {
|
||||
switch (field) {
|
||||
case 'itemID':
|
||||
|
@ -3799,6 +3848,9 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved) {
|
|||
newItem.setField(field, obj.primary[field]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newItem.setType(itemTypeID);
|
||||
}
|
||||
}
|
||||
|
||||
var changedFields = {};
|
||||
|
@ -4026,13 +4078,11 @@ Zotero.Item.prototype.erase = function() {
|
|||
|
||||
// Flag related items for notification
|
||||
var relateds = this._getRelatedItemsBidirectional();
|
||||
if (relateds) {
|
||||
for each(var id in relateds) {
|
||||
var relatedItem = Zotero.Items.get(id);
|
||||
if (changedItems.indexOf(id) != -1) {
|
||||
changedItemsNotifierData[id] = { old: relatedItem.serialize() };
|
||||
changedItems.push(id);
|
||||
}
|
||||
for each(var id in relateds) {
|
||||
var relatedItem = Zotero.Items.get(id);
|
||||
if (changedItems.indexOf(id) != -1) {
|
||||
changedItemsNotifierData[id] = { old: relatedItem.serialize() };
|
||||
changedItems.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4042,9 +4092,9 @@ Zotero.Item.prototype.erase = function() {
|
|||
//Zotero.Fulltext.clearItemContent(this.id);
|
||||
}
|
||||
|
||||
// Remove relations
|
||||
var relation = Zotero.URI.getItemURI(this);
|
||||
Zotero.Relations.eraseByURIPrefix(relation);
|
||||
// Remove relations (except for merge tracker)
|
||||
var uri = Zotero.URI.getItemURI(this);
|
||||
Zotero.Relations.eraseByURIPrefix(uri, [Zotero.Relations.deletedItemPredicate]);
|
||||
|
||||
Zotero.DB.query('DELETE FROM annotations WHERE itemID=?', this.id);
|
||||
Zotero.DB.query('DELETE FROM highlights WHERE itemID=?', this.id);
|
||||
|
@ -4061,7 +4111,7 @@ Zotero.Item.prototype.erase = function() {
|
|||
Zotero.DB.query('DELETE FROM itemSeeAlso WHERE linkedItemID=?', this.id);
|
||||
|
||||
var tags = this.getTags();
|
||||
if (tags) {
|
||||
if (tags.length) {
|
||||
var hasTags = true;
|
||||
Zotero.DB.query('DELETE FROM itemTags WHERE itemID=?', this.id);
|
||||
// DEBUG: Hack to reload linked items -- replace with something better
|
||||
|
@ -4236,19 +4286,14 @@ Zotero.Item.prototype.toArray = function (mode) {
|
|||
|
||||
arr.tags = [];
|
||||
var tags = this.getTags();
|
||||
if (tags) {
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
var tag = tags[i].serialize();
|
||||
tag.tag = tag.fields.name;
|
||||
tag.type = tag.fields.type;
|
||||
arr.tags.push(tag);
|
||||
}
|
||||
for (var i=0, len=tags.length; i<len; i++) {
|
||||
var tag = tags[i].serialize();
|
||||
tag.tag = tag.fields.name;
|
||||
tag.type = tag.fields.type;
|
||||
arr.tags.push(tag);
|
||||
}
|
||||
|
||||
arr.related = this._getRelatedItemsBidirectional();
|
||||
if (!arr.related) {
|
||||
arr.related = [];
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
@ -4376,16 +4421,12 @@ Zotero.Item.prototype.serialize = function(mode) {
|
|||
|
||||
arr.tags = [];
|
||||
var tags = this.getTags();
|
||||
if (tags) {
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
arr.tags.push(tags[i].serialize());
|
||||
}
|
||||
for (var i=0, len=tags.length; i<len; i++) {
|
||||
arr.tags.push(tags[i].serialize());
|
||||
}
|
||||
|
||||
var related = this._getRelatedItems(true);
|
||||
var reverse = this._getRelatedItemsReverse();
|
||||
arr.related = related ? related : [];
|
||||
arr.relatedReverse = reverse ? reverse : [];
|
||||
arr.related = this._getRelatedItems(true);
|
||||
arr.relatedReverse = this._getRelatedItemsReverse();
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
@ -4502,7 +4543,7 @@ Zotero.Item.prototype._loadRelatedItems = function() {
|
|||
* Returns related items this item point to
|
||||
*
|
||||
* @param bool asIDs Return as itemIDs
|
||||
* @return array Array of itemIDs, or FALSE if none
|
||||
* @return array Array of itemIDs
|
||||
*/
|
||||
Zotero.Item.prototype._getRelatedItems = function (asIDs) {
|
||||
if (!this._relatedItemsLoaded) {
|
||||
|
@ -4510,7 +4551,7 @@ Zotero.Item.prototype._getRelatedItems = function (asIDs) {
|
|||
}
|
||||
|
||||
if (this._relatedItems.length == 0) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Return itemIDs
|
||||
|
@ -4534,28 +4575,33 @@ Zotero.Item.prototype._getRelatedItems = function (asIDs) {
|
|||
/**
|
||||
* Returns related items that point to this item
|
||||
*
|
||||
* @return array Array of itemIDs, or FALSE if none
|
||||
* @return array Array of itemIDs
|
||||
*/
|
||||
Zotero.Item.prototype._getRelatedItemsReverse = function () {
|
||||
if (!this.id) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
var sql = "SELECT itemID FROM itemSeeAlso WHERE linkedItemID=?";
|
||||
return Zotero.DB.columnQuery(sql, this.id);
|
||||
var ids = Zotero.DB.columnQuery(sql, this.id);
|
||||
if (!ids) {
|
||||
return [];
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns related items this item points to and that point to this item
|
||||
*
|
||||
* @return array|bool Array of itemIDs, or false if none
|
||||
* @return array Array of itemIDs
|
||||
*/
|
||||
Zotero.Item.prototype._getRelatedItemsBidirectional = function () {
|
||||
var related = this._getRelatedItems(true);
|
||||
var reverse = this._getRelatedItemsReverse();
|
||||
if (reverse) {
|
||||
if (!related) {
|
||||
|
||||
if (reverse.length) {
|
||||
if (!related.length) {
|
||||
return reverse;
|
||||
}
|
||||
|
||||
|
@ -4566,7 +4612,7 @@ Zotero.Item.prototype._getRelatedItemsBidirectional = function () {
|
|||
}
|
||||
}
|
||||
else if (!related) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
return related;
|
||||
}
|
||||
|
@ -4582,9 +4628,6 @@ Zotero.Item.prototype._setRelatedItems = function (itemIDs) {
|
|||
}
|
||||
|
||||
var currentIDs = this._getRelatedItems(true);
|
||||
if (!currentIDs) {
|
||||
currentIDs = [];
|
||||
}
|
||||
var oldIDs = []; // children being kept
|
||||
var newIDs = []; // new children
|
||||
|
||||
|
|
|
@ -374,6 +374,68 @@ Zotero.Items = new function() {
|
|||
}
|
||||
|
||||
|
||||
this.merge = function (item, otherItems) {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var otherItemIDs = [];
|
||||
var itemURI = Zotero.URI.getItemURI(item);
|
||||
|
||||
for each(var otherItem in otherItems) {
|
||||
// Move child items to master
|
||||
var ids = otherItem.getAttachments(true).concat(otherItem.getNotes(true));
|
||||
for each(var id in ids) {
|
||||
var attachment = Zotero.Items.get(id);
|
||||
|
||||
// TODO: Skip identical children?
|
||||
|
||||
attachment.setSource(item.id);
|
||||
attachment.save();
|
||||
}
|
||||
|
||||
// All other operations are additive only and do not affect the,
|
||||
// old item, which will be put in the trash
|
||||
|
||||
// Add collections to master
|
||||
var collectionIDs = otherItem.getCollections();
|
||||
for each(var collectionID in collectionIDs) {
|
||||
var collection = Zotero.Collections.get(collectionID);
|
||||
collection.addItem(item.id);
|
||||
}
|
||||
|
||||
// Add tags to master
|
||||
var tags = otherItem.getTags();
|
||||
for each(var tag in tags) {
|
||||
item.addTagByID(tag.id);
|
||||
}
|
||||
|
||||
// Related items
|
||||
var relatedItems = otherItem.relatedItemsBidirectional;
|
||||
Zotero.debug(item._getRelatedItems(true));
|
||||
for each(var relatedItemID in relatedItems) {
|
||||
item.addRelatedItem(relatedItemID);
|
||||
}
|
||||
item.save();
|
||||
|
||||
// Relations
|
||||
Zotero.Relations.copyURIs(
|
||||
item.libraryID,
|
||||
Zotero.URI.getItemURI(item),
|
||||
Zotero.URI.getItemURI(otherItem)
|
||||
);
|
||||
|
||||
// Add relation to track merge
|
||||
var otherItemURI = Zotero.URI.getItemURI(otherItem);
|
||||
Zotero.Relations.add(item.libraryID, otherItemURI, Zotero.Relations.deletedItemPredicate, itemURI);
|
||||
|
||||
// Trash other item
|
||||
otherItem.deleted = true;
|
||||
otherItem.save();
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.trash = function (ids) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
|
|
|
@ -176,6 +176,27 @@ Zotero.Relation.prototype.save = function () {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Relation.prototype.erase = function () {
|
||||
if (!this.id) {
|
||||
throw ("ID not set in Zotero.Relation.erase()");
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var deleteData = {};
|
||||
deleteData[this.id] = {
|
||||
old: this.serialize()
|
||||
}
|
||||
|
||||
var sql = "DELETE FROM relations WHERE ROWID=?";
|
||||
Zotero.DB.query(sql, [this.id]);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'relation', [this.id], deleteData);
|
||||
}
|
||||
|
||||
|
||||
Zotero.Relation.prototype.toXML = function () {
|
||||
var xml = <relation/>;
|
||||
xml.subject = this.subject;
|
||||
|
@ -183,3 +204,22 @@ Zotero.Relation.prototype.toXML = function () {
|
|||
xml.object = this.object;
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Relation.prototype.serialize = function () {
|
||||
// Use a hash of the parts as the object key
|
||||
var key = Zotero.Utilities.Internal.md5(this.subject + "_" + this.predicate + "_" + this.object);
|
||||
|
||||
var obj = {
|
||||
primary: {
|
||||
libraryID: this.libraryID,
|
||||
key: key,
|
||||
},
|
||||
fields: {
|
||||
subject: this.subject,
|
||||
predicate: this.predicate,
|
||||
object: this.object
|
||||
}
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ Zotero.Relations = new function () {
|
|||
Zotero.DataObjects.apply(this, ['relation']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
|
||||
this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy');
|
||||
|
||||
var _namespaces = {
|
||||
dc: 'http://purl.org/dc/elements/1.1/',
|
||||
owl: 'http://www.w3.org/2002/07/owl#'
|
||||
};
|
||||
|
||||
|
@ -46,7 +49,10 @@ Zotero.Relations = new function () {
|
|||
* @return {Object[]}
|
||||
*/
|
||||
this.getByURIs = function (subject, predicate, object) {
|
||||
predicate = _getPrefixAndValue(predicate).join(':');
|
||||
if (predicate) {
|
||||
predicate = _getPrefixAndValue(predicate).join(':');
|
||||
}
|
||||
|
||||
if (!subject && !predicate && !object) {
|
||||
throw ("No values provided in Zotero.Relations.get()");
|
||||
}
|
||||
|
@ -151,34 +157,66 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
|
||||
|
||||
this.erase = function (id) {
|
||||
/**
|
||||
* Copy relations from one object to another within the same library
|
||||
*/
|
||||
this.copyURIs = function (libraryID, fromURI, toURI) {
|
||||
var rels = this.getByURIs(fromURI);
|
||||
for each(var rel in rels) {
|
||||
this.add(libraryID, toURI, rel.predicate, rel.object);
|
||||
}
|
||||
|
||||
var rels = this.getByURIs(false, false, fromURI);
|
||||
for each(var rel in rels) {
|
||||
this.add(libraryID, rel.subject, rel.predicate, toURI);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} prefix
|
||||
* @param {String[]} ignorePredicates
|
||||
*/
|
||||
this.eraseByURIPrefix = function (prefix, ignorePredicates) {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = "DELETE FROM relations WHERE ROWID=?";
|
||||
Zotero.DB.query(sql, [id]);
|
||||
prefix = prefix + '%';
|
||||
var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
|
||||
var params = [prefix, prefix];
|
||||
if (ignorePredicates) {
|
||||
sql += " AND predicate != ?";
|
||||
params = params.concat(ignorePredicates);
|
||||
}
|
||||
var ids = Zotero.DB.columnQuery(sql, params);
|
||||
|
||||
// TODO: log to syncDeleteLog
|
||||
for each(var id in ids) {
|
||||
var relation = this.get(id);
|
||||
relation.erase();
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.eraseByURIPrefix = function (prefix) {
|
||||
prefix = prefix + '%';
|
||||
var sql = "DELETE FROM relations WHERE subject LIKE ? OR object LIKE ?";
|
||||
Zotero.DB.query(sql, [prefix, prefix]);
|
||||
}
|
||||
|
||||
|
||||
this.eraseByURI = function (uri) {
|
||||
var sql = "DELETE FROM relations WHERE subject=? OR object=?";
|
||||
Zotero.DB.query(sql, [uri, uri]);
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = "SELECT ROWID FROM relations WHERE subject=? OR object=?";
|
||||
var ids = Zotero.DB.columnQuery(sql, [uri, uri]);
|
||||
|
||||
for each(var id in ids) {
|
||||
var relation = this.get(id);
|
||||
relation.erase();
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.purge = function () {
|
||||
var sql = "SELECT subject FROM relations UNION SELECT object FROM relations";
|
||||
var uris = Zotero.DB.columnQuery(sql);
|
||||
var sql = "SELECT subject FROM relations WHERE predicate != ? "
|
||||
+ "UNION SELECT object FROM relations WHERE predicate != ?";
|
||||
var uris = Zotero.DB.columnQuery(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
|
||||
if (uris) {
|
||||
var prefix = Zotero.URI.defaultPrefix;
|
||||
Zotero.DB.beginTransaction();
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
Zotero.Duplicate = function(duplicateID) {
|
||||
this._id = duplicateID ? duplicateID : null;
|
||||
this._itemIDs = [];
|
||||
}
|
||||
|
||||
Zotero.Duplicate.prototype.__defineGetter__('id', function () { return this._id; });
|
||||
|
||||
Zotero.Duplicate.prototype.getIDs = function(idsTable) {
|
||||
if (!idsTable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var minLen = 5, percentLen = 1./3, checkLen, i, j;
|
||||
|
||||
var sql = "SELECT itemID, value AS val "
|
||||
+ "FROM " + idsTable + " NATURAL JOIN itemData "
|
||||
+ "NATURAL JOIN itemDataValues "
|
||||
+ "WHERE fieldID BETWEEN 110 AND 113 AND "
|
||||
+ "itemID NOT IN (SELECT itemID FROM itemAttachments) "
|
||||
+ "ORDER BY val";
|
||||
|
||||
var results = Zotero.DB.query(sql);
|
||||
|
||||
var resultsLen = results.length;
|
||||
this._itemIDs = [];
|
||||
|
||||
for (i = 0; i < resultsLen; i++) {
|
||||
results[i].len = results[i].val.length;
|
||||
}
|
||||
|
||||
for (i = 0; i < resultsLen; i++) {
|
||||
// title must be at least minLen long to be a duplicate
|
||||
if (results[i].len < minLen) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = i + 1; j < resultsLen; j++) {
|
||||
// duplicates must match the first checkLen characters
|
||||
// checkLen = percentLen * the length of the longer title
|
||||
checkLen = (results[i].len >= results[j].len) ?
|
||||
parseInt(percentLen * results[i].len) : parseInt(percentLen * results[j].len);
|
||||
checkLen = (checkLen > results[i].len) ? results[i].len : checkLen;
|
||||
checkLen = (checkLen > results[j].len) ? results[j].len : checkLen;
|
||||
checkLen = (checkLen < minLen) ? minLen : checkLen;
|
||||
|
||||
if (results[i].val.substr(0, checkLen) == results[j].val.substr(0, checkLen)) {
|
||||
// include results[i] when a duplicate is first found
|
||||
if (j == i + 1) {
|
||||
this._itemIDs.push(results[i].itemID);
|
||||
}
|
||||
this._itemIDs.push(results[j].itemID);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = j - 1;
|
||||
}
|
||||
|
||||
return this._itemIDs;
|
||||
}
|
286
chrome/content/zotero/xpcom/duplicates.js
Normal file
286
chrome/content/zotero/xpcom/duplicates.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Duplicates = function (libraryID) {
|
||||
if (typeof libraryID == 'undefined') {
|
||||
throw ("libraryID not provided in Zotero.Duplicates constructor");
|
||||
}
|
||||
|
||||
if (!libraryID) {
|
||||
libraryID = null;
|
||||
}
|
||||
|
||||
this._libraryID = libraryID;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Duplicates.prototype.__defineGetter__('name', function () "Duplicate Items"); // TODO: localize
|
||||
Zotero.Duplicates.prototype.__defineGetter__('libraryID', function () this._libraryID);
|
||||
|
||||
|
||||
/**
|
||||
* Get duplicates, populate a temporary table, and return a search based
|
||||
* on that table
|
||||
*
|
||||
* @return {Zotero.Search}
|
||||
*/
|
||||
Zotero.Duplicates.prototype.getSearchObject = function () {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = "DROP TABLE IF EXISTS tmpDuplicates";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
var sql = "CREATE TEMPORARY TABLE tmpDuplicates "
|
||||
+ "(id INTEGER PRIMARY KEY)";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
this._findDuplicates();
|
||||
var ids = this._sets.findAll(true);
|
||||
|
||||
sql = "INSERT INTO tmpDuplicates VALUES (?)";
|
||||
var insertStatement = Zotero.DB.getStatement(sql);
|
||||
|
||||
for each(var id in ids) {
|
||||
insertStatement.bindInt32Parameter(0, id);
|
||||
|
||||
try {
|
||||
insertStatement.execute();
|
||||
}
|
||||
catch(e) {
|
||||
throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
var s = new Zotero.Search;
|
||||
s.libraryID = this._libraryID;
|
||||
s.addCondition('tempTable', 'is', 'tmpDuplicates');
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds all items in the same set as a given item
|
||||
*
|
||||
* @param {Integer} itemID
|
||||
* @return {Integer[]} Array of itemIDs
|
||||
*/
|
||||
Zotero.Duplicates.prototype.getSetItemsByItemID = function (itemID) {
|
||||
return this._sets.findAllInSet(this._getObjectFromID(itemID), true);
|
||||
}
|
||||
|
||||
|
||||
Zotero.Duplicates.prototype._getObjectFromID = function (id) {
|
||||
return {
|
||||
get id() { return id; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.Duplicates.prototype._findDuplicates = function () {
|
||||
var self = this;
|
||||
|
||||
this._sets = new Zotero.DisjointSetForest;
|
||||
var sets = this._sets;
|
||||
|
||||
function normalizeString(str) {
|
||||
// Make sure we have a string and not an integer
|
||||
str = str + "";
|
||||
|
||||
str = Zotero.Utilities.removeDiacritics(str)
|
||||
.replace(/[^!-~]/g, ' ') // Convert punctuation to spaces
|
||||
.replace(/ +/, ' ') // Normalize spaces
|
||||
.toLowerCase();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} compareRows Comparison function, if not exact match
|
||||
*/
|
||||
function processRows(compareRows) {
|
||||
if (!rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0, len = rows.length; i < len; i++) {
|
||||
var j = i + 1, lastMatch = false, added = false;
|
||||
while (j < len) {
|
||||
if (compareRows) {
|
||||
var match = compareRows(rows[i], rows[j]);
|
||||
// Not a match, and don't try any more with this i value
|
||||
if (match == -1) {
|
||||
break;
|
||||
}
|
||||
// Not a match, but keep looking
|
||||
if (match == 0) {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// If no comparison function, check for exact match
|
||||
else {
|
||||
if (rows[i].value != rows[j].value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sets.union(
|
||||
self._getObjectFromID(rows[i].itemID),
|
||||
self._getObjectFromID(rows[j].itemID)
|
||||
);
|
||||
|
||||
lastMatch = j;
|
||||
j++;
|
||||
}
|
||||
if (lastMatch) {
|
||||
i = lastMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match on normalized title
|
||||
var sql = "SELECT itemID, value FROM items JOIN itemData USING (itemID) "
|
||||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND fieldID BETWEEN 110 AND 113 "
|
||||
+ "AND itemTypeID NOT IN (1, 14) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ "ORDER BY value COLLATE locale";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID]);
|
||||
processRows(function (a, b) {
|
||||
a = normalizeString(a.value);
|
||||
b = normalizeString(b.value);
|
||||
// If we stripped one of the strings completely, we can't compare them
|
||||
if (a.length == 0 || b.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
return a == b ? 1 : -1;
|
||||
});
|
||||
|
||||
// Match on exact fields
|
||||
var fields = ['DOI', 'ISBN'];
|
||||
for each(var field in fields) {
|
||||
var sql = "SELECT itemID, value FROM items JOIN itemData USING (itemID) "
|
||||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND fieldID=? "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ "ORDER BY value";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID, Zotero.ItemFields.getID(field)]);
|
||||
processRows();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implements the Disjoint Set data structure
|
||||
*
|
||||
* Based on pseudo-code from http://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
*
|
||||
* Objects passed should have .id properties that uniquely identify them
|
||||
*/
|
||||
|
||||
Zotero.DisjointSetForest = function () {
|
||||
this._objects = {};
|
||||
}
|
||||
|
||||
Zotero.DisjointSetForest.prototype.find = function (x) {
|
||||
var id = x.id;
|
||||
|
||||
// If we've seen this object before, use the existing copy,
|
||||
// which will have .parent and .rank properties
|
||||
if (this._objects[id]) {
|
||||
var obj = this._objects[id];
|
||||
}
|
||||
// Otherwise initialize it as a new set
|
||||
else {
|
||||
this._makeSet(x);
|
||||
this._objects[id] = x;
|
||||
var obj = x;
|
||||
}
|
||||
|
||||
if (obj.parent.id == obj.id) {
|
||||
return obj;
|
||||
}
|
||||
else {
|
||||
obj.parent = this.find(obj.parent);
|
||||
return obj.parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.DisjointSetForest.prototype.union = function (x, y) {
|
||||
var xRoot = this.find(x);
|
||||
var yRoot = this.find(y);
|
||||
|
||||
// Already in same set
|
||||
if (xRoot.id == yRoot.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (xRoot.rank < yRoot.rank) {
|
||||
xRoot.parent = yRoot;
|
||||
}
|
||||
else if (xRoot.rank > yRoot.rank) {
|
||||
yRoot.parent = xRoot;
|
||||
}
|
||||
else {
|
||||
yRoot.parent = xRoot;
|
||||
xRoot.rank = xRoot.rank + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero.DisjointSetForest.prototype.sameSet = function (x, y) {
|
||||
return this.find(x) == this.find(y);
|
||||
}
|
||||
|
||||
|
||||
Zotero.DisjointSetForest.prototype.findAll = function (asIDs) {
|
||||
var objects = [];
|
||||
for each(var obj in this._objects) {
|
||||
objects.push(asIDs ? obj.id : obj);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
|
||||
Zotero.DisjointSetForest.prototype.findAllInSet = function (x, asIDs) {
|
||||
var xRoot = this.find(x);
|
||||
var objects = [];
|
||||
for each(var obj in this._objects) {
|
||||
if (this.find(obj) == xRoot) {
|
||||
objects.push(asIDs ? obj.id : obj);
|
||||
}
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
|
||||
Zotero.DisjointSetForest.prototype._makeSet = function (x) {
|
||||
x.parent = x;
|
||||
x.rank = 0;
|
||||
}
|
|
@ -1203,7 +1203,40 @@ Zotero.Integration.Session.prototype.addCitation = function(index, noteIndex, ar
|
|||
var zoteroItem = false;
|
||||
if(citationItem.uri) {
|
||||
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(citationItem.uri);
|
||||
if(needUpdate) this.updateIndices[index] = true;
|
||||
if(zoteroItem) {
|
||||
if(needUpdate) this.updateIndices[index] = true;
|
||||
} else {
|
||||
// Try merged item mappings
|
||||
for each(var uri in citationItem.uri) {
|
||||
var seen = [];
|
||||
|
||||
// Follow merged item relations until we find an item
|
||||
// or hit a dead end
|
||||
while (!zoteroItem) {
|
||||
var relations = Zotero.Relations.getByURIs(uri, Zotero.Relations.deletedItemPredicate);
|
||||
// No merged items found
|
||||
if(!relations.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
uri = relations[0].object;
|
||||
|
||||
// Keep track of mapped URIs in case there's a circular relation
|
||||
if(seen.indexOf(uri) != -1) {
|
||||
var msg = "Circular relation for '" + uri + "' in merged item mapping resolution";
|
||||
Zotero.debug(msg, 2);
|
||||
Components.utils.reportError(msg);
|
||||
break;
|
||||
}
|
||||
seen.push(uri);
|
||||
|
||||
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs([uri]);
|
||||
}
|
||||
}
|
||||
|
||||
if(zoteroItem && needUpdate) this.updateIndices[index] = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
if(citationItem.key) {
|
||||
zoteroItem = Zotero.Items.getByKey(citationItem.key);
|
||||
|
@ -2044,8 +2077,13 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = function(uris) {
|
|||
|
||||
for(var i in uris) {
|
||||
try {
|
||||
zoteroItem = Zotero.URI.getURIItem(uris[i]);
|
||||
if(zoteroItem) break;
|
||||
zoteroItem = Zotero.URI.getURIItem(uris[i]);
|
||||
if(zoteroItem) {
|
||||
// Ignore items in the trash
|
||||
if(zoteroItem.deleted) {
|
||||
zoteroItem = false;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,19 +122,37 @@ Zotero.ItemTreeView.prototype.setTree = function(treebox)
|
|||
obj.refresh();
|
||||
|
||||
// Add a keypress listener for expand/collapse
|
||||
var expandAllRows = obj.expandAllRows;
|
||||
var collapseAllRows = obj.collapseAllRows;
|
||||
var tree = obj._treebox.treeBody.parentNode;
|
||||
var listener = function(event) {
|
||||
var key = String.fromCharCode(event.which);
|
||||
|
||||
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
obj.expandAllRows(treebox);
|
||||
// Handle arrow keys specially on multiple selection, since
|
||||
// otherwise the tree just applies it to the last-selected row
|
||||
if (event.keyCode == 39 || event.keyCode == 37) {
|
||||
if (obj._treebox.view.selection.count > 1) {
|
||||
switch (event.keyCode) {
|
||||
case 39:
|
||||
obj.expandSelectedRows();
|
||||
break;
|
||||
|
||||
case 37:
|
||||
obj.collapseSelectedRows();
|
||||
break;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (key == '-' && !(event.shiftKey || event.ctrlKey ||
|
||||
event.altKey || event.metaKey)) {
|
||||
obj.collapseAllRows(treebox);
|
||||
|
||||
var key = String.fromCharCode(event.which);
|
||||
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
obj.expandAllRows();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
obj.collapseAllRows();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -206,7 +224,8 @@ Zotero.ItemTreeView.prototype.refresh = function()
|
|||
Zotero.DB.beginTransaction();
|
||||
Zotero.Items.cacheFields(cacheFields);
|
||||
|
||||
var newRows = this._itemGroup.getChildItems();
|
||||
var newRows = this._itemGroup.getItems();
|
||||
|
||||
var added = 0;
|
||||
|
||||
for (var i=0, len=newRows.length; i < len; i++) {
|
||||
|
@ -276,6 +295,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
var sort = false;
|
||||
|
||||
var savedSelection = this.saveSelection();
|
||||
var previousRow = false;
|
||||
|
||||
// Redraw the tree (for tag color changes)
|
||||
if (action == 'redraw') {
|
||||
|
@ -303,26 +323,27 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._itemGroup.isShare()) {
|
||||
if (itemGroup.isShare()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection.selectEventsSuppressed = true;
|
||||
|
||||
// See if we're in the active window
|
||||
var zp = Zotero.getActiveZoteroPane();
|
||||
var activeWindow = zp && zp.itemsView == this;
|
||||
|
||||
var quicksearch = this._ownerDocument.getElementById('zotero-tb-search');
|
||||
|
||||
|
||||
// 'collection-item' ids are in the form collectionID-itemID
|
||||
if (type == 'collection-item') {
|
||||
if (!itemGroup.isCollection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var splitIDs = [];
|
||||
for each(var id in ids) {
|
||||
var split = id.split('-');
|
||||
// Skip if not collection or not an item in this collection
|
||||
if (!itemGroup.isCollection() || split[0] != this._itemGroup.ref.id) {
|
||||
// Skip if not an item in this collection
|
||||
if (split[0] != itemGroup.ref.id) {
|
||||
continue;
|
||||
}
|
||||
splitIDs.push(split[1]);
|
||||
|
@ -336,41 +357,51 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
}
|
||||
}
|
||||
|
||||
this.selection.selectEventsSuppressed = true;
|
||||
|
||||
if ((action == 'remove' && !itemGroup.isLibrary(true))
|
||||
|| action == 'delete' || action == 'trash') {
|
||||
|
||||
// Since a remove involves shifting of rows, we have to do it in order,
|
||||
// so sort the ids by row
|
||||
var rows = [];
|
||||
for(var i=0, len=ids.length; i<len; i++)
|
||||
{
|
||||
if (action == 'delete' || action == 'trash' ||
|
||||
!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);
|
||||
}
|
||||
}
|
||||
|
||||
// On a delete in duplicates mode, just refresh rather than figuring
|
||||
// out what to remove
|
||||
if (itemGroup.isDuplicates()) {
|
||||
previousRow = this._itemRowMap[ids[0]];
|
||||
this.refresh();
|
||||
madeChanges = true;
|
||||
sort = true;
|
||||
}
|
||||
else {
|
||||
// Since a remove involves shifting of rows, we have to do it in order,
|
||||
// so sort the ids by row
|
||||
var rows = [];
|
||||
for (var i=0, len=ids.length; i<len; i++) {
|
||||
if (action == 'delete' || action == 'trash' ||
|
||||
!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')
|
||||
{
|
||||
|
@ -435,6 +466,8 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
if (item.deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise the item has to be added
|
||||
if(item.isRegularItem() || !item.getSource())
|
||||
{
|
||||
//most likely, the note or attachment's parent was removed.
|
||||
|
@ -580,7 +613,9 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
}
|
||||
else
|
||||
{
|
||||
var previousRow = this._itemRowMap[ids[0]];
|
||||
if (previousRow === false) {
|
||||
previousRow = this._itemRowMap[ids[0]];
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
this.sort(typeof sort == 'number' ? sort : false);
|
||||
|
@ -589,14 +624,25 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
this._refreshHashMap();
|
||||
}
|
||||
|
||||
// On delete, select item at previous position
|
||||
if (action == 'delete' || action == 'remove') {
|
||||
if (this._dataItems[previousRow]) {
|
||||
this.selection.select(previousRow);
|
||||
// On removal of a row, select item at previous position
|
||||
if (action == 'remove' || action == 'trash' || action == 'delete') {
|
||||
// In duplicates view, select the next set on delete
|
||||
if (itemGroup.isDuplicates()) {
|
||||
if (this._dataItems[previousRow]) {
|
||||
// Mirror ZoteroPane.onTreeMouseDown behavior
|
||||
var itemID = this._dataItems[previousRow].ref.id;
|
||||
var setItemIDs = itemGroup.ref.getSetItemsByItemID(itemID);
|
||||
this.selectItems(setItemIDs);
|
||||
}
|
||||
}
|
||||
// If no item at previous position, select last item in list
|
||||
else if (this._dataItems[this._dataItems.length - 1]) {
|
||||
this.selection.select(this._dataItems.length - 1);
|
||||
else {
|
||||
if (this._dataItems[previousRow]) {
|
||||
this.selection.select(previousRow);
|
||||
}
|
||||
// If no item at previous position, select last item in list
|
||||
else if (this._dataItems[this._dataItems.length - 1]) {
|
||||
this.selection.select(this._dataItems.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -631,7 +677,6 @@ Zotero.ItemTreeView.prototype.unregister = function()
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// nsITreeView functions
|
||||
/// http://www.xulplanet.com/references/xpcomref/ifaces/nsITreeView.html
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -1378,6 +1423,41 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select multiple top-level items
|
||||
*
|
||||
* @param {Integer[]} ids An array of itemIDs
|
||||
*/
|
||||
Zotero.ItemTreeView.prototype.selectItems = function(ids) {
|
||||
if (ids.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = [];
|
||||
for each(var id in ids) {
|
||||
rows.push(this._itemRowMap[id]);
|
||||
}
|
||||
rows.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
this.selection.clearSelection();
|
||||
|
||||
this.selection.selectEventsSuppressed = true;
|
||||
|
||||
var lastStart = 0;
|
||||
for (var i = 0, len = rows.length; i < len; i++) {
|
||||
if (i == len - 1 || rows[i + 1] != rows[i] + 1) {
|
||||
this.selection.rangedSelect(rows[lastStart], rows[i], true);
|
||||
lastStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.selection.selectEventsSuppressed = false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return an array of Item objects for selected items
|
||||
*
|
||||
|
@ -1702,7 +1782,7 @@ Zotero.ItemTreeView.prototype.rememberFirstRow = function(firstRow) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.ItemTreeView.prototype.expandAllRows = function(treebox) {
|
||||
Zotero.ItemTreeView.prototype.expandAllRows = function() {
|
||||
this._treebox.beginUpdateBatch();
|
||||
for (var i=0; i<this.rowCount; i++) {
|
||||
if (this.isContainer(i) && !this.isContainerOpen(i)) {
|
||||
|
@ -1714,7 +1794,7 @@ Zotero.ItemTreeView.prototype.expandAllRows = function(treebox) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.ItemTreeView.prototype.collapseAllRows = function(treebox) {
|
||||
Zotero.ItemTreeView.prototype.collapseAllRows = function() {
|
||||
this._treebox.beginUpdateBatch();
|
||||
for (var i=0; i<this.rowCount; i++) {
|
||||
if (this.isContainer(i) && this.isContainerOpen(i)) {
|
||||
|
@ -1726,6 +1806,38 @@ Zotero.ItemTreeView.prototype.collapseAllRows = function(treebox) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.ItemTreeView.prototype.expandSelectedRows = function() {
|
||||
var start = {}, end = {};
|
||||
this._treebox.beginUpdateBatch();
|
||||
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 (this.isContainer(j) && !this.isContainerOpen(j)) {
|
||||
this.toggleOpenState(j, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._refreshHashMap();
|
||||
this._treebox.endUpdateBatch();
|
||||
}
|
||||
|
||||
|
||||
Zotero.ItemTreeView.prototype.collapseSelectedRows = function() {
|
||||
var start = {}, end = {};
|
||||
this._treebox.beginUpdateBatch();
|
||||
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 (this.isContainer(j) && this.isContainerOpen(j)) {
|
||||
this.toggleOpenState(j, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._refreshHashMap();
|
||||
this._treebox.endUpdateBatch();
|
||||
}
|
||||
|
||||
|
||||
Zotero.ItemTreeView.prototype.getVisibleFields = function() {
|
||||
var columns = [];
|
||||
for (var i=0, len=this._treebox.columns.count; i<len; i++) {
|
||||
|
|
|
@ -28,7 +28,7 @@ Zotero.Notifier = new function(){
|
|||
var _disabled = false;
|
||||
var _types = [
|
||||
'collection', 'creator', 'search', 'share', 'share-items', 'item',
|
||||
'collection-item', 'item-tag', 'tag', 'group', 'bucket'
|
||||
'collection-item', 'item-tag', 'tag', 'group', 'bucket', 'relation'
|
||||
];
|
||||
var _inTransaction;
|
||||
var _locked = false;
|
||||
|
@ -90,7 +90,7 @@ Zotero.Notifier = new function(){
|
|||
*
|
||||
* event: 'add', 'modify', 'delete', 'move' ('c', for changing parent),
|
||||
* 'remove' (ci, it), 'refresh', 'redraw', 'trash'
|
||||
* type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag', 'group'
|
||||
* type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag', 'group', 'relation'
|
||||
* ids - single id or array of ids
|
||||
*
|
||||
* Notes:
|
||||
|
@ -152,7 +152,7 @@ Zotero.Notifier = new function(){
|
|||
}
|
||||
|
||||
for (var i in _observers.items){
|
||||
Zotero.debug("Calling notify() on observer with hash '" + i + "'", 4);
|
||||
Zotero.debug("Calling notify('" + event + "') on observer with hash '" + i + "'", 4);
|
||||
// Find observers that handle notifications for this type (or all types)
|
||||
if (!_observers.get(i).types || _observers.get(i).types.indexOf(type)!=-1){
|
||||
// Catch exceptions so all observers get notified even if
|
||||
|
|
|
@ -984,7 +984,7 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
var data = Zotero.SearchConditions.get(this._conditions[i]['condition']);
|
||||
|
||||
// Has a table (or 'savedSearch', which doesn't have a table but isn't special)
|
||||
if (data.table || data.name == 'savedSearch') {
|
||||
if (data.table || data.name == 'savedSearch' || data.name == 'tempTable') {
|
||||
conditions.push({
|
||||
name: data['name'],
|
||||
alias: data['name']!=this._conditions[i]['condition']
|
||||
|
@ -1283,6 +1283,14 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
openParens++;
|
||||
break;
|
||||
|
||||
case 'tempTable':
|
||||
if (!condition.value.match(/^[a-zA-Z0-9]+$/)) {
|
||||
throw ("Invalid temp table '" + condition.value + "'");
|
||||
}
|
||||
condSQL += "itemID IN (SELECT id FROM " + condition.value + ")";
|
||||
skipOperators = true;
|
||||
break;
|
||||
|
||||
// For quicksearch blocks
|
||||
case 'blockStart':
|
||||
case 'blockEnd':
|
||||
|
@ -2142,6 +2150,13 @@ Zotero.SearchConditions = new function(){
|
|||
doesNotContain: true
|
||||
},
|
||||
special: false
|
||||
},
|
||||
|
||||
{
|
||||
name: 'tempTable',
|
||||
operators: {
|
||||
is: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -223,6 +223,10 @@ Zotero.Sync = new function() {
|
|||
|
||||
|
||||
function _loadObjectTypes() {
|
||||
// TEMP: Take this out once system.sql > 31
|
||||
var sql = "UPDATE syncObjectTypes SET name='relation' WHERE syncObjectTypeID=6 AND name='relations'";
|
||||
Zotero.DB.query(sql);
|
||||
|
||||
var sql = "SELECT * FROM syncObjectTypes";
|
||||
var types = Zotero.DB.query(sql);
|
||||
for each(var type in types) {
|
||||
|
|
|
@ -558,7 +558,125 @@ Zotero.Utilities = {
|
|||
|
||||
return newString;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Replaces accented characters in a string with ASCII equivalents
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {Boolean} [lowercaseOnly] Limit conversions to lowercase characters
|
||||
* (for improved performance on lowercase input)
|
||||
* @return {String}
|
||||
*
|
||||
* From http://lehelk.com/2011/05/06/script-to-remove-diacritics/
|
||||
*/
|
||||
"removeDiacritics": function (str, lowercaseOnly) {
|
||||
var map = this._diacriticsRemovalMap.lowercase;
|
||||
for (var i=0, len=map.length; i<len; i++) {
|
||||
str = str.replace(map[i].letters, map[i].base);
|
||||
}
|
||||
|
||||
if (!lowercaseOnly) {
|
||||
var map = this._diacriticsRemovalMap.uppercase;
|
||||
for (var i=0, len=map.length; i<len; i++) {
|
||||
str = str.replace(map[i].letters, map[i].base);
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
},
|
||||
|
||||
"_diacriticsRemovalMap": {
|
||||
uppercase: [
|
||||
{'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
|
||||
{'base':'AA','letters':/[\uA732]/g},
|
||||
{'base':'AE','letters':/[\u00C6\u01FC\u01E2]/g},
|
||||
{'base':'AO','letters':/[\uA734]/g},
|
||||
{'base':'AU','letters':/[\uA736]/g},
|
||||
{'base':'AV','letters':/[\uA738\uA73A]/g},
|
||||
{'base':'AY','letters':/[\uA73C]/g},
|
||||
{'base':'B', 'letters':/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
|
||||
{'base':'C', 'letters':/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
|
||||
{'base':'D', 'letters':/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
|
||||
{'base':'DZ','letters':/[\u01F1\u01C4]/g},
|
||||
{'base':'Dz','letters':/[\u01F2\u01C5]/g},
|
||||
{'base':'E', 'letters':/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
|
||||
{'base':'F', 'letters':/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
|
||||
{'base':'G', 'letters':/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
|
||||
{'base':'H', 'letters':/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
|
||||
{'base':'I', 'letters':/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
|
||||
{'base':'J', 'letters':/[\u004A\u24BF\uFF2A\u0134\u0248]/g},
|
||||
{'base':'K', 'letters':/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
|
||||
{'base':'L', 'letters':/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
|
||||
{'base':'LJ','letters':/[\u01C7]/g},
|
||||
{'base':'Lj','letters':/[\u01C8]/g},
|
||||
{'base':'M', 'letters':/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
|
||||
{'base':'N', 'letters':/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
|
||||
{'base':'NJ','letters':/[\u01CA]/g},
|
||||
{'base':'Nj','letters':/[\u01CB]/g},
|
||||
{'base':'O', 'letters':/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
|
||||
{'base':'OI','letters':/[\u01A2]/g},
|
||||
{'base':'OO','letters':/[\uA74E]/g},
|
||||
{'base':'OU','letters':/[\u0222]/g},
|
||||
{'base':'P', 'letters':/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
|
||||
{'base':'Q', 'letters':/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
|
||||
{'base':'R', 'letters':/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
|
||||
{'base':'S', 'letters':/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
|
||||
{'base':'T', 'letters':/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
|
||||
{'base':'TZ','letters':/[\uA728]/g},
|
||||
{'base':'U', 'letters':/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
|
||||
{'base':'V', 'letters':/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
|
||||
{'base':'VY','letters':/[\uA760]/g},
|
||||
{'base':'W', 'letters':/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
|
||||
{'base':'X', 'letters':/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
|
||||
{'base':'Y', 'letters':/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
|
||||
{'base':'Z', 'letters':/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
|
||||
],
|
||||
|
||||
lowercase: [
|
||||
{'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
|
||||
{'base':'aa','letters':/[\uA733]/g},
|
||||
{'base':'ae','letters':/[\u00E6\u01FD\u01E3]/g},
|
||||
{'base':'ao','letters':/[\uA735]/g},
|
||||
{'base':'au','letters':/[\uA737]/g},
|
||||
{'base':'av','letters':/[\uA739\uA73B]/g},
|
||||
{'base':'ay','letters':/[\uA73D]/g},
|
||||
{'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
|
||||
{'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
|
||||
{'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
|
||||
{'base':'dz','letters':/[\u01F3\u01C6]/g},
|
||||
{'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
|
||||
{'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
|
||||
{'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
|
||||
{'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
|
||||
{'base':'hv','letters':/[\u0195]/g},
|
||||
{'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
|
||||
{'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
|
||||
{'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
|
||||
{'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
|
||||
{'base':'lj','letters':/[\u01C9]/g},
|
||||
{'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
|
||||
{'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
|
||||
{'base':'nj','letters':/[\u01CC]/g},
|
||||
{'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
|
||||
{'base':'oi','letters':/[\u01A3]/g},
|
||||
{'base':'ou','letters':/[\u0223]/g},
|
||||
{'base':'oo','letters':/[\uA74F]/g},
|
||||
{'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
|
||||
{'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
|
||||
{'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
|
||||
{'base':'s','letters':/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
|
||||
{'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
|
||||
{'base':'tz','letters':/[\uA729]/g},
|
||||
{'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
|
||||
{'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
|
||||
{'base':'vy','letters':/[\uA761]/g},
|
||||
{'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
|
||||
{'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
|
||||
{'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
|
||||
{'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* Run sets of data through multiple asynchronous callbacks
|
||||
*
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -110,8 +110,6 @@
|
|||
label="Search for Shared Libraries" oncommand="Zotero.Zeroconf.findInstances()"/>
|
||||
<menuseparator id="zotero-tb-actions-plugins-separator"/>
|
||||
<menuitem id="zotero-tb-actions-timeline" label="&zotero.toolbar.timeline.label;" command="cmd_zotero_createTimeline"/>
|
||||
<!-- TODO: localize <menuitem id="zotero-tb-actions-duplicate" label="&zotero.toolbar.duplicate.label;" oncommand="ZoteroPane_Local.showDuplicates()"/>-->
|
||||
<menuitem id="zotero-tb-actions-showDuplicates" label="Show Duplicates" oncommand="ZoteroPane_Local.showDuplicates()" hidden="true"/>
|
||||
<menuseparator hidden="true" id="zotero-tb-actions-sync-separator"/>
|
||||
<menuitem hidden="true" label="WebDAV Sync Debugging" disabled="true"/>
|
||||
<menuitem hidden="true" label=" Purge Deleted Storage Files" oncommand="Zotero.Sync.Storage.purgeDeletedStorageFiles('webdav', function(results) { Zotero.debug(results); })"/>
|
||||
|
@ -153,8 +151,9 @@
|
|||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-snapshot" label="&zotero.items.menu.attach.snapshot;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromPage(false, itemID)"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-web-link" label="&zotero.items.menu.attach.link;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromPage(true, itemID)"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-web-link" label="&zotero.items.menu.attach.link.uri;" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromURI(true, itemID);"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-file" label="Attach Stored Copy of File..." oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(false, itemID);"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-link" label="Attach Link to File..." oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(true, itemID);"/>
|
||||
<!-- TODO: localize -->
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-file" label="Attach Stored Copy of File…" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(false, itemID);"/>
|
||||
<menuitem class="menuitem-iconic zotero-menuitem-attachments-link" label="Attach Link to File…" oncommand="var itemID = ZoteroPane_Local.getSelectedItems()[0].id; ZoteroPane_Local.addAttachmentFromDialog(true, itemID);"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
<toolbarseparator/>
|
||||
|
@ -231,7 +230,8 @@
|
|||
<menuitem label="&zotero.toolbar.newSavedSearch.label;" command="cmd_zotero_newSavedSearch"/>
|
||||
<menuitem label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection().id)"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane_Local.setUnfiled(ZoteroPane_Local.getSelectedLibraryID(), true)"/>
|
||||
<menuitem label="&zotero.toolbar.duplicate.label;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'duplicates', true)"/>
|
||||
<menuitem label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'unfiled', true)"/>
|
||||
<menuitem oncommand="ZoteroPane_Local.editSelectedCollection();"/>
|
||||
<menuitem oncommand="ZoteroPane_Local.deleteSelectedCollection();"/>
|
||||
<menuseparator/>
|
||||
|
@ -261,6 +261,8 @@
|
|||
<menuitem label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane_Local.duplicateSelectedItem();"/>
|
||||
<menuitem oncommand="ZoteroPane_Local.deleteSelectedItems();"/>
|
||||
<menuitem oncommand="ZoteroPane_Local.deleteSelectedItems(true);"/>
|
||||
<!-- TODO: localize -->
|
||||
<menuitem oncommand="ZoteroPane_Local.mergeSelectedItems();" label="Merge Items…"/>
|
||||
<menuseparator/>
|
||||
<menuitem oncommand="Zotero_File_Interface.exportItems();"/>
|
||||
<menuitem oncommand="Zotero_File_Interface.bibliographyFromItems();"/>
|
||||
|
@ -316,7 +318,7 @@
|
|||
enableColumnDrag="true"
|
||||
onfocus="if (ZoteroPane_Local.itemsView.rowCount && !ZoteroPane_Local.itemsView.selection.count) { ZoteroPane_Local.itemsView.selection.select(0); }"
|
||||
onkeypress="ZoteroPane_Local.handleKeyPress(event, this.id)"
|
||||
onselect="ZoteroPane_Local.itemSelected();"
|
||||
onselect="ZoteroPane_Local.itemSelected(event)"
|
||||
ondragstart="if (event.target.localName == 'treechildren') { ZoteroPane_Local.itemsView.onDragStart(event); }"
|
||||
ondragenter="return ZoteroPane_Local.itemsView.onDragEnter(event)"
|
||||
ondragover="return ZoteroPane_Local.itemsView.onDragOver(event)"
|
||||
|
@ -417,37 +419,8 @@
|
|||
onmousemove="ZoteroPane_Local.updateToolbarPosition()"
|
||||
oncommand="ZoteroPane_Local.updateToolbarPosition()"/>
|
||||
|
||||
<vbox id="zotero-item-pane" zotero-persist="width">
|
||||
<!-- TODO: localize -->
|
||||
<button id="zotero-item-restore-button" label="Restore to Library"
|
||||
oncommand="ZoteroPane_Local.restoreSelectedItems()" hidden="true"/>
|
||||
<!-- TODO: localize -->
|
||||
<button id="zotero-item-show-original" label="Show Original"
|
||||
oncommand="ZoteroPane_Local.showOriginalItem()" hidden="true"/>
|
||||
<deck id="zotero-item-pane-content" selectedIndex="0" flex="1">
|
||||
<groupbox pack="center" align="center">
|
||||
<label id="zotero-view-selected-label"/>
|
||||
</groupbox>
|
||||
<tabbox id="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
|
||||
<tabs>
|
||||
<tab label="&zotero.tabs.info.label;"/>
|
||||
<tab label="&zotero.tabs.notes.label;"/>
|
||||
<tab label="&zotero.tabs.tags.label;"/>
|
||||
<tab label="&zotero.tabs.related.label;"/>
|
||||
</tabs>
|
||||
<tabpanels id="zotero-view-item" flex="1"/>
|
||||
</tabbox>
|
||||
<!-- Note info pane -->
|
||||
<groupbox id="zotero-view-note" flex="1">
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"/>
|
||||
<button id="zotero-view-note-button" label="&zotero.notes.separate;" oncommand="ZoteroPane_Local.openNoteWindow(this.getAttribute('noteID')); if(this.hasAttribute('sourceID')) ZoteroPane_Local.selectItem(this.getAttribute('sourceID'));"/>
|
||||
</groupbox>
|
||||
<!-- Attachment info pane -->
|
||||
<groupbox flex="1">
|
||||
<zoteroattachmentbox id="zotero-attachment-box" flex="1"/>
|
||||
</groupbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
<!-- itemPane.xul -->
|
||||
<vbox id="zotero-item-pane"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<!ENTITY zotero.tabs.related.label "Related">
|
||||
<!ENTITY zotero.notes.separate "Edit in a separate window">
|
||||
|
||||
<!ENTITY zotero.toolbar.duplicate.label "Show Duplicates">
|
||||
<!ENTITY zotero.collections.showUnfiledItems "Show Unfiled Items">
|
||||
|
||||
<!ENTITY zotero.items.itemType "Item Type">
|
||||
|
@ -85,7 +86,6 @@
|
|||
<!ENTITY zotero.toolbar.export.label "Export Library…">
|
||||
<!ENTITY zotero.toolbar.rtfScan.label "RTF Scan…">
|
||||
<!ENTITY zotero.toolbar.timeline.label "Create Timeline">
|
||||
<!ENTITY zotero.toolbar.duplicate.label "Show Duplicates">
|
||||
<!ENTITY zotero.toolbar.preferences.label "Preferences…">
|
||||
<!ENTITY zotero.toolbar.supportAndDocumentation "Support and Documentation">
|
||||
<!ENTITY zotero.toolbar.about.label "About Zotero">
|
||||
|
|
|
@ -148,4 +148,10 @@ hbox.zotero-date-field-status > label
|
|||
background-position: center !important;
|
||||
border-width: 0 !important;
|
||||
-moz-border-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Merge pane in duplicates view */
|
||||
.zotero-field-version-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -1,4 +1,54 @@
|
|||
#zotero-item-pane-message {
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox, #zotero-item-pane-content > groupbox, #zotero-item-pane-content > groupbox > .groupbox-body
|
||||
{
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox tabs tab
|
||||
{
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox tabs tab .tab-text
|
||||
{
|
||||
margin-top: .2em !important;
|
||||
margin-bottom: .25em !important;
|
||||
}
|
||||
|
||||
#zotero-view-item
|
||||
{
|
||||
padding: 1.5em .25em .25em;
|
||||
}
|
||||
|
||||
#zotero-view-item > tabpanel > *
|
||||
{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#zotero-view-item > vbox
|
||||
{
|
||||
overflow: auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Merge pane in duplicates view */
|
||||
#zotero-duplicates-merge-button
|
||||
{
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#zotero-duplicates-merge-pane > groupbox
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#zotero-duplicates-merge-item-box row
|
||||
{
|
||||
min-height: 20px;
|
||||
}
|
||||
|
|
|
@ -429,35 +429,6 @@
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox, #zotero-item-pane-content > groupbox, #zotero-item-pane-content > groupbox > .groupbox-body
|
||||
{
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox tabs tab
|
||||
{
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox tabs tab .tab-text
|
||||
{
|
||||
margin-top: .2em !important;
|
||||
margin-bottom: .25em !important;
|
||||
}
|
||||
|
||||
#zotero-view-item
|
||||
{
|
||||
padding: 1.5em .25em .25em;
|
||||
}
|
||||
|
||||
#zotero-view-item > vbox
|
||||
{
|
||||
overflow: auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#zotero-splitter
|
||||
{
|
||||
border-top: none;
|
||||
|
|
BIN
chrome/skin/default/zotero/treesource-duplicates.png
Normal file
BIN
chrome/skin/default/zotero/treesource-duplicates.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 278 B |
Binary file not shown.
Before Width: | Height: | Size: 317 B |
|
@ -76,7 +76,7 @@ const xpcomFilesLocal = [
|
|||
'data/tags',
|
||||
'date',
|
||||
'db',
|
||||
'duplicate',
|
||||
'duplicates',
|
||||
'enstyle',
|
||||
'fulltext',
|
||||
'id',
|
||||
|
|
|
@ -1359,4 +1359,4 @@ INSERT INTO "syncObjectTypes" VALUES(2, 'creator');
|
|||
INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
||||
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
||||
INSERT INTO "syncObjectTypes" VALUES(5, 'tag');
|
||||
INSERT INTO "syncObjectTypes" VALUES(6, 'relations');
|
||||
INSERT INTO "syncObjectTypes" VALUES(6, 'relation');
|
||||
|
|
Loading…
Reference in a new issue