From 7ed2cc1172f5091a6a8bf585982c51ef907a9da1 Mon Sep 17 00:00:00 2001 From: Michael Quevillon Date: Fri, 29 Nov 2024 16:53:16 -0600 Subject: [PATCH] Fix Conversation Controls and Search Function (#2198) * Fix 'Search in Conversation' for new design * Fix conversation controls (archive, delete) The search in conversation functionality requires the right sidebar to be open. The method to select the search button is simplified to just the last button. --- css/browser.css | 7 +-- source/browser.ts | 97 ++++++++++++++++++++----------------- source/browser/selectors.ts | 6 +-- source/menu.ts | 16 +----- 4 files changed, 58 insertions(+), 68 deletions(-) diff --git a/css/browser.css b/css/browser.css index ccca3215c..a2f45c098 100644 --- a/css/browser.css +++ b/css/browser.css @@ -102,7 +102,7 @@ body::-webkit-scrollbar { } /* A utility class for temporarily hiding all dropdown menus */ -html.hide-dropdowns [role='menu'].l9j0dhe7.swg4t2nn { +html.hide-dropdowns [role='menu'].x1n2onr6.xi5betq { visibility: hidden !important; } @@ -111,11 +111,6 @@ html.hide-preferences-window div[class='x9f619 x1n2onr6 x1ja2u2z'] > div:nth-of- display: none; } -/* A utility class for temporarily hiding right sidebar */ -html.hide-r-sidebar .rq0escxv.l9j0dhe7.du4w35lb.j83agx80.g5gj957u.rj1gh0hx.buofh1pr.hpfvmrgz.i1fnvgqd.gs1a9yip.owycx6da.btwxx1t3.jb3vyjys.nwf6jgls > div:nth-child(2) { - display: none; -} - /* -- Private mode -- */ /* Preferences button: profile picture */ html.private-mode [role='navigation'] .qi72231t.o9w3sbdw.nu7423ey.tav9wjvu.flwp5yud.tghlliq5.gkg15gwv.s9ok87oh.s9ljgwtm.lxqftegz.bf1zulr9.frfouenu.bonavkto.djs4p424.r7bn319e.bdao358l.fsf7x5fv.tgm57n0e.jez8cy9q.s5oniofx.m8h3af8h.l7ghb35v.kjdc1dyq.kmwttqpk.dnr7xe2t.aeinzg81.srn514ro.oxkhqvkx.rl78xhln.nch0832m.om3e55n1.cr00lzj9.rn8ck1ys.s3jn8y49.g4tp4svg.o9erhkwx.dzqi5evh.hupbnkgi.hvb2xoa8.fxk3tzhb.jl2a5g8c.f14ij5to.l3ldwz01.icdlwmnq { diff --git a/source/browser.ts b/source/browser.ts index 1be32e470..99535af2a 100644 --- a/source/browser.ts +++ b/source/browser.ts @@ -21,7 +21,8 @@ async function withMenu( menuButtonElement.click(); // Wait for the menu to close before removing the 'hide-dropdowns' class - const menuLayer = document.querySelector('.j83agx80.cbu4d94t.l9j0dhe7.jgljxmt5.be9z9djy > div:nth-child(2) > div'); + await elementReady('.x78zum5.xdt5ytf.x1n2onr6.xat3117.xxzkxad > div:nth-child(2) > div', {stopOnDomReady: false}); + const menuLayer = document.querySelector('.x78zum5.xdt5ytf.x1n2onr6.xat3117.xxzkxad > div:nth-child(2) > div'); if (menuLayer) { const observer = new MutationObserver(() => { @@ -149,45 +150,18 @@ ipc.answerMain('find', () => { }); async function openSearchInConversation() { - const mainView = document.querySelector('.rq0escxv.l9j0dhe7.du4w35lb.j83agx80.rj1gh0hx.buofh1pr.g5gj957u.hpfvmrgz.i1fnvgqd.gs1a9yip.owycx6da.btwxx1t3.jb3vyjys.gitj76qy')!; - const rightSidebarIsClosed = Boolean(mainView.querySelector('div:only-child')); + const mainView = document.querySelector('.x9f619.x1ja2u2z.x78zum5.x1n2onr6.x1r8uery.x1iyjqo2.xs83m0k.xeuugli.x1qughib.x1qjc9v5.xozqiw3.x1q0g3np.xexx8yu.x85a59c')!; + const rightSidebarIsClosed = Boolean(mainView.querySelector(':scope > div:only-child')); if (rightSidebarIsClosed) { - document.documentElement.classList.add('hide-r-sidebar'); - document.querySelector('.j9ispegn.pmk7jnqg.k4urcfbm.datstx6m.b5wmifdl.kr520xx4.mdpwds66.b2cqd1jy.n13yt9zj.eh67sqbx')?.click(); + document.querySelector(selectors.rightSidebarMenu)?.click(); } - await elementReady(selectors.rightSidebarSegments, {stopOnDomReady: false}); - const segments = document.querySelectorAll(selectors.rightSidebarSegments).length; - // If there are three segmetns in right sidebar (two users chat) then button index is 4 - // If there are not three segments (usually four, it's a group chat) then button index is 6 - const buttonIndex = segments === 3 ? 4 : 6; - await elementReady(selectors.rightSidebarButtons, {stopOnDomReady: false}); const buttonList = document.querySelectorAll(selectors.rightSidebarButtons); - if (buttonList.length > buttonIndex) { - buttonList[buttonIndex].click(); - } - - // If right sidebar was closed when shortcut was clicked, then close it back. - if (rightSidebarIsClosed) { - document.querySelector('.j9ispegn.pmk7jnqg.k4urcfbm.datstx6m.b5wmifdl.kr520xx4.mdpwds66.b2cqd1jy.n13yt9zj.eh67sqbx')?.click(); - - // Observe sidebar so when it's hidden, remove the utility class. This prevents split - // display of sidebar. - const sidebarObserver = new MutationObserver(records => { - const removedRecords = records.filter(({removedNodes}) => removedNodes.length > 0 && (removedNodes[0] as HTMLElement).tagName === 'DIV'); - - // In case there is a div removed, hide utility class and stop observing - if (removedRecords.length > 0) { - document.documentElement.classList.remove('hide-r-sidebar'); - sidebarObserver.disconnect(); - } - }); - - sidebarObserver.observe(mainView, {childList: true, subtree: true}); - } + // Search in conversation is the last button + buttonList[buttonList.length - 1].click(); } ipc.answerMain('search', () => { @@ -223,14 +197,21 @@ ipc.answerMain('mute-conversation', async () => { }); ipc.answerMain('delete-conversation', async () => { - await deleteSelectedConversation(); + const index = selectedConversationIndex(); + + if (index !== -1) { + await deleteSelectedConversation(); + + const key = index + 1; + await jumpToConversation(key); + } }); -ipc.answerMain('hide-conversation', async () => { +ipc.answerMain('archive-conversation', async () => { const index = selectedConversationIndex(); if (index !== -1) { - await hideSelectedConversation(); + await archiveSelectedConversation(); const key = index + 1; await jumpToConversation(key); @@ -588,7 +569,7 @@ function selectedConversationIndex(offset = 0): number { return -1; } - const newSelected = selected.parentNode!.parentNode!.parentNode! as HTMLElement; + const newSelected = selected.closest(`${selectors.conversationList} > div`)!; const list = [...newSelected.parentNode!.children]; const index = list.indexOf(newSelected) + offset; @@ -605,7 +586,7 @@ async function setZoom(zoomFactor: number): Promise { async function withConversationMenu(callback: () => void): Promise { // eslint-disable-next-line @typescript-eslint/ban-types let menuButton: HTMLElement | null = null; - const conversation = document.querySelector(`${selectors.selectedConversation}`)?.parentElement?.parentElement?.parentElement?.parentElement; + const conversation = document.querySelector(selectors.selectedConversation)!.closest(`${selectors.conversationList} > div`); menuButton = conversation?.querySelector('[aria-label=Menu][role=button]') ?? null; @@ -621,27 +602,53 @@ async function openMuteModal(): Promise { } /* -This function assumes: +These functions assume: - There is a selected conversation. - That the conversation already has its conversation menu open. In other words, you should only use this function within a callback that is provided to `withConversationMenu()`, because `withConversationMenu()` makes sure to have the conversation menu open before executing the callback and closes the conversation menu afterwards. */ function isSelectedConversationGroup(): boolean { - return Boolean(document.querySelector(`${selectors.conversationMenuSelectorNewDesign} [role=menuitem]:nth-child(4)`)); + // Individual conversations include an entry for "View Profile", which is type `a` + return !document.querySelector(`${selectors.conversationMenuSelectorNewDesign} a[role=menuitem]`); } -async function hideSelectedConversation(): Promise { +function isSelectedConversationMetaAI(): boolean { + // Meta AI menu only has 1 separator of type `hr` + return !document.querySelector(`${selectors.conversationMenuSelectorNewDesign} hr:nth-of-type(2)`); +} + +async function archiveSelectedConversation(): Promise { await withConversationMenu(() => { - const [isGroup, isNotGroup] = [5, 6]; - selectMenuItem(isSelectedConversationGroup() ? isGroup : isNotGroup); + const [isGroup, isNotGroup, isMetaAI] = [-4, -3, -2]; + + let archiveMenuIndex; + if (isSelectedConversationMetaAI()) { + archiveMenuIndex = isMetaAI; + } else if (isSelectedConversationGroup()) { + archiveMenuIndex = isGroup; + } else { + archiveMenuIndex = isNotGroup; + } + + selectMenuItem(archiveMenuIndex); }); } async function deleteSelectedConversation(): Promise { await withConversationMenu(() => { - const [isGroup, isNotGroup] = [6, 7]; - selectMenuItem(isSelectedConversationGroup() ? isGroup : isNotGroup); + const [isGroup, isNotGroup, isMetaAI] = [-3, -2, -1]; + + let deleteMenuIndex; + if (isSelectedConversationMetaAI()) { + deleteMenuIndex = isMetaAI; + } else if (isSelectedConversationGroup()) { + deleteMenuIndex = isGroup; + } else { + deleteMenuIndex = isNotGroup; + } + + selectMenuItem(deleteMenuIndex); }); } diff --git a/source/browser/selectors.ts b/source/browser/selectors.ts index 5d8d0d1ca..4e99164fd 100644 --- a/source/browser/selectors.ts +++ b/source/browser/selectors.ts @@ -8,15 +8,15 @@ export default { conversationSidebarTextSelector: '[class="x1lliihq x193iq5w x6ikm8r x10wlt62 xlyipyv xuxw1ft"]', // Generic selector for the text contents of all conversations conversationSidebarSelector: '[class="x9f619 x1n2onr6 x1ja2u2z x78zum5 x2lah0s x1qughib x6s0dn4 xozqiw3 x1q0g3np"]', // Selector for the top level element of a single conversation (children contain text content of the conversation and conversation image) notificationCheckbox: '._374b:nth-of-type(4) ._4ng2 input', - rightSidebarButtons: '.rq0escxv.l9j0dhe7.du4w35lb.j83agx80.cbu4d94t.g5gj957u.f4tghd1a.ifue306u.kuivcneq.t63ysoy8 [role=button]', - rightSidebarSegments: '.oajrlxb2.gs1a9yip.g5ia77u1.mtkw9kbi.tlpljxtp.qensuy8j.ppp5ayq2.goun2846.ccm00jje.s44p3ltw.mk2mc5f4.rt8b4zig.n8ej3o3l.agehan2d.sk4xxmp2.rq0escxv.nhd2j8a9.mg4g778l.pfnyh3mw.p7hjln8o.kvgmc6g5.cxmmr5t8.oygrvhab.hcukyx3x.tgvbjcpo.hpfvmrgz.jb3vyjys.rz4wbd8a.qt6c0cv9.a8nywdso.l9j0dhe7.i1ao9s8h.esuyzwwr.f1sip0of.du4w35lb.btwxx1t3.abiwlrkh.p8dawk7l.j83agx80.lzcic4wl.beltcj47.p86d2i9g.aot14ch1.kzx2olss', + rightSidebarMenu: '.x6s0dn4.x3nfvp2.x1fgtraw.xl56j7k.x1n2onr6.xgd8bvy', + rightSidebarButtons: '.x9f619.x1ja2u2z.x78zum5.x2lah0s.x1n2onr6.xl56j7k.x1qjc9v5.xozqiw3.x1q0g3np.xn6708d.x1ye3gou.x1cnzs8.xdj266r.x11i5rnm.xat24cr.x1mh8g0r > div [role=button]', muteIconNewDesign: 'path[d="M29.676 7.746c.353-.352.44-.92.15-1.324a1 1 0 00-1.524-.129L6.293 28.29a1 1 0 00.129 1.523c.404.29.972.204 1.324-.148l3.082-3.08A2.002 2.002 0 0112.242 26h15.244c.848 0 1.57-.695 1.527-1.541-.084-1.643-1.87-1.145-2.2-3.515l-1.073-8.157-.002-.01a1.976 1.976 0 01.562-1.656l3.376-3.375zm-9.165 20.252H15.51c-.313 0-.565.275-.506.575.274 1.38 1.516 2.422 3.007 2.422 1.49 0 2.731-1.042 3.005-2.422.06-.3-.193-.575-.505-.575zm-10.064-6.719L22.713 9.02a.997.997 0 00-.124-1.51 7.792 7.792 0 00-12.308 5.279l-1.04 7.897c-.089.672.726 1.074 1.206.594z"]', // ! Very fragile selector (most likely cause of hidden dialog issue) closePreferencesButton: 'div[role=dialog] > div > div > div:nth-child(2) > [role=button]', userMenu: '.qi72231t.o9w3sbdw.nu7423ey.tav9wjvu.flwp5yud.tghlliq5.gkg15gwv.s9ok87oh.s9ljgwtm.lxqftegz.bf1zulr9.frfouenu.bonavkto.djs4p424.r7bn319e.bdao358l.fsf7x5fv.tgm57n0e.jez8cy9q.s5oniofx.m8h3af8h.l7ghb35v.kjdc1dyq.kmwttqpk.dnr7xe2t.aeinzg81.srn514ro.oxkhqvkx.rl78xhln.nch0832m.om3e55n1.cr00lzj9.rn8ck1ys.s3jn8y49.g4tp4svg.o9erhkwx.dzqi5evh.hupbnkgi.hvb2xoa8.fxk3tzhb.jl2a5g8c.f14ij5to.l3ldwz01.icdlwmnq > .aglvbi8b.om3e55n1.i8zpp7h3.g4tp4svg', userMenuNewSidebar: '[role=navigation] > div > div:nth-child(2) > div > div > div:nth-child(1) [role=button]', viewsMenu: '.x9f619.x1n2onr6.x1ja2u2z.x78zum5.xdt5ytf.x2lah0s.x193iq5w.xdj266r', - selectedConversation: '[role=navigation] [role=grid] [role=row] [role=gridcell] [role=link][aria-current]', + selectedConversation: '[role=navigation] [role=grid] [role=row] [role=gridcell] [role=link][aria-current=page]', // ! Very fragile selector (most likely cause of hidden dialog issue) preferencesSelector: 'div[role=dialog][class="x1n2onr6 x1ja2u2z x1afcbsf x78zum5 xdt5ytf x1a2a7pz x6ikm8r x10wlt62 x71s49j x1jx94hy x1g2kw80 xxadwq3 x16n5opg x3hh19s xl7ujzl x1kl8bxo xhkep3z xb3b7hn xwhkkir xeb55yp x17omtbh"]', // TODO: Fix this selector for new design diff --git a/source/menu.ts b/source/menu.ts index 6ea00b147..ebf3e81b0 100644 --- a/source/menu.ts +++ b/source/menu.ts @@ -578,45 +578,35 @@ Press Command/Ctrl+R in Caprine to see your changes. const conversationSubmenu: MenuItemConstructorOptions[] = [ { - /* TODO: Fix conversation controls */ label: 'Mute Conversation', - visible: is.development, accelerator: 'CommandOrControl+Shift+M', click() { sendAction('mute-conversation'); }, }, { - /* TODO: Fix conversation controls */ - label: 'Hide Conversation', - visible: is.development, + label: 'Archive Conversation', accelerator: 'CommandOrControl+Shift+H', click() { - sendAction('hide-conversation'); + sendAction('archive-conversation'); }, }, { - /* TODO: Fix conversation controls */ label: 'Delete Conversation', - visible: is.development, accelerator: 'CommandOrControl+Shift+D', click() { sendAction('delete-conversation'); }, }, { - /* TODO: Fix conversation controls */ label: 'Select Next Conversation', - visible: is.development, accelerator: 'Control+Tab', click() { sendAction('next-conversation'); }, }, { - /* TODO: Fix conversation controls */ label: 'Select Previous Conversation', - visible: is.development, accelerator: 'Control+Shift+Tab', click() { sendAction('previous-conversation'); @@ -630,9 +620,7 @@ Press Command/Ctrl+R in Caprine to see your changes. }, }, { - /* TODO: Fix conversation controls */ label: 'Search in Conversation', - visible: is.development, accelerator: 'CommandOrControl+F', click() { sendAction('search');