From e9e3574bc4fedf97c0ac613dd7972f9fd8fe929e Mon Sep 17 00:00:00 2001 From: Weilin Chen Date: Fri, 19 Jun 2026 11:12:26 +0800 Subject: [PATCH] fix: resolve issue where label button does not display --- index.js | 621 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 412 insertions(+), 209 deletions(-) diff --git a/index.js b/index.js index 8cbd0eb..464e27a 100644 --- a/index.js +++ b/index.js @@ -63,6 +63,75 @@ const ALL_TAGS_BUTTON = ".jkGZFM"; // 标签切换窗口触发按钮 const ALL_TAGS_CONTAINER = ".hpRxDJ"; // 标签按钮容器 const ALL_TAGS_MODAL = ".ggMyQW"; // 原生标签切换窗口 const ALL_TAGS_MODAL_CONTAINER = ".gOPhqx"; // 原生标签切换窗口中标签按钮容器 +const CURRENT_PAGE_SIZE = 48; + +function getReactData(el) { + if (!el) return null; + const key = Object.keys(el).find( + (key) => + key.startsWith("__reactFiber$") || + key.startsWith("__reactProps$") || + key.startsWith("__reactInternalInstance$"), + ); + return key ? el[key] : null; +} + +function findDeep(root, predicate, maxDepth = 8) { + const seen = new Set(); + const stack = [{ value: root, depth: 0 }]; + while (stack.length) { + const { value, depth } = stack.pop(); + if (!value || typeof value !== "object" || seen.has(value)) continue; + seen.add(value); + if (predicate(value)) return value; + if (depth >= maxDepth) continue; + for (const child of Object.values(value)) { + if (child && typeof child === "object") { + stack.push({ value: child, depth: depth + 1 }); + } + } + } + return null; +} + +function getBookmarkPageState() { + const url = new URL(window.location.href); + const userId = url.pathname.match(/\/users\/(\d+)/)?.[1] || uid; + const tagMatch = decodeURIComponent(url.pathname).match( + /\/bookmarks\/artworks\/?([^/]*)/, + ); + const tag = tagMatch?.[1] || ""; + const page = parseInt(url.searchParams.get("p") || "1"); + const publicationType = url.searchParams.get("rest") === "hide" ? "hide" : "show"; + return { + userId: userId ? userId.toString() : "", + tag, + page, + publicationType, + restrict: publicationType === "hide", + offset: (page - 1) * CURRENT_PAGE_SIZE, + }; +} + +function getClientInfoFromGlobals() { + const context = unsafeWindow_?.pixiv?.context || {}; + const user = context.user || unsafeWindow_?.pixiv?.user || {}; + const dataLayer = unsafeWindow_?.dataLayer?.[0] || {}; + return { + uid: (dataLayer.user_id || user.id || context.userId || uid || "").toString(), + lang: + dataLayer.lang || + context.lang || + document.documentElement.lang || + navigator.language || + "en", + token: + context.token || + unsafeWindow_?.pixiv?.token || + document.querySelector("meta[name='csrf-token']")?.content || + token, + }; +} function getCharacterName(tag) { return tag.split("(")[0]; @@ -253,11 +322,17 @@ function loadResources() { } const bookmarkBatchSize = 100; -async function fetchBookmarks(uid, tagToQuery, offset, publicationType) { +async function fetchBookmarks( + uid, + tagToQuery, + offset, + publicationType, + limit = bookmarkBatchSize, +) { const bookmarksRaw = await fetch( `/ajax/user/${uid}` + - `/illusts/bookmarks?tag=${tagToQuery}` + - `&offset=${offset}&limit=${bookmarkBatchSize}&rest=${publicationType}`, + `/illusts/bookmarks?tag=${encodeURIComponent(tagToQuery || "")}` + + `&offset=${offset}&limit=${limit}&rest=${publicationType}`, ); if (!turboMode) await delay(500); const bookmarksRes = await bookmarksRaw.json(); @@ -607,16 +682,21 @@ async function handleClearBookmarkTags(evt) { const works = selected .map((el) => { - const middleChild = Object.values( - el.parentNode.parentNode.parentNode.parentNode, - )[0]["child"]; - const work = middleChild["memoizedProps"]["work"]; - work.associatedTags = - middleChild["child"]["memoizedProps"]["associatedTags"] || []; - work.bookmarkId = middleChild["memoizedProps"]["bookmarkId"]; + const reactData = getReactData(el.closest("li")) || getReactData(el); + const propsNode = findDeep( + reactData, + (obj) => obj.memoizedProps?.work && obj.memoizedProps?.bookmarkId, + ); + const tagNode = findDeep(reactData, (obj) => + Array.isArray(obj.memoizedProps?.associatedTags), + ); + const work = propsNode?.memoizedProps?.work; + if (!work) return null; + work.associatedTags = tagNode?.memoizedProps?.associatedTags || []; + work.bookmarkId = propsNode.memoizedProps.bookmarkId; return work; }) - .filter((work) => work.associatedTags.length); + .filter((work) => work?.associatedTags?.length); await clearBookmarkTags(works); } @@ -1441,6 +1521,7 @@ function shuffle(array) { const hold = false; function createModalElements() { + if (document.querySelector("#label_modal")) return; // noinspection TypeScriptUMDGlobal const bootstrap_ = bootstrap; const bgColor = theme ? "bg-white" : "bg-dark"; @@ -2932,17 +3013,17 @@ Tag ${tag} will be renamed to ${newName}.\n All related works (both public and p } async function fetchUserTags() { - const tagsRaw = await fetch( - `/ajax/user/${uid}/illusts/bookmark/tags?lang=${lang}`, - ); + const tagsRaw = await fetch(`/ajax/user/${uid}/illusts/bookmark/tags?lang=${lang}`); const tagsObj = await tagsRaw.json(); - if (tagsObj.error === true) - return alert( + if (!tagsRaw.ok || tagsObj.error === true) { + alert( `获取tags失败 Fail to fetch user tags` + "\n" + - decodeURI(tagsObj.message), + decodeURI(tagsObj.message || ""), ); + return []; + } userTagDict = tagsObj.body; const userTagsSet = new Set(); const addTag2Set = (tag) => { @@ -2959,10 +3040,10 @@ Error loading tag ${tag}. Please press F12 and take a screenshot of the error me } } }; - for (let obj of userTagDict.public) { + for (let obj of userTagDict.public || []) { addTag2Set(obj.tag); } - for (let obj of userTagDict["private"]) { + for (let obj of userTagDict["private"] || []) { addTag2Set(obj.tag); } userTagsSet.delete("未分類"); @@ -2985,34 +3066,81 @@ async function fetchTokenPolyfill() { return userRes.slice(tokenPos, tokenEnd).split('"')[1]; } +async function fetchCurrentWorkInfo(bookmarkTags) { + const state = getBookmarkPageState(); + if (!state.userId) return null; + const bookmarks = await fetchBookmarks( + state.userId, + state.tag, + state.offset, + state.publicationType, + CURRENT_PAGE_SIZE, + ); + if (!bookmarks?.works) return null; + if (bookmarkTags) { + bookmarks.works.forEach((work) => { + const bookmarkId = work["bookmarkData"]?.["id"]; + work.associatedTags = bookmarks["bookmarkTags"]?.[bookmarkId] || []; + }); + } + return { + ...bookmarks, + ...state, + tag: state.tag, + editMode: !!document.querySelector(REMOVE_BOOKMARK_CONTAINER), + }; +} + async function updateWorkInfo(bookmarkTags) { + try { + const workInfo = await fetchCurrentWorkInfo(bookmarkTags); + if (workInfo?.works) return workInfo; + } catch (err) { + if (DEBUG) console.log("[Label Bookmarks] API work info failed", err); + } + const el = await waitForDom(WORK_SECTION); let workInfo = {}; for (let i = 0; i < 100; i++) { - workInfo = Object.values(el)[0]["memoizedProps"]["children"][2]["props"]; + const reactData = getReactData(el); + workInfo = + reactData?.memoizedProps?.children?.[2]?.props || + findDeep(reactData, (obj) => Array.isArray(obj.works)) || + {}; if (Object.keys(workInfo).length) break; else await delay(200); } if (bookmarkTags) { [...el.querySelectorAll("li")].forEach((li, i) => { - workInfo["works"][i].associatedTags = - Object.values(li)[0].child.child["memoizedProps"].associatedTags; + const reactData = getReactData(li); + const propsWithTags = findDeep(reactData, (obj) => + Array.isArray(obj.associatedTags), + ); + if (workInfo["works"]?.[i]) { + workInfo["works"][i].associatedTags = + propsWithTags?.associatedTags || []; + } }); } - const page = window.location.search.match(/p=(\d+)/)?.[1] || 1; - workInfo.page = parseInt(page); - return workInfo; + const state = getBookmarkPageState(); + return { ...workInfo, ...state, tag: workInfo.tag || state.tag }; } async function initializeVariables() { async function polyfill() { try { - const dataLayer = unsafeWindow_["dataLayer"][0]; - uid = dataLayer["user_id"]; - lang = dataLayer["lang"]; - token = await fetchTokenPolyfill(); - pageInfo.userId = window.location.href.match(/users\/(\d+)/)?.[1]; + const globalInfo = getClientInfoFromGlobals(); + const state = getBookmarkPageState(); + uid = globalInfo.uid || state.userId; + lang = globalInfo.lang || "en"; + token = globalInfo.token; + pageInfo = pageInfo || {}; + pageInfo.userId = pageInfo.userId || state.userId || uid; pageInfo.client = { userId: uid, lang, token }; + if (!token) { + token = await fetchTokenPolyfill(); + pageInfo.client.token = token; + } } catch (err) { console.log(err); console.log("[Label Bookmarks] Initializing Failed"); @@ -3021,13 +3149,24 @@ async function initializeVariables() { try { if (DEBUG) console.log("[Label Bookmarks] Initializing Variables"); - pageInfo = Object.values(document.querySelector(BANNER))[0]["return"][ - "return" - ]["memoizedProps"]; + const globalInfo = getClientInfoFromGlobals(); + const state = getBookmarkPageState(); + const reactData = getReactData(document.querySelector(BANNER)); + pageInfo = + reactData?.return?.return?.memoizedProps || + findDeep(reactData, (obj) => obj.client?.userId) || + {}; + pageInfo.userId = (pageInfo.userId || state.userId || "").toString(); if (DEBUG) console.log("[Label Bookmarks] Page Info", pageInfo); - uid = pageInfo["client"]["userId"]; - token = pageInfo["client"]["token"]; - lang = pageInfo["client"]["lang"]; + uid = ( + pageInfo["client"]?.["userId"] || + globalInfo.uid || + state.userId || + "" + ).toString(); + token = pageInfo["client"]?.["token"] || globalInfo.token; + lang = pageInfo["client"]?.["lang"] || globalInfo.lang || "en"; + pageInfo.client = { userId: uid, lang, token }; if (!uid || !token || !lang) await polyfill(); } catch (err) { console.log(err); @@ -3035,6 +3174,8 @@ async function initializeVariables() { } userTags = await fetchUserTags(); + if (!Array.isArray(userTags)) userTags = []; + userTagDict = userTagDict || { public: [], private: [] }; // workType = Object.values(document.querySelector(".sc-1x9383j-0"))[0].child["memoizedProps"]["workType"]; @@ -3063,6 +3204,8 @@ async function initializeVariables() { }).observe(themeDiv, { attributes: true }); synonymDict = getValue("synonymDict", {}); + if (!synonymDict || typeof synonymDict !== "object" || Array.isArray(synonymDict)) + synonymDict = {}; if (Object.keys(synonymDict).length) { // remove empty values on load, which could be caused by unexpected interruption for (let key of Object.keys(synonymDict)) { @@ -3089,21 +3232,26 @@ async function waitForDom(selector, container) { async function injectElements() { if (DEBUG) console.log("[Label Bookmarks] Start Injecting"); const textColor = theme ? "text-lp-dark" : "text-lp-light"; - const pageBody = document.querySelector(PAGE_BODY); + const pageBody = + document.querySelector(PAGE_BODY) || document.querySelector("main") || document.body; const root = document.querySelector("nav"); - if (!root) console.log("[Label Bookmarks] Navbar Not Found"); + if (!root) throw new ReferenceError("[Label Bookmarks] Navbar Not Found"); root.classList.add("d-flex"); - const buttonContainer = document.createElement("span"); - buttonContainer.className = "flex-grow-1 justify-content-end d-flex"; - buttonContainer.id = "label_bookmarks_buttons"; - const gClass = generator ? "" : "d-none"; - const fClass = feature ? "" : "d-none"; - buttonContainer.innerHTML = ` - + + + `; + } const clearTagsThemeClass = theme ? "OPGIe" : "bDwYXF"; const clearTagsText = lang.includes("zh") ? "清除标签" : "Clear Tags"; @@ -3139,7 +3287,8 @@ async function injectElements() { async function injection(_, injectionObserver) { if (_) console.log(_); - if (pageInfo["userId"] !== uid) { + const pageUserId = getBookmarkPageState().userId || pageInfo?.["userId"]; + if (pageUserId && uid && pageUserId.toString() !== uid.toString()) { if (DEBUG) console.log( "[Label Bookmarks] Aborted Injection due to mismatch homepage", @@ -3150,145 +3299,171 @@ async function injectElements() { console.log("[Label Bookmarks] Try Injecting"); - const workInfo = await updateWorkInfo(true); + if (buttonContainer.parentElement !== document.body) + document.body.appendChild(buttonContainer); + setElementProperties(); + if (!window.labelBookmarksSynonymEventSet) { + setSynonymEventListener(); + window.labelBookmarksSynonymEventSet = true; + } + if (!document.querySelector("#basic_search_field").children.length) + setAdvancedSearch(); + + let workInfo = {}; + try { + workInfo = await updateWorkInfo(true); + } catch (err) { + console.log("[Label Bookmarks] Work info unavailable, buttons injected", err); + return true; + } if (!workInfo["works"]) { - if (injectionObserver) + if (injectionObserver && pageBody) injectionObserver.observe(pageBody, { childList: true }); - return console.log( + console.log( "[Label Bookmarks] Abort Injection due to no works detected yet", ); + return false; } if (DEBUG) { console.log("[Label Bookmarks] User Tags", userTags, userTagDict); console.log("[Label Bookmarks] Dict:", synonymDict); } - root.appendChild(buttonContainer); - setElementProperties(); - setSynonymEventListener(); - setAdvancedSearch(); - // show user-labeled tags - const ul = await waitForDom(WORK_CONTAINER); - async function updateAssociatedTagsCallback() { - const workInfo = await updateWorkInfo(true); - if (DEBUG) console.log("[Label Bookmarks] Page", workInfo.page, workInfo); - // TODO - [...ul.querySelectorAll("li")].forEach((li, i) => { - const pa = li.firstElementChild.firstElementChild; - if (showWorkTags) { - const tagsString = workInfo["works"][i].associatedTags - .map((i) => "#" + i) - .join(" "); - const tagDiv = document.createElement("div"); - tagDiv.className = "my-1"; - tagDiv.style.cssText = - "font-size: 10px; color: rgb(61, 118, 153); pointer-events: none"; - tagDiv.innerHTML = tagsString; - pa.insertBefore(tagDiv, pa.children[1]); - } - if (workInfo["works"][i]["userName"] === "-----") { - const pidDiv = document.createElement("div"); - pidDiv.className = "my-1"; - pidDiv.style.cssText = - "font-size: 10px; color: rgb(61, 118, 153); pointer-events: none"; - pidDiv.innerHTML = `PID: ${workInfo["works"][i].id}`; - pa.insertBefore(pidDiv, pa.children[1]); - } + try { + const ul = await waitForDom(WORK_CONTAINER); + async function updateAssociatedTagsCallback() { + const workInfo = await updateWorkInfo(true); + if (DEBUG) console.log("[Label Bookmarks] Page", workInfo.page, workInfo); + [...ul.querySelectorAll("li")].forEach((li, i) => { + const pa = li.firstElementChild?.firstElementChild; + const work = workInfo["works"]?.[i]; + if (!pa || !work) return; + if (showWorkTags) { + const tagsString = (work.associatedTags || []) + .map((i) => "#" + i) + .join(" "); + const tagDiv = document.createElement("div"); + tagDiv.className = "my-1"; + tagDiv.style.cssText = + "font-size: 10px; color: rgb(61, 118, 153); pointer-events: none"; + tagDiv.innerHTML = tagsString; + pa.insertBefore(tagDiv, pa.children[1]); + } + if (work["userName"] === "-----") { + const pidDiv = document.createElement("div"); + pidDiv.className = "my-1"; + pidDiv.style.cssText = + "font-size: 10px; color: rgb(61, 118, 153); pointer-events: none"; + pidDiv.innerHTML = `PID: ${work.id}`; + pa.insertBefore(pidDiv, pa.children[1]); + } + }); + } + await updateAssociatedTagsCallback(); + new MutationObserver(updateAssociatedTagsCallback).observe(ul, { + childList: true, }); + } catch (err) { + console.log("[Label Bookmarks] Work container unavailable, skipped", err); } - await updateAssociatedTagsCallback(); - new MutationObserver(updateAssociatedTagsCallback).observe(ul, { - childList: true, - }); - const editButtonContainer = await waitForDom(EDIT_BUTTON_CONTAINER); - if (editButtonContainer) { - editButtonContainer.style.justifyContent = "initial"; - editButtonContainer.firstElementChild.style.marginRight = "auto"; - editButtonContainer.insertBefore( - removeTagButton, - editButtonContainer.lastChild, - ); - let removeBookmarkContainerObserver; - const editButtonObserver = new MutationObserver( - async (MutationRecord) => { - const { tag } = await updateWorkInfo(); - if (!MutationRecord[0].addedNodes.length) { - // open edit mode - const removeBookmarkContainer = document.querySelector( - REMOVE_BOOKMARK_CONTAINER, - ); - removeBookmarkContainer.appendChild(clearTagsButton); - removeBookmarkContainerObserver = new MutationObserver(() => { - const value = - removeBookmarkContainer.children[2].getAttribute( - "aria-disabled", - ); - clearTagsButton.setAttribute("aria-disabled", value); - clearTagsButton.children[0].setAttribute("aria-disabled", value); - }); - removeBookmarkContainerObserver.observe( - removeBookmarkContainer.children[2], - { attributes: true }, - ); - if (tag && tag !== "未分類") { - document.querySelector("#remove_tag_prompt").innerText = - lang.includes("zh") ? "删除标签 " + tag : "Delete Tag " + tag; - removeTagButton.style.display = "flex"; + try { + const editButtonContainer = await waitForDom(EDIT_BUTTON_CONTAINER); + if (editButtonContainer) { + editButtonContainer.style.justifyContent = "initial"; + editButtonContainer.firstElementChild.style.marginRight = "auto"; + editButtonContainer.insertBefore( + removeTagButton, + editButtonContainer.lastChild, + ); + let removeBookmarkContainerObserver; + const editButtonObserver = new MutationObserver( + async (MutationRecord) => { + const { tag } = await updateWorkInfo(); + if (!MutationRecord[0].addedNodes.length) { + // open edit mode + const removeBookmarkContainer = document.querySelector( + REMOVE_BOOKMARK_CONTAINER, + ); + if (!removeBookmarkContainer?.children?.[2]) return; + removeBookmarkContainer.appendChild(clearTagsButton); + removeBookmarkContainerObserver = new MutationObserver(() => { + const value = + removeBookmarkContainer.children[2].getAttribute( + "aria-disabled", + ); + clearTagsButton.setAttribute("aria-disabled", value); + clearTagsButton.children[0].setAttribute("aria-disabled", value); + }); + removeBookmarkContainerObserver.observe( + removeBookmarkContainer.children[2], + { attributes: true }, + ); + if (tag && tag !== "未分類") { + document.querySelector("#remove_tag_prompt").innerText = + lang.includes("zh") ? "删除标签 " + tag : "Delete Tag " + tag; + removeTagButton.style.display = "flex"; + } + } else { + // exit edit mode + removeTagButton.style.display = "none"; + if (removeBookmarkContainerObserver) + removeBookmarkContainerObserver.disconnect(); + clearTagsButton.setAttribute("aria-disabled", "true"); + clearTagsButton.children[0].setAttribute("aria-disabled", "true"); + } + }, + ); + editButtonObserver.observe(editButtonContainer, { + childList: true, + }); + } + } catch (err) { + console.log("[Label Bookmarks] Edit button container unavailable, skipped", err); + } + + try { + let lastTag = workInfo.tag; + const tagsContainer = await waitForDom(ALL_TAGS_CONTAINER); + new MutationObserver(async () => { + const workInfo = await updateWorkInfo(); + if (lastTag !== workInfo.tag) { + lastTag = workInfo.tag; + const removeTagButton = document.querySelector("#remove_tag_button"); + if (!workInfo.tag || workInfo.tag === "未分類") { + if (removeTagButton && removeTagButton.style.display === "flex") { + removeTagButton.style.display = "none"; } } else { - // exit edit mode - removeTagButton.style.display = "none"; - if (removeBookmarkContainerObserver) - removeBookmarkContainerObserver.disconnect(); - clearTagsButton.setAttribute("aria-disabled", "true"); - clearTagsButton.children[0].setAttribute("aria-disabled", "true"); + if ( + workInfo["editMode"] && + removeTagButton && + removeTagButton.style.display === "none" + ) { + removeTagButton.style.display = "flex"; + } + const removeTagButtonPrompt = + document.querySelector("#remove_tag_prompt"); + if (removeTagButtonPrompt) + removeTagButtonPrompt.innerText = lang.includes("zh") + ? "删除标签 " + workInfo.tag + : "Delete Tag " + workInfo.tag; } - }, - ); - editButtonObserver.observe(editButtonContainer, { + } + if (DEBUG) + console.log( + "[Label Bookmarks] Current Tag", + workInfo.tag || "Uncategorized", + ); + }).observe(tagsContainer, { + subtree: true, childList: true, }); + } catch (err) { + console.log("[Label Bookmarks] Tags container unavailable, skipped", err); } - let lastTag = workInfo.tag; - const tagsContainer = await waitForDom(ALL_TAGS_CONTAINER); - new MutationObserver(async () => { - const workInfo = await updateWorkInfo(); - if (lastTag !== workInfo.tag) { - lastTag = workInfo.tag; - const removeTagButton = document.querySelector("#remove_tag_button"); - if (!workInfo.tag || workInfo.tag === "未分類") { - if (removeTagButton && removeTagButton.style.display === "flex") { - removeTagButton.style.display = "none"; - } - } else { - if ( - workInfo["editMode"] && - removeTagButton && - removeTagButton.style.display === "none" - ) { - removeTagButton.style.display = "flex"; - } - const removeTagButtonPrompt = - document.querySelector("#remove_tag_prompt"); - if (removeTagButtonPrompt) - removeTagButtonPrompt.innerText = lang.includes("zh") - ? "删除标签 " + workInfo.tag - : "Delete Tag " + workInfo.tag; - } - } - if (DEBUG) - console.log( - "[Label Bookmarks] Current Tag", - workInfo.tag || "Uncategorized", - ); - }).observe(tagsContainer, { - subtree: true, - childList: true, - }); - const toUncategorized = document.querySelector(WORK_NUM); if (toUncategorized) { toUncategorized.style.cursor = "pointer"; @@ -3323,31 +3498,35 @@ async function injectElements() { }); // all tags selection control - const prevAllTagsButton = await waitForDom(ALL_TAGS_BUTTON); - prevAllTagsButton.style.display = "none"; - addStyle(".ggMyQW { z-index: -1; }"); - const allTagsButton = document.createElement("div"); - allTagsButton.setAttribute("data-bs-toggle", "modal"); - allTagsButton.setAttribute("data-bs-target", "#all_tags_modal"); - allTagsButton.classList.add(ALL_TAGS_BUTTON.slice(1)); - allTagsButton.role = "button"; - allTagsButton.innerHTML = ` + try { + const prevAllTagsButton = await waitForDom(ALL_TAGS_BUTTON); + prevAllTagsButton.style.display = "none"; + addStyle(".ggMyQW { z-index: -1; }"); + const allTagsButton = document.createElement("div"); + allTagsButton.setAttribute("data-bs-toggle", "modal"); + allTagsButton.setAttribute("data-bs-target", "#all_tags_modal"); + allTagsButton.classList.add(ALL_TAGS_BUTTON.slice(1)); + allTagsButton.role = "button"; + allTagsButton.innerHTML = ` `; - const allTagsContainer = await waitForDom(ALL_TAGS_CONTAINER); - allTagsContainer.appendChild(allTagsButton); - allTagsButton.addEventListener("click", () => { - document.querySelector(ALL_TAGS_BUTTON)?.click(); - const modal = document.querySelector("#all_tags_modal"); - modal.addEventListener("shown.bs.modal", () => modal.focus()); - modal.addEventListener("hidden.bs.modal", () => { - document - .querySelector(ALL_TAGS_MODAL) - ?.querySelector("button") - .click(); + const allTagsContainer = await waitForDom(ALL_TAGS_CONTAINER); + allTagsContainer.appendChild(allTagsButton); + allTagsButton.addEventListener("click", () => { + document.querySelector(ALL_TAGS_BUTTON)?.click(); + const modal = document.querySelector("#all_tags_modal"); + modal.addEventListener("shown.bs.modal", () => modal.focus()); + modal.addEventListener("hidden.bs.modal", () => { + document + .querySelector(ALL_TAGS_MODAL) + ?.querySelector("button") + .click(); + }); }); - }); + } catch (err) { + console.log("[Label Bookmarks] All tags selector unavailable, skipped", err); + } } console.log("[Label Bookmarks] Injected"); @@ -3357,9 +3536,9 @@ async function injectElements() { () => { if (window.location.href.match(/\/users\/\d+\/bookmarks\/artworks/)) delay(1000) - .then(() => waitForDom(ALL_TAGS_CONTAINER)) .then(createModalElements) - .then(injectElements); + .then(injectElements) + .catch(console.log); }, { once: true }, ); @@ -3461,7 +3640,8 @@ function setElementProperties() { const searchButton = document.querySelector("#search_modal_button"); const generatorButton = document.querySelector("#generator_modal_button"); const featureButton = document.querySelector("#feature_modal_button"); - if (lang.includes("zh")) { + if (!labelButton || !searchButton || !generatorButton || !featureButton) return; + if ((lang || "").includes("zh")) { labelButton.innerText = "添加标签"; searchButton.innerText = "搜索图片"; generatorButton.innerText = "随机图片"; @@ -3473,40 +3653,62 @@ function setElementProperties() { featureButton.innerText = "Function"; } addStyle( - `.label-button { - padding: 0 24px; + `#label_bookmarks_buttons { + position: fixed; + top: 72px; + right: 16px; + z-index: 2147483647; + gap: 8px; + align-items: center; + padding: 6px; + border: 1px solid rgba(128, 128, 128, 0.28); + border-radius: 6px; + background: ${theme ? "rgba(255, 255, 255, 0.94)" : "rgba(31, 31, 31, 0.94)"}; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16); + } + #label_bookmarks_buttons .label-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 64px; + min-height: 28px; + padding: 4px 10px; + border: 1px solid rgba(128, 128, 128, 0.32); + border-radius: 4px; background: transparent; + color: ${theme ? "rgb(31, 31, 31)" : "rgb(245, 245, 245)"}; font-size: 16px; font-weight: 700; - border-top: 4px solid rgba(0, 150, 250, 0); - border-bottom: none; - border-left: none; - border-right: none; - line-height: 24px; - background: transparent; - transition: color 0.4s ease 0s, border 0.4s ease 0s; + line-height: 20px; + cursor: pointer; + white-space: nowrap; + transition: color 0.4s ease 0s, border-color 0.4s ease 0s; } - .label-button:hover { - border-top: 4px solid rgb(0, 150, 250); + #label_bookmarks_buttons .label-button:hover { + border-color: rgb(0, 150, 250); }`, ); // append user tags options const customSelects = [...document.querySelectorAll(".select-custom-tags")]; + const publicTags = userTagDict?.public || []; + const privateTags = userTagDict?.["private"] || []; + const hasOption = (select, value) => + [...select.options].some((option) => option.value === value); customSelects.forEach((el) => { const uncat = el.querySelector("option[value='未分類']"); if (uncat) { const t = "未分類"; - const pb = userTagDict.public.find((e) => e.tag === t)?.["cnt"] || 0; - const pr = userTagDict["private"].find((e) => e.tag === t)?.["cnt"] || 0; + const pb = publicTags.find((e) => e.tag === t)?.["cnt"] || 0; + const pr = privateTags.find((e) => e.tag === t)?.["cnt"] || 0; uncat.innerText = `未分类作品 / Uncategorized Works (${pb}, ${pr})`; } userTags.forEach((tag) => { + if (hasOption(el, tag)) return; const option = document.createElement("option"); option.value = tag; - const pb = userTagDict.public.find((e) => e.tag === tag)?.["cnt"] || 0; - const pr = - userTagDict["private"].find((e) => e.tag === tag)?.["cnt"] || 0; + const pb = publicTags.find((e) => e.tag === tag)?.["cnt"] || 0; + const pr = privateTags.find((e) => e.tag === tag)?.["cnt"] || 0; option.innerText = tag + ` (${pb}, ${pr})`; el.appendChild(option); }); @@ -3574,7 +3776,7 @@ function setElementProperties() { document .querySelector("#stop_remove_tag_button") - .addEventListener("click", () => (window.runFlag = false)); + .onclick = () => (window.runFlag = false); if (DEBUG) console.log("[Label Bookmarks] Element Properties Set"); } @@ -3965,5 +4167,6 @@ function registerMenu() { waitForDom("nav") .then(initializeVariables) .then(createModalElements) - .then(injectElements); + .then(injectElements) + .catch(console.log); })();