Unfollow tool for Bluesky

add dark theme

Changed files
+180 -53
src
+29 -19
index.html
···
-
<!DOCTYPE html>
+
<!doctype html>
<html lang="en">
-
-
<head>
-
<meta charset="utf-8" />
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
-
<meta name="theme-color" content="#000000" />
-
<meta property="og:title" content="cleanfollow-bsky" />
-
<meta property="og:type" content="website" />
-
<meta property="og:url" content="https://cleanfollow-bsky.pages.dev" />
-
<meta property="og:description" content="Unfollow blocked, deleted, suspended, and deactivated Bluesky accounts" />
-
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
-
<title>cleanfollow-bsky</title>
-
</head>
-
-
<body>
-
<noscript>You need to enable JavaScript to run this app.</noscript>
-
<div id="root"></div>
+
<head>
+
<meta charset="utf-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
+
<meta name="theme-color" content="#000000" />
+
<meta property="og:title" content="cleanfollow-bsky" />
+
<meta property="og:type" content="website" />
+
<meta property="og:url" content="https://cleanfollow-bsky.pages.dev" />
+
<meta
+
property="og:description"
+
content="Unfollow blocked, deleted, suspended, and deactivated Bluesky accounts"
+
/>
+
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
+
<title>cleanfollow-bsky</title>
+
<script>
+
if (
+
localStorage.theme === "dark" ||
+
(!("theme" in localStorage) &&
+
window.matchMedia("(prefers-color-scheme: dark)").matches)
+
)
+
document.documentElement.classList.add("dark");
+
else document.documentElement.classList.remove("dark");
+
</script>
+
</head>
-
<script src="/src/index.tsx" type="module"></script>
-
</body>
+
<body id="root" class="dark:bg-dark-500 bg-slate-100">
+
<noscript>You need to enable JavaScript to run this app.</noscript>
+
<div id="root"></div>
+
<script src="/src/index.tsx" type="module"></script>
+
</body>
</html>
+60 -34
src/App.tsx
···
resolveFromIdentity,
type Session,
} from "@atcute/oauth-browser-client";
+
import { AiFillGithub, Bluesky, TbMoonStar, TbSun } from "./svg";
configureOAuth({
metadata: {
···
return (
<div class="flex flex-col items-center">
<Show when={!loginState() && !notice().includes("Loading")}>
-
<form
-
class="flex flex-col items-center"
-
onsubmit={(e) => e.preventDefault()}
-
>
-
<label for="handle">Handle:</label>
+
<form class="flex flex-col" onsubmit={(e) => e.preventDefault()}>
+
<label for="handle" class="ml-0.5">
+
Handle
+
</label>
<input
type="text"
id="handle"
placeholder="user.bsky.social"
-
class="mb-3 mt-1 rounded-md border border-black px-2 py-1"
+
class="dark:bg-dark-100 mb-2 rounded-lg border border-gray-400 px-2 py-1 focus:outline-none focus:ring-1 focus:ring-gray-300"
onInput={(e) => setLoginInput(e.currentTarget.value)}
/>
<button
onclick={() => loginBsky(loginInput())}
-
class="rounded bg-blue-500 px-2 py-2 font-bold text-white hover:bg-blue-700"
+
class="rounded bg-blue-600 py-1.5 font-bold text-slate-100 hover:bg-blue-700"
>
Login
</button>
···
</Show>
<Show when={loginState() && handle()}>
<div class="mb-4">
-
Logged in as @{handle()} (
-
<a href="" class="text-red-600" onclick={() => logoutBsky()}>
+
Logged in as @{handle()}
+
<a href="" class="ml-2 text-red-500" onclick={() => logoutBsky()}>
Logout
</a>
-
)
</div>
</Show>
<Show when={notice()}>
···
<button
type="button"
onclick={() => fetchHiddenAccounts()}
-
class="rounded bg-blue-500 px-2 py-2 font-bold text-white hover:bg-blue-700"
+
class="rounded bg-blue-600 px-2 py-2 font-bold text-slate-100 hover:bg-blue-700"
>
Preview
</button>
···
<button
type="button"
onclick={() => unfollow()}
-
class="rounded bg-green-600 px-2 py-2 font-bold text-white hover:bg-green-700"
+
class="rounded bg-green-600 px-2 py-2 font-bold text-slate-100 hover:bg-green-700"
>
Confirm
</button>
···
return (
<div class="mt-6 flex flex-col sm:w-full sm:flex-row sm:justify-center">
-
<div class="sticky top-0 mb-3 mr-5 flex w-full flex-wrap justify-around border-b border-b-gray-400 bg-white pb-3 sm:top-3 sm:mb-0 sm:w-auto sm:flex-col sm:self-start sm:border-none">
+
<div class="dark:bg-dark-500 sticky top-0 mb-3 mr-5 flex w-full flex-wrap justify-around border-b border-b-gray-400 bg-slate-100 pb-3 sm:top-3 sm:mb-0 sm:w-auto sm:flex-col sm:self-start sm:border-none">
<For each={options}>
{(option, index) => (
<div
···
}
/>
<span class="peer relative h-5 w-9 rounded-full bg-gray-200 after:absolute after:start-[2px] after:top-[2px] after:h-4 after:w-4 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rtl:peer-checked:after:-translate-x-full dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800"></span>
-
<span class="ms-3 select-none dark:text-gray-300">
-
{option.label}
-
</span>
+
<span class="ms-3 select-none">{option.label}</span>
</label>
</div>
<div class="flex items-center">
···
<div
classList={{
"mb-1 flex items-center border-b py-1": true,
-
"bg-red-400": record.toDelete,
+
"bg-red-300 dark:bg-red-800": record.toDelete,
}}
>
<div class="mx-2">
···
};
const App: Component = () => {
+
const [theme, setTheme] = createSignal(
+
(
+
localStorage.theme === "dark" ||
+
(!("theme" in localStorage) &&
+
globalThis.matchMedia("(prefers-color-scheme: dark)").matches)
+
) ?
+
"dark"
+
: "light",
+
);
+
return (
-
<div class="m-5 flex flex-col items-center">
-
<h1 class="mb-2 text-xl font-bold">cleanfollow-bsky</h1>
-
<div class="mb-2 text-center">
-
<p>Select then unfollow inactive or blocked accounts</p>
-
<div>
-
<a
-
class="text-blue-600 hover:underline"
-
href="https://github.com/notjuliet/cleanfollow-bsky"
+
<div class="m-5 flex flex-col items-center text-slate-900 dark:text-slate-100">
+
<div class="mb-2 flex w-[20rem] items-center">
+
<div class="basis-1/3">
+
<div
+
class="w-fit cursor-pointer"
+
onclick={() => {
+
setTheme(theme() === "light" ? "dark" : "light");
+
if (theme() === "dark")
+
document.documentElement.classList.add("dark");
+
else document.documentElement.classList.remove("dark");
+
localStorage.theme = theme();
+
}}
>
-
Source Code
-
</a>
-
<span> | </span>
+
{theme() === "dark" ?
+
<TbMoonStar class="size-6" />
+
: <TbSun class="size-6" />}
+
</div>
+
</div>
+
<div class="basis-1/3 text-center text-xl font-bold">
+
<a href="">cleanfollow</a>
+
</div>
+
<div class="justify-right flex basis-1/3 gap-x-2">
<a
-
class="text-blue-600 hover:underline"
href="https://bsky.app/profile/did:plc:b3pn34agqqchkaf75v7h43dk"
+
target="_blank"
>
-
Bluesky
+
<Bluesky class="size-6" />
</a>
-
<span> | </span>
<a
-
class="text-blue-600 hover:underline"
-
href="https://mary-ext.codeberg.page/bluesky-quiet-posters/"
+
href="https://github.com/notjuliet/cleanfollow-bsky"
+
target="_blank"
>
-
Quiet Posters
+
<AiFillGithub class="size-6" />
</a>
</div>
+
</div>
+
<div class="mb-2 text-center">
+
<p>Select then unfollow inactive or blocked accounts</p>
+
<a
+
class="text-blue-500 hover:underline"
+
target="_blank"
+
href="https://mary-ext.codeberg.page/bluesky-quiet-posters/"
+
>
+
Quiet Posters
+
</a>
</div>
<Login />
<Show when={loginState()}>
+91
src/svg.tsx
···
+
import { Component } from "solid-js";
+
+
const AiFillGithub: Component<{ class?: string }> = (props) => {
+
return (
+
<div class={props.class}>
+
<svg
+
class="size-full"
+
fill="currentColor"
+
stroke-width="0"
+
xmlns="http://www.w3.org/2000/svg"
+
viewBox="0 0 16 16"
+
height="1em"
+
width="1em"
+
style="overflow: visible; color: currentcolor;"
+
>
+
<path
+
fill="currentColor"
+
d="M8 .198a8 8 0 0 0-2.529 15.591c.4.074.547-.174.547-.385 0-.191-.008-.821-.011-1.489-2.226.484-2.695-.944-2.695-.944-.364-.925-.888-1.171-.888-1.171-.726-.497.055-.486.055-.486.803.056 1.226.824 1.226.824.714 1.223 1.872.869 2.328.665.072-.517.279-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.954 0-.873.313-1.587.824-2.147-.083-.202-.357-1.015.077-2.117 0 0 .672-.215 2.201.82A7.672 7.672 0 0 1 8 4.066c.68.003 1.365.092 2.004.269 1.527-1.035 2.198-.82 2.198-.82.435 1.102.162 1.916.079 2.117.513.56.823 1.274.823 2.147 0 3.073-1.872 3.749-3.653 3.947.287.248.543.735.543 1.481 0 1.07-.009 1.932-.009 2.195 0 .213.144.462.55.384A8 8 0 0 0 8.001.196z"
+
></path>
+
</svg>
+
</div>
+
);
+
};
+
+
const Bluesky: Component<{ class?: string }> = (props) => {
+
return (
+
<div class={props.class}>
+
<svg
+
class="size-full"
+
width="1em"
+
height="1em"
+
viewBox="0 0 568 501"
+
fill="currentColor"
+
xmlns="http://www.w3.org/2000/svg"
+
>
+
<path d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 375.812 284.017 372.431 284 375.306C283.983 372.431 282.831 375.812 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z" />
+
</svg>
+
</div>
+
);
+
};
+
+
const TbMoonStar: Component<{ class?: string }> = (props) => {
+
return (
+
<div class={props.class}>
+
<svg
+
class="size-full"
+
fill="none"
+
stroke-width="2"
+
xmlns="http://www.w3.org/2000/svg"
+
width="1em"
+
height="1em"
+
viewBox="0 0 24 24"
+
stroke="currentColor"
+
stroke-linecap="round"
+
stroke-linejoin="round"
+
style="overflow: visible; color: currentcolor;"
+
>
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
+
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
+
<path d="M17 4a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2"></path>
+
<path d="M19 11h2m-1 -1v2"></path>
+
</svg>
+
</div>
+
);
+
};
+
+
const TbSun: Component<{ class?: string }> = (props) => {
+
return (
+
<div class={props.class}>
+
<svg
+
class="size-full"
+
fill="none"
+
stroke-width="2"
+
xmlns="http://www.w3.org/2000/svg"
+
width="1em"
+
height="1em"
+
viewBox="0 0 24 24"
+
stroke="currentColor"
+
stroke-linecap="round"
+
stroke-linejoin="round"
+
style="overflow: visible; color: currentcolor;"
+
>
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
+
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path>
+
<path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7"></path>
+
</svg>
+
</div>
+
);
+
};
+
+
export { AiFillGithub, Bluesky, TbMoonStar, TbSun };