···
1
-
import { createSignal, For, Show, type Component } from "solid-js";
1
+
import { createSignal, onMount, For, Show, type Component } from "solid-js";
import { createStore } from "solid-js/store";
import { Agent } from "@atproto/api";
···
const [followRecords, setFollowRecords] = createStore<FollowRecord[]>([]);
28
-
const [loginState, setLoginState] = createSignal<boolean>();
29
-
const [notice, setNotice] = createSignal("");
31
-
const client = await BrowserOAuthClient.load({
32
-
clientId: "https://cleanfollow-bsky.pages.dev/client-metadata.json",
33
-
handleResolver: "https://boletus.us-west.host.bsky.network",
36
-
client.addEventListener("deleted", () => {
37
-
setLoginState(false);
28
+
const [loginState, setLoginState] = createSignal(false);
41
-
let userHandle: string;
const resolveDid = async (did: string) => {
···
59
-
const result = await client.init().catch(() => {});
47
+
const Login: Component = () => {
48
+
const [loginInput, setLoginInput] = createSignal("");
49
+
const [handle, setHandle] = createSignal("");
50
+
const [notice, setNotice] = createSignal("");
51
+
let client: BrowserOAuthClient;
62
-
agent = new Agent(result.session);
63
-
setLoginState(true);
64
-
userHandle = await resolveDid(agent.did!);
54
+
onMount(async () => {
55
+
setNotice("Loading...");
56
+
//clientId: "https://cleanfollow-bsky.pages.dev/client-metadata.json",
57
+
client = new BrowserOAuthClient({
58
+
clientMetadata: undefined,
59
+
handleResolver: "https://boletus.us-west.host.bsky.network",
67
-
const loginBsky = async (handle: string) => {
68
-
setNotice("Redirecting...");
70
-
await client.signIn(handle, {
71
-
scope: "atproto transition:generic",
72
-
signal: new AbortController().signal,
62
+
client.addEventListener("deleted", () => {
63
+
setLoginState(false);
75
-
setNotice("Error during OAuth redirection");
65
+
const result = await client.init().catch(() => {});
79
-
const logoutBsky = async () => {
80
-
if (result) await client.revoke(result.session.sub);
68
+
agent = new Agent(result.session);
69
+
setLoginState(true);
70
+
setHandle(await resolveDid(agent.did!));
71
+
sub = result.session.sub;
83
-
const Follows: Component = () => {
84
-
function editRecords(
86
-
field: keyof FollowRecord,
89
-
followRecords.forEach((record, index) => {
90
-
if (record.status & status) setFollowRecords(index, field, value);
76
+
const loginBsky = async (handle: string) => {
77
+
setNotice("Redirecting...");
79
+
await client.signIn(handle, {
80
+
scope: "atproto transition:generic",
81
+
signal: new AbortController().signal,
84
+
setNotice("Error during OAuth redirection");
94
-
const options: { status: RepoStatus; label: string }[] = [
95
-
{ status: RepoStatus.DELETED, label: "Deleted" },
96
-
{ status: RepoStatus.DEACTIVATED, label: "Deactivated" },
97
-
{ status: RepoStatus.SUSPENDED, label: "Suspended" },
98
-
{ status: RepoStatus.BLOCKEDBY, label: "Blocked By" },
99
-
{ status: RepoStatus.BLOCKING, label: "Blocking" },
100
-
{ status: RepoStatus.NONMUTUAL, label: "Non Mutual" },
88
+
const logoutBsky = async () => {
89
+
if (sub) await client.revoke(sub);
104
-
<div class="mt-3 flex flex-col sm:flex-row">
105
-
<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">
106
-
<For each={options}>
107
-
{(option, index) => (
110
-
"sm:pb-2 min-w-36 sm:mb-2 mt-3 sm:mt-0": true,
111
-
"sm:border-b sm:border-b-gray-300":
112
-
index() < options.length - 1,
116
-
<label class="mb-2 mt-1 inline-flex cursor-pointer items-center">
119
-
class="peer sr-only"
125
-
e.currentTarget.checked,
129
-
<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 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800 rtl:peer-checked:after:-translate-x-full"></span>
130
-
<span class="ms-3 select-none dark:text-gray-300">
135
-
<div class="flex items-center">
139
-
class="h-4 w-4 rounded"
144
-
e.currentTarget.checked,
148
-
<label for={option.label} class="ml-2 select-none">
157
-
<For each={followRecords}>
158
-
{(record, index) => (
159
-
<Show when={record.visible}>
160
-
<div class="mb-2 flex items-center border-b pb-2">
164
-
id={"record" + index()}
165
-
class="h-4 w-4 rounded"
166
-
checked={record.toBeDeleted}
171
-
e.currentTarget.checked,
177
-
<label for={"record" + index()} class="flex flex-col">
178
-
<span>@{record.handle}</span>
179
-
<span>{record.did}</span>
180
-
<span>{record.status_label}</span>
93
+
<div class="flex flex-col items-center">
94
+
<Show when={!loginState() && !notice().includes("Loading")}>
96
+
class="flex flex-col items-center"
97
+
onsubmit={(e) => e.preventDefault()}
99
+
<label for="handle">Handle:</label>
103
+
placeholder="user.bsky.social"
104
+
class="mb-3 mt-1 rounded-md px-2 py-1"
105
+
onInput={(e) => setLoginInput(e.currentTarget.value)}
108
+
onclick={() => loginBsky(loginInput())}
109
+
class="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
115
+
<Show when={loginState() && handle()}>
117
+
Logged in as {handle()} (
118
+
<a href="" class="text-red-600" onclick={() => logoutBsky()}>
124
+
<Show when={notice()}>
125
+
<div class="m-3">{notice()}</div>
192
-
const Form: Component = () => {
193
-
const [loginInput, setLoginInput] = createSignal("");
131
+
const Fetch: Component = () => {
const [progress, setProgress] = createSignal(0);
const [followCount, setFollowCount] = createSignal(0);
134
+
const [notice, setNotice] = createSignal("");
const fetchHiddenAccounts = async () => {
const fetchFollows = async () => {
···
await fetchFollows().then((follows) => {
setFollowCount(follows.length);
···
<div class="flex flex-col items-center">
315
-
<Show when={!loginState()}>
317
-
class="flex flex-col items-center"
318
-
onsubmit={(e) => e.preventDefault()}
254
+
<Show when={!followRecords.length}>
257
+
onclick={() => fetchHiddenAccounts()}
258
+
class="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
320
-
<label for="handle">Handle:</label>
324
-
placeholder="user.bsky.social"
325
-
class="mb-3 mt-1 rounded-md px-2 py-1"
326
-
onInput={(e) => setLoginInput(e.currentTarget.value)}
329
-
onclick={() => loginBsky(loginInput())}
330
-
class="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
336
-
<Show when={loginState()}>
338
-
Logged in as {userHandle} (
339
-
<a href="" class="text-red-600" onclick={() => logoutBsky()}>
344
-
<Show when={!followRecords.length}>
347
-
onclick={() => fetchHiddenAccounts()}
348
-
class="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
353
-
<Show when={followRecords.length}>
356
-
onclick={() => unfollow()}
357
-
class="rounded bg-green-500 px-4 py-2 font-bold text-white hover:bg-green-700"
263
+
<Show when={followRecords.length}>
266
+
onclick={() => unfollow()}
267
+
class="rounded bg-green-500 px-4 py-2 font-bold text-white hover:bg-green-700"
<div class="m-3">{notice()}</div>
366
-
<Show when={loginState() && followCount()}>
275
+
<Show when={followCount()}>
Progress: {progress()}/{followCount()}
···
284
+
const Follows: Component = () => {
285
+
function editRecords(
286
+
status: RepoStatus,
287
+
field: keyof FollowRecord,
290
+
followRecords.forEach((record, index) => {
291
+
if (record.status & status) setFollowRecords(index, field, value);
295
+
const options: { status: RepoStatus; label: string }[] = [
296
+
{ status: RepoStatus.DELETED, label: "Deleted" },
297
+
{ status: RepoStatus.DEACTIVATED, label: "Deactivated" },
298
+
{ status: RepoStatus.SUSPENDED, label: "Suspended" },
299
+
{ status: RepoStatus.BLOCKEDBY, label: "Blocked By" },
300
+
{ status: RepoStatus.BLOCKING, label: "Blocking" },
301
+
{ status: RepoStatus.NONMUTUAL, label: "Non Mutual" },
305
+
<div class="mt-3 flex flex-col sm:flex-row">
306
+
<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">
307
+
<For each={options}>
308
+
{(option, index) => (
311
+
"sm:pb-2 min-w-36 sm:mb-2 mt-3 sm:mt-0": true,
312
+
"sm:border-b sm:border-b-gray-300":
313
+
index() < options.length - 1,
317
+
<label class="mb-2 mt-1 inline-flex cursor-pointer items-center">
320
+
class="peer sr-only"
326
+
e.currentTarget.checked,
330
+
<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 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800 rtl:peer-checked:after:-translate-x-full"></span>
331
+
<span class="ms-3 select-none dark:text-gray-300">
336
+
<div class="flex items-center">
340
+
class="h-4 w-4 rounded"
345
+
e.currentTarget.checked,
349
+
<label for={option.label} class="ml-2 select-none">
358
+
<For each={followRecords}>
359
+
{(record, index) => (
360
+
<Show when={record.visible}>
361
+
<div class="mb-2 flex items-center border-b pb-2">
365
+
id={"record" + index()}
366
+
class="h-4 w-4 rounded"
367
+
checked={record.toBeDeleted}
372
+
e.currentTarget.checked,
378
+
<label for={"record" + index()} class="flex flex-col">
379
+
<span>@{record.handle}</span>
380
+
<span>{record.did}</span>
381
+
<span>{record.status_label}</span>
const App: Component = () => {
<div class="m-5 flex flex-col items-center">
···
406
-
<Show when={loginState() && followRecords.length}>
424
+
<Show when={loginState()}>
426
+
<Show when={followRecords.length}>