diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js
index bdfad648e..cfad71d48 100644
--- a/chrome/content/zotero/overlay.js
+++ b/chrome/content/zotero/overlay.js
@@ -225,6 +225,10 @@ var ZoteroPane = new function()
sep.nextSibling.nextSibling.nextSibling.hidden = false;
sep.nextSibling.nextSibling.nextSibling.nextSibling.hidden = false;
}
+
+ if (Zotero.Prefs.get('debugShowDuplicates')) {
+ document.getElementById('zotero-tb-actions-showDuplicates').hidden = false;
+ }
}
@@ -796,6 +800,7 @@ var ZoteroPane = new function()
var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
itemgroup.setSearch('');
itemgroup.setTags(getTagSelection());
+ itemgroup.showDuplicates = false;
try {
Zotero.UnresponsiveScriptIndicator.disable();
@@ -825,6 +830,23 @@ var ZoteroPane = new function()
}
+
+ this.showDuplicates = function () {
+ if (this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) {
+ var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ itemGroup.showDuplicates = true;
+
+ try {
+ Zotero.UnresponsiveScriptIndicator.disable();
+ this.itemsView.refresh();
+ }
+ finally {
+ Zotero.UnresponsiveScriptIndicator.enable();
+ }
+ }
+ }
+
+
function itemSelected()
{
if (!Zotero.stateCheck()) {
@@ -998,7 +1020,7 @@ var ZoteroPane = new function()
if (this.itemsView._itemGroup.isCollection()) {
var noPrompt = true;
}
- // Do nothing in search view
+ // Do nothing in search and share views
else if (this.itemsView._itemGroup.isSearch() ||
this.itemsView._itemGroup.isShare()) {
return;
diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul
index 4b0f2acbb..9829b6ace 100644
--- a/chrome/content/zotero/overlay.xul
+++ b/chrome/content/zotero/overlay.xul
@@ -128,6 +128,8 @@
label="Search for Shared Libraries" oncommand="Zotero.Zeroconf.findInstances()"/>
+
+
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
index b26bf5d29..38996578b 100644
--- a/chrome/content/zotero/xpcom/collectionTreeView.js
+++ b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -37,6 +37,7 @@ Zotero.CollectionTreeView = function()
this.itemToSelect = null;
this._highlightedRows = {};
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share']);
+ this.showDuplicates = false;
}
/*
@@ -1095,7 +1096,16 @@ Zotero.ItemGroup.prototype.getChildItems = function()
var s = this.getSearchObject();
try {
- var ids = s.search();
+ 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();
+ }
}
catch (e) {
Zotero.DB.rollbackAllTransactions();
diff --git a/chrome/content/zotero/xpcom/duplicate.js b/chrome/content/zotero/xpcom/duplicate.js
new file mode 100644
index 000000000..9d5824f6f
--- /dev/null
+++ b/chrome/content/zotero/xpcom/duplicate.js
@@ -0,0 +1,83 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright (c) 2006 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://chnm.gmu.edu
+
+ Licensed under the Educational Community License, Version 1.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.opensource.org/licenses/ecl1.php
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+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;
+}
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index 4fe90f86e..a40bb6ac7 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -67,6 +67,7 @@
+