feat: Add multi-account support, more options for posts, UI improvements

This commit is contained in:
Jesse Wierzbinski 2024-06-09 17:24:55 -10:00
parent 48954baf06
commit ef9a6f1da4
No known key found for this signature in database
36 changed files with 649 additions and 344 deletions

View file

@ -1,28 +1,27 @@
import type { LysandClient } from "@lysand-org/client";
import { type RolePermissions, useCurrentIdentity } from "./Identities";
export const useCacheRefresh = (client: MaybeRef<LysandClient | null>) => {
if (process.server) return;
const tokenData = useTokenData();
const me = useMe();
const identity = useCurrentIdentity();
const instance = useInstance();
const customEmojis = useCustomEmojis();
// Refresh custom emojis and instance data and me on every reload
watchEffect(async () => {
console.log("Clearing cache");
if (tokenData.value) {
await toValue(client)
console.info("Refreshing emoji, instance and account cache");
if (identity.value) {
toValue(client)
?.verifyAccountCredentials()
.then((res) => {
me.value = res.data;
if (identity.value) identity.value.account = res.data;
})
.catch((err) => {
const code = err.response.status;
if (code === 401) {
// Reset tokenData
tokenData.value = null;
identity.value = null;
useEvent("notification:new", {
type: "error",
title: "Your session has expired",
@ -32,10 +31,25 @@ export const useCacheRefresh = (client: MaybeRef<LysandClient | null>) => {
}
});
await toValue(client)
toValue(client)
?.getInstanceCustomEmojis()
.then((res) => {
customEmojis.value = res.data;
if (identity.value) identity.value.emojis = res.data;
});
toValue(client)
?.getRoles()
.then((res) => {
const roles = res.data;
// Get all permissions and deduplicate
const permissions = roles
.flatMap((r) => r.permissions)
.filter((p, i, arr) => arr.indexOf(p) === i);
if (identity.value)
identity.value.permissions =
permissions as unknown as RolePermissions[];
});
}

View file

@ -1,6 +0,0 @@
import type { Emoji } from "~/types/mastodon/emoji";
export const useCustomEmojis = () => {
// Cache in localStorage
return useLocalStorage<Emoji[]>("lysand:custom_emojis", []);
};

View file

@ -1,6 +1,7 @@
import mitt from "mitt";
import type { Attachment } from "~/types/mastodon/attachment";
import type { Status } from "~/types/mastodon/status";
import type { Identity } from "./Identities";
export type NotificationEvent = {
type: "error" | "success" | "progress";
@ -19,6 +20,7 @@ type ApplicationEvents = {
"note:reblog": Status;
"note:unreblog": Status;
"note:quote": Status;
"note:report": Status;
"composer:open": undefined;
"composer:reply": Status;
"composer:quote": Status;
@ -28,6 +30,7 @@ type ApplicationEvents = {
"composer:close": undefined;
"notification:new": NotificationEvent;
"attachment:view": Attachment;
"identity:change": Identity;
};
const emitter = mitt<ApplicationEvents>();

119
composables/Identities.ts Normal file
View file

@ -0,0 +1,119 @@
import type { LysandClient, Token } from "@lysand-org/client";
import { StorageSerializers } from "@vueuse/core";
import type { Account } from "~/types/mastodon/account";
import type { Instance } from "./Instance";
export type Role = Awaited<ReturnType<LysandClient["getRole"]>>["data"];
export enum RolePermissions {
MANAGE_NOTES = "notes",
MANAGE_OWN_NOTES = "owner:note",
VIEW_NOTES = "read:note",
VIEW_NOTE_LIKES = "read:note_likes",
VIEW_NOTE_BOOSTS = "read:note_boosts",
MANAGE_ACCOUNTS = "accounts",
MANAGE_OWN_ACCOUNT = "owner:account",
VIEW_ACCOUNT_FOLLOWS = "read:account_follows",
MANAGE_LIKES = "likes",
MANAGE_OWN_LIKES = "owner:like",
MANAGE_BOOSTS = "boosts",
MANAGE_OWN_BOOSTS = "owner:boost",
VIEW_ACCOUNTS = "read:account",
MANAGE_EMOJIS = "emojis",
VIEW_EMOJIS = "read:emoji",
MANAGE_OWN_EMOJIS = "owner:emoji",
MANAGE_MEDIA = "media",
MANAGE_OWN_MEDIA = "owner:media",
MANAGE_BLOCKS = "blocks",
MANAGE_OWN_BLOCKS = "owner:block",
MANAGE_FILTERS = "filters",
MANAGE_OWN_FILTERS = "owner:filter",
MANAGE_MUTES = "mutes",
MANAGE_OWN_MUTES = "owner:mute",
MANAGE_REPORTS = "reports",
MANAGE_OWN_REPORTS = "owner:report",
MANAGE_SETTINGS = "settings",
MANAGE_OWN_SETTINGS = "owner:settings",
MANAGE_ROLES = "roles",
MANAGE_NOTIFICATIONS = "notifications",
MANAGE_OWN_NOTIFICATIONS = "owner:notification",
MANAGE_FOLLOWS = "follows",
MANAGE_OWN_FOLLOWS = "owner:follow",
MANAGE_OWN_APPS = "owner:app",
SEARCH = "search",
VIEW_PUBLIC_TIMELINES = "public_timelines",
VIEW_PRIVATE_TIMELINES = "private_timelines",
IGNORE_RATE_LIMITS = "ignore_rate_limits",
IMPERSONATE = "impersonate",
MANAGE_INSTANCE = "instance",
MANAGE_INSTANCE_FEDERATION = "instance:federation",
MANAGE_INSTANCE_SETTINGS = "instance:settings",
OAUTH = "oauth",
}
export type CustomEmoji = Awaited<
ReturnType<LysandClient["getInstanceCustomEmojis"]>
>["data"][0];
export type Identity = {
id: string;
tokens: Token;
account: Account;
instance: Instance;
permissions: RolePermissions[];
emojis: CustomEmoji[];
};
export const useIdentities = (): Ref<Identity[]> => {
return useLocalStorage<Identity[]>("lysand:identities", [], {
serializer: StorageSerializers.object,
});
};
export const useCurrentIdentity = (): Ref<Identity | null> => {
const currentId = useLocalStorage<string | null>(
"lysand:identities:current",
null,
);
const identities = useIdentities();
const current = ref(
identities.value.find((i) => i.id === currentId.value) ?? null,
);
watch(identities, (ids) => {
if (ids.length === 0) {
current.value = null;
}
});
watch(
current,
(newCurrent) => {
if (newCurrent) {
currentId.value = newCurrent.id;
// If the identity is updated, update the identity in the list
if (identities.value.find((i) => i.id === newCurrent.id))
identities.value = identities.value.map((i) =>
i.id === newCurrent.id ? newCurrent : i,
);
// If the identity is not in the list, add it
else identities.value.push(newCurrent);
// Force update the identities
identities.value = [...identities.value];
} else {
identities.value = identities.value.filter(
(i) => i.id !== currentId.value,
);
if (identities.value.length > 0) {
currentId.value = identities.value[0].id;
} else {
currentId.value = null;
}
}
},
{ deep: true },
);
return current;
};

View file

@ -1,8 +0,0 @@
import { StorageSerializers } from "@vueuse/core";
import type { Account } from "~/types/mastodon/account";
export const useMe = () => {
return useLocalStorage<Account | null>("lysand:me", null, {
serializer: StorageSerializers.object,
});
};

View file

@ -1,18 +1,18 @@
import { LysandClient, type Token } from "@lysand-org/client";
import { useCurrentIdentity } from "./Identities";
export const useClient = (
tokenData?: MaybeRef<Token | null>,
disableOnServer = false,
): Ref<LysandClient | null> => {
if (disableOnServer && process.server) {
return ref(null);
}
customToken: MaybeRef<Token | null> = null,
): Ref<LysandClient> => {
const identity = useCurrentIdentity();
return computed(
() =>
new LysandClient(
new URL(useBaseUrl().value),
toValue(tokenData)?.access_token,
toValue(customToken)?.access_token ??
identity.value?.tokens.access_token ??
undefined,
),
);
};

View file

@ -3,7 +3,7 @@ import type { Status } from "~/types/mastodon/status";
export const useNoteData = (
noteProp: MaybeRef<Status | undefined>,
client: Ref<LysandClient | null>,
client: Ref<LysandClient>,
) => {
const isReply = computed(() => !!toValue(noteProp)?.in_reply_to_id);
const isQuote = computed(() => !!toValue(noteProp)?.quote);
@ -53,7 +53,7 @@ export const useNoteData = (
);
const remove = async () => {
const result = await client.value?.deleteStatus(
const result = await client.value.deleteStatus(
renderedNote.value?.id ?? "",
);

View file

@ -0,0 +1,5 @@
export const usePermissions = () => {
const identity = useCurrentIdentity();
return computed(() => identity.value?.permissions ?? []);
};

View file

@ -1,5 +1,6 @@
import type { LysandClient } from "@lysand-org/client";
import type { Relationship } from "~/types/mastodon/relationship";
import { useCurrentIdentity } from "./Identities";
export const useRelationship = (
client: MaybeRef<LysandClient | null>,
@ -8,7 +9,7 @@ export const useRelationship = (
const relationship = ref(null as Relationship | null);
const isLoading = ref(false);
if (!useSignedIn().value) {
if (!useCurrentIdentity().value) {
return { relationship, isLoading };
}

View file

@ -1,6 +0,0 @@
export const useSignedIn = () => {
const tokenData = useTokenData();
return computed(
() => tokenData.value !== null && !!tokenData.value.access_token,
);
};

View file

@ -1,8 +0,0 @@
import type { Token } from "@lysand-org/client";
import { StorageSerializers } from "@vueuse/core";
export const useTokenData = () => {
return useLocalStorage<Token | null>("lysand:token_data", null, {
serializer: StorageSerializers.object,
});
};