Skip to content

Commit

Permalink
Ignore threads feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jvidalv committed Apr 3, 2024
1 parent a63ec0c commit 55d9812
Show file tree
Hide file tree
Showing 20 changed files with 292 additions and 76 deletions.
2 changes: 1 addition & 1 deletion public/manifest.json
Expand Up @@ -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": {
Expand Down
13 changes: 8 additions & 5 deletions public/styles/mediavida.css
Expand Up @@ -15,7 +15,6 @@ body {
opacity: 0 !important;
}


/*
* Sizes
*/
Expand Down Expand Up @@ -72,6 +71,7 @@ body {
/*
* Homepage
*/

#index #content {
min-height: calc(100vh - 177px);
}
Expand All @@ -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
}
opacity: 1;
}

.mv-ignite--thread:hover .mv-ignite--thread--button-ignore {
opacity: 1;
}
88 changes: 88 additions & 0 deletions 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", "");
}
}
}
}
};
4 changes: 4 additions & 0 deletions src/injected/configuration.tsx
Expand Up @@ -8,6 +8,10 @@ import React from "react";
let configurationButtonRoot: Root | undefined;
let configurationMenuRoot: Root | undefined;

export const rerenderConfigurationMenuRoot = () => {
configurationMenuRoot?.render(<ConfigurationMenu />);
};

export const injectConfiguration = () => {
const usermenuElement = document.getElementById("usermenu");
if (!usermenuElement) {
Expand Down
8 changes: 3 additions & 5 deletions src/injected/homepage.tsx
Expand Up @@ -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(),
Expand All @@ -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) {
Expand Down
48 changes: 37 additions & 11 deletions src/injected/index.tsx
Expand Up @@ -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,
Expand All @@ -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 🔴");
});
10 changes: 10 additions & 0 deletions src/injected/thread.tsx
Expand Up @@ -2,13 +2,23 @@ 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[] = [];

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");
Expand Down
3 changes: 3 additions & 0 deletions src/injected/threads.tsx
@@ -0,0 +1,3 @@
export const injectThreads = () => {
// Nothing yet
};
2 changes: 2 additions & 0 deletions src/injected/utils/brand.ts
Expand Up @@ -7,6 +7,8 @@ export const injectBrand = () => {
logo.setAttribute("style", "position: relative;");
logo.innerHTML = `${logo.innerHTML}<span style="position:absolute; font-size:20px; right: -16px;">🔥</span>`;
}

window.document.title = `${window.document.title}🔥`;
};

export const injectFont = async (): Promise<string | void> => {
Expand Down
2 changes: 1 addition & 1 deletion 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;
Expand Down
29 changes: 29 additions & 0 deletions 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");
50 changes: 31 additions & 19 deletions 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])?$/);
Expand All @@ -25,33 +29,41 @@ const Root = ({
children,
className,
}: PropsWithChildren<{ className?: string }>) => (
<div
className={clsx(
className,
"grid grid-cols-1 shadow gap-y-0.5 rounded overflow-hidden",
)}
>
<div className={clsx(className, "grid grid-cols-1 shadow gap-y-0.5 rounded")}>
{children}
</div>
);

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 (
<div className="flex justify-between overflow-hidden">
<div className="flex cursor-pointer flex-1 bg-surface items-center gap-2 p-2 hover:bg-opacity-50 transition duration-150">
<div
id={`mv-ignite--thread-${getThreadId(url)}`}
className="mv-ignite--thread flex justify-between relative bg-surface first:rounded-t last:rounded-b"
>
<button
className="mv-ignite--thread--button-ignore opacity-0 h-full w-8 -left-8 absolute text-gray-500 hover:scale-125 transition"
onClick={() => addThreadToIgnore(url)}
title={`Ignorar hilo: ${title}`}
>
<i className="fa fa-eye-slash"></i>
<span className="sr-only">Ignorar hilo</span>
</button>
<div className="flex cursor-pointer flex-1 items-center gap-2 p-2 hover:bg-opacity-50 transition duration-150">
<a
href={`https://www.mediavida.com/foro/${forumSlug}`}
title={`Ir a ${forumSlug}`}
Expand Down

0 comments on commit 55d9812

Please sign in to comment.