Header etc

This commit is contained in:
Lalle 2023-05-11 18:55:24 +02:00
parent 4cbb5df7c6
commit 2fe2cb2c5c
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
13 changed files with 122 additions and 179 deletions

View File

@ -41,6 +41,8 @@
"imageboard": "^0.6.25", "imageboard": "^0.6.25",
"jquery": "^3.6.4", "jquery": "^3.6.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"webextension-polyfill": "^0.10.0" "webextension-polyfill": "^0.10.0"
} }

75
pnpm-lock.yaml generated
View File

@ -28,6 +28,12 @@ dependencies:
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
react-dnd:
specifier: ^16.0.1
version: 16.0.1(@types/react@18.0.28)(react@18.2.0)
react-dnd-html5-backend:
specifier: ^16.0.1
version: 16.0.1
react-dom: react-dom:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0(react@18.2.0) version: 18.2.0(react@18.2.0)
@ -289,7 +295,6 @@ packages:
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
dev: true
/@babel/template@7.20.7: /@babel/template@7.20.7:
resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
@ -698,6 +703,18 @@ packages:
config-chain: 1.1.13 config-chain: 1.1.13
dev: true dev: true
/@react-dnd/asap@5.0.2:
resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==}
dev: false
/@react-dnd/invariant@4.0.2:
resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==}
dev: false
/@react-dnd/shallowequal@4.0.2:
resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
dev: false
/@samrum/vite-plugin-web-extension@4.0.0(vite@4.3.1): /@samrum/vite-plugin-web-extension@4.0.0(vite@4.3.1):
resolution: {integrity: sha512-j0wM6bZn9Pk2KFa8QAeUJwQOAyAvrtMZRjL88vZ3ecmRuUuheuj6mByDrPLm6EBzls1+d7Q4aUoH7z0sYKe97g==} resolution: {integrity: sha512-j0wM6bZn9Pk2KFa8QAeUJwQOAyAvrtMZRjL88vZ3ecmRuUuheuj6mByDrPLm6EBzls1+d7Q4aUoH7z0sYKe97g==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@ -786,7 +803,6 @@ packages:
/@types/prop-types@15.7.5: /@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
dev: true
/@types/react-dom@18.0.11: /@types/react-dom@18.0.11:
resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==}
@ -800,11 +816,9 @@ packages:
'@types/prop-types': 15.7.5 '@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3 '@types/scheduler': 0.16.3
csstype: 3.1.2 csstype: 3.1.2
dev: true
/@types/scheduler@0.16.3: /@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
dev: true
/@types/semver@7.5.0: /@types/semver@7.5.0:
resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==}
@ -1648,7 +1662,6 @@ packages:
/csstype@3.1.2: /csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
dev: true
/dashdash@1.14.1: /dashdash@1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
@ -1772,6 +1785,14 @@ packages:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dev: true dev: true
/dnd-core@16.0.1:
resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
dependencies:
'@react-dnd/asap': 5.0.2
'@react-dnd/invariant': 4.0.2
redux: 4.2.1
dev: false
/doctrine@2.1.0: /doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2836,6 +2857,12 @@ packages:
dependencies: dependencies:
function-bind: 1.1.1 function-bind: 1.1.1
/hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
dev: false
/html-document-parser@1.1.1: /html-document-parser@1.1.1:
resolution: {integrity: sha512-nbwGhL9zpmrtTXVdbRfmmxKJ2Vxd6ULOggjtPfaRFwWr67fM181XY0CvWLx/DqQR1BrJD93K/qJmp5ReSq4xHQ==} resolution: {integrity: sha512-nbwGhL9zpmrtTXVdbRfmmxKJ2Vxd6ULOggjtPfaRFwWr67fM181XY0CvWLx/DqQR1BrJD93K/qJmp5ReSq4xHQ==}
dependencies: dependencies:
@ -4073,6 +4100,36 @@ packages:
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
dev: true dev: true
/react-dnd-html5-backend@16.0.1:
resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
dependencies:
dnd-core: 16.0.1
dev: false
/react-dnd@16.0.1(@types/react@18.0.28)(react@18.2.0):
resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
peerDependencies:
'@types/hoist-non-react-statics': '>= 3.3.1'
'@types/node': '>= 12'
'@types/react': '>= 16'
react: '>= 16.14'
peerDependenciesMeta:
'@types/hoist-non-react-statics':
optional: true
'@types/node':
optional: true
'@types/react':
optional: true
dependencies:
'@react-dnd/invariant': 4.0.2
'@react-dnd/shallowequal': 4.0.2
'@types/react': 18.0.28
dnd-core: 16.0.1
fast-deep-equal: 3.1.3
hoist-non-react-statics: 3.3.2
react: 18.2.0
dev: false
/react-dom@18.2.0(react@18.2.0): /react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies: peerDependencies:
@ -4085,7 +4142,6 @@ packages:
/react-is@16.13.1: /react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
/react-refresh@0.14.0: /react-refresh@0.14.0:
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
@ -4139,9 +4195,14 @@ packages:
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
dev: true dev: true
/redux@4.2.1:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
dependencies:
'@babel/runtime': 7.21.0
dev: false
/regenerator-runtime@0.13.11: /regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: true
/regexp.prototype.flags@1.5.0: /regexp.prototype.flags@1.5.0:
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}

View File

@ -1,27 +0,0 @@
import Post from "./Post"
class Board {
boardName: string
apiUrl: string
posts: Post[]
constructor(boardName: string) {
this.boardName = boardName
this.apiUrl = `https://a.4cdn.org/${boardName}/threads.json`
this.posts = []
}
async fetchThreads() {
try {
const response = await fetch(this.apiUrl)
const data = await response.json()
console.log(data)
// Process data and populate this.posts
} catch (error) {
console.error("Error fetching threads:", error)
}
}
// Add more methods to interact with the 4chan API and manage the data
}
export default Board

View File

@ -1,13 +0,0 @@
export default class Post {
postId: number
author: string
content: string
timestamp: Date
constructor(postId: number, author: string, content: string, timestamp: Date) {
this.postId = postId
this.author = author
this.content = content
this.timestamp = timestamp
}
}

View File

@ -1,24 +0,0 @@
import Post from "./Post"
export default class Thread {
threadId: number
title: string
opPost: Post
replies: Post[]
constructor(threadId: number, title: string, opPost: Post) {
this.threadId = threadId
this.title = title
this.opPost = opPost
this.replies = []
}
addReply(reply: Post): void {
this.replies.push(reply)
}
getReplyCount(): number {
return this.replies.length
}
}

View File

@ -1,9 +1,20 @@
import $ from "jquery" import $ from "jquery"
import { Config } from "../options/Conf" import { Config } from "../options/Conf"
// This function initializes the image hovering functionality
/**
Initializes the image hovering functionality for 4chan.
*/
export function initImageHovering(): void { export function initImageHovering(): void {
// If image hovering is disabled, return early // Get the user's configuration
if (Config.main.ImageHover === false) { return } const config = Config.main.ImageHover
// If the user has disabled image hovering, do nothing
if (!config.valueOf()) {
return
}
// Find all thumbnail images on the page // Find all thumbnail images on the page
const thumbnails = $("a.fileThumb") const thumbnails = $("a.fileThumb")
@ -11,7 +22,6 @@ export function initImageHovering(): void {
thumbnails.each(function () { thumbnails.each(function () {
const thumbnail = $(this) const thumbnail = $(this)
const imageUrl = thumbnail.attr("href") as string const imageUrl = thumbnail.attr("href") as string
// Create a new image element to be displayed on hover // Create a new image element to be displayed on hover
const hoverImage = $("<img>") const hoverImage = $("<img>")
.attr("src", imageUrl) .attr("src", imageUrl)
@ -23,12 +33,13 @@ export function initImageHovering(): void {
.appendTo("body") .appendTo("body")
// Show the image on mouseover and hide it on mouseout // Show the image on mouseover and hide it on mouseout
thumbnail.on("mouseover", () => { thumbnail
hoverImage.show() .on("mouseover", () => {
}) hoverImage.show()
thumbnail.on("mouseout", () => { })
hoverImage.hide() .on("mouseout", () => {
}) hoverImage.hide()
})
// Update the hover image position based on the mouse cursor // Update the hover image position based on the mouse cursor
thumbnail.on("mousemove", (event) => { thumbnail.on("mousemove", (event) => {

View File

@ -1,19 +0,0 @@
.logo {
z-index: 99999;
position: fixed;
bottom: 20px;
right: 10px;
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
border: 4px solid #c72a21;
border-radius: 50%;
background-color: #fff;
}
img {
position: absolute;
top: 7px;
}

View File

@ -1,13 +1,14 @@
import React from "react" import React from "react"
import Catalog from "./Catalog"
import Header from "./Header"
function App() { function App() {
return (<> return (<>
<Header board="g" /> <div>
<Catalog /> <p>
Its over.
</p>
</div>
</> </>
) )
} }

View File

@ -1,17 +0,0 @@
import React from "react"
import $ from "jquery"
import Board from "../classes/Board"
const Catalog = () => {
const catalogText = "Hello world"
new Board("g")
return (
<div className="flex flex-col items-center justify-center">
<div className="flex flex-row items-center justify-center">
<p className="text-black font-bold">{catalogText}</p>
</div>
</div>
)
}
export default Catalog

View File

@ -1,14 +1,21 @@
import React, { useState } from "react" import React, { useState } from "react"
import SettingsPopup from "./SettingsPopup" import SettingsPopup from "./SettingsPopup"
import $ from "jquery"
interface HeaderBarProps { interface HeaderBarProps {
board: string; board: string;
} }
const Header: React.FC<HeaderBarProps> = ({ board }) => { const Header: React.FC<HeaderBarProps> = () => {
const indexUrl = `https://boards.4chan.org/${board}/` const indexUrl = "https://boards.4chan.org/pol/"
const catalogUrl = `https://boards.4chan.org/${board}/catalog` const catalogUrl = "https://boards.4chan.org/pol/catalog"
const [settingsVisible, setSettingsVisible] = useState<boolean>(false) const [settingsVisible, setSettingsVisible] = useState<boolean>(false)
// Remove the default header
$("#boardNavDesktop").remove()
$(".danbo-slot").remove()
$("h4").remove()
$(".abovePostForm").remove()
$(".boardList").remove()
const toggleSettingsPopup = () => { const toggleSettingsPopup = () => {
setSettingsVisible(!settingsVisible) setSettingsVisible(!settingsVisible)
@ -32,7 +39,7 @@ const Header: React.FC<HeaderBarProps> = ({ board }) => {
</button> </button>
{settingsVisible && ( {settingsVisible && (
<div className="absolute right-0 mt-2"> <div className="absolute right-0 mt-2">
<SettingsPopup /> <SettingsPopup onClose={toggleSettingsPopup} />
</div> </div>
)} )}
</div> </div>

View File

@ -1,54 +1,27 @@
import React, { useState } from "react" import React, { useState } from "react"
import Header from "./Header" import { Config } from "~/entries/options/Conf"
interface ConfigProps {
main: { interface SettingsPopupProps {
ImageHover: boolean; onClose: () => void;
};
} }
const defaultConfig: ConfigProps = {
main: {
ImageHover: true,
},
}
const SettingsPopup: React.FC = () => { const SettingsPopup: React.FC<SettingsPopupProps> = ({ onClose }) => {
const [config, setConfig] = useState<ConfigProps>(defaultConfig)
const [activeTab, setActiveTab] = useState<string>("Main") const [activeTab, setActiveTab] = useState<string>("Main")
const handleImageHoverChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleImageHoverChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setConfig({ ...config, main: { ...config.main, ImageHover: e.target.checked } }) const newConfig = { ...Config }
newConfig.main.ImageHover = e.target.checked
chrome.storage.sync.set({ config: newConfig })
} }
return ( return (
<div className="fixed top-0 left-0 w-full h-full flex justify-center items-center"> <div className="fixed top-0 left-0 w-full h-full flex justify-center items-center">
<div className="bg-white p-4 rounded-lg shadow-md"> <div className="bg-white p-4 rounded-lg shadow-md">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h1 className="text-xl font-semibold">Settings</h1> <h2 className="text-xl font-semibold">Settings</h2>
<button onClick={onClose}>X</button>
<button
className="text-red-500 hover:text-red-600"
onClick={() => {
chrome.storage.sync.set({ config })
window.location.reload()
}}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
</div> </div>
<div className="flex mb-4"> <div className="flex mb-4">
<button <button
@ -72,7 +45,7 @@ const SettingsPopup: React.FC = () => {
id="image-hover" id="image-hover"
type="checkbox" type="checkbox"
className="mr-2" className="mr-2"
checked={config.main.ImageHover} checked={Config.main.ImageHover}
onChange={handleImageHoverChange} onChange={handleImageHoverChange}
/> />
Image Hover Image Hover

View File

@ -4,34 +4,22 @@ import ReactDOM from "react-dom/client"
import renderContent from "../renderContent" import renderContent from "../renderContent"
import App from "./App" import App from "./App"
import "../../../index.css" import "../../../index.css"
import Post from "../classes/Post"
import Thread from "../classes/Thread"
import { initImageHovering } from "../image" import { initImageHovering } from "../image"
import $ from "jquery" import $ from "jquery"
import Header from "./Header"
renderContent(import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS, (appRoot) => { renderContent(import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS, (appRoot) => {
ReactDOM.createRoot(appRoot).render( ReactDOM.createRoot(appRoot).render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
<Header board="pol" />
</React.StrictMode> </React.StrictMode>
) )
}) })
// Usage example:
const opPost = new Post(1, "Anonymous", "Hello, this is the first post in the thread!", new Date())
const thread = new Thread(12345, "Example Thread", opPost)
const reply1 = new Post(2, "Anonymous", "Nice thread!", new Date())
thread.addReply(reply1)
console.log("Thread ID:", thread.threadId)
console.log("Thread Title:", thread.title)
console.log("Number of Replies:", thread.getReplyCount())
// Initialize the image hovering functionality once the document is ready // Initialize the image hovering functionality once the document is ready
$(document).ready(() => { $(document).ready(() => {
console.log("Document is ready!") console.log("Document is ready!")
initImageHovering() initImageHovering()
}) })

View File

@ -4,7 +4,7 @@ const sharedManifest = {
content_scripts: [ content_scripts: [
{ {
js: ["src/entries/contentScript/primary/main.tsx"], js: ["src/entries/contentScript/primary/main.tsx"],
matches: ["*://4chan.org/*", "*://boards.4chan.org/*, *://boards.4channel.org/*", "*://boards.4channel.org/*"], matches: ["*://4chan.org/*", "*://boards.4chan.org/*, *://boards.4chan.org/*", "*://boards.4channel.org/*"],
}, },
], ],
icons: { icons: {