From 2a3702566be46edd262f3f6d599147368a848d8c Mon Sep 17 00:00:00 2001 From: Artur Date: Wed, 3 Dec 2025 15:24:33 +0300 Subject: [PATCH] multiselect component --- sdkjs-plugins/content/zotero/index.html | 29 +- .../zotero/resources/css/components.css | 28 +- .../zotero/resources/css/plugin_style.css | 29 +- sdkjs-plugins/content/zotero/scripts/code.js | 264 ++++++++---------- .../zotero/scripts/components/input.js | 58 +++- .../zotero/scripts/components/selectbox.js | 261 +++++++++++++---- .../zotero/scripts/components/types.js | 18 +- .../content/zotero/scripts/zotero/zotero.js | 2 +- .../content/zotero/translations/cs-CS.json | 2 +- .../content/zotero/translations/de-DE.json | 2 +- .../content/zotero/translations/es-ES.json | 2 +- .../content/zotero/translations/fr-FR.json | 3 +- .../content/zotero/translations/it-IT.json | 4 +- .../content/zotero/translations/ja-JA.json | 2 +- .../content/zotero/translations/nl-NL.json | 2 +- .../content/zotero/translations/pt-BR.json | 2 +- .../content/zotero/translations/pt-PT.json | 2 +- .../content/zotero/translations/ru-RU.json | 2 +- .../content/zotero/translations/sq-AL.json | 2 +- .../zotero/translations/sr-Cyrl-RS.json | 2 +- .../zotero/translations/sr-Latn-RS.json | 2 +- .../content/zotero/translations/zh-ZH.json | 2 +- 22 files changed, 450 insertions(+), 270 deletions(-) diff --git a/sdkjs-plugins/content/zotero/index.html b/sdkjs-plugins/content/zotero/index.html index f2a81628..33164573 100644 --- a/sdkjs-plugins/content/zotero/index.html +++ b/sdkjs-plugins/content/zotero/index.html @@ -71,8 +71,7 @@
- -
- - -
- + + + +
@@ -140,12 +137,10 @@
- +
- +
diff --git a/sdkjs-plugins/content/zotero/resources/css/components.css b/sdkjs-plugins/content/zotero/resources/css/components.css index 65c46d65..d7707c9a 100644 --- a/sdkjs-plugins/content/zotero/resources/css/components.css +++ b/sdkjs-plugins/content/zotero/resources/css/components.css @@ -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 { diff --git a/sdkjs-plugins/content/zotero/resources/css/plugin_style.css b/sdkjs-plugins/content/zotero/resources/css/plugin_style.css index cd779aff..9c4af469 100644 --- a/sdkjs-plugins/content/zotero/resources/css/plugin_style.css +++ b/sdkjs-plugins/content/zotero/resources/css/plugin_style.css @@ -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; diff --git a/sdkjs-plugins/content/zotero/scripts/code.js b/sdkjs-plugins/content/zotero/scripts/code.js index c76c4b03..67f113c5 100644 --- a/sdkjs-plugins/content/zotero/scripts/code.js +++ b/sdkjs-plugins/content/zotero/scripts/code.js @@ -100,6 +100,9 @@ groups: [], }; + /** @type {Object.} */ + var customElements = {}; + /** @type {Object.} */ 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} */ 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} 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; diff --git a/sdkjs-plugins/content/zotero/scripts/components/input.js b/sdkjs-plugins/content/zotero/scripts/components/input.js index afe742c6..888e7a08 100644 --- a/sdkjs-plugins/content/zotero/scripts/components/input.js +++ b/sdkjs-plugins/content/zotero/scripts/components/input.js @@ -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, diff --git a/sdkjs-plugins/content/zotero/scripts/components/selectbox.js b/sdkjs-plugins/content/zotero/scripts/components/selectbox.js index 931f9aa1..e78c178e 100644 --- a/sdkjs-plugins/content/zotero/scripts/components/selectbox.js +++ b/sdkjs-plugins/content/zotero/scripts/components/selectbox.js @@ -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} 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} 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(); diff --git a/sdkjs-plugins/content/zotero/scripts/components/types.js b/sdkjs-plugins/content/zotero/scripts/components/types.js index f3970d50..69a9a749 100644 --- a/sdkjs-plugins/content/zotero/scripts/components/types.js +++ b/sdkjs-plugins/content/zotero/scripts/components/types.js @@ -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} values + * @property {string|number} current + * @property {boolean} enabled + * @property {Array} [items] + */ + /** ********************** */ /** diff --git a/sdkjs-plugins/content/zotero/scripts/zotero/zotero.js b/sdkjs-plugins/content/zotero/scripts/zotero/zotero.js index bc52c99a..dc68dba5 100644 --- a/sdkjs-plugins/content/zotero/scripts/zotero/zotero.js +++ b/sdkjs-plugins/content/zotero/scripts/zotero/zotero.js @@ -32,7 +32,7 @@ /** * @typedef {Object} UserGroupInfo - * @property {number} id + * @property {number|string} id * @property {string} name */ diff --git a/sdkjs-plugins/content/zotero/translations/cs-CS.json b/sdkjs-plugins/content/zotero/translations/cs-CS.json index e29761b6..ddf17956 100644 --- a/sdkjs-plugins/content/zotero/translations/cs-CS.json +++ b/sdkjs-plugins/content/zotero/translations/cs-CS.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/de-DE.json b/sdkjs-plugins/content/zotero/translations/de-DE.json index 9ae6fd4d..42696302 100644 --- a/sdkjs-plugins/content/zotero/translations/de-DE.json +++ b/sdkjs-plugins/content/zotero/translations/de-DE.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/es-ES.json b/sdkjs-plugins/content/zotero/translations/es-ES.json index 7eb6af89..60e965a8 100644 --- a/sdkjs-plugins/content/zotero/translations/es-ES.json +++ b/sdkjs-plugins/content/zotero/translations/es-ES.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/fr-FR.json b/sdkjs-plugins/content/zotero/translations/fr-FR.json index 61ffb58a..3a8ecbc5 100644 --- a/sdkjs-plugins/content/zotero/translations/fr-FR.json +++ b/sdkjs-plugins/content/zotero/translations/fr-FR.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/it-IT.json b/sdkjs-plugins/content/zotero/translations/it-IT.json index d3edc184..27b2f60c 100644 --- a/sdkjs-plugins/content/zotero/translations/it-IT.json +++ b/sdkjs-plugins/content/zotero/translations/it-IT.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/ja-JA.json b/sdkjs-plugins/content/zotero/translations/ja-JA.json index 88c31338..519f52cb 100644 --- a/sdkjs-plugins/content/zotero/translations/ja-JA.json +++ b/sdkjs-plugins/content/zotero/translations/ja-JA.json @@ -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": "グループライブラリ", diff --git a/sdkjs-plugins/content/zotero/translations/nl-NL.json b/sdkjs-plugins/content/zotero/translations/nl-NL.json index 5a3c7915..592f4e07 100644 --- a/sdkjs-plugins/content/zotero/translations/nl-NL.json +++ b/sdkjs-plugins/content/zotero/translations/nl-NL.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/pt-BR.json b/sdkjs-plugins/content/zotero/translations/pt-BR.json index 62108b91..ca405970 100644 --- a/sdkjs-plugins/content/zotero/translations/pt-BR.json +++ b/sdkjs-plugins/content/zotero/translations/pt-BR.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/pt-PT.json b/sdkjs-plugins/content/zotero/translations/pt-PT.json index 4aebb4bd..1050025f 100644 --- a/sdkjs-plugins/content/zotero/translations/pt-PT.json +++ b/sdkjs-plugins/content/zotero/translations/pt-PT.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/ru-RU.json b/sdkjs-plugins/content/zotero/translations/ru-RU.json index c5928ea5..6f8e9e03 100644 --- a/sdkjs-plugins/content/zotero/translations/ru-RU.json +++ b/sdkjs-plugins/content/zotero/translations/ru-RU.json @@ -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": "Группы библиотек", diff --git a/sdkjs-plugins/content/zotero/translations/sq-AL.json b/sdkjs-plugins/content/zotero/translations/sq-AL.json index a0ee60a8..20b94f41 100644 --- a/sdkjs-plugins/content/zotero/translations/sq-AL.json +++ b/sdkjs-plugins/content/zotero/translations/sq-AL.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/sr-Cyrl-RS.json b/sdkjs-plugins/content/zotero/translations/sr-Cyrl-RS.json index b5677b0b..5793195d 100644 --- a/sdkjs-plugins/content/zotero/translations/sr-Cyrl-RS.json +++ b/sdkjs-plugins/content/zotero/translations/sr-Cyrl-RS.json @@ -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": "Групе библиотека", diff --git a/sdkjs-plugins/content/zotero/translations/sr-Latn-RS.json b/sdkjs-plugins/content/zotero/translations/sr-Latn-RS.json index d4e3443f..d0e6fe06 100644 --- a/sdkjs-plugins/content/zotero/translations/sr-Latn-RS.json +++ b/sdkjs-plugins/content/zotero/translations/sr-Latn-RS.json @@ -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", diff --git a/sdkjs-plugins/content/zotero/translations/zh-ZH.json b/sdkjs-plugins/content/zotero/translations/zh-ZH.json index 1f10dcb4..198d370c 100644 --- a/sdkjs-plugins/content/zotero/translations/zh-ZH.json +++ b/sdkjs-plugins/content/zotero/translations/zh-ZH.json @@ -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": "群組圖書館",