mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 16:38:20 +01:00
feat: ✨ Implement proper login and logout using UI
This commit is contained in:
parent
e0c41bb9b5
commit
3c8093a3d2
27
app.vue
27
app.vue
|
|
@ -13,6 +13,33 @@ useServerSeoMeta({
|
|||
|
||||
// Use SSR-safe IDs for Headless UI
|
||||
provideHeadlessUseId(() => useId());
|
||||
|
||||
const code = useRequestURL().searchParams.get("code");
|
||||
|
||||
if (code) {
|
||||
const client = useMegalodon();
|
||||
const appData = useAppData();
|
||||
const tokenData = useTokenData();
|
||||
if (appData.value) {
|
||||
client.value
|
||||
?.fetchAccessToken(
|
||||
appData.value.client_id,
|
||||
appData.value.client_secret,
|
||||
code,
|
||||
new URL("/", useRequestURL().origin).toString(),
|
||||
)
|
||||
.then((res) => {
|
||||
tokenData.value = res;
|
||||
|
||||
// Remove code from URL
|
||||
window.history.replaceState(
|
||||
{},
|
||||
document.title,
|
||||
window.location.pathname,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<button v-bind="$props" type="button"
|
||||
class="rounded-md duration-200 hover:shadow disabled:opacity-70 disabled:cursor-not-allowed px-3 py-2 text-sm font-semibold text-white shadow-sm">
|
||||
<button v-bind="$props" type="button" :disabled="loading"
|
||||
:class="['rounded-md duration-200 relative hover:shadow disabled:opacity-70 content-none disabled:cursor-not-allowed px-3 py-2 text-sm font-semibold text-white shadow-sm', loading && '[&>*]:invisible']">
|
||||
<div v-if="loading" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 !visible">
|
||||
<Icon name="tabler:loader-2" class="animate-spin w-5 h-5" />
|
||||
</div>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -10,7 +13,11 @@ import type { ButtonHTMLAttributes } from "vue";
|
|||
|
||||
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
||||
|
||||
defineProps<Props>();
|
||||
defineProps<
|
||||
Props & {
|
||||
loading?: boolean;
|
||||
}
|
||||
>();
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ButtonsBase class="bg-white/10 hover:bg-white/20">
|
||||
<ButtonsBase class="bg-white/10 hover:bg-white/20" :loading="loading">
|
||||
<slot />
|
||||
</ButtonsBase>
|
||||
</template>
|
||||
|
|
@ -9,7 +9,11 @@ import type { ButtonHTMLAttributes } from "vue";
|
|||
|
||||
interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {}
|
||||
|
||||
defineProps<Props>();
|
||||
defineProps<
|
||||
Props & {
|
||||
loading?: boolean;
|
||||
}
|
||||
>();
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -21,14 +21,17 @@
|
|||
<div class="flex flex-col gap-3 mt-auto">
|
||||
<h3 class="font-semibold text-gray-300 text-xs uppercase opacity-0 group-hover:opacity-100 duration-200">
|
||||
Account</h3>
|
||||
<NuxtLink href="/about/apps">
|
||||
<ButtonsBase
|
||||
<ButtonsBase v-if="tokenData" @click="signOut().finally(() => loadingAuth = false)" :loading="loadingAuth"
|
||||
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
|
||||
<Icon name="tabler:logout" class="shrink-0 text-2xl" />
|
||||
<span class="pr-28 line-clamp-1">Sign Out</span>
|
||||
</ButtonsBase>
|
||||
<ButtonsBase v-else @click="signIn().finally(() => loadingAuth = false)" :loading="loadingAuth"
|
||||
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
|
||||
<Icon name="tabler:login" class="shrink-0 text-2xl" />
|
||||
<span class="pr-28 line-clamp-1">Sign In</span>
|
||||
</ButtonsBase>
|
||||
</NuxtLink>
|
||||
<NuxtLink href="/register">
|
||||
<NuxtLink href="/register" v-if="!tokenData">
|
||||
<ButtonsBase
|
||||
class="flex flex-row text-left items-center justify-start gap-3 text-lg hover:ring-1 ring-white/10 overflow-hidden h-12 w-full duration-200">
|
||||
<Icon name="tabler:certificate" class="shrink-0 text-2xl" />
|
||||
|
|
@ -52,4 +55,63 @@ const timelines = ref([
|
|||
icon: "tabler:home",
|
||||
},
|
||||
]);
|
||||
|
||||
const loadingAuth = ref(false);
|
||||
|
||||
const appData = useAppData();
|
||||
const tokenData = useTokenData();
|
||||
const client = useMegalodon();
|
||||
|
||||
const signIn = async () => {
|
||||
loadingAuth.value = true;
|
||||
|
||||
const output = await client.value?.createApp("Lysand", {
|
||||
scopes: ["read", "write", "follow", "push"],
|
||||
redirect_uris: new URL("/", useRequestURL().origin).toString(),
|
||||
website: useBaseUrl().value,
|
||||
});
|
||||
|
||||
if (!output) {
|
||||
alert("Failed to create app");
|
||||
return;
|
||||
}
|
||||
|
||||
appData.value = output;
|
||||
|
||||
const url = await client.value?.generateAuthUrl(
|
||||
output.client_id,
|
||||
output.client_secret,
|
||||
{
|
||||
scope: ["read", "write", "follow", "push"],
|
||||
redirect_uri: new URL("/", useRequestURL().origin).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (!url) {
|
||||
alert("Failed to generate auth URL");
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
loadingAuth.value = true;
|
||||
|
||||
if (!appData.value || !tokenData.value) {
|
||||
console.error("No app or token data to sign out");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't do anything on error, as Lysand doesn't implement the revoke endpoint yet
|
||||
await client.value
|
||||
?.revokeToken(
|
||||
appData.value.client_id,
|
||||
tokenData.value.access_token,
|
||||
tokenData.value.access_token,
|
||||
)
|
||||
.catch(() => {});
|
||||
|
||||
tokenData.value = null;
|
||||
};
|
||||
</script>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Status } from '~/types/mastodon/status';
|
||||
import type { Status } from "~/types/mastodon/status";
|
||||
|
||||
const props = defineProps<{
|
||||
note?: Status;
|
||||
|
|
|
|||
|
|
@ -91,12 +91,23 @@ const props = defineProps<{
|
|||
|
||||
// Handle reblogs
|
||||
const note = computed(() => props.note?.reblog ?? props.note);
|
||||
const noteClosed = ref(note.value?.sensitive || !!note.value?.spoiler_text || false);
|
||||
const noteClosed = ref(
|
||||
note.value?.sensitive || !!note.value?.spoiler_text || false,
|
||||
);
|
||||
|
||||
const { copy } = useClipboard();
|
||||
const client = useMegalodon();
|
||||
const mentions = await useResolveMentions(note.value?.mentions ?? [], client);
|
||||
const eventualReblogAccountName = props.note?.reblog ? (useParsedContent(props.note?.account.display_name, props.note?.account.emojis, mentions.value)).value : null;
|
||||
const mentions = await useResolveMentions(
|
||||
note.value?.mentions ?? [],
|
||||
client.value,
|
||||
);
|
||||
const eventualReblogAccountName = props.note?.reblog
|
||||
? useParsedContent(
|
||||
props.note?.account.display_name,
|
||||
props.note?.account.emojis,
|
||||
mentions.value,
|
||||
).value
|
||||
: null;
|
||||
const content =
|
||||
note.value && process.client
|
||||
? useParsedContent(
|
||||
|
|
|
|||
|
|
@ -20,48 +20,52 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Notification } from '~/types/mastodon/notification';
|
||||
import type { Notification } from "~/types/mastodon/notification";
|
||||
|
||||
const props = defineProps<{
|
||||
notification?: Notification;
|
||||
}>();
|
||||
|
||||
const accountName = useParsedContent(props.notification?.account?.display_name ?? '', props.notification?.account?.emojis ?? [], []);
|
||||
const accountName = useParsedContent(
|
||||
props.notification?.account?.display_name ?? "",
|
||||
props.notification?.account?.emojis ?? [],
|
||||
[],
|
||||
);
|
||||
const text = computed(() => {
|
||||
if (!props.notification) return '';
|
||||
if (!props.notification) return "";
|
||||
|
||||
switch (props.notification.type) {
|
||||
case 'mention':
|
||||
return 'mentioned you';
|
||||
case 'reblog':
|
||||
return 'reblogged your note';
|
||||
case 'favourite':
|
||||
return 'liked your note';
|
||||
case 'follow':
|
||||
return 'followed you';
|
||||
case 'follow_request':
|
||||
return 'requested to follow you';
|
||||
case "mention":
|
||||
return "mentioned you";
|
||||
case "reblog":
|
||||
return "reblogged your note";
|
||||
case "favourite":
|
||||
return "liked your note";
|
||||
case "follow":
|
||||
return "followed you";
|
||||
case "follow_request":
|
||||
return "requested to follow you";
|
||||
default:
|
||||
console.error('Unknown notification type', props.notification.type)
|
||||
return '';
|
||||
console.error("Unknown notification type", props.notification.type);
|
||||
return "";
|
||||
}
|
||||
});
|
||||
const icon = computed(() => {
|
||||
if (!props.notification) return '';
|
||||
if (!props.notification) return "";
|
||||
|
||||
switch (props.notification.type) {
|
||||
case 'mention':
|
||||
return 'tabler:at';
|
||||
case 'reblog':
|
||||
return 'tabler:repeat';
|
||||
case 'favourite':
|
||||
return 'tabler:heart';
|
||||
case 'follow':
|
||||
return 'tabler:plus';
|
||||
case 'follow_request':
|
||||
return 'tabler:plus';
|
||||
case "mention":
|
||||
return "tabler:at";
|
||||
case "reblog":
|
||||
return "tabler:repeat";
|
||||
case "favourite":
|
||||
return "tabler:heart";
|
||||
case "follow":
|
||||
return "tabler:plus";
|
||||
case "follow_request":
|
||||
return "tabler:plus";
|
||||
default:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -115,27 +115,25 @@ watch(
|
|||
skeleton,
|
||||
async () => {
|
||||
if (skeleton.value) return;
|
||||
parsedNote.value = (
|
||||
parsedNote.value =
|
||||
useParsedContent(
|
||||
props.account?.note ?? "",
|
||||
props.account?.emojis ?? [],
|
||||
[],
|
||||
)
|
||||
).value ?? "";
|
||||
parsedFields.value = props.account?.fields.map((field) => ({
|
||||
name: (
|
||||
parsedFields.value =
|
||||
props.account?.fields.map((field) => ({
|
||||
name:
|
||||
useParsedContent(
|
||||
field.name,
|
||||
props.account?.emojis ?? [],
|
||||
[],
|
||||
)
|
||||
).value ?? "",
|
||||
value: (
|
||||
value:
|
||||
useParsedContent(
|
||||
field.value,
|
||||
props.account?.emojis ?? [],
|
||||
[],
|
||||
)
|
||||
).value ?? "",
|
||||
})) ?? [];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const access_token = useLocalStorage("lysand:access_token", null);
|
||||
const client = useMegalodon(access_token);
|
||||
const tokenData = useTokenData();
|
||||
const client = useMegalodon(tokenData);
|
||||
|
||||
const isLoading = ref(true);
|
||||
|
||||
const timelineParameters = ref({});
|
||||
const hasReachedEnd = ref(false);
|
||||
const { timeline, loadNext, loadPrev } = useNotificationTimeline(
|
||||
client,
|
||||
client.value,
|
||||
timelineParameters,
|
||||
);
|
||||
const skeleton = ref<HTMLSpanElement | null>(null);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const isLoading = ref(true);
|
|||
const timelineParameters = ref({});
|
||||
const hasReachedEnd = ref(false);
|
||||
const { timeline, loadNext, loadPrev } = usePublicTimeline(
|
||||
client,
|
||||
client.value,
|
||||
timelineParameters,
|
||||
);
|
||||
const skeleton = ref<HTMLSpanElement | null>(null);
|
||||
|
|
|
|||
8
composables/AccessToken.ts
Normal file
8
composables/AccessToken.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { StorageSerializers } from "@vueuse/core";
|
||||
import type { OAuth } from "megalodon";
|
||||
|
||||
export const useTokenData = () => {
|
||||
return useLocalStorage<OAuth.TokenData | null>("lysand:token_data", null, {
|
||||
serializer: StorageSerializers.object,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
import type { Mastodon } from "megalodon";
|
||||
import type { Account } from "~/types/mastodon/account";
|
||||
|
||||
export const useAccount = (client: Mastodon | null, accountId: string) => {
|
||||
export const useAccount = (
|
||||
client: MaybeRef<Mastodon | null>,
|
||||
accountId: string,
|
||||
) => {
|
||||
if (!client) {
|
||||
return ref(null as Account | null);
|
||||
}
|
||||
|
||||
const output = ref(null as Account | null);
|
||||
|
||||
client.getAccount(accountId).then((res) => {
|
||||
ref(client)
|
||||
.value?.getAccount(accountId)
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
import type { Mastodon } from "megalodon";
|
||||
import type { Account } from "~/types/mastodon/account";
|
||||
|
||||
export const useAccountSearch = async (client: Mastodon | null, q: string) => {
|
||||
if (!client) {
|
||||
return null;
|
||||
}
|
||||
export const useAccountSearch = (
|
||||
client: MaybeRef<Mastodon | null>,
|
||||
q: string,
|
||||
): Ref<Account[] | null> => {
|
||||
const output = ref(null as Account[] | null);
|
||||
|
||||
return (
|
||||
await client.searchAccount(q, {
|
||||
ref(client)
|
||||
.value?.searchAccount(q, {
|
||||
resolve: true,
|
||||
})
|
||||
).data;
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
return output;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,72 +29,4 @@ export const useAccountTimeline = (
|
|||
}),
|
||||
options,
|
||||
);
|
||||
/* if (!client) {
|
||||
return {
|
||||
timeline: ref([]),
|
||||
loadNext: async () => {},
|
||||
loadPrev: async () => {},
|
||||
};
|
||||
}
|
||||
|
||||
const fetchedNotes = ref<Status[]>([]);
|
||||
const fetchedNoteIds = new Set<string>();
|
||||
let nextMaxId: string | undefined = undefined;
|
||||
let prevMinId: string | undefined = undefined;
|
||||
|
||||
const loadNext = async () => {
|
||||
const response = await client.getAccountStatuses(ref(id).value ?? "", {
|
||||
only_media: false,
|
||||
...ref(options).value,
|
||||
max_id: nextMaxId,
|
||||
limit: useConfig().NOTES_PER_PAGE,
|
||||
});
|
||||
|
||||
const newNotes = response.data.filter(
|
||||
(note) => !fetchedNoteIds.has(note.id),
|
||||
);
|
||||
if (newNotes.length > 0) {
|
||||
fetchedNotes.value = [...fetchedNotes.value, ...newNotes];
|
||||
nextMaxId = newNotes[newNotes.length - 1].id;
|
||||
for (const note of newNotes) {
|
||||
fetchedNoteIds.add(note.id);
|
||||
}
|
||||
} else {
|
||||
nextMaxId = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const loadPrev = async () => {
|
||||
const response = await client.getAccountStatuses(ref(id).value ?? "", {
|
||||
only_media: false,
|
||||
...ref(options).value,
|
||||
min_id: prevMinId,
|
||||
limit: useConfig().NOTES_PER_PAGE,
|
||||
});
|
||||
|
||||
const newNotes = response.data.filter(
|
||||
(note) => !fetchedNoteIds.has(note.id),
|
||||
);
|
||||
if (newNotes.length > 0) {
|
||||
fetchedNotes.value = [...newNotes, ...fetchedNotes.value];
|
||||
prevMinId = newNotes[0].id;
|
||||
for (const note of newNotes) {
|
||||
fetchedNoteIds.add(note.id);
|
||||
}
|
||||
} else {
|
||||
prevMinId = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => ref(id).value, () => ref(options).value],
|
||||
async ([id, { max_id, min_id }]) => {
|
||||
nextMaxId = max_id;
|
||||
prevMinId = min_id;
|
||||
id && (await loadNext());
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return { timeline: fetchedNotes, loadNext, loadPrev }; */
|
||||
};
|
||||
|
|
|
|||
8
composables/AppData.ts
Normal file
8
composables/AppData.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { StorageSerializers } from "@vueuse/core";
|
||||
import type { OAuth } from "megalodon";
|
||||
|
||||
export const useAppData = () => {
|
||||
return useLocalStorage<OAuth.AppData | null>("lysand:app_data", null, {
|
||||
serializer: StorageSerializers.object,
|
||||
});
|
||||
};
|
||||
|
|
@ -5,14 +5,16 @@ type ExtendedDescription = {
|
|||
content: string;
|
||||
};
|
||||
|
||||
export const useExtendedDescription = (client: Mastodon | null) => {
|
||||
if (!client) {
|
||||
export const useExtendedDescription = (client: MaybeRef<Mastodon | null>) => {
|
||||
if (!ref(client).value) {
|
||||
return ref(null as ExtendedDescription | null);
|
||||
}
|
||||
|
||||
const output = ref(null as ExtendedDescription | null);
|
||||
|
||||
client.client.get("/api/v1/instance/extended_description").then((res) => {
|
||||
ref(client)
|
||||
.value?.client.get("/api/v1/instance/extended_description")
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,16 @@ type InstanceWithExtra = Instance & {
|
|||
lysand_version?: string;
|
||||
};
|
||||
|
||||
export const useInstance = (client: Mastodon | null) => {
|
||||
if (!client) {
|
||||
export const useInstance = (client: MaybeRef<Mastodon | null>) => {
|
||||
if (!ref(client).value) {
|
||||
return ref(null as InstanceWithExtra | null);
|
||||
}
|
||||
|
||||
const output = ref(null as InstanceWithExtra | null);
|
||||
|
||||
client.getInstance().then((res) => {
|
||||
ref(client)
|
||||
.value?.getInstance()
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { Mastodon } from "megalodon";
|
||||
import { Mastodon, type OAuth } from "megalodon";
|
||||
|
||||
export const useMegalodon = (
|
||||
accessToken?: MaybeRef<string | null | undefined>,
|
||||
tokenData?: MaybeRef<OAuth.TokenData | null>,
|
||||
disableOnServer = false,
|
||||
) => {
|
||||
): Ref<Mastodon | null> => {
|
||||
if (disableOnServer && process.server) {
|
||||
return null;
|
||||
return ref(null);
|
||||
}
|
||||
|
||||
const baseUrl = useBaseUrl().value;
|
||||
|
||||
const client = new Mastodon(baseUrl, ref(accessToken).value);
|
||||
|
||||
return client;
|
||||
return computed(
|
||||
() =>
|
||||
new Mastodon(
|
||||
useBaseUrl().value,
|
||||
ref(tokenData).value?.access_token,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import type { Mastodon } from "megalodon";
|
||||
import type { Status } from "~/types/mastodon/status";
|
||||
|
||||
export const useNote = (client: Mastodon | null, noteId: string) => {
|
||||
if (!client) {
|
||||
export const useNote = (client: MaybeRef<Mastodon | null>, noteId: string) => {
|
||||
if (!ref(client).value) {
|
||||
return ref(null as Status | null);
|
||||
}
|
||||
|
||||
const output = ref(null as Status | null);
|
||||
|
||||
client.getStatus(noteId).then((res) => {
|
||||
ref(client)
|
||||
.value?.getStatus(noteId)
|
||||
.then((res) => {
|
||||
output.value = res.data;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<aside
|
||||
class="max-w-md max-h-dvh overflow-y-auto w-full bg-dark-900 ring-1 ring-white/10 hidden lg:flex flex-col gap-10">
|
||||
<ClientOnly>
|
||||
<div class="grow p-10" v-if="!accessToken">
|
||||
<div class="grow p-10" v-if="!tokenData">
|
||||
<button type="button"
|
||||
class="relative block h-full w-full rounded-lg border-2 border-dashed border-dark-300 p-12 text-center">
|
||||
<Icon name="tabler:notification" class="mx-auto h-12 w-12 text-gray-400" />
|
||||
|
|
@ -58,8 +58,8 @@
|
|||
<script setup lang="ts">
|
||||
import { convert } from "html-to-text";
|
||||
|
||||
const accessToken = useLocalStorage("lysand:access_token", "");
|
||||
const client = useMegalodon(accessToken);
|
||||
const tokenData = useTokenData();
|
||||
const client = useMegalodon(tokenData);
|
||||
const instance = useInstance(client);
|
||||
const description = useExtendedDescription(client);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,14 +38,13 @@ const route = useRoute();
|
|||
const client = useMegalodon(undefined, true);
|
||||
const username = (route.params.username as string).replace("@", "");
|
||||
|
||||
const account: Ref<Account | null> = ref(null);
|
||||
const accounts = useAccountSearch(client, username);
|
||||
const account = computed<Account | null>(
|
||||
() => accounts.value?.find((account) => account.acct === username) ?? null,
|
||||
);
|
||||
const accountId = computed(() => account.value?.id ?? null);
|
||||
|
||||
onMounted(async () => {
|
||||
const accounts = await useAccountSearch(client, username);
|
||||
account.value =
|
||||
(await accounts?.find((account) => account.acct === username)) ?? null;
|
||||
|
||||
useIntersectionObserver(skeleton, async (entries) => {
|
||||
if (
|
||||
entries[0].isIntersecting &&
|
||||
|
|
@ -70,7 +69,7 @@ const isLoadingTimeline = ref(true);
|
|||
const timelineParameters = ref({});
|
||||
const hasReachedEnd = ref(false);
|
||||
const { timeline, loadNext, loadPrev } = useAccountTimeline(
|
||||
client,
|
||||
client.value,
|
||||
accountId,
|
||||
timelineParameters,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const isLoading = ref(true);
|
|||
const timelineParameters = ref({});
|
||||
const hasReachedEnd = ref(false);
|
||||
const { timeline, loadNext, loadPrev } = useLocalTimeline(
|
||||
client,
|
||||
client.value,
|
||||
timelineParameters,
|
||||
);
|
||||
const skeleton = ref<HTMLSpanElement | null>(null);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ const register = (result: {
|
|||
reason: string;
|
||||
}) => {
|
||||
isLoading.value = true;
|
||||
client?.registerAccount(
|
||||
ref(client)
|
||||
.value?.registerAccount(
|
||||
result.username,
|
||||
result.email,
|
||||
result.password,
|
||||
|
|
|
|||
Loading…
Reference in a new issue