Deduplicate chat controls by composer owner
Derive conversation roots from composer seats instead of every nested phase element, remove invalid and duplicate control groups, and anchor the sole surviving group directly before Git Working Dirs. Preserve Zen relocation and measurement-safe composer collapse.
This commit is contained in:
parent
3eccc0411d
commit
ed00c7b83a
3 changed files with 72 additions and 16 deletions
|
|
@ -292,14 +292,46 @@ window.__ModuleLoader__.load({
|
|||
updateChatControls();
|
||||
}
|
||||
|
||||
function placeChatControls() {
|
||||
document.querySelectorAll("[data-phase]").forEach((conversation) => {
|
||||
const controls = conversation.querySelector(".dsh-chat-controls");
|
||||
function conversationRoots() {
|
||||
const roots = [];
|
||||
document.querySelectorAll("[data-composer-seat]").forEach((seat) => {
|
||||
const conversation = seat.closest("[data-phase]");
|
||||
if (conversation && !roots.includes(conversation)) roots.push(conversation);
|
||||
});
|
||||
return roots;
|
||||
}
|
||||
|
||||
function normalizeChatControls() {
|
||||
const roots = conversationRoots();
|
||||
const validRoots = new Set(roots);
|
||||
const owners = new Map();
|
||||
document.querySelectorAll(".dsh-chat-controls").forEach((controls) => {
|
||||
const owner = controls.closest("[data-phase]");
|
||||
if (!validRoots.has(owner) || owners.has(owner)) {
|
||||
controls.remove();
|
||||
return;
|
||||
}
|
||||
owners.set(owner, controls);
|
||||
});
|
||||
roots.forEach((conversation) => {
|
||||
if (owners.has(conversation)) return;
|
||||
const controls = makeChatControls();
|
||||
conversation.appendChild(controls);
|
||||
owners.set(conversation, controls);
|
||||
});
|
||||
return roots;
|
||||
}
|
||||
|
||||
function placeChatControls(roots = conversationRoots()) {
|
||||
roots.forEach((conversation) => {
|
||||
const controls = Array.from(conversation.querySelectorAll(".dsh-chat-controls")).find((candidate) => candidate.closest("[data-phase]") === conversation);
|
||||
if (!controls) return;
|
||||
const git = conversation.querySelector(".dsh-git-working-dirs");
|
||||
const utilities = conversation.querySelector('header [class*="_headerUtilities"]');
|
||||
if (!zenActive && utilities?.parentElement) {
|
||||
const anchor = git ?? utilities;
|
||||
if (!zenActive && anchor?.parentElement) {
|
||||
controls.removeAttribute("data-floating");
|
||||
if (controls.parentElement !== utilities.parentElement) utilities.parentElement.insertBefore(controls, utilities);
|
||||
if (controls.parentElement !== anchor.parentElement) anchor.parentElement.insertBefore(controls, anchor);
|
||||
return;
|
||||
}
|
||||
controls.setAttribute("data-floating", "1");
|
||||
|
|
@ -311,7 +343,7 @@ window.__ModuleLoader__.load({
|
|||
zenActive = active;
|
||||
if (active) document.documentElement.setAttribute("data-dsh-zen", "1");
|
||||
else document.documentElement.removeAttribute("data-dsh-zen");
|
||||
placeChatControls();
|
||||
placeChatControls(normalizeChatControls());
|
||||
updateChatControls();
|
||||
}
|
||||
|
||||
|
|
@ -336,10 +368,8 @@ window.__ModuleLoader__.load({
|
|||
}
|
||||
|
||||
function enhanceChatControls() {
|
||||
document.querySelectorAll("[data-phase]").forEach((conversation) => {
|
||||
if (!conversation.querySelector(".dsh-chat-controls")) conversation.appendChild(makeChatControls());
|
||||
});
|
||||
placeChatControls();
|
||||
const roots = normalizeChatControls();
|
||||
placeChatControls(roots);
|
||||
updateChatControls();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "dsh-ui-tweaks",
|
||||
"description": "dsh web plugin: cohesive sidebar, header, composer-collapse, and Zen-mode controls for the DSH Web chat interface. Client-only.",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,20 @@ class Element {
|
|||
return null
|
||||
}
|
||||
|
||||
querySelectorAll(selector) {
|
||||
if (selector.startsWith('.')) return this.descendants().filter((child) => child.hasClass(selector.slice(1)))
|
||||
return []
|
||||
}
|
||||
|
||||
closest(selector) {
|
||||
let current = this
|
||||
while (current) {
|
||||
if (selector === '[data-phase]' && current.attributes.has('data-phase')) return current
|
||||
current = current.parentElement
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
hasClass(name) {
|
||||
return this.className.split(/\s+/).includes(name)
|
||||
}
|
||||
|
|
@ -84,13 +98,22 @@ const conversation = new Element('div')
|
|||
conversation.setAttribute('data-phase', 'active')
|
||||
const header = new Element('header')
|
||||
const titleRow = new Element('div')
|
||||
const headerActions = new Element('div')
|
||||
const git = new Element('div')
|
||||
git.className = 'dsh-git-working-dirs'
|
||||
const utilities = new Element('div')
|
||||
utilities.className = 'fixture_headerUtilities'
|
||||
titleRow.appendChild(utilities)
|
||||
headerActions.appendChild(git)
|
||||
titleRow.append(headerActions, utilities)
|
||||
header.appendChild(titleRow)
|
||||
const seat = new Element('div')
|
||||
seat.setAttribute('data-composer-seat', '')
|
||||
conversation.append(header, seat)
|
||||
const nestedPhase = new Element('div')
|
||||
nestedPhase.setAttribute('data-phase', 'streaming')
|
||||
const strayControls = new Element('div')
|
||||
strayControls.className = 'dsh-chat-controls'
|
||||
nestedPhase.appendChild(strayControls)
|
||||
conversation.append(header, nestedPhase, seat)
|
||||
body.appendChild(conversation)
|
||||
const documentListeners = new Map()
|
||||
|
||||
|
|
@ -144,8 +167,10 @@ plugin.apply({ effect: (start) => { cleanup = start() } })
|
|||
|
||||
const controls = conversation.querySelector('.dsh-chat-controls')
|
||||
assert(controls)
|
||||
assert.equal(controls.parentElement, titleRow)
|
||||
assert(controls.parentElement.children.indexOf(controls) < controls.parentElement.children.indexOf(utilities))
|
||||
assert.equal(document.querySelectorAll('.dsh-chat-controls').length, 1)
|
||||
assert.equal(strayControls.parentElement, null)
|
||||
assert.equal(controls.parentElement, headerActions)
|
||||
assert(controls.parentElement.children.indexOf(controls) < controls.parentElement.children.indexOf(git))
|
||||
const collapse = controls.querySelector('.dsh-composer-collapse-toggle')
|
||||
const zen = controls.querySelector('.dsh-zen-toggle')
|
||||
assert.equal(collapse.textContent, '⌄ Chat')
|
||||
|
|
@ -166,7 +191,8 @@ assert.equal(controls.getAttribute('data-floating'), '1')
|
|||
documentListeners.get('keydown')?.({ key: 'Escape' })
|
||||
assert.equal(documentElement.getAttribute('data-dsh-zen'), null)
|
||||
assert.equal(zen.textContent, 'Zen')
|
||||
assert.equal(controls.parentElement, titleRow)
|
||||
assert.equal(controls.parentElement, headerActions)
|
||||
assert(controls.parentElement.children.indexOf(controls) < controls.parentElement.children.indexOf(git))
|
||||
assert.equal(controls.getAttribute('data-floating'), null)
|
||||
|
||||
cleanup()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue