From 55d981264fafb3a1115219d7b03bb257f6def5c6 Mon Sep 17 00:00:00 2001 From: jvidalv Date: Wed, 3 Apr 2024 17:51:38 +0200 Subject: [PATCH] Ignore threads feature --- public/manifest.json | 2 +- public/styles/mediavida.css | 13 ++-- src/domains/thread.ts | 88 +++++++++++++++++++++++++ src/injected/configuration.tsx | 4 ++ src/injected/homepage.tsx | 8 +-- src/injected/index.tsx | 48 ++++++++++---- src/injected/thread.tsx | 10 +++ src/injected/threads.tsx | 3 + src/injected/utils/brand.ts | 2 + src/injected/utils/data.ts | 2 +- src/injected/utils/loader.ts | 29 ++++++++ src/react/site/components/threads.tsx | 50 ++++++++------ src/react/site/components/ui/button.tsx | 15 +++++ src/react/site/components/ui/index.ts | 1 + src/react/site/configuration/index.tsx | 54 ++++++++++++--- src/react/site/home/index.tsx | 8 +-- src/react/site/thread/user.tsx | 2 +- src/types/globals.d.ts | 2 +- src/types/site-types.ts | 17 ----- src/utils/store.ts | 10 ++- 20 files changed, 292 insertions(+), 76 deletions(-) create mode 100644 src/domains/thread.ts create mode 100644 src/injected/threads.tsx create mode 100644 src/injected/utils/loader.ts create mode 100644 src/react/site/components/ui/button.tsx create mode 100644 src/react/site/components/ui/index.ts delete mode 100644 src/types/site-types.ts diff --git a/public/manifest.json b/public/manifest.json index 00c04ea..d4426e0 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -3,7 +3,7 @@ "name": "MV-Ignited", "description": "Extensión de navegador para el foro de mediavida.com que enriquece la experiencia de usuario añadiendo y mejorando funcionalidades.", - "version": "0.1.0", + "version": "1.0.0", "browser_specific_settings": { "gecko": { diff --git a/public/styles/mediavida.css b/public/styles/mediavida.css index 81c9f0e..4c14e58 100644 --- a/public/styles/mediavida.css +++ b/public/styles/mediavida.css @@ -15,7 +15,6 @@ body { opacity: 0 !important; } - /* * Sizes */ @@ -72,6 +71,7 @@ body { /* * Homepage */ + #index #content { min-height: calc(100vh - 177px); } @@ -87,16 +87,19 @@ a.post-btn.btnmola i { padding: 1px 0 0; } - /* * New features */ .mv-ignite--actions-container { opacity: 0; - transition: opacity .25s; + transition: opacity 0.25s; } .post:hover .mv-ignite--actions-container { - opacity: 1 -} \ No newline at end of file + opacity: 1; +} + +.mv-ignite--thread:hover .mv-ignite--thread--button-ignore { + opacity: 1; +} diff --git a/src/domains/thread.ts b/src/domains/thread.ts new file mode 100644 index 0000000..378d175 --- /dev/null +++ b/src/domains/thread.ts @@ -0,0 +1,88 @@ +import { mvIgniteStore } from "../utils/store"; +import { + getSubForumContainerElement, + isFeaturedThreads, + isHomepage, +} from "../injected/utils/loader"; +import { rerenderConfigurationMenuRoot } from "../injected/configuration"; + +export type Thread = { + url: string; + forumSlug: string; + title: string; + thumbnail?: string; + hasLive?: boolean; + urlSinceLastVisit?: string | null; + responsesSinceLastVisit?: number; + userResponseAt?: string | null; + lastActivityAt?: string; + createdAt?: string; + totalResponses?: string; + userLastResponse?: { + username: string; + avatar: string; + }; +}; + +export const getThreadId = (threadUrl: string) => { + const urlSplit = threadUrl.split("-"); + return urlSplit[urlSplit.length - 1]; +}; + +export const addThreadToIgnore = (threadUrl: string) => { + const ignoredThreads = mvIgniteStore.get().ignoredThreads ?? []; + ignoredThreads.push(threadUrl); + mvIgniteStore.set("ignoredThreads", [...new Set(ignoredThreads)]); + + ignoreThreads(); + // this is a hack, should be better implemented + rerenderConfigurationMenuRoot(); + console.log(`MV-Ignited: Thread ${threadUrl} ignored`); +}; + +export const ignoreThreads = () => { + const ignoredThreads = mvIgniteStore.get().ignoredThreads; + const ignoredThreadsIds = ignoredThreads.map((threadUrl) => + getThreadId(threadUrl), + ); + + if (ignoredThreads.length) { + if (isHomepage()) { + document + .querySelectorAll(".mv-ignite--thread") + ?.forEach((threadElement) => { + const idSplit = threadElement.id?.split("-"); + if (ignoredThreadsIds.includes(idSplit[idSplit.length - 1])) { + threadElement?.setAttribute("style", "display:none"); + } else { + threadElement?.setAttribute("style", ""); + } + }); + } + + if (isFeaturedThreads()) { + document.querySelectorAll("a")?.forEach((a) => { + const thread = document.getElementById(`t${a.id.slice(1)}`); + if (ignoredThreadsIds.includes(a.id.slice(1))) { + thread?.setAttribute("style", "display:none"); + } else { + thread?.setAttribute("style", ""); + } + }); + } + + const temasElement = getSubForumContainerElement(); + if (temasElement) { + for (const a of document.querySelectorAll("a")) { + const thread = document.getElementById(`a${a.id.slice(1)}`); + const trElement = thread?.parentElement?.parentElement?.parentElement; + + if (ignoredThreadsIds.includes(a.id.slice(1))) { + trElement?.setAttribute("style", "display:none"); + } else { + trElement?.setAttribute("style", ""); + } + } + } + } +}; diff --git a/src/injected/configuration.tsx b/src/injected/configuration.tsx index 86e2996..2eae3d4 100644 --- a/src/injected/configuration.tsx +++ b/src/injected/configuration.tsx @@ -8,6 +8,10 @@ import React from "react"; let configurationButtonRoot: Root | undefined; let configurationMenuRoot: Root | undefined; +export const rerenderConfigurationMenuRoot = () => { + configurationMenuRoot?.render(); +}; + export const injectConfiguration = () => { const usermenuElement = document.getElementById("usermenu"); if (!usermenuElement) { diff --git a/src/injected/homepage.tsx b/src/injected/homepage.tsx index 33dfbf2..f2ad3d5 100644 --- a/src/injected/homepage.tsx +++ b/src/injected/homepage.tsx @@ -9,11 +9,10 @@ import { getUsername, } from "./utils/data"; import Home from "../react/site/home"; +import { hideContent, showContent } from "./utils/loader"; export const injectHomepage = async () => { - const content = document.getElementById("content"); - content?.setAttribute("style", "opacity: 0 !important"); - + hideContent(); const [forums, favorites, lastPosts, lastNews, userLastsPosts] = await Promise.all([ getForums(), @@ -22,8 +21,7 @@ export const injectHomepage = async () => { getLastNews(), getUserLastPosts(getUsername()), ]); - - content?.setAttribute("style", "opacity: 1 !important"); + showContent(); const main = document.getElementById("main"); if (main) { diff --git a/src/injected/index.tsx b/src/injected/index.tsx index bf1c8bc..b4be234 100644 --- a/src/injected/index.tsx +++ b/src/injected/index.tsx @@ -6,6 +6,16 @@ import { injectBrand, injectFont } from "./utils/brand"; import { injectConfiguration } from "./configuration"; import { injectThread } from "./thread"; import { injectHomepage } from "./homepage"; +import { + isFeaturedThreads, + isHomepage, + isSubForumThreads, + isThread, + showBody, + showContent, +} from "./utils/loader"; +import { injectThreads } from "./threads"; +import { ignoreThreads } from "../domains/thread"; window.ignite = { isFirstRender: true, @@ -15,35 +25,51 @@ window.ignite = { console.log(`MV-Ignited loaded font: ${customFont}`); } }); + // Await for page mounted before trying to modify anything await awaitUntil(() => !!document.getElementById("content")); // To prevent blink's the default CSS loads with opacity:0, we restore the opacity here. - const body = document.getElementsByTagName("body").item(0); - body?.setAttribute("style", "opacity: 1 !important;"); + showBody(); + + if (window.ignite.isFirstRender) { + injectTheme(); + injectBrand(); + } - injectTheme(); - injectBrand(); trackForumVisits(); - // Settings + // Configuration if (document.getElementById("usermenu")) { injectConfiguration(); } // Homepage - if (document.getElementById("index")) { + if (isHomepage()) { injectHomepage(); } + // Threads + if (isSubForumThreads() || isFeaturedThreads()) { + injectThreads(); + } + // Thread - if (document.getElementById("post-container")) { + if (isThread()) { injectThread(); } + + ignoreThreads(); }, }; -window.ignite.render().then(() => { - window.ignite.isFirstRender = false; - console.log("MV-ignite: Successfully rendered"); -}); +window.ignite + .render() + .then(() => { + window.ignite.isFirstRender = false; + console.log("MV-Ignited🔥 successfully rendered ✅"); + }) + .catch(() => { + showContent(); + console.log("MV-Ignited🔥 errored 🔴"); + }); diff --git a/src/injected/thread.tsx b/src/injected/thread.tsx index 5a33599..bd25893 100644 --- a/src/injected/thread.tsx +++ b/src/injected/thread.tsx @@ -2,6 +2,7 @@ import { mvIgniteStore } from "../utils/store"; import { createRoot } from "react-dom/client"; import { UserIgnoredInThread, UserActionsInThread } from "../react/site/thread"; import React from "react"; +import { addThreadToIgnore } from "../domains/thread"; const oldPostsHtml: string[] = []; const oldPostsClass: string[] = []; @@ -9,6 +10,15 @@ const oldPostsClass: string[] = []; export const injectThread = () => { const posts = document.querySelectorAll(".cf.post"); + // Post ignore + const ignoreButton = document.querySelector('a[title="Ignorar el tema"]'); + ignoreButton?.addEventListener("click", () => + addThreadToIgnore( + window.location.href.replace("https://www.mediavida.com", ""), + ), + ); + + // Posts modification Array.from(posts).forEach((post, index) => { const username = post.getAttribute("data-autor"); const postNum = post.getAttribute("data-num"); diff --git a/src/injected/threads.tsx b/src/injected/threads.tsx new file mode 100644 index 0000000..60c98df --- /dev/null +++ b/src/injected/threads.tsx @@ -0,0 +1,3 @@ +export const injectThreads = () => { + // Nothing yet +}; diff --git a/src/injected/utils/brand.ts b/src/injected/utils/brand.ts index 67c093f..f7fce93 100644 --- a/src/injected/utils/brand.ts +++ b/src/injected/utils/brand.ts @@ -7,6 +7,8 @@ export const injectBrand = () => { logo.setAttribute("style", "position: relative;"); logo.innerHTML = `${logo.innerHTML}🔥`; } + + window.document.title = `${window.document.title}🔥`; }; export const injectFont = async (): Promise => { diff --git a/src/injected/utils/data.ts b/src/injected/utils/data.ts index 22e84fc..af01975 100644 --- a/src/injected/utils/data.ts +++ b/src/injected/utils/data.ts @@ -1,4 +1,4 @@ -import { Thread } from "../../types/site-types"; +import { Thread } from "../../domains/thread"; export const getUsername = () => document.querySelector("#user-data span")?.innerHTML; diff --git a/src/injected/utils/loader.ts b/src/injected/utils/loader.ts new file mode 100644 index 0000000..8c60018 --- /dev/null +++ b/src/injected/utils/loader.ts @@ -0,0 +1,29 @@ +export const showBody = () => { + const body = document.getElementsByTagName("body").item(0); + body?.setAttribute("style", "opacity: 1 !important;"); +}; +export const hideBody = () => { + const body = document.getElementsByTagName("body").item(0); + body?.setAttribute("style", "opacity: 0 !important;"); +}; + +export const showContent = () => { + const content = document.getElementById("content"); + content?.setAttribute("style", "opacity: 1 !important;"); +}; +export const hideContent = () => { + const content = document.getElementById("content"); + content?.setAttribute("style", "opacity: 0 !important;"); +}; + +export const isHomepage = () => !!document.getElementById("index"); +export const isThread = () => !!document.getElementById("post-container"); +export const getSubForumContainerElement = () => + document.getElementById("temas"); +export const isSubForumThreads = () => !!getSubForumContainerElement(); +export const isFeaturedThreads = () => + document.getElementById("top") || + document.getElementById("foros_spy") || + document.getElementById("featured") || + document.getElementById("unread") || + document.getElementById("new"); diff --git a/src/react/site/components/threads.tsx b/src/react/site/components/threads.tsx index 0139342..2f6c272 100644 --- a/src/react/site/components/threads.tsx +++ b/src/react/site/components/threads.tsx @@ -1,7 +1,11 @@ import React, { PropsWithChildren } from "react"; -import { type Thread } from "../../../types/site-types"; import clsx from "clsx"; import { getIconClassBySlug } from "../utils/forums"; +import { + addThreadToIgnore, + getThreadId, + Thread, +} from "../../../domains/thread"; const abbrevNumberToInt = (str: string) => { const match = str.match(/^(\d+(\.\d+)?)([kK])?$/); @@ -25,33 +29,41 @@ const Root = ({ children, className, }: PropsWithChildren<{ className?: string }>) => ( -
+
{children}
); -const Thread = ({ - url, - urlSinceLastVisit, - title, - lastActivityAt, - responsesSinceLastVisit, - totalResponses, - hasLive, - forumSlug, -}: Thread) => { +const Thread = (props: Thread) => { + const { + url, + urlSinceLastVisit, + title, + lastActivityAt, + responsesSinceLastVisit, + totalResponses, + hasLive, + forumSlug, + } = props; + const totalResponsesAsInt = totalResponses ? abbrevNumberToInt(totalResponses) : 0; return ( -
-
+
+ +
, + HTMLButtonElement + >, +) => { + return ( + +
-
+
{!ignoredUsers?.length && (
No tienes ningun usuario ignorado. @@ -72,6 +87,25 @@ export const ConfigurationMenu = () => { ))}
+
+ +
+ {!ignoredThreads?.length && ( +
+ No tienes ningun hilo ignorado. +
+ )} + {ignoredThreads?.map((thread) => ( + + ))} +
+
); @@ -102,7 +136,7 @@ export const ConfigurationButton = ({ title="MV-ignite configuracion" > - Configuración MV-Ignite + Configuración MV-Ignited ); }; diff --git a/src/react/site/home/index.tsx b/src/react/site/home/index.tsx index c983a71..f795ac3 100644 --- a/src/react/site/home/index.tsx +++ b/src/react/site/home/index.tsx @@ -1,10 +1,10 @@ import React from "react"; import clsx from "clsx"; import Threads from "../components/threads"; -import { Thread } from "../../../types/site-types"; import { getIconClassBySlug } from "../utils/forums"; import { mvIgniteStore } from "../../../utils/store"; import { getUsername } from "../../../injected/utils/data"; +import { Thread } from "../../../domains/thread"; function Home({ favorites, @@ -23,7 +23,7 @@ function Home({ return (
@@ -72,13 +72,13 @@ function Home({
-
+

Foro

- {forumsLastVisited?.length && + {!!forumsLastVisited?.length && forumsLastVisited .filter((_, i) => i < 8) .map((forumSlug) => ( diff --git a/src/react/site/thread/user.tsx b/src/react/site/thread/user.tsx index 12d1a06..d4f84f8 100644 --- a/src/react/site/thread/user.tsx +++ b/src/react/site/thread/user.tsx @@ -13,7 +13,7 @@ export const UserActionsInThread = ({ username }: { username: string }) => { mvIgniteStore.set("ignoredUsers", [...new Set(ignoredUsers)]); window.ignite .render() - .then(() => console.log(`MV-Ignite: User ${username} ignored`)); + .then(() => console.log(`MV-Ignited: User ${username} ignored`)); } }; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 81797db..2f9137b 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -2,7 +2,7 @@ declare global { interface Window { ignite: { isFirstRender: boolean; - render: () => Promise; + render: (configuration?: { refetchHomepage: boolean }) => Promise; // roots : { // get : (rootKey: string) => Root | undefined // set : (rootKey: string, root: Root) => Root diff --git a/src/types/site-types.ts b/src/types/site-types.ts deleted file mode 100644 index d0f69c3..0000000 --- a/src/types/site-types.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type Thread = { - url: string; - forumSlug: string; - title: string; - thumbnail?: string; - hasLive?: boolean; - urlSinceLastVisit?: string | null; - responsesSinceLastVisit?: number; - userResponseAt?: string | null; - lastActivityAt?: string; - createdAt?: string; - totalResponses?: string; - userLastResponse?: { - username: string; - avatar: string; - }; -}; diff --git a/src/utils/store.ts b/src/utils/store.ts index acd4d95..8d72b93 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,6 +1,7 @@ export type MVIgniteStore = { forumsLastVisited: string[]; ignoredUsers: string[]; + ignoredThreads: string[]; customFont?: string; }; @@ -12,7 +13,14 @@ const set = (key: K, data: MVIgniteStore[K]) => { }; const get = (): MVIgniteStore => - JSON.parse(localStorage.getItem(MEDIAVIDA_KEY) ?? "{}"); + JSON.parse( + localStorage.getItem(MEDIAVIDA_KEY) ?? + JSON.stringify({ + forumsLastVisited: [], + ignoredUsers: [], + ignoredThreads: [], + }), + ); export const mvIgniteStore = { set,