您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Components library for AniList Edit Multiple Media Simultaneously
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyforks.org/scripts/496874/1387742/components.js
// ==UserScript== // @name components // @license MIT // @namespace rtonne // @match https://anilist.co/* // @version 1.0 // @author Rtonne // @description Components library for AniList Edit Multiple Media Simultaneously // ==/UserScript== /** * Creates a select input. * @param {HTMLElement} container A parent element to append the input to. * @param {string[]} options The option values for the select input. * @returns The select input element. */ function createSelectInput(container, options) { const input_container_1 = document.createElement("div"); input_container_1.className = "el-select"; input_container_1.style.width = "100%"; container.append(input_container_1); const input_container_2 = document.createElement("div"); input_container_2.className = "el-input el-input--suffix"; input_container_1.append(input_container_2); const input = document.createElement("input"); input.className = "el-input__inner"; input.readOnly = true; input.autocomplete = "off"; input_container_2.append(input); const input_suffix = document.createElement("span"); input_suffix.className = "el-input__suffix"; input_container_2.append(input_suffix); const input_suffix_inner = document.createElement("span"); input_suffix_inner.className = "el-input__suffix-inner"; input_suffix.append(input_suffix_inner); const input_icon = document.createElement("i"); input_icon.className = "el-select__caret el-input__icon el-icon-arrow-up"; input_suffix_inner.append(input_icon); input.value = options[0]; const dropdown = document.createElement("div"); dropdown.className = "el-select-dropdown el-popper"; dropdown.style.minWidth = "180px"; dropdown.style.zIndex = "10000"; dropdown.style.position = "absolute"; dropdown.style.transition = "transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1)"; dropdown.style.opacity = "0"; container.append(dropdown); const dropdown_arrow = document.createElement("div"); dropdown_arrow.className = "popper__arrow"; dropdown_arrow.style.left = "35px"; dropdown_arrow.setAttribute("x-arrow", ""); dropdown.append(dropdown_arrow); const dropdown_scrollbar = document.createElement("div"); dropdown_scrollbar.className = "el-scrollbar"; dropdown.append(dropdown_scrollbar); const dropdown_list_container = document.createElement("div"); dropdown_list_container.className = "el-select-dropdown__wrap el-scrollbar__wrap"; dropdown_list_container.style.overflow = "auto"; dropdown_scrollbar.append(dropdown_list_container); const dropdown_list = document.createElement("ul"); dropdown_list.className = "el-scrollbar__view el-select-dropdown__list"; dropdown_list_container.append(dropdown_list); for (const option of options) { const dropdown_list_item = document.createElement("li"); dropdown_list_item.className = "el-select-dropdown__item"; dropdown_list_item.innerText = option; dropdown_list_item.onmousedown = () => { input.value = option; }; dropdown_list.append(dropdown_list_item); } const dropdown_dims = dropdown.getBoundingClientRect(); const dropdown_arrow_dims = dropdown_arrow.getBoundingClientRect(); const full_dropdown_height = dropdown_dims.height + dropdown_arrow_dims.height; dropdown.style.transform = "scaleY(0)"; function setDropdownPosition() { const input_dims = input_container_1.getBoundingClientRect(); const dropdown_fits_below = window.innerHeight - input_dims.bottom >= full_dropdown_height; if (dropdown_fits_below) { dropdown.style.top = `${window.scrollY + input_dims.bottom}px`; dropdown.setAttribute("x-placement", "bottom-start"); dropdown.style.transformOrigin = "center top"; } else { // Subtract 17px for the margins dropdown.style.top = `${ window.scrollY + input_dims.top - dropdown_dims.height - 17 }px`; dropdown.setAttribute("x-placement", "top-start"); dropdown.style.transformOrigin = "center bottom"; } dropdown.style.left = `${window.scrollX + input_dims.left}px`; } new IntersectionObserver( () => { setDropdownPosition(); }, { rootMargin: `0px 0px -${dropdown_dims.height}px 0px`, threshold: 1, } ).observe(input_container_1); let isFocused = false; input_container_1.onclick = (event) => { if (isFocused) { input.blur(); } else { input.focus(); } }; input.onmousedown = () => false; // Prevent default input.onfocus = () => { isFocused = true; setDropdownPosition(); input_container_2.className += " is-focus"; input_icon.className += " is-reverse"; for (const item of dropdown_list.children) { item.classList.remove("selected"); if (item.innerText === input.value) { item.className += " selected"; } } dropdown.style.transform = "scaleY(1)"; dropdown.style.opacity = "1"; }; input.onblur = () => { isFocused = false; input_container_2.classList.remove("is-focus"); input_icon.classList.remove("is-reverse"); dropdown.style.transform = "scaleY(0)"; dropdown.style.opacity = "0"; }; // To prevent the input blurring when clicking the icon input_icon.onmousedown = (event) => { event.preventDefault(); }; return input; } /** * Creates a number input. Step can be 0 to have step=1 and to not limit values to steppable ones. * @returns The number input element. * @param {HTMLElement} container A parent element to append the input to. * @param {number} max_value The maximum value the input can reach. * @param {number} step The step value of the input. */ function createNumberInput(container, max_value = Infinity, step = 1) { const input_container_1 = document.createElement("div"); input_container_1.className = "el-input-number is-controls-right"; input_container_1.style.width = "100%"; container.append(input_container_1); const input_decrease_button = document.createElement("span"); input_decrease_button.className = "el-input-number__decrease is-disabled"; input_decrease_button.role = "button"; input_container_1.append(input_decrease_button); const input_decrease_button_arrow = document.createElement("i"); input_decrease_button_arrow.className = "el-icon-arrow-down"; input_decrease_button.append(input_decrease_button_arrow); const input_increase_button = document.createElement("span"); input_increase_button.className = "el-input-number__increase"; input_increase_button.role = "button"; input_container_1.append(input_increase_button); const input_increase_button_arrow = document.createElement("i"); input_increase_button_arrow.className = "el-icon-arrow-up"; input_increase_button.append(input_increase_button_arrow); const input_container_2 = document.createElement("div"); input_container_2.className = "el-input"; input_container_1.append(input_container_2); const input = document.createElement("input"); input.className = "el-input__inner"; input.type = "number"; input.min = input.value = 0; input.max = max_value; input.step = step; input_container_2.append(input); function setButtonDisabledStatus() { if (Number(input.value) <= input_min) { input_decrease_button.className += " is-disabled"; } else { input_decrease_button.classList.remove("is-disabled"); } if (Number(input.value) >= input_max) { input_increase_button.className += " is-disabled"; } else { input_increase_button.classList.remove("is-disabled"); } } input_decrease_button.onclick = () => { input.stepDown(); setButtonDisabledStatus(); }; input_increase_button.onclick = () => { input.stepUp(); setButtonDisabledStatus(); }; input.oninput = setButtonDisabledStatus; const input_max = Number(input.max); const input_min = Number(input.min); function makeValueValid() { // https://stackoverflow.com/questions/17369098/simplest-way-of-getting-the-number-of-decimals-in-a-number-in-javascript // Step 0 is a special case that should ignore step validation if (step !== 0 && Math.floor(Number(input.value)) !== Number(input.value)) { let decimalCount; if (input.value.indexOf(".") !== -1 && input.value.indexOf("-") !== -1) { decimalCount = input.value.split(/[.-]/)[1].length + input.value.split("-")[1] || 0; } else if (input.value.indexOf(".") !== -1) { decimalCount = input.value.split(".")[1].length || 0; } else { decimalCount = input.value.split("-")[1] || 0; } // Using Math.round to clean up arithmetic imprecisions const remainder = Math.round((Number(input.value) % step) * Math.pow(10, decimalCount)) / Math.pow(10, decimalCount); input.value = Number(input.value) - remainder; } if (Number(input.value) > input_max) { input.value = input_max; } if (Number(input.value) < input_min) { input.value = input_min; } } input.onblur = makeValueValid; input.onkeydown = (event) => { if (event.key === "Enter") { makeValueValid(); } }; return input; } /** * Creates a date input. * @param {HTMLElement} container A parent element to append the input to. * @returns The date input element. */ function createDateInput(container) { const input_container = document.createElement("div"); input_container.className = "el-input el-input--suffix"; container.append(input_container); const input = document.createElement("input"); input.className = "el-input__inner"; input.type = "date"; input.style.width = "100%"; input_container.append(input); const input_icon_container = document.createElement("span"); input_icon_container.className = "el-input__suffix"; input_container.append(input_icon_container); const input_icon = document.createElement("i"); input_icon.className = "el-input__icon el-icon-date"; input_icon.style.pointerEvents = "auto"; input_icon_container.append(input_icon); input_icon.onclick = () => { input.showPicker(); }; return input; } /** * Creates a textarea. * @param {HTMLElement} container A parent element to append the input to. * @returns The textarea element. */ function createTextarea(container) { const textarea_container = document.createElement("div"); textarea_container.className = "el-textarea"; container.append(textarea_container); const textarea = document.createElement("textarea"); textarea.className = "el-textarea__inner"; textarea.style.minHeight = "34px"; textarea_container.append(textarea); return textarea; } /** * Creates a checkbox. * @param {HTMLElement} container A parent element to append the checkbox to. * @param {string} text The text of the checkbox's label. * @returns The checkbox element. */ function createCheckbox(container, text) { const checkbox_container = document.createElement("div"); checkbox_container.className = "rtonne-anilist-multiselect-checkbox"; checkbox_container.style.paddingBottom = "4px"; container.append(checkbox_container); const label = document.createElement("label"); label.innerText = text; label.className = "el-checkbox"; label.style.fontSize = "1.2rem"; label.style.fontWeight = "400"; checkbox_container.append(label); const middle_label = document.createElement("label"); middle_label.className = "el-checkbox__input"; middle_label.style.marginRight = "4px"; middle_label.style.verticalAlign = "text-top"; label.prepend(middle_label); const button_label = document.createElement("label"); button_label.className = "el-checkbox__inner"; button_label.style.cursor = "pointer"; middle_label.append(button_label); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.className = "el-checkbox__original"; button_label.prepend(checkbox); checkbox.onchange = () => { if (checkbox.checked) { label.className += " is-checked"; middle_label.className += " is-checked"; } else { label.classList.remove("is-checked"); middle_label.classList.remove("is-checked"); } }; return checkbox; } /** * Creates an indeterminate checkbox. * When the return checkbox.readOnly or checkbox.indeterminate is true, * it is on a 3rd state where it isn't on nor off. * From https://css-tricks.com/indeterminate-checkboxes/. * @param {HTMLElement} container A parent element to append the input to. * @param {string} text The text of the checkbox's label. * @returns The checkbox element. */ function createIndeterminateCheckbox(container, text) { const checkbox_container = document.createElement("div"); checkbox_container.className = "rtonne-anilist-multiselect-checkbox"; checkbox_container.style.paddingBottom = "4px"; container.append(checkbox_container); const label = document.createElement("label"); label.innerText = text; label.className = "el-checkbox"; label.style.fontSize = "1.2rem"; label.style.fontWeight = "400"; checkbox_container.append(label); const middle_label = document.createElement("label"); middle_label.className = "el-checkbox__input"; middle_label.style.marginRight = "4px"; middle_label.style.verticalAlign = "text-top"; label.prepend(middle_label); const button_label = document.createElement("label"); button_label.className = "el-checkbox__inner"; button_label.style.cursor = "pointer"; middle_label.append(button_label); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.className = "el-checkbox__original"; checkbox.readOnly = true; checkbox.indeterminate = true; button_label.prepend(checkbox); checkbox.onclick = () => { if (checkbox.readOnly) { checkbox.checked = checkbox.readOnly = false; } else if (!checkbox.checked) { checkbox.readOnly = checkbox.indeterminate = true; } }; checkbox.onchange = () => { if (checkbox.checked) { label.className += " is-checked"; middle_label.className += " is-checked"; } else { label.classList.remove("is-checked"); middle_label.classList.remove("is-checked"); } }; return checkbox; } /** * Creates a button. * @param {HTMLElement} container A parent element to append the input to. * @param {string} text The text inside the button. * @returns The button element. */ function createButton(container, text) { const button = document.createElement("button"); button.innerText = text; button.style.backgroundColor = "rgb(var(--color-blue-600))"; button.style.color = "rgb(var(--color-text-bright))"; button.style.border = "none"; button.style.borderRadius = "3px"; button.style.cursor = "pointer"; button.style.fontSize = "12px"; button.style.padding = "9px 15px"; button.style.transition = ".2s"; container.append(button); return button; } /** * Creates a cancel button. * @param {HTMLElement} container A parent element to append the input to. * @param {string} text The text inside the button. * @returns The button element. */ function createCancelButton(container, text) { const button = createButton(container, text); button.style.backgroundColor = "rgba(var(--color-background),.8)"; button.style.color = "rgb(var(--color-text))"; return button; } /** * Creates a cancel button with a lighter background. * @param {HTMLElement} container A parent element to append the input to. * @param {string} text The text inside the button. * @returns The button element. */ function createCancelLighterButton(container, text) { const button = createButton(container, text); button.style.backgroundColor = "rgba(var(--color-foreground),.8)"; button.style.color = "rgb(var(--color-text))"; return button; } /** * Creates a danger button. * @param {HTMLElement} container A parent element to append the input to. * @param {string} text The text inside the button. * @returns The button element. */ function createDangerButton(container, text) { const button = createButton(container, text); button.style.backgroundColor = "rgba(var(--color-red),.8)"; button.style.color = "rgb(var(--color-white))"; return button; } /** * Creates a confirmation popup. * @param {string} title_text A text/html title for the popup. * @param {string} message_text The text/html content of the popup. * @returns The confirm button of the popup. */ function createConfirmPopup(title_text, message_text) { const modal = document.createElement("div"); modal.className = "v-modal"; modal.style.zIndex = "99999"; document.body.append(modal); const wrapper = document.createElement("div"); wrapper.className = "el-message-box__wrapper"; wrapper.style.zIndex = "100000"; document.body.append(wrapper); const container = document.createElement("div"); container.className = "el-message-box"; wrapper.append(container); const header = document.createElement("div"); header.className = "el-message-box__header"; container.append(header); const title = document.createElement("div"); title.className = "el-message-box__title"; title.innerHTML = `<span>${title_text}</span>`; header.append(title); const close_button = document.createElement("button"); close_button.className = "el-message-box__headerbtn"; header.append(close_button); const close_button_icon = document.createElement("i"); close_button_icon.className = "el-message-box__close el-icon-close"; close_button.append(close_button_icon); const content = document.createElement("div"); content.className = "el-message-box__content"; container.append(content); const message = document.createElement("div"); message.className = "el-message-box__message"; message.innerHTML = `<p>${message_text}</p>`; content.append(message); const buttons = document.createElement("div"); buttons.className = "el-message-box__btns"; container.append(buttons); const cancel_button = createCancelButton(buttons, "Cancel"); const confirm_button = createButton(buttons, "Confirm"); wrapper.addEventListener("click", (e) => { // e.stopPropagation() doesn't seem to work so this condition is here if (e.target !== wrapper) { return; } modal.remove(); wrapper.remove(); }); close_button.onclick = cancel_button.onclick = () => { modal.remove(); wrapper.remove(); }; // Used .addEventListener instead of .onclick // so either can be used outside this function confirm_button.addEventListener("click", () => { modal.remove(); wrapper.remove(); }); return confirm_button; } /** * Creates an updatable cancel popup. * @param {string} initial_title * @param {HTMLElement} initial_content * @returns The two elements that close the popup with the click event, two functions to update the popup, and a function to close the popup. */ function createUpdatableCancelPopup(initial_title, initial_content) { const modal = document.createElement("div"); modal.className = "v-modal"; modal.style.zIndex = "99999"; document.body.append(modal); const wrapper = document.createElement("div"); wrapper.className = "el-message-box__wrapper"; wrapper.style.zIndex = "100000"; document.body.append(wrapper); const container = document.createElement("div"); container.className = "el-message-box"; wrapper.append(container); const header = document.createElement("div"); header.className = "el-message-box__header"; container.append(header); const title_element = document.createElement("div"); title_element.className = "el-message-box__title"; title_element.innerHTML = `<span>${initial_title}</span>`; header.append(title_element); const content_element = document.createElement("div"); content_element.className = "el-message-box__content"; container.append(content_element); const message = document.createElement("div"); message.className = "el-message-box__message"; message.replaceChildren(initial_content); content_element.append(message); const buttons = document.createElement("div"); buttons.className = "el-message-box__btns"; container.append(buttons); const cancel_button = createCancelButton(buttons, "Cancel"); function closePopup() { modal.remove(); wrapper.remove(); } // Used .addEventListener instead of .onclick // so either can be used outside this function wrapper.addEventListener("click", (e) => { // e.stopPropagation() doesn't seem to work so this condition is here if (e.target !== wrapper) { return; } closePopup(); }); cancel_button.addEventListener("click", () => { closePopup(); }); function changeTitle(title) { title_element.innerHTML = `<span>${title}</span>`; } function changeContent(content) { message.replaceChildren(content); } return { popup_wrapper: wrapper, popup_cancel_button: cancel_button, changePopupTitle: changeTitle, changePopupContent: changeContent, closePopup: closePopup, }; } /** * Creates entry specific content to add to a popup. * @param {string} text Text to display to the left of the cover. * @param {string} cover The cover to add as a background-image style. * @param {number} current_index The index of the current entry being show. * @param {number} total The total entries going to be shown. * @returns */ function createEntryPopupContent(text, cover, current_index, total) { const content = document.createElement("div"); content.style.display = "flex"; content.style.flexWrap = "wrap"; content.style.flexDirection = "column"; content.style.gap = "10px"; content.style.justifyContent = "center"; content.style.alignItems = "center"; content.style.textAlign = "center"; const content_text = document.createElement("span"); content_text.innerHTML = text; content_text.style.flexGrow = "1"; content.append(content_text); const content_image = document.createElement("div"); content_image.style.backgroundImage = cover; content_image.style.backgroundPosition = "50%"; content_image.style.backgroundRepeat = "no-repeat"; content_image.style.backgroundSize = "cover"; content_image.style.borderRadius = "3px"; content_image.style.minHeight = "210px"; content_image.style.minWidth = "150px"; content.append(content_image); const bar = document.createElement("div"); bar.style.width = "100%"; bar.style.height = "24px"; bar.style.display = "flex"; bar.style.justifyContent = "center"; bar.style.alignItems = "center"; bar.style.textAlign = "center"; bar.style.background = `linear-gradient(90deg, rgb(var(--color-blue)) ${Math.floor( (current_index / total) * 100 )}%, rgb(var(--color-background)) 0)`; bar.style.borderRadius = "3px"; content.append(bar); const bar_text = document.createElement("span"); bar_text.innerText = `${current_index} / ${total}`; bar_text.style.boxShadow = `inset 0 0 0 100vw rgb(var(--color-background)), 0 0 0 2px rgb(var(--color-background)), 0 0 3px 3px rgb(var(--color-background))`; bar_text.style.borderRadius = "1px"; bar.appendChild(bar_text); return content; } /** * Creates an error popup. * @param {string} text The message/content of the popup. * @returns {Promise<boolean>} A promise that returns if the user asked to cancel. */ function createErrorPopup(text) { const modal = document.createElement("div"); modal.className = "v-modal"; modal.style.zIndex = "99999"; document.body.append(modal); const wrapper = document.createElement("div"); wrapper.className = "el-message-box__wrapper"; wrapper.style.zIndex = "100000"; document.body.append(wrapper); const container = document.createElement("div"); container.className = "el-message-box"; wrapper.append(container); const header = document.createElement("div"); header.className = "el-message-box__header"; container.append(header); const title = document.createElement("div"); title.className = "el-message-box__title"; title.innerHTML = `<span>ERROR<span>`; header.append(title); const close_button = document.createElement("button"); close_button.className = "el-message-box__headerbtn"; header.append(close_button); const close_button_icon = document.createElement("i"); close_button_icon.className = "el-message-box__close el-icon-close"; close_button.append(close_button_icon); const content = document.createElement("div"); content.className = "el-message-box__content"; container.append(content); const message = document.createElement("div"); message.className = "el-message-box__message"; message.innerHTML = `<p>${text}</p>`; content.append(message); const buttons = document.createElement("div"); buttons.className = "el-message-box__btns"; container.append(buttons); const cancel_button = createDangerButton(buttons, "Cancel"); const retry_button = createButton(buttons, "Retry"); return new Promise((resolve) => { wrapper.onclick = (e) => { // e.stopPropagation() doesn't seem to work so this condition is here if (e.target !== wrapper) { return; } modal.remove(); wrapper.remove(); resolve(true); }; close_button.onclick = cancel_button.onclick = () => { modal.remove(); wrapper.remove(); resolve(true); }; retry_button.onclick = () => { modal.remove(); wrapper.remove(); resolve(false); }; }); } /** * Creates a popup. * @param {string} title_text A text/html title for the popup. * @param {string} message_text The text/html content of the popup. * @returns {Promise<void>} */ function createPopup(title_text, message_text) { const modal = document.createElement("div"); modal.className = "v-modal"; modal.style.zIndex = "99999"; document.body.append(modal); const wrapper = document.createElement("div"); wrapper.className = "el-message-box__wrapper"; wrapper.style.zIndex = "100000"; document.body.append(wrapper); const container = document.createElement("div"); container.className = "el-message-box"; wrapper.append(container); const header = document.createElement("div"); header.className = "el-message-box__header"; container.append(header); const title = document.createElement("div"); title.className = "el-message-box__title"; title.innerHTML = `<span>${title_text}</span>`; header.append(title); const close_button = document.createElement("button"); close_button.className = "el-message-box__headerbtn"; header.append(close_button); const close_button_icon = document.createElement("i"); close_button_icon.className = "el-message-box__close el-icon-close"; close_button.append(close_button_icon); const content = document.createElement("div"); content.className = "el-message-box__content"; container.append(content); const message = document.createElement("div"); message.className = "el-message-box__message"; message.innerHTML = `<p>${message_text}</p>`; content.append(message); const buttons = document.createElement("div"); buttons.className = "el-message-box__btns"; container.append(buttons); const ok_button = createButton(buttons, "Ok"); return new Promise((resolve) => { wrapper.onclick = (e) => { // e.stopPropagation() doesn't seem to work so this condition is here if (e.target !== wrapper) { return; } modal.remove(); wrapper.remove(); resolve(); }; close_button.onclick = ok_button.onclick = () => { modal.remove(); wrapper.remove(); resolve(); }; }); }