mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
feat: ✨ Add multi-account support, more options for posts, UI improvements
This commit is contained in:
parent
48954baf06
commit
ef9a6f1da4
36 changed files with 649 additions and 344 deletions
|
|
@ -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[];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import type { Emoji } from "~/types/mastodon/emoji";
|
||||
|
||||
export const useCustomEmojis = () => {
|
||||
// Cache in localStorage
|
||||
return useLocalStorage<Emoji[]>("lysand:custom_emojis", []);
|
||||
};
|
||||
|
|
@ -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
119
composables/Identities.ts
Normal 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;
|
||||
};
|
||||
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 ?? "",
|
||||
);
|
||||
|
||||
|
|
|
|||
5
composables/Permissions.ts
Normal file
5
composables/Permissions.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export const usePermissions = () => {
|
||||
const identity = useCurrentIdentity();
|
||||
|
||||
return computed(() => identity.value?.permissions ?? []);
|
||||
};
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
export const useSignedIn = () => {
|
||||
const tokenData = useTokenData();
|
||||
return computed(
|
||||
() => tokenData.value !== null && !!tokenData.value.access_token,
|
||||
);
|
||||
};
|
||||
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue