multiselect component

This commit is contained in:
Artur
2025-12-03 15:24:33 +03:00
parent 3a32bb3308
commit 2a3702566b
22 changed files with 450 additions and 270 deletions

View File

@ -71,8 +71,7 @@
<div id="mainCont" class="flexCol">
<div id="configState" class="hidden">
<p>
<span class="i18n">This plugin help you search references by author, title or year in your own
library.</span>
<span class="i18n">This plugin help you search references by author, title or year in your own library.</span>
</p>
<p>
<span class="i18n">To use Zotero you should get an API key.</span>
@ -112,17 +111,15 @@
<div id="selectedThumb" class="scrollThumb hidden"></div>
</div>
<div id="searchWrapper">
<label for="library" class="i18n">Search in:</label>
<div class="selectHolder">
<input id="library" readonly class="form-control control select form-control" type="text">
<div id="searchLibrary" class="selectList hidden">
</div>
</div>
<label id="searchLabel">
<input id="searchField" autocomplete="off" class="form-control i18n" type="text"
placeholder="Search references by author, title or year" style="width: 100%;" />
<span id="searchClear" class="hidden">&#xd7;</span>
</label>
<label for="searchField" class="i18n">Search the references you want to cite in this document.</label>
<input id="searchField" autocomplete="off" class="form-control i18n" type="text"
placeholder="Search references by author, title" />
<button>
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 9L7 11V5L12 0H0L5 5V9Z" fill="black" fill-opacity="0.8"/>
</svg>
</button>
<div id="librarySelectList"></div>
</div>
<div id="docsWrapper" class="flexSize">
@ -140,12 +137,10 @@
<button id="insertLinkBtn" class="button control i18n btn-text-default"
style="flex: 1; margin-left: 8px; margin-right: 0;" disabled>Insert Citation</button>
</div>
<button id="cancelBtn" class="button control i18n btn-text-default" disabled>Cancel
select</button>
<button id="cancelBtn" class="button control i18n btn-text-default" disabled>Cancel select</button>
</div>
<div id="insertBibDiv" class="flex">
<button id="insertBibBtn" class="button control i18n btn-text-default">Insert
Bibliography</button>
<button id="insertBibBtn" class="button control i18n btn-text-default">Insert Bibliography</button>
<button id="refreshBtn" class="button control btn-text-default i18n">Refresh</button>
</div>
<div id="omitAuthorDiv">

View File

@ -344,6 +344,9 @@ body.theme-dark .custom-button-container {
-webkit-appearance: none;
}
}
.input-field-clearable {
padding-right: 22px;
}
.input-field-disabled .input-field-element {
pointer-events: none;
@ -512,6 +515,7 @@ body.theme-dark {
}
.selectbox-header {
overflow: hidden;
border-style: solid;
border-width: 1px;
border-radius: 1px;
@ -598,6 +602,12 @@ body.theme-dark {
pointer-events: none;
}
}
.selectbox-option-divider {
margin: 4px 0;
border: none;
border-bottom-width: 1px;
border-bottom-style: solid;
}
.selectbox-checkbox {
margin: 0;
@ -653,10 +663,8 @@ body.theme-light .selectbox-container {
}
}
.selectbox-option {
&:hover {
background-color: var(--select-option-hover);
}
.selectbox-option:hover {
background-color: var(--select-option-hover);
}
.selectbox-option-selected {
@ -665,6 +673,9 @@ body.theme-light .selectbox-container {
background-color: var(--select-option-selected);
}
}
.selectbox-option-divider {
border-color: var(--input-border);
}
}
body.theme-dark .selectbox-container {
@ -705,10 +716,8 @@ body.theme-dark .selectbox-container {
}
}
.selectbox-option {
&:hover {
background-color: var(--select-option-hover-dark);
}
.selectbox-option:hover {
background-color: var(--select-option-hover-dark);
}
.selectbox-option-selected {
@ -717,6 +726,9 @@ body.theme-dark .selectbox-container {
background-color: var(--select-option-selected-dark);
}
}
.selectbox-option-divider {
border-color: var(--input-border-dark);
}
}
/** Message ************ */
.message-container {

View File

@ -106,22 +106,7 @@ input[type="text"] {
flex-direction: column;
flex: 1;
overflow-y: hidden;
padding: 10px 12px;
}
#searchLabel {
position: relative;
}
#searchClear {
position: absolute;
top: 7px;
right: 4px;
font-family: Arial;
font-size: 22px;
line-height: 0;
color: #BDBDBD;
cursor: pointer;
padding: 14px 12px 10px 12px;
}
#mainCont {
@ -163,18 +148,16 @@ input[type="text"] {
margin-bottom: 14px;
}
#searchWrapper input {
width: 100%;
height: 22px;
padding-right: 18px;
margin-bottom: 15px;
font-size: 11px;
#searchWrapper .input-field-container-searchField {
width: 207px;
}
#searchWrapper .selectList {
top: 22px;
z-index: 1;
}
#searchField {
width: 207px;
}
#controlsHolder {
border-top: 1px solid #cfcfcf;

View File

@ -100,6 +100,9 @@
groups: [],
};
/** @type {Object.<string, Button | InputField | SelectBox>} */
var customElements = {};
/** @type {Object.<string, HTMLElement | HTMLInputElement>} */
var elements = {};
function initElements() {
@ -159,26 +162,6 @@
if (!locatorLabelsList) {
throw new Error("locatorLabelsList not found");
}
const library = document.getElementById("library");
if (!library) {
throw new Error("library not found");
}
const searchLibrary = document.getElementById("searchLibrary");
if (!searchLibrary) {
throw new Error("searchLibrary not found");
}
const searchLabel = document.getElementById("searchLabel");
if (!searchLabel) {
throw new Error("searchLabel not found");
}
const searchClear = document.getElementById("searchClear");
if (!searchClear) {
throw new Error("searchClear not found");
}
const searchField = document.getElementById("searchField");
if (!searchField) {
throw new Error("searchField not found");
}
const styleWrapper = document.getElementById("styleWrapper");
if (!styleWrapper) {
throw new Error("styleWrapper not found");
@ -242,6 +225,18 @@
if (!cslFileInput) {
throw new Error("cslFileInput not found");
}
customElements = {
searchField: new InputField("searchField", {
type: "text",
autofocus: true,
showClear: true,
}),
librarySelectList: new SelectBox("librarySelectList", {
placeholder: "Loading...",
multiple: true,
}),
saveAsTextBtn: new Button("saveAsTextBtn"),
};
elements = {
loader: loader,
libLoader: libLoader,
@ -263,12 +258,6 @@
locatorLabel: locatorLabel,
locatorLabelsList: locatorLabelsList,
library: library,
searchLibrary: searchLibrary,
searchLabel: searchLabel,
searchClear: searchClear,
searchField: searchField,
styleWrapper: styleWrapper,
styleSelectList: styleSelectList,
styleSelectListOther: styleSelectListOther,
@ -283,7 +272,6 @@
insertLinkBtn: insertLinkBtn,
cancelBtn: cancelBtn,
refreshBtn: refreshBtn,
saveAsTextBtn: new Button("saveAsTextBtn"),
checkOmitAuthor: checkOmitAuthor,
cslFileInput: cslFileInput,
@ -298,9 +286,6 @@
window.Asc.plugin.init = function () {
initElements();
showLoader(true);
setTimeout(function () {
if (elements.searchField) elements.searchField.focus();
}, 100);
router = new Router();
sdk = new ZoteroSdk();
@ -447,22 +432,35 @@
return sdk
.getUserGroups()
.then(function (/** @type {Array<UserGroupInfo>} */ groups) {
let selectedItem = localStorage.getItem("selectedGroup");
let hasSelected = false;
groups.forEach(function (group) {
group.id = String(group.id);
});
const customGroups = [
{ id: "all", name: translate("Everywhere") },
{ id: "my_library", name: translate("My Library") },
{
id: "group_libraries",
name: translate("Group Libraries"),
},
];
if (elements.library instanceof HTMLInputElement) {
elements.library.value = customGroups[0].name;
!hasSelected &&
customGroups.forEach(function (group) {
if (group.id === selectedItem) {
hasSelected = true;
}
});
!hasSelected &&
groups.forEach(function (group) {
if (group.id.toString() === selectedItem) {
hasSelected = true;
}
});
if (!hasSelected) {
selectedItem = "my_library";
hasSelected = true;
}
elements.library.setAttribute("data-value", customGroups[0].id);
elements.library.setAttribute("title", customGroups[0].name);
const selectedItem =
localStorage.getItem("selectedGroup") || "all";
/**
* @param {string|number} id
@ -472,95 +470,92 @@
if (typeof id === "number") {
id = id.toString();
}
const el = document.createElement("span");
el.setAttribute("data-value", id);
el.textContent = name;
elements.searchLibrary.appendChild(el);
if (
id === selectedItem &&
elements.library instanceof HTMLInputElement
) {
el.setAttribute("selected", "");
elements.library.value = name;
elements.library.setAttribute("data-value", id);
elements.library.setAttribute("title", name);
}
if (customElements.librarySelectList instanceof SelectBox)
customElements.librarySelectList.addItem(
id,
name,
id === selectedItem
);
};
const addSeparator = function () {
const el = document.createElement("hr");
elements.searchLibrary.appendChild(el);
};
elements.searchLibrary.addEventListener("click", function (e) {
const target = e.target;
let option;
if (target && target instanceof HTMLSpanElement) {
option = target;
} else {
return;
}
const selected =
elements.searchLibrary.querySelector("span[selected]");
selected && selected.attributes.removeNamedItem("selected");
option.setAttribute("selected", "");
const id = option.getAttribute("data-value");
const name = option.textContent;
if (
!(elements.library instanceof HTMLInputElement) ||
!(elements.searchField instanceof HTMLInputElement) ||
typeof id !== "string"
) {
return;
}
elements.library.value = option.textContent;
elements.library.setAttribute("data-value", id);
elements.library.setAttribute("title", name);
localStorage.setItem("selectedGroup", id);
switchClass(elements.searchClear, displayNoneClass, true);
elements.searchField.value = "";
lastSearch.text = "";
clearLibrary();
});
if (groups.length === 0) {
return;
}
for (var i = 0; i < customGroups.length; i++) {
const id = customGroups[i].id;
const name = customGroups[i].name;
addGroupToSelectBox(id, name);
}
addSeparator();
if (groups.length === 0) {
return;
}
customElements.librarySelectList.addSeparator();
for (var i = 0; i < groups.length; i++) {
const id = groups[i].id;
const name = groups[i].name;
addGroupToSelectBox(id, name);
}
selectedGroupsWatcher(customGroups, groups);
});
}
/**
* @return {number|"all"|"my_library"|"group_libraries"}
* @param {Array<{id: string, name: string}>} customGroups
* @param {Array<UserGroupInfo>} groups
* @returns
*/
function selectedGroupsWatcher(customGroups, groups) {
if (customElements.librarySelectList instanceof SelectBox === false) {
return;
}
customElements.librarySelectList.subscribe(function (event) {
if (event.type !== "selectbox:change") {
return;
}
const values = event.detail.values;
const current = event.detail.current;
const bEnabled = event.detail.enabled;
const customIds = customGroups.map(function (group) {
return group.id;
});
let ids = groups.map(function (group) {
return group.id;
});
let bWasCustom = customIds.indexOf(String(current)) !== -1;
if (bWasCustom && current === "group_libraries") {
if (bEnabled) {
customElements.librarySelectList.selectItems(ids, true);
} else {
customElements.librarySelectList.unselectItems(ids, true);
}
} else if (!bWasCustom) {
let bAllGroupsSelected = ids.every(function (id) {
return values.indexOf(id) !== -1;
});
if (bAllGroupsSelected) {
customElements.librarySelectList.selectItems(
"group_libraries",
true
);
} else {
customElements.librarySelectList.unselectItems(
"group_libraries",
true
);
}
}
});
}
/**
* @return {number|"my_library"|"group_libraries"}
*/
function getSelectedGroup() {
for (var i = 0; i < elements.searchLibrary.children.length; i++) {
const option = elements.searchLibrary.children[i];
if (option.hasAttribute("selected")) {
const id = option.getAttribute("data-value");
if (
id === "my_library" ||
id === "group_libraries" ||
id === "all"
) {
return id;
}
return Number(id);
}
const id = customElements.librarySelectList.getValue();
if (id === "my_library" || id === "group_libraries") {
return id;
}
return "all";
return Number(id);
}
/**
@ -706,7 +701,6 @@
case "my_library":
groups = [];
break;
case "all":
case "group_libraries":
groups = userGroups.map(function (group) {
return group.id;
@ -722,10 +716,7 @@
let hideLoader = !groups.length;
const bCount = true;
if (
selectedGroup === "my_library" ||
selectedGroup === "all"
) {
if (selectedGroup === "my_library") {
promises.push(
loadLibrary(
sdk.getItems(text),
@ -757,37 +748,16 @@
return Promise.all(promises);
});
}
elements.searchField.onkeypress = function (e) {
if (!(e.target instanceof HTMLInputElement)) return;
if (e.keyCode == 13) searchFor(e.target.value);
};
elements.searchField.onblur = function (e) {
setTimeout(function () {
if (!(e.target instanceof HTMLInputElement)) return;
searchFor(e.target.value);
}, 500);
};
elements.searchField.onkeyup = function (e) {
if (!(e.target instanceof HTMLInputElement)) return;
switchClass(
elements.searchClear,
displayNoneClass,
!e.target.value
);
};
elements.searchClear.onclick = function (e) {
if (
!(e.target instanceof HTMLElement) ||
!(elements.searchField instanceof HTMLInputElement)
)
return;
if (e.target.classList.contains(displayNoneClass)) return true;
switchClass(elements.searchClear, displayNoneClass, true);
elements.searchField.value = "";
lastSearch.text = "";
clearLibrary();
return true;
};
if (customElements.searchField instanceof HTMLInputElement) {
customElements.searchField.subscribe(function (e) {
if (
e.type === "inputfield:blur" ||
e.type === "inputfield:submit"
) {
searchFor(e.detail.value);
}
});
}
elements.cancelBtn.onclick = function (e) {
var ids = [];
@ -898,8 +868,8 @@
});
};
if (elements.saveAsTextBtn instanceof Button) {
elements.saveAsTextBtn.subscribe(function (event) {
if (customElements.saveAsTextBtn instanceof Button) {
customElements.saveAsTextBtn.subscribe(function (event) {
if (event.type !== "button:click") {
return;
}
@ -998,7 +968,6 @@
elements.styleLang.onselectchange = function (inp, val, isClick) {
showLoader(true);
localesManager.saveLastUsedLanguage(val);
console.warn("load locale", val);
localesManager
.loadLocale(val)
.then(function () {
@ -1262,7 +1231,6 @@
*/
function onClickListElement(list, input) {
return function (/** @type {MouseEvent} */ ev) {
console.warn("onClickListElement", input);
if (!ev.target || !(ev.target instanceof HTMLElement)) {
console.error("onClickListElement: no target");
return;

View File

@ -48,6 +48,7 @@ function InputField(input, options) {
}
}
this._id = input.id || "input_" + Math.random().toString(36).slice(2, 9);
this.isFocused = false;
this.isValid = true;
this._validationMessage = "";
@ -55,11 +56,11 @@ function InputField(input, options) {
this._subscribers = [];
/** @type {InputBoundHandlesType} */
this._boundHandles = {
focus: function () {
self._handleFocus();
focus: function (e) {
self._handleFocus(e);
},
blur: function () {
self._handleBlur();
blur: function (e) {
self._handleBlur(e);
},
input: function (e) {
self._handleInput(e);
@ -106,7 +107,7 @@ InputField.prototype._createDOM = function () {
var fragment = document.createDocumentFragment();
fragment.appendChild(this._container);
this._container.className += " input-field-container";
this._container.className += " input-field-container input-field-container-" + this._id;
var inputField = document.createElement("div");
this._container.appendChild(inputField);
@ -165,6 +166,7 @@ InputField.prototype._createDOM = function () {
this._validationElement.style.display = "none";
if (this._options.showClear) {
this.input.className += " input-field-clearable";
this._clearButton = document.createElement("button");
inputField.appendChild(this._clearButton);
this._clearButton.className += " input-field-clear";
@ -191,13 +193,20 @@ InputField.prototype._bindEvents = function () {
this.input.addEventListener("change", this._boundHandles.validate);
};
InputField.prototype._handleFocus = function () {
/**
* @param {Event} e
*/
InputField.prototype._handleFocus = function (e) {
this.isFocused = true;
this._container.className += " input-field-focused";
this._updateClearButton();
this._triggerFocusEvent(e);
};
InputField.prototype._handleBlur = function () {
/**
* @param {Event} e
*/
InputField.prototype._handleBlur = function (e) {
this.isFocused = false;
var classes = this._container.className.split(" ");
@ -210,6 +219,7 @@ InputField.prototype._handleBlur = function () {
this._container.className = newClasses.join(" ");
this.validate();
this._triggerBlurEvent(e);
};
/**
@ -480,6 +490,40 @@ InputField.prototype._triggerInputEvent = function (e) {
});
};
/**
* @param {Event} e
*/
InputField.prototype._triggerFocusEvent = function (e) {
var detail = {
value: this.input.value,
originalEvent: e,
};
this._subscribers.forEach(function (cb) {
cb({
type: "inputfield:focus",
detail: detail,
});
});
};
/**
* @param {Event} e
*/
InputField.prototype._triggerBlurEvent = function (e) {
var detail = {
value: this.input.value,
originalEvent: e,
};
this._subscribers.forEach(function (cb) {
cb({
type: "inputfield:blur",
detail: detail,
});
});
};
InputField.prototype._triggerChange = function () {
var detail = {
value: this.input.value,

View File

@ -39,7 +39,7 @@ function SelectBox(container, options) {
this._selectedValues = new Set();
this._isOpen = false;
/** @type {Array<{ value: string | number, text: string, selected: boolean }>} */
/** @type {Array<{ value: string, text: string, selected: boolean } | null>} */
this._items = [];
/** @type {Function[]} */
this._subscribers = [];
@ -54,7 +54,8 @@ function SelectBox(container, options) {
close: function (e) {
if (
e.target instanceof HTMLElement &&
!self._container.contains(e.target)
!self._container.contains(e.target) &&
!e.target.classList.contains("selectbox-option")
) {
self._closeDropdown();
}
@ -222,6 +223,10 @@ SelectBox.prototype._handleSearch = function (e) {
*/
SelectBox.prototype._handleKeydown = function (e) {
var key = e.key || e.keyCode;
const items = this._items.filter(function (item) {
return item !== null;
});
let newItem;
switch (key) {
case " ":
@ -238,50 +243,51 @@ SelectBox.prototype._handleKeydown = function (e) {
case "ArrowDown":
case 40:
e.preventDefault();
if (this._selectedValues.size === 0 && this._items.length > 0) {
var firstItem = this._items[0];
this._selectedValues.add(firstItem.value);
if (this._selectedValues.size === 0 && items.length > 0) {
newItem = items[0];
this._selectedValues.add(newItem.value);
} else {
var selectedArray = Array.from(this._selectedValues);
var currentIndex = -1;
for (var i = 0; i < this._items.length; i++) {
if (this._items[i].value === selectedArray[0]) {
for (var i = 0; i < items.length; i++) {
if (items[i].value === selectedArray[0]) {
currentIndex = i;
break;
}
}
var nextIndex = (currentIndex + 1) % this._items.length;
var nextIndex = (currentIndex + 1) % items.length;
this._selectedValues.clear();
this._selectedValues.add(this._items[nextIndex].value);
newItem = items[nextIndex];
this._selectedValues.add(newItem.value);
}
this._updateSelectedText();
this._renderOptions(this.searchInput ? this.searchInput.value : "");
this._triggerChange();
this._triggerChange(newItem.value, true);
break;
case "ArrowUp":
case 38:
e.preventDefault();
if (this._selectedValues.size === 0 && this._items.length > 0) {
var lastItem = this._items[this._items.length - 1];
this._selectedValues.add(lastItem.value);
if (this._selectedValues.size === 0 && items.length > 0) {
newItem = items[items.length - 1];
this._selectedValues.add(newItem.value);
} else {
var selectedArray = Array.from(this._selectedValues);
var currentIndex = -1;
for (var i = 0; i < this._items.length; i++) {
if (this._items[i].value === selectedArray[0]) {
for (var i = 0; i < items.length; i++) {
if (items[i].value === selectedArray[0]) {
currentIndex = i;
break;
}
}
var prevIndex =
(currentIndex - 1 + this._items.length) %
this._items.length;
(currentIndex - 1 + items.length) % items.length;
this._selectedValues.clear();
this._selectedValues.add(this._items[prevIndex].value);
newItem = items[prevIndex];
this._selectedValues.add(newItem.value);
}
this._updateSelectedText();
this._renderOptions(this.searchInput ? this.searchInput.value : "");
this._triggerChange();
this._triggerChange(newItem.value, true);
break;
case "Tab":
case 9:
@ -299,28 +305,32 @@ SelectBox.prototype._renderOptions = function (searchTerm) {
/** @type {HTMLDivElement | null} */
var selectedOption = null;
var filteredItems = [];
var filteredItems = this._items;
if (searchTerm) {
for (var i = 0; i < this._items.length; i++) {
var item = this._items[i];
if (item.text.toLowerCase().indexOf(searchTerm) !== -1) {
filteredItems.push(item);
}
}
} else {
filteredItems = this._items.slice();
filteredItems = filteredItems.filter(function (item) {
return (
item !== null &&
item.text.toLowerCase().indexOf(searchTerm) !== -1
);
});
}
var fragment = document.createDocumentFragment();
for (var i = 0; i < filteredItems.length; i++) {
var item = filteredItems[i];
if (!item) {
const hr = document.createElement("hr");
hr.className += " selectbox-option-divider";
fragment.appendChild(hr);
continue;
}
var option = document.createElement("div");
option.className += " selectbox-option";
if (this._selectedValues.has(item.value)) {
option.className += " selectbox-option-selected";
selectedOption = option;
}
option.setAttribute("data-value", String(item.value));
option.setAttribute("data-value", item.value);
if (this._options.multiple) {
var input = document.createElement("input");
@ -398,23 +408,24 @@ SelectBox.prototype._handleDropdownClick = function (e) {
}
var value = option.getAttribute("data-value");
if (value === null) return;
let enabled = true;
if (this._options.multiple) {
if (this._selectedValues.has(value)) {
this._selectedValues.delete(value);
this.unselectItems(value, true);
enabled = false;
} else {
this._selectedValues.add(value);
this.selectItems(value, true);
}
} else {
this._selectedValues.clear();
this._selectedValues.add(value);
this.selectItems(value, true);
this._closeDropdown();
}
this._updateSelectedText();
this._renderOptions(this.searchInput ? this.searchInput.value : "");
this._triggerChange();
this._triggerChange(value, enabled);
};
SelectBox.prototype._updateSelectedText = function () {
@ -427,7 +438,7 @@ SelectBox.prototype._updateSelectedText = function () {
var selectedItems = [];
for (var i = 0; i < this._items.length; i++) {
var item = this._items[i];
if (this._selectedValues.has(item.value)) {
if (item && this._selectedValues.has(item.value)) {
selectedItems.push(item);
}
}
@ -444,7 +455,7 @@ SelectBox.prototype._updateSelectedText = function () {
var selectedItem = null;
for (var i = 0; i < this._items.length; i++) {
var item = this._items[i];
if (this._selectedValues.has(item.value)) {
if (item && this._selectedValues.has(item.value)) {
selectedItem = item;
break;
}
@ -456,19 +467,26 @@ SelectBox.prototype._updateSelectedText = function () {
}
};
SelectBox.prototype._triggerChange = function () {
/**
* @param {string} currentValue
* @param {boolean} enabled
*/
SelectBox.prototype._triggerChange = function (currentValue, enabled) {
var values = Array.from(this._selectedValues);
var items = [];
for (var i = 0; i < this._items.length; i++) {
var item = this._items[i];
if (this._selectedValues.has(item.value)) {
if (item && this._selectedValues.has(item.value)) {
items.push(item);
}
}
/** @type {SelectboxEventDetail} */
var detail = {
values: values,
items: items,
current: currentValue,
enabled: enabled,
};
this._subscribers.forEach(function (cb) {
@ -480,7 +498,7 @@ SelectBox.prototype._triggerChange = function () {
};
/**
* @param {Function} callback
* @param {function(SelectboxEventType): void} callback
* @returns {Object}
*/
SelectBox.prototype.subscribe = function (callback) {
@ -497,7 +515,7 @@ SelectBox.prototype.subscribe = function (callback) {
};
/**
* @param {string | number} value
* @param {string} value
* @param {string} text
* @param {boolean} selected
*/
@ -517,17 +535,20 @@ SelectBox.prototype.addItem = function (value, text, selected) {
this._updateSelectedText();
};
SelectBox.prototype.addSeparator = function () {
this._items.push(null);
};
/**
* @param {string} value
*/
SelectBox.prototype.removeItem = function (value) {
var newItems = [];
for (var i = 0; i < this._items.length; i++) {
if (this._items[i].value !== value) {
newItems.push(this._items[i]);
this._items = this._items.filter(function (item) {
if (item === null || item.value !== value) {
return true;
}
}
this._items = newItems;
return false;
});
this._selectedValues.delete(value);
this._updateSelectedText();
};
@ -554,6 +575,148 @@ SelectBox.prototype.setValue = function (value) {
this._renderOptions();
};
/**
* @param {string | Array<string>} values
* @param {boolean} [bSilent]
*/
SelectBox.prototype.selectItems = function (values, bSilent) {
const self = this;
if (!this._options.multiple && Array.isArray(values)) {
console.error(
"Method selectItem is only available for multi-select boxes."
);
return;
}
/** @type {string} */
let value = "";
if (this._options.multiple) {
/**
* @param {string} value
*/
let checkMultiOption = function (value) {
if (self._optionsContainer) {
let option = self._optionsContainer.querySelector(
'[data-value="' + value + '"]'
);
if (option) {
let checkbox = option.querySelector(
'input[type="checkbox"]'
);
if (checkbox && checkbox instanceof HTMLInputElement) {
checkbox.checked = true;
}
option.classList.add("selectbox-option-selected");
}
}
};
if (Array.isArray(values)) {
for (var i = 0; i < values.length; i++) {
value = values[i];
if (!this._selectedValues.has(value)) {
this._selectedValues.add(value);
checkMultiOption(value);
}
}
} else {
value = values;
if (!this._selectedValues.has(value)) {
this._selectedValues.add(value);
checkMultiOption(value);
}
}
} else if (!Array.isArray(values)) {
value = values;
this._selectedValues.clear();
this._selectedValues.add(value);
if (this._optionsContainer) {
let selectedOptions = this._optionsContainer.querySelectorAll(
'.selectbox-option-selected[data-value="' + value + '"]'
);
selectedOptions.forEach(function (option) {
option.classList.remove("selectbox-option-selected");
});
let option = this._optionsContainer.querySelector(
'[data-value="' + value + '"]'
);
if (option) {
option.classList.add("selectbox-option-selected");
}
}
this._closeDropdown();
}
this._updateSelectedText();
if (bSilent) {
return;
}
this._triggerChange(value, true);
};
/**
* @param {string | Array<string>} values
* @param {boolean} [bSilent]
*/
SelectBox.prototype.unselectItems = function (values, bSilent) {
const self = this;
if (!this._options.multiple) {
console.error(
"Method unselectItem is only available for multi-select boxes."
);
return;
}
/** @type {string} */
let value = "";
/**
* @param {string} value
*/
let uncheckMultiOption = function (value) {
if (self._optionsContainer) {
let option = self._optionsContainer.querySelector(
'[data-value="' + value + '"]'
);
if (option) {
let checkbox = option.querySelector('input[type="checkbox"]');
if (checkbox && checkbox instanceof HTMLInputElement) {
checkbox.checked = false;
}
option.classList.remove("selectbox-option-selected");
}
}
};
if (Array.isArray(values)) {
for (var i = 0; i < values.length; i++) {
value = values[i];
if (this._selectedValues.has(value)) {
this._selectedValues.delete(value);
uncheckMultiOption(value);
}
}
} else {
value = values;
if (this._selectedValues.has(value)) {
this._selectedValues.delete(value);
uncheckMultiOption(value);
}
}
this._updateSelectedText();
if (bSilent) {
return;
}
this._triggerChange(value, true);
};
/**
* @param {boolean} bSelectFirst
*/
@ -562,7 +725,9 @@ SelectBox.prototype.clear = function (bSelectFirst) {
this._selectedValues.clear();
if (bSelectFirst && this._items.length > 0) {
var firstItem = this._items[0];
this._selectedValues.add(firstItem.value);
if (firstItem) {
this._selectedValues.add(firstItem.value);
}
}
this._updateSelectedText();
this._renderOptions();

View File

@ -26,8 +26,8 @@
/**
* @typedef {Object} InputBoundHandlesType
* @property {() => void} focus
* @property {() => void} blur
* @property {(ev: Event) => void} focus
* @property {(ev: Event) => void} blur
* @property {(ev: Event) => void} input
* @property {(ev: KeyboardEvent) => void} keydown
* @property {() => void} clear
@ -68,6 +68,20 @@
* @property {boolean} [multiple]
*/
/**
* @typedef {Object} SelectboxEventType
* @property {string} type
* @property {SelectboxEventDetail} detail
*/
/**
* @typedef {Object} SelectboxEventDetail
* @property {Array<string|number>} values
* @property {string|number} current
* @property {boolean} enabled
* @property {Array<SelectboxItem>} [items]
*/
/** ********************** */
/**

View File

@ -32,7 +32,7 @@
/**
* @typedef {Object} UserGroupInfo
* @property {number} id
* @property {number|string} id
* @property {string} name
*/

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Nepodarilo se vložit citaci",
"Failed to insert bibliography": "Nepodarilo se vložit bibliografii",
"Failed to refresh": "Nepodarilo se aktualizovat",
"Search in:": "Vyhledat v:",
"Search the references you want to cite in this document.": "Vyhledejte odkazy, které chcete citovat v tomto dokumentu.",
"Everywhere": "Vезде",
"My Library": "Moje knihovna",
"Group Libraries": "Skupinové knihovny",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Fehler beim Einfügen des Zitats",
"Failed to insert bibliography": "Fehler beim Einfügen der Bibliographie",
"Failed to refresh": "Fehler beim Aktualisieren",
"Search in:": "Suche in:",
"Search the references you want to cite in this document.": "Suchen Sie die Referenzen, die Sie in diesem Dokument zitieren wollen.",
"Everywhere": "Alle",
"My Library": "Meine Bibliothek",
"Group Libraries": "Gruppenbibliotheken",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Fallo al insertar la cita",
"Failed to insert bibliography": "Fallo al insertar la bibliografía",
"Failed to refresh": "Fallo al actualizar",
"Search in:": "Buscar en:",
"Search the references you want to cite in this document.": "Busque las referencias que desea citar en este documento.",
"Everywhere": "Todo",
"My Library": "Mi biblioteca",
"Group Libraries": "Bibliotecas de grupos",

View File

@ -26,7 +26,6 @@
"Refresh": "Rafraîchir",
"Unlink citations": "Dissocier les citations",
"Please insert some citation into the document.": "Veuillez insérer une citation dans le document.",
"Omit Author": "Omettre l'auteur",
"Connection to Zotero failed": "La connexion à Zotero a échoué",
"Connection to Zotero failed. Make sure Zotero is running": "La connexion à Zotero a échoué. Vérifiez que Zotero est actif",
@ -38,7 +37,7 @@
"Failed to insert citation": "Échec de l'insertion de la citation",
"Failed to insert bibliography": "Échec de l'insertion de la bibliographie",
"Failed to refresh": "Échec de la mise à jour",
"Search in:": "Rechercher dans:",
"Search the references you want to cite in this document.": "Rechercher les citations que vous souhaitez citer dans ce document.",
"Everywhere": "Partout",
"My Library": "Ma bibliothèque",
"Group Libraries": "Bibliothèques de groupes",

View File

@ -6,7 +6,7 @@
"Open Zotero website": "Apri il sito web Zotero",
"Invalid API key": "Chiave API non valida",
"API Key": "Chiave API",
"Search in all literature:": "Cerca in tutta la letteratura",
"Nothing found": "Niente trovato",
"Style is not selected": "Lo stile non ? selezionato",
"Language is not selected": "Il linguaggio non ? selezionato",
"Search references by author, title or year": "Cerca riferimenti per autore, titolo o anno",
@ -37,7 +37,7 @@
"Failed to insert citation": "Impossibile inserire la citazione",
"Failed to insert bibliography": "Impossibile inserire la bibliografia",
"Failed to refresh": "Impossibile aggiornare",
"Search in:": "Cerca in:",
"Search the references you want to cite in this document.": "Cerca i riferimenti che desideri citare nel documento corrente.",
"Everywhere": "Tutto",
"My Library": "La mia biblioteca",
"Group Libraries": "Biblioteca dei gruppi",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "引用の挿入に失敗しました",
"Failed to insert bibliography": "文献引用の挿入に失敗しました",
"Failed to refresh": "リフレッシュに失敗しました",
"Search in:": "検索対象:",
"Search the references you want to cite in this document.": "この文書内で引用したい文献を検索します。",
"Everywhere": "全て",
"My Library": "私のライブラリ",
"Group Libraries": "グループライブラリ",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Het invoegen van de citaat is mislukt",
"Failed to insert bibliography": "Het invoegen van de bibliografie is mislukt",
"Failed to refresh": "Het vernieuwen is mislukt",
"Search in:": "Zoeken in:",
"Search the references you want to cite in this document.": "Zoek de referenties die u wilt citaat in dit document.",
"Everywhere": "Elke plaats",
"My Library": "Mijn bibliotheek",
"Group Libraries": "Groepsbibliotheek",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Falha ao inserir citação",
"Failed to insert bibliography": "Falha ao inserir bibliografia",
"Failed to refresh": "Falha ao atualizar",
"Search in:": "Procurar em:",
"Search the references you want to cite in this document.": "Procure as referências que deseja citar neste documento.",
"Everywhere": "Todos os lugares",
"My Library": "A minha biblioteca",
"Group Libraries": "Grupos de bibliotecas",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Falha ao inserir citação",
"Failed to insert bibliography": "Falha ao inserir bibliografia",
"Failed to refresh": "Falha ao actualizar",
"Search in:": "Procurar em:",
"Search the references you want to cite in this document.": "Procura as referências que queres citar neste documento.",
"Everywhere": "Todos os lugares",
"My Library": "A minha biblioteca",
"Group Libraries": "Grupos de bibliotecas",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Не удалось вставить цитату",
"Failed to insert bibliography": "Не удалось вставить библиографию",
"Failed to refresh": "Не удалось обновить",
"Search in:": "Поиск в:",
"Search the references you want to cite in this document.": "Поиск ссылок, которые вы хотите цитировать в этом документе.",
"Everywhere": "Везде",
"My Library": "Моя библиотека",
"Group Libraries": "Группы библиотек",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Zbatimi i citimit dështoi",
"Failed to insert bibliography": "Zbatimi i bibliografise dështoi",
"Failed to refresh": "Rifreskimi dështoi",
"Search in:": "Kerkoni sipas:",
"Search the references you want to cite in this document.": "Kerkoni referenca sipas autorit, titullit ose vitit",
"Everywhere": "Gjithashtu",
"My Library": "Kutia mija",
"Group Libraries": "Kutia e grupave",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Убациње цитата није успело",
"Failed to insert bibliography": "Убациње библиографије није успело",
"Failed to refresh": "Освежавање није успело",
"Search in:": "Претражите у:",
"Search the references you want to cite in this document.": "Претражите референце које желите да цитирате у овом документу.",
"Everywhere": "Све",
"My Library": "Моја библиотека",
"Group Libraries": "Групе библиотека",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "Neuspelo ubacivanje citata",
"Failed to insert bibliography": "Neuspelo ubacivanje bibliografije",
"Failed to refresh": "Neuspelo osvežavanje",
"Search in:": "Pretražite u:",
"Search the references you want to cite in this document.": "Pretražite referencu koje zelite da citirate u ovom dokumentu.",
"Everywhere": "Sve",
"My Library": "Moja biblioteka",
"Group Libraries": "Grupe biblioteka",

View File

@ -37,7 +37,7 @@
"Failed to insert citation": "插入引用失敗",
"Failed to insert bibliography": "插入參考文献失敗",
"Failed to refresh": "刷新失敗",
"Search in:": "搜索:",
"Search the references you want to cite in this document.": "在此文件中搜索引用。",
"Everywhere": "全部",
"My Library": "我的圖書館",
"Group Libraries": "群組圖書館",