diff --git a/components/status/StatusCard.vue b/components/status/StatusCard.vue index 1ec162cf..84ebcd41 100644 --- a/components/status/StatusCard.vue +++ b/components/status/StatusCard.vue @@ -62,6 +62,7 @@ const timeago = useTimeAgo(() => status.value.createdAt, timeAgoOptions) const isSelfReply = computed(() => status.value.inReplyToAccountId === status.value.account.id) const collapseRebloggedBy = computed(() => rebloggedBy.value?.id === status.value.account.id) const isDM = computed(() => status.value.visibility === 'direct') +const isPinned = computed(() => status.value.pinned) const showUpperBorder = computed(() => props.newer && !directReply.value) const showReplyTo = computed(() => !replyToMain.value && !directReply.value) @@ -75,6 +76,19 @@ const forceShow = ref(false) <div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 z--1 /> <slot name="meta"> + <!-- Pinned status --> + <div flex="~ col" justify-between> + <div + v-if="isPinned" + flex="~ gap2" items-center h-auto text-sm text-orange + m="is-5" p="t-1 is-5" + relative text-secondary ws-nowrap + > + <div i-ri:pushpin-line /> + <span>{{ $t('status.pinned') }}</span> + </div> + </div> + <!-- Line connecting to previous status --> <template v-if="status.inReplyToAccountId"> <StatusReplyingTo diff --git a/locales/en.json b/locales/en.json index f498f5a8..20520aa0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -633,6 +633,7 @@ "dismiss": "Dismiss", "read": "Read {0} description" }, + "pinned": "Pinned post", "poll": { "count": "{0} votes|{0} vote|{0} votes", "ends": "ends {0}", diff --git a/pages/[[server]]/@[account]/index/index.vue b/pages/[[server]]/@[account]/index/index.vue index 506f722d..e8ed7a47 100644 --- a/pages/[[server]]/@[account]/index/index.vue +++ b/pages/[[server]]/@[account]/index/index.vue @@ -10,11 +10,21 @@ const { t } = useI18n() const account = await fetchAccountByHandle(handle.value) +// we need to ensure `pinned === true` on status +// because this prop is appeared only on current account's posts +function applyPinned(statuses: mastodon.v1.Status[]) { + return statuses.map((status) => { + status.pinned = true + return status + }) +} + function reorderAndFilter(items: mastodon.v1.Status[]) { return reorderedTimeline(items, 'account') } -const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ limit: 30, excludeReplies: true }) +const pinnedPaginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ pinned: true }) +const postPaginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ limit: 30, excludeReplies: true }) if (account) { useHydratedHead({ @@ -26,6 +36,9 @@ if (account) { <template> <div> <AccountTabs /> - <TimelinePaginator :paginator="paginator" :preprocess="reorderAndFilter" context="account" :account="account" /> + <TimelinePaginator :paginator="pinnedPaginator" :preprocess="applyPinned" context="account" :account="account" :end-message="false" /> + <!-- Upper border --> + <div h="1px" w-auto bg-border mb-1 /> + <TimelinePaginator :paginator="postPaginator" :preprocess="reorderAndFilter" context="account" :account="account" /> </div> </template>