Merge branch 'main' into userquin/feat-remember-last-position
|
@ -2,5 +2,6 @@
|
|||
*.png
|
||||
*.ico
|
||||
*.toml
|
||||
*.patch
|
||||
https-dev-config/localhost.crt
|
||||
https-dev-config/localhost.key
|
||||
|
|
|
@ -141,6 +141,7 @@ This is the full list of entries that will be available for number formatting in
|
|||
- `account.followers_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `account.following_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `account.posts_count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `compose.drafts`: `{v}` for formatted number and `{n}` for raw number - **{v} should be use**
|
||||
- `notification.followed_you_count`: `{followers}` for formatted number and `{n}` for raw number - **{followers} should be use**
|
||||
- `status.poll.count`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**
|
||||
- `time_ago_options.*`: `{0}` for formatted number and `{n}` for raw number - **{0} should be use**: since numbers will be always small, we can also use `{n}`
|
||||
|
|
11
README.md
|
@ -17,7 +17,12 @@
|
|||
|
||||
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you to use if if you would like to help us building it. We appreciate your feedback and contributions. Check out the [Open Issues](https://github.com/elk-zone/elk/issues) and jump in the action. Join the [Elk discord server](https://chat.elk.zone) to chat with us and learn more about the project.
|
||||
|
||||
The client is deployed to [elk.zone](https://elk.zone), you can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friedns and invite others you think could be interested in helping to improve Elk.
|
||||
The client is deployed on:
|
||||
|
||||
- 🦌 Production: [elk.zone](https://elk.zone)
|
||||
- 🐙 Canary: [main.elk.zone](https://main.elk.zone) (deploys on every commit to `main` branch)
|
||||
|
||||
You can share screenshots on social media but we prefer you avoid sharing this URL directly until the app is more polished. Feel free to share the URL with your friends and invite others you think could be interested in helping to improve Elk.
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
@ -41,6 +46,10 @@ And all the companies and individuals sponsoring Elk Team members. If you're enj
|
|||
|
||||
We would also appreciate sponsoring other contributors to the Elk project. If someone helps you solve an issue or implement a feature you wanted, supporting them would help make this project and OS more sustainable.
|
||||
|
||||
## Roadmap
|
||||
|
||||
[Open board on Volta](https://volta.net/elk-zone/elk)
|
||||
|
||||
## Contributing
|
||||
|
||||
We're really excited that you're interested in contributing to Elk! Before submitting your contribution, please read through the following guide.
|
||||
|
|
|
@ -8,8 +8,9 @@ defineProps<{
|
|||
<div
|
||||
flex="~ gap1" items-center
|
||||
:class="{ 'border border-base rounded-md px-1': showLabel }"
|
||||
text-secondary-light text-xs
|
||||
text-secondary-light
|
||||
>
|
||||
<slot name="prepend" />
|
||||
<CommonTooltip :content="$t('account.bot')" :disabled="showLabel">
|
||||
<div i-ri:robot-line />
|
||||
</CommonTooltip>
|
||||
|
|
|
@ -23,7 +23,7 @@ defineOptions({
|
|||
<div flex="~ col" shrink pt-1 h-full overflow-hidden justify-center leading-none>
|
||||
<div flex="~" gap-2>
|
||||
<AccountDisplayName :account="account" font-bold line-clamp-1 ws-pre-wrap break-all text-lg />
|
||||
<AccountBotIndicator v-if="account.bot" />
|
||||
<AccountBotIndicator v-if="account.bot" text-xs />
|
||||
</div>
|
||||
<AccountHandle :account="account" text-secondary-light />
|
||||
</div>
|
||||
|
|
|
@ -34,6 +34,13 @@ const toggleBlockDomain = async () => {
|
|||
relationship!.domainBlocking = !relationship!.domainBlocking
|
||||
await masto.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
||||
}
|
||||
|
||||
const toggleReblogs = async () => {
|
||||
// TODO: Add confirmation
|
||||
|
||||
const showingReblogs = !relationship?.showingReblogs
|
||||
relationship = await masto.v1.accounts.follow(account.id, { reblogs: showingReblogs })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -68,6 +75,21 @@ const toggleBlockDomain = async () => {
|
|||
@click="directMessageUser(account)"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!relationship?.showingReblogs"
|
||||
icon="i-ri:repeat-line"
|
||||
:text="$t('menu.show_reblogs', [`@${account.acct}`])"
|
||||
:command="command"
|
||||
@click="toggleReblogs"
|
||||
/>
|
||||
<CommonDropdownItem
|
||||
v-else
|
||||
:text="$t('menu.hide_reblogs', [`@${account.acct}`])"
|
||||
icon="i-ri:repeat-line"
|
||||
:command="command"
|
||||
@click="toggleReblogs"
|
||||
/>
|
||||
|
||||
<CommonDropdownItem
|
||||
v-if="!relationship?.muting"
|
||||
:text="$t('menu.mute_account', [`@${account.acct}`])"
|
||||
|
|
|
@ -22,7 +22,7 @@ const tabs = $computed(() => [
|
|||
params: { server, account },
|
||||
},
|
||||
display: t('tab.posts_with_replies'),
|
||||
icon: 'i-ri:chat-3-line',
|
||||
icon: 'i-ri:chat-1-line',
|
||||
},
|
||||
{
|
||||
name: 'account-media',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script setup lang="ts" generic="T extends any, O extends any">
|
||||
<script setup lang="ts" generic="T, O">
|
||||
// @ts-expect-error missing types
|
||||
import { DynamicScroller } from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
@ -22,7 +22,9 @@ const {
|
|||
|
||||
defineSlots<{
|
||||
default: {
|
||||
items: T[]
|
||||
item: T
|
||||
index: number
|
||||
active?: boolean
|
||||
older?: T
|
||||
newer?: T // newer is undefined when index === 0
|
||||
|
@ -61,6 +63,8 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
|||
:active="active"
|
||||
:older="items[index + 1]"
|
||||
:newer="items[index - 1]"
|
||||
:index="index"
|
||||
:items="items"
|
||||
/>
|
||||
</DynamicScroller>
|
||||
</template>
|
||||
|
@ -71,6 +75,8 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
|
|||
:item="item"
|
||||
:older="items[index + 1]"
|
||||
:newer="items[index - 1]"
|
||||
:index="index"
|
||||
:items="items"
|
||||
/>
|
||||
</template>
|
||||
</slot>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
|
||||
const buildInfo = useRuntimeConfig().public.buildInfo
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
const buildTimeDate = new Date(buildInfo.time)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
|
||||
const { env } = buildInfo
|
||||
const { env } = useBuildInfo()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import { formatTimeAgo } from '@vueuse/core'
|
||||
|
||||
const route = useRoute()
|
||||
const { formatNumber } = useHumanReadableNumber()
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
let draftKey = $ref('home')
|
||||
|
||||
|
@ -25,7 +27,7 @@ onMounted(() => {
|
|||
<div text-right h-8>
|
||||
<VDropdown v-if="nonEmptyDrafts.length" placement="bottom-end">
|
||||
<button btn-text flex="inline center">
|
||||
Drafts ({{ nonEmptyDrafts.length }}) <div i-ri:arrow-down-s-line />
|
||||
{{ $t('compose.drafts', nonEmptyDrafts.length, { named: { v: formatNumber(nonEmptyDrafts.length) } }) }} <div aria-hidden="true" i-ri:arrow-down-s-line />
|
||||
</button>
|
||||
<template #popper="{ hide }">
|
||||
<div flex="~ col">
|
||||
|
@ -38,9 +40,11 @@ onMounted(() => {
|
|||
>
|
||||
<div>
|
||||
<div flex="~ gap-1" items-center>
|
||||
Draft <code>{{ key }}</code>
|
||||
<i18n-t keypath="compose.draft_title">
|
||||
<code>{{ key }}</code>
|
||||
</i18n-t>
|
||||
<span v-if="draft.lastUpdated" text-secondary text-sm>
|
||||
· {{ formatTimeAgo(new Date(draft.lastUpdated)) }}
|
||||
· {{ formatTimeAgo(new Date(draft.lastUpdated), timeAgoOptions) }}
|
||||
</span>
|
||||
</div>
|
||||
<div text-secondary>
|
||||
|
|
|
@ -12,7 +12,7 @@ defineProps<{
|
|||
<div flex="~ col gap1" shrink h-full overflow-hidden leading-none>
|
||||
<div flex="~" gap-2>
|
||||
<AccountDisplayName :account="account" line-clamp-1 ws-pre-wrap break-all text-base />
|
||||
<AccountBotIndicator v-if="account.bot" />
|
||||
<AccountBotIndicator v-if="account.bot" text-xs />
|
||||
</div>
|
||||
<AccountHandle text-sm :account="account" text-secondary-light />
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
const { as = 'button', command, disabled, content, icon } = defineProps<{
|
||||
text?: string | number
|
||||
content: string
|
||||
color: string
|
||||
|
@ -27,10 +27,10 @@ useCommand({
|
|||
scope: 'Actions',
|
||||
|
||||
order: -2,
|
||||
visible: () => props.command && !props.disabled,
|
||||
visible: () => command && !disabled,
|
||||
|
||||
name: () => props.content,
|
||||
icon: () => props.icon,
|
||||
name: () => content,
|
||||
icon: () => icon,
|
||||
|
||||
onActivate() {
|
||||
if (!checkLogin())
|
||||
|
@ -47,18 +47,27 @@ useCommand({
|
|||
|
||||
<template>
|
||||
<component
|
||||
:is="as || 'button'"
|
||||
:is="as"
|
||||
v-bind="$attrs" ref="el"
|
||||
w-fit flex gap-1 items-center
|
||||
rounded group :hover="hover"
|
||||
focus:outline-none cursor-pointer
|
||||
rounded group
|
||||
:hover=" !disabled ? hover : undefined"
|
||||
focus:outline-none
|
||||
:focus-visible="hover"
|
||||
:class="active ? [color] : 'text-secondary'"
|
||||
:class="active ? color : 'text-secondary'"
|
||||
:aria-label="content"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<CommonTooltip placement="bottom" :content="content">
|
||||
<div rounded-full p2 :group-hover="groupHover" :group-focus-visible="groupHover" group-focus-visible:ring="2 current">
|
||||
<div :class="[active && activeIcon ? activeIcon : icon, { 'pointer-events-none': disabled }]" />
|
||||
<div
|
||||
rounded-full p2
|
||||
v-bind="disabled ? {} : {
|
||||
'group-hover': groupHover,
|
||||
'group-focus-visible': groupHover,
|
||||
'group-focus-visible:ring': '2 current',
|
||||
}"
|
||||
>
|
||||
<div :class="active && activeIcon ? activeIcon : icon" />
|
||||
</div>
|
||||
</CommonTooltip>
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ const { details, command } = $(props)
|
|||
const {
|
||||
status,
|
||||
isLoading,
|
||||
canReblog,
|
||||
toggleBookmark,
|
||||
toggleFavourite,
|
||||
toggleReblog,
|
||||
|
@ -39,7 +40,7 @@ const reply = () => {
|
|||
:content="$t('action.reply')"
|
||||
:text="status.repliesCount || ''"
|
||||
color="text-blue" hover="text-blue" group-hover="bg-blue/10"
|
||||
icon="i-ri:chat-3-line"
|
||||
icon="i-ri:chat-1-line"
|
||||
:command="command"
|
||||
@click="reply"
|
||||
>
|
||||
|
@ -63,7 +64,7 @@ const reply = () => {
|
|||
icon="i-ri:repeat-line"
|
||||
active-icon="i-ri:repeat-fill"
|
||||
:active="!!status.reblogged"
|
||||
:disabled="isLoading.reblogged"
|
||||
:disabled="isLoading.reblogged || !canReblog"
|
||||
:command="command"
|
||||
@click="toggleReblog()"
|
||||
>
|
||||
|
|
|
@ -129,7 +129,7 @@ async function editStatus() {
|
|||
<template v-if="userSettings.zenMode">
|
||||
<CommonDropdownItem
|
||||
:text="$t('action.reply')"
|
||||
icon="i-ri:chat-3-line"
|
||||
icon="i-ri:chat-1-line"
|
||||
:command="command"
|
||||
@click="reply()"
|
||||
/>
|
||||
|
|
|
@ -65,14 +65,23 @@ const video = ref<HTMLVideoElement | undefined>()
|
|||
const prefersReducedMotion = usePreferredReducedMotion()
|
||||
|
||||
useIntersectionObserver(video, (entries) => {
|
||||
if (prefersReducedMotion.value === 'reduce')
|
||||
const ready = video.value?.dataset.ready === 'true'
|
||||
if (prefersReducedMotion.value === 'reduce') {
|
||||
if (ready && !video.value?.paused)
|
||||
video.value?.pause()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
if (entry.intersectionRatio <= 0.75)
|
||||
!video.value!.paused && video.value!.pause()
|
||||
else
|
||||
video.value!.play()
|
||||
if (entry.intersectionRatio <= 0.75) {
|
||||
ready && !video.value?.paused && video.value?.pause()
|
||||
}
|
||||
else {
|
||||
video.value?.play().then(() => {
|
||||
video.value!.dataset.ready = 'true'
|
||||
}).catch(noop)
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.75 })
|
||||
</script>
|
||||
|
|
|
@ -99,35 +99,35 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
|||
<!-- Upper border -->
|
||||
<div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 />
|
||||
|
||||
<!-- Line connecting to previous status -->
|
||||
<template v-if="status.inReplyToAccountId">
|
||||
<StatusReplyingTo
|
||||
v-if="showReplyTo"
|
||||
ml-6 pt-1 pl-5
|
||||
:status="status"
|
||||
:is-self-reply="isSelfReply"
|
||||
:class="faded ? 'text-secondary-light' : ''"
|
||||
/>
|
||||
<div flex="~ col gap-1" items-center pos="absolute top-0 left-0" w="20.5" z--1>
|
||||
<template v-if="showReplyTo">
|
||||
<div w="1px" h="0.5" border="x base" mt-3 />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
</template>
|
||||
<div w="1px" h-10 border="x base" />
|
||||
</div>
|
||||
</template>
|
||||
<slot name="meta">
|
||||
<!-- Line connecting to previous status -->
|
||||
<template v-if="status.inReplyToAccountId">
|
||||
<StatusReplyingTo
|
||||
v-if="showReplyTo"
|
||||
ml-6 pt-1 pl-5
|
||||
:status="status"
|
||||
:is-self-reply="isSelfReply"
|
||||
:class="faded ? 'text-secondary-light' : ''"
|
||||
/>
|
||||
<div flex="~ col gap-1" items-center pos="absolute top-0 left-0" w="20.5" z--1>
|
||||
<template v-if="showReplyTo">
|
||||
<div w="1px" h="0.5" border="x base" mt-3 />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
<div w="1px" h="0.5" border="x base" />
|
||||
</template>
|
||||
<div w="1px" h-10 border="x base" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Reblog status & Meta -->
|
||||
<div flex="~ col" justify-between>
|
||||
<slot name="meta">
|
||||
<!-- Reblog status -->
|
||||
<div flex="~ col" justify-between>
|
||||
<div
|
||||
v-if="rebloggedBy && !collapseRebloggedBy"
|
||||
flex="~" items-center
|
||||
p="t-1 b-0.5 x-1px"
|
||||
relative text-secondary ws-nowrap
|
||||
>
|
||||
<div i-ri:repeat-fill me-46px text-primary w-16px h-16px />
|
||||
<div i-ri:repeat-fill me-46px text-green w-16px h-16px />
|
||||
<div absolute top-1 ms-24px w-32px h-32px rounded-full>
|
||||
<AccountHoverWrapper :account="rebloggedBy">
|
||||
<NuxtLink :to="getAccountRoute(rebloggedBy)">
|
||||
|
@ -137,14 +137,14 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
|||
</div>
|
||||
<AccountInlineInfo font-bold :account="rebloggedBy" :avatar="false" text-sm />
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<div flex gap-3 :class="{ 'text-secondary': faded }">
|
||||
<!-- Avatar -->
|
||||
<div relative>
|
||||
<div v-if="collapseRebloggedBy" absolute flex items-center justify-center top--6px px-2px py-3px rounded-full bg-base>
|
||||
<div i-ri:repeat-fill text-primary w-16px h-16px />
|
||||
<div i-ri:repeat-fill text-green w-16px h-16px />
|
||||
</div>
|
||||
<AccountHoverWrapper :account="status.account">
|
||||
<NuxtLink :to="getAccountRoute(status.account)" rounded-full>
|
||||
|
|
|
@ -18,16 +18,18 @@ const account = isSelf ? computed(() => status.account) : useAccountById(status.
|
|||
v-if="status.inReplyToId"
|
||||
flex="~ gap2" items-center h-auto text-sm text-secondary
|
||||
:to="getStatusInReplyToRoute(status)"
|
||||
:title=" $t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])"
|
||||
:title="$t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])"
|
||||
text-blue saturate-50 hover:saturate-100
|
||||
>
|
||||
<template v-if="isSelfReply">
|
||||
<span btn-text p0 mb-1>{{ $t('status.show_full_thread') }}</span>
|
||||
<div i-ri-discuss-line text-blue />
|
||||
<span>{{ $t('status.show_full_thread') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div i-ri-chat-1-line />
|
||||
<div i-ri-chat-1-line text-blue />
|
||||
<i18n-t keypath="status.replying_to">
|
||||
<template v-if="account">
|
||||
<AccountInlineInfo :account="account" :link="false" mx1 />
|
||||
<AccountInlineInfo :account="account" :link="false" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('status.someone') }}
|
||||
|
|
|
@ -6,38 +6,43 @@ const { status } = defineProps<{
|
|||
status: mastodon.v1.Status
|
||||
}>()
|
||||
|
||||
const masto = useMasto()
|
||||
const { data: statusEdits } = useAsyncData(`status:history:${status.id}`, () => masto.v1.statuses.listHistory(status.id).then(res => res.reverse()))
|
||||
const paginator = useMasto().v1.statuses.listHistory(status.id)
|
||||
|
||||
const showHistory = (edit: mastodon.v1.StatusEdit) => {
|
||||
openEditHistoryDialog(edit)
|
||||
}
|
||||
const timeAgoOptions = useTimeAgoOptions()
|
||||
|
||||
const reverseHistory = (items: mastodon.v1.StatusEdit[]) =>
|
||||
[...items].reverse()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="statusEdits">
|
||||
<CommonDropdownItem
|
||||
v-for="(edit, idx) in statusEdits"
|
||||
:key="idx"
|
||||
px="0.5"
|
||||
@click="showHistory(edit)"
|
||||
>
|
||||
{{ getDisplayName(edit.account) }}
|
||||
<CommonPaginator :paginator="paginator" key-prop="createdAt" :preprocess="reverseHistory">
|
||||
<template #default="{ items, item, index }">
|
||||
<CommonDropdownItem
|
||||
px="0.5"
|
||||
@click="showHistory(item)"
|
||||
>
|
||||
{{ getDisplayName(item.account) }}
|
||||
|
||||
<template v-if="idx === statusEdits.length - 1">
|
||||
<i18n-t keypath="status_history.created">
|
||||
{{ formatTimeAgo(new Date(edit.createdAt), timeAgoOptions) }}
|
||||
<template v-if="index === items.length - 1">
|
||||
<i18n-t keypath="status_history.created">
|
||||
{{ formatTimeAgo(new Date(item.createdAt), timeAgoOptions) }}
|
||||
</i18n-t>
|
||||
</template>
|
||||
<i18n-t v-else keypath="status_history.edited">
|
||||
{{ formatTimeAgo(new Date(item.createdAt), timeAgoOptions) }}
|
||||
</i18n-t>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i18n-t keypath="status_history.edited">
|
||||
{{ formatTimeAgo(new Date(edit.createdAt), timeAgoOptions) }}
|
||||
</i18n-t>
|
||||
</template>
|
||||
</CommonDropdownItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div i-ri:loader-2-fill animate-spin text-2xl ma />
|
||||
</template>
|
||||
</CommonDropdownItem>
|
||||
</template>
|
||||
<template #loading>
|
||||
<StatusEditHistorySkeleton />
|
||||
<StatusEditHistorySkeleton op50 />
|
||||
<StatusEditHistorySkeleton op25 />
|
||||
</template>
|
||||
<template #done>
|
||||
<span />
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
||||
|
|
3
components/status/edit/StatusEditHistorySkeleton.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<div class="skeleton-loading-bg" h-5 w-full rounded my2 />
|
||||
</template>
|
22
components/tag/TagCardPaginator.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
|
||||
const { paginator } = defineProps<{
|
||||
paginator: Paginator<mastodon.v1.Tag[], mastodon.DefaultPaginationParams>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonPaginator :paginator="paginator" key-prop="name">
|
||||
<template #default="{ item }">
|
||||
<TagCard :tag="item" border="b base" />
|
||||
</template>
|
||||
<template #loading>
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op25 />
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
|
@ -1,13 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineProps<{
|
||||
timelines: mastodon.v1.Status[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="status of timelines" :key="status.id">
|
||||
<StatusCard :status="status" border="t base" />
|
||||
</template>
|
||||
</template>
|
|
@ -1,3 +1,5 @@
|
|||
import type { BuildInfo } from '~~/types'
|
||||
|
||||
export interface Team {
|
||||
github: string
|
||||
display: string
|
||||
|
@ -31,3 +33,7 @@ export const teams: Team[] = [
|
|||
mastodon: 'sxzz@webtoo.ls',
|
||||
},
|
||||
].sort(() => Math.random() - 0.5)
|
||||
|
||||
export function useBuildInfo() {
|
||||
return useRuntimeConfig().public.buildInfo as BuildInfo
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Accoun
|
|||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
const domain = currentInstance.value?.domain
|
||||
const domain = currentInstance.value?.uri
|
||||
const promise = useMasto().v1.accounts.fetch(id)
|
||||
.then((r) => {
|
||||
if (r.acct && !r.acct.includes('@') && domain)
|
||||
|
@ -60,7 +60,7 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
|
|||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
const domain = currentInstance.value?.domain
|
||||
const domain = currentInstance.value?.uri
|
||||
const account = useMasto().v1.accounts.lookup({ acct })
|
||||
.then((r) => {
|
||||
if (r.acct && !r.acct.includes('@') && domain)
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface ContentParseOptions {
|
|||
markdown?: boolean
|
||||
replaceUnicodeEmoji?: boolean
|
||||
astTransforms?: Transform[]
|
||||
convertMentionLink?: boolean
|
||||
}
|
||||
|
||||
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
|
||||
|
@ -53,6 +54,7 @@ export function parseMastodonHTML(
|
|||
const {
|
||||
markdown = true,
|
||||
replaceUnicodeEmoji = true,
|
||||
convertMentionLink = false,
|
||||
} = options
|
||||
|
||||
if (markdown) {
|
||||
|
@ -77,6 +79,9 @@ export function parseMastodonHTML(
|
|||
if (markdown)
|
||||
transforms.push(transformMarkdown)
|
||||
|
||||
if (convertMentionLink)
|
||||
transforms.push(transformMentionLink)
|
||||
|
||||
transforms.push(replaceCustomEmoji(options.emojis || {}))
|
||||
|
||||
transforms.push(transformParagraphs)
|
||||
|
@ -92,6 +97,7 @@ export function convertMastodonHTML(html: string, customEmojis: Record<string, m
|
|||
emojis: customEmojis,
|
||||
markdown: true,
|
||||
replaceUnicodeEmoji: false,
|
||||
convertMentionLink: true,
|
||||
})
|
||||
return render(tree)
|
||||
}
|
||||
|
@ -316,7 +322,7 @@ const _markdownReplacements: [RegExp, (c: (string | Node)[]) => Node][] = [
|
|||
[/~~(.*?)~~/g, c => h('del', null, c)],
|
||||
[/`([^`]+?)`/g, c => h('code', null, c)],
|
||||
// transform @username@twitter.com as links
|
||||
[/(?:^|\b)@([a-zA-Z0-9_]+)@twitter\.com(?:$|\b)/gi, c => h('a', { href: `https://twitter.com/${c[0]}`, target: '_blank', class: 'mention external' }, `@${c[0]}@twitter.com`)],
|
||||
[/\B@([a-zA-Z0-9_]+)@twitter\.com\b/gi, c => h('a', { href: `https://twitter.com/${c}`, target: '_blank', class: 'mention external' }, `@${c}@twitter.com`)],
|
||||
]
|
||||
|
||||
function _markdownProcess(value: string) {
|
||||
|
@ -360,3 +366,19 @@ function transformParagraphs(node: Node): Node | Node[] {
|
|||
return [node, h('p')]
|
||||
return node
|
||||
}
|
||||
|
||||
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
|
||||
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
|
||||
const href = node.attributes.href
|
||||
if (href) {
|
||||
const matchUser = href.match(UserLinkRE)
|
||||
if (matchUser) {
|
||||
const [, server, username] = matchUser
|
||||
const handle = `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
||||
// convert to TipTap mention node
|
||||
return h('span', { 'data-type': 'mention', 'data-id': handle }, handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export function getServerName(account: mastodon.v1.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?.domain || ''
|
||||
return currentInstance.value?.uri || ''
|
||||
}
|
||||
|
||||
export function getFullHandle(account: mastodon.v1.Account) {
|
||||
|
@ -38,7 +38,7 @@ export function toShortHandle(fullHandle: string) {
|
|||
|
||||
export function extractAccountHandle(account: mastodon.v1.Account) {
|
||||
let handle = getFullHandle(account).slice(1)
|
||||
const uri = currentInstance.value?.domain ?? currentServer.value
|
||||
const uri = currentInstance.value?.uri ?? currentServer.value
|
||||
if (currentInstance.value && handle.endsWith(`@${uri}`))
|
||||
handle = handle.slice(0, -uri.length - 1)
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { MaybeRef } from '@vueuse/core'
|
||||
import type { MaybeComputedRef } from '@vueuse/core'
|
||||
import type { Paginator, mastodon } from 'masto'
|
||||
import type { RouteLocation } from 'vue-router'
|
||||
|
||||
export interface UseSearchOptions {
|
||||
type?: MaybeRef<mastodon.v2.SearchType>
|
||||
}
|
||||
export type UseSearchOptions = MaybeComputedRef<
|
||||
Partial<Omit<mastodon.v1.SearchParams, keyof mastodon.DefaultPaginationParams | 'q'>>
|
||||
>
|
||||
|
||||
export interface BuildSearchResult<K extends keyof any, T> {
|
||||
id: string
|
||||
|
@ -20,7 +20,7 @@ export type StatusSearchResult = BuildSearchResult<'status', mastodon.v1.Status>
|
|||
|
||||
export type SearchResult = HashTagSearchResult | AccountSearchResult | StatusSearchResult
|
||||
|
||||
export function useSearch(query: MaybeRef<string>, options?: UseSearchOptions) {
|
||||
export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOptions = {}) {
|
||||
const done = ref(false)
|
||||
const masto = useMasto()
|
||||
const loading = ref(false)
|
||||
|
@ -71,9 +71,9 @@ export function useSearch(query: MaybeRef<string>, options?: UseSearchOptions) {
|
|||
* but that doesn't seem to be the case. So instead we just create a new paginator with the new params.
|
||||
*/
|
||||
paginator = masto.v2.search({
|
||||
q: unref(query),
|
||||
q: resolveUnref(query),
|
||||
...resolveUnref(options),
|
||||
resolve: !!currentUser.value,
|
||||
type: unref(options?.type),
|
||||
})
|
||||
const nextResults = await paginator.next()
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
// check login
|
||||
if (!checkLogin())
|
||||
return
|
||||
|
||||
isLoading[action] = true
|
||||
fetchNewStatus().then((newStatus) => {
|
||||
Object.assign(status, newStatus)
|
||||
|
@ -44,6 +45,12 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
if (countField)
|
||||
status[countField] += status[action] ? 1 : -1
|
||||
}
|
||||
|
||||
const canReblog = $computed(() =>
|
||||
status.visibility !== 'direct'
|
||||
&& (status.visibility !== 'private' || status.account.id === currentUser.value?.account.id),
|
||||
)
|
||||
|
||||
const toggleReblog = () => toggleStatusAction(
|
||||
'reblogged',
|
||||
() => masto.v1.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
|
||||
|
@ -79,6 +86,7 @@ export function useStatusActions(props: StatusActionsProps) {
|
|||
return {
|
||||
status: $$(status),
|
||||
isLoading: $$(isLoading),
|
||||
canReblog: $$(canReblog),
|
||||
toggleMute,
|
||||
toggleReblog,
|
||||
toggleFavourite,
|
||||
|
|
|
@ -54,12 +54,17 @@ function mentionHTML(acct: string) {
|
|||
return `<span data-type="mention" data-id="${acct}" contenteditable="false">@${acct}</span>`
|
||||
}
|
||||
|
||||
export function getReplyDraft(status: mastodon.v1.Status) {
|
||||
const accountsToMention: string[] = []
|
||||
function getAccountsToMention(status: mastodon.v1.Status) {
|
||||
const userId = currentUser.value?.account.id
|
||||
const accountsToMention: string[] = []
|
||||
if (status.account.id !== userId)
|
||||
accountsToMention.push(status.account.acct)
|
||||
accountsToMention.push(...(status.mentions.filter(mention => mention.id !== userId).map(mention => mention.acct)))
|
||||
return accountsToMention
|
||||
}
|
||||
|
||||
export function getReplyDraft(status: mastodon.v1.Status) {
|
||||
const accountsToMention = getAccountsToMention(status)
|
||||
return {
|
||||
key: `reply-${status.id}`,
|
||||
draft: () => {
|
||||
|
@ -77,7 +82,9 @@ export const isEmptyDraft = (draft: Draft | null | undefined) => {
|
|||
return true
|
||||
const { params, attachments } = draft
|
||||
const status = params.status || ''
|
||||
return (status.length === 0 || status === '<p></p>')
|
||||
const text = htmlToText(status).trim().replace(/^(@\S+\s?)+/, '').trim()
|
||||
|
||||
return (text.length === 0)
|
||||
&& attachments.length === 0
|
||||
&& (params.spoilerText || '').length === 0
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { Directions } from 'vue-i18n-routing'
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
import type { LocaleObject } from '#i18n'
|
||||
|
||||
export function setupPageHeader() {
|
||||
const { locale, locales, t } = useI18n()
|
||||
const buildInfo = useBuildInfo()
|
||||
|
||||
const localeMap = (locales.value as LocaleObject[]).reduce((acc, l) => {
|
||||
acc[l.code!] = l.dir ?? 'auto'
|
||||
|
|
|
@ -29,7 +29,13 @@ export const HashtagSuggestion: Partial<SuggestionOptions> = {
|
|||
if (query.length === 0)
|
||||
return []
|
||||
|
||||
const paginator = useMasto().v2.search({ q: query, type: 'hashtags', limit: 25, resolve: true })
|
||||
const paginator = useMasto().v2.search({
|
||||
q: query,
|
||||
type: 'hashtags',
|
||||
limit: 25,
|
||||
resolve: false,
|
||||
excludeUnreviewed: true,
|
||||
})
|
||||
const results = await paginator.next()
|
||||
|
||||
return results.value.hashtags
|
||||
|
|
|
@ -40,7 +40,7 @@ const initializeUsers = async (): Promise<Ref<UserLogin[]> | RemovableRef<UserLo
|
|||
}
|
||||
|
||||
const users = await initializeUsers()
|
||||
const instances = useLocalStorage<Record<string, mastodon.v2.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||
const instances = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
||||
|
||||
export const currentUser = computed<UserLogin | undefined>(() => {
|
||||
|
@ -53,8 +53,8 @@ export const currentUser = computed<UserLogin | undefined>(() => {
|
|||
return users.value[0]
|
||||
})
|
||||
|
||||
const publicInstance = ref<mastodon.v2.Instance | null>(null)
|
||||
export const currentInstance = computed<null | mastodon.v2.Instance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value)
|
||||
const publicInstance = ref<mastodon.v1.Instance | null>(null)
|
||||
export const currentInstance = computed<null | mastodon.v1.Instance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value)
|
||||
|
||||
export const publicServer = ref('')
|
||||
export const currentServer = computed<string>(() => currentUser.value?.server || publicServer.value)
|
||||
|
@ -91,7 +91,7 @@ if (process.client) {
|
|||
}
|
||||
|
||||
export const currentUserHandle = computed(() => currentUser.value?.account.id
|
||||
? `${currentUser.value.account.acct}@${currentInstance.value?.domain || currentServer.value}`
|
||||
? `${currentUser.value.account.acct}@${currentInstance.value?.uri || currentServer.value}`
|
||||
: '[anonymous]',
|
||||
)
|
||||
|
||||
|
@ -111,14 +111,14 @@ async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: mastodon.
|
|||
|
||||
if (!user?.token) {
|
||||
publicServer.value = server
|
||||
publicInstance.value = await masto.v2.instance.fetch()
|
||||
publicInstance.value = await masto.v1.instances.fetch()
|
||||
}
|
||||
|
||||
else {
|
||||
try {
|
||||
const [me, instance, pushSubscription] = await Promise.all([
|
||||
masto.v1.accounts.verifyCredentials(),
|
||||
masto.v2.instance.fetch(),
|
||||
masto.v1.instances.fetch(),
|
||||
// if PWA is not enabled, don't get push subscription
|
||||
useRuntimeConfig().public.pwaEnabled
|
||||
// we get 404 response instead empty data
|
||||
|
@ -127,7 +127,7 @@ async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: mastodon.
|
|||
])
|
||||
|
||||
if (!me.acct.includes('@'))
|
||||
me.acct = `${me.acct}@${instance.domain}`
|
||||
me.acct = `${me.acct}@${instance.uri}`
|
||||
|
||||
user.account = me
|
||||
user.pushSubscription = pushSubscription
|
||||
|
@ -169,7 +169,7 @@ export function setAccountInfo(userId: string, account: mastodon.v1.AccountCrede
|
|||
export async function pullMyAccountInfo() {
|
||||
const account = await useMasto().v1.accounts.verifyCredentials()
|
||||
if (!account.acct.includes('@'))
|
||||
account.acct = `${account.acct}@${currentInstance.value!.domain}`
|
||||
account.acct = `${account.acct}@${currentInstance.value!.uri}`
|
||||
|
||||
setAccountInfo(currentUserId.value, account)
|
||||
cacheAccount(account, currentServer.value, true)
|
||||
|
@ -180,9 +180,6 @@ export function getUsersIndexByUserId(userId: string) {
|
|||
}
|
||||
|
||||
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
|
||||
if (!user.pushSubscription)
|
||||
return
|
||||
|
||||
// clear push subscription
|
||||
user.pushSubscription = undefined
|
||||
const { acct } = user.account
|
||||
|
@ -192,9 +189,12 @@ export async function removePushNotificationData(user: UserLogin, fromSWPushMana
|
|||
delete useLocalStorage<PushNotificationPolicy>(STORAGE_KEY_NOTIFICATION_POLICY, {}).value[acct]
|
||||
|
||||
const pwaEnabled = useRuntimeConfig().public.pwaEnabled
|
||||
const pwa = useNuxtApp().$pwa
|
||||
const registrationError = pwa?.registrationError === true
|
||||
const unregister = pwaEnabled && !registrationError && pwa?.registrationError === true && fromSWPushManager
|
||||
|
||||
// we remove the sw push manager if required and there are no more accounts with subscriptions
|
||||
if (pwaEnabled && fromSWPushManager && (users.value.length === 0 || users.value.every(u => !u.pushSubscription))) {
|
||||
if (unregister && (users.value.length === 0 || users.value.every(u => !u.pushSubscription))) {
|
||||
// clear sw push subscription
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready
|
||||
|
@ -334,7 +334,7 @@ export function clearUserLocalStorage(account?: mastodon.v1.Account) {
|
|||
if (!account)
|
||||
return
|
||||
|
||||
const id = `${account.acct}@${currentInstance.value?.domain || currentServer.value}`
|
||||
const id = `${account.acct}@${currentInstance.value?.uri || currentServer.value}`
|
||||
// @ts-expect-error bind value to the function
|
||||
;(useUserLocalStorage._ as Map<string, Ref<Record<string, any>>>).forEach((storage) => {
|
||||
if (storage.value[id])
|
||||
|
|
|
@ -88,6 +88,10 @@
|
|||
"not_found": "404 Not Found",
|
||||
"offline_desc": "Seems like you are offline. Please check your network connection."
|
||||
},
|
||||
"compose": {
|
||||
"draft_title": "Draft {0}",
|
||||
"drafts": "Drafts ({v})"
|
||||
},
|
||||
"conversation": {
|
||||
"with": "with"
|
||||
},
|
||||
|
|
|
@ -97,6 +97,10 @@
|
|||
"not_found": "404 Not Found",
|
||||
"offline_desc": "Seems like you are offline. Please check your network connection."
|
||||
},
|
||||
"compose": {
|
||||
"draft_title": "Draft {0}",
|
||||
"drafts": "Drafts ({v})"
|
||||
},
|
||||
"conversation": {
|
||||
"with": "with"
|
||||
},
|
||||
|
@ -135,12 +139,14 @@
|
|||
},
|
||||
"direct_message_account": "Direct message {0}",
|
||||
"edit": "Edit",
|
||||
"hide_reblogs": "Hide boosts from {0}",
|
||||
"mention_account": "Mention {0}",
|
||||
"mute_account": "Mute {0}",
|
||||
"mute_conversation": "Mute this post",
|
||||
"open_in_original_site": "Open in original site",
|
||||
"pin_on_profile": "Pin on profile",
|
||||
"share_post": "Share this post",
|
||||
"show_reblogs": "Show boosts from {0}",
|
||||
"show_untranslated": "Show untranslated",
|
||||
"toggle_theme": {
|
||||
"dark": "Toggle dark mode",
|
||||
|
|
|
@ -88,6 +88,10 @@
|
|||
"not_found": "404 No Encontrado",
|
||||
"offline_desc": "Al parecer estás fuera de línea. Por favor, comprueba tu conexión a la red."
|
||||
},
|
||||
"compose": {
|
||||
"draft_title": "Borrador {0}",
|
||||
"drafts": "Borradores ({v})"
|
||||
},
|
||||
"conversation": {
|
||||
"with": "con"
|
||||
},
|
||||
|
|
33
modules/build-env.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createResolver, defineNuxtModule } from '@nuxt/kit'
|
||||
import { isCI } from 'std-env'
|
||||
import { getEnv, version } from '../config/env'
|
||||
import type { BuildInfo } from '~/types'
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'elk:build-env',
|
||||
},
|
||||
async setup(_options, nuxt) {
|
||||
const { env, commit, branch } = await getEnv()
|
||||
const buildInfo: BuildInfo = {
|
||||
version,
|
||||
time: +Date.now(),
|
||||
commit,
|
||||
branch,
|
||||
env,
|
||||
}
|
||||
|
||||
nuxt.options.runtimeConfig.public.env = env
|
||||
nuxt.options.runtimeConfig.public.buildInfo = buildInfo
|
||||
|
||||
nuxt.hook('nitro:config', (config) => {
|
||||
config.publicAssets = config.publicAssets || []
|
||||
if (env === 'dev')
|
||||
config.publicAssets.push({ dir: resolve('../public-dev') })
|
||||
else if (env === 'canary' || env === 'preview' || !isCI)
|
||||
config.publicAssets.push({ dir: resolve('../public-staging') })
|
||||
})
|
||||
},
|
||||
})
|
|
@ -1,33 +0,0 @@
|
|||
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
|
||||
import { getEnv, version } from '../config/env'
|
||||
import type { BuildInfo } from '~/types'
|
||||
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'elk:build-info',
|
||||
},
|
||||
async setup(_options, nuxt) {
|
||||
const { env, commit, branch } = await getEnv()
|
||||
nuxt.options.runtimeConfig.public.env = env
|
||||
|
||||
const buildInfo: BuildInfo = {
|
||||
version,
|
||||
time: +Date.now(),
|
||||
commit,
|
||||
branch,
|
||||
env,
|
||||
}
|
||||
|
||||
addVitePlugin({
|
||||
name: 'elk:build-info',
|
||||
resolveId(id) {
|
||||
if (id === 'virtual:build-info')
|
||||
return id
|
||||
},
|
||||
load(id) {
|
||||
if (id === 'virtual:build-info')
|
||||
return `export const buildInfo = ${JSON.stringify(buildInfo, null, 2)}`
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
|
@ -1,11 +1,9 @@
|
|||
import { createResolver } from '@nuxt/kit'
|
||||
import Inspect from 'vite-plugin-inspect'
|
||||
import { isCI, isDevelopment } from 'std-env'
|
||||
import { isPreview } from './config/env'
|
||||
import { i18n } from './config/i18n'
|
||||
import { pwa } from './config/pwa'
|
||||
import { isPreview } from './config/env'
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
import type { BuildInfo } from './types'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
typescript: {
|
||||
|
@ -26,7 +24,7 @@ export default defineNuxtConfig({
|
|||
'@nuxtjs/color-mode',
|
||||
'~/modules/purge-comments',
|
||||
'~/modules/setup-components',
|
||||
'~/modules/build-info',
|
||||
'~/modules/build-env',
|
||||
'~/modules/pwa/index', // change to '@vite-pwa/nuxt' once released and remove pwa module
|
||||
'~/modules/tauri/index',
|
||||
],
|
||||
|
@ -93,7 +91,8 @@ export default defineNuxtConfig({
|
|||
inviteToken: '',
|
||||
},
|
||||
public: {
|
||||
env: '', // set in build-info module
|
||||
env: '', // set in build-env module
|
||||
buildInfo: {} as BuildInfo, // set in build-env module
|
||||
pwaEnabled: !isDevelopment || process.env.VITE_DEV_PWA === 'true',
|
||||
translateApi: '',
|
||||
defaultServer: 'mas.to',
|
||||
|
@ -112,9 +111,6 @@ export default defineNuxtConfig({
|
|||
},
|
||||
},
|
||||
nitro: {
|
||||
publicAssets: [
|
||||
...(!isCI || isPreview ? [{ dir: resolve('./public-dev') }] : []),
|
||||
],
|
||||
prerender: {
|
||||
crawlLinks: false,
|
||||
routes: ['/'],
|
||||
|
|
|
@ -113,6 +113,11 @@
|
|||
"vue-tsc": "^1.0.22",
|
||||
"workbox-window": "^6.5.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"mlly@1.0.0": "patches/mlly@1.0.0.patch"
|
||||
}
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
},
|
||||
|
|
|
@ -4,10 +4,9 @@ import { STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS } from '~~/constants'
|
|||
const { t } = useI18n()
|
||||
|
||||
const masto = useMasto()
|
||||
const { data, pending, error } = useLazyAsyncData(
|
||||
async () => masto.v1.trends.listTags({ limit: 20 }),
|
||||
{ immediate: true },
|
||||
)
|
||||
const paginator = masto.v1.trends.listTags({
|
||||
limit: 20,
|
||||
})
|
||||
|
||||
const hideTagsTips = useLocalStorage(STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS, false)
|
||||
|
||||
|
@ -17,28 +16,9 @@ useHeadFixed({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<CommonAlert v-if="isHydrated && !hideTagsTips && data && data.length" @close="hideTagsTips = true">
|
||||
<CommonAlert v-if="!hideTagsTips" @close="hideTagsTips = true">
|
||||
<p>{{ $t('tooltip.explore_tags_intro') }}</p>
|
||||
</CommonAlert>
|
||||
|
||||
<div v-if="data && data.length">
|
||||
<TagCard v-for="item of data" :key="item.name" :tag="item" border="b base" />
|
||||
|
||||
<div p5 text-center text-secondary-light italic>
|
||||
{{ $t('common.end_of_list') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="pending">
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op50 />
|
||||
<TagCardSkeleton border="b base" op25 />
|
||||
</div>
|
||||
<div v-else-if="error" p5 text-center text-red italic>
|
||||
{{ $t('common.error') }}: {{ error }}
|
||||
</div>
|
||||
<div v-else p5 text-center text-secondary italic>
|
||||
{{ $t('error.explore-list-empty') }}
|
||||
</div>
|
||||
<TagCardPaginator v-bind="{ paginator }" />
|
||||
</template>
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
const { t } = useI18n()
|
||||
|
||||
// limit: 20 is the default configuration of the official client
|
||||
const masto = useMasto()
|
||||
const { data, pending, error } = useLazyAsyncData(
|
||||
async () => masto.v2.suggestions.list({ limit: 20 }),
|
||||
{ immediate: true },
|
||||
)
|
||||
const paginator = useMasto().v2.suggestions.list({ limit: 20 })
|
||||
|
||||
useHeadFixed({
|
||||
title: () => `${t('tab.for_you')} | ${t('nav.explore')}`,
|
||||
|
@ -14,29 +10,19 @@ useHeadFixed({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="data && data.length">
|
||||
<AccountBigCard
|
||||
v-for="suggestion of data"
|
||||
:key="suggestion.account.id"
|
||||
:account="suggestion.account"
|
||||
as="router-link"
|
||||
:to="getAccountRoute(suggestion.account)"
|
||||
border="b base"
|
||||
/>
|
||||
|
||||
<div p5 text-center text-secondary-light italic>
|
||||
{{ $t('common.end_of_list') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="pending">
|
||||
<AccountBigCardSkeleton border="b base" />
|
||||
<AccountBigCardSkeleton border="b base" op50 />
|
||||
<AccountBigCardSkeleton border="b base" op25 />
|
||||
</div>
|
||||
<div v-else-if="error" p5 text-center text-red italic>
|
||||
{{ $t('common.error') }}: {{ error }}
|
||||
</div>
|
||||
<div v-else p5 text-center text-secondary italic>
|
||||
{{ $t('common.not_found') }}
|
||||
</div>
|
||||
<CommonPaginator :paginator="paginator" key-prop="account">
|
||||
<template #default="{ item }">
|
||||
<AccountBigCard
|
||||
:account="item.account"
|
||||
as="router-link"
|
||||
:to="getAccountRoute(item.account)"
|
||||
border="b base"
|
||||
/>
|
||||
</template>
|
||||
<template #loading>
|
||||
<AccountBigCardSkeleton border="b base" />
|
||||
<AccountBigCardSkeleton border="b base" op50 />
|
||||
<AccountBigCardSkeleton border="b base" op25 />
|
||||
</template>
|
||||
</CommonPaginator>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { buildInfo } from 'virtual:build-info'
|
||||
|
||||
const buildInfo = useBuildInfo()
|
||||
const { t } = useI18n()
|
||||
|
||||
useHeadFixed({
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import type { mastodon } from 'masto'
|
||||
import { satisfies } from 'semver'
|
||||
import { useForm } from 'slimeform'
|
||||
import { parse } from 'ultrahtml'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
// Keep alive the form page will reduce raw data timeliness and its status timeliness
|
||||
keepalive: false,
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
@ -43,21 +42,17 @@ const { form, reset, submitter, dirtyFields, isError } = useForm({
|
|||
|
||||
fieldsAttributes,
|
||||
|
||||
bot: account?.bot ?? false,
|
||||
|
||||
// These look more like account and privacy settings than appearance settings
|
||||
// discoverable: false,
|
||||
// bot: false,
|
||||
// locked: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
onMastoInit(async () => {
|
||||
// Keep the information to be edited up to date
|
||||
await pullMyAccountInfo()
|
||||
reset()
|
||||
})
|
||||
|
||||
const isCanSubmit = computed(() => !isError.value && !isEmptyObject(dirtyFields.value))
|
||||
const isDirty = $computed(() => !isEmptyObject(dirtyFields.value))
|
||||
const isCanSubmit = computed(() => !isError.value && isDirty)
|
||||
|
||||
const { submit, submitting } = submitter(async ({ dirtyFields }) => {
|
||||
if (!isCanSubmit.value)
|
||||
|
@ -76,6 +71,16 @@ const { submit, submitting } = submitter(async ({ dirtyFields }) => {
|
|||
setAccountInfo(account!.id, res.account)
|
||||
reset()
|
||||
})
|
||||
|
||||
const refreshInfo = async () => {
|
||||
// Keep the information to be edited up to date
|
||||
await pullMyAccountInfo()
|
||||
if (!isDirty)
|
||||
reset()
|
||||
}
|
||||
|
||||
onMastoInit(refreshInfo)
|
||||
onReactivated(refreshInfo)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -107,15 +112,25 @@ const { submit, submitting } = submitter(async ({ dirtyFields }) => {
|
|||
rounded-full border="bg-base 4"
|
||||
w="sm:30 24" min-w="sm:30 24" h="sm:30 24"
|
||||
/>
|
||||
<div flex="~ col gap1" self-end>
|
||||
</div>
|
||||
<CommonCropImage v-model="form.avatar" />
|
||||
|
||||
<div px4>
|
||||
<div flex justify-between>
|
||||
<AccountDisplayName
|
||||
:account="{ ...account, displayName: form.displayName }"
|
||||
font-bold sm:text-2xl text-xl
|
||||
/>
|
||||
<AccountHandle :account="account" />
|
||||
<label>
|
||||
<AccountBotIndicator show-label px2 py1>
|
||||
<template #prepend>
|
||||
<input v-model="form.bot" type="checkbox">
|
||||
</template>
|
||||
</AccountBotIndicator>
|
||||
</label>
|
||||
</div>
|
||||
<AccountHandle :account="account" />
|
||||
</div>
|
||||
<CommonCropImage v-model="form.avatar" />
|
||||
</div>
|
||||
|
||||
<div px4 py3 space-y-5>
|
||||
|
|
17
patches/mlly@1.0.0.patch
Normal file
|
@ -0,0 +1,17 @@
|
|||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 6b5fb1566bee73cefdf165519146604b59ebe7a5..8df0f81f3df4c13bf06b003c472c46db9772db91 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -972,10 +972,10 @@ function fileURLToPath(id) {
|
||||
}
|
||||
const INVALID_CHAR_RE = /[\u0000-\u001F"#$&*+,/:;<=>?@[\]^`{|}\u007F]+/g;
|
||||
function sanitizeURIComponent(name = "", replacement = "_") {
|
||||
- return name.replace(INVALID_CHAR_RE, replacement);
|
||||
+ return name.replace(INVALID_CHAR_RE, replacement).replace(/%../g, replacement);
|
||||
}
|
||||
function sanitizeFilePath(filePath = "") {
|
||||
- return filePath.split(/[/\\]/g).map((p) => sanitizeURIComponent(p)).join("/").replace(/^([A-Za-z])_\//, "$1:/");
|
||||
+ return filePath.replace(/[?#].*$/, '').split(/[/\\]/g).map((p) => sanitizeURIComponent(p)).join("/").replace(/^([A-Za-z])_\//, "$1:/");
|
||||
}
|
||||
function normalizeid(id) {
|
||||
if (typeof id !== "string") {
|
|
@ -2,30 +2,48 @@ import { useRegisterSW } from 'virtual:pwa-register/vue'
|
|||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const online = useOnline()
|
||||
const registrationError = ref(false)
|
||||
const swActivated = ref(false)
|
||||
|
||||
const registerPeriodicSync = (swUrl: string, r: ServiceWorkerRegistration) => {
|
||||
setInterval(async () => {
|
||||
if (!online.value)
|
||||
return
|
||||
|
||||
const resp = await fetch(swUrl, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'cache': 'no-store',
|
||||
'cache-control': 'no-cache',
|
||||
},
|
||||
})
|
||||
|
||||
if (resp?.status === 200)
|
||||
await r.update()
|
||||
}, 60 * 60 * 1000 /* 1 hour */)
|
||||
}
|
||||
|
||||
const {
|
||||
needRefresh, updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
immediate: true,
|
||||
onRegisterError() {
|
||||
registrationError.value = true
|
||||
},
|
||||
onRegisteredSW(swUrl, r) {
|
||||
if (!r || r.installing)
|
||||
return
|
||||
|
||||
setInterval(async () => {
|
||||
if (!online.value)
|
||||
return
|
||||
|
||||
const resp = await fetch(swUrl, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'cache': 'no-store',
|
||||
'cache-control': 'no-cache',
|
||||
},
|
||||
// should add support in pwa plugin
|
||||
if (r?.active?.state === 'activated') {
|
||||
swActivated.value = true
|
||||
registerPeriodicSync(swUrl, r)
|
||||
}
|
||||
else if (r?.installing) {
|
||||
r.installing.addEventListener('statechange', (e) => {
|
||||
const sw = e.target as ServiceWorker
|
||||
swActivated.value = sw.state === 'activated'
|
||||
if (swActivated.value)
|
||||
registerPeriodicSync(swUrl, r)
|
||||
})
|
||||
|
||||
if (resp?.status === 200)
|
||||
await r.update()
|
||||
}, 60 * 60 * 1000 /* 1 hour */)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -36,6 +54,8 @@ export default defineNuxtPlugin(() => {
|
|||
return {
|
||||
provide: {
|
||||
pwa: reactive({
|
||||
swActivated,
|
||||
registrationError,
|
||||
needRefresh,
|
||||
updateServiceWorker,
|
||||
close,
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
lockfileVersion: 5.4
|
||||
|
||||
patchedDependencies:
|
||||
mlly@1.0.0:
|
||||
hash: afe7v34zn4lohdq7767l3tlrje
|
||||
path: patches/mlly@1.0.0.patch
|
||||
|
||||
specifiers:
|
||||
'@antfu/eslint-config': ^0.34.0
|
||||
'@antfu/ni': ^0.18.8
|
||||
|
@ -1916,7 +1921,7 @@ packages:
|
|||
jiti: 1.16.1
|
||||
knitwork: 1.0.0
|
||||
lodash.template: 4.5.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -1942,7 +1947,7 @@ packages:
|
|||
jiti: 1.16.1
|
||||
knitwork: 1.0.0
|
||||
lodash.template: 4.5.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -2052,7 +2057,7 @@ packages:
|
|||
h3: 1.0.1
|
||||
knitwork: 1.0.0
|
||||
magic-string: 0.26.7
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
ohash: 1.0.0
|
||||
pathe: 1.0.0
|
||||
perfect-debounce: 0.1.3
|
||||
|
@ -2110,7 +2115,7 @@ packages:
|
|||
js-cookie: 3.0.1
|
||||
knitwork: 1.0.0
|
||||
magic-string: 0.26.7
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
ufo: 1.0.1
|
||||
|
@ -4195,7 +4200,7 @@ packages:
|
|||
dotenv: 16.0.3
|
||||
gittar: 0.1.1
|
||||
jiti: 1.16.1
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
rc9: 2.0.0
|
||||
|
@ -5760,7 +5765,7 @@ packages:
|
|||
resolution: {integrity: sha512-MAU9ci3XdpqOX1aoIoyL2DMzW97P8LYeJxIUkfXhOfsrkH4KLHFaYDwKN0B2l6tqedVJWiTIJtWmxmZfa05vOQ==}
|
||||
dependencies:
|
||||
enhanced-resolve: 5.12.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
ufo: 1.0.1
|
||||
dev: true
|
||||
|
@ -7320,13 +7325,14 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/mlly/1.0.0:
|
||||
/mlly/1.0.0_afe7v34zn4lohdq7767l3tlrje:
|
||||
resolution: {integrity: sha512-QL108Hwt+u9bXdWgOI0dhzZfACovn5Aen4Xvc8Jasd9ouRH4NjnrXEiyP3nVvJo91zPlYjVRckta0Nt2zfoR6g==}
|
||||
dependencies:
|
||||
acorn: 8.8.1
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
ufo: 1.0.1
|
||||
patched: true
|
||||
|
||||
/mri/1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
|
@ -7421,7 +7427,7 @@ packages:
|
|||
knitwork: 1.0.0
|
||||
listhen: 1.0.1
|
||||
mime: 3.0.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
mri: 1.2.0
|
||||
node-fetch-native: 1.0.1
|
||||
ofetch: 1.0.0
|
||||
|
@ -7600,7 +7606,7 @@ packages:
|
|||
hookable: 5.4.2
|
||||
knitwork: 1.0.0
|
||||
magic-string: 0.26.7
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
nitropack: 1.0.0
|
||||
nuxi: 3.0.0
|
||||
ofetch: 1.0.0
|
||||
|
@ -7978,7 +7984,7 @@ packages:
|
|||
resolution: {integrity: sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==}
|
||||
dependencies:
|
||||
jsonc-parser: 3.2.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
|
||||
/pluralize/8.0.0:
|
||||
|
@ -9613,7 +9619,7 @@ packages:
|
|||
fast-glob: 3.2.12
|
||||
local-pkg: 0.4.2
|
||||
magic-string: 0.26.7
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -9630,7 +9636,7 @@ packages:
|
|||
fast-glob: 3.2.12
|
||||
local-pkg: 0.4.2
|
||||
magic-string: 0.26.7
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -9648,7 +9654,7 @@ packages:
|
|||
fast-glob: 3.2.12
|
||||
local-pkg: 0.4.2
|
||||
magic-string: 0.27.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -9665,7 +9671,7 @@ packages:
|
|||
fast-glob: 3.2.12
|
||||
local-pkg: 0.4.2
|
||||
magic-string: 0.27.0
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 1.0.0
|
||||
pkg-types: 1.0.1
|
||||
scule: 1.0.0
|
||||
|
@ -9924,7 +9930,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 0.2.0
|
||||
source-map: 0.6.1
|
||||
source-map-support: 0.5.21
|
||||
|
@ -9945,7 +9951,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
mlly: 1.0.0
|
||||
mlly: 1.0.0_afe7v34zn4lohdq7767l3tlrje
|
||||
pathe: 0.2.0
|
||||
source-map: 0.6.1
|
||||
source-map-support: 0.5.21
|
||||
|
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -1,11 +1,11 @@
|
|||
<svg width="250" height="250" viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_13_22" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="1" width="240" height="234">
|
||||
<path d="M244 123C244 187.617 205.617 235 141 235C76.3827 235 38 204.117 38 139.5C38 111.194 -8.72891 36.2356 8.00002 16C29.4601 -9.9586 88.6887 5.99994 125 5.99994C189.617 5.99994 244 58.3827 244 123Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_13_22)">
|
||||
<path d="M116.94 88.0994C103.596 89.6517 96.5039 86.0813 92.2336 98.8104C92.2336 98.8104 106.57 120.465 144.774 119.922C142.639 128.77 143.63 135.29 143.63 143.129C143.63 169.208 123.041 191.95 77.6687 191.95C54.6395 191.95 26.6536 196.141 5.30196 207.861C-9.87294 216.166 -21.746 228.197 -27 244.884L-21.0444 253.345L-9.64418 253.5V301.389L-23.5532 323.355L-19.5574 387H-6.36518L-5.22134 335.773C1.33666 331.892 16.3591 321.802 29.17 306.279C46.5564 285.4 59.9011 255.052 44.1924 217.486L55.9358 212.441C68.823 243.254 64.324 269.955 53.0381 291.454C74.6185 290.756 93.1486 289.359 108.857 286.72L105.273 243.022L117.932 241.935L129.98 387H143.096L145.308 292.541C155.755 288.039 179.547 271.507 190.68 214.071C192.052 207.085 192.815 201.186 193.196 196.141C194.95 183.335 195.941 168.898 196.247 152.443L177.564 146.467H234.984L240.551 133.66C235.137 133.893 228.655 131.021 228.655 131.021L229.952 124.812H242L176.801 90.4279C169.557 93.222 161.931 96.87 156.593 101.294C152.323 98.1895 137.53 88.4874 116.94 88.0994Z" fill="#5AB1CC"/>
|
||||
<path d="M6.21704 24.4927L18.4942 21C24.4422 42.5773 31.839 54.375 41.1422 60.3515C49.5303 65.4509 60.8925 65.5906 72.9409 64.9309C69.4331 63.7666 66.1541 62.1367 63.1039 59.8858C56.3171 54.8407 50.5217 46.4582 46.1751 31.2454L58.376 27.5974C61.655 39.0846 65.4678 45.682 70.577 49.4852C75.6861 53.2108 81.8628 54.1422 89.1834 54.9184C102.909 56.4707 120.067 57.0916 141.495 67.1817C144.393 68.2684 147.367 69.6655 150.264 71.2178C149.883 70.4416 149.502 69.6655 148.968 68.8117C145.308 62.9904 138.14 56.8588 124.871 51.8913L129.141 39.7832C150.722 47.7 159.262 58.9544 162.694 67.8803C166.659 78.048 164.219 86.0037 164.219 86.0037C161.169 87.0127 158.119 88.4098 154.611 89.4964C147.977 84.9171 141.724 81.4631 135.776 78.7466C113.814 70.4416 92.3099 76.1076 73.2459 77.8928C58.9098 79.2123 45.7938 78.5913 34.4317 71.2954C23.2221 64.1547 13.3851 50.4942 6.21704 24.4927Z" fill="#1E4E5E"/>
|
||||
<path d="M90.0984 45.2939C87.582 39.5503 86.0569 32.4872 86.7432 23.7942L99.4016 24.7256C98.6391 35.2814 102.299 42.4221 106.417 47.0791C101.079 46.1477 95.9701 46.039 90.0984 45.2939Z" fill="#1E4E5E"/>
|
||||
<path d="M170.167 43.9744L178.479 34.2724C200.059 53.366 186.638 80.687 186.638 80.687L174.819 79.3675C174.437 73.1271 173.675 61.5313 168.184 54.996C171.768 56.8355 174.819 58.8613 178.174 61.9038C178.174 56.2378 176.42 49.5628 170.167 43.9744Z" fill="#1E4E5E"/>
|
||||
</g>
|
||||
<mask id="mask0_107_22" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="1" width="240" height="234">
|
||||
<path d="M244 123C244 187.617 205.617 235 141 235C76.3827 235 38 204.117 38 139.5C38 111.194 -8.72891 36.2357 8.00002 16C29.4601 -9.9586 88.6887 5.99994 125 5.99994C189.617 5.99994 244 58.3827 244 123Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_107_22)">
|
||||
<path d="M116.94 88.0994C103.596 89.6517 96.5039 86.0813 92.2336 98.8104C92.2336 98.8104 106.57 120.465 144.774 119.922C142.639 128.77 143.63 135.29 143.63 143.129C143.63 169.208 123.041 191.95 77.6687 191.95C54.6395 191.95 26.6536 196.141 5.30196 207.861C-9.87294 216.166 -21.746 228.197 -27 244.884L-21.0444 253.345L-9.64418 253.5V301.389L-23.5532 323.355L-19.5574 387H-6.36518L-5.22134 335.773C1.33666 331.892 16.3591 321.802 29.17 306.279C46.5564 285.4 59.9011 255.052 44.1924 217.486L55.9358 212.441C68.823 243.254 64.324 269.955 53.0381 291.454C74.6185 290.756 93.1486 289.359 108.857 286.72L105.273 243.022L117.932 241.935L129.98 387H143.096L145.308 292.541C155.755 288.039 179.547 271.507 190.68 214.071C192.052 207.085 192.815 201.186 193.196 196.141C194.95 183.335 195.941 168.898 196.247 152.443L177.564 146.467H234.984L240.551 133.66C235.137 133.893 228.655 131.021 228.655 131.021L229.952 124.812H242L176.801 90.4279C169.557 93.222 161.931 96.87 156.593 101.294C152.323 98.1895 137.53 88.4874 116.94 88.0994Z" fill="#91BA4D"/>
|
||||
<path d="M6.21704 24.4927L18.4942 21C24.4422 42.5773 31.839 54.375 41.1422 60.3515C49.5303 65.4509 60.8925 65.5906 72.9409 64.9309C69.4331 63.7666 66.1541 62.1367 63.1039 59.8858C56.3171 54.8407 50.5217 46.4582 46.1751 31.2454L58.376 27.5974C61.655 39.0846 65.4678 45.682 70.577 49.4852C75.6861 53.2108 81.8628 54.1422 89.1834 54.9184C102.909 56.4707 120.067 57.0916 141.495 67.1817C144.393 68.2684 147.367 69.6655 150.264 71.2178C149.883 70.4416 149.502 69.6655 148.968 68.8117C145.308 62.9904 138.14 56.8588 124.871 51.8913L129.141 39.7832C150.722 47.7 159.262 58.9544 162.694 67.8803C166.659 78.048 164.219 86.0037 164.219 86.0037C161.169 87.0127 158.119 88.4098 154.611 89.4964C147.977 84.9171 141.724 81.4631 135.776 78.7466C113.814 70.4416 92.3099 76.1076 73.2459 77.8928C58.9098 79.2123 45.7938 78.5913 34.4317 71.2954C23.2221 64.1547 13.3851 50.4942 6.21704 24.4927Z" fill="#20461A"/>
|
||||
<path d="M90.0984 45.2939C87.582 39.5503 86.0569 32.4872 86.7432 23.7942L99.4016 24.7256C98.6391 35.2814 102.299 42.4221 106.417 47.0791C101.079 46.1477 95.9701 46.039 90.0984 45.2939Z" fill="#20461A"/>
|
||||
<path d="M170.167 43.9744L178.479 34.2724C200.059 53.366 186.638 80.687 186.638 80.687L174.819 79.3675C174.437 73.1271 173.675 61.5313 168.184 54.996C171.768 56.8355 174.819 58.8613 178.174 61.9038C178.174 56.2378 176.42 49.5628 170.167 43.9744Z" fill="#20461A"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
public-staging/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public-staging/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
11
public-staging/logo.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="250" height="250" viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_13_22" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="1" width="240" height="234">
|
||||
<path d="M244 123C244 187.617 205.617 235 141 235C76.3827 235 38 204.117 38 139.5C38 111.194 -8.72891 36.2356 8.00002 16C29.4601 -9.9586 88.6887 5.99994 125 5.99994C189.617 5.99994 244 58.3827 244 123Z" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_13_22)">
|
||||
<path d="M116.94 88.0994C103.596 89.6517 96.5039 86.0813 92.2336 98.8104C92.2336 98.8104 106.57 120.465 144.774 119.922C142.639 128.77 143.63 135.29 143.63 143.129C143.63 169.208 123.041 191.95 77.6687 191.95C54.6395 191.95 26.6536 196.141 5.30196 207.861C-9.87294 216.166 -21.746 228.197 -27 244.884L-21.0444 253.345L-9.64418 253.5V301.389L-23.5532 323.355L-19.5574 387H-6.36518L-5.22134 335.773C1.33666 331.892 16.3591 321.802 29.17 306.279C46.5564 285.4 59.9011 255.052 44.1924 217.486L55.9358 212.441C68.823 243.254 64.324 269.955 53.0381 291.454C74.6185 290.756 93.1486 289.359 108.857 286.72L105.273 243.022L117.932 241.935L129.98 387H143.096L145.308 292.541C155.755 288.039 179.547 271.507 190.68 214.071C192.052 207.085 192.815 201.186 193.196 196.141C194.95 183.335 195.941 168.898 196.247 152.443L177.564 146.467H234.984L240.551 133.66C235.137 133.893 228.655 131.021 228.655 131.021L229.952 124.812H242L176.801 90.4279C169.557 93.222 161.931 96.87 156.593 101.294C152.323 98.1895 137.53 88.4874 116.94 88.0994Z" fill="#5AB1CC"/>
|
||||
<path d="M6.21704 24.4927L18.4942 21C24.4422 42.5773 31.839 54.375 41.1422 60.3515C49.5303 65.4509 60.8925 65.5906 72.9409 64.9309C69.4331 63.7666 66.1541 62.1367 63.1039 59.8858C56.3171 54.8407 50.5217 46.4582 46.1751 31.2454L58.376 27.5974C61.655 39.0846 65.4678 45.682 70.577 49.4852C75.6861 53.2108 81.8628 54.1422 89.1834 54.9184C102.909 56.4707 120.067 57.0916 141.495 67.1817C144.393 68.2684 147.367 69.6655 150.264 71.2178C149.883 70.4416 149.502 69.6655 148.968 68.8117C145.308 62.9904 138.14 56.8588 124.871 51.8913L129.141 39.7832C150.722 47.7 159.262 58.9544 162.694 67.8803C166.659 78.048 164.219 86.0037 164.219 86.0037C161.169 87.0127 158.119 88.4098 154.611 89.4964C147.977 84.9171 141.724 81.4631 135.776 78.7466C113.814 70.4416 92.3099 76.1076 73.2459 77.8928C58.9098 79.2123 45.7938 78.5913 34.4317 71.2954C23.2221 64.1547 13.3851 50.4942 6.21704 24.4927Z" fill="#1E4E5E"/>
|
||||
<path d="M90.0984 45.2939C87.582 39.5503 86.0569 32.4872 86.7432 23.7942L99.4016 24.7256C98.6391 35.2814 102.299 42.4221 106.417 47.0791C101.079 46.1477 95.9701 46.039 90.0984 45.2939Z" fill="#1E4E5E"/>
|
||||
<path d="M170.167 43.9744L178.479 34.2724C200.059 53.366 186.638 80.687 186.638 80.687L174.819 79.3675C174.437 73.1271 173.675 61.5313 168.184 54.996C171.768 56.8355 174.819 58.8613 178.174 61.9038C178.174 56.2378 176.42 49.5628 170.167 43.9744Z" fill="#1E4E5E"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public-staging/pwa-192x192.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
public-staging/pwa-512x512.png
Normal file
After Width: | Height: | Size: 22 KiB |
5
shims.d.ts
vendored
|
@ -1,8 +1,3 @@
|
|||
/// <reference types="@types/wicg-file-system-access" />
|
||||
/// <reference types="vite-plugin-pwa/info" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
declare module 'virtual:build-info' {
|
||||
import type { BuildInfo } from '~/types'
|
||||
export const buildInfo: BuildInfo
|
||||
}
|
||||
|
|
|
@ -27,12 +27,6 @@ export interface ElkMasto extends mastodon.Client {
|
|||
|
||||
export type PaginatorState = 'idle' | 'loading' | 'done' | 'error'
|
||||
|
||||
export interface ServerInfo extends mastodon.v2.Instance {
|
||||
server: string
|
||||
timeUpdated: number
|
||||
customEmojis?: Record<string, mastodon.v1.CustomEmoji>
|
||||
}
|
||||
|
||||
export interface GroupedNotifications {
|
||||
id: string
|
||||
type: Exclude<string, 'grouped-reblogs-and-favourites'>
|
||||
|
|