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:
Dan Stillman 2013-03-09 02:42:34 -05:00
parent d291084af6
commit f932f312eb
5 changed files with 406 additions and 212 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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) {

View file

@ -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');

View file

@ -0,0 +1,3 @@
textbox {
margin: 0;
}