mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-30 00:22:10 -04:00
fix(ui): route tasks.js + skills.js dropdowns through topPortalZ() (#4768)
Fixes #4767. #4724 routed 16 body-portaled dropdowns through the shared topPortalZ() helper so they always render just above the currently-raised tool modal, but two were missed and still used a hardcoded z-index, so they hit the same #4720 bug once a modal's bring-to-front counter climbed past the literal: - tasks.js _showTaskDropdown(): inline z-index:100000 on .task-dropdown - skills.js kebab menu (.skill-kebab-menu): z-index:100002 in style.css Both now set zIndex from topPortalZ() after they are appended to the body, matching the other migrated sites. The dead CSS z-index on .skill-kebab-menu is removed (the inline value always wins). test_portal_dropdown_z_js.py gains a source guard asserting both files use topPortalZ() and that no hardcoded 100000/100002 portal literal survives in either file or style.css.
This commit is contained in:
committed by
GitHub
parent
5d23495eb2
commit
de12d4734a
@@ -8,6 +8,7 @@
|
|||||||
import uiModule from './ui.js';
|
import uiModule from './ui.js';
|
||||||
import * as spinnerModule from './spinner.js';
|
import * as spinnerModule from './spinner.js';
|
||||||
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||||
|
import { topPortalZ } from './toolWindowZOrder.js';
|
||||||
|
|
||||||
const API = window.location.origin;
|
const API = window.location.origin;
|
||||||
let skills = [];
|
let skills = [];
|
||||||
@@ -437,6 +438,10 @@ function _openSkillMenu(btn, card, sk, name, isPublished) {
|
|||||||
menu.appendChild(cancelItem);
|
menu.appendChild(cancelItem);
|
||||||
|
|
||||||
document.body.appendChild(menu);
|
document.body.appendChild(menu);
|
||||||
|
// Override the CSS z-index (100002) with a value derived from the live
|
||||||
|
// tool-window stack so the kebab menu stays above its modal even after the
|
||||||
|
// bring-to-front counter climbs past the static value (#4720).
|
||||||
|
menu.style.zIndex = String(topPortalZ());
|
||||||
const r = btn.getBoundingClientRect();
|
const r = btn.getBoundingClientRect();
|
||||||
menu.style.top = (r.bottom + 4) + 'px';
|
menu.style.top = (r.bottom + 4) + 'px';
|
||||||
menu.style.right = Math.max(6, window.innerWidth - r.right) + 'px';
|
menu.style.right = Math.max(6, window.innerWidth - r.right) + 'px';
|
||||||
|
|||||||
+6
-1
@@ -6,6 +6,7 @@ import uiModule from './ui.js';
|
|||||||
import markdownModule from './markdown.js';
|
import markdownModule from './markdown.js';
|
||||||
import * as spinnerModule from './spinner.js';
|
import * as spinnerModule from './spinner.js';
|
||||||
import { makeWindowDraggable } from './windowDrag.js';
|
import { makeWindowDraggable } from './windowDrag.js';
|
||||||
|
import { topPortalZ } from './toolWindowZOrder.js';
|
||||||
import { sortModelIds } from './modelSort.js';
|
import { sortModelIds } from './modelSort.js';
|
||||||
import { ordinalSuffix } from './util/ordinal.js';
|
import { ordinalSuffix } from './util/ordinal.js';
|
||||||
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||||
@@ -903,7 +904,7 @@ function _showTaskDropdown(anchor, items) {
|
|||||||
document.querySelectorAll('.task-dropdown').forEach(dismissOrRemove);
|
document.querySelectorAll('.task-dropdown').forEach(dismissOrRemove);
|
||||||
const dd = document.createElement('div');
|
const dd = document.createElement('div');
|
||||||
dd.className = 'task-dropdown';
|
dd.className = 'task-dropdown';
|
||||||
dd.style.cssText = 'position:fixed;z-index:100000;background:var(--panel);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.3);padding:4px;min-width:120px;';
|
dd.style.cssText = 'position:fixed;background:var(--panel);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.3);padding:4px;min-width:120px;';
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.style.cssText = 'display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 10px;border:none;background:none;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;border-radius:4px;transition:background 0.1s;';
|
btn.style.cssText = 'display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:6px 10px;border:none;background:none;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;border-radius:4px;transition:background 0.1s;';
|
||||||
@@ -919,6 +920,10 @@ function _showTaskDropdown(anchor, items) {
|
|||||||
dd.appendChild(btn);
|
dd.appendChild(btn);
|
||||||
});
|
});
|
||||||
document.body.appendChild(dd);
|
document.body.appendChild(dd);
|
||||||
|
// Sit above the currently-raised tool modal at any stack depth (#4720): the
|
||||||
|
// modal bring-to-front counter climbs unbounded, so a hardcoded z eventually
|
||||||
|
// loses. topPortalZ() derives the value from the live tool-window stack.
|
||||||
|
dd.style.zIndex = String(topPortalZ());
|
||||||
const rect = anchor.getBoundingClientRect();
|
const rect = anchor.getBoundingClientRect();
|
||||||
let top = rect.bottom + 4;
|
let top = rect.bottom + 4;
|
||||||
let left = rect.right - dd.offsetWidth;
|
let left = rect.right - dd.offsetWidth;
|
||||||
|
|||||||
+2
-1
@@ -16960,7 +16960,8 @@ body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.mod
|
|||||||
/* Kebab dropdown */
|
/* Kebab dropdown */
|
||||||
.skill-kebab-menu {
|
.skill-kebab-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100002;
|
/* z-index is set inline via topPortalZ() at open time (#4720); a static
|
||||||
|
value here loses once the modal bring-to-front counter climbs past it. */
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
background: var(--panel, var(--bg));
|
background: var(--panel, var(--bg));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ and the dock-chip floor, without importing the browser-heavy UI modules.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
@@ -87,3 +88,22 @@ def test_portal_z_uses_chip_floor_when_the_open_modal_sits_below_it():
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert values == {"z": 10031}
|
assert values == {"z": 10031}
|
||||||
|
|
||||||
|
|
||||||
|
# tasks.js and skills.js were not in #4724's batch; #4767 routes their portaled
|
||||||
|
# dropdowns through the same helper. Pin that they use topPortalZ() and carry no
|
||||||
|
# hardcoded portal z-index, so they cannot regress to the #4720 bug.
|
||||||
|
@pytest.mark.parametrize("rel", ["static/js/tasks.js", "static/js/skills.js"])
|
||||||
|
def test_late_routed_dropdowns_use_top_portal_z(rel):
|
||||||
|
src = (ROOT / rel).read_text()
|
||||||
|
assert "topPortalZ" in src, f"{rel} must import/use topPortalZ()"
|
||||||
|
assert "topPortalZ()" in src, f"{rel} must call topPortalZ() for its dropdown z"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("rel", ["static/js/tasks.js", "static/js/skills.js", "static/style.css"])
|
||||||
|
def test_no_hardcoded_portal_z_literals_remain(rel):
|
||||||
|
src = (ROOT / rel).read_text()
|
||||||
|
# Match the exact 100000/100002 these dropdowns used; the trailing-digit
|
||||||
|
# guard avoids false-matching an unrelated 1000000 elsewhere.
|
||||||
|
hits = re.findall(r"z-index:\s*10000[02](?!\d)", src)
|
||||||
|
assert not hits, f"{rel} still has hardcoded portal z: {hits}"
|
||||||
|
|||||||
Reference in New Issue
Block a user