Unfollow tool for Bluesky

add toggles and non mutuals

Changed files
+138 -113
src
+22 -22
package-lock.json
···
"dev": true
},
"node_modules/ansi-regex": {
-
"version": "6.0.1",
-
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+
"version": "6.1.0",
+
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": {
"node": ">=12"
···
},
"node_modules/caniuse-lite": {
-
"version": "1.0.30001657",
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001657.tgz",
-
"integrity": "sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA==",
+
"version": "1.0.30001660",
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
+
"integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
"dev": true,
"funding": [
···
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/debug": {
-
"version": "4.3.6",
-
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
-
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+
"version": "4.3.7",
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"dependencies": {
-
"ms": "2.1.2"
+
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
···
"dev": true
},
"node_modules/electron-to-chromium": {
-
"version": "1.5.14",
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.14.tgz",
-
"integrity": "sha512-bEfPECb3fJ15eaDnu9LEJ2vPGD6W1vt7vZleSVyFhYuMIKm3vz/g9lt7IvEzgdwj58RjbPKUF2rXTCN/UW47tQ==",
+
"version": "1.5.19",
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
+
"integrity": "sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w==",
"dev": true
},
"node_modules/emoji-regex": {
···
},
"node_modules/ms": {
-
"version": "2.1.2",
-
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+
"version": "2.1.3",
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/multiformats": {
···
},
"node_modules/source-map-js": {
-
"version": "1.2.0",
-
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
-
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+
"version": "1.2.1",
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
···
"dev": true
},
"node_modules/typescript": {
-
"version": "5.5.4",
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
-
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+
"version": "5.6.2",
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
+
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
+116 -91
src/App.tsx
···
DEACTIVATED = 1 << 3,
SUSPENDED = 1 << 4,
YOURSELF = 1 << 5,
+
NONMUTUAL = 1 << 6,
}
type FollowRecord = {
···
uri: string;
status: RepoStatus;
toBeDeleted: boolean;
+
visible: boolean;
};
const [followRecords, setFollowRecords] = createStore<FollowRecord[]>([]);
···
});
}
-
const options: Record<string, { status: RepoStatus; label: string }> = {
-
deleted: {
-
status: RepoStatus.DELETED,
-
label: "Deleted",
-
},
-
deactivated: {
-
status: RepoStatus.DEACTIVATED,
-
label: "Deactivated",
-
},
-
suspended: {
-
status: RepoStatus.SUSPENDED,
-
label: "Suspended",
-
},
-
blockedby: {
-
status: RepoStatus.BLOCKEDBY,
-
label: "Blocked By",
-
},
-
blocking: {
-
status: RepoStatus.BLOCKING,
-
label: "Blocking",
-
},
-
};
+
function changeVisibility(status: RepoStatus, visible: boolean) {
+
followRecords.forEach((record, index) => {
+
if (record.status & status) setFollowRecords(index, "visible", visible);
+
});
+
}
+
+
const options: { status: RepoStatus; label: string }[] = [
+
{ status: RepoStatus.DELETED, label: "Deleted" },
+
{ status: RepoStatus.DEACTIVATED, label: "Deactivated" },
+
{ status: RepoStatus.SUSPENDED, label: "Suspended" },
+
{ status: RepoStatus.BLOCKEDBY, label: "Blocked By" },
+
{ status: RepoStatus.BLOCKING, label: "Blocking" },
+
{ status: RepoStatus.NONMUTUAL, label: "Non Mutual" },
+
];
return (
-
<div class="mt-3">
-
<Show when={followRecords.length}>
-
<div class="flex flex-row flex-wrap gap-x-5 gap-y-2">
-
<For each={Object.entries(options)}>
-
{(option) => (
-
<div class="flex h-6 items-center">
+
<div class="mt-3 flex flex-col sm:flex-row">
+
<div class="mr-5 mb-3 pb-3 justify-around sm:mb-0 sm:top-3 top-0 border-b border-b-gray-400 sm:border-none w-full bg-white sm:w-auto sticky sm:self-start flex flex-wrap sm:flex-col">
+
<For each={options}>
+
{(option, index) => (
+
<div
+
classList={{
+
"sm:pb-2 min-w-36 sm:mb-2 mt-3 sm:mt-0": true,
+
"sm:border-b sm:border-b-gray-300":
+
index() < options.length - 1,
+
}}
+
>
+
<div>
+
<label class="inline-flex items-center mt-1 mb-2 cursor-pointer">
+
<input
+
type="checkbox"
+
class="sr-only peer"
+
checked
+
onChange={(e) =>
+
changeVisibility(option.status, e.currentTarget.checked)
+
}
+
/>
+
<span class="relative w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></span>
+
<span class="ms-3 dark:text-gray-300 select-none">
+
{option.label}
+
</span>
+
</label>
+
</div>
+
<div class="flex items-center">
<input
type="checkbox"
-
id={option[0]}
-
class="h-4 w-4 rounded border-gray-400 text-indigo-600 focus:ring-indigo-600"
+
id={option.label}
+
class="h-4 w-4 rounded"
onChange={(e) =>
-
selectRecords(option[1].status, e.currentTarget.checked)
+
selectRecords(option.status, e.currentTarget.checked)
}
/>
-
<label for={option[0]} class="ml-2 select-none">
-
{option[1].label}
+
<label for={option.label} class="ml-2 select-none">
+
Select All
</label>
</div>
-
)}
-
</For>
-
</div>
-
</Show>
-
<div class="mt-5">
+
</div>
+
)}
+
</For>
+
</div>
+
<div>
<For each={followRecords}>
{(record, index) => (
-
<div class="flex flex-row items-center border-b mb-2 pb-2">
-
<div class="mr-4">
-
<input
-
type="checkbox"
-
id={"record" + index()}
-
class="h-4 w-4 rounded border-gray-400 text-indigo-600 focus:ring-indigo-600"
-
checked={record.toBeDeleted}
-
onChange={(e) =>
-
setFollowRecords(
-
index(),
-
"toBeDeleted",
-
e.currentTarget.checked,
-
)
-
}
-
/>
+
<Show when={record.visible}>
+
<div class="flex items-center border-b mb-2 pb-2">
+
<div class="mr-4">
+
<input
+
type="checkbox"
+
id={"record" + index()}
+
class="h-4 w-4 rounded border-gray-400 text-indigo-600 focus:ring-indigo-600"
+
checked={record.toBeDeleted}
+
onChange={(e) =>
+
setFollowRecords(
+
index(),
+
"toBeDeleted",
+
e.currentTarget.checked,
+
)
+
}
+
/>
+
</div>
+
<div>
+
<label for={"record" + index()} class="flex flex-col">
+
<span>@{record.handle}</span>
+
<span>{record.did}</span>
+
<span>
+
<Switch>
+
<Match
+
when={
+
record.status ==
+
(RepoStatus.BLOCKEDBY | RepoStatus.BLOCKING)
+
}
+
>
+
Mutual Block
+
</Match>
+
<Match when={record.status == RepoStatus.DELETED}>
+
Deleted
+
</Match>
+
<Match when={record.status == RepoStatus.DEACTIVATED}>
+
Deactivated
+
</Match>
+
<Match when={record.status == RepoStatus.BLOCKEDBY}>
+
Blocked by
+
</Match>
+
<Match when={record.status == RepoStatus.BLOCKING}>
+
Blocking
+
</Match>
+
<Match when={record.status == RepoStatus.SUSPENDED}>
+
Suspended
+
</Match>
+
<Match when={record.status == RepoStatus.YOURSELF}>
+
Literally Yourself
+
</Match>
+
<Match when={record.status == RepoStatus.NONMUTUAL}>
+
Non Mutual
+
</Match>
+
</Switch>
+
</span>
+
</label>
+
</div>
</div>
-
<div>
-
<label for={"record" + index()} class="flex flex-col">
-
<span>@{record.handle}</span>
-
<span>{record.did}</span>
-
<span>
-
<Switch>
-
<Match
-
when={
-
record.status ==
-
(RepoStatus.BLOCKEDBY | RepoStatus.BLOCKING)
-
}
-
>
-
Mutual Block
-
</Match>
-
<Match when={record.status == RepoStatus.DELETED}>
-
Deleted
-
</Match>
-
<Match when={record.status == RepoStatus.DEACTIVATED}>
-
Deactivated
-
</Match>
-
<Match when={record.status == RepoStatus.BLOCKEDBY}>
-
Blocked by
-
</Match>
-
<Match when={record.status == RepoStatus.BLOCKING}>
-
Blocking
-
</Match>
-
<Match when={record.status == RepoStatus.SUSPENDED}>
-
Suspended
-
</Match>
-
<Match when={record.status == RepoStatus.YOURSELF}>
-
Literally Yourself
-
</Match>
-
</Switch>
-
</span>
-
</label>
-
</div>
-
</div>
+
</Show>
)}
</For>
</div>
···
const viewer = res.data.viewer!;
let status: RepoStatus | undefined = undefined;
+
+
if (!viewer.followedBy) status = RepoStatus.NONMUTUAL;
if (viewer.blockedBy) {
status =
···
uri: record.uri,
status: status,
toBeDeleted: false,
+
visible: true,
});
}
} catch (e: any) {
···
uri: record.uri,
status: status,
toBeDeleted: false,
+
visible: true,
});
}
}
···
</div>
</div>
<Form />
-
<Show when={loginState()}>
+
<Show when={loginState() && followRecords.length}>
<Follows />
</Show>
</div>