diff --git a/app.vue b/app.vue index e0ffddf9..4d44ed25 100644 --- a/app.vue +++ b/app.vue @@ -6,12 +6,15 @@ provideGlobalCommands() // We want to trigger rerendering the page when account changes const key = computed(() => `${currentUser.value?.server ?? currentServer.value}:${currentUser.value?.account.id || ''}`) + +const { params } = useRoute() </script> <template> <NuxtLoadingIndicator color="repeating-linear-gradient(to right,var(--c-primary) 0%,var(--c-primary-active) 100%)" /> <NuxtLayout :key="key"> - <NuxtPage v-if="isMastoInitialised" /> + <!-- TODO: rework the /[account] routes to remove conditional loading --> + <NuxtPage v-if="(!params.account && $route.path !== '/signin/callback') || isMastoInitialised" /> </NuxtLayout> <AriaAnnouncer /> </template> diff --git a/components/common/CommonRouteTabs.vue b/components/common/CommonRouteTabs.vue index 65d8864b..d596b889 100644 --- a/components/common/CommonRouteTabs.vue +++ b/components/common/CommonRouteTabs.vue @@ -5,6 +5,7 @@ const { options, command, replace, preventScrollTop = false } = $defineProps<{ options: { to: RouteLocationRaw display: string + disabled?: boolean name?: string icon?: string }[] @@ -28,18 +29,25 @@ useCommands(() => command <template> <div flex w-full items-center lg:text-lg of-x-auto scrollbar-hide> - <NuxtLink + <template v-for="(option, index) in options" :key="option?.name || index" - :to="option.to" - :replace="replace" - relative flex flex-auto cursor-pointer sm:px6 px2 rounded transition-all - tabindex="1" - hover:bg-active transition-100 - exact-active-class="children:(font-bold !border-primary !op100)" - @click="!preventScrollTop && $scrollToTop()" > - <span ws-nowrap mxa sm:px2 sm:py3 py2 text-center border-b-3 op50 hover:op70 border-transparent>{{ option.display }}</span> - </NuxtLink> + <NuxtLink + v-if="!option.disabled" + :to="option.to" + :replace="replace" + relative flex flex-auto cursor-pointer sm:px6 px2 rounded transition-all + tabindex="1" + hover:bg-active transition-100 + exact-active-class="children:(text-secondary !border-primary !op100)" + @click="!preventScrollTop && $scrollToTop()" + > + <span ws-nowrap mxa sm:px2 sm:py3 py2 text-center border-b-3 text-secondary-light hover:text-secondary border-transparent>{{ option.display }}</span> + </NuxtLink> + <div v-else flex flex-auto sm:px6 px2> + <span ws-nowrap mxa sm:px2 sm:py3 py2 text-center text-secondary-light op50>{{ option.display }}</span> + </div> + </template> </div> </template> diff --git a/components/timeline/TimelineBlocks.vue b/components/timeline/TimelineBlocks.vue new file mode 100644 index 00000000..5b20b337 --- /dev/null +++ b/components/timeline/TimelineBlocks.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().blocks.iterate() +</script> + +<template> + <AccountPaginator :paginator="paginator" /> +</template> diff --git a/components/timeline/TimelineBookmarks.vue b/components/timeline/TimelineBookmarks.vue new file mode 100644 index 00000000..f99ba34c --- /dev/null +++ b/components/timeline/TimelineBookmarks.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().bookmarks.iterate() +</script> + +<template> + <TimelinePaginator :paginator="paginator" /> +</template> diff --git a/components/timeline/TimelineConversations.vue b/components/timeline/TimelineConversations.vue new file mode 100644 index 00000000..a9af6ba3 --- /dev/null +++ b/components/timeline/TimelineConversations.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().conversations.iterate() +</script> + +<template> + <ConversationPaginator :paginator="paginator" /> +</template> diff --git a/components/timeline/TimelineDomainBlocks.vue b/components/timeline/TimelineDomainBlocks.vue new file mode 100644 index 00000000..a0f564f8 --- /dev/null +++ b/components/timeline/TimelineDomainBlocks.vue @@ -0,0 +1,21 @@ +<script setup lang="ts"> +const masto = useMasto() +const paginator = masto.domainBlocks.iterate() + +const unblock = async (domain: string) => { + await masto.domainBlocks.unblock(domain) +} +</script> + +<template> + <CommonPaginator :paginator="paginator"> + <template #default="{ item }"> + <CommonDropdownItem class="!cursor-auto"> + {{ item }} + <template #actions> + <div i-ri:lock-unlock-line text-primary cursor-pointer @click="unblock(item)" /> + </template> + </CommonDropdownItem> + </template> + </CommonPaginator> +</template> diff --git a/components/timeline/TimelineFavourites.vue b/components/timeline/TimelineFavourites.vue new file mode 100644 index 00000000..b5e286a5 --- /dev/null +++ b/components/timeline/TimelineFavourites.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().favourites.iterate() +</script> + +<template> + <TimelinePaginator :paginator="paginator" /> +</template> diff --git a/components/timeline/TimelineHome.vue b/components/timeline/TimelineHome.vue new file mode 100644 index 00000000..c9792b9b --- /dev/null +++ b/components/timeline/TimelineHome.vue @@ -0,0 +1,12 @@ +<script setup lang="ts"> +const paginator = useMasto().timelines.iterateHome() +const stream = await useMasto().stream.streamUser() +onBeforeUnmount(() => stream.disconnect()) +</script> + +<template> + <div> + <PublishWidget draft-key="home" border="b base" /> + <TimelinePaginator v-bind="{ paginator, stream }" context="home" /> + </div> +</template> diff --git a/components/timeline/TimelineMentions.vue b/components/timeline/TimelineMentions.vue new file mode 100644 index 00000000..8e0a925b --- /dev/null +++ b/components/timeline/TimelineMentions.vue @@ -0,0 +1,13 @@ +<script setup lang="ts"> +// Default limit is 20 notifications, and servers are normally caped to 30 +const paginator = useMasto().notifications.iterate({ limit: 30, types: ['mention'] }) + +const { clearNotifications } = useNotifications() +onActivated(clearNotifications) + +const stream = await useMasto().stream.streamUser() +</script> + +<template> + <NotificationPaginator v-bind="{ paginator, stream }" /> +</template> diff --git a/components/timeline/TimelineMutes.vue b/components/timeline/TimelineMutes.vue new file mode 100644 index 00000000..303c10ff --- /dev/null +++ b/components/timeline/TimelineMutes.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().mutes.iterate() +</script> + +<template> + <AccountPaginator :paginator="paginator" /> +</template> diff --git a/components/timeline/TimelineNotifications.vue b/components/timeline/TimelineNotifications.vue new file mode 100644 index 00000000..53c067ec --- /dev/null +++ b/components/timeline/TimelineNotifications.vue @@ -0,0 +1,13 @@ +<script setup lang="ts"> +// Default limit is 20 notifications, and servers are normally caped to 30 +const paginator = useMasto().notifications.iterate({ limit: 30 }) + +const { clearNotifications } = useNotifications() +onActivated(clearNotifications) + +const stream = await useMasto().stream.streamUser() +</script> + +<template> + <NotificationPaginator v-bind="{ paginator, stream }" /> +</template> diff --git a/components/timeline/TimelinePinned.vue b/components/timeline/TimelinePinned.vue new file mode 100644 index 00000000..e0862f01 --- /dev/null +++ b/components/timeline/TimelinePinned.vue @@ -0,0 +1,7 @@ +<script setup lang="ts"> +const paginator = useMasto().accounts.iterateStatuses(currentUser.value!.account.id, { pinned: true }) +</script> + +<template> + <TimelinePaginator :paginator="paginator" /> +</template> diff --git a/pages/[[server]]/explore.vue b/pages/[[server]]/explore.vue index eed38e1b..ca3446a7 100644 --- a/pages/[[server]]/explore.vue +++ b/pages/[[server]]/explore.vue @@ -17,15 +17,11 @@ const tabs = $computed(() => [ display: t('tab.news'), }, // This section can only be accessed after logging in - ...invoke(() => currentUser.value - ? [ - { - to: `/${currentServer.value}/explore/users`, - display: t('tab.for_you'), - }, - ] - : [], - ), + { + to: `/${currentServer.value}/explore/users`, + display: t('tab.for_you'), + disabled: !isMastoInitialised.value || !currentUser.value, + }, ] as const) </script> @@ -41,6 +37,6 @@ const tabs = $computed(() => [ <template #header> <CommonRouteTabs replace :options="tabs" /> </template> - <NuxtPage /> + <NuxtPage v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/blocks.vue b/pages/blocks.vue index 010e2012..5e460657 100644 --- a/pages/blocks.vue +++ b/pages/blocks.vue @@ -3,8 +3,6 @@ definePageMeta({ middleware: 'auth', }) -const paginator = useMasto().blocks.iterate() - useHeadFixed({ title: 'Blocked users', }) @@ -15,6 +13,7 @@ useHeadFixed({ <template #title> <span text-lg font-bold>{{ $t('account.blocked_users') }}</span> </template> - <AccountPaginator :paginator="paginator" /> + + <TimelineBlocks v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/bookmarks.vue b/pages/bookmarks.vue index fd578689..c1921a5c 100644 --- a/pages/bookmarks.vue +++ b/pages/bookmarks.vue @@ -3,8 +3,6 @@ definePageMeta({ middleware: 'auth', }) -const paginator = useMasto().bookmarks.iterate() - const { t } = useI18n() useHeadFixed({ @@ -21,8 +19,6 @@ useHeadFixed({ </NuxtLink> </template> - <slot> - <TimelinePaginator :paginator="paginator" /> - </slot> + <TimelineBookmarks v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/conversations.vue b/pages/conversations.vue index 31d54ec3..13654581 100644 --- a/pages/conversations.vue +++ b/pages/conversations.vue @@ -3,8 +3,6 @@ definePageMeta({ middleware: 'auth', }) -const paginator = useMasto().conversations.iterate() - const { t } = useI18n() useHeadFixed({ @@ -21,8 +19,6 @@ useHeadFixed({ </NuxtLink> </template> - <slot> - <ConversationPaginator :paginator="paginator" /> - </slot> + <TimelineConversations v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/domain_blocks.vue b/pages/domain_blocks.vue index ed25088f..e91b7e16 100644 --- a/pages/domain_blocks.vue +++ b/pages/domain_blocks.vue @@ -1,18 +1,13 @@ <script setup lang="ts"> +import TimelineDomainBlocks from '~~/components/timeline/TimelineDomainBlocks.vue' + definePageMeta({ middleware: 'auth', }) -const masto = useMasto() -const paginator = masto.domainBlocks.iterate() - useHeadFixed({ title: 'Blocked domains', }) - -const unblock = async (domain: string) => { - await masto.domainBlocks.unblock(domain) -} </script> <template> @@ -21,15 +16,6 @@ const unblock = async (domain: string) => { <span text-lg font-bold>{{ $t('account.blocked_domains') }}</span> </template> - <CommonPaginator :paginator="paginator"> - <template #default="{ item }"> - <CommonDropdownItem class="!cursor-auto"> - {{ item }} - <template #actions> - <div i-ri:lock-unlock-line text-primary cursor-pointer @click="unblock(item)" /> - </template> - </CommonDropdownItem> - </template> - </CommonPaginator> + <TimelineDomainBlocks v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/favourites.vue b/pages/favourites.vue index b0f97722..23685701 100644 --- a/pages/favourites.vue +++ b/pages/favourites.vue @@ -3,7 +3,6 @@ definePageMeta({ middleware: 'auth', }) -const paginator = useMasto().favourites.iterate() const { t } = useI18n() useHeadFixed({ @@ -19,8 +18,7 @@ useHeadFixed({ <span>{{ t('nav_side.favourites') }}</span> </NuxtLink> </template> - <slot> - <TimelinePaginator :paginator="paginator" /> - </slot> + + <TimelineFavourites v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/home.vue b/pages/home.vue index f0249840..22c0fcd4 100644 --- a/pages/home.vue +++ b/pages/home.vue @@ -6,16 +6,6 @@ definePageMeta({ alias: ['/signin/callback'], }) -if (useRoute().path === '/signin/callback') { - // This only cleans up the URL; page content should stay the same - useRouter().push('/home') -} - -const masto = useMasto() -const paginator = masto.timelines.iterateHome() -const stream = await masto.stream.streamUser() -onBeforeUnmount(() => stream.disconnect()) - const { t } = useI18n() useHeadFixed({ title: () => t('nav_side.home'), @@ -30,9 +20,7 @@ useHeadFixed({ <span>{{ $t('nav_side.home') }}</span> </NuxtLink> </template> - <slot> - <PublishWidget draft-key="home" border="b base" /> - <TimelinePaginator v-bind="{ paginator, stream }" context="home" /> - </slot> + + <TimelineHome v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/mutes.vue b/pages/mutes.vue index c26a24a9..4e773bb7 100644 --- a/pages/mutes.vue +++ b/pages/mutes.vue @@ -2,9 +2,6 @@ definePageMeta({ middleware: 'auth', }) - -const paginator = useMasto().mutes.iterate() - useHeadFixed({ title: 'Muted users', }) @@ -15,6 +12,7 @@ useHeadFixed({ <template #title> <span text-lg font-bold>{{ $t('account.muted_users') }}</span> </template> - <AccountPaginator :paginator="paginator" /> + + <TimelineMutes v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/pages/notifications.vue b/pages/notifications.vue index 4e6e63ea..795b4577 100644 --- a/pages/notifications.vue +++ b/pages/notifications.vue @@ -53,6 +53,7 @@ onActivated(() => { <template v-if="pwaEnabled"> <NotificationPreferences :show="showSettings" /> </template> + <NuxtPage /> </slot> </MainContent> diff --git a/pages/notifications/index.vue b/pages/notifications/index.vue index 08bf4aab..ce586d62 100644 --- a/pages/notifications/index.vue +++ b/pages/notifications/index.vue @@ -1,20 +1,10 @@ <script setup lang="ts"> const { t } = useI18n() -const masto = useMasto() - -// Default limit is 20 notifications, and servers are normally caped to 30 -const paginator = masto.notifications.iterate({ limit: 30 }) - -const { clearNotifications } = useNotifications() -onActivated(clearNotifications) - -const stream = await masto.stream.streamUser() - useHeadFixed({ title: () => `${t('tab.notifications_all')} | ${t('nav_side.notifications')}`, }) </script> <template> - <NotificationPaginator v-bind="{ paginator, stream }" /> + <TimelineNotifications v-if="isMastoInitialised" /> </template> diff --git a/pages/notifications/mention.vue b/pages/notifications/mention.vue index 97a50781..a85c87c8 100644 --- a/pages/notifications/mention.vue +++ b/pages/notifications/mention.vue @@ -1,20 +1,10 @@ <script setup lang="ts"> const { t } = useI18n() - -const masto = useMasto() -// Default limit is 20 notifications, and servers are normally caped to 30 -const paginator = masto.notifications.iterate({ limit: 30, types: ['mention'] }) - -const { clearNotifications } = useNotifications() -onActivated(clearNotifications) - -const stream = await masto.stream.streamUser() - useHeadFixed({ title: () => `${t('tab.notifications_mention')} | ${t('nav_side.notifications')}`, }) </script> <template> - <NotificationPaginator v-bind="{ paginator, stream }" /> + <TimelineNotifications v-if="isMastoInitialised" /> </template> diff --git a/pages/pinned.vue b/pages/pinned.vue index 7094b55f..a88a16e6 100644 --- a/pages/pinned.vue +++ b/pages/pinned.vue @@ -18,6 +18,6 @@ useHeadFixed({ <span>{{ t('account.pinned') }}</span> </template> - <TimelinePaginator :paginator="paginator" /> + <TimelinePinned v-if="isMastoInitialised" /> </MainContent> </template> diff --git a/plugins/masto.ts b/plugins/masto.ts index 1fd0ac5b..a3cecbed 100644 --- a/plugins/masto.ts +++ b/plugins/masto.ts @@ -2,7 +2,8 @@ export default defineNuxtPlugin(async (nuxtApp) => { const masto = createMasto() if (process.client) { - const { query } = useRoute() + const { query, path } = useRoute() + const router = useRouter() const user = typeof query.server === 'string' && typeof query.token === 'string' ? { server: query.server, @@ -13,8 +14,13 @@ export default defineNuxtPlugin(async (nuxtApp) => { nuxtApp.hook('app:suspense:resolve', () => { // TODO: improve upstream to make this synchronous (delayed auth) - if (!masto.loggedIn.value) - masto.loginTo(user) + if (!masto.loggedIn.value) { + masto.loginTo(user).then(() => { + // This only cleans up the URL; page content should stay the same + if (path === '/signin/callback') + router.push('/home') + }) + } }) }