diff --git a/index.json b/index.json index 5fba5f4..fe2c1b1 100644 --- a/index.json +++ b/index.json @@ -122,7 +122,7 @@ "id": "bennett-ui-improvements", "name": "Bennett UI Improvements", "description": "迁移自 b-nnett Bennett UI 的 renderer-only UI 增强", - "version": "1.0.6-bigpizza.1", + "version": "1.0.8-bigpizza.1", "author": "bennett; JHees", "tags": [ "codex", @@ -133,7 +133,7 @@ ], "homepage": "", "script_url": "https://raw.githubusercontent.com/BigPizzaV3/CodexPlusPlusScriptMarket/main/scripts/bennett-ui-improvements.js", - "sha256": "087a96fd4b407eb86c15e21cf7612debd7c27575638f64b840322e43675aef5d" + "sha256": "20b439e067ffa8a16580c750b63478f4220dcafdfe6e490ed15c8d76753c7ae4" } ] } diff --git a/scripts/bennett-ui-improvements.js b/scripts/bennett-ui-improvements.js index 1357bde..a00fba4 100644 --- a/scripts/bennett-ui-improvements.js +++ b/scripts/bennett-ui-improvements.js @@ -21,7 +21,7 @@ "use strict"; const INSTALL_KEY = "__bennettUiImprovementsBigPizza"; - const VERSION = "1.0.6-bigpizza.1"; + const VERSION = "1.0.8-bigpizza.1"; const previous = window[INSTALL_KEY]; if (previous && typeof previous.stop === "function") { try { @@ -1148,9 +1148,19 @@ const FEATURES = { .trim() .toLowerCase(); + const controlLabelText = (node) => + [node.getAttribute?.("aria-label"), node.getAttribute?.("title")] + .filter(Boolean) + .join(" ") + .replace(/\s+/g, " ") + .trim() + .toLowerCase(); + const isDeviceButton = (button) => { + const label = controlLabelText(button); + if (/\bmobile\b|\bphone\b|\bdevice\b|手机|移动|设备|连接/.test(label)) return true; const text = controlText(button); - return /\bmobile\b|\bphone\b|\bdevice\b|手机|移动|设备|连接/.test(text); + return text.length <= 28 && /\bmobile\b|\bphone\b|\bdevice\b|手机|移动|设备|连接/.test(text); }; const isSettingsButton = (button) => { @@ -1158,6 +1168,41 @@ const FEATURES = { return /\bsettings?\b|preferences?|设置|偏好/.test(text); }; + const isNearSidebarBottom = (sidebar, node) => { + if (!(sidebar instanceof HTMLElement) || !(node instanceof HTMLElement)) return false; + const sidebarRect = sidebar.getBoundingClientRect(); + const rect = node.getBoundingClientRect(); + const bottomBand = Math.min(Math.max(sidebarRect.height * 0.22, 120), 240); + return rect.bottom >= sidebarRect.bottom - bottomBand; + }; + + const isCompactIconControl = (control) => { + const rect = control.getBoundingClientRect(); + const text = controlText(control); + return rect.width > 0 && rect.width <= 56 && rect.height > 0 && rect.height <= 56 && text.length <= 32; + }; + + const isUsageControlNode = (node) => + node.closest?.('[data-codexpp="usage-slot"], [data-codexpp="usage-box"], [data-codexpp="usage-boxes"]'); + + const rowControls = (row) => + Array.from(row.querySelectorAll('button, a, [role="button"]')) + .filter((control) => + control instanceof HTMLElement && + isVisibleElement(control) && + !isUsageControlNode(control), + ); + + const isSlotAfterRightmostControl = (slot) => { + const row = slot.parentElement; + if (!(row instanceof HTMLElement)) return false; + const controls = rowControls(row); + if (!controls.length) return true; + const slotRect = slot.getBoundingClientRect(); + const rightmostControl = Math.max(...controls.map((control) => control.getBoundingClientRect().right)); + return slotRect.right >= rightmostControl - 2; + }; + const nearestControlRow = (sidebar, button) => { const sidebarRect = sidebar.getBoundingClientRect(); let row = button.parentElement; @@ -1169,15 +1214,17 @@ const FEATURES = { const insideSidebar = rect.left >= sidebarRect.left - 8 && rect.right <= sidebarRect.right + 8; + const nearBottom = isNearSidebarBottom(sidebar, row); const looksLikeControlLayer = insideSidebar && + nearBottom && rect.height > 0 && rect.height <= 88 && (style.display === "flex" || style.display === "grid" || buttonCount >= 2); if (looksLikeControlLayer) return row; row = row.parentElement; } - return button.parentElement instanceof HTMLElement ? button.parentElement : null; + return null; }; const createInlineSlot = (row, anchor) => { @@ -1197,18 +1244,45 @@ const FEATURES = { const findSidebarSlot = () => { const sidebar = findUsageSidebar(); if (!sidebar) return null; - const existingSlot = sidebar.querySelector('[data-codexpp="usage-slot"]'); + for (const slot of sidebar.querySelectorAll('[data-codexpp="usage-slot"]')) { + const row = slot.parentElement; + if ( + !(slot instanceof HTMLElement) || + !(row instanceof HTMLElement) || + !isNearSidebarBottom(sidebar, row) || + !isSlotAfterRightmostControl(slot) + ) { + slot.remove(); + } + } + const existingSlot = Array.from(sidebar.querySelectorAll('[data-codexpp="usage-slot"]')) + .find((slot) => + slot instanceof HTMLElement && + slot.parentElement instanceof HTMLElement && + isNearSidebarBottom(sidebar, slot.parentElement) && + isSlotAfterRightmostControl(slot), + ); if (existingSlot instanceof HTMLElement) return existingSlot; const controls = Array.from(sidebar.querySelectorAll('button, a, [role="button"]')) - .filter((button) => button instanceof HTMLElement && isVisibleElement(button)); + .filter((button) => + button instanceof HTMLElement && + isVisibleElement(button) && + isNearSidebarBottom(sidebar, button) && + !isUsageControlNode(button), + ); const deviceControls = controls.filter(isDeviceButton); const settingsControls = controls.filter(isSettingsButton); - const preferredControls = deviceControls.length ? deviceControls : settingsControls; + const compactControls = controls.filter(isCompactIconControl); + const preferredControls = deviceControls.length + ? deviceControls + : compactControls.length + ? compactControls + : settingsControls; const ordered = (preferredControls.length ? preferredControls : controls).sort((a, b) => { const ar = a.getBoundingClientRect(); const br = b.getBoundingClientRect(); - return br.bottom - ar.bottom; + return br.right - ar.right || br.bottom - ar.bottom; }); for (const button of ordered) {