diff --git a/composables/hydration.ts b/composables/hydration.ts deleted file mode 100644 index 094b16cc..00000000 --- a/composables/hydration.ts +++ /dev/null @@ -1 +0,0 @@ -export const isHydrated = ref(false) diff --git a/composables/masto.ts b/composables/masto.ts deleted file mode 100644 index b79fdb1a..00000000 --- a/composables/masto.ts +++ /dev/null @@ -1,160 +0,0 @@ -import type { Ref } from 'vue' -import type { Account, Relationship, Status } from 'masto' -import { withoutProtocol } from 'ufo' -import type { ElkMasto } from '~/types' - -export const useMasto = () => useNuxtApp().$masto as ElkMasto - -export const isMastoInitialised = computed(() => process.client && useMasto().loggedIn.value) - -export const onMastoInit = (cb: () => unknown) => { - watchOnce(isMastoInitialised, () => { - cb() - }, { immediate: isMastoInitialised.value }) -} - -export function getDisplayName(account?: Account, options?: { rich?: boolean }) { - const displayName = account?.displayName || account?.username || '' - if (options?.rich) - return displayName - return displayName.replace(/:([\w-]+?):/g, '') -} - -export function getShortHandle({ acct }: Account) { - if (!acct) - return '' - return `@${acct.includes('@') ? acct.split('@')[0] : acct}` -} - -export function getServerName(account: Account) { - if (account.acct?.includes('@')) - return account.acct.split('@')[1] - // We should only lack the server name if we're on the same server as the account - return currentInstance.value?.uri || '' -} - -export function getFullHandle(account: Account) { - const handle = `@${account.acct}` - if (!currentUser.value || account.acct.includes('@')) - return handle - return `${handle}@${getServerName(account)}` -} - -export function toShortHandle(fullHandle: string) { - if (!currentUser.value) - return fullHandle - const server = currentUser.value.server - if (fullHandle.endsWith(`@${server}`)) - return fullHandle.slice(0, -server.length - 1) - return fullHandle -} - -export function extractAccountHandle(account: Account) { - let handle = getFullHandle(account).slice(1) - const uri = currentInstance.value?.uri ?? currentServer.value - if (currentInstance.value && handle.endsWith(`@${uri}`)) - handle = handle.slice(0, -uri.length - 1) - - return handle -} - -export function getAccountRoute(account: Account) { - return useRouter().resolve({ - name: 'account-index', - params: { - server: currentServer.value, - account: extractAccountHandle(account), - }, - }) -} -export function getAccountFollowingRoute(account: Account) { - return useRouter().resolve({ - name: 'account-following', - params: { - server: currentServer.value, - account: extractAccountHandle(account), - }, - }) -} -export function getAccountFollowersRoute(account: Account) { - return useRouter().resolve({ - name: 'account-followers', - params: { - server: currentServer.value, - account: extractAccountHandle(account), - }, - }) -} - -export function getStatusRoute(status: Status) { - return useRouter().resolve({ - name: 'status', - params: { - server: currentServer.value, - account: extractAccountHandle(status.account), - status: status.id, - }, - }) -} - -export function getTagRoute(tag: string) { - return useRouter().resolve({ - name: 'tag', - params: { - server: currentServer.value, - tag, - }, - }) -} - -export function getStatusPermalinkRoute(status: Status) { - return status.url ? withoutProtocol(status.url) : null -} - -export function getStatusInReplyToRoute(status: Status) { - return useRouter().resolve({ - name: 'status-by-id', - params: { - server: currentServer.value, - status: status.inReplyToId, - }, - }) -} - -export function useAccountHandle(account: Account, fullServer = true) { - return computed(() => fullServer - ? getFullHandle(account) - : getShortHandle(account), - ) -} - -// Batch requests for relationships when used in the UI -// We don't want to hold to old values, so every time a Relationship is needed it -// is requested again from the server to show the latest state - -const requestedRelationships = new Map>() -let timeoutHandle: NodeJS.Timeout | undefined - -export function useRelationship(account: Account): Ref { - if (!currentUser.value) - return ref() - let relationship = requestedRelationships.get(account.id) - if (relationship) - return relationship - relationship = ref() - requestedRelationships.set(account.id, relationship) - if (timeoutHandle) - clearTimeout(timeoutHandle) - timeoutHandle = setTimeout(() => { - timeoutHandle = undefined - fetchRelationships() - }, 100) - return relationship -} - -async function fetchRelationships() { - const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value) - const relationships = await useMasto().accounts.fetchRelationships(requested.map(([id]) => id)) - for (let i = 0; i < requested.length; i++) - requested[i][1].value = relationships[i] -} diff --git a/composables/masto/account.ts b/composables/masto/account.ts new file mode 100644 index 00000000..4c365a13 --- /dev/null +++ b/composables/masto/account.ts @@ -0,0 +1,53 @@ +import type { Account } from 'masto' + +export function getDisplayName(account?: Account, options?: { rich?: boolean }) { + const displayName = account?.displayName || account?.username || '' + if (options?.rich) + return displayName + return displayName.replace(/:([\w-]+?):/g, '') +} + +export function getShortHandle({ acct }: Account) { + if (!acct) + return '' + return `@${acct.includes('@') ? acct.split('@')[0] : acct}` +} + +export function getServerName(account: Account) { + if (account.acct?.includes('@')) + return account.acct.split('@')[1] + // We should only lack the server name if we're on the same server as the account + return currentInstance.value?.uri || '' +} + +export function getFullHandle(account: Account) { + const handle = `@${account.acct}` + if (!currentUser.value || account.acct.includes('@')) + return handle + return `${handle}@${getServerName(account)}` +} + +export function toShortHandle(fullHandle: string) { + if (!currentUser.value) + return fullHandle + const server = currentUser.value.server + if (fullHandle.endsWith(`@${server}`)) + return fullHandle.slice(0, -server.length - 1) + return fullHandle +} + +export function extractAccountHandle(account: Account) { + let handle = getFullHandle(account).slice(1) + const uri = currentInstance.value?.uri ?? currentServer.value + if (currentInstance.value && handle.endsWith(`@${uri}`)) + handle = handle.slice(0, -uri.length - 1) + + return handle +} + +export function useAccountHandle(account: Account, fullServer = true) { + return computed(() => fullServer + ? getFullHandle(account) + : getShortHandle(account), + ) +} diff --git a/composables/masto/masto.ts b/composables/masto/masto.ts new file mode 100644 index 00000000..590595a4 --- /dev/null +++ b/composables/masto/masto.ts @@ -0,0 +1,11 @@ +import type { ElkMasto } from '~/types' + +export const useMasto = () => useNuxtApp().$masto as ElkMasto + +export const isMastoInitialised = computed(() => process.client && useMasto().loggedIn.value) + +export const onMastoInit = (cb: () => unknown) => { + watchOnce(isMastoInitialised, () => { + cb() + }, { immediate: isMastoInitialised.value }) +} diff --git a/composables/masto/relationship.ts b/composables/masto/relationship.ts new file mode 100644 index 00000000..4a92b084 --- /dev/null +++ b/composables/masto/relationship.ts @@ -0,0 +1,33 @@ +import type { Account, Relationship } from 'masto' +import type { Ref } from 'vue' + +// Batch requests for relationships when used in the UI +// We don't want to hold to old values, so every time a Relationship is needed it +// is requested again from the server to show the latest state + +const requestedRelationships = new Map>() +let timeoutHandle: NodeJS.Timeout | undefined + +export function useRelationship(account: Account): Ref { + if (!currentUser.value) + return ref() + let relationship = requestedRelationships.get(account.id) + if (relationship) + return relationship + relationship = ref() + requestedRelationships.set(account.id, relationship) + if (timeoutHandle) + clearTimeout(timeoutHandle) + timeoutHandle = setTimeout(() => { + timeoutHandle = undefined + fetchRelationships() + }, 100) + return relationship +} + +async function fetchRelationships() { + const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value) + const relationships = await useMasto().accounts.fetchRelationships(requested.map(([id]) => id)) + for (let i = 0; i < requested.length; i++) + requested[i][1].value = relationships[i] +} diff --git a/composables/masto/routes.ts b/composables/masto/routes.ts new file mode 100644 index 00000000..05fe07e7 --- /dev/null +++ b/composables/masto/routes.ts @@ -0,0 +1,65 @@ +import { withoutProtocol } from 'ufo' +import type { Account, Status } from 'masto' + +export function getAccountRoute(account: Account) { + return useRouter().resolve({ + name: 'account-index', + params: { + server: currentServer.value, + account: extractAccountHandle(account), + }, + }) +} +export function getAccountFollowingRoute(account: Account) { + return useRouter().resolve({ + name: 'account-following', + params: { + server: currentServer.value, + account: extractAccountHandle(account), + }, + }) +} +export function getAccountFollowersRoute(account: Account) { + return useRouter().resolve({ + name: 'account-followers', + params: { + server: currentServer.value, + account: extractAccountHandle(account), + }, + }) +} + +export function getStatusRoute(status: Status) { + return useRouter().resolve({ + name: 'status', + params: { + server: currentServer.value, + account: extractAccountHandle(status.account), + status: status.id, + }, + }) +} + +export function getTagRoute(tag: string) { + return useRouter().resolve({ + name: 'tag', + params: { + server: currentServer.value, + tag, + }, + }) +} + +export function getStatusPermalinkRoute(status: Status) { + return status.url ? withoutProtocol(status.url) : null +} + +export function getStatusInReplyToRoute(status: Status) { + return useRouter().resolve({ + name: 'status-by-id', + params: { + server: currentServer.value, + status: status.inReplyToId, + }, + }) +} diff --git a/composables/search.ts b/composables/masto/search.ts similarity index 100% rename from composables/search.ts rename to composables/masto/search.ts diff --git a/composables/status.ts b/composables/masto/status.ts similarity index 100% rename from composables/status.ts rename to composables/masto/status.ts diff --git a/composables/statusDrafts.ts b/composables/masto/statusDrafts.ts similarity index 100% rename from composables/statusDrafts.ts rename to composables/masto/statusDrafts.ts diff --git a/composables/translate.ts b/composables/masto/translate.ts similarity index 100% rename from composables/translate.ts rename to composables/masto/translate.ts diff --git a/composables/utils.ts b/composables/misc.ts similarity index 100% rename from composables/utils.ts rename to composables/misc.ts diff --git a/composables/paginator.ts b/composables/paginator.ts index 5c9f5de1..25ee81b7 100644 --- a/composables/paginator.ts +++ b/composables/paginator.ts @@ -1,5 +1,4 @@ import type { Paginator, WsEvents } from 'masto' -import { useDeactivated } from './lifecycle' import type { PaginatorState } from '~/types' export function usePaginator( diff --git a/composables/lifecycle.ts b/composables/vue.ts similarity index 56% rename from composables/lifecycle.ts rename to composables/vue.ts index b6d06e1b..1c3224da 100644 --- a/composables/lifecycle.ts +++ b/composables/vue.ts @@ -1,5 +1,10 @@ import type { ComponentInternalInstance } from 'vue' import { onActivated, onDeactivated, ref } from 'vue' +import type { ActiveHeadEntry, HeadEntryOptions, UseHeadInput } from '@vueuse/head' +import type { HeadAugmentations } from '@nuxt/schema' +import { useHead } from '#head' + +export const isHydrated = ref(false) /** * ### Whether the current component is running in the background @@ -28,3 +33,13 @@ export function onReactivated(hook: Function, target?: ComponentInternalInstance }, target) onDeactivated(() => initial.value = false) } + +// TODO: Workaround for Nuxt bug: https://github.com/elk-zone/elk/pull/199#issuecomment-1329771961 +export function useHeadFixed(input: UseHeadInput, options?: HeadEntryOptions): ActiveHeadEntry> | void { + const deactivated = useDeactivated() + return useHead(() => { + if (deactivated.value) + return {} + return resolveUnref(input) + }, options) +} diff --git a/composables/workarounds.ts b/composables/workarounds.ts deleted file mode 100644 index e737add4..00000000 --- a/composables/workarounds.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ActiveHeadEntry, HeadEntryOptions, UseHeadInput } from '@vueuse/head' -import type { HeadAugmentations } from '@nuxt/schema' -import { useHead } from '#head' - -// TODO: Workaround for Nuxt bug: https://github.com/elk-zone/elk/pull/199#issuecomment-1329771961 -export function useHeadFixed(input: UseHeadInput, options?: HeadEntryOptions): ActiveHeadEntry> | void { - const deactivated = useDeactivated() - return useHead(() => { - if (deactivated.value) - return {} - return resolveUnref(input) - }, options) -}