mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
feat: apply our exisitng icons to engage sidebar
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { waitForEngageMenuList } from "@/seqta/utils/waitForEngageMenuList";
|
||||
import { addExtensionSettings } from "@/seqta/utils/Adders/AddExtensionSettings";
|
||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
import { loadEngageHomePage } from "@/seqta/utils/Loaders/LoadEngageHomePage";
|
||||
@@ -287,44 +288,6 @@ async function createSettingsButton(parent?: Element) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Engage mounts the sidebar inside batched React trees; EventManager-based waitForElm can miss `#menu`. Polling `waitForElm` matches the real DOM reliably. */
|
||||
async function waitForEngageMenuList(): Promise<HTMLElement | null> {
|
||||
const poll = true as const;
|
||||
const interval = 100;
|
||||
const trySelectors: { selector: string; maxIterations: number }[] = [
|
||||
{ selector: "#menu > ul > li", maxIterations: 500 },
|
||||
{ selector: "#menu ul", maxIterations: 350 },
|
||||
{ selector: "#menu", maxIterations: 350 },
|
||||
];
|
||||
|
||||
for (const { selector, maxIterations } of trySelectors) {
|
||||
try {
|
||||
await waitForElm(selector, poll, interval, maxIterations);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selector === "#menu > ul > li") {
|
||||
const ul = document.querySelector("#menu > ul") as HTMLElement | null;
|
||||
if (ul) return ul;
|
||||
} else if (selector === "#menu ul") {
|
||||
const ul = document.querySelector("#menu ul") as HTMLElement | null;
|
||||
if (ul) return ul;
|
||||
} else {
|
||||
const menu = document.getElementById("menu");
|
||||
const ul =
|
||||
(menu?.querySelector("ul") as HTMLElement | null) ??
|
||||
(menu?.firstElementChild as HTMLElement | null);
|
||||
if (ul) return ul;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"[BetterSEQTA+] Engage: could not find a menu list to inject the home button",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function injectEngageHomeButton() {
|
||||
if (document.getElementById("homebutton")) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||
import {
|
||||
ChangeMenuItemPositions,
|
||||
MenuOptionsOpen,
|
||||
} from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import stringToHTML from "@/seqta/utils/stringToHTML";
|
||||
import { waitForEngageMenuList } from "@/seqta/utils/waitForEngageMenuList";
|
||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||
import { eventManager } from "@/seqta/utils/listeners/EventManager";
|
||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
|
||||
const BETTERSEQTA_ICON_ATTR = "data-betterseqta-icon";
|
||||
|
||||
function getMenuLabel(element: HTMLElement): HTMLElement | null {
|
||||
const label = element.querySelector(":scope > label");
|
||||
return label instanceof HTMLElement ? label : null;
|
||||
}
|
||||
|
||||
function getTopLevelMenuList(menu = document.getElementById("menu")): HTMLElement | null {
|
||||
if (!menu) return null;
|
||||
return (
|
||||
(menu.querySelector(":scope > ul") as HTMLElement | null) ??
|
||||
(menu.querySelector("ul") as HTMLElement | null)
|
||||
);
|
||||
}
|
||||
|
||||
export function isTopLevelSidebarItem(node: HTMLElement): boolean {
|
||||
if (!node.classList.contains("item")) return false;
|
||||
if (node.nodeName !== "LI" && node.nodeName !== "SECTION") return false;
|
||||
|
||||
const topList = getTopLevelMenuList();
|
||||
return !!topList && node.parentElement === topList;
|
||||
}
|
||||
|
||||
function wrapMenuLabelText(label: HTMLElement) {
|
||||
const textNode = label.lastChild;
|
||||
if (
|
||||
textNode?.nodeType === 3 &&
|
||||
textNode.parentNode &&
|
||||
textNode.parentNode.nodeName !== "SPAN"
|
||||
) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = textNode.nodeValue;
|
||||
label.replaceChild(span, textNode);
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceMenuSVG(element: HTMLElement, svg: string) {
|
||||
const label = getMenuLabel(element);
|
||||
if (!label?.firstChild) return;
|
||||
|
||||
if (label.firstElementChild?.getAttribute(BETTERSEQTA_ICON_ATTR) === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
label.firstChild.remove();
|
||||
label.innerHTML = `<span>${label.innerHTML}</span>`;
|
||||
|
||||
const newSvg = stringToHTML(svg).firstChild;
|
||||
if (!(newSvg instanceof Element)) return;
|
||||
|
||||
newSvg.setAttribute(BETTERSEQTA_ICON_ATTR, "true");
|
||||
label.insertBefore(newSvg, label.firstChild);
|
||||
}
|
||||
|
||||
export function processMenuItemNode(node: HTMLElement) {
|
||||
if (!isTopLevelSidebarItem(node) || MenuOptionsOpen) return;
|
||||
|
||||
const key = node.dataset.key as keyof typeof MenuitemSVGKey | undefined;
|
||||
if (key && MenuitemSVGKey[key]) {
|
||||
replaceMenuSVG(node, MenuitemSVGKey[key]);
|
||||
} else {
|
||||
const label = getMenuLabel(node);
|
||||
if (label) wrapMenuLabelText(label);
|
||||
}
|
||||
}
|
||||
|
||||
function processTopLevelMenuItems(reorder = !isSeqtaEngageExperience()) {
|
||||
if (MenuOptionsOpen) return;
|
||||
|
||||
const topList = getTopLevelMenuList();
|
||||
if (!topList) return;
|
||||
|
||||
for (const child of topList.children) {
|
||||
if (child instanceof HTMLElement) {
|
||||
processMenuItemNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (reorder) {
|
||||
ChangeMenuItemPositions(settingsState.menuorder);
|
||||
}
|
||||
}
|
||||
|
||||
let engageMenuIconObserver: MutationObserver | null = null;
|
||||
let engageMenuIconFrame: number | null = null;
|
||||
|
||||
function scheduleEngageMenuIconPass() {
|
||||
if (engageMenuIconFrame !== null) return;
|
||||
|
||||
engageMenuIconFrame = window.requestAnimationFrame(() => {
|
||||
engageMenuIconFrame = null;
|
||||
processTopLevelMenuItems(false);
|
||||
});
|
||||
}
|
||||
|
||||
async function observeEngageMenuIcons() {
|
||||
const menuList = await waitForEngageMenuList();
|
||||
const menu = document.getElementById("menu");
|
||||
if (!menu || !menuList) return;
|
||||
|
||||
processTopLevelMenuItems(false);
|
||||
|
||||
engageMenuIconObserver?.disconnect();
|
||||
engageMenuIconObserver = new MutationObserver(() => {
|
||||
scheduleEngageMenuIconPass();
|
||||
});
|
||||
engageMenuIconObserver.observe(menu, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
const processedSymbol = Symbol("processed");
|
||||
|
||||
export async function observeMenuItemPosition() {
|
||||
if (isSeqtaEngageExperience()) {
|
||||
await observeEngageMenuIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
await waitForElm("#menu > ul > li");
|
||||
|
||||
eventManager.register(
|
||||
"menuList",
|
||||
{
|
||||
parentElement: document.querySelector("#menu")!.firstChild as Element,
|
||||
},
|
||||
(element: Element) => {
|
||||
const node = element as HTMLElement;
|
||||
|
||||
if (!isTopLevelSidebarItem(node)) return;
|
||||
if ((element as any)[processedSymbol]) return;
|
||||
|
||||
if (!MenuOptionsOpen) {
|
||||
processMenuItemNode(node);
|
||||
ChangeMenuItemPositions(settingsState.menuorder);
|
||||
(element as any)[processedSymbol] = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||
|
||||
/** Engage mounts the sidebar inside batched React trees; polling `waitForElm` matches the real DOM reliably. */
|
||||
export async function waitForEngageMenuList(): Promise<HTMLElement | null> {
|
||||
const poll = true as const;
|
||||
const interval = 100;
|
||||
const trySelectors: { selector: string; maxIterations: number }[] = [
|
||||
{ selector: "#menu > ul > li", maxIterations: 500 },
|
||||
{ selector: "#menu ul", maxIterations: 350 },
|
||||
{ selector: "#menu", maxIterations: 350 },
|
||||
];
|
||||
|
||||
for (const { selector, maxIterations } of trySelectors) {
|
||||
try {
|
||||
await waitForElm(selector, poll, interval, maxIterations);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selector === "#menu > ul > li") {
|
||||
const ul = document.querySelector("#menu > ul") as HTMLElement | null;
|
||||
if (ul) return ul;
|
||||
} else if (selector === "#menu ul") {
|
||||
const ul = document.querySelector("#menu ul") as HTMLElement | null;
|
||||
if (ul) return ul;
|
||||
} else {
|
||||
const menu = document.getElementById("menu");
|
||||
const ul =
|
||||
(menu?.querySelector("ul") as HTMLElement | null) ??
|
||||
(menu?.firstElementChild as HTMLElement | null);
|
||||
if (ul) return ul;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"[BetterSEQTA+] Engage: could not find a menu list to inject the home button",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user