From c44621791676ee95477c5be7ad1b230552088075 Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Sun, 23 Feb 2025 21:54:59 +1100 Subject: [PATCH] feat: notifications open the message #10 --- src/SEQTA.ts | 29 +-- src/pageState.js | 254 ++++++++++++-------- src/seqta/utils/ReactFiber.ts | 87 ++++--- src/seqta/utils/listeners/ClickListeners.ts | 46 ++++ 4 files changed, 252 insertions(+), 164 deletions(-) create mode 100644 src/seqta/utils/listeners/ClickListeners.ts diff --git a/src/SEQTA.ts b/src/SEQTA.ts index b5e5df46..09ae6aff 100644 --- a/src/SEQTA.ts +++ b/src/SEQTA.ts @@ -13,11 +13,12 @@ import { StorageChangeHandler } from '@/seqta/utils/listeners/StorageChanges' import { eventManager } from '@/seqta/utils/listeners/EventManager' // UI and theme management -import loading, { AppendLoadingSymbol } from '@/seqta/ui/Loading' -import { enableCurrentTheme } from '@/seqta/ui/themes/enableCurrent' -import { updateAllColors } from '@/seqta/ui/colors/Manager' -import { SettingsResizer } from '@/seqta/ui/SettingsResizer' +import RegisterClickListeners from './seqta/utils/listeners/ClickListeners' import { AddBetterSEQTAElements } from '@/seqta/ui/AddBetterSEQTAElements' +import { enableCurrentTheme } from '@/seqta/ui/themes/enableCurrent' +import loading, { AppendLoadingSymbol } from '@/seqta/ui/Loading' +import { SettingsResizer } from '@/seqta/ui/SettingsResizer' +import { updateAllColors } from '@/seqta/ui/colors/Manager' // JSON content import MenuitemSVGKey from '@/seqta/content/MenuItemSVGKey.json' @@ -38,7 +39,6 @@ import documentLoadCSS from '@/css/documentload.scss?inline' import renderSvelte from '@/interface/main' import Settings from '@/interface/pages/settings.svelte' import { settingsPopup } from './interface/hooks/SettingsPopup' -import ReactFiber from './seqta/utils/ReactFiber' let SettingsClicked = false export let MenuOptionsOpen = false @@ -74,6 +74,7 @@ async function init() { await initializeSettingsState(); if (settingsState.onoff) { + browser.runtime.sendMessage({ type: 'injectMainScript' }) enableCurrentTheme() if (typeof settingsState.assessmentsAverage == 'undefined') { @@ -91,22 +92,6 @@ async function init() { } console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.') main() - - /* waitForElm('.Viewer__Viewer___32BH-', true).then(async () => { - console.log('Element exists') - - await browser.runtime.sendMessage({ type: 'injectMainScript' }) - - const nice = ReactFiber.find(".Viewer__Viewer___32BH-", { debug: true }) - - - - console.log(nice.getState()) - nice.setState({ selected: new Set([999431]) }) - - - //console.log(nice) - }) */ } catch (error: any) { console.error(error) } @@ -804,6 +789,8 @@ async function LoadPageElements(): Promise { }, handleAssessments); } + RegisterClickListeners(); + await handleSublink(sublink); } diff --git a/src/pageState.js b/src/pageState.js index beb4e1f2..1c9af3a6 100644 --- a/src/pageState.js +++ b/src/pageState.js @@ -1,120 +1,163 @@ -// pageState.ts class ReactFiber { - constructor(selector, options = {}) { - this.selector = selector; - this.debug = options.debug || false; - this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements - this.fibers = this.nodes.map(node => this.getFiberNode(node)); - this.components = this.fibers.map(fiber => this.getOwnerComponent(fiber)); + constructor(selector, options = {}) { + this.selector = selector; + this.debug = options.debug || false; + this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements + this.fibers = this.nodes.map(node => this.getFiberNode(node)); + this.components = this.fibers.map(fiber => this.getOwnerComponent(fiber)); - if (this.debug) { - console.log("📌 Selected Nodes:", this.nodes); - console.log("🔍 Found Fibers:", this.fibers); - console.log("🛠 Found Components:", this.components); - } + if (this.debug) { + console.log("Selected Nodes:", this.nodes); + console.log("🔍 Found Fibers:", this.fibers); + console.log("🛠 Found Components:", this.components); } + } - static find(selector, options = {}) { - return new ReactFiber(selector, options); - } + static find(selector, options = {}) { + return new ReactFiber(selector, options); + } - getFiberNode(node) { - if (!node) return null; - const fiberKey = Object.getOwnPropertyNames(node).find(name => - name.startsWith('__reactFiber') || name.startsWith('__reactInternalInstance') - ); - return fiberKey ? node[fiberKey] : null; - } + getFiberNode(node) { + if (!node) return null; + const fiberKey = Object.getOwnPropertyNames(node).find(name => + name.startsWith('__reactFiber') || name.startsWith('__reactInternalInstance') + ); + return fiberKey ? node[fiberKey] : null; + } - getOwnerComponent(fiberNode) { - let current = fiberNode; - while (current) { - if (current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate)) { - return current.stateNode; - } - current = current.return; - } - return null; - } - - getState(key) { - if (!this.components.length) return null; - const state = this.components[0]?.state || null; - - if (key === undefined) { - return state; // Return entire state - } else if (typeof key === 'string') { - return state?.[key]; // Return single key - } else if (Array.isArray(key)) { - // Return object with only specified keys - const filteredState = {}; - for (const k of key) { - if (state && Object.hasOwn(state, k)) { // Use Object.hasOwn for safety - filteredState[k] = state[k]; - } - } - return filteredState; + getOwnerComponent(fiberNode) { + let current = fiberNode; + while (current) { + if (current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate)) { + return current.stateNode; } - return null; // Invalid key type + current = current.return; } + return null; + } - setState(update) { - this.components.forEach(component => { - if (component?.setState) { - if (typeof update === 'function') { - // Functional update - component.setState(prevState => { - const newState = update(prevState); - if (this.debug) console.log("✅ Updated State (Functional):", newState); - return newState; - }); - } else { - // Object update (merge with existing state) - component.setState(prevState => { - const newState = { ...prevState, ...update }; // Merge here! - if (this.debug) console.log("✅ Updated State (Object Merge):", newState); - return newState; - }); - } + getState(key) { + if (!this.components.length) return null; + const state = this.components[0]?.state || null; + + if (key === undefined) { + return state; + } else if (typeof key === 'string') { + return state?.[key]; + } else if (Array.isArray(key)) { + const filteredState = {}; + for (const k of key) { + if (state && Object.hasOwn(state, k)) { + filteredState[k] = state[k]; } - }); - return this; + } + return filteredState; } + return null; + } - getProp(propName) { - if (!this.fibers.length) return null; - return this.fibers[0]?.memoizedProps?.[propName]; - } - - setProp(propName) { - this.fibers.forEach(fiber => { - if (fiber?.memoizedProps) { - fiber.memoizedProps[propName] = value; + setState(update) { + this.components.forEach(component => { + if (component?.setState) { + if (typeof update === 'function') { + // Functional update + component.setState(prevState => { + const newState = update(prevState); + if (this.debug) console.log("✅ Updated State (Functional):", newState); + return newState; + }); + } else { + // Object update (merge with existing state) + component.setState(prevState => { + const newState = { + ...prevState, + ...update + }; + if (this.debug) console.log("✅ Updated State (Object Merge):", newState); + return newState; + }); } - }); - return this; // Enable chaining + } + }); + return this; + } + + getProp(propName) { + if (!this.fibers.length) return null; + + if (propName === undefined) { + return this.fibers[0]?.memoizedProps; } - forceUpdate() { - this.components.forEach(component => { - if (component?.forceUpdate) { - component.forceUpdate(); - if (this.debug) console.log("🔄 Forced React Re-render"); - } - }); - return this; // Enable chaining - } + return this.fibers[0]?.memoizedProps?.[propName]; + } + + setProp(propName) { + this.fibers.forEach(fiber => { + if (fiber?.memoizedProps) { + fiber.memoizedProps[propName] = value; + } + }); + return this; // Enable chaining + } + + forceUpdate() { + this.components.forEach(component => { + if (component?.forceUpdate) { + component.forceUpdate(); + if (this.debug) console.log("🔄 Forced React Re-render"); + } + }); + return this; // Enable chaining + } } -console.log("Window cat: ", window.cat); +function makeSerializable(obj) { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(item => makeSerializable(item)); + } + + const serializableObj = {}; + for (const key in obj) { + if (Object.hasOwn(obj, key)) { + let value = obj[key]; + + if (typeof value === 'function') { + value = '[Function]'; + } else if (value instanceof HTMLElement) { + value = { + type: 'HTMLElement', + id: value.id, + tagName: value.tagName + }; // Replace DOM node with ID/tag info + } else if (typeof value === 'symbol') { + value = value.toString(); + } else if (typeof value === 'object' && value !== null) { + value = makeSerializable(value); + } + + serializableObj[key] = value; + } + } + return serializableObj; +} -// Listen for messages from the background script (via window.postMessage) window.addEventListener('message', (event) => { - console.log(event) - if (event.data.type === "reactFiberRequest") { - const { selector, action, payload, debug, messageId } = event.data; - const fiberInstance = ReactFiber.find(selector, { debug }); + const { + selector, + action, + payload, + debug, + messageId + } = event.data; + const fiberInstance = ReactFiber.find(selector, { + debug + }); let response; switch (action) { @@ -124,12 +167,12 @@ window.addEventListener('message', (event) => { case "setState": // Handle both function and object updates if (payload.updateFn) { - const updateFn = eval(`(${payload.updateFn})`); - fiberInstance.setState(updateFn); + const updateFn = eval(`(${payload.updateFn})`); + fiberInstance.setState(updateFn); } else { - fiberInstance.setState(payload.updateObject); + fiberInstance.setState(payload.updateObject); } - response = {}; // Acknowledge + response = {}; break; case "getProp": @@ -137,18 +180,21 @@ window.addEventListener('message', (event) => { break; case "setProp": fiberInstance.setProp(payload.propName, payload.value); - response = {}; // Acknowledge + response = {}; break; case "forceUpdate": fiberInstance.forceUpdate(); - response = {}; // Acknowledge + response = {}; break; default: console.warn(`[pageState] Unknown action: ${action}`); response = null; } - // Send the response back to the background script using window.postMessage + if (response !== null && typeof response === 'object') { + response = makeSerializable(response); + } + window.postMessage({ type: "reactFiberResponse", response, diff --git a/src/seqta/utils/ReactFiber.ts b/src/seqta/utils/ReactFiber.ts index f7ca51d7..65f49ead 100644 --- a/src/seqta/utils/ReactFiber.ts +++ b/src/seqta/utils/ReactFiber.ts @@ -1,72 +1,81 @@ -import browser from 'webextension-polyfill'; - class ReactFiber { private selector: string; private debug: boolean; private messageIdCounter: number = 0; // Counter for unique message IDs - constructor(selector: string, options: { debug?: boolean } = {}) { + constructor(selector: string, options: { + debug ? : boolean + } = {}) { this.selector = selector; this.debug = options.debug || false; } - static find(selector: string, options: { debug?: boolean } = {}) { + static find(selector: string, options: { + debug ? : boolean + } = {}) { return new ReactFiber(selector, options); } - private async sendMessage(action: string, payload: any = {}): Promise { - return new Promise((resolve, _) => { - const messageId = this.messageIdCounter++; - const message = { - type: "reactFiberRequest", - selector: this.selector, - action, - payload, - debug: this.debug, - messageId, // Include the unique message ID - }; + private async sendMessage(action: string, payload: any = {}): Promise < any > { + return new Promise((resolve, _) => { + const messageId = this.messageIdCounter++; + const message = { + type: "reactFiberRequest", + selector: this.selector, + action, + payload, + debug: this.debug, + messageId, + }; - const listener = (response: any) => { - if (response.data?.type === 'reactFiberResponse' && response.data?.messageId === messageId) { - if (this.debug) { - console.log("Content Received Response:", response.data.response); - } - resolve(response.data.response); - window.removeEventListener("message", listener) - } - }; - window.addEventListener('message', listener); - window.postMessage(message, "*"); - }); + const listener = (response: any) => { + if (response.data?.type === 'reactFiberResponse' && response.data?.messageId === messageId) { + if (this.debug) { + console.log("Content Received Response:", response.data.response); + } + resolve(response.data.response); + window.removeEventListener("message", listener) + } + }; + window.addEventListener('message', listener); + window.postMessage(message, "*"); + }); } - async getState(key?: string | string[]): Promise { // Type change: allow string or string[] - return this.sendMessage("getState", { key }); + async getState(key ? : string | string[]): Promise < any > { + return this.sendMessage("getState", { + key + }); } - async setState(update: any | ((prevState: any) => any)): Promise { - // Now async again. + async setState(update: any | ((prevState: any) => any)): Promise < ReactFiber > { const updateFnString = typeof update === 'function' ? update.toString() : null; const updateObject = typeof update !== 'function' ? update : null; - await this.sendMessage("setState", { updateFn: updateFnString, updateObject }); + await this.sendMessage("setState", { + updateFn: updateFnString, + updateObject + }); return this; } - async getProp(propName: string): Promise { - return this.sendMessage("getProp", { propName }); + async getProps(propName ? : string): Promise < any > { + return this.sendMessage("getProp", { + propName + }); } - async setProp(propName: string, value: any): Promise { - // Now async again - await this.sendMessage("setProp", { propName, value }); + async setProp(propName: string, value: any): Promise < ReactFiber > { + await this.sendMessage("setProp", { + propName, + value + }); return this; } - async forceUpdate(): Promise { - // Now async again + async forceUpdate(): Promise < ReactFiber > { await this.sendMessage("forceUpdate"); return this; } diff --git a/src/seqta/utils/listeners/ClickListeners.ts b/src/seqta/utils/listeners/ClickListeners.ts new file mode 100644 index 00000000..84e76699 --- /dev/null +++ b/src/seqta/utils/listeners/ClickListeners.ts @@ -0,0 +1,46 @@ +import { waitForElm } from "@/SEQTA"; +import ReactFiber from "../ReactFiber"; + +const handleNotificationClick = async (target: HTMLElement) => { + const notificationItem = target.closest('.notifications__item___2ErJN') as HTMLElement | null; + if (!notificationItem) return; + + const buttonType = notificationItem.getAttribute('data-type'); + if (buttonType !== 'message') return; + + const notificationList = await ReactFiber.find('.notifications__list___rp2L2').getState(); + const buttonId = notificationItem.getAttribute('data-id'); + if (!buttonId) return; + + const matchingNotification = notificationList.storeState.notifications.items.find( + (item: any) => item.notificationID === parseInt(buttonId) + ); + + await waitForElm('.Viewer__Viewer___32BH-', true, 20); + + // Select the specific direct message + ReactFiber.find('.Viewer__Viewer___32BH-').setState({ selected: new Set([matchingNotification.message.messageID]) }); +}; + +const clickListeners = [ + { + selector: '.notifications__item___2ErJN', + handler: handleNotificationClick, + }, +]; + +const registerClickListeners = () => { + console.log("Registering click listeners..."); + + document.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + + clickListeners.forEach(({ selector, handler }) => { + if (target.closest(selector)) { + handler(target); + } + }); + }); +}; + +export default registerClickListeners;