Configurable secondary sorting and other improvements

- Each column in the middle pane can now have its own persistent
  secondary sort column, configurable from a new submenu in the column
  picker menu (top right of items list). The settings are stored in
  extensions.zotero.secondarySort.[primaryField]. The submenu title
  includes the current primary field (e.g., "Secondary Sort (Creator)"),
  which is pretty weird, and I'm not sure I want to keep it, but it does
  convey that the setting is specific to the selected column.

- The fallback sort fields (firstCreator, date, title, dateAdded) are
  now configurable via the extensions.zotero.fallbackSort. Setting that
  pref to an empty string avoids all fallback sorts, which
  allows reverse-order clicking to set the order, as requested by
  @aurimasv in #275.

- The previous behavior of sorting based on the exact Creator string
  (rather than the actual creators) can now be restored with the
  extensions.zotero.sortCreatorAsString pref. (It simply circumvents all
  the newer code, so it's pretty safe.) This setting should result in
  faster sorting in large libraries that have many items with the same
  Creator string.

- Some of the lesser fields in the column picker menu are now in the
  More Columns submenu (which is now alphabetical)

- The "Type" column is now the less-ambiguous "Item Type".

- This uses a different method to modify the column picker menu that is
  simultaneously less and more hacky. (It no longer has to duplicate
  Mozilla code in a custom XBL binding that wouldn't reflect future
  upstream changes, and instead it bushwhacks its way through various
  boxObject properties to get to the underlying menupopup.)
This commit is contained in:
Dan Stillman 2014-05-07 04:22:49 -04:00
parent d65ee27592
commit dd477e15b8
10 changed files with 341 additions and 343 deletions

View file

@ -1,146 +0,0 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2012 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
The Initial Developer of this code is Gracile. Portions created by the
Initial Developer are Copyright © 2012 the Initial Developer.
This file contains code derived from Mozilla's tree.xml, © 2012 its
developers.
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 *****
-->
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="extended-columnpicker" display="xul:button"
extends="chrome://global/content/bindings/tree.xml#tree-base">
<content>
<xul:image class="tree-columnpicker-icon"/>
<xul:menupopup anonid="zotero-items-column-main-menu">
<xul:menuseparator/>
<xul:menu label="&zotero.items.moreColumns.label;">
<xul:menupopup anonid="zotero-items-column-sub-menu"/>
</xul:menu>
<xul:menuseparator/>
<xul:menuitem anonid="menuitem" label="&zotero.items.restoreColumnOrder.label;"/>
</xul:menupopup>
</content>
<implementation implements="nsIAccessibleProvider">
<property name="accessibleType" readonly="true">
<getter>
return Components.interfaces.nsIAccessibleProvider.XULButton;
</getter>
</property>
<method name="buildPopup">
<parameter name="aPopup"/>
<parameter name="bPopup"/>
<body>
<![CDATA[
// We no longer cache the picker content, remove the old content.
while (aPopup.childNodes.length > 4) {
aPopup.removeChild(aPopup.firstChild);
}
while (bPopup.childNodes.length > 0) {
bPopup.removeChild(bPopup.firstChild);
}
var refChild = aPopup.firstChild;
var refChild2 = bPopup.firstChild;
var tree = this.parentNode.parentNode;
for (var currCol = tree.columns.getFirstColumn(); currCol; currCol = currCol.getNext()) {
var currElement = currCol.element;
// Construct an entry for each column in the row, unless
// it is not being shown.
if ((!currElement.hasAttribute("ignoreincolumnpicker")) && (!currElement.hasAttribute("submenu"))) {
var popupChild = document.createElement("menuitem");
popupChild.setAttribute("type", "checkbox");
var columnName = currElement.getAttribute("display") || currElement.getAttribute("label");
popupChild.setAttribute("label", columnName);
popupChild.setAttribute("colindex", currCol.index);
if (currElement.getAttribute("hidden") != "true") {
popupChild.setAttribute("checked", "true");
}
if (currCol.primary) {
popupChild.setAttribute("disabled", "true");
}
aPopup.insertBefore(popupChild, refChild);
}
//Idem for the submenu
if ((!currElement.hasAttribute("ignoreincolumnpicker")) && (currElement.hasAttribute("submenu"))) {
var popupChild = document.createElement("menuitem");
popupChild.setAttribute("type", "checkbox");
var columnName = currElement.getAttribute("display") || currElement.getAttribute("label");
popupChild.setAttribute("label", columnName);
popupChild.setAttribute("colindex", currCol.index);
if (currElement.getAttribute("hidden") != "true") {
popupChild.setAttribute("checked", "true");
}
bPopup.insertBefore(popupChild, refChild2);
}
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="command">
<![CDATA[
if (event.originalTarget == this) {
var popup = document.getAnonymousElementByAttribute(this, "anonid", "zotero-items-column-main-menu");
var popup2 = document.getAnonymousElementByAttribute(this, "anonid", "zotero-items-column-sub-menu");
this.buildPopup(popup, popup2);
popup.showPopup(this, -1, -1, "popup", "bottomright", "topright");
} else {
var tree = this.parentNode.parentNode;
tree.stopEditing(true);
var menuitem = document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
if (event.originalTarget == menuitem) {
tree.columns.restoreNaturalOrder();
tree._ensureColumnOrder();
} else {
var colindex = event.originalTarget.getAttribute("colindex");
var column = tree.columns[colindex];
if (column) {
var element = column.element;
if (element.getAttribute("hidden") == "true") {
element.setAttribute("hidden", "false");
} else {
element.setAttribute("hidden", "true");
}
}
}
}
]]>
</handler>
</handlers>
</binding>
</bindings>

View file

@ -90,7 +90,7 @@
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-type" hidden="true"
id="zotero-items-column-itemType" hidden="true"
label="&zotero.items.type_column;"
width="40" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>

View file

@ -82,7 +82,7 @@
flex="1" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-type" hidden="true"
id="zotero-items-column-itemType" hidden="true"
label="&zotero.items.type_column;"
width="40" persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>

View file

@ -938,8 +938,7 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
if (column.id === "zotero-items-column-hasAttachment") {
return;
}
else if(column.id == "zotero-items-column-type")
{
else if (column.id == "zotero-items-column-itemType") {
val = Zotero.ItemTypes.getLocalizedString(obj.ref.itemTypeID);
}
// Year column is just date field truncated
@ -1295,14 +1294,14 @@ Zotero.ItemTreeView.prototype.cycleHeader = function(column)
*/
Zotero.ItemTreeView.prototype.sort = function(itemID)
{
var t = new Date;
// If Zotero pane is hidden, mark tree for sorting later in setTree()
if (!this._treebox.columns) {
this._needsSort = true;
return;
}
else {
this._needsSort = false;
}
this._needsSort = false;
// Single child item sort -- just toggle parent open and closed
if (itemID && this._itemRowMap[itemID] &&
@ -1313,36 +1312,19 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
return;
}
var columnField = this.getSortField();
var order = this.getSortDirection() == 'descending';
var primaryField = this.getSortField();
var sortFields = this.getSortFields();
var dir = this.getSortDirection();
var order = dir == 'descending' ? -1 : 1;
var collation = Zotero.getLocaleCollation();
var sortCreatorAsString = Zotero.Prefs.get('sortCreatorAsString');
// Year is really the date field truncated
var originalColumnField = columnField;
if (columnField == 'year') {
columnField = 'date';
}
// The visible fields affect the secondary sorting
var visibleFields = {};
this.getVisibleFields().forEach(function (val) {
visibleFields[val] = true;
});
// Some fields (e.g. dates) need to be retrieved unformatted for sorting
switch (columnField) {
case 'date':
var unformatted = true;
break;
default:
var unformatted = false;
}
Zotero.debug("Sorting items list by " + sortFields.join(", ") + " " + dir);
// Set whether rows with empty values should be displayed last,
// which may be different for primary and secondary sorting.
var emptyFirst = {};
switch (columnField) {
switch (primaryField) {
case 'title':
emptyFirst.title = true;
break;
@ -1357,36 +1339,22 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// Cache primary values while sorting, since base-field-mapped getField()
// calls are relatively expensive
var cache = {};
sortFields.forEach(function (x) cache[x] = {})
// Get the display field for a row (which might be a placeholder title)
var getField;
switch (originalColumnField) {
case 'title':
getField = function (row) {
var field;
var type = row.ref.itemTypeID;
switch (type) {
case 8: // letter
case 10: // interview
case 17: // case
field = row.ref.getDisplayTitle();
break;
default:
field = row.getField(columnField, unformatted);
}
// Ignore some leading and trailing characters when sorting
return Zotero.Items.getSortTitle(field);
};
break;
function getField(field, row) {
var item = row.ref;
case 'hasAttachment':
getField = function (row) {
if (row.ref.isAttachment()) {
var state = row.ref.fileExists() ? 1 : -1;
switch (field) {
case 'title':
return Zotero.Items.getSortTitle(item.getDisplayTitle());
case 'hasAttachment':
if (item.isAttachment()) {
var state = item.fileExists() ? 1 : -1;
}
else if (row.ref.isRegularItem()) {
var state = row.ref.getBestAttachmentState();
else if (item.isRegularItem()) {
var state = item.getBestAttachmentState();
}
else {
return 0;
@ -1396,20 +1364,23 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
state = 2;
}
return state * -1;
};
break;
case 'numNotes':
getField = function (row) {
// Sort descending by default
order = !order;
case 'numNotes':
return row.numNotes(false, true) || 0;
};
break;
case 'year':
getField = function (row) {
var val = row.getField(columnField, unformatted);
// Use unformatted part of date strings (YYYY-MM-DD) for sorting
case 'date':
var val = row.ref.getField('date', true);
if (val) {
val = val.substr(0, 10);
if (val.indexOf('0000') == 0) {
val = "";
}
}
return val;
case 'year':
var val = row.ref.getField('date', true);
if (val) {
val = val.substr(0, 4);
if (val == '0000') {
@ -1417,127 +1388,58 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
}
return val;
};
break;
default:
getField = function (row) row.getField(columnField, unformatted);
default:
return row.ref.getField(field, false, true);
}
}
var includeTrashed = this._itemGroup.isTrash();
var me = this,
isEmptyFirst = emptyFirst[columnField];
function rowSort(a, b) {
var cmp,
aItemID = a.id,
bItemID = b.id,
fieldA = cache[aItemID],
fieldB = cache[bItemID];
function fieldCompare(a, b, sortField) {
var aItemID = a.id;
var bItemID = b.id;
var fieldA = cache[sortField][aItemID];
var fieldB = cache[sortField][bItemID];
switch (columnField) {
case 'date':
fieldA = getField(a).substr(0, 10);
fieldB = getField(b).substr(0, 10);
cmp = strcmp(fieldA, fieldB);
if (cmp !== 0) {
return cmp;
}
break;
switch (sortField) {
case 'firstCreator':
cmp = creatorSort(a, b);
if (cmp !== 0) {
return cmp;
}
break;
return creatorSort(a, b);
case 'type':
case 'itemType':
var typeA = Zotero.ItemTypes.getLocalizedString(a.ref.itemTypeID);
var typeB = Zotero.ItemTypes.getLocalizedString(b.ref.itemTypeID);
cmp = (typeA > typeB) ? -1 : (typeA < typeB) ? 1 : 0;
if (cmp !== 0) {
return cmp;
}
break;
return (typeA > typeB) ? 1 : (typeA < typeB) ? -1 : 0;
default:
if (fieldA === undefined) {
cache[aItemID] = fieldA = getField(a);
cache[sortField][aItemID] = fieldA = getField(sortField, a);
}
if (fieldB === undefined) {
cache[bItemID] = fieldB = getField(b);
cache[sortField][bItemID] = fieldB = getField(sortField, b);
}
// Display rows with empty values last
if (!isEmptyFirst) {
if(fieldA === '' && fieldB !== '') return -1;
if(fieldA !== '' && fieldB === '') return 1;
if (!emptyFirst[sortField]) {
if(fieldA === '' && fieldB !== '') return 1;
if(fieldA !== '' && fieldB === '') return -1;
}
cmp = collation.compareString(1, fieldB, fieldA);
if (cmp !== 0) {
return cmp;
}
return collation.compareString(1, fieldA, fieldB);
}
if (columnField !== 'firstCreator') {
cmp = creatorSort(a, b);
}
function rowSort(a, b) {
var sortFields = Array.slice(arguments, 2);
var sortField;
while (sortField = sortFields.shift()) {
let cmp = fieldCompare(a, b, sortField);
if (cmp !== 0) {
return cmp;
}
}
if (columnField !== 'date') {
// If year is visible and not date, don't use full date
if (visibleFields.year && !visibleFields.date) {
fieldA = a.getField('date', true).substr(0, 4);
if (fieldA == '0000') {
fieldA = "";
}
fieldB = b.getField('date', true).substr(0, 4);
if (fieldB == '0000') {
fieldB = "";
}
cmp = strcmp(fieldA, fieldB);
if (cmp !== 0) {
return cmp;
}
}
// Otherwise use full date, even if Date column is hidden
else {
fieldA = a.getField('date', true).substr(0, 10);
fieldB = b.getField('date', true).substr(0, 10);
cmp = strcmp(fieldA, fieldB);
if (cmp !== 0) {
return cmp;
}
}
}
if (columnField !== 'title') {
fieldA = a.getField('title', true);
fieldB = b.getField('title', true);
if (!emptyFirst.title) {
if (fieldA === '' && fieldB !== '') return -1;
if (fieldA !== '' && fieldB === '') return 1;
}
cmp = collation.compareString(1, fieldB, fieldA);
if (cmp !== 0) {
return cmp;
}
}
fieldA = a.getField('dateAdded');
fieldB = b.getField('dateAdded');
return (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0;
return 0;
}
var firstCreatorSortCache = {};
@ -1565,8 +1467,8 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
return 0;
}
var cmp = strcmp(fieldA, fieldB, true);
if (cmp !== 0) {
var cmp = strcmp(fieldA, fieldB);
if (cmp !== 0 || sortCreatorAsString) {
return cmp;
}
@ -1663,14 +1565,14 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// Compare names
fieldA = Zotero.Items.getSortTitle(aCreators[aPos].ref.lastName);
fieldB = Zotero.Items.getSortTitle(bCreators[bPos].ref.lastName);
cmp = strcmp(fieldA, fieldB, true);
cmp = strcmp(fieldA, fieldB);
if (cmp) {
return cmp;
}
fieldA = Zotero.Items.getSortTitle(aCreators[aPos].ref.firstName);
fieldB = Zotero.Items.getSortTitle(bCreators[bPos].ref.firstName);
cmp = strcmp(fieldA, fieldB, true);
cmp = strcmp(fieldA, fieldB);
if (cmp) {
return cmp;
}
@ -1728,14 +1630,10 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
function strcmp(a, b, collationSort) {
// Display rows with empty values last
if(a === '' && b !== '') return -1;
if(a !== '' && b === '') return 1;
if (a === '' && b !== '') return 1;
if (a !== '' && b === '') return -1;
if (collationSort) {
return collation.compareString(1, b, a);
}
return (a > b) ? -1 : (a < b) ? 1 : 0;
return collation.compareString(1, a, b);
}
// Need to close all containers before sorting
@ -1748,23 +1646,19 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// Single-row sort
if (itemID) {
var row = this._itemRowMap[itemID];
for (var i=0, len=this._dataItems.length; i<len; i++) {
let row = this._itemRowMap[itemID];
for (let i=0, len=this._dataItems.length; i<len; i++) {
if (i === row) {
continue;
}
if (order) {
var cmp = -1*rowSort(this._dataItems[i], this._dataItems[row]);
}
else {
var cmp = rowSort(this._dataItems[i], this._dataItems[row]);
}
let cmp = rowSort.apply(this,
[this._dataItems[i], this._dataItems[row]].concat(sortFields)) * order;
// As soon as we find a value greater (or smaller if reverse sort),
// insert row at that position
if (cmp < 0) {
var rowItem = this._dataItems.splice(row, 1);
if (cmp > 0) {
let rowItem = this._dataItems.splice(row, 1);
this._dataItems.splice(row < i ? i-1 : i, 0, rowItem[0]);
this._treebox.invalidate();
break;
@ -1772,7 +1666,7 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// If greater than last row, move to end
if (i == len-1) {
var rowItem = this._dataItems.splice(row, 1);
let rowItem = this._dataItems.splice(row, 1);
this._dataItems.splice(i, 0, rowItem[0]);
this._treebox.invalidate();
}
@ -1780,8 +1674,9 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
// Full sort
else {
this._dataItems.sort(rowSort);
if(!order) this._dataItems.reverse();
this._dataItems.sort(function (a, b) {
return rowSort.apply(this, [a, b].concat(sortFields)) * order;
}.bind(this));
}
this._refreshHashMap();
@ -1793,6 +1688,8 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
this.selection.selectEventsSuppressed = false;
this._treebox.endUpdateBatch();
}
Zotero.debug("Sorted items list in " + (new Date - t) + " ms");
}
////////////////////////////////////////////////////////////////////////////////
@ -2374,6 +2271,39 @@ Zotero.ItemTreeView.prototype.getSortField = function() {
}
Zotero.ItemTreeView.prototype.getSortFields = function () {
var fields = [this.getSortField()];
var secondaryField = this.getSecondarySortField();
if (secondaryField) {
fields.push(secondaryField);
}
try {
var fallbackFields = Zotero.Prefs.get('fallbackSort')
.split(',')
.map((x) => x.trim())
.filter((x) => x !== '');
}
catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
// This should match the default value for the fallbackSort pref
var fallbackFields = ['firstCreator', 'date', 'title', 'dateAdded'];
}
fields = Zotero.Utilities.arrayUnique(fields.concat(fallbackFields));
// If date appears after year, remove it, unless it's the explicit secondary sort
var yearPos = fields.indexOf('year');
if (yearPos != -1) {
let datePos = fields.indexOf('date');
if (datePos > yearPos && secondaryField != 'date') {
fields.splice(datePos, 1);
}
}
return fields;
}
/*
* Returns 'ascending' or 'descending'
*/
@ -2386,6 +2316,197 @@ Zotero.ItemTreeView.prototype.getSortDirection = function() {
}
Zotero.ItemTreeView.prototype.getSecondarySortField = function () {
var primaryField = this.getSortField();
var secondaryField = Zotero.Prefs.get('secondarySort.' + primaryField);
if (!secondaryField || secondaryField == primaryField) {
return false;
}
return secondaryField;
}
Zotero.ItemTreeView.prototype.setSecondarySortField = function (secondaryField) {
var primaryField = this.getSortField();
var currentSecondaryField = this.getSecondarySortField();
var sortFields = this.getSortFields();
if (primaryField == secondaryField) {
return false;
}
if (currentSecondaryField) {
// If same as the current explicit secondary sort, ignore
if (currentSecondaryField == secondaryField) {
return false;
}
// If not, but same as first implicit sort, remove current explicit sort
if (sortFields[2] && sortFields[2] == secondaryField) {
Zotero.Prefs.clear('secondarySort.' + primaryField);
return true;
}
}
// If same as current implicit secondary sort, ignore
else if (sortFields[1] && sortFields[1] == secondaryField) {
return false;
}
Zotero.Prefs.set('secondarySort.' + primaryField, secondaryField);
return true;
}
/**
* Build the More Columns and Secondary Sort submenus while the popup is opening
*/
Zotero.ItemTreeView.prototype.onColumnPickerShowing = function (event) {
var menupopup = event.originalTarget;
var ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
var prefix = 'zotero-column-header-';
var doc = menupopup.ownerDocument;
var anonid = menupopup.getAttribute('anonid');
if (anonid.indexOf(prefix) == 0) {
return;
}
var lastChild = menupopup.lastChild;
// More Columns menu
try {
let id = prefix + 'more-menu';
let moreMenu = doc.createElementNS(ns, 'menu');
moreMenu.setAttribute('label', Zotero.getString('pane.items.columnChooser.moreColumns'));
moreMenu.setAttribute('anonid', id);
let moreMenuPopup = doc.createElementNS(ns, 'menupopup');
moreMenuPopup.setAttribute('anonid', id + '-popup');
let treecols = menupopup.parentNode.parentNode;
let subs = [x.getAttribute('label') for (x of treecols.getElementsByAttribute('submenu', 'true'))];
var moreItems = [];
for (let i=0; i<menupopup.childNodes.length; i++) {
let elem = menupopup.childNodes[i];
if (elem.localName == 'menuseparator') {
break;
}
if (elem.localName == 'menuitem' && subs.indexOf(elem.getAttribute('label')) != -1) {
moreItems.push(elem);
}
}
// Sort fields and move to submenu
var collation = Zotero.getLocaleCollation();
moreItems.sort(function (a, b) {
return collation.compareString(1, a.getAttribute('label'), b.getAttribute('label'));
});
moreItems.forEach(function (elem) {
moreMenuPopup.appendChild(menupopup.removeChild(elem));
});
moreMenu.appendChild(moreMenuPopup);
menupopup.insertBefore(moreMenu, lastChild);
}
catch (e) {
Components.utils.reportError(e);
Zotero.debug(e, 1);
}
// Secondary Sort menu
try {
let id = prefix + 'sort-menu';
let primaryField = this.getSortField();
let sortFields = this.getSortFields();
let secondaryField = false;
if (sortFields[1]) {
secondaryField = sortFields[1];
}
// Get localized names from treecols, since the names are currently done via .dtd
let treecols = menupopup.parentNode.parentNode;
let primaryFieldLabel = treecols.getElementsByAttribute('id',
'zotero-items-column-' + primaryField)[0].getAttribute('label');
let sortMenu = doc.createElementNS(ns, 'menu');
sortMenu.setAttribute('label',
Zotero.getString('pane.items.columnChooser.secondarySort', primaryFieldLabel));
sortMenu.setAttribute('anonid', id);
let sortMenuPopup = doc.createElementNS(ns, 'menupopup');
sortMenuPopup.setAttribute('anonid', id + '-popup');
// Generate menuitems
let sortOptions = [
'title',
'firstCreator',
'itemType',
'date',
'year',
'publisher',
'publicationTitle',
'dateAdded',
'dateModified'
];
for (let i=0; i<sortOptions.length; i++) {
let field = sortOptions[i];
// Hide current primary field, and don't show Year for Date, since it would be a no-op
if (field == primaryField || (primaryField == 'date' && field == 'year')) {
continue;
}
let label = treecols.getElementsByAttribute('id',
'zotero-items-column-' + field)[0].getAttribute('label');
let sortMenuItem = doc.createElementNS(ns, 'menuitem');
sortMenuItem.setAttribute('fieldName', field);
sortMenuItem.setAttribute('label', label);
sortMenuItem.setAttribute('type', 'checkbox');
if (field == secondaryField) {
sortMenuItem.setAttribute('checked', 'true');
}
sortMenuItem.setAttribute('oncommand',
'var view = ZoteroPane.itemsView; '
+ 'if (view.setSecondarySortField(this.getAttribute("fieldName"))) { view.sort(); }');
sortMenuPopup.appendChild(sortMenuItem);
}
sortMenu.appendChild(sortMenuPopup);
menupopup.insertBefore(sortMenu, lastChild);
}
catch (e) {
Components.utils.reportError(e);
Zotero.debug(e, 1);
}
sep = doc.createElementNS(ns, 'menuseparator');
sep.setAttribute('anonid', prefix + 'sep');
menupopup.insertBefore(sep, lastChild);
}
Zotero.ItemTreeView.prototype.onColumnPickerHidden = function (event) {
var menupopup = event.originalTarget;
var prefix = 'zotero-column-header-';
for (let i=0; i<menupopup.childNodes.length; i++) {
let elem = menupopup.childNodes[i];
if (elem.getAttribute('anonid').indexOf(prefix) == 0) {
try {
menupopup.removeChild(elem);
}
catch (e) {
Zotero.debug(e, 1);
}
i--;
}
}
}
////////////////////////////////////////////////////////////////////////////////
///
/// Command Controller:

View file

@ -1185,6 +1185,25 @@ var ZoteroPane = new function()
this.itemsView.addCallback(_setTagScope);
document.getElementById('zotero-items-tree').view = this.itemsView;
this.itemsView.selection.clearSelection();
// Add events to treecolpicker to update menu before showing/hiding
try {
let treecols = document.getElementById('zotero-items-columns-header');
let treecolpicker = treecols.boxObject.firstChild.nextSibling;
let menupopup = treecolpicker.boxObject.firstChild.nextSibling;
let attr = menupopup.getAttribute('onpopupshowing');
if (attr.indexOf('Zotero') == -1) {
menupopup.setAttribute('onpopupshowing', 'ZoteroPane.itemsView.onColumnPickerShowing(event);')
// Keep whatever else is there
+ ' ' + attr;
menupopup.setAttribute('onpopuphidden', 'ZoteroPane.itemsView.onColumnPickerHidden(event);')
// Keep whatever else is there
+ ' ' + menupopup.getAttribute('onpopuphidden');
}
}
catch (e) {
Zotero.debug(e);
}
}
finally {
Zotero.UnresponsiveScriptIndicator.enable();

View file

@ -359,7 +359,7 @@
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-type" hidden="true"
id="zotero-items-column-itemType" hidden="true"
label="&zotero.items.type_column;"
width="40" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
@ -385,21 +385,25 @@
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-journalAbbreviation" hidden="true"
submenu="true"
label="&zotero.items.journalAbbr_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-language" hidden="true"
submenu="true"
label="&zotero.items.language_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-accessDate" hidden="true"
submenu="true"
label="&zotero.items.accessDate_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>
<treecol
id="zotero-items-column-libraryCatalog" hidden="true"
submenu="true"
label="&zotero.items.libraryCatalog_column;"
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
<splitter class="tree-splitter"/>

View file

@ -45,7 +45,7 @@
<!ENTITY zotero.collections.showUnfiledItems "Show Unfiled Items">
<!ENTITY zotero.items.itemType "Item Type">
<!ENTITY zotero.items.type_column "Type">
<!ENTITY zotero.items.type_column "Item Type">
<!ENTITY zotero.items.title_column "Title">
<!ENTITY zotero.items.creator_column "Creator">
<!ENTITY zotero.items.date_column "Date">

View file

@ -192,6 +192,8 @@ tagColorChooser.numberKeyInstructions = You can add this tag to selecte
tagColorChooser.maxTags = Up to %S tags in each library can have colors assigned.
pane.items.loading = Loading items list…
pane.items.columnChooser.moreColumns = More Columns
pane.items.columnChooser.secondarySort = Secondary Sort (%S)
pane.items.attach.link.uri.title = Attach Link to URI
pane.items.attach.link.uri = Enter a URI:
pane.items.trash.title = Move to Trash

View file

@ -174,11 +174,6 @@ zoteroguidancepanel
-moz-binding: url('chrome://zotero/content/bindings/guidancepanel.xml#guidancepanel');
}
#zotero-items-columns-header > treecolpicker
{
-moz-binding: url('chrome://zotero/content/bindings/columnpicker.xml#extended-columnpicker');
}
zoterofilesyncstatus {
-moz-binding: url('chrome://zotero/content/bindings/filesyncstatus.xml#file-sync-status');
}

View file

@ -61,6 +61,9 @@ pref("extensions.zotero.lastViewedFolder", 'L');
pref("extensions.zotero.lastLongTagMode", 0);
pref("extensions.zotero.lastLongTagDelimiter", ";");
pref("extensions.zotero.fallbackSort", 'firstCreator,date,title,dateAdded');
pref("extensions.zotero.sortCreatorAsString", false);
//Tag Cloud
pref("extensions.zotero.tagCloud", false);