Overhaul tags box
- Improvements to #20, with the tags box switching to a multiline textbox in the style of #164 on a multiline paste or Shift-Return. In the multiline box, Return is a newline and Shift-Return saves - Allow tabbing through tags via keyboard (and keep the last empty textbox open on tab, so you can hold down the tab key to get all the way to the end) - Fix various post-update focusing issues (though the wrong textbox is still selected for some multiline updates via Tab/Shift-Tab) - Make (single-line) tag entering much faster by not reloading the whole tags list and just placing the new tag in the correct sorted position. This could be made even faster with tag selector optimizations. - Allow the Add button to focus when switching to the Tags pane (and the same for the Related pane, for good measure)
This commit is contained in:
parent
d291084af6
commit
f932f312eb
5 changed files with 406 additions and 212 deletions
|
@ -289,7 +289,8 @@
|
||||||
<xul:vbox xbl:inherits="flex" class="zotero-box">
|
<xul:vbox xbl:inherits="flex" class="zotero-box">
|
||||||
<xul:hbox align="center">
|
<xul:hbox align="center">
|
||||||
<xul:label id="seeAlsoNum"/>
|
<xul:label id="seeAlsoNum"/>
|
||||||
<xul:button id="addButton" label="&zotero.item.add;" oncommand="this.parentNode.parentNode.parentNode.add();" hidden="true"/>
|
<xul:button id="addButton" label="&zotero.item.add;"
|
||||||
|
oncommand="this.parentNode.parentNode.parentNode.add();"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
<xul:grid flex="1">
|
<xul:grid flex="1">
|
||||||
<xul:columns>
|
<xul:columns>
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
|
|
||||||
<implementation>
|
<implementation>
|
||||||
<field name="clickHandler"/>
|
<field name="clickHandler"/>
|
||||||
|
|
||||||
|
<field name="_lastTabIndex">false</field>
|
||||||
|
<field name="_tabDirection"/>
|
||||||
<field name="_tagColors"/>
|
<field name="_tagColors"/>
|
||||||
<field name="_notifierID"/>
|
<field name="_notifierID"/>
|
||||||
|
|
||||||
|
@ -74,6 +77,10 @@
|
||||||
<property name="item" onget="return this._item;">
|
<property name="item" onget="return this._item;">
|
||||||
<setter>
|
<setter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
// Don't reload if item hasn't changed
|
||||||
|
if (this._item == val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._item = val;
|
this._item = val;
|
||||||
this.reload();
|
this.reload();
|
||||||
]]>
|
]]>
|
||||||
|
@ -143,11 +150,14 @@
|
||||||
<method name="reload">
|
<method name="reload">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
var self = this;
|
|
||||||
Zotero.debug('Reloading tags');
|
Zotero.debug('Reloading tags');
|
||||||
var addButton = self.id('addButton');
|
|
||||||
addButton.hidden = !self.editable;
|
|
||||||
|
|
||||||
|
// Cancel field focusing while we're updating
|
||||||
|
this._reloading = true;
|
||||||
|
|
||||||
|
this.id('addButton').hidden = !this.editable;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
return Zotero.Tags.getColors(self.item.libraryIDInt)
|
return Zotero.Tags.getColors(self.item.libraryIDInt)
|
||||||
.then(function (colors) {
|
.then(function (colors) {
|
||||||
self._tagColors = colors;
|
self._tagColors = colors;
|
||||||
|
@ -161,11 +171,12 @@
|
||||||
for (var i=0; i<tags.length; i++) {
|
for (var i=0; i<tags.length; i++) {
|
||||||
self.addDynamicRow(tags[i], i+1);
|
self.addDynamicRow(tags[i], i+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//self.fixPopup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateCount(0);
|
self.updateCount(0);
|
||||||
|
|
||||||
|
self._reloading = false;
|
||||||
|
self._focusField();
|
||||||
})
|
})
|
||||||
.done();
|
.done();
|
||||||
]]>
|
]]>
|
||||||
|
@ -187,29 +198,12 @@
|
||||||
name = '';
|
name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tabindex)
|
if (!tabindex) {
|
||||||
{
|
tabindex = this.id('tagRows').childNodes.length + 1;
|
||||||
if (this.id('tagRows').lastChild)
|
|
||||||
{
|
|
||||||
tabindex = parseInt(this.id('tagRows').lastChild.
|
|
||||||
firstChild.nextSibling.getAttribute('ztabindex')) + 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tabindex = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = document.createElement("image");
|
var icon = document.createElement("image");
|
||||||
icon.className = "zotero-box-icon";
|
icon.className = "zotero-box-icon";
|
||||||
var iconFile = 'tag';
|
|
||||||
if (type == 0) {
|
|
||||||
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.user'));
|
|
||||||
}
|
|
||||||
else if (type == 1) {
|
|
||||||
iconFile += '-automatic';
|
|
||||||
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.automatic'));
|
|
||||||
}
|
|
||||||
icon.setAttribute('src', 'chrome://zotero/skin/' + iconFile + '.png');
|
|
||||||
|
|
||||||
// DEBUG: Why won't just this.nextSibling.blur() work?
|
// DEBUG: Why won't just this.nextSibling.blur() work?
|
||||||
icon.setAttribute('onclick','if (this.nextSibling.inputField){ this.nextSibling.inputField.blur() }');
|
icon.setAttribute('onclick','if (this.nextSibling.inputField){ this.nextSibling.inputField.blur() }');
|
||||||
|
@ -220,15 +214,7 @@
|
||||||
var remove = document.createElement("label");
|
var remove = document.createElement("label");
|
||||||
remove.setAttribute('value','-');
|
remove.setAttribute('value','-');
|
||||||
remove.setAttribute('class','zotero-clicky zotero-clicky-minus');
|
remove.setAttribute('class','zotero-clicky zotero-clicky-minus');
|
||||||
if (tagID)
|
remove.setAttribute('tabindex', -1);
|
||||||
{
|
|
||||||
remove.setAttribute('ztabindex', -1);
|
|
||||||
remove.setAttribute('onclick',"document.getBindingParent(this).remove('"+ tagID +"');");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remove.setAttribute('disabled', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var row = document.createElement("row");
|
var row = document.createElement("row");
|
||||||
|
@ -238,19 +224,73 @@
|
||||||
row.appendChild(remove);
|
row.appendChild(remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagID)
|
this.updateRow(row, tagObj, tabindex);
|
||||||
{
|
|
||||||
row.setAttribute('id', 'tag-' + tagID);
|
|
||||||
row.setAttribute('tagType', type);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id('tagRows').appendChild(row);
|
this.id('tagRows').appendChild(row);
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="updateRow">
|
||||||
|
<parameter name="row"/>
|
||||||
|
<parameter name="tagObj"/>
|
||||||
|
<parameter name="tabindex"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
if (tagObj) {
|
||||||
|
var tagID = tagObj.id;
|
||||||
|
var type = tagObj.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon = row.firstChild;
|
||||||
|
var label = row.firstChild.nextSibling;
|
||||||
|
if (this.editable) {
|
||||||
|
var remove = row.lastChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row
|
||||||
|
if (tagObj) {
|
||||||
|
row.setAttribute('id', 'tag-' + tagID);
|
||||||
|
row.setAttribute('tagType', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
var iconFile = 'tag';
|
||||||
|
if (type == 0) {
|
||||||
|
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.user'));
|
||||||
|
}
|
||||||
|
else if (type == 1) {
|
||||||
|
iconFile += '-automatic';
|
||||||
|
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.automatic'));
|
||||||
|
}
|
||||||
|
icon.setAttribute('src', 'chrome://zotero/skin/' + iconFile + '.png');
|
||||||
|
|
||||||
|
// "-" button
|
||||||
|
if (this.editable) {
|
||||||
|
if (tagID) {
|
||||||
|
remove.setAttribute('disabled', false);
|
||||||
|
var self = this;
|
||||||
|
remove.addEventListener('click', function () {
|
||||||
|
self._lastTabIndex = false;
|
||||||
|
document.getBindingParent(this).remove(tagID);
|
||||||
|
|
||||||
|
// Return focus to items pane
|
||||||
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
|
if (tree) {
|
||||||
|
tree.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remove.setAttribute('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="createValueElement">
|
<method name="createValueElement">
|
||||||
<parameter name="valueText"/>
|
<parameter name="valueText"/>
|
||||||
<parameter name="tabindex"/>
|
<parameter name="tabindex"/>
|
||||||
|
@ -273,8 +313,6 @@
|
||||||
valueElement.className += ' zotero-clicky';
|
valueElement.className += ' zotero-clicky';
|
||||||
}
|
}
|
||||||
|
|
||||||
this._tabIndexMaxTagsFields = Math.max(this._tabIndexMaxTagsFields, tabindex);
|
|
||||||
|
|
||||||
var firstSpace;
|
var firstSpace;
|
||||||
if (typeof valueText == 'string') {
|
if (typeof valueText == 'string') {
|
||||||
firstSpace = valueText.indexOf(" ");
|
firstSpace = valueText.indexOf(" ");
|
||||||
|
@ -308,6 +346,8 @@
|
||||||
|
|
||||||
<method name="showEditor">
|
<method name="showEditor">
|
||||||
<parameter name="elem"/>
|
<parameter name="elem"/>
|
||||||
|
<parameter name="rows"/>
|
||||||
|
<parameter name="value"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
// Blur any active fields
|
// Blur any active fields
|
||||||
|
@ -323,7 +363,9 @@
|
||||||
var tabindex = elem.getAttribute('ztabindex');
|
var tabindex = elem.getAttribute('ztabindex');
|
||||||
|
|
||||||
var tagID = elem.parentNode.getAttribute('id').split('-')[1];
|
var tagID = elem.parentNode.getAttribute('id').split('-')[1];
|
||||||
var value = tagID ? Zotero.Tags.getName(tagID) : '';
|
if (!value) {
|
||||||
|
var value = tagID ? Zotero.Tags.getName(tagID) : '';
|
||||||
|
}
|
||||||
var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.id;
|
var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.id;
|
||||||
|
|
||||||
var t = document.createElement("textbox");
|
var t = document.createElement("textbox");
|
||||||
|
@ -331,33 +373,37 @@
|
||||||
t.setAttribute('fieldname', fieldName);
|
t.setAttribute('fieldname', fieldName);
|
||||||
t.setAttribute('ztabindex', tabindex);
|
t.setAttribute('ztabindex', tabindex);
|
||||||
t.setAttribute('flex', '1');
|
t.setAttribute('flex', '1');
|
||||||
t.setAttribute('newlines','pasteintact');
|
t.setAttribute('newlines','pasteintact');
|
||||||
|
// Multi-line
|
||||||
|
if (rows > 1) {
|
||||||
|
t.setAttribute('multiline', true);
|
||||||
|
t.setAttribute('rows', rows);
|
||||||
|
}
|
||||||
// Add auto-complete
|
// Add auto-complete
|
||||||
t.setAttribute('type', 'autocomplete');
|
else {
|
||||||
t.setAttribute('autocompletesearch', 'zotero');
|
t.setAttribute('type', 'autocomplete');
|
||||||
var suffix = itemID ? itemID : '';
|
t.setAttribute('autocompletesearch', 'zotero');
|
||||||
t.setAttribute('autocompletesearchparam', fieldName + '/' + suffix);
|
var suffix = itemID ? itemID : '';
|
||||||
|
t.setAttribute('autocompletesearchparam', fieldName + '/' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
var box = elem.parentNode;
|
var box = elem.parentNode;
|
||||||
box.replaceChild(t, elem);
|
box.replaceChild(t, elem);
|
||||||
|
|
||||||
|
t.setAttribute('onblur', "return document.getBindingParent(this).blurHandler(this)");
|
||||||
|
t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
|
||||||
|
t.setAttribute('onpaste', "return document.getBindingParent(this).handlePaste(event)");
|
||||||
|
|
||||||
|
this._tabDirection = false;
|
||||||
|
this._lastTabIndex = tabindex;
|
||||||
|
|
||||||
// Prevent error when clicking between a changed field
|
// Prevent error when clicking between a changed field
|
||||||
// and another -- there's probably a better way
|
// and another -- there's probably a better way
|
||||||
if (!t.select) {
|
if (!t.select) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
t.select();
|
t.select();
|
||||||
|
|
||||||
t.addEventListener('blur', function () {
|
|
||||||
document.getBindingParent(this).blurHandler(this);
|
|
||||||
}, false);
|
|
||||||
t.setAttribute('onkeypress', "return document.getBindingParent(this).handleKeyPress(event)");
|
|
||||||
|
|
||||||
this._tabDirection = false;
|
|
||||||
this._lastTabIndex = tabindex;
|
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
|
@ -373,23 +419,53 @@
|
||||||
|
|
||||||
switch (event.keyCode) {
|
switch (event.keyCode) {
|
||||||
case event.DOM_VK_RETURN:
|
case event.DOM_VK_RETURN:
|
||||||
|
var multiline = target.getAttribute('multiline');
|
||||||
|
var empty = target.value == "";
|
||||||
|
if (event.shiftKey) {
|
||||||
|
if (!multiline) {
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
var val = target.value;
|
||||||
|
if (val !== "") {
|
||||||
|
val += "\n";
|
||||||
|
}
|
||||||
|
self.makeMultiline(target, val, 6);
|
||||||
|
}, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Submit
|
||||||
|
}
|
||||||
|
else if (multiline) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var fieldname = 'tag';
|
var fieldname = 'tag';
|
||||||
|
|
||||||
// Prevent blur on containing textbox
|
|
||||||
// DEBUG: what happens if this isn't present?
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// If last tag row, create new one
|
// If last tag row, create new one
|
||||||
var row = target.parentNode.parentNode;
|
var row = Zotero.getAncestorByTagName(target, 'row');
|
||||||
if (row == row.parentNode.lastChild) {
|
|
||||||
|
// If non-empty last row, add new row
|
||||||
|
if (row == row.parentNode.lastChild && !empty) {
|
||||||
|
var focusField = true;
|
||||||
this._tabDirection = 1;
|
this._tabDirection = 1;
|
||||||
var lastTag = true;
|
}
|
||||||
|
// If empty non-last row, refocus current row
|
||||||
|
else if (row != row.parentNode.lastChild && empty) {
|
||||||
|
var focusField = true;
|
||||||
|
}
|
||||||
|
// If non-empty non-last row, return focus to items pane
|
||||||
|
else {
|
||||||
|
var focusField = false;
|
||||||
|
this._lastTabIndex = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
focused.blur();
|
focused.blur();
|
||||||
|
|
||||||
|
if (focusField) {
|
||||||
|
this._focusField();
|
||||||
|
}
|
||||||
// Return focus to items pane
|
// Return focus to items pane
|
||||||
if (!lastTag) {
|
else {
|
||||||
var tree = document.getElementById('zotero-items-tree');
|
var tree = document.getElementById('zotero-items-tree');
|
||||||
if (tree) {
|
if (tree) {
|
||||||
tree.focus();
|
tree.focus();
|
||||||
|
@ -400,10 +476,16 @@
|
||||||
|
|
||||||
case event.DOM_VK_ESCAPE:
|
case event.DOM_VK_ESCAPE:
|
||||||
// Reset field to original value
|
// Reset field to original value
|
||||||
target.value = target.getAttribute('value');
|
if (target.getAttribute('multiline')) {
|
||||||
|
target.value = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target.value = target.getAttribute('value');
|
||||||
|
}
|
||||||
|
|
||||||
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
|
var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
|
||||||
|
|
||||||
|
this._lastTabIndex = false;
|
||||||
focused.blur();
|
focused.blur();
|
||||||
|
|
||||||
if (tagsbox) {
|
if (tagsbox) {
|
||||||
|
@ -419,8 +501,17 @@
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case event.DOM_VK_TAB:
|
case event.DOM_VK_TAB:
|
||||||
|
// If already an empty last row, ignore forward tab
|
||||||
|
if (target.value == "" && !event.shiftKey) {
|
||||||
|
var row = Zotero.getAncestorByTagName(target, 'row');
|
||||||
|
if (row == row.parentNode.lastChild) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._tabDirection = event.shiftKey ? -1 : 1;
|
this._tabDirection = event.shiftKey ? -1 : 1;
|
||||||
focused.blur();
|
focused.blur();
|
||||||
|
this._focusField();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +520,63 @@
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Intercept paste, check for newlines, and convert textbox
|
||||||
|
to multiline if necessary
|
||||||
|
-->
|
||||||
|
<method name="handlePaste">
|
||||||
|
<parameter name="event"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
var textbox = event.target;
|
||||||
|
|
||||||
|
var clip = Components.classes["@mozilla.org/widget/clipboard;1"]
|
||||||
|
.getService(Components.interfaces.nsIClipboard);
|
||||||
|
var trans = Components.classes["@mozilla.org/widget/transferable;1"]
|
||||||
|
.createInstance(Components.interfaces.nsITransferable);
|
||||||
|
trans.addDataFlavor("text/unicode");
|
||||||
|
clip.getData(trans, clip.kGlobalClipboard);
|
||||||
|
var str = {};
|
||||||
|
try {
|
||||||
|
trans.getTransferData("text/unicode", str, {});
|
||||||
|
str = str.value.QueryInterface(Components.interfaces.nsISupportsString).data;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiline = !!str.trim().match(/\n/);
|
||||||
|
if (multiline) {
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
self.makeMultiline(textbox, str.trim());
|
||||||
|
}, 0);
|
||||||
|
// Cancel paste
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="makeMultiline">
|
||||||
|
<parameter name="textbox"/>
|
||||||
|
<parameter name="value"/>
|
||||||
|
<parameter name="rows"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
// If rows not specified, use one more than lines in input
|
||||||
|
if (!rows) {
|
||||||
|
rows = value.match(/\n/g).length + 1;
|
||||||
|
}
|
||||||
|
textbox = this.showEditor(textbox, rows, value);
|
||||||
|
// Move cursor to end
|
||||||
|
textbox.selectionStart = value.length;
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="hideEditor">
|
<method name="hideEditor">
|
||||||
<parameter name="textbox"/>
|
<parameter name="textbox"/>
|
||||||
|
@ -449,7 +597,7 @@
|
||||||
|
|
||||||
var fieldName = 'tag';
|
var fieldName = 'tag';
|
||||||
var tabindex = textbox.getAttribute('ztabindex');
|
var tabindex = textbox.getAttribute('ztabindex');
|
||||||
|
|
||||||
var value = textbox.value;
|
var value = textbox.value;
|
||||||
|
|
||||||
var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
|
var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
|
||||||
|
@ -468,69 +616,74 @@
|
||||||
var tagArray = value.split(/\r\n?|\n/);
|
var tagArray = value.split(/\r\n?|\n/);
|
||||||
|
|
||||||
if (saveChanges) {
|
if (saveChanges) {
|
||||||
if (id && (tagArray.length < 2)) {
|
// Modifying existing tag
|
||||||
|
if (id && tagArray.length < 2) {
|
||||||
if (value) {
|
if (value) {
|
||||||
var origTagIndex = this.item.getTagIndex(id);
|
var newTagID = tagsbox.replace(id, value);
|
||||||
var changed = tagsbox.replace(id, value);
|
if (newTagID) {
|
||||||
if (changed) {
|
id = newTagID;
|
||||||
var newTagIndex = this.item.getTagIndex(changed);
|
}
|
||||||
if (newTagIndex>origTagIndex) {
|
// Changed tag to existing
|
||||||
if (this._tabDirection == 1) {
|
|
||||||
this._lastTabIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (newTagIndex<origTagIndex) {
|
|
||||||
if (this._tabDirection == -1) {
|
|
||||||
this._lastTabIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value != Zotero.Tags.getName(id)) {
|
else if (value != Zotero.Tags.getName(id)) {
|
||||||
if (this._tabDirection == 1) {
|
if (this._tabDirection == 1) {
|
||||||
this._lastTabIndex -= 1;
|
this._lastTabIndex -= 1;
|
||||||
}
|
}
|
||||||
|
this.reload();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
var unchanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Existing tag cleared
|
||||||
|
else {
|
||||||
|
tagsbox.remove(id);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// New tag
|
// // Multiple tags
|
||||||
|
else if (tagArray.length > 1) {
|
||||||
|
var lastTag = row == row.parentNode.lastChild;
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
// If old tag isn't in array, remove it
|
||||||
|
if (id) {
|
||||||
|
var oldValue = Zotero.Tags.getName(id);
|
||||||
|
if (tagArray.indexOf(oldValue) == -1) {
|
||||||
|
this.item.removeTag(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.item.addTags(tagArray);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
// TODO: get tab index right
|
||||||
|
|
||||||
|
if (lastTag) {
|
||||||
|
this._lastTabIndex = this.item.getTags().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Single tag at end
|
||||||
else {
|
else {
|
||||||
//Check for newlines or carriage returns used as delimiters
|
id = tagsbox.add(value);
|
||||||
//in a series of tags added at once. Add each tag
|
// New tag
|
||||||
//separately.
|
if (id) {
|
||||||
if (tagArray.length > 1) {
|
// Stay put, since a tag was added above
|
||||||
var extremeTag = false;
|
|
||||||
var nextTag = false;
|
|
||||||
if (this._tabDirection == -1) {
|
if (this._tabDirection == -1) {
|
||||||
if (this._lastTabIndex == 1) {
|
this._tabDirection = false;
|
||||||
extremeTag = true;
|
|
||||||
} else {
|
|
||||||
nextTag = row.previousSibling.getAttribute('id').split('-')[1];
|
|
||||||
}
|
|
||||||
} else if (this._tabDirection == 1) {
|
|
||||||
if (this._lastTabIndex >= this.item.getTags().length) {
|
|
||||||
extremeTag = true;
|
|
||||||
} else {
|
|
||||||
nextTag = row.nextSibling.getAttribute('id').split('-')[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id = this.item.addTags(tagArray);
|
|
||||||
|
|
||||||
if (extremeTag) {
|
|
||||||
if (this._tabDirection == 1) {
|
|
||||||
this._lastTabIndex = this.item.getTags().length;
|
|
||||||
} else if (this._tabDirection == -1) {
|
|
||||||
this._lastTabIndex = 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._lastTabIndex = this.item.getTagIndex(nextTag)+1-this._tabDirection;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Already exists
|
||||||
else {
|
else {
|
||||||
id = tagsbox.add(value);
|
// Go back one, since we'll remove this below
|
||||||
}
|
if (this._tabDirection == 1) {
|
||||||
if (!id && (this._tabDirection==1)) {
|
this._lastTabIndex--;
|
||||||
this._lastTabIndex--;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,8 +694,55 @@
|
||||||
tabindex
|
tabindex
|
||||||
);
|
);
|
||||||
|
|
||||||
var box = textbox.parentNode;
|
var row = textbox.parentNode;
|
||||||
box.replaceChild(elem, textbox);
|
row.replaceChild(elem, textbox);
|
||||||
|
|
||||||
|
this.updateRow(row, Zotero.Tags.get(id), tabindex);
|
||||||
|
|
||||||
|
if (!unchanged) {
|
||||||
|
// Move row to appropriate place, alphabetically
|
||||||
|
var collation = Zotero.getLocaleCollation();
|
||||||
|
var rows = row.parentNode;
|
||||||
|
var labels = rows.getElementsByAttribute('fieldname', 'tag');
|
||||||
|
|
||||||
|
rows.removeChild(row);
|
||||||
|
var currentTabIndex = elem.getAttribute('ztabindex');
|
||||||
|
|
||||||
|
var before = null;
|
||||||
|
var inserted = false;
|
||||||
|
for (var i=0; i<labels.length; i++) {
|
||||||
|
let newTabIndex = i + 1;
|
||||||
|
if (inserted) {
|
||||||
|
labels[i].setAttribute('ztabindex', newTabIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collation.compareString(1, value, labels[i].textContent) > 0) {
|
||||||
|
labels[i].setAttribute('ztabindex', newTabIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.setAttribute('ztabindex', newTabIndex);
|
||||||
|
rows.insertBefore(row, labels[i].parentNode);
|
||||||
|
inserted = true;
|
||||||
|
|
||||||
|
// Adjust last tab index
|
||||||
|
if (this._tabDirection == -1) {
|
||||||
|
if (this._lastTabIndex > newTabIndex) {
|
||||||
|
this._lastTabIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this._tabDirection == 1) {
|
||||||
|
if (this._lastTabIndex < newTabIndex) {
|
||||||
|
this._lastTabIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inserted) {
|
||||||
|
elem.setAttribute('ztabindex', i + 1);
|
||||||
|
rows.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Just remove the row
|
// Just remove the row
|
||||||
|
@ -553,12 +753,6 @@
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
var focusBox = tagsbox;
|
|
||||||
|
|
||||||
if (this._tabDirection) {
|
|
||||||
this._focusNextField(focusBox, this._lastTabIndex, this._tabDirection == -1);
|
|
||||||
}
|
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -569,6 +763,7 @@
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
var row = this.addDynamicRow();
|
var row = this.addDynamicRow();
|
||||||
row.firstChild.nextSibling.click();
|
row.firstChild.nextSibling.click();
|
||||||
|
return row;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
@ -648,29 +843,6 @@
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<!-- No longer used -->
|
|
||||||
<method name="fixPopup">
|
|
||||||
<body>
|
|
||||||
<![CDATA[
|
|
||||||
// Hack to fix popup close problems after using
|
|
||||||
// autocomplete -- something to do with the popup used
|
|
||||||
// in the XBL autocomplete binding?
|
|
||||||
//
|
|
||||||
// We reset the popup manually if it's showing
|
|
||||||
if (this.parentNode.getAttribute('showing')=='true'){
|
|
||||||
//Zotero.debug('Fixing popup');
|
|
||||||
// The target element is 'tagsLabel', so change the
|
|
||||||
// path if the XUL DOM in the note editor XBL changes
|
|
||||||
this.parentNode.showPopup(
|
|
||||||
this.parentNode.parentNode.previousSibling,
|
|
||||||
-1, -1, 'popup');
|
|
||||||
}
|
|
||||||
]]>
|
|
||||||
</body>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
|
|
||||||
<method name="closePopup">
|
<method name="closePopup">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -683,69 +855,101 @@
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Advance the field focus forward or backward
|
Open the textbox for a particular label
|
||||||
|
|
||||||
Note: We're basically replicating the built-in tabindex functionality,
|
Note: We're basically replicating the built-in tabindex functionality,
|
||||||
which doesn't work well with the weird label/textbox stuff we're doing.
|
which doesn't work well with the weird label/textbox stuff we're doing.
|
||||||
(The textbox being tabbed away from is deleted before the blur()
|
(The textbox being tabbed away from is deleted before the blur()
|
||||||
completes, so it doesn't know where it's supposed to go next.)
|
completes, so it doesn't know where it's supposed to go next.)
|
||||||
-->
|
-->
|
||||||
<method name="_focusNextField">
|
<method name="_focusField">
|
||||||
<parameter name="box"/>
|
|
||||||
<parameter name="tabindex"/>
|
|
||||||
<parameter name="back"/>
|
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
tabindex = parseInt(tabindex);
|
if (this._reloading) {
|
||||||
if (back) {
|
return;
|
||||||
switch (tabindex) {
|
}
|
||||||
case 1:
|
|
||||||
return false;
|
if (this._lastTabIndex === false) {
|
||||||
|
return;
|
||||||
default:
|
}
|
||||||
var nextIndex = tabindex - 1;
|
|
||||||
|
var maxIndex = this.id('tagRows').childNodes.length + 1;
|
||||||
|
|
||||||
|
var tabindex = parseInt(this._lastTabIndex);
|
||||||
|
var dir = this._tabDirection;
|
||||||
|
|
||||||
|
if (dir == 1) {
|
||||||
|
var nextIndex = tabindex + 1;
|
||||||
|
}
|
||||||
|
else if (dir == -1) {
|
||||||
|
if (tabindex == 1) {
|
||||||
|
// Focus Add button
|
||||||
|
this.id('addButton').focus();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
var nextIndex = tabindex - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (tabindex) {
|
var nextIndex = tabindex;
|
||||||
case this._tabIndexMaxTagsFields:
|
|
||||||
// In tags box, keep going to create new row
|
|
||||||
var nextIndex = tabindex + 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
var nextIndex = tabindex + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextIndex = Math.min(nextIndex, maxIndex);
|
||||||
|
|
||||||
Zotero.debug('Looking for tabindex ' + nextIndex, 4);
|
Zotero.debug('Looking for tabindex ' + nextIndex, 4);
|
||||||
|
|
||||||
var next = document.getAnonymousNodes(box)[0].
|
var next = document.getAnonymousNodes(this)[0]
|
||||||
getElementsByAttribute('ztabindex', nextIndex);
|
.getElementsByAttribute('ztabindex', nextIndex);
|
||||||
if (!next[0]) {
|
if (next.length) {
|
||||||
next[0] = box.addDynamicRow();
|
next = next[0];
|
||||||
}
|
next.click();
|
||||||
|
|
||||||
next[0].click();
|
|
||||||
|
|
||||||
// DEBUG: next[0] is always equal to the target element,
|
|
||||||
// but for some reason it's necessary to scroll to the next
|
|
||||||
// element when moving forward for the target element to
|
|
||||||
// be fully in view
|
|
||||||
if (!back && next[0].parentNode.nextSibling) {
|
|
||||||
var visElem = next[0].parentNode.nextSibling;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var visElem = next[0];
|
next = this.new();
|
||||||
|
next = next.firstChild.nextSibling;
|
||||||
}
|
}
|
||||||
this.ensureElementIsVisible(visElem);
|
|
||||||
|
|
||||||
return true;
|
if (!next) {
|
||||||
|
Components.utils.reportError('Next row not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ensureElementIsVisible(next);
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<method name="_onAddButtonKeypress">
|
||||||
|
<parameter name="event"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
if (event.keyCode != event.DOM_VK_TAB || event.shiftKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._lastTabIndex = 0;
|
||||||
|
this._tabDirection = 1;
|
||||||
|
this._focusField();
|
||||||
|
return false;
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- unused -->
|
||||||
|
<method name="getTagIndex">
|
||||||
|
<parameter name="id"/>
|
||||||
|
<body><![CDATA[
|
||||||
|
var rows = this.id('tagRows').getElementsByTagName('row');
|
||||||
|
for (let i=0; i<rows.length; i++) {
|
||||||
|
var row = rows[i].getAttribute('id');
|
||||||
|
if (row && row.split("-")[1] == id) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
|
||||||
<method name="scrollToTop">
|
<method name="scrollToTop">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
@ -778,6 +982,8 @@
|
||||||
<method name="blurOpenField">
|
<method name="blurOpenField">
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
this._lastTabIndex = false;
|
||||||
|
|
||||||
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
|
var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
|
||||||
if (textboxes && textboxes.length) {
|
if (textboxes && textboxes.length) {
|
||||||
textboxes[0].inputField.blur();
|
textboxes[0].inputField.blur();
|
||||||
|
@ -800,7 +1006,9 @@
|
||||||
<xul:scrollbox xbl:inherits="flex" orient="vertical" style="overflow:auto" class="zotero-box">
|
<xul:scrollbox xbl:inherits="flex" orient="vertical" style="overflow:auto" class="zotero-box">
|
||||||
<xul:hbox align="center">
|
<xul:hbox align="center">
|
||||||
<xul:label id="tagsNum"/>
|
<xul:label id="tagsNum"/>
|
||||||
<xul:button id="addButton" label="&zotero.item.add;" oncommand="document.getBindingParent(this).new();" hidden="true"/>
|
<xul:button id="addButton" label="&zotero.item.add;"
|
||||||
|
onkeypress="return document.getBindingParent(this)._onAddButtonKeypress(event)"
|
||||||
|
oncommand="document.getBindingParent(this).new();"/>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
<xul:grid>
|
<xul:grid>
|
||||||
<xul:columns>
|
<xul:columns>
|
||||||
|
|
|
@ -49,7 +49,7 @@ var ZoteroItemPane = new function() {
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load an item
|
* Load a top-level item
|
||||||
*/
|
*/
|
||||||
this.viewItem = function (item, mode, index) {
|
this.viewItem = function (item, mode, index) {
|
||||||
if (!index) {
|
if (!index) {
|
||||||
|
|
|
@ -3753,19 +3753,16 @@ Zotero.Item.prototype.addTag = function(name, type) {
|
||||||
Zotero.Item.prototype.addTags = function (tags, type) {
|
Zotero.Item.prototype.addTags = function (tags, type) {
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
try {
|
try {
|
||||||
var tagIDArray = [];
|
var tagIDs = [];
|
||||||
var tempID = false;
|
|
||||||
for (var i = 0; i < tags.length; i++) {
|
for (var i = 0; i < tags.length; i++) {
|
||||||
tempID = this.addTag(tags[i], type);
|
let id = this.addTag(tags[i], type);
|
||||||
if (tempID) {
|
if (id) {
|
||||||
tagIDArray.push(tempID);
|
tagIDs.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagIDArray = (tagIDArray.length>0) ? tagIDArray : false;
|
|
||||||
|
|
||||||
Zotero.DB.commitTransaction();
|
Zotero.DB.commitTransaction();
|
||||||
return tagIDArray;
|
return tagIDs.length > 0 ? tagIDs : false;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
Zotero.DB.rollbackTransaction();
|
Zotero.DB.rollbackTransaction();
|
||||||
|
@ -3847,21 +3844,6 @@ Zotero.Item.prototype.getTagIDs = function() {
|
||||||
return Zotero.DB.columnQuery(sql, this.id);
|
return Zotero.DB.columnQuery(sql, this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the index of tagID in the list of the item's tags sorted in alphabetical order.
|
|
||||||
*/
|
|
||||||
Zotero.Item.prototype.getTagIndex = function(tagID) {
|
|
||||||
var tags = this.getTags();
|
|
||||||
|
|
||||||
for (var i=0;i<tags.length;i++) {
|
|
||||||
if (tagID == tags[i].id) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
throw ('Cannot replace tag on unsaved item');
|
throw ('Cannot replace tag on unsaved item');
|
||||||
|
|
3
chrome/skin/default/zotero/bindings/tagsbox.css
Normal file
3
chrome/skin/default/zotero/bindings/tagsbox.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
textbox {
|
||||||
|
margin: 0;
|
||||||
|
}
|
Loading…
Reference in a new issue