+29
-19
index.html
+29
-19
index.html
···
1
-
<!DOCTYPE html>
1
+
<!doctype html>
2
2
<html lang="en">
3
-
4
-
<head>
5
-
<meta charset="utf-8" />
6
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
-
<meta name="theme-color" content="#000000" />
8
-
<meta property="og:title" content="cleanfollow-bsky" />
9
-
<meta property="og:type" content="website" />
10
-
<meta property="og:url" content="https://cleanfollow-bsky.pages.dev" />
11
-
<meta property="og:description" content="Unfollow blocked, deleted, suspended, and deactivated Bluesky accounts" />
12
-
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
13
-
<title>cleanfollow-bsky</title>
14
-
</head>
15
-
16
-
<body>
17
-
<noscript>You need to enable JavaScript to run this app.</noscript>
18
-
<div id="root"></div>
3
+
<head>
4
+
<meta charset="utf-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6
+
<meta name="theme-color" content="#000000" />
7
+
<meta property="og:title" content="cleanfollow-bsky" />
8
+
<meta property="og:type" content="website" />
9
+
<meta property="og:url" content="https://cleanfollow-bsky.pages.dev" />
10
+
<meta
11
+
property="og:description"
12
+
content="Unfollow blocked, deleted, suspended, and deactivated Bluesky accounts"
13
+
/>
14
+
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
15
+
<title>cleanfollow-bsky</title>
16
+
<script>
17
+
if (
18
+
localStorage.theme === "dark" ||
19
+
(!("theme" in localStorage) &&
20
+
window.matchMedia("(prefers-color-scheme: dark)").matches)
21
+
)
22
+
document.documentElement.classList.add("dark");
23
+
else document.documentElement.classList.remove("dark");
24
+
</script>
25
+
</head>
19
26
20
-
<script src="/src/index.tsx" type="module"></script>
21
-
</body>
27
+
<body id="root" class="dark:bg-dark-500 bg-slate-100">
28
+
<noscript>You need to enable JavaScript to run this app.</noscript>
29
+
<div id="root"></div>
22
30
31
+
<script src="/src/index.tsx" type="module"></script>
32
+
</body>
23
33
</html>
+60
-34
src/App.tsx
+60
-34
src/App.tsx
···
24
24
resolveFromIdentity,
25
25
type Session,
26
26
} from "@atcute/oauth-browser-client";
27
+
import { AiFillGithub, Bluesky, TbMoonStar, TbSun } from "./svg";
27
28
28
29
configureOAuth({
29
30
metadata: {
···
148
149
return (
149
150
<div class="flex flex-col items-center">
150
151
<Show when={!loginState() && !notice().includes("Loading")}>
151
-
<form
152
-
class="flex flex-col items-center"
153
-
onsubmit={(e) => e.preventDefault()}
154
-
>
155
-
<label for="handle">Handle:</label>
152
+
<form class="flex flex-col" onsubmit={(e) => e.preventDefault()}>
153
+
<label for="handle" class="ml-0.5">
154
+
Handle
155
+
</label>
156
156
<input
157
157
type="text"
158
158
id="handle"
159
159
placeholder="user.bsky.social"
160
-
class="mb-3 mt-1 rounded-md border border-black px-2 py-1"
160
+
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"
161
161
onInput={(e) => setLoginInput(e.currentTarget.value)}
162
162
/>
163
163
<button
164
164
onclick={() => loginBsky(loginInput())}
165
-
class="rounded bg-blue-500 px-2 py-2 font-bold text-white hover:bg-blue-700"
165
+
class="rounded bg-blue-600 py-1.5 font-bold text-slate-100 hover:bg-blue-700"
166
166
>
167
167
Login
168
168
</button>
···
170
170
</Show>
171
171
<Show when={loginState() && handle()}>
172
172
<div class="mb-4">
173
-
Logged in as @{handle()} (
174
-
<a href="" class="text-red-600" onclick={() => logoutBsky()}>
173
+
Logged in as @{handle()}
174
+
<a href="" class="ml-2 text-red-500" onclick={() => logoutBsky()}>
175
175
Logout
176
176
</a>
177
-
)
178
177
</div>
179
178
</Show>
180
179
<Show when={notice()}>
···
315
314
<button
316
315
type="button"
317
316
onclick={() => fetchHiddenAccounts()}
318
-
class="rounded bg-blue-500 px-2 py-2 font-bold text-white hover:bg-blue-700"
317
+
class="rounded bg-blue-600 px-2 py-2 font-bold text-slate-100 hover:bg-blue-700"
319
318
>
320
319
Preview
321
320
</button>
···
324
323
<button
325
324
type="button"
326
325
onclick={() => unfollow()}
327
-
class="rounded bg-green-600 px-2 py-2 font-bold text-white hover:bg-green-700"
326
+
class="rounded bg-green-600 px-2 py-2 font-bold text-slate-100 hover:bg-green-700"
328
327
>
329
328
Confirm
330
329
</button>
···
371
370
372
371
return (
373
372
<div class="mt-6 flex flex-col sm:w-full sm:flex-row sm:justify-center">
374
-
<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">
373
+
<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">
375
374
<For each={options}>
376
375
{(option, index) => (
377
376
<div
···
396
395
}
397
396
/>
398
397
<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>
399
-
<span class="ms-3 select-none dark:text-gray-300">
400
-
{option.label}
401
-
</span>
398
+
<span class="ms-3 select-none">{option.label}</span>
402
399
</label>
403
400
</div>
404
401
<div class="flex items-center">
···
434
431
<div
435
432
classList={{
436
433
"mb-1 flex items-center border-b py-1": true,
437
-
"bg-red-400": record.toDelete,
434
+
"bg-red-300 dark:bg-red-800": record.toDelete,
438
435
}}
439
436
>
440
437
<div class="mx-2">
···
471
468
};
472
469
473
470
const App: Component = () => {
471
+
const [theme, setTheme] = createSignal(
472
+
(
473
+
localStorage.theme === "dark" ||
474
+
(!("theme" in localStorage) &&
475
+
globalThis.matchMedia("(prefers-color-scheme: dark)").matches)
476
+
) ?
477
+
"dark"
478
+
: "light",
479
+
);
480
+
474
481
return (
475
-
<div class="m-5 flex flex-col items-center">
476
-
<h1 class="mb-2 text-xl font-bold">cleanfollow-bsky</h1>
477
-
<div class="mb-2 text-center">
478
-
<p>Select then unfollow inactive or blocked accounts</p>
479
-
<div>
480
-
<a
481
-
class="text-blue-600 hover:underline"
482
-
href="https://github.com/notjuliet/cleanfollow-bsky"
482
+
<div class="m-5 flex flex-col items-center text-slate-900 dark:text-slate-100">
483
+
<div class="mb-2 flex w-[20rem] items-center">
484
+
<div class="basis-1/3">
485
+
<div
486
+
class="w-fit cursor-pointer"
487
+
onclick={() => {
488
+
setTheme(theme() === "light" ? "dark" : "light");
489
+
if (theme() === "dark")
490
+
document.documentElement.classList.add("dark");
491
+
else document.documentElement.classList.remove("dark");
492
+
localStorage.theme = theme();
493
+
}}
483
494
>
484
-
Source Code
485
-
</a>
486
-
<span> | </span>
495
+
{theme() === "dark" ?
496
+
<TbMoonStar class="size-6" />
497
+
: <TbSun class="size-6" />}
498
+
</div>
499
+
</div>
500
+
<div class="basis-1/3 text-center text-xl font-bold">
501
+
<a href="">cleanfollow</a>
502
+
</div>
503
+
<div class="justify-right flex basis-1/3 gap-x-2">
487
504
<a
488
-
class="text-blue-600 hover:underline"
489
505
href="https://bsky.app/profile/did:plc:b3pn34agqqchkaf75v7h43dk"
506
+
target="_blank"
490
507
>
491
-
Bluesky
508
+
<Bluesky class="size-6" />
492
509
</a>
493
-
<span> | </span>
494
510
<a
495
-
class="text-blue-600 hover:underline"
496
-
href="https://mary-ext.codeberg.page/bluesky-quiet-posters/"
511
+
href="https://github.com/notjuliet/cleanfollow-bsky"
512
+
target="_blank"
497
513
>
498
-
Quiet Posters
514
+
<AiFillGithub class="size-6" />
499
515
</a>
500
516
</div>
517
+
</div>
518
+
<div class="mb-2 text-center">
519
+
<p>Select then unfollow inactive or blocked accounts</p>
520
+
<a
521
+
class="text-blue-500 hover:underline"
522
+
target="_blank"
523
+
href="https://mary-ext.codeberg.page/bluesky-quiet-posters/"
524
+
>
525
+
Quiet Posters
526
+
</a>
501
527
</div>
502
528
<Login />
503
529
<Show when={loginState()}>
+91
src/svg.tsx
+91
src/svg.tsx
···
1
+
import { Component } from "solid-js";
2
+
3
+
const AiFillGithub: Component<{ class?: string }> = (props) => {
4
+
return (
5
+
<div class={props.class}>
6
+
<svg
7
+
class="size-full"
8
+
fill="currentColor"
9
+
stroke-width="0"
10
+
xmlns="http://www.w3.org/2000/svg"
11
+
viewBox="0 0 16 16"
12
+
height="1em"
13
+
width="1em"
14
+
style="overflow: visible; color: currentcolor;"
15
+
>
16
+
<path
17
+
fill="currentColor"
18
+
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"
19
+
></path>
20
+
</svg>
21
+
</div>
22
+
);
23
+
};
24
+
25
+
const Bluesky: Component<{ class?: string }> = (props) => {
26
+
return (
27
+
<div class={props.class}>
28
+
<svg
29
+
class="size-full"
30
+
width="1em"
31
+
height="1em"
32
+
viewBox="0 0 568 501"
33
+
fill="currentColor"
34
+
xmlns="http://www.w3.org/2000/svg"
35
+
>
36
+
<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" />
37
+
</svg>
38
+
</div>
39
+
);
40
+
};
41
+
42
+
const TbMoonStar: Component<{ class?: string }> = (props) => {
43
+
return (
44
+
<div class={props.class}>
45
+
<svg
46
+
class="size-full"
47
+
fill="none"
48
+
stroke-width="2"
49
+
xmlns="http://www.w3.org/2000/svg"
50
+
width="1em"
51
+
height="1em"
52
+
viewBox="0 0 24 24"
53
+
stroke="currentColor"
54
+
stroke-linecap="round"
55
+
stroke-linejoin="round"
56
+
style="overflow: visible; color: currentcolor;"
57
+
>
58
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
59
+
<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>
60
+
<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>
61
+
<path d="M19 11h2m-1 -1v2"></path>
62
+
</svg>
63
+
</div>
64
+
);
65
+
};
66
+
67
+
const TbSun: Component<{ class?: string }> = (props) => {
68
+
return (
69
+
<div class={props.class}>
70
+
<svg
71
+
class="size-full"
72
+
fill="none"
73
+
stroke-width="2"
74
+
xmlns="http://www.w3.org/2000/svg"
75
+
width="1em"
76
+
height="1em"
77
+
viewBox="0 0 24 24"
78
+
stroke="currentColor"
79
+
stroke-linecap="round"
80
+
stroke-linejoin="round"
81
+
style="overflow: visible; color: currentcolor;"
82
+
>
83
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
84
+
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"></path>
85
+
<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>
86
+
</svg>
87
+
</div>
88
+
);
89
+
};
90
+
91
+
export { AiFillGithub, Bluesky, TbMoonStar, TbSun };