fx-compat: Item box: Fix multiline fields & autocomplete
This commit is contained in:
parent
02bcb1712c
commit
fdd73d4ada
3 changed files with 131 additions and 59 deletions
|
@ -26,6 +26,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
{
|
{
|
||||||
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript("chrome://zotero/content/elements/shadowAutocompleteInput.js", this);
|
||||||
|
|
||||||
class ItemBox extends XULElement {
|
class ItemBox extends XULElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -1146,13 +1150,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureElementIsVisible(elem) {
|
ensureElementIsVisible(elem) {
|
||||||
try {
|
elem.scrollIntoView();
|
||||||
var sbo = this.boxObject;
|
|
||||||
sbo.ensureElementIsVisible(elem);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
Zotero.logError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTypeTo(itemTypeID, menu) {
|
changeTypeTo(itemTypeID, menu) {
|
||||||
|
@ -1441,7 +1439,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
showEditor(elem) {
|
showEditor(elem) {
|
||||||
return (async function () {
|
return (async () => {
|
||||||
Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`);
|
Zotero.debug(`Showing editor for ${elem.getAttribute('fieldname')}`);
|
||||||
|
|
||||||
var label = elem.closest('tr').querySelector('th');
|
var label = elem.closest('tr').querySelector('th');
|
||||||
|
@ -1508,61 +1506,67 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var t = document.createElement("input");
|
var t;
|
||||||
t.setAttribute('id', `itembox-field-textbox-${fieldName}`);
|
|
||||||
t.setAttribute('value', value);
|
|
||||||
t.setAttribute('fieldname', fieldName);
|
|
||||||
t.setAttribute('ztabindex', tabindex);
|
|
||||||
t.setAttribute('flex', '1');
|
|
||||||
|
|
||||||
if (creatorField=='lastName') {
|
|
||||||
t.setAttribute('fieldMode', elem.getAttribute('fieldMode'));
|
|
||||||
t.setAttribute('newlines','pasteintact');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Zotero.ItemFields.isMultiline(fieldName) || Zotero.ItemFields.isLong(fieldName)) {
|
if (Zotero.ItemFields.isMultiline(fieldName) || Zotero.ItemFields.isLong(fieldName)) {
|
||||||
t.setAttribute('multiline', true);
|
t = document.createElement("textarea");
|
||||||
t.setAttribute('rows', 8);
|
t.setAttribute('rows', 8);
|
||||||
}
|
}
|
||||||
else {
|
// Add auto-complete for certain fields
|
||||||
// Add auto-complete for certain fields
|
else if (Zotero.ItemFields.isAutocompleteField(fieldName)
|
||||||
if (Zotero.ItemFields.isAutocompleteField(fieldName)
|
|| fieldName == 'creator') {
|
||||||
|| fieldName == 'creator') {
|
t = document.createElement("input", { is: 'shadow-autocomplete-input' });
|
||||||
t.setAttribute('type', 'autocomplete');
|
t.setAttribute('autocompletesearch', 'zotero');
|
||||||
t.setAttribute('autocompletesearch', 'zotero');
|
|
||||||
|
let params = {
|
||||||
|
fieldName: fieldName,
|
||||||
|
libraryID: this.item.libraryID
|
||||||
|
};
|
||||||
|
if (field == 'creator') {
|
||||||
|
params.fieldMode = parseInt(elem.getAttribute('fieldMode'));
|
||||||
|
|
||||||
let params = {
|
// Include itemID and creatorTypeID so the autocomplete can
|
||||||
fieldName: fieldName,
|
// avoid showing results for creators already set on the item
|
||||||
libraryID: this.item.libraryID
|
let row = elem.closest('tr');
|
||||||
};
|
let creatorTypeID = parseInt(
|
||||||
if (field == 'creator') {
|
row.getElementsByClassName('creator-type-label')[0]
|
||||||
params.fieldMode = parseInt(elem.getAttribute('fieldMode'));
|
.getAttribute('typeid')
|
||||||
|
|
||||||
// Include itemID and creatorTypeID so the autocomplete can
|
|
||||||
// avoid showing results for creators already set on the item
|
|
||||||
let row = elem.closest('tr');
|
|
||||||
let creatorTypeID = parseInt(
|
|
||||||
row.getElementsByClassName('creator-type-label')[0]
|
|
||||||
.getAttribute('typeid')
|
|
||||||
);
|
|
||||||
if (itemID) {
|
|
||||||
params.itemID = itemID;
|
|
||||||
params.creatorTypeID = creatorTypeID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return
|
|
||||||
t.setAttribute('ontextentered',
|
|
||||||
'this.handleCreatorAutoCompleteSelect(this, true)');
|
|
||||||
// Tab/Shift-Tab
|
|
||||||
t.setAttribute('onchange',
|
|
||||||
'this.handleCreatorAutoCompleteSelect(this)');
|
|
||||||
};
|
|
||||||
t.setAttribute(
|
|
||||||
'autocompletesearchparam', JSON.stringify(params)
|
|
||||||
);
|
);
|
||||||
t.setAttribute('completeselectedindex', true);
|
if (itemID) {
|
||||||
|
params.itemID = itemID;
|
||||||
|
params.creatorTypeID = creatorTypeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
t.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this.handleCreatorAutoCompleteSelect(t, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Tab/Shift-Tab
|
||||||
|
t.setAttribute('onchange',
|
||||||
|
'this.handleCreatorAutoCompleteSelect(this)');
|
||||||
|
|
||||||
|
if (creatorField == 'lastName') {
|
||||||
|
t.setAttribute('fieldMode', elem.getAttribute('fieldMode'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
t.setAttribute(
|
||||||
|
'autocompletesearchparam', JSON.stringify(params)
|
||||||
|
);
|
||||||
|
t.setAttribute('completeselectedindex', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!t) {
|
||||||
|
t = document.createElement("input");
|
||||||
|
}
|
||||||
|
|
||||||
|
t.id = `itembox-field-textbox-${fieldName}`;
|
||||||
|
t.value = value;
|
||||||
|
t.dataset.originalValue = value;
|
||||||
|
t.style.mozBoxFlex = 1;
|
||||||
|
t.setAttribute('fieldname', fieldName);
|
||||||
|
t.setAttribute('ztabindex', tabindex);
|
||||||
|
|
||||||
var box = elem.parentNode;
|
var box = elem.parentNode;
|
||||||
box.replaceChild(t, elem);
|
box.replaceChild(t, elem);
|
||||||
|
|
||||||
|
@ -1597,7 +1601,7 @@
|
||||||
t.onkeypress = (event) => this.handleKeyPress(event);
|
t.onkeypress = (event) => this.handleKeyPress(event);
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
}.bind(this))();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1701,7 +1705,7 @@
|
||||||
// Shift-enter adds new creator row
|
// Shift-enter adds new creator row
|
||||||
if (fieldname.indexOf('creator-') == 0 && event.shiftKey) {
|
if (fieldname.indexOf('creator-') == 0 && event.shiftKey) {
|
||||||
// Value hasn't changed
|
// Value hasn't changed
|
||||||
if (target.getAttribute('value') == target.value) {
|
if (target.dataset.originalValue == target.value) {
|
||||||
Zotero.debug("Value hasn't changed");
|
Zotero.debug("Value hasn't changed");
|
||||||
// If + button is disabled, just focus next creator row
|
// If + button is disabled, just focus next creator row
|
||||||
if (target.closest('tr').lastChild.lastChild.disabled) {
|
if (target.closest('tr').lastChild.lastChild.disabled) {
|
||||||
|
@ -1732,7 +1736,7 @@
|
||||||
|
|
||||||
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');
|
target.value = target.dataset.originalValue;
|
||||||
|
|
||||||
focused.blur();
|
focused.blur();
|
||||||
|
|
||||||
|
|
60
chrome/content/zotero/elements/shadowAutocompleteInput.js
Normal file
60
chrome/content/zotero/elements/shadowAutocompleteInput.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright © 2022 Corporation for Digital Scholarship
|
||||||
|
Vienna, Virginia, USA
|
||||||
|
https://www.zotero.org
|
||||||
|
|
||||||
|
This file is part of Zotero.
|
||||||
|
|
||||||
|
Zotero is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Zotero is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
{
|
||||||
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
// Load specific element because customElements.js loads on element creation only
|
||||||
|
Services.scriptloader.loadSubScript("chrome://global/content/elements/autocomplete-input.js", this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends AutocompleteInput to fix document.activeElement checks that
|
||||||
|
* don't work in a shadow DOM context.
|
||||||
|
*/
|
||||||
|
class ShadowAutocompleteInput extends customElements.get('autocomplete-input') {
|
||||||
|
get focused() {
|
||||||
|
// document.activeElement by itself doesn't traverse shadow DOMs; see
|
||||||
|
// https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
|
||||||
|
function activeElement(root) {
|
||||||
|
let activeHere = root.activeElement;
|
||||||
|
|
||||||
|
if (activeHere?.shadowRoot) {
|
||||||
|
return activeElement(activeHere.shadowRoot);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return activeHere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this === activeElement(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("shadow-autocomplete-input", ShadowAutocompleteInput, {
|
||||||
|
extends: "input",
|
||||||
|
});
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ td > [fieldname] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value.multiline {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
/*td > vbox > description
|
/*td > vbox > description
|
||||||
{
|
{
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
@ -66,6 +70,10 @@ label[singleField=false]:after
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
/* metadata field names */
|
/* metadata field names */
|
||||||
th, .creator-type-label {
|
th, .creator-type-label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
Loading…
Reference in a new issue