forked from Mirrors/elk
Merge branch 'main' into feature/emoji-autocomplete
This commit is contained in:
commit
17e8130638
170 changed files with 3169 additions and 1141 deletions
|
@ -10,6 +10,8 @@ NUXT_CLOUDFLARE_API_TOKEN=
|
||||||
NUXT_STORAGE_DRIVER=
|
NUXT_STORAGE_DRIVER=
|
||||||
NUXT_STORAGE_FS_BASE=
|
NUXT_STORAGE_FS_BASE=
|
||||||
|
|
||||||
|
NUXT_ADMIN_KEY=
|
||||||
|
|
||||||
NUXT_PUBLIC_DISABLE_VERSION_CHECK=
|
NUXT_PUBLIC_DISABLE_VERSION_CHECK=
|
||||||
|
|
||||||
NUXT_GITHUB_CLIENT_ID=
|
NUXT_GITHUB_CLIENT_ID=
|
||||||
|
|
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!-- Thank you for contributing! -->
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
<!-- e.g. is there anything you'd like reviewers to focus on? -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New Feature
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Translations update
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
### Before submitting the PR, please make sure you do the following
|
||||||
|
|
||||||
|
- [ ] Read the [Contributing Guidelines](https://github.com/elk-zone/elk/blob/main/CONTRIBUTING.md).
|
||||||
|
- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
|
||||||
|
- [ ] Provide related snapshots or videos.
|
||||||
|
- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
|
|
@ -93,9 +93,9 @@ We are using [vue-i18n](https://vue-i18n.intlify.dev/) via [nuxt-i18n](https://i
|
||||||
|
|
||||||
1. Add a new file in [locales](./locales) folder with the language code as the filename.
|
1. Add a new file in [locales](./locales) folder with the language code as the filename.
|
||||||
2. Copy [en-US](./locales/en-US.json) and translate the strings.
|
2. Copy [en-US](./locales/en-US.json) and translate the strings.
|
||||||
3. Add the language to the `locales` array in [config/i18n.ts](./config/i18n.ts#L13)
|
3. Add the language to the `locales` array in [config/i18n.ts](./config/i18n.ts#L12), below `en` variants and `ar-EG`.
|
||||||
4. If the language is `right-to-left`, add `dir` option with `rtl` value, for example, for [ar-EG](./config/i18n.ts#L79)
|
4. If the language is `right-to-left`, add `dir` option with `rtl` value, for example, for [ar-EG](./config/i18n.ts#L27)
|
||||||
5. If the language requires special pluralization rules, add `pluralRule` callback option, for example, for [ar-EG](./config/i18n.ts#L80)
|
5. If the language requires special pluralization rules, add `pluralRule` callback option, for example, for [ar-EG](./config/i18n.ts#L27)
|
||||||
|
|
||||||
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
|
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
|
||||||
|
|
||||||
|
|
11
README.md
11
README.md
|
@ -28,11 +28,20 @@ A nimble Mastodon web client
|
||||||
|
|
||||||
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you use it if you would like to help us build 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.
|
It is already quite usable, but it isn't ready for wide adoption yet. We recommend you use it if you would like to help us build 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 on:
|
## Official Deployment
|
||||||
|
|
||||||
|
The Elk team maintains a deployment at:
|
||||||
|
|
||||||
- 🦌 Production: [elk.zone](https://elk.zone)
|
- 🦌 Production: [elk.zone](https://elk.zone)
|
||||||
- 🐙 Canary: [main.elk.zone](https://main.elk.zone) (deploys on every commit to `main` branch)
|
- 🐙 Canary: [main.elk.zone](https://main.elk.zone) (deploys on every commit to `main` branch)
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
These are known deployments using Elk as an alternative Web client for Mastodon servers or as a base for other projects in the fediverse:
|
||||||
|
|
||||||
|
- [elk.h4.io](https://elk.h4.io) - Use Elk for the `h4.io` Server
|
||||||
|
- [elk.universeodon.com](https://elk.universeodon.com) - Use Elk for the Universeodon Server
|
||||||
|
|
||||||
## 💖 Sponsors
|
## 💖 Sponsors
|
||||||
|
|
||||||
We are grateful for the generous sponsorship and help of:
|
We are grateful for the generous sponsorship and help of:
|
||||||
|
|
1
app.vue
1
app.vue
|
@ -3,6 +3,7 @@ setupPageHeader()
|
||||||
provideGlobalCommands()
|
provideGlobalCommands()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
if (process.server && !route.path.startsWith('/settings')) {
|
if (process.server && !route.path.startsWith('/settings')) {
|
||||||
useHead({
|
useHead({
|
||||||
meta: [
|
meta: [
|
||||||
|
|
|
@ -8,15 +8,15 @@ const { account, command, context, ...props } = defineProps<{
|
||||||
command?: boolean
|
command?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
const isSelf = $(useSelfAccount(() => account))
|
||||||
const enable = $computed(() => !isSelf && currentUser.value)
|
const enable = $computed(() => !isSelf && currentUser.value)
|
||||||
const relationship = $computed(() => props.relationship || useRelationship(account).value)
|
const relationship = $computed(() => props.relationship || useRelationship(account).value)
|
||||||
|
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
async function toggleFollow() {
|
async function toggleFollow() {
|
||||||
relationship!.following = !relationship!.following
|
relationship!.following = !relationship!.following
|
||||||
try {
|
try {
|
||||||
const newRel = await masto.v1.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
|
const newRel = await client.v1.accounts[relationship!.following ? 'follow' : 'unfollow'](account.id)
|
||||||
Object.assign(relationship!, newRel)
|
Object.assign(relationship!, newRel)
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
@ -29,7 +29,7 @@ async function toggleFollow() {
|
||||||
async function unblock() {
|
async function unblock() {
|
||||||
relationship!.blocking = false
|
relationship!.blocking = false
|
||||||
try {
|
try {
|
||||||
const newRel = await masto.v1.accounts.unblock(account.id)
|
const newRel = await client.v1.accounts.unblock(account.id)
|
||||||
Object.assign(relationship!, newRel)
|
Object.assign(relationship!, newRel)
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
@ -42,7 +42,7 @@ async function unblock() {
|
||||||
async function unmute() {
|
async function unmute() {
|
||||||
relationship!.muting = false
|
relationship!.muting = false
|
||||||
try {
|
try {
|
||||||
const newRel = await masto.v1.accounts.unmute(account.id)
|
const newRel = await client.v1.accounts.unmute(account.id)
|
||||||
Object.assign(relationship!, newRel)
|
Object.assign(relationship!, newRel)
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ const { account } = defineProps<{
|
||||||
command?: boolean
|
command?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const createdAt = $(useFormattedDateTime(() => account.createdAt, {
|
const createdAt = $(useFormattedDateTime(() => account.createdAt, {
|
||||||
|
@ -14,6 +16,8 @@ const createdAt = $(useFormattedDateTime(() => account.createdAt, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const relationship = $(useRelationship(account))
|
||||||
|
|
||||||
const namedFields = ref<mastodon.v1.AccountField[]>([])
|
const namedFields = ref<mastodon.v1.AccountField[]>([])
|
||||||
const iconFields = ref<mastodon.v1.AccountField[]>([])
|
const iconFields = ref<mastodon.v1.AccountField[]>([])
|
||||||
|
|
||||||
|
@ -39,6 +43,18 @@ function previewAvatar() {
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleNotifications() {
|
||||||
|
relationship!.notifying = !relationship?.notifying
|
||||||
|
try {
|
||||||
|
const newRel = await client.v1.accounts.follow(account.id, { notify: relationship?.notifying })
|
||||||
|
Object.assign(relationship!, newRel)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// TODO error handling
|
||||||
|
relationship!.notifying = !relationship?.notifying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const named: mastodon.v1.AccountField[] = []
|
const named: mastodon.v1.AccountField[] = []
|
||||||
const icons: mastodon.v1.AccountField[] = []
|
const icons: mastodon.v1.AccountField[] = []
|
||||||
|
@ -59,7 +75,8 @@ watchEffect(() => {
|
||||||
iconFields.value = icons
|
iconFields.value = icons
|
||||||
})
|
})
|
||||||
|
|
||||||
const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
const isSelf = $(useSelfAccount(() => account))
|
||||||
|
const isNotifiedOnPost = $computed(() => !!relationship?.notifying)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -83,6 +100,17 @@ const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
||||||
</div>
|
</div>
|
||||||
<div absolute top-18 inset-ie-0 flex gap-2 items-center>
|
<div absolute top-18 inset-ie-0 flex gap-2 items-center>
|
||||||
<AccountMoreButton :account="account" :command="command" />
|
<AccountMoreButton :account="account" :command="command" />
|
||||||
|
<button
|
||||||
|
v-if="!isSelf && relationship?.following"
|
||||||
|
:aria-pressed="isNotifiedOnPost"
|
||||||
|
:aria-label="t('account.notify_on_post', { username: `@${account.username}` })"
|
||||||
|
rounded-full p2 border-1 transition-colors
|
||||||
|
:class="isNotifiedOnPost ? 'text-primary border-primary hover:bg-red/20 hover:text-red hover:border-red' : 'border-base hover:text-primary'"
|
||||||
|
@click="toggleNotifications"
|
||||||
|
>
|
||||||
|
<span v-if="isNotifiedOnPost" i-ri:bell-fill block text-current />
|
||||||
|
<span v-else i-ri-bell-line block text-current />
|
||||||
|
</button>
|
||||||
<AccountFollowButton :account="account" :command="command" />
|
<AccountFollowButton :account="account" :command="command" />
|
||||||
<!-- Edit profile -->
|
<!-- Edit profile -->
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
|
@ -93,11 +121,6 @@ const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
||||||
>
|
>
|
||||||
{{ $t('settings.profile.appearance.title') }}
|
{{ $t('settings.profile.appearance.title') }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<!-- <button flex gap-1 items-center w-full rounded op75 hover="op100 text-purple" group>
|
|
||||||
<div rounded p2 group-hover="bg-rose/10">
|
|
||||||
<div i-ri:bell-line />
|
|
||||||
</div>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="account.note" max-h-100 overflow-y-auto>
|
<div v-if="account.note" max-h-100 overflow-y-auto>
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { link = true, avatar = true } = defineProps<{
|
||||||
<AccountHoverWrapper :account="account">
|
<AccountHoverWrapper :account="account">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="link ? getAccountRoute(account) : undefined"
|
:to="link ? getAccountRoute(account) : undefined"
|
||||||
:class="link ? 'text-link-rounded ms-0 ps-0' : ''"
|
:class="link ? 'text-link-rounded -ml-1.8rem pl-1.8rem rtl-(ml0 pl-0.5rem -mr-1.8rem pr-1.8rem)' : ''"
|
||||||
min-w-0 flex gap-2 items-center
|
min-w-0 flex gap-2 items-center
|
||||||
>
|
>
|
||||||
<AccountAvatar v-if="avatar" :account="account" w-5 h-5 />
|
<AccountAvatar v-if="avatar" :account="account" w-5 h-5 />
|
||||||
|
|
|
@ -7,39 +7,49 @@ const { account } = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
let relationship = $(useRelationship(account))
|
let relationship = $(useRelationship(account))
|
||||||
|
|
||||||
const isSelf = $computed(() => currentUser.value?.account.id === account.id)
|
const isSelf = $(useSelfAccount(() => account))
|
||||||
|
|
||||||
const masto = useMasto()
|
const { t } = useI18n()
|
||||||
const toggleMute = async () => {
|
const { client } = $(useMasto())
|
||||||
// TODO: Add confirmation
|
|
||||||
|
const isConfirmed = async (title: string) => {
|
||||||
|
return await openConfirmDialog(t('common.confirm_dialog.title', [title])) === 'confirm'
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleMute = async (title: string) => {
|
||||||
|
if (!await isConfirmed(title))
|
||||||
|
return
|
||||||
|
|
||||||
relationship!.muting = !relationship!.muting
|
relationship!.muting = !relationship!.muting
|
||||||
relationship = relationship!.muting
|
relationship = relationship!.muting
|
||||||
? await masto.v1.accounts.mute(account.id, {
|
? await client.v1.accounts.mute(account.id, {
|
||||||
// TODO support more options
|
// TODO support more options
|
||||||
})
|
})
|
||||||
: await masto.v1.accounts.unmute(account.id)
|
: await client.v1.accounts.unmute(account.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleBlockUser = async () => {
|
const toggleBlockUser = async (title: string) => {
|
||||||
// TODO: Add confirmation
|
if (!await isConfirmed(title))
|
||||||
|
return
|
||||||
|
|
||||||
relationship!.blocking = !relationship!.blocking
|
relationship!.blocking = !relationship!.blocking
|
||||||
relationship = await masto.v1.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
|
relationship = await client.v1.accounts[relationship!.blocking ? 'block' : 'unblock'](account.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleBlockDomain = async () => {
|
const toggleBlockDomain = async (title: string) => {
|
||||||
// TODO: Add confirmation
|
if (!await isConfirmed(title))
|
||||||
|
return
|
||||||
|
|
||||||
relationship!.domainBlocking = !relationship!.domainBlocking
|
relationship!.domainBlocking = !relationship!.domainBlocking
|
||||||
await masto.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
await client.v1.domainBlocks[relationship!.domainBlocking ? 'block' : 'unblock'](getServerName(account))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleReblogs = async () => {
|
const toggleReblogs = async (title: string) => {
|
||||||
// TODO: Add confirmation
|
if (!await isConfirmed(title))
|
||||||
|
return
|
||||||
|
|
||||||
const showingReblogs = !relationship?.showingReblogs
|
const showingReblogs = !relationship?.showingReblogs
|
||||||
relationship = await masto.v1.accounts.follow(account.id, { reblogs: showingReblogs })
|
relationship = await client.v1.accounts.follow(account.id, { reblogs: showingReblogs })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -80,14 +90,14 @@ const toggleReblogs = async () => {
|
||||||
icon="i-ri:repeat-line"
|
icon="i-ri:repeat-line"
|
||||||
:text="$t('menu.show_reblogs', [`@${account.acct}`])"
|
:text="$t('menu.show_reblogs', [`@${account.acct}`])"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleReblogs"
|
@click="toggleReblogs($t('menu.show_reblogs', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
v-else
|
v-else
|
||||||
:text="$t('menu.hide_reblogs', [`@${account.acct}`])"
|
:text="$t('menu.hide_reblogs', [`@${account.acct}`])"
|
||||||
icon="i-ri:repeat-line"
|
icon="i-ri:repeat-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleReblogs"
|
@click="toggleReblogs($t('menu.hide_reblogs', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
|
@ -95,14 +105,14 @@ const toggleReblogs = async () => {
|
||||||
:text="$t('menu.mute_account', [`@${account.acct}`])"
|
:text="$t('menu.mute_account', [`@${account.acct}`])"
|
||||||
icon="i-ri:volume-up-fill"
|
icon="i-ri:volume-up-fill"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleMute"
|
@click="toggleMute($t('menu.mute_account', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
v-else
|
v-else
|
||||||
:text="$t('menu.unmute_account', [`@${account.acct}`])"
|
:text="$t('menu.unmute_account', [`@${account.acct}`])"
|
||||||
icon="i-ri:volume-mute-line"
|
icon="i-ri:volume-mute-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleMute"
|
@click="toggleMute($t('menu.unmute_account', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
|
@ -110,14 +120,14 @@ const toggleReblogs = async () => {
|
||||||
:text="$t('menu.block_account', [`@${account.acct}`])"
|
:text="$t('menu.block_account', [`@${account.acct}`])"
|
||||||
icon="i-ri:forbid-2-line"
|
icon="i-ri:forbid-2-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleBlockUser"
|
@click="toggleBlockUser($t('menu.block_account', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
v-else
|
v-else
|
||||||
:text="$t('menu.unblock_account', [`@${account.acct}`])"
|
:text="$t('menu.unblock_account', [`@${account.acct}`])"
|
||||||
icon="i-ri:checkbox-circle-line"
|
icon="i-ri:checkbox-circle-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleBlockUser"
|
@click="toggleBlockUser($t('menu.unblock_account', [`@${account.acct}`]))"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template v-if="getServerName(account) !== currentServer">
|
<template v-if="getServerName(account) !== currentServer">
|
||||||
|
@ -126,14 +136,14 @@ const toggleReblogs = async () => {
|
||||||
:text="$t('menu.block_domain', [getServerName(account)])"
|
:text="$t('menu.block_domain', [getServerName(account)])"
|
||||||
icon="i-ri:shut-down-line"
|
icon="i-ri:shut-down-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleBlockDomain"
|
@click="toggleBlockDomain($t('menu.block_domain', [getServerName(account)]))"
|
||||||
/>
|
/>
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
v-else
|
v-else
|
||||||
:text="$t('menu.unblock_domain', [getServerName(account)])"
|
:text="$t('menu.unblock_domain', [getServerName(account)])"
|
||||||
icon="i-ri:restart-line"
|
icon="i-ri:restart-line"
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleBlockDomain"
|
@click="toggleBlockDomain($t('menu.unblock_domain', [getServerName(account)]))"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Paginator, mastodon } from 'masto'
|
import type { Paginator, mastodon } from 'masto'
|
||||||
|
|
||||||
const { paginator } = defineProps<{
|
const { paginator, account, context } = defineProps<{
|
||||||
paginator: Paginator<mastodon.v1.Account[], mastodon.DefaultPaginationParams>
|
paginator: Paginator<mastodon.v1.Account[], mastodon.DefaultPaginationParams>
|
||||||
|
context?: 'following' | 'followers'
|
||||||
|
account?: mastodon.v1.Account
|
||||||
relationshipContext?: 'followedBy' | 'following'
|
relationshipContext?: 'followedBy' | 'following'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const fallbackContext = $computed(() => {
|
||||||
|
return ['following', 'followers'].includes(context!)
|
||||||
|
})
|
||||||
|
const showOriginSite = $computed(() =>
|
||||||
|
account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -17,5 +26,18 @@ const { paginator } = defineProps<{
|
||||||
border="b base" py2 px4
|
border="b base" py2 px4
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="fallbackContext && showOriginSite" #done>
|
||||||
|
<div p5 text-secondary text-center flex flex-col items-center gap1>
|
||||||
|
<span italic>{{ $t(`account.view_other_${context}`) }}</span>
|
||||||
|
<NuxtLink
|
||||||
|
:href="account!.url" target="_blank" external
|
||||||
|
flex="~ gap-1" items-center text-primary
|
||||||
|
hover="underline text-primary-active"
|
||||||
|
>
|
||||||
|
<div i-ri:external-link-fill />
|
||||||
|
{{ $t('menu.open_in_original_site') }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</CommonPaginator>
|
</CommonPaginator>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -40,7 +40,7 @@ const userSettings = useUserSettings()
|
||||||
</template>
|
</template>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="!getWellnessSetting(userSettings, 'hideFollowerCount')"
|
v-if="!getPreferences(userSettings, 'hideFollowerCount')"
|
||||||
:to="getAccountFollowersRoute(account)"
|
:to="getAccountFollowersRoute(account)"
|
||||||
replace text-secondary
|
replace text-secondary
|
||||||
exact-active-class="text-primary"
|
exact-active-class="text-primary"
|
||||||
|
|
|
@ -88,17 +88,19 @@ watch(file, (image, _, onCleanup) => {
|
||||||
w-full
|
w-full
|
||||||
h-full
|
h-full
|
||||||
>
|
>
|
||||||
<div absolute bg="black/50" text-white rounded-full text-xl w12 h12 flex justify-center items-center hover="bg-black/40 text-primary">
|
<span absolute bg="black/50" text-white rounded-full text-xl w12 h12 flex justify-center items-center hover="bg-black/40 text-primary">
|
||||||
<div i-ri:upload-line />
|
<span block i-ri:upload-line />
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div
|
<span
|
||||||
v-if="loading"
|
v-if="loading"
|
||||||
absolute inset-0
|
absolute inset-0
|
||||||
bg="black/30" text-white
|
bg="black/30" text-white
|
||||||
flex justify-center items-center
|
flex justify-center items-center
|
||||||
>
|
>
|
||||||
<div class="i-ri:loader-4-line animate-spin animate-duration-[2.5s]" text-4xl />
|
<span class="animate-spin animate-duration-[2.5s] preserve-3d">
|
||||||
</div>
|
<span block i-ri:loader-4-line text-4xl />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -42,7 +42,7 @@ defineSlots<{
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, stream, eventType, preprocess)
|
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, $$(stream), eventType, preprocess)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -9,6 +9,7 @@ defineProps<{
|
||||||
<template>
|
<template>
|
||||||
<VTooltip
|
<VTooltip
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
auto-hide
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
|
|
|
@ -18,5 +18,6 @@ const highlighted = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<pre class="code-block" v-html="highlighted" />
|
<pre v-if="lang" class="code-block" v-html="highlighted" />
|
||||||
|
<pre v-else class="code-block">{{ raw }}</pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -17,7 +17,7 @@ defineProps<{
|
||||||
<div flex justify-between px5 py2 :class="{ 'xl:hidden': $route.name !== 'tag' }">
|
<div flex justify-between px5 py2 :class="{ 'xl:hidden': $route.name !== 'tag' }">
|
||||||
<div flex gap-3 items-center overflow-hidden py2>
|
<div flex gap-3 items-center overflow-hidden py2>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="backOnSmallScreen || back" flex="~ gap1" items-center btn-text p-0 lg:hidden
|
v-if="backOnSmallScreen || back" flex="~ gap1" items-center btn-text p-0 xl:hidden
|
||||||
:aria-label="$t('nav.back')"
|
:aria-label="$t('nav.back')"
|
||||||
@click="$router.go(-1)"
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
|
@ -31,7 +31,7 @@ defineProps<{
|
||||||
<div flex items-center flex-shrink-0 gap-x-2>
|
<div flex items-center flex-shrink-0 gap-x-2>
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
<PwaBadge lg:hidden />
|
<PwaBadge lg:hidden />
|
||||||
<NavUser v-if="isMastoInitialised" />
|
<NavUser v-if="isHydrated" />
|
||||||
<NavUserSkeleton v-else />
|
<NavUserSkeleton v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,7 +51,7 @@ const handleFavouritedBoostedByClose = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="isMastoInitialised">
|
<template v-if="isHydrated">
|
||||||
<ModalDialog v-model="isSigninDialogOpen" py-4 px-8 max-w-125>
|
<ModalDialog v-model="isSigninDialogOpen" py-4 px-8 max-w-125>
|
||||||
<UserSignIn />
|
<UserSignIn />
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
|
|
|
@ -53,6 +53,10 @@ const { modelValue: visible } = defineModel<{
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
const deactivated = useDeactivated()
|
const deactivated = useDeactivated()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
@ -132,12 +136,6 @@ useEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
inheritAttrs: false,
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<!-- Dialog component -->
|
<!-- Dialog component -->
|
||||||
|
|
|
@ -10,7 +10,7 @@ const moreMenuVisible = ref(false)
|
||||||
class="after-content-empty after:(h-[calc(100%+0.5px)] w-0.1px pointer-events-none)"
|
class="after-content-empty after:(h-[calc(100%+0.5px)] w-0.1px pointer-events-none)"
|
||||||
>
|
>
|
||||||
<!-- These weird styles above are used for scroll locking, don't change it unless you know exactly what you're doing. -->
|
<!-- These weird styles above are used for scroll locking, don't change it unless you know exactly what you're doing. -->
|
||||||
<template v-if="isMastoInitialised && currentUser">
|
<template v-if="currentUser">
|
||||||
<NuxtLink to="/home" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
<NuxtLink to="/home" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
||||||
<div i-ri:home-5-line />
|
<div i-ri:home-5-line />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
@ -24,7 +24,7 @@ const moreMenuVisible = ref(false)
|
||||||
<div i-ri:at-line />
|
<div i-ri:at-line />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="isMastoInitialised && !currentUser">
|
<template v-else>
|
||||||
<NuxtLink :to="`/${currentServer}/explore`" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
<NuxtLink :to="`/${currentServer}/explore`" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
||||||
<div i-ri:hashtag />
|
<div i-ri:hashtag />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
|
@ -16,7 +16,7 @@ const { notifications } = useNotifications()
|
||||||
<NavSideItem :text="$t('nav.notifications')" to="/notifications" icon="i-ri:notification-4-line" user-only :command="command">
|
<NavSideItem :text="$t('nav.notifications')" to="/notifications" icon="i-ri:notification-4-line" user-only :command="command">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<div flex relative>
|
<div flex relative>
|
||||||
<div class="i-ri:notification-4-line" md:text-size-inherit text-xl />
|
<div class="i-ri:notification-4-line" text-xl />
|
||||||
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
|
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
|
||||||
{{ notifications < 10 ? notifications : '•' }}
|
{{ notifications < 10 ? notifications : '•' }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,9 +29,9 @@ const { notifications } = useNotifications()
|
||||||
<NavSideItem :text="$t('action.compose')" to="/compose" icon="i-ri:quill-pen-line" user-only :command="command" />
|
<NavSideItem :text="$t('action.compose')" to="/compose" icon="i-ri:quill-pen-line" user-only :command="command" />
|
||||||
|
|
||||||
<div shrink hidden sm:block mt-4 />
|
<div shrink hidden sm:block mt-4 />
|
||||||
<NavSideItem :text="$t('nav.explore')" :to="isMastoInitialised ? `/${currentServer}/explore` : '/explore'" icon="i-ri:hashtag" :command="command" />
|
<NavSideItem :text="$t('nav.explore')" :to="isHydrated ? `/${currentServer}/explore` : '/explore'" icon="i-ri:hashtag" :command="command" />
|
||||||
<NavSideItem :text="$t('nav.local')" :to="isMastoInitialised ? `/${currentServer}/public/local` : '/public/local'" icon="i-ri:group-2-line " :command="command" />
|
<NavSideItem :text="$t('nav.local')" :to="isHydrated ? `/${currentServer}/public/local` : '/public/local'" icon="i-ri:group-2-line " :command="command" />
|
||||||
<NavSideItem :text="$t('nav.federated')" :to="isMastoInitialised ? `/${currentServer}/public` : '/public'" icon="i-ri:earth-line" :command="command" />
|
<NavSideItem :text="$t('nav.federated')" :to="isHydrated ? `/${currentServer}/public` : '/public'" icon="i-ri:earth-line" :command="command" />
|
||||||
|
|
||||||
<div shrink hidden sm:block mt-4 />
|
<div shrink hidden sm:block mt-4 />
|
||||||
<NavSideItem :text="$t('nav.settings')" to="/settings" icon="i-ri:settings-3-line" :command="command" />
|
<NavSideItem :text="$t('nav.settings')" to="/settings" icon="i-ri:settings-3-line" :command="command" />
|
||||||
|
|
|
@ -29,7 +29,7 @@ useCommand({
|
||||||
})
|
})
|
||||||
|
|
||||||
let activeClass = $ref('text-primary')
|
let activeClass = $ref('text-primary')
|
||||||
onMastoInit(async () => {
|
onHydrated(async () => {
|
||||||
// TODO: force NuxtLink to reevaluate, we now we are in this route though, so we should force it to active
|
// TODO: force NuxtLink to reevaluate, we now we are in this route though, so we should force it to active
|
||||||
// we don't have currentServer defined until later
|
// we don't have currentServer defined until later
|
||||||
activeClass = ''
|
activeClass = ''
|
||||||
|
@ -39,8 +39,8 @@ onMastoInit(async () => {
|
||||||
|
|
||||||
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items
|
// Optimize rendering for the common case of being logged in, only show visual feedback for disabled user-only items
|
||||||
// when we know there is no user.
|
// when we know there is no user.
|
||||||
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value))
|
const noUserDisable = computed(() => !isHydrated.value || (props.userOnly && !currentUser.value))
|
||||||
const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && !currentUser.value)
|
const noUserVisual = computed(() => isHydrated.value && props.userOnly && !currentUser.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -66,7 +66,7 @@ const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly &
|
||||||
<div :class="icon" text-xl />
|
<div :class="icon" text-xl />
|
||||||
</slot>
|
</slot>
|
||||||
<slot>
|
<slot>
|
||||||
<span block sm:hidden xl:block>{{ isHydrated ? text : ' ' }}</span>
|
<span block sm:hidden xl:block select-none>{{ isHydrated ? text : ' ' }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</CommonTooltip>
|
</CommonTooltip>
|
||||||
|
|
|
@ -17,6 +17,7 @@ router.afterEach(() => {
|
||||||
flex items-end gap-4
|
flex items-end gap-4
|
||||||
py2 px-5
|
py2 px-5
|
||||||
text-2xl
|
text-2xl
|
||||||
|
select-none
|
||||||
focus-visible:ring="2 current"
|
focus-visible:ring="2 current"
|
||||||
to="/"
|
to="/"
|
||||||
external
|
external
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<VDropdown v-if="isMastoInitialised && currentUser" sm:hidden>
|
<VDropdown v-if="isHydrated && currentUser" sm:hidden>
|
||||||
<div style="-webkit-touch-callout: none;">
|
<div style="-webkit-touch-callout: none;">
|
||||||
<AccountAvatar
|
<AccountAvatar
|
||||||
ref="avatar"
|
ref="avatar"
|
||||||
|
|
|
@ -66,7 +66,10 @@ const isLegacyAccount = computed(() => !currentUser.value?.vapidKey)
|
||||||
:disabled="busy || isLegacyAccount"
|
:disabled="busy || isLegacyAccount"
|
||||||
@click="$emit('subscribe')"
|
@click="$emit('subscribe')"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" :class="busy && animate ? 'i-ri:loader-2-fill animate-spin' : 'i-ri:check-line'" />
|
<span v-if="busy && animate" aria-hidden="true" block animate-spin preserve-3d>
|
||||||
|
<span block i-ri:loader-2-fill aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span v-else aria-hidden="true" block i-ri:check-line />
|
||||||
<span>{{ $t('settings.notifications.push_notifications.warning.enable_desktop') }}</span>
|
<span>{{ $t('settings.notifications.push_notifications.warning.enable_desktop') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<slot name="error" />
|
<slot name="error" />
|
||||||
|
|
|
@ -147,7 +147,10 @@ onActivated(() => (busy = false))
|
||||||
:class="busy || !saveEnabled ? 'border-transparent' : null"
|
:class="busy || !saveEnabled ? 'border-transparent' : null"
|
||||||
:disabled="busy || !saveEnabled"
|
:disabled="busy || !saveEnabled"
|
||||||
>
|
>
|
||||||
<span :class="busy && animateSave ? 'i-ri:loader-2-fill animate-spin' : 'i-ri:save-2-fill'" />
|
<span v-if="busy && animateSave" aria-hidden="true" block animate-spin preserve-3d>
|
||||||
|
<span block i-ri:loader-2-fill aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span v-else block aria-hidden="true" i-ri:save-2-fill />
|
||||||
{{ $t('settings.notifications.push_notifications.save_settings') }}
|
{{ $t('settings.notifications.push_notifications.save_settings') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
@ -157,7 +160,7 @@ onActivated(() => (busy = false))
|
||||||
:disabled="busy || !saveEnabled"
|
:disabled="busy || !saveEnabled"
|
||||||
@click="undoChanges"
|
@click="undoChanges"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" class="i-material-symbols:undo-rounded" />
|
<span aria-hidden="true" class="block i-material-symbols:undo-rounded" />
|
||||||
{{ $t('settings.notifications.push_notifications.undo_settings') }}
|
{{ $t('settings.notifications.push_notifications.undo_settings') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,7 +172,10 @@ onActivated(() => (busy = false))
|
||||||
:class="busy ? 'border-transparent' : null"
|
:class="busy ? 'border-transparent' : null"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" :class="busy && animateRemoveSubscription ? 'i-ri:loader-2-fill animate-spin' : 'i-material-symbols:cancel-rounded'" />
|
<span v-if="busy && animateRemoveSubscription" aria-hidden="true" block animate-spin preserve-3d>
|
||||||
|
<span block i-ri:loader-2-fill aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span v-else block aria-hidden="true" i-material-symbols:cancel-rounded />
|
||||||
{{ $t('settings.notifications.push_notifications.unsubscribe') }}
|
{{ $t('settings.notifications.push_notifications.unsubscribe') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const disabled = computed(() => !isMastoInitialised.value || !currentUser.value)
|
const disabled = computed(() => !isHydrated.value || !currentUser.value)
|
||||||
const disabledVisual = computed(() => isMastoInitialised.value && !currentUser.value)
|
const disabledVisual = computed(() => isHydrated.value && !currentUser.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -44,7 +44,7 @@ const hideEmojiPicker = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonTooltip content="Add emojis">
|
<CommonTooltip :content="$t('tooltip.add_emojis')">
|
||||||
<VDropdown
|
<VDropdown
|
||||||
auto-boundary-max-size
|
auto-boundary-max-size
|
||||||
@apply-show="openEmojiPicker()"
|
@apply-show="openEmojiPicker()"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EditorContent } from '@tiptap/vue-3'
|
import { EditorContent } from '@tiptap/vue-3'
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import type { Draft } from '~/types'
|
import type { Draft } from '~/types'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -90,6 +89,19 @@ async function publish() {
|
||||||
emit('published', status)
|
emit('published', status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useWebShareTarget(async ({ data: { data, action } }: any) => {
|
||||||
|
if (action !== 'compose-with-shared-data')
|
||||||
|
return
|
||||||
|
|
||||||
|
editor.value?.commands.focus('end')
|
||||||
|
|
||||||
|
if (data.text !== undefined)
|
||||||
|
editor.value?.commands.insertContent(data.text)
|
||||||
|
|
||||||
|
if (data.files !== undefined)
|
||||||
|
await uploadAttachments(data.files)
|
||||||
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
focusEditor: () => {
|
focusEditor: () => {
|
||||||
editor.value?.commands?.focus?.()
|
editor.value?.commands?.focus?.()
|
||||||
|
@ -98,7 +110,7 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isMastoInitialised && currentUser" flex="~ col gap-4" py3 px2 sm:px4>
|
<div v-if="isHydrated && currentUser" flex="~ col gap-4" py3 px2 sm:px4>
|
||||||
<template v-if="draft.editingStatus">
|
<template v-if="draft.editingStatus">
|
||||||
<div flex="~ col gap-1">
|
<div flex="~ col gap-1">
|
||||||
<div id="state-editing" text-secondary self-center>
|
<div id="state-editing" text-secondary self-center>
|
||||||
|
@ -145,7 +157,9 @@ defineExpose({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isUploading" flex gap-1 items-center text-sm p1 text-primary>
|
<div v-if="isUploading" flex gap-1 items-center text-sm p1 text-primary>
|
||||||
<div i-ri:loader-2-fill animate-spin />
|
<div animate-spin preserve-3d>
|
||||||
|
<div i-ri:loader-2-fill />
|
||||||
|
</div>
|
||||||
{{ $t('state.uploading') }}
|
{{ $t('state.uploading') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -199,7 +213,7 @@ defineExpose({
|
||||||
<div flex gap-4>
|
<div flex gap-4>
|
||||||
<div w-12 h-full sm:block hidden />
|
<div w-12 h-full sm:block hidden />
|
||||||
<div
|
<div
|
||||||
v-if="shouldExpanded" flex="~ gap-1 1 wrap" m="s--1" pt-2 justify="between" max-w-full
|
v-if="shouldExpanded" flex="~ gap-1 1 wrap" m="s--1" pt-2 justify="end" max-w-full
|
||||||
border="t base"
|
border="t base"
|
||||||
>
|
>
|
||||||
<PublishEmojiPicker
|
<PublishEmojiPicker
|
||||||
|
@ -274,7 +288,9 @@ defineExpose({
|
||||||
aria-describedby="publish-tooltip"
|
aria-describedby="publish-tooltip"
|
||||||
@click="publish"
|
@click="publish"
|
||||||
>
|
>
|
||||||
<div v-if="isSending" i-ri:loader-2-fill animate-spin />
|
<span v-if="isSending" block animate-spin preserve-3d>
|
||||||
|
<div block i-ri:loader-2-fill />
|
||||||
|
</span>
|
||||||
<span v-if="draft.editingStatus">{{ $t('action.save_changes') }}</span>
|
<span v-if="draft.editingStatus">{{ $t('action.save_changes') }}</span>
|
||||||
<span v-else-if="draft.params.inReplyToId">{{ $t('action.reply') }}</span>
|
<span v-else-if="draft.params.inReplyToId">{{ $t('action.reply') }}</span>
|
||||||
<span v-else>{{ !isSending ? $t('action.publish') : $t('state.publishing') }}</span>
|
<span v-else>{{ !isSending ? $t('action.publish') : $t('state.publishing') }}</span>
|
||||||
|
|
|
@ -66,6 +66,7 @@ const activate = () => {
|
||||||
bg-transparent
|
bg-transparent
|
||||||
outline="focus:none"
|
outline="focus:none"
|
||||||
pe-4
|
pe-4
|
||||||
|
select-none
|
||||||
:placeholder="isHydrated ? t('nav.search') : ''"
|
:placeholder="isHydrated ? t('nav.search') : ''"
|
||||||
pb="1px"
|
pb="1px"
|
||||||
placeholder-text-secondary
|
placeholder-text-secondary
|
||||||
|
|
|
@ -9,9 +9,9 @@ function setColorMode(mode: ColorMode) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div flex="~ gap4" w-full>
|
<div flex="~ gap4 wrap" w-full>
|
||||||
<button
|
<button
|
||||||
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base
|
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base ws-nowrap
|
||||||
:tabindex="colorMode.preference === 'dark' ? 0 : -1"
|
:tabindex="colorMode.preference === 'dark' ? 0 : -1"
|
||||||
:class="colorMode.preference === 'dark' ? 'pointer-events-none' : 'filter-saturate-0'"
|
:class="colorMode.preference === 'dark' ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||||
@click="setColorMode('dark')"
|
@click="setColorMode('dark')"
|
||||||
|
@ -20,7 +20,7 @@ function setColorMode(mode: ColorMode) {
|
||||||
{{ $t('settings.interface.dark_mode') }}
|
{{ $t('settings.interface.dark_mode') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base
|
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base ws-nowrap
|
||||||
:tabindex="colorMode.preference === 'light' ? 0 : -1"
|
:tabindex="colorMode.preference === 'light' ? 0 : -1"
|
||||||
:class="colorMode.preference === 'light' ? 'pointer-events-none' : 'filter-saturate-0'"
|
:class="colorMode.preference === 'light' ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||||
@click="setColorMode('light')"
|
@click="setColorMode('light')"
|
||||||
|
@ -29,7 +29,7 @@ function setColorMode(mode: ColorMode) {
|
||||||
{{ $t('settings.interface.light_mode') }}
|
{{ $t('settings.interface.light_mode') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base
|
btn-text flex-1 flex="~ gap-1 center" p4 border="~ base rounded" bg-base ws-nowrap
|
||||||
:tabindex="colorMode.preference === 'system' ? 0 : -1"
|
:tabindex="colorMode.preference === 'system' ? 0 : -1"
|
||||||
:class="colorMode.preference === 'system' ? 'pointer-events-none' : 'filter-saturate-0'"
|
:class="colorMode.preference === 'system' ? 'pointer-events-none' : 'filter-saturate-0'"
|
||||||
@click="setColorMode('system')"
|
@click="setColorMode('system')"
|
||||||
|
|
|
@ -49,7 +49,7 @@ useCommand({
|
||||||
<component
|
<component
|
||||||
:is="as"
|
:is="as"
|
||||||
v-bind="$attrs" ref="el"
|
v-bind="$attrs" ref="el"
|
||||||
w-fit flex gap-1 items-center
|
w-fit flex gap-1 items-center transition-all
|
||||||
rounded group
|
rounded group
|
||||||
:hover=" !disabled ? hover : undefined"
|
:hover=" !disabled ? hover : undefined"
|
||||||
focus:outline-none
|
focus:outline-none
|
||||||
|
|
|
@ -55,7 +55,7 @@ const reply = () => {
|
||||||
<div flex-1>
|
<div flex-1>
|
||||||
<StatusActionButton
|
<StatusActionButton
|
||||||
:content="$t('action.boost')"
|
:content="$t('action.boost')"
|
||||||
:text="!getWellnessSetting(userSettings, 'hideBoostCount') && status.reblogsCount ? status.reblogsCount : ''"
|
:text="!getPreferences(userSettings, 'hideBoostCount') && status.reblogsCount ? status.reblogsCount : ''"
|
||||||
color="text-green" hover="text-green" group-hover="bg-green/10"
|
color="text-green" hover="text-green" group-hover="bg-green/10"
|
||||||
icon="i-ri:repeat-line"
|
icon="i-ri:repeat-line"
|
||||||
active-icon="i-ri:repeat-fill"
|
active-icon="i-ri:repeat-fill"
|
||||||
|
@ -64,7 +64,7 @@ const reply = () => {
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleReblog()"
|
@click="toggleReblog()"
|
||||||
>
|
>
|
||||||
<template v-if="status.reblogsCount && !getWellnessSetting(userSettings, 'hideBoostCount')" #text>
|
<template v-if="status.reblogsCount && !getPreferences(userSettings, 'hideBoostCount')" #text>
|
||||||
<CommonLocalizedNumber
|
<CommonLocalizedNumber
|
||||||
keypath="action.boost_count"
|
keypath="action.boost_count"
|
||||||
:count="status.reblogsCount"
|
:count="status.reblogsCount"
|
||||||
|
@ -76,7 +76,7 @@ const reply = () => {
|
||||||
<div flex-1>
|
<div flex-1>
|
||||||
<StatusActionButton
|
<StatusActionButton
|
||||||
:content="$t('action.favourite')"
|
:content="$t('action.favourite')"
|
||||||
:text="!getWellnessSetting(userSettings, 'hideFavoriteCount') && status.favouritesCount ? status.favouritesCount : ''"
|
:text="!getPreferences(userSettings, 'hideFavoriteCount') && status.favouritesCount ? status.favouritesCount : ''"
|
||||||
color="text-rose" hover="text-rose" group-hover="bg-rose/10"
|
color="text-rose" hover="text-rose" group-hover="bg-rose/10"
|
||||||
icon="i-ri:heart-3-line"
|
icon="i-ri:heart-3-line"
|
||||||
active-icon="i-ri:heart-3-fill"
|
active-icon="i-ri:heart-3-fill"
|
||||||
|
@ -85,7 +85,7 @@ const reply = () => {
|
||||||
:command="command"
|
:command="command"
|
||||||
@click="toggleFavourite()"
|
@click="toggleFavourite()"
|
||||||
>
|
>
|
||||||
<template v-if="status.favouritesCount && !getWellnessSetting(userSettings, 'hideFavoriteCount')" #text>
|
<template v-if="status.favouritesCount && !getPreferences(userSettings, 'hideFavoriteCount')" #text>
|
||||||
<CommonLocalizedNumber
|
<CommonLocalizedNumber
|
||||||
keypath="action.favourite_count"
|
keypath="action.favourite_count"
|
||||||
:count="status.favouritesCount"
|
:count="status.favouritesCount"
|
||||||
|
|
|
@ -39,7 +39,7 @@ const toggleTranslation = async () => {
|
||||||
isLoading.translation = false
|
isLoading.translation = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
const getPermalinkUrl = (status: mastodon.v1.Status) => {
|
const getPermalinkUrl = (status: mastodon.v1.Status) => {
|
||||||
const url = getStatusPermalinkRoute(status)
|
const url = getStatusPermalinkRoute(status)
|
||||||
|
@ -70,7 +70,7 @@ const deleteStatus = async () => {
|
||||||
return
|
return
|
||||||
|
|
||||||
removeCachedStatus(status.id)
|
removeCachedStatus(status.id)
|
||||||
await masto.v1.statuses.remove(status.id)
|
await client.v1.statuses.remove(status.id)
|
||||||
|
|
||||||
if (route.name === 'status')
|
if (route.name === 'status')
|
||||||
router.back()
|
router.back()
|
||||||
|
@ -88,7 +88,7 @@ const deleteAndRedraft = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCachedStatus(status.id)
|
removeCachedStatus(status.id)
|
||||||
await masto.v1.statuses.remove(status.id)
|
await client.v1.statuses.remove(status.id)
|
||||||
await openPublishDialog('dialog', await getDraftFromStatus(status), true)
|
await openPublishDialog('dialog', await getDraftFromStatus(status), true)
|
||||||
|
|
||||||
// Go to the new status, if the page is the old status
|
// Go to the new status, if the page is the old status
|
||||||
|
@ -214,7 +214,7 @@ const showFavoritedAndBoostedBy = () => {
|
||||||
@click="toggleTranslation"
|
@click="toggleTranslation"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template v-if="isMastoInitialised && currentUser">
|
<template v-if="isHydrated && currentUser">
|
||||||
<template v-if="isAuthor">
|
<template v-if="isAuthor">
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
:text="status.pinned ? $t('menu.unpin_on_profile') : $t('menu.pin_on_profile')"
|
:text="status.pinned ? $t('menu.unpin_on_profile') : $t('menu.pin_on_profile')"
|
||||||
|
|
|
@ -188,7 +188,7 @@ useIntersectionObserver(video, (entries) => {
|
||||||
{{ $t('status.img_alt.dismiss') }}
|
{{ $t('status.img_alt.dismiss') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p whitespace-pre-wrap>
|
||||||
{{ attachment.description }}
|
{{ attachment.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,8 @@ const props = withDefaults(defineProps<{
|
||||||
actions: true,
|
actions: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const userSettings = useUserSettings()
|
||||||
|
|
||||||
const status = $computed(() => {
|
const status = $computed(() => {
|
||||||
if (props.status.reblog && props.status.reblog)
|
if (props.status.reblog && props.status.reblog)
|
||||||
return props.status.reblog
|
return props.status.reblog
|
||||||
|
@ -59,7 +61,7 @@ const isDM = $computed(() => status.visibility === 'direct')
|
||||||
{{ status.application?.name }}
|
{{ status.application?.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div border="t base" pt-2>
|
<div border="t base" py-2>
|
||||||
<StatusActions v-if="actions" :status="status" details :command="command" />
|
<StatusActions v-if="actions" :status="status" details :command="command" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { favouritedBoostedByStatusId } from '~/composables/dialog'
|
||||||
|
|
||||||
const type = ref<'favourited-by' | 'boosted-by'>('favourited-by')
|
const type = ref<'favourited-by' | 'boosted-by'>('favourited-by')
|
||||||
|
|
||||||
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
return useMasto().v1.statuses[type.value === 'favourited-by' ? 'listFavouritedBy' : 'listRebloggedBy'](favouritedBoostedByStatusId.value!)
|
return client.v1.statuses[type.value === 'favourited-by' ? 'listFavouritedBy' : 'listRebloggedBy'](favouritedBoostedByStatusId.value!)
|
||||||
}
|
}
|
||||||
|
|
||||||
const paginator = $computed(() => load())
|
const paginator = $computed(() => load())
|
||||||
|
|
|
@ -15,7 +15,8 @@ const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
|
||||||
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
|
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
|
||||||
const { formatPercentage } = useHumanReadableNumber()
|
const { formatPercentage } = useHumanReadableNumber()
|
||||||
|
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
async function vote(e: Event) {
|
async function vote(e: Event) {
|
||||||
const formData = new FormData(e.target as HTMLFormElement)
|
const formData = new FormData(e.target as HTMLFormElement)
|
||||||
const choices = formData.getAll('choices') as string[]
|
const choices = formData.getAll('choices') as string[]
|
||||||
|
@ -30,25 +31,29 @@ async function vote(e: Event) {
|
||||||
poll.votersCount = (poll.votersCount || 0) + 1
|
poll.votersCount = (poll.votersCount || 0) + 1
|
||||||
cacheStatus({ ...status, poll }, undefined, true)
|
cacheStatus({ ...status, poll }, undefined, true)
|
||||||
|
|
||||||
await masto.v1.polls.vote(poll.id, { choices })
|
await client.v1.polls.vote(poll.id, { choices })
|
||||||
}
|
}
|
||||||
|
|
||||||
const votersCount = $computed(() => poll.votersCount ?? 0)
|
const votersCount = $computed(() => poll.votersCount ?? 0)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div flex flex-col w-full items-stretch gap-3 dir="auto">
|
<div flex flex-col w-full items-stretch gap-2 py3 dir="auto">
|
||||||
<form v-if="!poll.voted && !poll.expired" flex flex-col gap-4 accent-primary @click.stop="noop" @submit.prevent="vote">
|
<form v-if="!poll.voted && !poll.expired" flex="~ col gap3" accent-primary @click.stop="noop" @submit.prevent="vote">
|
||||||
<label v-for="(option, index) of poll.options" :key="index" flex items-center gap-2 px-2>
|
<label v-for="(option, index) of poll.options" :key="index" flex="~ gap2" items-center>
|
||||||
<input name="choices" :value="index" :type="poll.multiple ? 'checkbox' : 'radio'">
|
<input name="choices" :value="index" :type="poll.multiple ? 'checkbox' : 'radio'">
|
||||||
{{ option.title }}
|
{{ option.title }}
|
||||||
</label>
|
</label>
|
||||||
<button btn-solid>
|
<button btn-solid mt-1>
|
||||||
{{ $t('action.vote') }}
|
{{ $t('action.vote') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-for="(option, index) of poll.options" :key="index" py-1 relative :style="{ '--bar-width': toPercentage((option.votesCount || 0) / poll.votesCount) }">
|
<div
|
||||||
|
v-for="(option, index) of poll.options"
|
||||||
|
:key="index" py-1 relative
|
||||||
|
:style="{ '--bar-width': toPercentage((option.votesCount || 0) / poll.votesCount) }"
|
||||||
|
>
|
||||||
<div flex justify-between pb-2 w-full>
|
<div flex justify-between pb-2 w-full>
|
||||||
<span inline-flex align-items>
|
<span inline-flex align-items>
|
||||||
{{ option.title }}
|
{{ option.title }}
|
||||||
|
@ -61,7 +66,7 @@ const votersCount = $computed(() => poll.votersCount ?? 0)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div text-sm flex="~ inline" gap-x-1>
|
<div text-sm flex="~ inline" gap-x-1 text-secondary>
|
||||||
<CommonLocalizedNumber
|
<CommonLocalizedNumber
|
||||||
keypath="status.poll.count"
|
keypath="status.poll.count"
|
||||||
:count="poll.votesCount"
|
:count="poll.votesCount"
|
||||||
|
|
|
@ -21,7 +21,7 @@ const isSquare = $computed(() => (
|
||||||
))
|
))
|
||||||
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
||||||
|
|
||||||
const gitHubCards = $(useFeatureFlag('experimentalGitHubCards'))
|
const gitHubCards = $(usePreferences('experimentalGitHubCards'))
|
||||||
|
|
||||||
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
||||||
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
|
|
|
@ -6,7 +6,7 @@ const { status } = defineProps<{
|
||||||
status: mastodon.v1.Status
|
status: mastodon.v1.Status
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const paginator = useMasto().v1.statuses.listHistory(status.id)
|
const paginator = useMastoClient().v1.statuses.listHistory(status.id)
|
||||||
|
|
||||||
const showHistory = (edit: mastodon.v1.StatusEdit) => {
|
const showHistory = (edit: mastodon.v1.StatusEdit) => {
|
||||||
openEditHistoryDialog(edit)
|
openEditHistoryDialog(edit)
|
||||||
|
|
|
@ -9,13 +9,13 @@ const emit = defineEmits<{
|
||||||
(event: 'change'): void
|
(event: 'change'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
const toggleFollowTag = async () => {
|
const toggleFollowTag = async () => {
|
||||||
if (tag.following)
|
if (tag.following)
|
||||||
await masto.v1.tags.unfollow(tag.name)
|
await client.v1.tags.unfollow(tag.name)
|
||||||
else
|
else
|
||||||
await masto.v1.tags.follow(tag.name)
|
await client.v1.tags.follow(tag.name)
|
||||||
|
|
||||||
emit('change')
|
emit('change')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.blocks.list()
|
const paginator = useMastoClient().v1.blocks.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.bookmarks.list()
|
const paginator = useMastoClient().v1.bookmarks.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.conversations.list()
|
const paginator = useMastoClient().v1.conversations.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
const paginator = masto.v1.domainBlocks.list()
|
const paginator = client.v1.domainBlocks.list()
|
||||||
|
|
||||||
const unblock = async (domain: string) => {
|
const unblock = async (domain: string) => {
|
||||||
await masto.v1.domainBlocks.unblock(domain)
|
await client.v1.domainBlocks.unblock(domain)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.favourites.list()
|
const paginator = useMastoClient().v1.favourites.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.timelines.listHome({ limit: 30 })
|
const paginator = useMastoClient().v1.timelines.listHome({ limit: 30 })
|
||||||
const stream = useMasto().v1.stream.streamUser()
|
const stream = $(useStreaming(client => client.v1.stream.streamUser()))
|
||||||
onBeforeUnmount(() => stream?.then(s => s.disconnect()))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// Default limit is 20 notifications, and servers are normally caped to 30
|
// Default limit is 20 notifications, and servers are normally caped to 30
|
||||||
const paginator = useMasto().v1.notifications.list({ limit: 30, types: ['mention'] })
|
const paginator = useMastoClient().v1.notifications.list({ limit: 30, types: ['mention'] })
|
||||||
|
const stream = $(useStreaming(client => client.v1.stream.streamUser()))
|
||||||
|
|
||||||
const { clearNotifications } = useNotifications()
|
const { clearNotifications } = useNotifications()
|
||||||
onActivated(clearNotifications)
|
onActivated(clearNotifications)
|
||||||
|
|
||||||
const stream = useMasto().v1.stream.streamUser()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.mutes.list()
|
const paginator = useMastoClient().v1.mutes.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// Default limit is 20 notifications, and servers are normally caped to 30
|
// Default limit is 20 notifications, and servers are normally caped to 30
|
||||||
const paginator = useMasto().v1.notifications.list({ limit: 30 })
|
const paginator = useMastoClient().v1.notifications.list({ limit: 30 })
|
||||||
|
const stream = useStreaming(client => client.v1.stream.streamUser())
|
||||||
|
|
||||||
const { clearNotifications } = useNotifications()
|
const { clearNotifications } = useNotifications()
|
||||||
onActivated(clearNotifications)
|
onActivated(clearNotifications)
|
||||||
|
|
||||||
const stream = useMasto().v1.stream.streamUser()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -14,7 +14,7 @@ const { paginator, stream, account, buffer = 10 } = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatNumber } = useHumanReadableNumber()
|
const { formatNumber } = useHumanReadableNumber()
|
||||||
const virtualScroller = $(useFeatureFlag('experimentalVirtualScroller'))
|
const virtualScroller = $(usePreferences('experimentalVirtualScroller'))
|
||||||
|
|
||||||
const showOriginSite = $computed(() =>
|
const showOriginSite = $computed(() =>
|
||||||
account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
|
account && account.id !== currentUser.value?.account.id && getServerName(account) !== currentServer.value,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.accounts.listStatuses(currentUser.value!.account.id, { pinned: true })
|
const paginator = useMastoClient().v1.accounts.listStatuses(currentUser.value!.account.id, { pinned: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.timelines.listPublic({ limit: 30 })
|
const paginator = useMastoClient().v1.timelines.listPublic({ limit: 30 })
|
||||||
const stream = useMasto().v1.stream.streamPublicTimeline()
|
const stream = useStreaming(client => client.v1.stream.streamPublicTimeline())
|
||||||
onBeforeUnmount(() => stream.then(s => s.disconnect()))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const paginator = useMasto().v1.timelines.listPublic({ limit: 30, local: true })
|
const paginator = useMastoClient().v1.timelines.listPublic({ limit: 30, local: true })
|
||||||
const stream = useMasto().v1.stream.streamCommunityTimeline()
|
const stream = useStreaming(client => client.v1.stream.streamCommunityTimeline())
|
||||||
onBeforeUnmount(() => stream.then(s => s.disconnect()))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -46,7 +46,9 @@ defineExpose({
|
||||||
<div v-if="isPending || items.length" relative bg-base text-base shadow border="~ base rounded" text-sm py-2 overflow-x-hidden overflow-y-auto max-h-100>
|
<div v-if="isPending || items.length" relative bg-base text-base shadow border="~ base rounded" text-sm py-2 overflow-x-hidden overflow-y-auto max-h-100>
|
||||||
<template v-if="isPending">
|
<template v-if="isPending">
|
||||||
<div flex gap-1 items-center p2 animate-pulse>
|
<div flex gap-1 items-center p2 animate-pulse>
|
||||||
<div i-ri:loader-2-line animate-spin />
|
<div animate-spin preserve-3d>
|
||||||
|
<div i-ri:loader-2-line />
|
||||||
|
</div>
|
||||||
<span>Fetching...</span>
|
<span>Fetching...</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -46,7 +46,9 @@ defineExpose({
|
||||||
<div v-if="isPending || items.length" relative bg-base text-base shadow border="~ base rounded" text-sm py-2 overflow-x-hidden overflow-y-auto max-h-100>
|
<div v-if="isPending || items.length" relative bg-base text-base shadow border="~ base rounded" text-sm py-2 overflow-x-hidden overflow-y-auto max-h-100>
|
||||||
<template v-if="isPending">
|
<template v-if="isPending">
|
||||||
<div flex gap-1 items-center p2 animate-pulse>
|
<div flex gap-1 items-center p2 animate-pulse>
|
||||||
<div i-ri:loader-2-line animate-spin />
|
<div animate-spin preserve-3d>
|
||||||
|
<div i-ri:loader-2-line />
|
||||||
|
</div>
|
||||||
<span>Fetching...</span>
|
<span>Fetching...</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<VDropdown :distance="0" placement="top-start">
|
<VDropdown :distance="0" placement="top-start" strategy="fixed">
|
||||||
<button btn-action-icon :aria-label="$t('action.switch_account')">
|
<button btn-action-icon :aria-label="$t('action.switch_account')">
|
||||||
<div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line />
|
<div :class="{ 'hidden xl:block': currentUser }" i-ri:more-2-line />
|
||||||
<AccountAvatar v-if="currentUser" xl:hidden :account="currentUser.account" w-9 h-9 square />
|
<AccountAvatar v-if="currentUser" xl:hidden :account="currentUser.account" w-9 h-9 square />
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
import type { UserLogin } from '~/types'
|
import type { UserLogin } from '~/types'
|
||||||
|
|
||||||
const all = useUsers()
|
const all = useUsers()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const masto = useMasto()
|
|
||||||
const switchUser = (user: UserLogin) => {
|
const clickUser = (user: UserLogin) => {
|
||||||
if (user.account.id === currentUser.value?.account.id)
|
if (user.account.id === currentUser.value?.account.id)
|
||||||
router.push(getAccountRoute(user.account))
|
router.push(getAccountRoute(user.account))
|
||||||
else
|
else
|
||||||
masto.loginTo(user)
|
switchUser(user)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ const switchUser = (user: UserLogin) => {
|
||||||
aria-label="Switch user"
|
aria-label="Switch user"
|
||||||
:class="user.account.id === currentUser?.account.id ? '' : 'op25 grayscale'"
|
:class="user.account.id === currentUser?.account.id ? '' : 'op25 grayscale'"
|
||||||
hover="filter-none op100"
|
hover="filter-none op100"
|
||||||
@click="switchUser(user)"
|
@click="clickUser(user)"
|
||||||
>
|
>
|
||||||
<AccountAvatar w-13 h-13 :account="user.account" square />
|
<AccountAvatar w-13 h-13 :account="user.account" square />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -11,6 +11,9 @@ let knownServers = $ref<string[]>([])
|
||||||
let autocompleteIndex = $ref(0)
|
let autocompleteIndex = $ref(0)
|
||||||
let autocompleteShow = $ref(false)
|
let autocompleteShow = $ref(false)
|
||||||
|
|
||||||
|
const users = useUsers()
|
||||||
|
const userSettings = useUserSettings()
|
||||||
|
|
||||||
async function oauth() {
|
async function oauth() {
|
||||||
if (busy)
|
if (busy)
|
||||||
return
|
return
|
||||||
|
@ -25,12 +28,15 @@ async function oauth() {
|
||||||
server = server.split('/')[0]
|
server = server.split('/')[0]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
location.href = await (globalThis.$fetch as any)(`/api/${server || publicServer.value}/login`, {
|
const url = await (globalThis.$fetch as any)(`/api/${server || publicServer.value}/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
|
force_login: users.value.some(u => u.server === server),
|
||||||
origin: location.origin,
|
origin: location.origin,
|
||||||
|
lang: userSettings.value.language,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
location.href = url
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -208,7 +214,10 @@ onClickOutside($$(input), () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
|
<button flex="~ row" gap-x-2 items-center btn-solid mt2 :disabled="!server || busy">
|
||||||
<span aria-hidden="true" inline-block :class="busy ? 'i-ri:loader-2-fill animate animate-spin' : 'i-ri:login-circle-line'" class="rtl-flip" />
|
<span v-if="busy" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip">
|
||||||
|
<span block i-ri:loader-2-fill aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span v-else aria-hidden="true" block i-ri:login-circle-line class="rtl-flip" />
|
||||||
{{ $t('action.sign_in') }}
|
{{ $t('action.sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div p8 lg:flex="~ col gap2" hidden>
|
<div p8 lg:flex="~ col gap2" hidden>
|
||||||
<p v-if="isMastoInitialised" text-sm>
|
<p v-if="isHydrated" text-sm>
|
||||||
<i18n-t keypath="user.sign_in_notice_title">
|
<i18n-t keypath="user.sign_in_notice_title">
|
||||||
<strong>{{ currentServer }}</strong>
|
<strong>{{ currentServer }}</strong>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
<p text-sm text-secondary>
|
<p text-sm text-secondary>
|
||||||
{{ $t('user.sign_in_desc') }}
|
{{ $t('user.sign_in_desc') }}
|
||||||
</p>
|
</p>
|
||||||
<button btn-solid rounded-3 text-center mt-2 @click="openSigninDialog()">
|
<button btn-solid rounded-3 text-center mt-2 select-none @click="openSigninDialog()">
|
||||||
{{ $t('action.sign_in') }}
|
{{ $t('action.sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,12 +15,11 @@ const sorted = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const masto = useMasto()
|
const clickUser = (user: UserLogin) => {
|
||||||
const switchUser = (user: UserLogin) => {
|
|
||||||
if (user.account.id === currentUser.value?.account.id)
|
if (user.account.id === currentUser.value?.account.id)
|
||||||
router.push(getAccountRoute(user.account))
|
router.push(getAccountRoute(user.account))
|
||||||
else
|
else
|
||||||
masto.loginTo(user)
|
switchUser(user)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ const switchUser = (user: UserLogin) => {
|
||||||
flex rounded px4 py3 text-left
|
flex rounded px4 py3 text-left
|
||||||
hover:bg-active cursor-pointer transition-100
|
hover:bg-active cursor-pointer transition-100
|
||||||
aria-label="Switch user"
|
aria-label="Switch user"
|
||||||
@click="switchUser(user)"
|
@click="clickUser(user)"
|
||||||
>
|
>
|
||||||
<AccountInfo :account="user.account" :hover-card="false" square />
|
<AccountInfo :account="user.account" :hover-card="false" square />
|
||||||
<div flex-auto />
|
<div flex-auto />
|
||||||
|
@ -45,7 +44,7 @@ const switchUser = (user: UserLogin) => {
|
||||||
@click="openSigninDialog"
|
@click="openSigninDialog"
|
||||||
/>
|
/>
|
||||||
<CommonDropdownItem
|
<CommonDropdownItem
|
||||||
v-if="isMastoInitialised && currentUser"
|
v-if="isHydrated && currentUser"
|
||||||
:text="$t('user.sign_out_account', [getFullHandle(currentUser.account)])"
|
:text="$t('user.sign_out_account', [getFullHandle(currentUser.account)])"
|
||||||
icon="i-ri:logout-box-line rtl-flip"
|
icon="i-ri:logout-box-line rtl-flip"
|
||||||
@click="signout"
|
@click="signout"
|
||||||
|
|
|
@ -19,11 +19,12 @@ function removeCached(key: string) {
|
||||||
|
|
||||||
export function fetchStatus(id: string, force = false): Promise<mastodon.v1.Status> {
|
export function fetchStatus(id: string, force = false): Promise<mastodon.v1.Status> {
|
||||||
const server = currentServer.value
|
const server = currentServer.value
|
||||||
const key = `${server}:status:${id}`
|
const userId = currentUser.value?.account.id
|
||||||
|
const key = `${server}:${userId}:status:${id}`
|
||||||
const cached = cache.get(key)
|
const cached = cache.get(key)
|
||||||
if (cached && !force)
|
if (cached && !force)
|
||||||
return cached
|
return cached
|
||||||
const promise = useMasto().v1.statuses.fetch(id)
|
const promise = useMastoClient().v1.statuses.fetch(id)
|
||||||
.then((status) => {
|
.then((status) => {
|
||||||
cacheStatus(status)
|
cacheStatus(status)
|
||||||
return status
|
return status
|
||||||
|
@ -37,12 +38,13 @@ export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Accoun
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
|
|
||||||
const server = currentServer.value
|
const server = currentServer.value
|
||||||
const key = `${server}:account:${id}`
|
const userId = currentUser.value?.account.id
|
||||||
|
const key = `${server}:${userId}:account:${id}`
|
||||||
const cached = cache.get(key)
|
const cached = cache.get(key)
|
||||||
if (cached)
|
if (cached)
|
||||||
return cached
|
return cached
|
||||||
const domain = currentInstance.value?.uri
|
const domain = currentInstance.value ? getInstanceDomain(currentInstance.value) : null
|
||||||
const promise = useMasto().v1.accounts.fetch(id)
|
const promise = useMastoClient().v1.accounts.fetch(id)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.acct && !r.acct.includes('@') && domain)
|
if (r.acct && !r.acct.includes('@') && domain)
|
||||||
r.acct = `${r.acct}@${domain}`
|
r.acct = `${r.acct}@${domain}`
|
||||||
|
@ -56,16 +58,28 @@ export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Accoun
|
||||||
|
|
||||||
export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Account> {
|
export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Account> {
|
||||||
const server = currentServer.value
|
const server = currentServer.value
|
||||||
const key = `${server}:account:${acct}`
|
const userId = currentUser.value?.account.id
|
||||||
|
const key = `${server}:${userId}:account:${acct}`
|
||||||
const cached = cache.get(key)
|
const cached = cache.get(key)
|
||||||
if (cached)
|
if (cached)
|
||||||
return cached
|
return cached
|
||||||
const domain = currentInstance.value?.uri
|
const domain = currentInstance.value ? getInstanceDomain(currentInstance.value) : undefined
|
||||||
const account = useMasto().v1.accounts.lookup({ acct })
|
|
||||||
.then((r) => {
|
|
||||||
if (r.acct && !r.acct.includes('@') && domain)
|
|
||||||
r.acct = `${r.acct}@${domain}`
|
|
||||||
|
|
||||||
|
async function lookupAccount() {
|
||||||
|
const client = useMastoClient()
|
||||||
|
let account: mastodon.v1.Account
|
||||||
|
if (!isGotoSocial.value)
|
||||||
|
account = await client.v1.accounts.lookup({ acct })
|
||||||
|
else
|
||||||
|
account = (await client.v1.search({ q: `@${acct}`, type: 'accounts' })).accounts[0]
|
||||||
|
|
||||||
|
if (account.acct && !account.acct.includes('@') && domain)
|
||||||
|
account.acct = `${account.acct}@${domain}`
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = lookupAccount()
|
||||||
|
.then((r) => {
|
||||||
cacheAccount(r, server, true)
|
cacheAccount(r, server, true)
|
||||||
return r
|
return r
|
||||||
})
|
})
|
||||||
|
@ -82,14 +96,17 @@ export function useAccountById(id?: string | null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cacheStatus(status: mastodon.v1.Status, server = currentServer.value, override?: boolean) {
|
export function cacheStatus(status: mastodon.v1.Status, server = currentServer.value, override?: boolean) {
|
||||||
setCached(`${server}:status:${status.id}`, status, override)
|
const userId = currentUser.value?.account.id
|
||||||
|
setCached(`${server}:${userId}:status:${status.id}`, status, override)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeCachedStatus(id: string, server = currentServer.value) {
|
export function removeCachedStatus(id: string, server = currentServer.value) {
|
||||||
removeCached(`${server}:status:${id}`)
|
const userId = currentUser.value?.account.id
|
||||||
|
removeCached(`${server}:${userId}:status:${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cacheAccount(account: mastodon.v1.Account, server = currentServer.value, override?: boolean) {
|
export function cacheAccount(account: mastodon.v1.Account, server = currentServer.value, override?: boolean) {
|
||||||
setCached(`${server}:account:${account.id}`, account, override)
|
const userId = currentUser.value?.account.id
|
||||||
setCached(`${server}:account:${account.acct}`, account, override)
|
setCached(`${server}:${userId}:account:${account.id}`, account, override)
|
||||||
|
setCached(`${server}:${userId}:account:${account.acct}`, account, override)
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,7 +337,7 @@ export const provideGlobalCommands = () => {
|
||||||
icon: 'i-ri:user-shared-line',
|
icon: 'i-ri:user-shared-line',
|
||||||
|
|
||||||
onActivate() {
|
onActivate() {
|
||||||
masto.loginTo(user)
|
loginTo(masto, user)
|
||||||
},
|
},
|
||||||
})))
|
})))
|
||||||
useCommand({
|
useCommand({
|
||||||
|
|
|
@ -35,6 +35,12 @@ const sanitizer = sanitize({
|
||||||
code: {
|
code: {
|
||||||
class: filterClasses(/^language-\w+$/),
|
class: filterClasses(/^language-\w+$/),
|
||||||
},
|
},
|
||||||
|
// other elements supported in glitch
|
||||||
|
h1: {},
|
||||||
|
ol: {},
|
||||||
|
ul: {},
|
||||||
|
li: {},
|
||||||
|
em: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,7 +169,7 @@ export function treeToText(input: Node): string {
|
||||||
if ('children' in input)
|
if ('children' in input)
|
||||||
body = (input.children as Node[]).map(n => treeToText(n)).join('')
|
body = (input.children as Node[]).map(n => treeToText(n)).join('')
|
||||||
|
|
||||||
if (input.name === 'img') {
|
if (input.name === 'img' || input.name === 'picture') {
|
||||||
if (input.attributes.class?.includes('custom-emoji'))
|
if (input.attributes.class?.includes('custom-emoji'))
|
||||||
return `:${input.attributes['data-emoji-id']}:`
|
return `:${input.attributes['data-emoji-id']}:`
|
||||||
if (input.attributes.class?.includes('iconify-emoji'))
|
if (input.attributes.class?.includes('iconify-emoji'))
|
||||||
|
@ -320,11 +326,34 @@ function replaceCustomEmoji(customEmojis: Record<string, mastodon.v1.CustomEmoji
|
||||||
if (i % 2 === 0)
|
if (i % 2 === 0)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
const emoji = customEmojis[name]
|
const emoji = customEmojis[name] as mastodon.v1.CustomEmoji
|
||||||
if (!emoji)
|
if (!emoji)
|
||||||
return `:${name}:`
|
return `:${name}:`
|
||||||
|
|
||||||
return h('img', { 'src': emoji.url, 'alt': `:${name}:`, 'class': 'custom-emoji', 'data-emoji-id': name })
|
return h(
|
||||||
|
'picture',
|
||||||
|
{
|
||||||
|
'alt': `:${name}:`,
|
||||||
|
'class': 'custom-emoji',
|
||||||
|
'data-emoji-id': name,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h(
|
||||||
|
'source',
|
||||||
|
{
|
||||||
|
srcset: emoji.staticUrl,
|
||||||
|
media: '(prefers-reduced-motion: reduce)',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'img',
|
||||||
|
{
|
||||||
|
src: emoji.url,
|
||||||
|
alt: `:${name}:`,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ export async function updateCustomEmojis() {
|
||||||
if (Date.now() - currentCustomEmojis.value.lastUpdate < TTL)
|
if (Date.now() - currentCustomEmojis.value.lastUpdate < TTL)
|
||||||
return
|
return
|
||||||
|
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
const emojis = await masto.v1.customEmojis.list()
|
const emojis = await client.v1.customEmojis.list()
|
||||||
Object.assign(currentCustomEmojis.value, {
|
Object.assign(currentCustomEmojis.value, {
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
emojis,
|
emojis,
|
||||||
|
|
|
@ -3,6 +3,8 @@ import type { Ref } from 'vue'
|
||||||
import { del, get, set, update } from 'idb-keyval'
|
import { del, get, set, update } from 'idb-keyval'
|
||||||
import type { UseIDBOptions } from '@vueuse/integrations/useIDBKeyval'
|
import type { UseIDBOptions } from '@vueuse/integrations/useIDBKeyval'
|
||||||
|
|
||||||
|
const isIDBSupported = !process.test && typeof indexedDB !== 'undefined'
|
||||||
|
|
||||||
export async function useAsyncIDBKeyval<T>(
|
export async function useAsyncIDBKeyval<T>(
|
||||||
key: IDBValidKey,
|
key: IDBValidKey,
|
||||||
initialValue: MaybeComputedRef<T>,
|
initialValue: MaybeComputedRef<T>,
|
||||||
|
@ -22,6 +24,8 @@ export async function useAsyncIDBKeyval<T>(
|
||||||
const rawInit: T = resolveUnref(initialValue)
|
const rawInit: T = resolveUnref(initialValue)
|
||||||
|
|
||||||
async function read() {
|
async function read() {
|
||||||
|
if (!isIDBSupported)
|
||||||
|
return
|
||||||
try {
|
try {
|
||||||
const rawValue = await get<T>(key)
|
const rawValue = await get<T>(key)
|
||||||
if (rawValue === undefined) {
|
if (rawValue === undefined) {
|
||||||
|
@ -40,6 +44,8 @@ export async function useAsyncIDBKeyval<T>(
|
||||||
await read()
|
await read()
|
||||||
|
|
||||||
async function write() {
|
async function write() {
|
||||||
|
if (!isIDBSupported)
|
||||||
|
return
|
||||||
try {
|
try {
|
||||||
if (data.value == null) {
|
if (data.value == null) {
|
||||||
await del(key)
|
await del(key)
|
||||||
|
|
|
@ -17,7 +17,7 @@ export function getServerName(account: mastodon.v1.Account) {
|
||||||
if (account.acct?.includes('@'))
|
if (account.acct?.includes('@'))
|
||||||
return account.acct.split('@')[1]
|
return account.acct.split('@')[1]
|
||||||
// We should only lack the server name if we're on the same server as the account
|
// We should only lack the server name if we're on the same server as the account
|
||||||
return currentInstance.value?.uri || ''
|
return currentInstance.value ? getInstanceDomain(currentInstance.value) : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFullHandle(account: mastodon.v1.Account) {
|
export function getFullHandle(account: mastodon.v1.Account) {
|
||||||
|
@ -38,7 +38,7 @@ export function toShortHandle(fullHandle: string) {
|
||||||
|
|
||||||
export function extractAccountHandle(account: mastodon.v1.Account) {
|
export function extractAccountHandle(account: mastodon.v1.Account) {
|
||||||
let handle = getFullHandle(account).slice(1)
|
let handle = getFullHandle(account).slice(1)
|
||||||
const uri = currentInstance.value?.uri ?? currentServer.value
|
const uri = currentInstance.value ? getInstanceDomain(currentInstance.value) : currentServer.value
|
||||||
if (currentInstance.value && handle.endsWith(`@${uri}`))
|
if (currentInstance.value && handle.endsWith(`@${uri}`))
|
||||||
handle = handle.slice(0, -uri.length - 1)
|
handle = handle.slice(0, -uri.length - 1)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,115 @@
|
||||||
import type { ElkMasto } from '~/types'
|
import type { Pausable } from '@vueuse/core'
|
||||||
|
import type { CreateClientParams, WsEvents, mastodon } from 'masto'
|
||||||
|
import { createClient, fetchV1Instance } from 'masto'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import type { ElkInstance } from '../users'
|
||||||
|
import type { Mutable } from '~/types/utils'
|
||||||
|
import type { UserLogin } from '~/types'
|
||||||
|
|
||||||
|
export const createMasto = () => {
|
||||||
|
let client = $shallowRef<mastodon.Client>(undefined as never)
|
||||||
|
let params = $ref<Mutable<CreateClientParams>>()
|
||||||
|
const canStreaming = $computed(() => !!params?.streamingApiUrl)
|
||||||
|
|
||||||
|
const setParams = (newParams: Partial<CreateClientParams>) => {
|
||||||
|
const p = { ...params, ...newParams } as CreateClientParams
|
||||||
|
client = createClient(p)
|
||||||
|
params = p
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
client: $$(client),
|
||||||
|
params: readonly($$(params)),
|
||||||
|
canStreaming: $$(canStreaming),
|
||||||
|
setParams,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export type ElkMasto = ReturnType<typeof createMasto>
|
||||||
|
|
||||||
export const useMasto = () => useNuxtApp().$masto as ElkMasto
|
export const useMasto = () => useNuxtApp().$masto as ElkMasto
|
||||||
|
export const useMastoClient = () => useMasto().client.value
|
||||||
|
|
||||||
export const isMastoInitialised = computed(() => process.client && useMasto().loggedIn.value)
|
export function mastoLogin(masto: ElkMasto, user: Pick<UserLogin, 'server' | 'token'>) {
|
||||||
|
const { setParams } = $(masto)
|
||||||
|
|
||||||
export const onMastoInit = (cb: () => unknown) => {
|
const server = user.server
|
||||||
watchOnce(isMastoInitialised, () => {
|
const url = `https://${server}`
|
||||||
cb()
|
const instance: ElkInstance = reactive(getInstanceCache(server) || { uri: server, accountDomain: server })
|
||||||
}, { immediate: isMastoInitialised.value })
|
setParams({
|
||||||
|
url,
|
||||||
|
accessToken: user?.token,
|
||||||
|
disableVersionCheck: true,
|
||||||
|
streamingApiUrl: instance?.urls?.streamingApi,
|
||||||
|
})
|
||||||
|
|
||||||
|
fetchV1Instance({ url }).then((newInstance) => {
|
||||||
|
Object.assign(instance, newInstance)
|
||||||
|
setParams({
|
||||||
|
streamingApiUrl: newInstance.urls.streamingApi,
|
||||||
|
})
|
||||||
|
instances.value[server] = newInstance
|
||||||
|
})
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseStreamingOptions<Controls extends boolean> {
|
||||||
|
/**
|
||||||
|
* Expose more controls
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
controls?: Controls
|
||||||
|
/**
|
||||||
|
* Connect on calling
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
immediate?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStreaming(
|
||||||
|
cb: (client: mastodon.Client) => Promise<WsEvents>,
|
||||||
|
options: UseStreamingOptions<true>,
|
||||||
|
): { stream: Ref<Promise<WsEvents> | undefined> } & Pausable
|
||||||
|
export function useStreaming(
|
||||||
|
cb: (client: mastodon.Client) => Promise<WsEvents>,
|
||||||
|
options?: UseStreamingOptions<false>,
|
||||||
|
): Ref<Promise<WsEvents> | undefined>
|
||||||
|
export function useStreaming(
|
||||||
|
cb: (client: mastodon.Client) => Promise<WsEvents>,
|
||||||
|
{ immediate = true, controls }: UseStreamingOptions<boolean> = {},
|
||||||
|
): ({ stream: Ref<Promise<WsEvents> | undefined> } & Pausable) | Ref<Promise<WsEvents> | undefined> {
|
||||||
|
const { canStreaming, client } = useMasto()
|
||||||
|
|
||||||
|
const isActive = ref(immediate)
|
||||||
|
const stream = ref<Promise<WsEvents>>()
|
||||||
|
|
||||||
|
function pause() {
|
||||||
|
isActive.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function resume() {
|
||||||
|
isActive.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
if (stream.value) {
|
||||||
|
stream.value.then(s => s.disconnect()).catch(() => Promise.resolve())
|
||||||
|
stream.value = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
cleanup()
|
||||||
|
if (canStreaming.value && isActive.value)
|
||||||
|
stream.value = cb(client.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
tryOnBeforeUnmount(() => isActive.value = false)
|
||||||
|
|
||||||
|
if (controls)
|
||||||
|
return { stream, isActive, pause, resume }
|
||||||
|
else
|
||||||
|
return stream
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,32 +4,39 @@ const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, st
|
||||||
|
|
||||||
export const useNotifications = () => {
|
export const useNotifications = () => {
|
||||||
const id = currentUser.value?.account.id
|
const id = currentUser.value?.account.id
|
||||||
const masto = useMasto()
|
|
||||||
|
const { client, canStreaming } = $(useMasto())
|
||||||
|
|
||||||
async function clearNotifications() {
|
async function clearNotifications() {
|
||||||
if (!id || !notifications[id])
|
if (!id || !notifications[id])
|
||||||
return
|
return
|
||||||
const lastReadId = notifications[id]![1][0]
|
const lastReadId = notifications[id]![1][0]
|
||||||
notifications[id]![1] = []
|
notifications[id]![1] = []
|
||||||
|
if (lastReadId) {
|
||||||
await masto.v1.markers.create({
|
await client.v1.markers.create({
|
||||||
notifications: { lastReadId },
|
notifications: { lastReadId },
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connect(): Promise<void> {
|
async function connect(): Promise<void> {
|
||||||
if (!isMastoInitialised.value || !id || notifications[id] || !currentUser.value?.token)
|
if (!isHydrated.value || !id || notifications[id] || !currentUser.value?.token)
|
||||||
return
|
return
|
||||||
|
|
||||||
const stream = masto.v1.stream.streamUser()
|
let resolveStream
|
||||||
|
const stream = new Promise<WsEvents>(resolve => resolveStream = resolve)
|
||||||
notifications[id] = [stream, []]
|
notifications[id] = [stream, []]
|
||||||
|
|
||||||
|
await until($$(canStreaming)).toBe(true)
|
||||||
|
|
||||||
|
client.v1.stream.streamUser().then(resolveStream)
|
||||||
stream.then(s => s.on('notification', (n) => {
|
stream.then(s => s.on('notification', (n) => {
|
||||||
if (notifications[id])
|
if (notifications[id])
|
||||||
notifications[id]![1].unshift(n.id)
|
notifications[id]![1].unshift(n.id)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const position = await masto.v1.markers.fetch({ timeline: ['notifications'] })
|
const position = await client.v1.markers.fetch({ timeline: ['notifications'] })
|
||||||
const paginator = masto.v1.notifications.list({ limit: 30 })
|
const paginator = client.v1.notifications.list({ limit: 30 })
|
||||||
do {
|
do {
|
||||||
const result = await paginator.next()
|
const result = await paginator.next()
|
||||||
if (!result.done && result.value.length) {
|
if (!result.done && result.value.length) {
|
||||||
|
@ -53,10 +60,10 @@ export const useNotifications = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(currentUser, disconnect)
|
watch(currentUser, disconnect)
|
||||||
if (isMastoInitialised.value)
|
|
||||||
|
onHydrated(() => {
|
||||||
connect()
|
connect()
|
||||||
else
|
})
|
||||||
watchOnce(isMastoInitialised, connect)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notifications: computed(() => id ? notifications[id]?.[1].length ?? 0 : 0),
|
notifications: computed(() => id ? notifications[id]?.[1].length ?? 0 : 0),
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const usePublish = (options: {
|
||||||
}) => {
|
}) => {
|
||||||
const { expanded, isUploading, initialDraft } = $(options)
|
const { expanded, isUploading, initialDraft } = $(options)
|
||||||
let { draft, isEmpty } = $(options.draftState)
|
let { draft, isEmpty } = $(options.draftState)
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
let isSending = $ref(false)
|
let isSending = $ref(false)
|
||||||
const isExpanded = $ref(false)
|
const isExpanded = $ref(false)
|
||||||
|
@ -51,9 +51,9 @@ export const usePublish = (options: {
|
||||||
|
|
||||||
let status: mastodon.v1.Status
|
let status: mastodon.v1.Status
|
||||||
if (!draft.editingStatus)
|
if (!draft.editingStatus)
|
||||||
status = await masto.v1.statuses.create(payload)
|
status = await client.v1.statuses.create(payload)
|
||||||
else
|
else
|
||||||
status = await masto.v1.statuses.update(draft.editingStatus.id, payload)
|
status = await client.v1.statuses.update(draft.editingStatus.id, payload)
|
||||||
if (draft.params.inReplyToId)
|
if (draft.params.inReplyToId)
|
||||||
navigateToStatus({ status })
|
navigateToStatus({ status })
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ export type MediaAttachmentUploadError = [filename: string, message: string]
|
||||||
|
|
||||||
export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
||||||
const draft = $(draftRef)
|
const draft = $(draftRef)
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
let isUploading = $ref<boolean>(false)
|
let isUploading = $ref<boolean>(false)
|
||||||
|
@ -96,12 +96,12 @@ export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
||||||
failedAttachments = []
|
failedAttachments = []
|
||||||
// TODO: display some kind of message if too many media are selected
|
// TODO: display some kind of message if too many media are selected
|
||||||
// DONE
|
// DONE
|
||||||
const limit = currentInstance.value!.configuration.statuses.maxMediaAttachments || 4
|
const limit = currentInstance.value!.configuration?.statuses.maxMediaAttachments || 4
|
||||||
for (const file of files.slice(0, limit)) {
|
for (const file of files.slice(0, limit)) {
|
||||||
if (draft.attachments.length < limit) {
|
if (draft.attachments.length < limit) {
|
||||||
isExceedingAttachmentLimit = false
|
isExceedingAttachmentLimit = false
|
||||||
try {
|
try {
|
||||||
const attachment = await masto.v1.mediaAttachments.create({
|
const attachment = await client.v1.mediaAttachments.create({
|
||||||
file,
|
file,
|
||||||
})
|
})
|
||||||
draft.attachments.push(attachment)
|
draft.attachments.push(attachment)
|
||||||
|
@ -121,7 +121,7 @@ export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pickAttachments() {
|
async function pickAttachments() {
|
||||||
const mimeTypes = currentInstance.value!.configuration.mediaAttachments.supportedMimeTypes
|
const mimeTypes = currentInstance.value!.configuration?.mediaAttachments.supportedMimeTypes
|
||||||
const files = await fileOpen({
|
const files = await fileOpen({
|
||||||
description: 'Attachments',
|
description: 'Attachments',
|
||||||
multiple: true,
|
multiple: true,
|
||||||
|
@ -132,7 +132,7 @@ export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
||||||
|
|
||||||
async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
|
async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
|
||||||
att.description = description
|
att.description = description
|
||||||
await masto.v1.mediaAttachments.update(att.id, { description: att.description })
|
await client.v1.mediaAttachments.update(att.id, { description: att.description })
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAttachment(index: number) {
|
function removeAttachment(index: number) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function useRelationship(account: mastodon.v1.Account): Ref<mastodon.v1.R
|
||||||
|
|
||||||
async function fetchRelationships() {
|
async function fetchRelationships() {
|
||||||
const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value)
|
const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value)
|
||||||
const relationships = await useMasto().v1.accounts.fetchRelationships(requested.map(([id]) => id))
|
const relationships = await useMastoClient().v1.accounts.fetchRelationships(requested.map(([id]) => id))
|
||||||
for (let i = 0; i < requested.length; i++)
|
for (let i = 0; i < requested.length; i++)
|
||||||
requested[i][1].value = relationships[i]
|
requested[i][1].value = relationships[i]
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export type SearchResult = HashTagSearchResult | AccountSearchResult | StatusSea
|
||||||
|
|
||||||
export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOptions = {}) {
|
export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOptions = {}) {
|
||||||
const done = ref(false)
|
const done = ref(false)
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const accounts = ref<AccountSearchResult[]>([])
|
const accounts = ref<AccountSearchResult[]>([])
|
||||||
const hashtags = ref<HashTagSearchResult[]>([])
|
const hashtags = ref<HashTagSearchResult[]>([])
|
||||||
|
@ -59,11 +59,11 @@ export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => resolveUnref(query), () => {
|
watch(() => resolveUnref(query), () => {
|
||||||
loading.value = !!(q && isMastoInitialised.value)
|
loading.value = !!(q && isHydrated.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
debouncedWatch(() => resolveUnref(query), async () => {
|
debouncedWatch(() => resolveUnref(query), async () => {
|
||||||
if (!q || !isMastoInitialised.value)
|
if (!q || !isHydrated.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
@ -72,7 +72,7 @@ export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOpt
|
||||||
* Based on the source it seems like modifying the params when calling next would result in a new search,
|
* Based on the source it seems like modifying the params when calling next would result in a new search,
|
||||||
* but that doesn't seem to be the case. So instead we just create a new paginator with the new params.
|
* 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({
|
paginator = client.v2.search({
|
||||||
q,
|
q,
|
||||||
...resolveUnref(options),
|
...resolveUnref(options),
|
||||||
resolve: !!currentUser.value,
|
resolve: !!currentUser.value,
|
||||||
|
@ -87,7 +87,7 @@ export function useSearch(query: MaybeComputedRef<string>, options: UseSearchOpt
|
||||||
}, { debounce: 300 })
|
}, { debounce: 300 })
|
||||||
|
|
||||||
const next = async () => {
|
const next = async () => {
|
||||||
if (!q || !isMastoInitialised.value || !paginator)
|
if (!q || !isHydrated.value || !paginator)
|
||||||
return
|
return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
|
@ -9,7 +9,7 @@ export interface StatusActionsProps {
|
||||||
|
|
||||||
export function useStatusActions(props: StatusActionsProps) {
|
export function useStatusActions(props: StatusActionsProps) {
|
||||||
let status = $ref<mastodon.v1.Status>({ ...props.status })
|
let status = $ref<mastodon.v1.Status>({ ...props.status })
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.status,
|
() => props.status,
|
||||||
|
@ -61,7 +61,7 @@ export function useStatusActions(props: StatusActionsProps) {
|
||||||
|
|
||||||
const toggleReblog = () => toggleStatusAction(
|
const toggleReblog = () => toggleStatusAction(
|
||||||
'reblogged',
|
'reblogged',
|
||||||
() => masto.v1.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
|
() => client.v1.statuses[status.reblogged ? 'unreblog' : 'reblog'](status.id).then((res) => {
|
||||||
if (status.reblogged)
|
if (status.reblogged)
|
||||||
// returns the original status
|
// returns the original status
|
||||||
return res.reblog!
|
return res.reblog!
|
||||||
|
@ -72,23 +72,23 @@ export function useStatusActions(props: StatusActionsProps) {
|
||||||
|
|
||||||
const toggleFavourite = () => toggleStatusAction(
|
const toggleFavourite = () => toggleStatusAction(
|
||||||
'favourited',
|
'favourited',
|
||||||
() => masto.v1.statuses[status.favourited ? 'unfavourite' : 'favourite'](status.id),
|
() => client.v1.statuses[status.favourited ? 'unfavourite' : 'favourite'](status.id),
|
||||||
'favouritesCount',
|
'favouritesCount',
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleBookmark = () => toggleStatusAction(
|
const toggleBookmark = () => toggleStatusAction(
|
||||||
'bookmarked',
|
'bookmarked',
|
||||||
() => masto.v1.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
|
() => client.v1.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
const togglePin = async () => toggleStatusAction(
|
const togglePin = async () => toggleStatusAction(
|
||||||
'pinned',
|
'pinned',
|
||||||
() => masto.v1.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
|
() => client.v1.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleMute = async () => toggleStatusAction(
|
const toggleMute = async () => toggleStatusAction(
|
||||||
'muted',
|
'muted',
|
||||||
() => masto.v1.statuses[status.muted ? 'unmute' : 'mute'](status.id),
|
() => client.v1.statuses[status.muted ? 'unmute' : 'mute'](status.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { STORAGE_KEY_DRAFTS } from '~/constants'
|
||||||
import type { Draft, DraftMap } from '~/types'
|
import type { Draft, DraftMap } from '~/types'
|
||||||
import type { Mutable } from '~/types/utils'
|
import type { Mutable } from '~/types/utils'
|
||||||
|
|
||||||
export const currentUserDrafts = process.server ? computed<DraftMap>(() => ({})) : useUserLocalStorage<DraftMap>(STORAGE_KEY_DRAFTS, () => ({}))
|
export const currentUserDrafts = process.server || process.test ? computed<DraftMap>(() => ({})) : useUserLocalStorage<DraftMap>(STORAGE_KEY_DRAFTS, () => ({}))
|
||||||
|
|
||||||
export const builtinDraftKeys = [
|
export const builtinDraftKeys = [
|
||||||
'dialog',
|
'dialog',
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import type { Paginator, WsEvents, mastodon } from 'masto'
|
import type { Paginator, WsEvents, mastodon } from 'masto'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
import type { PaginatorState } from '~/types'
|
import type { PaginatorState } from '~/types'
|
||||||
|
|
||||||
export function usePaginator<T, P, U = T>(
|
export function usePaginator<T, P, U = T>(
|
||||||
_paginator: Paginator<T[], P>,
|
_paginator: Paginator<T[], P>,
|
||||||
stream?: Promise<WsEvents>,
|
stream: Ref<Promise<WsEvents> | undefined>,
|
||||||
eventType: 'notification' | 'update' = 'update',
|
eventType: 'notification' | 'update' = 'update',
|
||||||
preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[],
|
preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[],
|
||||||
buffer = 10,
|
buffer = 10,
|
||||||
|
@ -13,7 +14,7 @@ export function usePaginator<T, P, U = T>(
|
||||||
// so clone it
|
// so clone it
|
||||||
const paginator = _paginator.clone()
|
const paginator = _paginator.clone()
|
||||||
|
|
||||||
const state = ref<PaginatorState>(isMastoInitialised.value ? 'idle' : 'loading')
|
const state = ref<PaginatorState>(isHydrated.value ? 'idle' : 'loading')
|
||||||
const items = ref<U[]>([])
|
const items = ref<U[]>([])
|
||||||
const nextItems = ref<U[]>([])
|
const nextItems = ref<U[]>([])
|
||||||
const prevItems = ref<T[]>([])
|
const prevItems = ref<T[]>([])
|
||||||
|
@ -29,37 +30,39 @@ export function usePaginator<T, P, U = T>(
|
||||||
prevItems.value = []
|
prevItems.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
stream?.then((s) => {
|
watch(stream, (stream) => {
|
||||||
s.on(eventType, (status) => {
|
stream?.then((s) => {
|
||||||
if ('uri' in status)
|
s.on(eventType, (status) => {
|
||||||
|
if ('uri' in status)
|
||||||
|
cacheStatus(status, undefined, true)
|
||||||
|
|
||||||
|
const index = prevItems.value.findIndex((i: any) => i.id === status.id)
|
||||||
|
if (index >= 0)
|
||||||
|
prevItems.value.splice(index, 1)
|
||||||
|
|
||||||
|
prevItems.value.unshift(status as any)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: update statuses
|
||||||
|
s.on('status.update', (status) => {
|
||||||
cacheStatus(status, undefined, true)
|
cacheStatus(status, undefined, true)
|
||||||
|
|
||||||
const index = prevItems.value.findIndex((i: any) => i.id === status.id)
|
const data = items.value as mastodon.v1.Status[]
|
||||||
if (index >= 0)
|
const index = data.findIndex(s => s.id === status.id)
|
||||||
prevItems.value.splice(index, 1)
|
if (index >= 0)
|
||||||
|
data[index] = status
|
||||||
|
})
|
||||||
|
|
||||||
prevItems.value.unshift(status as any)
|
s.on('delete', (id) => {
|
||||||
|
removeCachedStatus(id)
|
||||||
|
|
||||||
|
const data = items.value as mastodon.v1.Status[]
|
||||||
|
const index = data.findIndex(s => s.id === id)
|
||||||
|
if (index >= 0)
|
||||||
|
data.splice(index, 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
}, { immediate: true })
|
||||||
// TODO: update statuses
|
|
||||||
s.on('status.update', (status) => {
|
|
||||||
cacheStatus(status, undefined, true)
|
|
||||||
|
|
||||||
const data = items.value as mastodon.v1.Status[]
|
|
||||||
const index = data.findIndex(s => s.id === status.id)
|
|
||||||
if (index >= 0)
|
|
||||||
data[index] = status
|
|
||||||
})
|
|
||||||
|
|
||||||
s.on('delete', (id) => {
|
|
||||||
removeCachedStatus(id)
|
|
||||||
|
|
||||||
const data = items.value as mastodon.v1.Status[]
|
|
||||||
const index = data.findIndex(s => s.id === id)
|
|
||||||
if (index >= 0)
|
|
||||||
data.splice(index, 1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
async function loadNext() {
|
async function loadNext() {
|
||||||
if (state.value !== 'idle')
|
if (state.value !== 'idle')
|
||||||
|
@ -101,8 +104,8 @@ export function usePaginator<T, P, U = T>(
|
||||||
bound.update()
|
bound.update()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
if (!isMastoInitialised.value) {
|
if (!isHydrated.value) {
|
||||||
onMastoInit(() => {
|
onHydrated(() => {
|
||||||
state.value = 'idle'
|
state.value = 'idle'
|
||||||
loadNext()
|
loadNext()
|
||||||
})
|
})
|
||||||
|
|
|
@ -132,5 +132,5 @@ async function sendSubscriptionToBackend(
|
||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
|
|
||||||
return await useMasto().v1.webPushSubscriptions.create(params)
|
return await useMastoClient().v1.webPushSubscriptions.create(params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ const supportsPushNotifications = typeof window !== 'undefined'
|
||||||
&& 'getKey' in PushSubscription.prototype
|
&& 'getKey' in PushSubscription.prototype
|
||||||
|
|
||||||
export const usePushManager = () => {
|
export const usePushManager = () => {
|
||||||
const masto = useMasto()
|
const { client } = $(useMasto())
|
||||||
const isSubscribed = ref(false)
|
const isSubscribed = ref(false)
|
||||||
const notificationPermission = ref<PermissionState | undefined>(
|
const notificationPermission = ref<PermissionState | undefined>(
|
||||||
Notification.permission === 'denied'
|
Notification.permission === 'denied'
|
||||||
|
@ -168,7 +168,7 @@ export const usePushManager = () => {
|
||||||
if (policyChanged)
|
if (policyChanged)
|
||||||
await subscribe(data, policy, true)
|
await subscribe(data, policy, true)
|
||||||
else
|
else
|
||||||
currentUser.value.pushSubscription = await masto.v1.webPushSubscriptions.update({ data })
|
currentUser.value.pushSubscription = await client.v1.webPushSubscriptions.update({ data })
|
||||||
|
|
||||||
policyChanged && await nextTick()
|
policyChanged && await nextTick()
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,44 @@
|
||||||
import { DEFAULT_FONT_SIZE, DEFAULT_LANGUAGE } from '~/constants'
|
import { DEFAULT_FONT_SIZE } from '~/constants'
|
||||||
|
|
||||||
export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
export type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
export type ColorMode = 'light' | 'dark' | 'system'
|
export type ColorMode = 'light' | 'dark' | 'system'
|
||||||
|
|
||||||
export interface FeatureFlags {
|
export interface PreferencesSettings {
|
||||||
|
hideBoostCount: boolean
|
||||||
|
hideFavoriteCount: boolean
|
||||||
|
hideFollowerCount: boolean
|
||||||
experimentalVirtualScroller: boolean
|
experimentalVirtualScroller: boolean
|
||||||
experimentalGitHubCards: boolean
|
experimentalGitHubCards: boolean
|
||||||
experimentalUserPicker: boolean
|
experimentalUserPicker: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WellnessSettings {
|
|
||||||
hideBoostCount: boolean
|
|
||||||
hideFavoriteCount: boolean
|
|
||||||
hideFollowerCount: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
featureFlags: Partial<FeatureFlags>
|
preferences: Partial<PreferencesSettings>
|
||||||
wellnessSettings: Partial<WellnessSettings>
|
|
||||||
colorMode?: ColorMode
|
colorMode?: ColorMode
|
||||||
fontSize: FontSize
|
fontSize: FontSize
|
||||||
language: string
|
language: string
|
||||||
zenMode?: boolean
|
zenMode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultUserSettings(): UserSettings {
|
export function getDefaultLanguage(languages: string[]) {
|
||||||
|
if (process.server)
|
||||||
|
return 'en-US'
|
||||||
|
return matchLanguages(languages, navigator.languages) || 'en-US'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultUserSettings(locales: string[]): UserSettings {
|
||||||
return {
|
return {
|
||||||
language: DEFAULT_LANGUAGE,
|
language: getDefaultLanguage(locales),
|
||||||
fontSize: DEFAULT_FONT_SIZE,
|
fontSize: DEFAULT_FONT_SIZE,
|
||||||
featureFlags: {},
|
zenMode: false,
|
||||||
wellnessSettings: {},
|
preferences: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_WELLNESS_SETTINGS: WellnessSettings = {
|
export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
|
||||||
hideBoostCount: false,
|
hideBoostCount: false,
|
||||||
hideFavoriteCount: false,
|
hideFavoriteCount: false,
|
||||||
hideFollowerCount: false,
|
hideFollowerCount: false,
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_FEATURE_FLAGS: FeatureFlags = {
|
|
||||||
experimentalVirtualScroller: true,
|
experimentalVirtualScroller: true,
|
||||||
experimentalGitHubCards: true,
|
experimentalGitHubCards: true,
|
||||||
experimentalUserPicker: true,
|
experimentalUserPicker: true,
|
||||||
|
|
|
@ -1,53 +1,35 @@
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { FeatureFlags, UserSettings, WellnessSettings } from './definition'
|
import type { VueI18n } from 'vue-i18n'
|
||||||
|
import type { LocaleObject } from 'vue-i18n-routing'
|
||||||
|
import type { PreferencesSettings, UserSettings } from './definition'
|
||||||
import { STORAGE_KEY_SETTINGS } from '~/constants'
|
import { STORAGE_KEY_SETTINGS } from '~/constants'
|
||||||
|
|
||||||
export const useUserSettings = () => {
|
export function useUserSettings() {
|
||||||
if (process.server)
|
const i18n = useNuxtApp().vueApp.config.globalProperties.$i18n as VueI18n
|
||||||
return useState('user-settings', getDefaultUserSettings)
|
const { locales } = i18n
|
||||||
return useUserLocalStorage(STORAGE_KEY_SETTINGS, getDefaultUserSettings)
|
const supportLanguages = (locales as LocaleObject[]).map(locale => locale.code)
|
||||||
|
return useUserLocalStorage<UserSettings>(STORAGE_KEY_SETTINGS, () => getDefaultUserSettings(supportLanguages))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor & simplify this
|
// TODO: refactor & simplify this
|
||||||
|
|
||||||
export function useWellnessSetting<T extends keyof WellnessSettings>(name: T): Ref<WellnessSettings[T]> {
|
export function usePreferences<T extends keyof PreferencesSettings>(name: T): Ref<PreferencesSettings[T]> {
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
return computed({
|
return computed({
|
||||||
get() {
|
get() {
|
||||||
return getWellnessSetting(userSettings.value, name)
|
return getPreferences(userSettings.value, name)
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
userSettings.value.wellnessSettings[name] = value
|
userSettings.value.preferences[name] = value
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWellnessSetting<T extends keyof WellnessSettings>(userSettings: UserSettings, name: T): WellnessSettings[T] {
|
export function getPreferences<T extends keyof PreferencesSettings>(userSettings: UserSettings, name: T): PreferencesSettings[T] {
|
||||||
return userSettings?.wellnessSettings?.[name] ?? DEFAULT_WELLNESS_SETTINGS[name]
|
return userSettings?.preferences?.[name] ?? DEFAULT__PREFERENCES_SETTINGS[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleWellnessSetting(key: keyof WellnessSettings) {
|
export function togglePreferences(key: keyof PreferencesSettings) {
|
||||||
const flag = useWellnessSetting(key)
|
const flag = usePreferences(key)
|
||||||
flag.value = !flag.value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFeatureFlag<T extends keyof FeatureFlags>(name: T): Ref<FeatureFlags[T]> {
|
|
||||||
const userSettings = useUserSettings()
|
|
||||||
return computed({
|
|
||||||
get() {
|
|
||||||
return getFeatureFlag(userSettings.value, name)
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
userSettings.value.featureFlags[name] = value
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFeatureFlag<T extends keyof FeatureFlags>(userSettings: UserSettings, name: T): FeatureFlags[T] {
|
|
||||||
return userSettings?.featureFlags?.[name] ?? DEFAULT_FEATURE_FLAGS[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleFeatureFlag(key: keyof FeatureFlags) {
|
|
||||||
const flag = useFeatureFlag(key)
|
|
||||||
flag.value = !flag.value
|
flag.value = !flag.value
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,11 @@ export function useHightlighter(lang: Lang) {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
registeredLang.value.set(lang, true)
|
registeredLang.value.set(lang, true)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch(() => {
|
||||||
console.error(`[shiki] Failed to load language ${lang}`)
|
const fallbackLang = 'md'
|
||||||
console.error(e)
|
shiki.value?.loadLanguage(fallbackLang).then(() => {
|
||||||
registeredLang.value.set(lang, false)
|
registeredLang.value.set(fallbackLang, true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -47,10 +48,22 @@ export function useShikiTheme() {
|
||||||
return useColorMode().value === 'dark' ? 'vitesse-dark' : 'vitesse-light'
|
return useColorMode().value === 'dark' ? 'vitesse-dark' : 'vitesse-light'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HTML_ENTITIES = {
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'&': '&',
|
||||||
|
'\'': ''',
|
||||||
|
'"': '"',
|
||||||
|
} as Record<string, string>
|
||||||
|
|
||||||
|
function escapeHtml(text: string) {
|
||||||
|
return text.replace(/[<>&'"]/g, ch => HTML_ENTITIES[ch])
|
||||||
|
}
|
||||||
|
|
||||||
export function highlightCode(code: string, lang: Lang) {
|
export function highlightCode(code: string, lang: Lang) {
|
||||||
const shiki = useHightlighter(lang)
|
const shiki = useHightlighter(lang)
|
||||||
if (!shiki)
|
if (!shiki)
|
||||||
return code
|
return escapeHtml(code)
|
||||||
|
|
||||||
return shiki.codeToHtml(code, {
|
return shiki.codeToHtml(code, {
|
||||||
lang,
|
lang,
|
||||||
|
|
|
@ -43,7 +43,7 @@ function getDecorations({
|
||||||
findChildren(doc, node => node.type.name === name)
|
findChildren(doc, node => node.type.name === name)
|
||||||
.forEach((block) => {
|
.forEach((block) => {
|
||||||
let from = block.pos + 1
|
let from = block.pos + 1
|
||||||
const language = block.node.attrs.language || 'text'
|
const language = block.node.attrs.language
|
||||||
|
|
||||||
const shiki = useHightlighter(language)
|
const shiki = useHightlighter(language)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const MentionSuggestion: Partial<SuggestionOptions> = {
|
||||||
if (query.length === 0)
|
if (query.length === 0)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
const results = await useMasto().v2.search({ q: query, type: 'accounts', limit: 25, resolve: true })
|
const results = await useMastoClient().v2.search({ q: query, type: 'accounts', limit: 25, resolve: true })
|
||||||
return results.accounts
|
return results.accounts
|
||||||
},
|
},
|
||||||
render: createSuggestionRenderer(TiptapMentionList),
|
render: createSuggestionRenderer(TiptapMentionList),
|
||||||
|
@ -36,7 +36,7 @@ export const HashtagSuggestion: Partial<SuggestionOptions> = {
|
||||||
if (query.length === 0)
|
if (query.length === 0)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
const results = await useMasto().v2.search({
|
const results = await useMastoClient().v2.search({
|
||||||
q: query,
|
q: query,
|
||||||
type: 'hashtags',
|
type: 'hashtags',
|
||||||
limit: 25,
|
limit: 25,
|
||||||
|
@ -113,10 +113,13 @@ function createSuggestionRenderer(component: Component): SuggestionOptions['rend
|
||||||
|
|
||||||
// Use arrow function here because Nuxt will transform it incorrectly as Vue hook causing the build to fail
|
// Use arrow function here because Nuxt will transform it incorrectly as Vue hook causing the build to fail
|
||||||
onBeforeUpdate: (props) => {
|
onBeforeUpdate: (props) => {
|
||||||
renderer.updateProps({ ...props, isPending: true })
|
props.editor.isFocused && renderer.updateProps({ ...props, isPending: true })
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdate(props) {
|
onUpdate(props) {
|
||||||
|
if (!props.editor.isFocused)
|
||||||
|
return
|
||||||
|
|
||||||
renderer.updateProps({ ...props, isPending: false })
|
renderer.updateProps({ ...props, isPending: false })
|
||||||
|
|
||||||
if (!props.clientRect)
|
if (!props.clientRect)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import { createClient, fetchV1Instance } from 'masto'
|
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
import type { Ref } from 'vue'
|
import type { EffectScope, Ref } from 'vue'
|
||||||
import type { MaybeComputedRef, RemovableRef } from '@vueuse/core'
|
import type { MaybeComputedRef, RemovableRef } from '@vueuse/core'
|
||||||
import type { ElkMasto, UserLogin } from '~/types'
|
import type { ElkMasto } from './masto/masto'
|
||||||
|
import type { UserLogin } from '~/types'
|
||||||
|
import type { Overwrite } from '~/types/utils'
|
||||||
import {
|
import {
|
||||||
DEFAULT_POST_CHARS_LIMIT,
|
DEFAULT_POST_CHARS_LIMIT,
|
||||||
STORAGE_KEY_CURRENT_USER,
|
STORAGE_KEY_CURRENT_USER,
|
||||||
|
STORAGE_KEY_CURRENT_USER_HANDLE,
|
||||||
|
STORAGE_KEY_NODES,
|
||||||
STORAGE_KEY_NOTIFICATION,
|
STORAGE_KEY_NOTIFICATION,
|
||||||
STORAGE_KEY_NOTIFICATION_POLICY,
|
STORAGE_KEY_NOTIFICATION_POLICY,
|
||||||
STORAGE_KEY_SERVERS,
|
STORAGE_KEY_SERVERS,
|
||||||
|
@ -40,9 +43,17 @@ const initializeUsers = async (): Promise<Ref<UserLogin[]> | RemovableRef<UserLo
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = await initializeUsers()
|
const users = await initializeUsers()
|
||||||
const instances = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
export const instances = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||||
|
export const nodes = useLocalStorage<Record<string, any>>(STORAGE_KEY_NODES, {}, { deep: true })
|
||||||
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
||||||
|
|
||||||
|
export type ElkInstance = Partial<mastodon.v1.Instance> & {
|
||||||
|
uri: string
|
||||||
|
/** support GoToSocial */
|
||||||
|
accountDomain?: string | null
|
||||||
|
}
|
||||||
|
export const getInstanceCache = (server: string): mastodon.v1.Instance | undefined => instances.value[server]
|
||||||
|
|
||||||
export const currentUser = computed<UserLogin | undefined>(() => {
|
export const currentUser = computed<UserLogin | undefined>(() => {
|
||||||
if (currentUserId.value) {
|
if (currentUserId.value) {
|
||||||
const user = users.value.find(user => user.account?.id === currentUserId.value)
|
const user = users.value.find(user => user.account?.id === currentUserId.value)
|
||||||
|
@ -53,13 +64,20 @@ export const currentUser = computed<UserLogin | undefined>(() => {
|
||||||
return users.value[0]
|
return users.value[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
const publicInstance = ref<mastodon.v1.Instance | null>(null)
|
const publicInstance = ref<ElkInstance | null>(null)
|
||||||
export const currentInstance = computed<null | mastodon.v1.Instance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value)
|
export const currentInstance = computed<null | ElkInstance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value)
|
||||||
export const isGlitchEdition = computed(() => currentInstance.value?.version.includes('+glitch'))
|
|
||||||
|
export function getInstanceDomain(instance: ElkInstance) {
|
||||||
|
return instance.accountDomain || instance.uri
|
||||||
|
}
|
||||||
|
|
||||||
export const publicServer = ref('')
|
export const publicServer = ref('')
|
||||||
export const currentServer = computed<string>(() => currentUser.value?.server || publicServer.value)
|
export const currentServer = computed<string>(() => currentUser.value?.server || publicServer.value)
|
||||||
|
|
||||||
|
export const currentNodeInfo = computed<null | Record<string, any>>(() => nodes.value[currentServer.value] || null)
|
||||||
|
export const isGotoSocial = computed(() => currentNodeInfo.value?.software?.name === 'gotosocial')
|
||||||
|
export const isGlitchEdition = computed(() => currentInstance.value?.version?.includes('+glitch'))
|
||||||
|
|
||||||
// when multiple tabs: we need to reload window when sign in, switch account or sign out
|
// when multiple tabs: we need to reload window when sign in, switch account or sign out
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
const windowReload = () => {
|
const windowReload = () => {
|
||||||
|
@ -89,102 +107,81 @@ if (process.client) {
|
||||||
window.addEventListener('visibilitychange', windowReload, { capture: true })
|
window.addEventListener('visibilitychange', windowReload, { capture: true })
|
||||||
}
|
}
|
||||||
}, { immediate: true, flush: 'post' })
|
}, { immediate: true, flush: 'post' })
|
||||||
}
|
|
||||||
|
|
||||||
export const currentUserHandle = computed(() => currentUser.value?.account.id
|
// for injected script to read
|
||||||
? `${currentUser.value.account.acct}@${currentInstance.value?.uri || currentServer.value}`
|
const currentUserHandle = computed(() => currentUser.value?.account.acct || '')
|
||||||
: '[anonymous]',
|
watchEffect(() => {
|
||||||
)
|
localStorage.setItem(STORAGE_KEY_CURRENT_USER_HANDLE, currentUserHandle.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const useUsers = () => users
|
export const useUsers = () => users
|
||||||
export const useSelfAccount = (user: MaybeComputedRef<mastodon.v1.Account | undefined>) =>
|
export const useSelfAccount = (user: MaybeComputedRef<mastodon.v1.Account | undefined>) =>
|
||||||
computed(() => currentUser.value && resolveUnref(user)?.id === currentUser.value.account.id)
|
computed(() => currentUser.value && resolveUnref(user)?.id === currentUser.value.account.id)
|
||||||
|
|
||||||
export const characterLimit = computed(() => currentInstance.value?.configuration.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)
|
export const characterLimit = computed(() => currentInstance.value?.configuration?.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)
|
||||||
|
|
||||||
async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: mastodon.v1.AccountCredentials }) {
|
export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { account?: mastodon.v1.AccountCredentials }>) {
|
||||||
const route = useRoute()
|
const { client } = $(masto)
|
||||||
const router = useRouter()
|
const instance = mastoLogin(masto, user)
|
||||||
const server = user?.server || route.params.server as string || publicServer.value
|
|
||||||
const url = `https://${server}`
|
// GoToSocial only API
|
||||||
const instance = await fetchV1Instance({
|
const url = `https://${user.server}`
|
||||||
url,
|
fetch(`${url}/nodeinfo/2.0`).then(r => r.json()).then((info) => {
|
||||||
})
|
nodes.value[user.server] = info
|
||||||
const masto = createClient({
|
}).catch(() => undefined)
|
||||||
url,
|
|
||||||
streamingApiUrl: instance.urls.streamingApi,
|
|
||||||
accessToken: user?.token,
|
|
||||||
disableVersionCheck: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!user?.token) {
|
if (!user?.token) {
|
||||||
publicServer.value = server
|
publicServer.value = user.server
|
||||||
publicInstance.value = instance
|
publicInstance.value = instance
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUser() {
|
||||||
|
return users.value.find(u => u.server === user.server && u.token === user.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = getUser()?.account
|
||||||
|
if (account)
|
||||||
|
currentUserId.value = account.id
|
||||||
|
|
||||||
|
const [me, pushSubscription] = await Promise.all([
|
||||||
|
fetchAccountInfo(client, user.server),
|
||||||
|
// if PWA is not enabled, don't get push subscription
|
||||||
|
useRuntimeConfig().public.pwaEnabled
|
||||||
|
// we get 404 response instead empty data
|
||||||
|
? client.v1.webPushSubscriptions.fetch().catch(() => Promise.resolve(undefined))
|
||||||
|
: Promise.resolve(undefined),
|
||||||
|
])
|
||||||
|
|
||||||
|
const existingUser = getUser()
|
||||||
|
if (existingUser) {
|
||||||
|
existingUser.account = me
|
||||||
|
existingUser.pushSubscription = pushSubscription
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
users.value.push({
|
||||||
const [me, pushSubscription] = await Promise.all([
|
...user,
|
||||||
masto.v1.accounts.verifyCredentials(),
|
account: me,
|
||||||
// if PWA is not enabled, don't get push subscription
|
pushSubscription,
|
||||||
useRuntimeConfig().public.pwaEnabled
|
|
||||||
// we get 404 response instead empty data
|
|
||||||
? masto.v1.webPushSubscriptions.fetch().catch(() => Promise.resolve(undefined))
|
|
||||||
: Promise.resolve(undefined),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!me.acct.includes('@'))
|
|
||||||
me.acct = `${me.acct}@${instance.uri}`
|
|
||||||
|
|
||||||
user.account = me
|
|
||||||
user.pushSubscription = pushSubscription
|
|
||||||
currentUserId.value = me.id
|
|
||||||
instances.value[server] = instance
|
|
||||||
|
|
||||||
if (!users.value.some(u => u.server === user.server && u.token === user.token))
|
|
||||||
users.value.push(user as UserLogin)
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
await signout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This only cleans up the URL; page content should stay the same
|
|
||||||
if (route.path === '/signin/callback') {
|
|
||||||
await router.push('/home')
|
|
||||||
}
|
|
||||||
|
|
||||||
else if ('server' in route.params && user?.token && !useNuxtApp()._processingMiddleware) {
|
|
||||||
await router.push({
|
|
||||||
...route,
|
|
||||||
force: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return masto
|
currentUserId.value = me.id
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAccountInfo(userId: string, account: mastodon.v1.AccountCredentials) {
|
export async function fetchAccountInfo(client: mastodon.Client, server: string) {
|
||||||
const index = getUsersIndexByUserId(userId)
|
const account = await client.v1.accounts.verifyCredentials()
|
||||||
if (index === -1)
|
|
||||||
return false
|
|
||||||
|
|
||||||
users.value[index].account = account
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function pullMyAccountInfo() {
|
|
||||||
const account = await useMasto().v1.accounts.verifyCredentials()
|
|
||||||
if (!account.acct.includes('@'))
|
if (!account.acct.includes('@'))
|
||||||
account.acct = `${account.acct}@${currentInstance.value!.uri}`
|
account.acct = `${account.acct}@${server}`
|
||||||
|
cacheAccount(account, server, true)
|
||||||
setAccountInfo(currentUserId.value, account)
|
return account
|
||||||
cacheAccount(account, currentServer.value, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsersIndexByUserId(userId: string) {
|
export async function refreshAccountInfo() {
|
||||||
return users.value.findIndex(u => u.account?.id === userId)
|
const account = await fetchAccountInfo(useMastoClient(), currentServer.value)
|
||||||
|
currentUser.value!.account = account
|
||||||
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
|
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
|
||||||
|
@ -221,7 +218,23 @@ export async function removePushNotifications(user: UserLogin) {
|
||||||
return
|
return
|
||||||
|
|
||||||
// unsubscribe push notifications
|
// unsubscribe push notifications
|
||||||
await useMasto().v1.webPushSubscriptions.remove().catch(() => Promise.resolve())
|
await useMastoClient().v1.webPushSubscriptions.remove().catch(() => Promise.resolve())
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function switchUser(user: UserLogin) {
|
||||||
|
const masto = useMasto()
|
||||||
|
|
||||||
|
await loginTo(masto, user)
|
||||||
|
|
||||||
|
// This only cleans up the URL; page content should stay the same
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
if ('server' in route.params && user?.token && !useNuxtApp()._processingMiddleware) {
|
||||||
|
await router.push({
|
||||||
|
...route,
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signout() {
|
export async function signout() {
|
||||||
|
@ -256,7 +269,7 @@ export async function signout() {
|
||||||
if (!currentUserId.value)
|
if (!currentUserId.value)
|
||||||
await useRouter().push('/')
|
await useRouter().push('/')
|
||||||
|
|
||||||
await masto.loginTo(currentUser.value)
|
loginTo(masto, currentUser.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkLogin() {
|
export function checkLogin() {
|
||||||
|
@ -267,18 +280,37 @@ export function checkLogin() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UseUserLocalStorageCache {
|
||||||
|
scope: EffectScope
|
||||||
|
value: Ref<Record<string, any>>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create reactive storage for the current user
|
* Create reactive storage for the current user
|
||||||
*/
|
*/
|
||||||
export function useUserLocalStorage<T extends object>(key: string, initial: () => T) {
|
export function useUserLocalStorage<T extends object>(key: string, initial: () => T): Ref<T> {
|
||||||
const all = useLocalStorage<Record<string, T>>(key, {}, { deep: true })
|
if (process.server || process.test)
|
||||||
return computed(() => {
|
return shallowRef(initial())
|
||||||
const id = currentUser.value?.account.id
|
|
||||||
? currentUser.value.account.acct
|
// @ts-expect-error bind value to the function
|
||||||
: '[anonymous]'
|
const map: Map<string, UseUserLocalStorageCache> = useUserLocalStorage._ = useUserLocalStorage._ || new Map()
|
||||||
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
|
||||||
return all.value[id]
|
if (!map.has(key)) {
|
||||||
})
|
const scope = effectScope(true)
|
||||||
|
const value = scope.run(() => {
|
||||||
|
const all = useLocalStorage<Record<string, T>>(key, {}, { deep: true })
|
||||||
|
return computed(() => {
|
||||||
|
const id = currentUser.value?.account.id
|
||||||
|
? currentUser.value.account.acct
|
||||||
|
: '[anonymous]'
|
||||||
|
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
||||||
|
return all.value[id]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
map.set(key, { scope, value: value! })
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.get(key)!.value as Ref<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -291,63 +323,11 @@ export function clearUserLocalStorage(account?: mastodon.v1.Account) {
|
||||||
return
|
return
|
||||||
|
|
||||||
const id = `${account.acct}@${currentInstance.value?.uri || currentServer.value}`
|
const id = `${account.acct}@${currentInstance.value?.uri || currentServer.value}`
|
||||||
|
|
||||||
// @ts-expect-error bind value to the function
|
// @ts-expect-error bind value to the function
|
||||||
;(useUserLocalStorage._ as Map<string, Ref<Record<string, any>>> | undefined)?.forEach((storage) => {
|
const cacheMap = useUserLocalStorage._ as Map<string, UseUserLocalStorageCache> | undefined
|
||||||
if (storage.value[id])
|
cacheMap?.forEach(({ value }) => {
|
||||||
delete storage.value[id]
|
if (value.value[id])
|
||||||
|
delete value.value[id]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createMasto = () => {
|
|
||||||
const api = shallowRef<mastodon.Client | null>(null)
|
|
||||||
const apiPromise = ref<Promise<mastodon.Client> | null>(null)
|
|
||||||
const initialised = computed(() => !!api.value)
|
|
||||||
|
|
||||||
const masto = new Proxy({} as ElkMasto, {
|
|
||||||
get(_, key: keyof ElkMasto) {
|
|
||||||
if (key === 'loggedIn')
|
|
||||||
return initialised
|
|
||||||
|
|
||||||
if (key === 'loginTo') {
|
|
||||||
return (...args: any[]): Promise<mastodon.Client> => {
|
|
||||||
return apiPromise.value = loginTo(...args).then((r) => {
|
|
||||||
api.value = r
|
|
||||||
return masto
|
|
||||||
}).catch(() => {
|
|
||||||
// Show error page when Mastodon server is down
|
|
||||||
throw createError({
|
|
||||||
fatal: true,
|
|
||||||
statusMessage: 'Could not log into account.',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.value && key in api.value)
|
|
||||||
return api.value[key as keyof mastodon.Client]
|
|
||||||
|
|
||||||
if (!api.value) {
|
|
||||||
return new Proxy({}, {
|
|
||||||
get(_, subkey) {
|
|
||||||
if (typeof subkey === 'string' && subkey.startsWith('iterate')) {
|
|
||||||
return (...args: any[]) => {
|
|
||||||
let paginator: any
|
|
||||||
function next() {
|
|
||||||
paginator = paginator || (api.value as any)?.[key][subkey](...args)
|
|
||||||
return paginator.next()
|
|
||||||
}
|
|
||||||
return { next }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (...args: any[]) => apiPromise.value?.then((r: any) => r[key][subkey](...args))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return masto
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,10 @@ import { useHead } from '#head'
|
||||||
|
|
||||||
export const isHydrated = ref(false)
|
export const isHydrated = ref(false)
|
||||||
|
|
||||||
|
export const onHydrated = (cb: () => unknown) => {
|
||||||
|
watchOnce(isHydrated, () => cb(), { immediate: isHydrated.value })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ### Whether the current component is running in the background
|
* ### Whether the current component is running in the background
|
||||||
*
|
*
|
||||||
|
|
24
composables/web-share-target.ts
Normal file
24
composables/web-share-target.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export function useWebShareTarget(listener?: (message: MessageEvent) => void) {
|
||||||
|
if (process.server)
|
||||||
|
return
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// PWA must be installed to use share target
|
||||||
|
if (useNuxtApp().$pwa.isInstalled && 'serviceWorker' in navigator) {
|
||||||
|
if (listener)
|
||||||
|
navigator.serviceWorker.addEventListener('message', listener)
|
||||||
|
|
||||||
|
navigator.serviceWorker.getRegistration()
|
||||||
|
.then((registration) => {
|
||||||
|
if (registration && registration.active) {
|
||||||
|
// we need to signal the service worker that we are ready to receive data
|
||||||
|
registration.active.postMessage({ action: 'ready-to-receive' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Could not get registration', err))
|
||||||
|
|
||||||
|
if (listener)
|
||||||
|
onBeforeUnmount(() => navigator.serviceWorker.removeEventListener('message', listener))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -20,6 +20,16 @@ const locales: LocaleObjectData[] = [
|
||||||
file: 'en-GB.json',
|
file: 'en-GB.json',
|
||||||
name: 'English (UK)',
|
name: 'English (UK)',
|
||||||
},
|
},
|
||||||
|
({
|
||||||
|
code: 'ar-EG',
|
||||||
|
file: 'ar-EG.json',
|
||||||
|
name: 'العربية',
|
||||||
|
dir: 'rtl',
|
||||||
|
pluralRule: (choice: number) => {
|
||||||
|
const name = new Intl.PluralRules('ar-EG').select(choice)
|
||||||
|
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
|
||||||
|
},
|
||||||
|
} satisfies LocaleObjectData),
|
||||||
{
|
{
|
||||||
code: 'de-DE',
|
code: 'de-DE',
|
||||||
file: 'de-DE.json',
|
file: 'de-DE.json',
|
||||||
|
@ -72,16 +82,16 @@ const locales: LocaleObjectData[] = [
|
||||||
file: 'cs-CZ.json',
|
file: 'cs-CZ.json',
|
||||||
name: 'Česky',
|
name: 'Česky',
|
||||||
},
|
},
|
||||||
({
|
{
|
||||||
code: 'ar-EG',
|
code: 'pt-PT',
|
||||||
file: 'ar-EG.json',
|
file: 'pt-PT.json',
|
||||||
name: 'العربية',
|
name: 'Português',
|
||||||
dir: 'rtl',
|
},
|
||||||
pluralRule: (choice: number) => {
|
{
|
||||||
const name = new Intl.PluralRules('ar-EG').select(choice)
|
code: 'tr-TR',
|
||||||
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
|
file: 'tr-TR.json',
|
||||||
},
|
name: 'Türkçe',
|
||||||
} satisfies LocaleObjectData),
|
},
|
||||||
].sort((a, b) => a.code.localeCompare(b.code))
|
].sort((a, b) => a.code.localeCompare(b.code))
|
||||||
|
|
||||||
const datetimeFormats = Object.values(locales).reduce((acc, data) => {
|
const datetimeFormats = Object.values(locales).reduce((acc, data) => {
|
||||||
|
|
|
@ -2,12 +2,13 @@ export const APP_NAME = 'Elk'
|
||||||
|
|
||||||
export const DEFAULT_POST_CHARS_LIMIT = 500
|
export const DEFAULT_POST_CHARS_LIMIT = 500
|
||||||
export const DEFAULT_FONT_SIZE = 'md'
|
export const DEFAULT_FONT_SIZE = 'md'
|
||||||
export const DEFAULT_LANGUAGE = 'en-US'
|
|
||||||
|
|
||||||
export const STORAGE_KEY_DRAFTS = 'elk-drafts'
|
export const STORAGE_KEY_DRAFTS = 'elk-drafts'
|
||||||
export const STORAGE_KEY_USERS = 'elk-users'
|
export const STORAGE_KEY_USERS = 'elk-users'
|
||||||
export const STORAGE_KEY_SERVERS = 'elk-servers'
|
export const STORAGE_KEY_SERVERS = 'elk-servers'
|
||||||
|
export const STORAGE_KEY_NODES = 'elk-nodes'
|
||||||
export const STORAGE_KEY_CURRENT_USER = 'elk-current-user'
|
export const STORAGE_KEY_CURRENT_USER = 'elk-current-user'
|
||||||
|
export const STORAGE_KEY_CURRENT_USER_HANDLE = 'elk-current-user-handle'
|
||||||
export const STORAGE_KEY_NOTIFY_TAB = 'elk-notify-tab'
|
export const STORAGE_KEY_NOTIFY_TAB = 'elk-notify-tab'
|
||||||
export const STORAGE_KEY_FIRST_VISIT = 'elk-first-visit'
|
export const STORAGE_KEY_FIRST_VISIT = 'elk-first-visit'
|
||||||
export const STORAGE_KEY_SETTINGS = 'elk-settings'
|
export const STORAGE_KEY_SETTINGS = 'elk-settings'
|
||||||
|
@ -20,7 +21,4 @@ export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy'
|
||||||
|
|
||||||
export const COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60 * 1000
|
export const COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
export const COOKIE_KEY_FONT_SIZE = 'elk-font-size'
|
|
||||||
export const COOKIE_KEY_LOCALE = 'elk-lang'
|
|
||||||
|
|
||||||
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
||||||
|
|
10
docs/app.vue
10
docs/app.vue
|
@ -3,13 +3,3 @@
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
font-family: 'DM Sans';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(/fonts/DM-sans-v11.ttf) format('truetype');
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { defineTheme, palette } from 'pinceau'
|
import { defineTheme, palette } from 'pinceau'
|
||||||
|
|
||||||
export default defineTheme({
|
export default defineTheme({
|
||||||
font: {
|
|
||||||
sans: 'DM Sans',
|
|
||||||
},
|
|
||||||
color: {
|
color: {
|
||||||
primary: palette('#d98018'),
|
primary: palette('#d98018'),
|
||||||
},
|
},
|
||||||
|
|
10
error.vue
10
error.vue
|
@ -11,17 +11,17 @@ const errorCodes: Record<number, string> = {
|
||||||
404: 'Page not found',
|
404: 'Page not found',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.dev)
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
const defaultMessage = 'Something went wrong'
|
const defaultMessage = 'Something went wrong'
|
||||||
|
|
||||||
const message = error.message ?? errorCodes[error.statusCode!] ?? defaultMessage
|
const message = error.message ?? errorCodes[error.statusCode!] ?? defaultMessage
|
||||||
|
|
||||||
const state = ref<'error' | 'reloading'>('error')
|
const state = ref<'error' | 'reloading'>('error')
|
||||||
const masto = useMasto()
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
state.value = 'reloading'
|
state.value = 'reloading'
|
||||||
try {
|
try {
|
||||||
if (!masto.loggedIn.value)
|
|
||||||
await masto.loginTo(currentUser.value)
|
|
||||||
clearError({ redirect: currentUser.value ? '/home' : `/${currentServer.value}/public/local` })
|
clearError({ redirect: currentUser.value ? '/home' : `/${currentServer.value}/public/local` })
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
@ -47,7 +47,9 @@ const reload = async () => {
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<button flex items-center gap-2 justify-center btn-solid text-center :disabled="state === 'reloading'">
|
<button flex items-center gap-2 justify-center btn-solid text-center :disabled="state === 'reloading'">
|
||||||
<span v-if="state === 'reloading'" i-ri:loader-2-fill animate-spin inline-block />
|
<span v-if="state === 'reloading'" block animate-spin preserve-3d>
|
||||||
|
<span block i-ri:loader-2-fill />
|
||||||
|
</span>
|
||||||
{{ state === 'reloading' ? 'Reloading' : 'Reload' }}
|
{{ state === 'reloading' ? 'Reloading' : 'Reload' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useFeatureFlag } from '~/composables/settings'
|
import { usePreferences } from '~/composables/settings'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
|
@ -7,13 +7,13 @@ const userSettings = useUserSettings()
|
||||||
const wideLayout = computed(() => route.meta.wideLayout ?? false)
|
const wideLayout = computed(() => route.meta.wideLayout ?? false)
|
||||||
|
|
||||||
const showUserPicker = logicAnd(
|
const showUserPicker = logicAnd(
|
||||||
useFeatureFlag('experimentalUserPicker'),
|
usePreferences('experimentalUserPicker'),
|
||||||
() => useUsers().value.length > 1,
|
() => useUsers().value.length > 1,
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div h-full :class="{ zen: userSettings.zenMode }">
|
<div h-full>
|
||||||
<main flex w-full mxa lg:max-w-80rem>
|
<main flex w-full mxa lg:max-w-80rem>
|
||||||
<aside class="hidden sm:flex w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 justify-end xl:me-4 zen-hide" relative>
|
<aside class="hidden sm:flex w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 justify-end xl:me-4 zen-hide" relative>
|
||||||
<div sticky top-0 w-20 xl:w-100 h-screen flex="~ col" lt-xl-items-center>
|
<div sticky top-0 w-20 xl:w-100 h-screen flex="~ col" lt-xl-items-center>
|
||||||
|
@ -22,7 +22,7 @@ const showUserPicker = logicAnd(
|
||||||
<NavTitle />
|
<NavTitle />
|
||||||
<NavSide command />
|
<NavSide command />
|
||||||
<div flex-auto />
|
<div flex-auto />
|
||||||
<div v-if="isMastoInitialised" flex flex-col>
|
<div v-if="isHydrated" flex flex-col>
|
||||||
<div hidden xl:block>
|
<div hidden xl:block>
|
||||||
<UserSignInEntry v-if="!currentUser" />
|
<UserSignInEntry v-if="!currentUser" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -240,12 +240,6 @@
|
||||||
"description": "قم بتحرير إعدادات حسابك في موقع Mastodon الأصلي",
|
"description": "قم بتحرير إعدادات حسابك في موقع Mastodon الأصلي",
|
||||||
"label": "إعدادت الحساب"
|
"label": "إعدادت الحساب"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
|
||||||
"github_cards": "GitHub بطاقات",
|
|
||||||
"title": "الميزات التجريبية",
|
|
||||||
"user_picker": "الشريط الجانبي لمبدل المستخدم",
|
|
||||||
"virtual_scroll": "التمرير الافتراضي"
|
|
||||||
},
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "وضع اللون",
|
"color_mode": "وضع اللون",
|
||||||
"dark_mode": "الوضع الداكن",
|
"dark_mode": "الوضع الداكن",
|
||||||
|
@ -315,7 +309,14 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "التنبيهات",
|
"notifications_settings": "التنبيهات",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "التفضيلات"
|
"github_cards": "GitHub بطاقات",
|
||||||
|
"hide_boost_count": "إخفاء عدد المشاركات",
|
||||||
|
"hide_favorite_count": "إخفاء عدد المفضلة",
|
||||||
|
"hide_follower_count": "إخفاء عدد المتابعين",
|
||||||
|
"label": "التفضيلات",
|
||||||
|
"title": "الميزات التجريبية",
|
||||||
|
"user_picker": "الشريط الجانبي لمبدل المستخدم",
|
||||||
|
"virtual_scroll": "التمرير الافتراضي"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -338,14 +339,6 @@
|
||||||
"export": "Export User Tokens",
|
"export": "Export User Tokens",
|
||||||
"import": "Import User Tokens",
|
"import": "Import User Tokens",
|
||||||
"label": "المستخدمون المسجلون"
|
"label": "المستخدمون المسجلون"
|
||||||
},
|
|
||||||
"wellness": {
|
|
||||||
"feature": {
|
|
||||||
"hide_boost_count": "إخفاء عدد المشاركات",
|
|
||||||
"hide_favorite_count": "إخفاء عدد المفضلة",
|
|
||||||
"hide_follower_count": "إخفاء عدد المتابعين"
|
|
||||||
},
|
|
||||||
"label": "صحة النفس"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
{
|
{
|
||||||
|
"a11y": {
|
||||||
|
"loading_page": "Seite wird geladen, bitte warten",
|
||||||
|
"loading_titled_page": "Seite {0} wird geladen, bitte warten",
|
||||||
|
"locale_changed": "Sprache wurde zu {0} geändert",
|
||||||
|
"locale_changing": "Sprache wird geändert, bitte warten",
|
||||||
|
"route_loaded": "Seite {0} geladen"
|
||||||
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"avatar_description": "{0}'s Avatar",
|
"avatar_description": "{0}'s Avatar",
|
||||||
"blocked_by": "Du wurdest von diesem Account geblockt",
|
"blocked_by": "Du wurdest von diesem Account geblockt",
|
||||||
"blocked_domains": "Gesperrte Domänen",
|
"blocked_domains": "Gesperrte Domänen",
|
||||||
"blocked_users": "Gesperrte Accounts",
|
"blocked_users": "Gesperrte Accounts",
|
||||||
|
"blocking": "Blockiert",
|
||||||
"bot": "BOT",
|
"bot": "BOT",
|
||||||
"favourites": "Favoriten",
|
"favourites": "Favoriten",
|
||||||
"follow": "Folgen",
|
"follow": "Folgen",
|
||||||
|
@ -18,29 +26,44 @@
|
||||||
"joined": "Beigetreten",
|
"joined": "Beigetreten",
|
||||||
"moved_title": "hat angegeben, dass dies der neue Account ist:",
|
"moved_title": "hat angegeben, dass dies der neue Account ist:",
|
||||||
"muted_users": "Stummgeschaltete Accounts",
|
"muted_users": "Stummgeschaltete Accounts",
|
||||||
|
"muting": "Stummgeschaltet",
|
||||||
"mutuals": "Freunde",
|
"mutuals": "Freunde",
|
||||||
|
"notify_on_post": "Benachrichtige mich, wenn {username} etwas postet",
|
||||||
"pinned": "Angepinnt",
|
"pinned": "Angepinnt",
|
||||||
"posts": "Beiträge",
|
"posts": "Beiträge",
|
||||||
"posts_count": "{0} Beiträge",
|
"posts_count": "{0} Beiträge",
|
||||||
"profile_description": "{0}'s Profil",
|
"profile_description": "{0}'s Profil",
|
||||||
"profile_unavailable": "Profil nicht verfügbar",
|
"profile_unavailable": "Profil nicht verfügbar",
|
||||||
"unfollow": "Entfolgen"
|
"unblock": "Entblocken",
|
||||||
|
"unfollow": "Entfolgen",
|
||||||
|
"unmute": "Stummschaltung aufheben",
|
||||||
|
"view_other_followers": "Follower aus anderen Instanzen werden möglicherweise nicht angezeigt.",
|
||||||
|
"view_other_following": "Das Folgen von anderen Instanzen wird möglicherweise nicht angezeigt."
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
|
"apply": "Anwenden",
|
||||||
"bookmark": "Lesezeichen",
|
"bookmark": "Lesezeichen",
|
||||||
"bookmarked": "Gemerkt",
|
"bookmarked": "Gemerkt",
|
||||||
"boost": "Teilen",
|
"boost": "Teilen",
|
||||||
|
"boost_count": "{0}",
|
||||||
"boosted": "Geteilt",
|
"boosted": "Geteilt",
|
||||||
|
"clear_upload_failed": "Fehler beim Hochladen von Dateien entfernen",
|
||||||
"close": "Schliessen",
|
"close": "Schliessen",
|
||||||
"compose": "Verfassen",
|
"compose": "Verfassen",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
"enter_app": "App öffnen",
|
"enter_app": "App öffnen",
|
||||||
"favourite": "Favorisieren",
|
"favourite": "Favorisieren",
|
||||||
|
"favourite_count": "{0}",
|
||||||
"favourited": "Favorisiert",
|
"favourited": "Favorisiert",
|
||||||
"more": "Mehr",
|
"more": "Mehr",
|
||||||
"next": "Nächster",
|
"next": "Nächster",
|
||||||
"prev": "Vorheriger",
|
"prev": "Vorheriger",
|
||||||
"publish": "Veröffentlichen",
|
"publish": "Veröffentlichen",
|
||||||
"reply": "Antworten",
|
"reply": "Antworten",
|
||||||
|
"reply_count": "{0}",
|
||||||
|
"reset": "Zurücksetzen",
|
||||||
|
"save": "Speichern",
|
||||||
"save_changes": "Änderungen speichern",
|
"save_changes": "Änderungen speichern",
|
||||||
"sign_in": "Anmelden",
|
"sign_in": "Anmelden",
|
||||||
"switch_account": "Account wechseln",
|
"switch_account": "Account wechseln",
|
||||||
|
@ -49,6 +72,10 @@
|
||||||
"app_desc_short": "Ein flinker Mastodon Web-Client",
|
"app_desc_short": "Ein flinker Mastodon Web-Client",
|
||||||
"app_logo": "Elk Logo",
|
"app_logo": "Elk Logo",
|
||||||
"app_name": "Elk",
|
"app_name": "Elk",
|
||||||
|
"attachment": {
|
||||||
|
"edit_title": "Beschreibung",
|
||||||
|
"remove_label": "Anhang entfernen"
|
||||||
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"activate": "Aktivieren",
|
"activate": "Aktivieren",
|
||||||
"complete": "Vollständig",
|
"complete": "Vollständig",
|
||||||
|
@ -62,25 +89,40 @@
|
||||||
"toggle_zen_mode": "Zen-Modus ändern"
|
"toggle_zen_mode": "Zen-Modus ändern"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
"confirm_dialog": {
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "OK",
|
||||||
|
"title": "Bist du sicher, {0}?"
|
||||||
|
},
|
||||||
"end_of_list": "Ende der Liste",
|
"end_of_list": "Ende der Liste",
|
||||||
"error": "FEHLER",
|
"error": "FEHLER",
|
||||||
|
"in": "in",
|
||||||
"not_found": "404 Nicht Gefunden",
|
"not_found": "404 Nicht Gefunden",
|
||||||
"offline_desc": "Anscheinend bist du offline. Bitte überprüfe deine Netzwerkverbindung."
|
"offline_desc": "Anscheinend bist du offline. Bitte überprüfe deine Netzwerkverbindung."
|
||||||
},
|
},
|
||||||
|
"compose": {
|
||||||
|
"draft_title": "Entwurf {0}",
|
||||||
|
"drafts": "Entwürfe ({v})"
|
||||||
|
},
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"with": "mit"
|
"with": "mit"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"account_not_found": "Account {0} nicht gefunden",
|
"account_not_found": "Account {0} nicht gefunden",
|
||||||
"explore-list-empty": "Momentan ist nichts im Trend. Schau später nochmal vorbei!",
|
"explore-list-empty": "Momentan ist nichts im Trend. Schau später nochmal vorbei!",
|
||||||
|
"file_size_cannot_exceed_n_mb": "Die Dateigröße darf {0} MB nicht überschreiten",
|
||||||
"sign_in_error": "Kann nicht zu Server verbinden",
|
"sign_in_error": "Kann nicht zu Server verbinden",
|
||||||
"status_not_found": "Beitrag nicht gefunden"
|
"status_not_found": "Beitrag nicht gefunden",
|
||||||
|
"unsupported_file_format": "Nicht unterstütztes Dateiformat"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"desc_highlight": "Erwarte hier und da einige Bugs und fehlende Funktionen.",
|
"desc_highlight": "Erwarte hier und da einige Bugs und fehlende Funktionen.",
|
||||||
"desc_para1": "Danke für dein Interesse in Elk, unser noch in der Bearbeitung befindliche, generische Mastodon Client!",
|
"desc_para1": "Danke für dein Interesse in Elk, unser noch in der Bearbeitung befindliche, generische Mastodon Client!",
|
||||||
"desc_para2": "Wir arbeiten hart an der Entwicklung und verbessern ihn mit der Zeit. Und wir laden dich schon sehr bald ein uns zu helfen, sobald wir ihn Quelloffen machen!",
|
"desc_para2": "Wir arbeiten hart an der Entwicklung und verbessern ihn mit der Zeit. Und wir laden dich schon sehr bald ein uns zu helfen, sobald wir ihn Quelloffen machen!",
|
||||||
"desc_para3": "Doch in der Zwischenzeit kannst du der Entwicklung aushelfen, indem du unsere Teammitglieder durch die unten stehenden Links unterstützt.",
|
"desc_para3": "Doch in der Zwischenzeit kannst du der Entwicklung aushelfen, indem du unsere Teammitglieder durch die unten stehenden Links unterstützt.",
|
||||||
|
"desc_para4": "Elk ist Open Source. \nWenn du beim Testen helfen, Feedback geben oder einen Beitrag leisten möchtest,",
|
||||||
|
"desc_para5": "Kontaktiere uns auf GitHub",
|
||||||
|
"desc_para6": "und mache mit.",
|
||||||
"title": "Elk ist in der Alpha!"
|
"title": "Elk ist in der Alpha!"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
|
@ -92,13 +134,22 @@
|
||||||
"copy_link_to_post": "Link zu diesem Beitrag kopieren",
|
"copy_link_to_post": "Link zu diesem Beitrag kopieren",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"delete_and_redraft": "Löschen und neu erstellen",
|
"delete_and_redraft": "Löschen und neu erstellen",
|
||||||
|
"delete_confirm": {
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "Löschen",
|
||||||
|
"title": "Möchtest du diesen Beitrag wirklich löschen?"
|
||||||
|
},
|
||||||
"direct_message_account": "Direktnachricht an {0}",
|
"direct_message_account": "Direktnachricht an {0}",
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
|
"hide_reblogs": "Boosts von {0} ausblenden",
|
||||||
"mention_account": "Erwähne {0}",
|
"mention_account": "Erwähne {0}",
|
||||||
"mute_account": "{0} stummschalten",
|
"mute_account": "{0} stummschalten",
|
||||||
"mute_conversation": "Diesem Beitrag stummschalten",
|
"mute_conversation": "Diesem Beitrag stummschalten",
|
||||||
"open_in_original_site": "Auf Originalseite öffnen",
|
"open_in_original_site": "Auf Originalseite öffnen",
|
||||||
"pin_on_profile": "An Profil anpinnen",
|
"pin_on_profile": "An Profil anpinnen",
|
||||||
|
"share_post": "Teile diesen Beitrag",
|
||||||
|
"show_favourited_and_boosted_by": "Zeige mir, wer favorisiert und geboostet hat",
|
||||||
|
"show_reblogs": "Boosts von {0} anzeigen",
|
||||||
"show_untranslated": "Übersetzung schliessen",
|
"show_untranslated": "Übersetzung schliessen",
|
||||||
"toggle_theme": {
|
"toggle_theme": {
|
||||||
"dark": "Dunkles Farbschema aktivieren",
|
"dark": "Dunkles Farbschema aktivieren",
|
||||||
|
@ -112,18 +163,24 @@
|
||||||
"unpin_on_profile": "Von Profil lösen"
|
"unpin_on_profile": "Von Profil lösen"
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
|
"back": "Zurück",
|
||||||
|
"blocked_domains": "Gesperrte Domänen",
|
||||||
|
"blocked_users": "Blockierte Benutzer",
|
||||||
"bookmarks": "Lesezeichen",
|
"bookmarks": "Lesezeichen",
|
||||||
"built_at": "Letzter Build: {0}",
|
"built_at": "Letzter Build: {0}",
|
||||||
|
"compose": "Verfassen",
|
||||||
"conversations": "Direktnachrichten",
|
"conversations": "Direktnachrichten",
|
||||||
"explore": "Entdecken",
|
"explore": "Entdecken",
|
||||||
"favourites": "Favoriten",
|
"favourites": "Favoriten",
|
||||||
"federated": "Föderiert",
|
"federated": "Föderiert",
|
||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"local": "Lokal",
|
"local": "Lokal",
|
||||||
|
"muted_users": "Stummgeschaltete Benutzer",
|
||||||
"notifications": "Mitteilungen",
|
"notifications": "Mitteilungen",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"search": "Suche",
|
"search": "Suche",
|
||||||
"select_feature_flags": "Feature-Flags aktivieren",
|
"select_feature_flags": "Feature-Flags aktivieren",
|
||||||
|
"select_font_size": "Schriftgröße",
|
||||||
"select_language": "Sprache auswählen",
|
"select_language": "Sprache auswählen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"show_intro": "Intro anzeigen",
|
"show_intro": "Intro anzeigen",
|
||||||
|
@ -144,7 +201,36 @@
|
||||||
"content_warning": "Schreib hier deine Warnung",
|
"content_warning": "Schreib hier deine Warnung",
|
||||||
"default_1": "Was geht dir gerade durch den Kopf?",
|
"default_1": "Was geht dir gerade durch den Kopf?",
|
||||||
"reply_to_account": "Antwort an {0}",
|
"reply_to_account": "Antwort an {0}",
|
||||||
"replying": "Antworten"
|
"replying": "Antworten",
|
||||||
|
"the_thread": "der Thread"
|
||||||
|
},
|
||||||
|
"pwa": {
|
||||||
|
"dismiss": "Ignorieren",
|
||||||
|
"title": "Neues Elk-Update verfügbar!",
|
||||||
|
"update": "Aktualisieren",
|
||||||
|
"update_available_short": "Elk aktualisieren",
|
||||||
|
"webmanifest": {
|
||||||
|
"canary": {
|
||||||
|
"description": "Ein flinker Mastodon-Webclient (Canary)",
|
||||||
|
"name": "Elk (canary)",
|
||||||
|
"short_name": "Elk (canary)"
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"description": "Ein flinker Mastodon-Webclient (dev)",
|
||||||
|
"name": "Elk (dev)",
|
||||||
|
"short_name": "Elk (dev)"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"description": "Ein flinker Mastodon-Webclient (Vorschau)",
|
||||||
|
"name": "Elk (Vorschau)",
|
||||||
|
"short_name": "Elk (Vorschau)"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"description": "Ein flinker Mastodon-Webclient",
|
||||||
|
"name": "Elk",
|
||||||
|
"short_name": "Elk"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search_desc": "Suche nach Accounts & Hashtags",
|
"search_desc": "Suche nach Accounts & Hashtags",
|
||||||
|
@ -152,10 +238,21 @@
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"about": {
|
"about": {
|
||||||
"label": "Info"
|
"label": "Info",
|
||||||
|
"meet_the_team": "Triff das Team",
|
||||||
|
"sponsor_action": "Sponser uns",
|
||||||
|
"sponsor_action_desc": "Um das Team bei der Entwicklung von Elk zu unterstützen",
|
||||||
|
"sponsors": "Sponsoren",
|
||||||
|
"sponsors_body_1": "Elk wird ermöglicht durch das großzügige Sponsoring und die Hilfe von:",
|
||||||
|
"sponsors_body_2": "Und alle Unternehmen und Einzelpersonen, die das Elk Team und die Mitglieder sponsern.",
|
||||||
|
"sponsors_body_3": "Wenn dir die App gefällt, erwäge uns zu sponsern:"
|
||||||
|
},
|
||||||
|
"account_settings": {
|
||||||
|
"description": "Bearbeite Kontoeinstellungen in der Mastodon-Benutzeroberfläche",
|
||||||
|
"label": "Account Einstellungen"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
"feature_flags": {
|
||||||
"github_cards": "GitHub-Karten",
|
"github_cards": "GitHub Cards",
|
||||||
"title": "Experimentelle Funktionen",
|
"title": "Experimentelle Funktionen",
|
||||||
"user_picker": "Benutzerauswahl",
|
"user_picker": "Benutzerauswahl",
|
||||||
"virtual_scroll": "Virtuelles Scrollen"
|
"virtual_scroll": "Virtuelles Scrollen"
|
||||||
|
@ -166,14 +263,79 @@
|
||||||
"default": " (Standard)",
|
"default": " (Standard)",
|
||||||
"font_size": "Schriftgröße",
|
"font_size": "Schriftgröße",
|
||||||
"label": "Oberfläche",
|
"label": "Oberfläche",
|
||||||
"light_mode": "Helles Farbschema"
|
"light_mode": "Helles Farbschema",
|
||||||
|
"size_label": {
|
||||||
|
"lg": "Groß",
|
||||||
|
"md": "Mittel",
|
||||||
|
"sm": "Klein",
|
||||||
|
"xl": "Extra groß",
|
||||||
|
"xs": "Extra klein"
|
||||||
|
},
|
||||||
|
"system_mode": "System"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "Anzeigesprache",
|
"display_language": "Anzeigesprache",
|
||||||
"label": "Sprache"
|
"label": "Sprache"
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"label": "Benachrichtigungen",
|
||||||
|
"notifications": {
|
||||||
|
"label": "Benachrichtigungseinstellungen"
|
||||||
|
},
|
||||||
|
"push_notifications": {
|
||||||
|
"alerts": {
|
||||||
|
"favourite": "Favoriten",
|
||||||
|
"follow": "Neue Follower",
|
||||||
|
"mention": "Erwähnungen",
|
||||||
|
"poll": "Umfragen",
|
||||||
|
"reblog": "Reblogge deinen Beitrag",
|
||||||
|
"title": "Welche Benachrichtigungen erhalten?"
|
||||||
|
},
|
||||||
|
"description": "Erhalte Benachrichtigungen, auch wenn du Elk nicht verwendest.",
|
||||||
|
"instructions": "Vergesse nicht, Änderungen mit der Schaltfläche @:settings.notifications.push_notifications.save_settings zu speichern!",
|
||||||
|
"label": "Einstellungen für Push-Benachrichtigungen",
|
||||||
|
"policy": {
|
||||||
|
"all": "Von irgendjemandem",
|
||||||
|
"followed": "Von Accounts, denen ich folge",
|
||||||
|
"follower": "Von Menschen, die mir folgen",
|
||||||
|
"none": "Von niemandem",
|
||||||
|
"title": "Von wem kann ich Benachrichtigungen erhalten?"
|
||||||
|
},
|
||||||
|
"save_settings": "Einstellungen speichern",
|
||||||
|
"subscription_error": {
|
||||||
|
"clear_error": "Fehler aufräumen",
|
||||||
|
"permission_denied": "Berechtigung verweigert: Aktiviere Benachrichtigungen im Browser.",
|
||||||
|
"request_error": "Beim Anfordern des Abonnements ist ein Fehler aufgetreten. Versuche es erneut. Wenn der Fehler weiterhin besteht, melde das Problem bitte dem Elk-Repository.",
|
||||||
|
"title": "Push-Benachrichtigungen konnten nicht abonniert werden",
|
||||||
|
"too_many_registrations": "Aufgrund von Browserbeschränkungen kann Elk den Push-Benachrichtigungsdienst nicht für mehrere Konten auf verschiedenen Servern verwenden. \nDu solltest Push-Benachrichtigungen für andere Konten abbestellen und es erneut versuchen."
|
||||||
|
},
|
||||||
|
"title": "Einstellungen für Push-Benachrichtigungen",
|
||||||
|
"undo_settings": "Änderungen rückgängig machen",
|
||||||
|
"unsubscribe": "Push-Benachrichtigungen deaktivieren",
|
||||||
|
"unsupported": "Ihr Browser unterstützt keine Push-Benachrichtigungen.",
|
||||||
|
"warning": {
|
||||||
|
"enable_close": "Schließen",
|
||||||
|
"enable_description": "Um Benachrichtigungen zu erhalten, wenn Elk nicht geöffnet ist, aktiviere Push-Benachrichtigungen. \nDu kannst genau steuern, welche Arten von Interaktionen Push-Benachrichtigungen generieren, indem du die Schaltfläche \"@:settings.notifications.show_btn{'\"'} oben aktivierest, sobald sie aktiviert ist.",
|
||||||
|
"enable_description_desktop": "Um Benachrichtigungen zu erhalten, wenn Elk nicht geöffnet ist, aktiviere Push-Benachrichtigungen. \nDu kannst unter „Einstellungen > Benachrichtigungen > Einstellungen für Push-Benachrichtigungen“ genau steuern, welche Arten von Interaktionen Push-Benachrichtigungen generieren, sobald diese aktiviert sind.",
|
||||||
|
"enable_description_mobile": "Du erreichst die Einstellungen auch über das Navigationsmenü „Einstellungen > Benachrichtigungen > Push-Benachrichtigungseinstellungen“.",
|
||||||
|
"enable_description_settings": "Um Benachrichtigungen zu erhalten, wenn Elk nicht geöffnet ist, aktiviere Push-Benachrichtigungen. \nDu kannst genau steuern, welche Arten von Interaktionen Push-Benachrichtigungen auf demselben Bildschirm generieren, sobald du sie aktivierst.",
|
||||||
|
"enable_desktop": "Aktiviere Push-Benachrichtigungen",
|
||||||
|
"enable_title": "Verpasse nie wieder Benachrichtigungen",
|
||||||
|
"re_auth": "Offenbar unterstützt dein Server keine Push-Benachrichtigungen. \nVersuche dich abzumelden und erneut anzumelden. Wenn diese Meldung weiterhin angezeigt wird, wende dich an dein Serveradministrator."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"show_btn": "Gehe zu den Benachrichtigungseinstellungen"
|
||||||
|
},
|
||||||
|
"notifications_settings": "Benachrichtigungen",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Einstellungen"
|
"github_cards": "GitHub Cards",
|
||||||
|
"hide_boost_count": "Boost-Zähler ausblenden",
|
||||||
|
"hide_favorite_count": "Favoritenzahl ausblenden",
|
||||||
|
"hide_follower_count": "Anzahl der Follower ausblenden",
|
||||||
|
"label": "Einstellungen",
|
||||||
|
"title": "Experimentelle Funktionen",
|
||||||
|
"user_picker": "Benutzerauswahl",
|
||||||
|
"virtual_scroll": "Virtuelles Scrollen"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -198,14 +360,25 @@
|
||||||
"label": "Eingeloggte Benutzer"
|
"label": "Eingeloggte Benutzer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share-target": {
|
||||||
|
"description": "Elk kann so konfiguriert werden, dass du Inhalte aus anderen Anwendungen teilen kannst, installiere einfach Elk auf deinem Gerät oder Computer und melden dich an.",
|
||||||
|
"hint": "Um Inhalte mit Elk zu teilen, muss Elk installiert sein und du musst angemeldet sein.",
|
||||||
|
"title": "Teile via Elk"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
|
"attachments_exceed_server_limit": "Die Anzahl der Anhänge hat das Limit pro Beitrag überschritten.",
|
||||||
|
"attachments_limit_error": "Limit pro Beitrag überschritten",
|
||||||
"edited": "(bearbeitet)",
|
"edited": "(bearbeitet)",
|
||||||
"editing": "Bearbeiten",
|
"editing": "Bearbeiten",
|
||||||
"loading": "Laden...",
|
"loading": "Laden...",
|
||||||
|
"publishing": "Veröffentlichung",
|
||||||
|
"upload_failed": "Upload fehlgeschlagen",
|
||||||
"uploading": "Hochladen..."
|
"uploading": "Hochladen..."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"boosted_by": "Boosted von",
|
||||||
"edited": "Zuletzt bearbeitet: {0}",
|
"edited": "Zuletzt bearbeitet: {0}",
|
||||||
|
"favourited_by": "Favorisiert von",
|
||||||
"filter_hidden_phrase": "Versteckt durch",
|
"filter_hidden_phrase": "Versteckt durch",
|
||||||
"filter_removed_phrase": "Entfernt durch Filter",
|
"filter_removed_phrase": "Entfernt durch Filter",
|
||||||
"filter_show_anyway": "Trotzdem zeigen",
|
"filter_show_anyway": "Trotzdem zeigen",
|
||||||
|
@ -219,9 +392,12 @@
|
||||||
"finished": "Beendet: {0}"
|
"finished": "Beendet: {0}"
|
||||||
},
|
},
|
||||||
"reblogged": "{0} teilte",
|
"reblogged": "{0} teilte",
|
||||||
|
"replying_to": "Antworten auf {0}",
|
||||||
|
"show_full_thread": "Vollständigen Thread anzeigen",
|
||||||
"someone": "Jemand",
|
"someone": "Jemand",
|
||||||
"spoiler_show_less": "Zeige weniger",
|
"spoiler_show_less": "Zeige weniger",
|
||||||
"spoiler_show_more": "Zeige mehr",
|
"spoiler_show_more": "Zeige mehr",
|
||||||
|
"thread": "Thread",
|
||||||
"try_original_site": "Versuche die original Seite"
|
"try_original_site": "Versuche die original Seite"
|
||||||
},
|
},
|
||||||
"status_history": {
|
"status_history": {
|
||||||
|
@ -276,7 +452,8 @@
|
||||||
"year_past": "vor 0 Jahren|letztes Jahren|vor {n} Jahren"
|
"year_past": "vor 0 Jahren|letztes Jahren|vor {n} Jahren"
|
||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
"show_new_items": "Zeige {v} neue Beiträge|Zeige {v} neuen Beitrag|Zeige {v} neue Beiträge"
|
"show_new_items": "Zeige {v} neue Beiträge|Zeige {v} neuen Beitrag|Zeige {v} neue Beiträge",
|
||||||
|
"view_older_posts": "Ältere Beiträge aus anderen Instanzen werden möglicherweise nicht angezeigt."
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"federated_timeline": "Föderierte Timeline",
|
"federated_timeline": "Föderierte Timeline",
|
||||||
|
@ -284,9 +461,12 @@
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"add_content_warning": "Inhaltswarnung hinzufügen",
|
"add_content_warning": "Inhaltswarnung hinzufügen",
|
||||||
|
"add_emojis": "Emojis hinzufügen",
|
||||||
"add_media": "Bilder, ein Video oder eine Audiodatei hinzufügen",
|
"add_media": "Bilder, ein Video oder eine Audiodatei hinzufügen",
|
||||||
|
"add_publishable_content": "Füge Inhalte zum Veröffentlichen hinzu",
|
||||||
"change_content_visibility": "Sichtbarkeit von Inhalten ändern",
|
"change_content_visibility": "Sichtbarkeit von Inhalten ändern",
|
||||||
"change_language": "Sprache ändern",
|
"change_language": "Sprache ändern",
|
||||||
|
"emoji": "Emoji",
|
||||||
"explore_links_intro": "Diese Nachrichten werden gerade von Leuten auf diesem und anderen Servern des dezentralen Netzwerks besprochen.",
|
"explore_links_intro": "Diese Nachrichten werden gerade von Leuten auf diesem und anderen Servern des dezentralen Netzwerks besprochen.",
|
||||||
"explore_posts_intro": "Diese Beiträge von diesem Server gewinnen gerade unter den Leuten von diesem und anderen Servern des dezentralen Netzweks an Reichweite.",
|
"explore_posts_intro": "Diese Beiträge von diesem Server gewinnen gerade unter den Leuten von diesem und anderen Servern des dezentralen Netzweks an Reichweite.",
|
||||||
"explore_tags_intro": "Diese Hashtags gewinnen gerade unter den Leuten von diesem und anderen Servern des dezentralen Netzweks an Reichweite.",
|
"explore_tags_intro": "Diese Hashtags gewinnen gerade unter den Leuten von diesem und anderen Servern des dezentralen Netzweks an Reichweite.",
|
||||||
|
@ -296,6 +476,7 @@
|
||||||
"add_existing": "Bestehendes Konto hinzufügen",
|
"add_existing": "Bestehendes Konto hinzufügen",
|
||||||
"server_address_label": "Mastodon Server Adresse",
|
"server_address_label": "Mastodon Server Adresse",
|
||||||
"sign_in_desc": "Melde dich an, um Profilen oder Hashtags zu folgen, Beiträge zu favorisieren, zu teilen und zu beantworten oder von deinem Konto auf einem anderen Server aus zu interagieren.",
|
"sign_in_desc": "Melde dich an, um Profilen oder Hashtags zu folgen, Beiträge zu favorisieren, zu teilen und zu beantworten oder von deinem Konto auf einem anderen Server aus zu interagieren.",
|
||||||
|
"sign_in_notice_title": "Anzeigen von {0} öffentlichen Daten",
|
||||||
"sign_out_account": "{0} abmelden",
|
"sign_out_account": "{0} abmelden",
|
||||||
"tip_no_account": "Wenn du noch kein Mastodon-Konto hast, {0}.",
|
"tip_no_account": "Wenn du noch kein Mastodon-Konto hast, {0}.",
|
||||||
"tip_register_account": "wähle einen Server aus und registriere eines"
|
"tip_register_account": "wähle einen Server aus und registriere eines"
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
"desc_highlight": "Expect some bugs and missing features here and there.",
|
"desc_highlight": "Expect some bugs and missing features here and there.",
|
||||||
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
||||||
"desc_para2": "we are working hard on the development and improving it over time.",
|
"desc_para2": "we are working hard on the development and improving it over time.",
|
||||||
"desc_para3": "To help boosting out development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
"desc_para3": "To boost development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
||||||
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
||||||
"desc_para5": "reach out to us on GitHub",
|
"desc_para5": "reach out to us on GitHub",
|
||||||
"desc_para6": "and get involved.",
|
"desc_para6": "and get involved.",
|
||||||
|
@ -198,11 +198,11 @@
|
||||||
},
|
},
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "Color Mode",
|
"color_mode": "Color Mode",
|
||||||
"dark_mode": "Dark Mode",
|
"dark_mode": "Dark",
|
||||||
"default": " (default)",
|
"default": " (default)",
|
||||||
"font_size": "Font Size",
|
"font_size": "Font Size",
|
||||||
"label": "Interface",
|
"label": "Interface",
|
||||||
"light_mode": "Light Mode",
|
"light_mode": "Light",
|
||||||
"system_mode": "System"
|
"system_mode": "System"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
|
@ -282,6 +282,11 @@
|
||||||
"label": "Logged in users"
|
"label": "Logged in users"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share-target": {
|
||||||
|
"description": "Elk can be configured so that you can share content from other applications, simply install Elk on your device or computer and sign in.",
|
||||||
|
"hint": "In order to share content with Elk, Elk must be installed and you must be signed in.",
|
||||||
|
"title": "Share with Elk"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
|
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
|
||||||
"attachments_limit_error": "Limit per post exceeded",
|
"attachments_limit_error": "Limit per post exceeded",
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
"muted_users": "Muted users",
|
"muted_users": "Muted users",
|
||||||
"muting": "Muted",
|
"muting": "Muted",
|
||||||
"mutuals": "Mutuals",
|
"mutuals": "Mutuals",
|
||||||
|
"notify_on_post": "Notify me when {username} posts",
|
||||||
"pinned": "Pinned",
|
"pinned": "Pinned",
|
||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
"posts_count": "{0} Posts|{0} Post|{0} Posts",
|
"posts_count": "{0} Posts|{0} Post|{0} Posts",
|
||||||
|
@ -35,7 +36,9 @@
|
||||||
"profile_unavailable": "Profile unavailable",
|
"profile_unavailable": "Profile unavailable",
|
||||||
"unblock": "Unblock",
|
"unblock": "Unblock",
|
||||||
"unfollow": "Unfollow",
|
"unfollow": "Unfollow",
|
||||||
"unmute": "Unmute"
|
"unmute": "Unmute",
|
||||||
|
"view_other_followers": "Followers from other instances may not be displayed.",
|
||||||
|
"view_other_following": "Following from other instances may not be displayed."
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
|
@ -89,7 +92,7 @@
|
||||||
"confirm_dialog": {
|
"confirm_dialog": {
|
||||||
"cancel": "No",
|
"cancel": "No",
|
||||||
"confirm": "Yes",
|
"confirm": "Yes",
|
||||||
"title": "Are you sure?"
|
"title": "Are you sure {0}?"
|
||||||
},
|
},
|
||||||
"end_of_list": "End of the list",
|
"end_of_list": "End of the list",
|
||||||
"error": "ERROR",
|
"error": "ERROR",
|
||||||
|
@ -116,7 +119,7 @@
|
||||||
"desc_highlight": "Expect some bugs and missing features here and there.",
|
"desc_highlight": "Expect some bugs and missing features here and there.",
|
||||||
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
||||||
"desc_para2": "we are working hard on the development and improving it over time.",
|
"desc_para2": "we are working hard on the development and improving it over time.",
|
||||||
"desc_para3": "To help boosting out development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
"desc_para3": "To boost development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
||||||
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
||||||
"desc_para5": "reach out to us on GitHub",
|
"desc_para5": "reach out to us on GitHub",
|
||||||
"desc_para6": "and get involved.",
|
"desc_para6": "and get involved.",
|
||||||
|
@ -165,6 +168,7 @@
|
||||||
"blocked_users": "Blocked users",
|
"blocked_users": "Blocked users",
|
||||||
"bookmarks": "Bookmarks",
|
"bookmarks": "Bookmarks",
|
||||||
"built_at": "Built {0}",
|
"built_at": "Built {0}",
|
||||||
|
"compose": "Compose",
|
||||||
"conversations": "Conversations",
|
"conversations": "Conversations",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
"favourites": "Favorites",
|
"favourites": "Favorites",
|
||||||
|
@ -247,19 +251,13 @@
|
||||||
"description": "Edit your account settings in Mastodon UI",
|
"description": "Edit your account settings in Mastodon UI",
|
||||||
"label": "Account settings"
|
"label": "Account settings"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
|
||||||
"github_cards": "GitHub Cards",
|
|
||||||
"title": "Experimental Features",
|
|
||||||
"user_picker": "User Picker",
|
|
||||||
"virtual_scroll": "Virtual Scrolling"
|
|
||||||
},
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "Color Mode",
|
"color_mode": "Color Mode",
|
||||||
"dark_mode": "Dark Mode",
|
"dark_mode": "Dark",
|
||||||
"default": " (default)",
|
"default": " (default)",
|
||||||
"font_size": "Font Size",
|
"font_size": "Font Size",
|
||||||
"label": "Interface",
|
"label": "Interface",
|
||||||
"light_mode": "Light Mode",
|
"light_mode": "Light",
|
||||||
"size_label": {
|
"size_label": {
|
||||||
"lg": "Large",
|
"lg": "Large",
|
||||||
"md": "Medium",
|
"md": "Medium",
|
||||||
|
@ -324,7 +322,14 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "Notifications",
|
"notifications_settings": "Notifications",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Preferences"
|
"github_cards": "GitHub Cards",
|
||||||
|
"hide_boost_count": "Hide boost count",
|
||||||
|
"hide_favorite_count": "Hide favorite count",
|
||||||
|
"hide_follower_count": "Hide follower count",
|
||||||
|
"label": "Preferences",
|
||||||
|
"title": "Experimental Features",
|
||||||
|
"user_picker": "User Picker",
|
||||||
|
"virtual_scroll": "Virtual Scrolling"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -347,16 +352,13 @@
|
||||||
"export": "Export User Tokens",
|
"export": "Export User Tokens",
|
||||||
"import": "Import User Tokens",
|
"import": "Import User Tokens",
|
||||||
"label": "Logged in users"
|
"label": "Logged in users"
|
||||||
},
|
|
||||||
"wellness": {
|
|
||||||
"feature": {
|
|
||||||
"hide_boost_count": "Hide boost count",
|
|
||||||
"hide_favorite_count": "Hide favorite count",
|
|
||||||
"hide_follower_count": "Hide follower count"
|
|
||||||
},
|
|
||||||
"label": "Wellness"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share-target": {
|
||||||
|
"description": "Elk can be configured so that you can share content from other applications, simply install Elk on your device or computer and sign in.",
|
||||||
|
"hint": "In order to share content with Elk, Elk must be installed and you must be signed in.",
|
||||||
|
"title": "Share with Elk"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
|
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
|
||||||
"attachments_limit_error": "Limit per post exceeded",
|
"attachments_limit_error": "Limit per post exceeded",
|
||||||
|
@ -453,6 +455,7 @@
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"add_content_warning": "Add content warning",
|
"add_content_warning": "Add content warning",
|
||||||
|
"add_emojis": "Add emojis",
|
||||||
"add_media": "Add images, a video or an audio file",
|
"add_media": "Add images, a video or an audio file",
|
||||||
"add_publishable_content": "Add content to publish",
|
"add_publishable_content": "Add content to publish",
|
||||||
"change_content_visibility": "Change content visibility",
|
"change_content_visibility": "Change content visibility",
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"loading_page": "Cargando página, espere por favor",
|
"loading_page": "Cargando página, espera por favor",
|
||||||
"loading_titled_page": "Cargando página {0}, espere por favor",
|
"loading_titled_page": "Cargando página {0}, espera por favor",
|
||||||
"locale_changed": "Idioma cambiado a {0}",
|
"locale_changed": "Idioma cambiado a {0}",
|
||||||
"locale_changing": "Cambiando idioma, espere por favor",
|
"locale_changing": "Cambiando idioma, espera por favor",
|
||||||
"route_loaded": "Página {0} cargada"
|
"route_loaded": "Página {0} cargada"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"blocked_users": "Usuarios bloqueados",
|
"blocked_users": "Usuarios bloqueados",
|
||||||
"blocking": "Bloqueado",
|
"blocking": "Bloqueado",
|
||||||
"bot": "BOT",
|
"bot": "BOT",
|
||||||
"favourites": "Favoritos",
|
"favourites": "Favoritas",
|
||||||
"follow": "Seguir",
|
"follow": "Seguir",
|
||||||
"follow_back": "Seguir de vuelta",
|
"follow_back": "Seguir de vuelta",
|
||||||
"follow_requested": "Solicitado",
|
"follow_requested": "Solicitado",
|
||||||
|
@ -50,9 +50,9 @@
|
||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"enter_app": "Entrar",
|
"enter_app": "Entrar",
|
||||||
"favourite": "Favorito",
|
"favourite": "Favorita",
|
||||||
"favourite_count": "{0}",
|
"favourite_count": "{0}",
|
||||||
"favourited": "Marcado como favorito",
|
"favourited": "Marcado como favorita",
|
||||||
"more": "Más",
|
"more": "Más",
|
||||||
"next": "Siguiente",
|
"next": "Siguiente",
|
||||||
"prev": "Anterior",
|
"prev": "Anterior",
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
"error": "ERROR",
|
"error": "ERROR",
|
||||||
"in": "en",
|
"in": "en",
|
||||||
"not_found": "404 No Encontrado",
|
"not_found": "404 No Encontrado",
|
||||||
"offline_desc": "Al parecer estás fuera de línea. Por favor, comprueba tu conexión a la red."
|
"offline_desc": "Al parecer no tienes conexión a internet. Por favor, comprueba tu conexión a la red."
|
||||||
},
|
},
|
||||||
"compose": {
|
"compose": {
|
||||||
"draft_title": "Borrador {0}",
|
"draft_title": "Borrador {0}",
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
"delete_and_redraft": "Borrar y volver a borrador",
|
"delete_and_redraft": "Borrar y volver a borrador",
|
||||||
"delete_confirm": {
|
"delete_confirm": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"confirm": "Borrar",
|
"confirm": "Eliminar",
|
||||||
"title": "¿Estás seguro que deseas eliminar esta publicación?"
|
"title": "¿Estás seguro que deseas eliminar esta publicación?"
|
||||||
},
|
},
|
||||||
"direct_message_account": "Mensaje directo a {0}",
|
"direct_message_account": "Mensaje directo a {0}",
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
"open_in_original_site": "Abrir página original",
|
"open_in_original_site": "Abrir página original",
|
||||||
"pin_on_profile": "Fijar en tu perfil",
|
"pin_on_profile": "Fijar en tu perfil",
|
||||||
"share_post": "Compartir esta publicación",
|
"share_post": "Compartir esta publicación",
|
||||||
"show_favourited_and_boosted_by": "Mostrar quien marcó como favorito y quien retooteó",
|
"show_favourited_and_boosted_by": "Mostrar quien marcó como favorita y quien retooteó",
|
||||||
"show_reblogs": "Mostrar retoots de {0}",
|
"show_reblogs": "Mostrar retoots de {0}",
|
||||||
"show_untranslated": "Mostrar original",
|
"show_untranslated": "Mostrar original",
|
||||||
"toggle_theme": {
|
"toggle_theme": {
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
"built_at": "Compilado {0}",
|
"built_at": "Compilado {0}",
|
||||||
"conversations": "Conversaciones",
|
"conversations": "Conversaciones",
|
||||||
"explore": "Explorar",
|
"explore": "Explorar",
|
||||||
"favourites": "Favoritos",
|
"favourites": "Favoritas",
|
||||||
"federated": "Federados",
|
"federated": "Federados",
|
||||||
"home": "Inicio",
|
"home": "Inicio",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
|
@ -179,12 +179,12 @@
|
||||||
"select_font_size": "Cambiar tamaño de letra",
|
"select_font_size": "Cambiar tamaño de letra",
|
||||||
"select_language": "Cambiar idioma",
|
"select_language": "Cambiar idioma",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"show_intro": "Mostrar intro",
|
"show_intro": "Mostrar introducción",
|
||||||
"toggle_theme": "Cambiar tema",
|
"toggle_theme": "Cambiar modo de color",
|
||||||
"zen_mode": "Modo Zen"
|
"zen_mode": "Modo Zen"
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"favourited_post": "marcó tu publicación como favorito",
|
"favourited_post": "marcó como favorita tu publicación",
|
||||||
"followed_you": "te ha seguido",
|
"followed_you": "te ha seguido",
|
||||||
"followed_you_count": "{0} personas te siguieron|{0} persona te siguió|{0} personas te siguieron",
|
"followed_you_count": "{0} personas te siguieron|{0} persona te siguió|{0} personas te siguieron",
|
||||||
"missing_type": "MISSING notification.type:",
|
"missing_type": "MISSING notification.type:",
|
||||||
|
@ -230,29 +230,30 @@
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"search_desc": "Buscar personas y etiquetas",
|
"search_desc": "Buscar personas y etiquetas",
|
||||||
"search_empty": "No se pudo encontrar nada para estos términos de búsqueda"
|
"search_empty": "No hubo resultados para estos términos de búsqueda"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"about": {
|
"about": {
|
||||||
"label": "Acerca de"
|
"label": "Acerca de",
|
||||||
|
"meet_the_team": "Conoce al equipo",
|
||||||
|
"sponsor_action": "Patrocinar",
|
||||||
|
"sponsor_action_desc": "Apoya al equipo detrás de Elk",
|
||||||
|
"sponsors": "Patrocinadores",
|
||||||
|
"sponsors_body_1": "Elk es posible gracias al generoso patrocinio y apoyo de:",
|
||||||
|
"sponsors_body_2": "Y todas las empresas y personas que patrocinan al equipo de Elk y sus miembros.",
|
||||||
|
"sponsors_body_3": "Si estás disfrutando de la aplicación, considera patrocinarnos:"
|
||||||
},
|
},
|
||||||
"account_settings": {
|
"account_settings": {
|
||||||
"description": "Edita los ajustes de tu cuenta en la interfaz de Mastodon",
|
"description": "Edita los ajustes de tu cuenta en la interfaz de Mastodon",
|
||||||
"label": "Ajustes de cuenta"
|
"label": "Ajustes de cuenta"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
|
||||||
"github_cards": "Tarjetas GitHub",
|
|
||||||
"title": "Funcionalidades experimentales",
|
|
||||||
"user_picker": "Selector de usuarios",
|
|
||||||
"virtual_scroll": "Desplazamiento virtual"
|
|
||||||
},
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "Modo Color",
|
"color_mode": "Modos de color",
|
||||||
"dark_mode": "Modo Oscuro",
|
"dark_mode": "Modo oscuro",
|
||||||
"default": " (por defecto)",
|
"default": " (por defecto)",
|
||||||
"font_size": "Tamaño de Letra",
|
"font_size": "Tamaño de Letra",
|
||||||
"label": "Interfaz",
|
"label": "Interfaz",
|
||||||
"light_mode": "Modo Claro",
|
"light_mode": "Modo claro",
|
||||||
"size_label": {
|
"size_label": {
|
||||||
"lg": "Grande",
|
"lg": "Grande",
|
||||||
"md": "Mediana",
|
"md": "Mediana",
|
||||||
|
@ -272,7 +273,7 @@
|
||||||
},
|
},
|
||||||
"push_notifications": {
|
"push_notifications": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"favourite": "Favoritos",
|
"favourite": "Favoritas",
|
||||||
"follow": "Nuevos seguidores",
|
"follow": "Nuevos seguidores",
|
||||||
"mention": "Menciones",
|
"mention": "Menciones",
|
||||||
"poll": "Encuestas",
|
"poll": "Encuestas",
|
||||||
|
@ -316,12 +317,19 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "Notificaciones",
|
"notifications_settings": "Notificaciones",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Preferencias"
|
"github_cards": "Tarjetas GitHub",
|
||||||
|
"hide_boost_count": "Ocultar contador de retoots",
|
||||||
|
"hide_favorite_count": "Ocultar contador de favoritas",
|
||||||
|
"hide_follower_count": "Ocultar contador de seguidores",
|
||||||
|
"label": "Preferencias",
|
||||||
|
"title": "Funcionalidades experimentales",
|
||||||
|
"user_picker": "Selector de usuarios",
|
||||||
|
"virtual_scroll": "Desplazamiento virtual"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"bio": "Biografía",
|
"bio": "Biografía",
|
||||||
"description": "Editar avatar, nombre de usuario, perfil, etc.",
|
"description": "Modificar avatar, nombre de usuario, perfil, etc.",
|
||||||
"display_name": "Nombre a mostrar",
|
"display_name": "Nombre a mostrar",
|
||||||
"label": "Apariencia",
|
"label": "Apariencia",
|
||||||
"profile_metadata": "Metadatos de perfil",
|
"profile_metadata": "Metadatos de perfil",
|
||||||
|
@ -329,7 +337,7 @@
|
||||||
"title": "Editar perfil"
|
"title": "Editar perfil"
|
||||||
},
|
},
|
||||||
"featured_tags": {
|
"featured_tags": {
|
||||||
"description": "Las personas pueden navegar por tus publicaciones públicas con estas hashtags.",
|
"description": "Las personas pueden navegar por tus publicaciones públicas con estos hashtags.",
|
||||||
"label": "Hashtags destacados"
|
"label": "Hashtags destacados"
|
||||||
},
|
},
|
||||||
"label": "Perfil"
|
"label": "Perfil"
|
||||||
|
@ -341,19 +349,25 @@
|
||||||
"label": "Usuarios conectados"
|
"label": "Usuarios conectados"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share-target": {
|
||||||
|
"description": "Elk puede ser configurado para que pueda compartir contenido desde otras aplicaciones, simplemente tiene que instalar Elk en su dispositivo u ordenador e iniciar sesión.",
|
||||||
|
"hint": "Para poder compartir contenido con Elk, debes instalar Elk e iniciar sesión.",
|
||||||
|
"title": "Compartir con Elk"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"attachments_exceed_server_limit": "Número máximo de archivos adjuntos por publicación excedido.",
|
"attachments_exceed_server_limit": "Número máximo de archivos adjuntos por publicación excedido.",
|
||||||
"attachments_limit_error": "Límite por publicación excedido",
|
"attachments_limit_error": "Límite por publicación excedido",
|
||||||
"edited": "(Editado)",
|
"edited": "(Editado)",
|
||||||
"editing": "Editando",
|
"editing": "Editando",
|
||||||
"loading": "Cargando...",
|
"loading": "Cargando...",
|
||||||
|
"publishing": "Publicando",
|
||||||
"upload_failed": "Subida fallida",
|
"upload_failed": "Subida fallida",
|
||||||
"uploading": "Subiendo..."
|
"uploading": "Subiendo..."
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"boosted_by": "Retooteado por",
|
"boosted_by": "Retooteado por",
|
||||||
"edited": "Editado {0}",
|
"edited": "Editado {0}",
|
||||||
"favourited_by": "Marcado como favorito por",
|
"favourited_by": "Marcado como favorita por",
|
||||||
"filter_hidden_phrase": "Filtrado por",
|
"filter_hidden_phrase": "Filtrado por",
|
||||||
"filter_removed_phrase": "Eliminado por filtrado",
|
"filter_removed_phrase": "Eliminado por filtrado",
|
||||||
"filter_show_anyway": "Mostrar de todas formas",
|
"filter_show_anyway": "Mostrar de todas formas",
|
||||||
|
@ -436,20 +450,21 @@
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"add_content_warning": "Añadir advertencia de contenido",
|
"add_content_warning": "Añadir advertencia de contenido",
|
||||||
|
"add_emojis": "Añadir emojis",
|
||||||
"add_media": "Añadir imágenes, video o audio",
|
"add_media": "Añadir imágenes, video o audio",
|
||||||
"add_publishable_content": "Agregar contenido a publicar",
|
"add_publishable_content": "Publicar contenido",
|
||||||
"change_content_visibility": "Cambiar visibilidad de contenido",
|
"change_content_visibility": "Cambiar visibilidad de contenido",
|
||||||
"change_language": "Cambiar idioma",
|
"change_language": "Cambiar idioma",
|
||||||
"emoji": "Emoji",
|
"emoji": "Emoji",
|
||||||
"explore_links_intro": "Estas noticias están siendo comentadas por la gente en este y otros servidores de la red descentralizada en este momento.",
|
"explore_links_intro": "Estas noticias están siendo comentadas ahora mismo por los usuarios de este y otros servidores de la red descentralizada.",
|
||||||
"explore_posts_intro": "Estos mensajes de este y otros servidores de la red descentralizada están ganando tracción en este servidor en este momento.",
|
"explore_posts_intro": "Estos mensajes de este y otros servidores de la red descentralizada están siendo tendencia ahora mismo en este servidor.",
|
||||||
"explore_tags_intro": "Estas etiquetas están ganando tracción entre la gente de este y otros servidores de la red descentralizada en este momento.",
|
"explore_tags_intro": "Estas etiquetas están siendo tendencia ahora mismo entre los usuarios de este y otros servidores de la red descentralizada.",
|
||||||
"toggle_code_block": "Cambiar a bloque de código"
|
"toggle_code_block": "Cambiar a bloque de código"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"add_existing": "Agregar una cuenta existente",
|
"add_existing": "Agregar una cuenta existente",
|
||||||
"server_address_label": "Dirección de Servidor de Mastodon",
|
"server_address_label": "Dirección de Servidor de Mastodon",
|
||||||
"sign_in_desc": "Inicia sesión para seguir perfiles o hashtags, marcar como favorito, compartir and responder a publicaciones, o interactuar desde tu usuario con un servidor diferente.",
|
"sign_in_desc": "Inicia sesión para seguir perfiles o hashtags, marcar cómo favorita, compartir y responder a publicaciones, o interactuar con un servidor diferente con tu usuario.",
|
||||||
"sign_in_notice_title": "Viendo información pública de {0}",
|
"sign_in_notice_title": "Viendo información pública de {0}",
|
||||||
"sign_out_account": "Cerrar sesión {0}",
|
"sign_out_account": "Cerrar sesión {0}",
|
||||||
"tip_no_account": "Si aún no tienes una cuenta Mastodon, {0}.",
|
"tip_no_account": "Si aún no tienes una cuenta Mastodon, {0}.",
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
"blocked_by": "Vous êtes bloqué·e par cet·te utilisateur·ice.",
|
"blocked_by": "Vous êtes bloqué·e par cet·te utilisateur·ice.",
|
||||||
"blocked_domains": "Domaines bloqués",
|
"blocked_domains": "Domaines bloqués",
|
||||||
"blocked_users": "Utilisateur·ice·s bloqué·e·s",
|
"blocked_users": "Utilisateur·ice·s bloqué·e·s",
|
||||||
"blocking": "Blocked",
|
"blocking": "Bloqué·e",
|
||||||
"bot": "Automatisé",
|
"bot": "Automatisé",
|
||||||
"favourites": "Appréciés",
|
"favourites": "Aimés",
|
||||||
"follow": "Suivre",
|
"follow": "Suivre",
|
||||||
"follow_back": "Suivre en retour",
|
"follow_back": "Suivre en retour",
|
||||||
"follow_requested": "Abonnement demandé",
|
"follow_requested": "Abonnement demandé",
|
||||||
|
@ -27,15 +27,15 @@
|
||||||
"moved_title": "a indiqué que son nouveau compte est désormais :",
|
"moved_title": "a indiqué que son nouveau compte est désormais :",
|
||||||
"muted_users": "Utilisateur·ice·s masqué·e·s",
|
"muted_users": "Utilisateur·ice·s masqué·e·s",
|
||||||
"muting": "Masqué·e",
|
"muting": "Masqué·e",
|
||||||
"mutuals": "@:account.following",
|
"mutuals": "Abonné·e·s",
|
||||||
"pinned": "Épinglés",
|
"pinned": "Épinglés",
|
||||||
"posts": "Messages",
|
"posts": "Messages",
|
||||||
"posts_count": "{0} Messages",
|
"posts_count": "{0} Messages",
|
||||||
"profile_description": "En-tête du profil de {0}",
|
"profile_description": "En-tête du profil de {0}",
|
||||||
"profile_unavailable": "Profil non accessible",
|
"profile_unavailable": "Profil non accessible",
|
||||||
"unblock": "Unblock",
|
"unblock": "Débloquer",
|
||||||
"unfollow": "Ne plus suivre",
|
"unfollow": "Ne plus suivre",
|
||||||
"unmute": "Unmute"
|
"unmute": "Réafficher"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"apply": "Appliquer",
|
"apply": "Appliquer",
|
||||||
|
@ -49,8 +49,8 @@
|
||||||
"confirm": "Confirmer",
|
"confirm": "Confirmer",
|
||||||
"edit": "Éditer",
|
"edit": "Éditer",
|
||||||
"enter_app": "Entrer dans l'application",
|
"enter_app": "Entrer dans l'application",
|
||||||
"favourite": "Ajouter aux messages appréciés",
|
"favourite": "J'aime",
|
||||||
"favourited": "Ajouté aux messages appréciés",
|
"favourited": "Aimé",
|
||||||
"more": "Plus",
|
"more": "Plus",
|
||||||
"next": "Suivant",
|
"next": "Suivant",
|
||||||
"prev": "Précédent",
|
"prev": "Précédent",
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
"open_in_original_site": "Ouvrir sur le site d'origine",
|
"open_in_original_site": "Ouvrir sur le site d'origine",
|
||||||
"pin_on_profile": "Épingler sur le profil",
|
"pin_on_profile": "Épingler sur le profil",
|
||||||
"share_post": "Partager ce message",
|
"share_post": "Partager ce message",
|
||||||
"show_favourited_and_boosted_by": "Montrer qui a apprécié et partagé",
|
"show_favourited_and_boosted_by": "Montrer qui a aimé et partagé",
|
||||||
"show_reblogs": "Voir les partages de {0}",
|
"show_reblogs": "Voir les partages de {0}",
|
||||||
"show_untranslated": "Montrer le message non-traduit",
|
"show_untranslated": "Montrer le message non-traduit",
|
||||||
"toggle_theme": {
|
"toggle_theme": {
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
"built_at": "Dernière compilation {0}",
|
"built_at": "Dernière compilation {0}",
|
||||||
"conversations": "Conversations",
|
"conversations": "Conversations",
|
||||||
"explore": "Explorer",
|
"explore": "Explorer",
|
||||||
"favourites": "Appréciés",
|
"favourites": "Favoris",
|
||||||
"federated": "Fédérés",
|
"federated": "Fédérés",
|
||||||
"home": "Accueil",
|
"home": "Accueil",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
|
@ -181,7 +181,7 @@
|
||||||
"zen_mode": "Mode Zen"
|
"zen_mode": "Mode Zen"
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"favourited_post": "apprécie votre message",
|
"favourited_post": "a aimé votre message",
|
||||||
"followed_you": "vous suit",
|
"followed_you": "vous suit",
|
||||||
"followed_you_count": "{0} personnes vous suivent|{0} personne vous suit|{0} personnes vous suivent",
|
"followed_you_count": "{0} personnes vous suivent|{0} personne vous suit|{0} personnes vous suivent",
|
||||||
"missing_type": "MISSING notification.type:",
|
"missing_type": "MISSING notification.type:",
|
||||||
|
@ -244,12 +244,6 @@
|
||||||
"description": "Modifiez les paramètres de votre compte dans l'interface de Mastodon",
|
"description": "Modifiez les paramètres de votre compte dans l'interface de Mastodon",
|
||||||
"label": "Paramètres de compte"
|
"label": "Paramètres de compte"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
|
||||||
"github_cards": "GitHub Cards",
|
|
||||||
"title": "Fonctionnalités expérimentales",
|
|
||||||
"user_picker": "User Picker",
|
|
||||||
"virtual_scroll": "Défilement virtuel"
|
|
||||||
},
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "Couleur de thème",
|
"color_mode": "Couleur de thème",
|
||||||
"dark_mode": "Mode sombre",
|
"dark_mode": "Mode sombre",
|
||||||
|
@ -277,11 +271,11 @@
|
||||||
},
|
},
|
||||||
"push_notifications": {
|
"push_notifications": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"favourite": "Apprécier",
|
"favourite": "Messages aimés",
|
||||||
"follow": "Nouveaux abonné·e·s",
|
"follow": "Nouveaux abonné·e·s",
|
||||||
"mention": "Mentions",
|
"mention": "Mentions",
|
||||||
"poll": "Sondages",
|
"poll": "Sondages",
|
||||||
"reblog": "A republié votre message",
|
"reblog": "Messages partagés",
|
||||||
"title": "Quelles notifications recevoir ?"
|
"title": "Quelles notifications recevoir ?"
|
||||||
},
|
},
|
||||||
"description": "Recevez des notifications même lorsque vous n'utilisez pas Elk.",
|
"description": "Recevez des notifications même lorsque vous n'utilisez pas Elk.",
|
||||||
|
@ -320,7 +314,14 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "Notifications",
|
"notifications_settings": "Notifications",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "Préférences"
|
"github_cards": "GitHub Cards",
|
||||||
|
"hide_boost_count": "Cacher les compteurs de partages",
|
||||||
|
"hide_favorite_count": "Cacher les compteurs de favoris",
|
||||||
|
"hide_follower_count": "Cacher les compteurs d'abonné·e·s",
|
||||||
|
"label": "Préférences",
|
||||||
|
"title": "Fonctionnalités expérimentales",
|
||||||
|
"user_picker": "User Picker",
|
||||||
|
"virtual_scroll": "Défilement virtuel"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -343,14 +344,6 @@
|
||||||
"export": "Exporter les tokens d'utilisateur·ice",
|
"export": "Exporter les tokens d'utilisateur·ice",
|
||||||
"import": "Importer des tokens d'utilisateur·ice",
|
"import": "Importer des tokens d'utilisateur·ice",
|
||||||
"label": "Utilisateur·ice·s connecté·e·s"
|
"label": "Utilisateur·ice·s connecté·e·s"
|
||||||
},
|
|
||||||
"wellness": {
|
|
||||||
"feature": {
|
|
||||||
"hide_boost_count": "Cacher les compteurs de partages",
|
|
||||||
"hide_favorite_count": "Cacher les compteurs d'appréciations",
|
|
||||||
"hide_follower_count": "Cacher les compteurs d'abonné·e·s"
|
|
||||||
},
|
|
||||||
"label": "Bien-être"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
|
@ -366,7 +359,7 @@
|
||||||
"status": {
|
"status": {
|
||||||
"boosted_by": "Partagé par",
|
"boosted_by": "Partagé par",
|
||||||
"edited": "Edité {0}",
|
"edited": "Edité {0}",
|
||||||
"favourited_by": "Apprécié par",
|
"favourited_by": "Aimé par",
|
||||||
"filter_hidden_phrase": "Filtré par",
|
"filter_hidden_phrase": "Filtré par",
|
||||||
"filter_removed_phrase": "Caché par le filtre",
|
"filter_removed_phrase": "Caché par le filtre",
|
||||||
"filter_show_anyway": "Montrer coûte que coûte",
|
"filter_show_anyway": "Montrer coûte que coûte",
|
||||||
|
@ -462,7 +455,7 @@
|
||||||
"user": {
|
"user": {
|
||||||
"add_existing": "Ajouter un compte existant",
|
"add_existing": "Ajouter un compte existant",
|
||||||
"server_address_label": "Adresse du serveur mastodon",
|
"server_address_label": "Adresse du serveur mastodon",
|
||||||
"sign_in_desc": "Connectez-vous pour suivre des profils ou des hashtags, dire que vous appréciez, partager et répondre à des messages, ou interagir à partir de votre compte sur un autre serveur...",
|
"sign_in_desc": "Connectez-vous pour suivre des profils ou des hashtags, aimer, partagez et répondre à des messages, ou interagir à partir de votre compte d'autre serveur.",
|
||||||
"sign_in_notice_title": "Affichage de {0} données publiques",
|
"sign_in_notice_title": "Affichage de {0} données publiques",
|
||||||
"sign_out_account": "Se déconnecter de {0}",
|
"sign_out_account": "Se déconnecter de {0}",
|
||||||
"tip_no_account": "Si vous n'avez pas encore de compte Mastodon, {0}.",
|
"tip_no_account": "Si vous n'avez pas encore de compte Mastodon, {0}.",
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
"desc_highlight": "Expect some bugs and missing features here and there.",
|
"desc_highlight": "Expect some bugs and missing features here and there.",
|
||||||
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!",
|
||||||
"desc_para2": "we are working hard on the development and improving it over time.",
|
"desc_para2": "we are working hard on the development and improving it over time.",
|
||||||
"desc_para3": "To help boosting out development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
"desc_para3": "To boost development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
|
||||||
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,",
|
||||||
"desc_para5": "reach out to us on GitHub",
|
"desc_para5": "reach out to us on GitHub",
|
||||||
"desc_para6": "and get involved.",
|
"desc_para6": "and get involved.",
|
||||||
|
|
495
locales/pt-PT.json
Normal file
495
locales/pt-PT.json
Normal file
|
@ -0,0 +1,495 @@
|
||||||
|
{
|
||||||
|
"a11y": {
|
||||||
|
"loading_page": "A carregar página, por favor aguarde",
|
||||||
|
"loading_titled_page": "A carregar página {0}, por favor aguarde",
|
||||||
|
"locale_changed": "Idioma alterado para {0}",
|
||||||
|
"locale_changing": "A alterar idioma, por favor aguarde",
|
||||||
|
"route_loaded": "Página {0} carregada"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"avatar_description": "Imagem de perfil de {0}",
|
||||||
|
"blocked_by": "Está bloqueado por este utilizador.",
|
||||||
|
"blocked_domains": "Domínios bloqueados",
|
||||||
|
"blocked_users": "Utilizadores bloqueados",
|
||||||
|
"blocking": "Bloqueado",
|
||||||
|
"bot": "BOT",
|
||||||
|
"favourites": "Favoritos",
|
||||||
|
"follow": "Seguir",
|
||||||
|
"follow_back": "Seguir de volta",
|
||||||
|
"follow_requested": "Pedido",
|
||||||
|
"followers": "Seguidores",
|
||||||
|
"followers_count": "{0} Seguidores|{0} Seguidor|{0} Seguidores",
|
||||||
|
"following": "A seguir",
|
||||||
|
"following_count": "A seguir {0}",
|
||||||
|
"follows_you": "Segue-o",
|
||||||
|
"go_to_profile": "Ir para o perfil",
|
||||||
|
"joined": "Juntou-se a",
|
||||||
|
"moved_title": "indicou que a sua novo conta é agora:",
|
||||||
|
"muted_users": "Utilizadores silenciados",
|
||||||
|
"muting": "Silenciados",
|
||||||
|
"mutuals": "Mútuos",
|
||||||
|
"notify_on_post": "Notifique-me quando {username} publicar",
|
||||||
|
"pinned": "Fixado",
|
||||||
|
"posts": "Publicações",
|
||||||
|
"posts_count": "{0} Publicações|{0} Publicação|{0} Publicações",
|
||||||
|
"profile_description": "Descrição de perfil de {0}",
|
||||||
|
"profile_unavailable": "Perfil indisponível",
|
||||||
|
"unblock": "Desbloquear",
|
||||||
|
"unfollow": "Deixar de seguir",
|
||||||
|
"unmute": "Deixar de silenciar",
|
||||||
|
"view_other_followers": "Os seguidores de outras instâncias podem não ser exibidos.",
|
||||||
|
"view_other_following": "As pessoas que segue de outras instâncias podem não ser exibidas."
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"apply": "Aplicar",
|
||||||
|
"bookmark": "Salvar",
|
||||||
|
"bookmarked": "Adicionado aos itens salvos",
|
||||||
|
"boost": "Partilhar",
|
||||||
|
"boost_count": "{0}",
|
||||||
|
"boosted": "Partilhado",
|
||||||
|
"clear_upload_failed": "Limpar erro de carregamento de ficheiro",
|
||||||
|
"close": "Fechar",
|
||||||
|
"compose": "Compor",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"edit": "Editar",
|
||||||
|
"enter_app": "Entrar na App",
|
||||||
|
"favourite": "Adicionar aos favoritos",
|
||||||
|
"favourite_count": "{0}",
|
||||||
|
"favourited": "Adicionado aos favoritos",
|
||||||
|
"more": "Mais",
|
||||||
|
"next": "Próximo",
|
||||||
|
"prev": "Anterior",
|
||||||
|
"publish": "Publicar",
|
||||||
|
"reply": "Responder",
|
||||||
|
"reply_count": "{0}",
|
||||||
|
"reset": "Repor",
|
||||||
|
"save": "Guardar",
|
||||||
|
"save_changes": "Guardar alterações",
|
||||||
|
"sign_in": "Entrar",
|
||||||
|
"switch_account": "Mudar contar",
|
||||||
|
"vote": "Votar"
|
||||||
|
},
|
||||||
|
"app_desc_short": "Uma ágil aplicação web para o Mastodon",
|
||||||
|
"app_logo": "Logo do Elk",
|
||||||
|
"app_name": "Elk",
|
||||||
|
"attachment": {
|
||||||
|
"edit_title": "Descrição",
|
||||||
|
"remove_label": "Remover anexo"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"activate": "Ativar",
|
||||||
|
"complete": "Completar",
|
||||||
|
"compose_desc": "Escrever uma nova publicação",
|
||||||
|
"n-people-in-the-past-n-days": "{0} pessoas nos últimos {1} dias",
|
||||||
|
"select_lang": "Selecionar idioma",
|
||||||
|
"sign_in_desc": "Adicionar uma conta existente",
|
||||||
|
"switch_account": "Mudar para {0}",
|
||||||
|
"switch_account_desc": "Mudar para outra conta",
|
||||||
|
"toggle_dark_mode": "Alternar modo escuro",
|
||||||
|
"toggle_zen_mode": "Alternar modo zen"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"confirm_dialog": {
|
||||||
|
"cancel": "Não",
|
||||||
|
"confirm": "Sim",
|
||||||
|
"title": "Tem a certeza?"
|
||||||
|
},
|
||||||
|
"end_of_list": "Fim da lista",
|
||||||
|
"error": "ERRO",
|
||||||
|
"in": "em",
|
||||||
|
"not_found": "404 Não Encontrado",
|
||||||
|
"offline_desc": "Parece que está offline. Por favor, confirme a sua conexão à internet."
|
||||||
|
},
|
||||||
|
"compose": {
|
||||||
|
"draft_title": "Rascunho {0}",
|
||||||
|
"drafts": "Rascunhos ({v})"
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"with": "com"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"account_not_found": "Conta {0} não encontrada",
|
||||||
|
"explore-list-empty": "Nada está em tendência agora. Confirme mais tarde!",
|
||||||
|
"file_size_cannot_exceed_n_mb": "O tamanho do ficheiro não pode exceder {0}MB",
|
||||||
|
"sign_in_error": "Não é possível conectar ao servidor.",
|
||||||
|
"status_not_found": "Publicação não encontrada",
|
||||||
|
"unsupported_file_format": "Formato de ficheiro não suportado"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"desc_highlight": "Espere alguns problemas e funcionalidades em falta.",
|
||||||
|
"desc_para1": "Obrigado pelo seu interesse em experimentar o Elk, o nosso aplicativo web para o Mastodon, ainda em construção!",
|
||||||
|
"desc_para2": "Estamos a trabalhar arduamente no seu desenvolvimento e melhoria ao longo do tempo.",
|
||||||
|
"desc_para3": "Para ajudar a impulsionar o desenvolvimento, pode patrocionar a Equipa através do GitHub Sponsors. Esperamos que aprecie o Elk!",
|
||||||
|
"desc_para4": "Elk é um software de código aberto. Se quiser ajudar a testar a aplicação, dando o seu feedback ou contributo,",
|
||||||
|
"desc_para5": "pode encontrar-nos no GitHub",
|
||||||
|
"desc_para6": "e participar.",
|
||||||
|
"title": "Elk está em Antevisão!"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"search": "Procurar"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"block_account": "Bloquear {0}",
|
||||||
|
"block_domain": "Bloquear domínio {0}",
|
||||||
|
"copy_link_to_post": "Copiar ligação para esta publicação",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"delete_and_redraft": "Eliminar & re-editar",
|
||||||
|
"delete_confirm": {
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Eliminar",
|
||||||
|
"title": "Tem a certeza que pretende eliminar esta publicação?"
|
||||||
|
},
|
||||||
|
"direct_message_account": "Mensagem direta a {0}",
|
||||||
|
"edit": "Editar",
|
||||||
|
"hide_reblogs": "Esconder partilhas de {0}",
|
||||||
|
"mention_account": "Mencionar {0}",
|
||||||
|
"mute_account": "Silenciar {0}",
|
||||||
|
"mute_conversation": "Silenciar esta publicação",
|
||||||
|
"open_in_original_site": "Abrir no sítio original",
|
||||||
|
"pin_on_profile": "Fixar no perfil",
|
||||||
|
"share_post": "Partilhar esta publicação",
|
||||||
|
"show_favourited_and_boosted_by": "Mostrar quem adicionou aos favoritos e partilhou",
|
||||||
|
"show_reblogs": "Mostrar partilhas de {0}",
|
||||||
|
"show_untranslated": "Mostrar não traduzidas",
|
||||||
|
"toggle_theme": {
|
||||||
|
"dark": "Alternar modo escuro",
|
||||||
|
"light": "Alternar modo claro"
|
||||||
|
},
|
||||||
|
"translate_post": "Traduzir publicação",
|
||||||
|
"unblock_account": "Desbloquear {0}",
|
||||||
|
"unblock_domain": "Desbloquear domínio {0}",
|
||||||
|
"unmute_account": "Deixar de silenciar {0}",
|
||||||
|
"unmute_conversation": "Deixar de silenciar esta publicação",
|
||||||
|
"unpin_on_profile": "Desafixar do perfil"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"back": "Voltar",
|
||||||
|
"blocked_domains": "Domínios bloqueados",
|
||||||
|
"blocked_users": "Utilizadores bloqueados",
|
||||||
|
"bookmarks": "Itens Salvos",
|
||||||
|
"built_at": "Produzido {0}",
|
||||||
|
"compose": "Compor",
|
||||||
|
"conversations": "Conversações",
|
||||||
|
"explore": "Explorar",
|
||||||
|
"favourites": "Favoritos",
|
||||||
|
"federated": "Federada",
|
||||||
|
"home": "Início",
|
||||||
|
"local": "Local",
|
||||||
|
"muted_users": "Utilizadores silenciados",
|
||||||
|
"notifications": "Notificações",
|
||||||
|
"profile": "Perfil",
|
||||||
|
"search": "Procurar",
|
||||||
|
"select_feature_flags": "Alternar Funcionalidades",
|
||||||
|
"select_font_size": "Tamanho da Fonte",
|
||||||
|
"select_language": "Idioma de Apresentação",
|
||||||
|
"settings": "Definições",
|
||||||
|
"show_intro": "Mostrar introdução",
|
||||||
|
"toggle_theme": "Alternar Tema",
|
||||||
|
"zen_mode": "Modo Zen"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"favourited_post": "adicionou a sua publicação aos favoritos",
|
||||||
|
"followed_you": "começou a segui-lo",
|
||||||
|
"followed_you_count": "{0} pessoas seguem-no|{0} pessoa segue-o|{0} pessoas seguem-no",
|
||||||
|
"missing_type": "notification.type em FALTA:",
|
||||||
|
"reblogged_post": "partilhou a sua publicação",
|
||||||
|
"request_to_follow": "pediu para segui-lo",
|
||||||
|
"signed_up": "inscreveu-se",
|
||||||
|
"update_status": "atualizou a sua publicação"
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"content_warning": "Escreva aqui o seu aviso",
|
||||||
|
"default_1": "Em que está a pensar?",
|
||||||
|
"reply_to_account": "Responder a {0}",
|
||||||
|
"replying": "Respondedo",
|
||||||
|
"the_thread": "a conversa"
|
||||||
|
},
|
||||||
|
"pwa": {
|
||||||
|
"dismiss": "Dispensar",
|
||||||
|
"title": "Nova atualização do Elk disponível!",
|
||||||
|
"update": "Atualizar",
|
||||||
|
"update_available_short": "Atualizar Elk",
|
||||||
|
"webmanifest": {
|
||||||
|
"canary": {
|
||||||
|
"description": "Uma ágil aplicação web para o Mastodon (canary)",
|
||||||
|
"name": "Elk (canary)",
|
||||||
|
"short_name": "Elk (canary)"
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"description": "Uma ágil aplicação web para o Mastodon (dev)",
|
||||||
|
"name": "Elk (dev)",
|
||||||
|
"short_name": "Elk (dev)"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"description": "Uma ágil aplicação web para o Mastodon (preview)",
|
||||||
|
"name": "Elk (preview)",
|
||||||
|
"short_name": "Elk (preview)"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"description": "Uma ágil aplicação web para o Mastodon",
|
||||||
|
"name": "Elk",
|
||||||
|
"short_name": "Elk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"search_desc": "Procure por pessoas e hashtags",
|
||||||
|
"search_empty": "Não foi possível encontrar nada para os termos que pesquisou"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"about": {
|
||||||
|
"label": "Sobre",
|
||||||
|
"meet_the_team": "Conheça a equipa",
|
||||||
|
"sponsor_action": "Patrocine-nos",
|
||||||
|
"sponsor_action_desc": "Para ajudar a equipa que desenvolve o Elk",
|
||||||
|
"sponsors": "Patrocinadores",
|
||||||
|
"sponsors_body_1": "O Elk é possível graças ao genoroso patrocinio e ajuda de:",
|
||||||
|
"sponsors_body_2": "E todas as empresas e pessoas que apoiam a Equipa do Elk e os seus membros.",
|
||||||
|
"sponsors_body_3": "Se está a gostar de utilizar esta aplicação, considere apoiar-nos:"
|
||||||
|
},
|
||||||
|
"account_settings": {
|
||||||
|
"description": "Editar as configurações da sua conta na aplicação web do Mastodon",
|
||||||
|
"label": "Configurações da conta"
|
||||||
|
},
|
||||||
|
"feature_flags": {
|
||||||
|
"github_cards": "Cartões do GitHub",
|
||||||
|
"title": "Funcionalidades Experimentais",
|
||||||
|
"user_picker": "Selecionador de Utilizador",
|
||||||
|
"virtual_scroll": "Deslocamento Virtual"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"color_mode": "Modo de cores",
|
||||||
|
"dark_mode": "Modo Escuro",
|
||||||
|
"default": " (padrão)",
|
||||||
|
"font_size": "Tamanho da fonte",
|
||||||
|
"label": "Apresentação",
|
||||||
|
"light_mode": "Modo Claro",
|
||||||
|
"size_label": {
|
||||||
|
"lg": "Grande",
|
||||||
|
"md": "Médio",
|
||||||
|
"sm": "Pequeno",
|
||||||
|
"xl": "Extra grande",
|
||||||
|
"xs": "Extra pequeno"
|
||||||
|
},
|
||||||
|
"system_mode": "Sistema"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"display_language": "Idioma de Apresentação",
|
||||||
|
"label": "Idioma"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"label": "Notificações",
|
||||||
|
"notifications": {
|
||||||
|
"label": "Configurar notificações"
|
||||||
|
},
|
||||||
|
"push_notifications": {
|
||||||
|
"alerts": {
|
||||||
|
"favourite": "Favoritos",
|
||||||
|
"follow": "Novos seguidores",
|
||||||
|
"mention": "Menções",
|
||||||
|
"poll": "Votações",
|
||||||
|
"reblog": "Partilha das sua publicação",
|
||||||
|
"title": "Que notificações quer receber?"
|
||||||
|
},
|
||||||
|
"description": "Receba notificações mesmo quando não está a utilizar o Elk.",
|
||||||
|
"instructions": "Não esqueça de salvar as suas alterações utilizando o botão @:settings.notifications.push_notifications.save_settings!",
|
||||||
|
"label": "Configurar notificações push",
|
||||||
|
"policy": {
|
||||||
|
"all": "De todos",
|
||||||
|
"followed": "De pessoas que sigo",
|
||||||
|
"follower": "De pessoas que me seguem",
|
||||||
|
"none": "De ninguém",
|
||||||
|
"title": "De quem quer receber notificações?"
|
||||||
|
},
|
||||||
|
"save_settings": "Salvar configurações",
|
||||||
|
"subscription_error": {
|
||||||
|
"clear_error": "Limpar erro",
|
||||||
|
"permission_denied": "Permissão negada: habilite as notificações no seu browser.",
|
||||||
|
"request_error": "Um erro ocorreu durante o pedido de subcrição, tente novamente e se o erro persistir, por favor reporte o problema no repositório do Elk.",
|
||||||
|
"title": "Náo é possível subscrever as notificações push",
|
||||||
|
"too_many_registrations": "Devido a limitações do browser, o Elk não consegue utilizar o serviço de notificações push para multiplas contas em diferentes servidores. Deve cancelar a subcrição de notificações push nas outras contas e tentar novamente."
|
||||||
|
},
|
||||||
|
"title": "Configuração de notificações push",
|
||||||
|
"undo_settings": "Reverter alterações",
|
||||||
|
"unsubscribe": "Desabilitar notificações push",
|
||||||
|
"unsupported": "O seu browser não suporta notificações push.",
|
||||||
|
"warning": {
|
||||||
|
"enable_close": "Fechar",
|
||||||
|
"enable_description": "Para receber notificações quanto o Elk não está aberto, habilite as notificações push. Poderá controlar com precisão que tipos de interações geram notificações push através do \"@:settings.notifications.show_btn{'\"'} botão acima, uma vez habilitadas.",
|
||||||
|
"enable_description_desktop": "Para receber notificações quanto o Elk não está aberto, habilite as notificações push. Poderá controlar com precisão que tipos de interações geram notificações push em \"Preferências > Notificaçõess > Configuração de notificações push\", uma vez habilitadas.",
|
||||||
|
"enable_description_mobile": "Pode também aceder às configurações através do menu de navegação \"Preferências > Notificações > Configuração de notificações push\".",
|
||||||
|
"enable_description_settings": "Para receber notificações quanto o Elk não está aberto, habilite as notificações push. Poderá controlar com precisão que tipos de interações geram notificações neste mesmo ecrã, uma vez habilitadas.",
|
||||||
|
"enable_desktop": "Habilitar notificações push",
|
||||||
|
"enable_title": "Nunca perca nada",
|
||||||
|
"re_auth": "Parece que o seu servidor não suporta notificações push. Tenta desconectar e voltar a entrar, se esta mensagem permanecer contacte o administrador do seu servidor."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"show_btn": "Ir para a configuração de nofiticações"
|
||||||
|
},
|
||||||
|
"notifications_settings": "Notificações",
|
||||||
|
"preferences": {
|
||||||
|
"label": "Preferências"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"appearance": {
|
||||||
|
"bio": "Bio",
|
||||||
|
"description": "Editar imagem de perfil, nome, perfil, etc.",
|
||||||
|
"display_name": "Nome de apresentação",
|
||||||
|
"label": "Aspecto",
|
||||||
|
"profile_metadata": "Metadados de perfil",
|
||||||
|
"profile_metadata_desc": "Pode ter até {0} itens expostos, em forma de tabela, no seu perfil",
|
||||||
|
"title": "Editar perfil"
|
||||||
|
},
|
||||||
|
"featured_tags": {
|
||||||
|
"description": "As pessoas podem encontrar as suas publicações públicas que incluem essas hashtags.",
|
||||||
|
"label": "Hashtags destacadas"
|
||||||
|
},
|
||||||
|
"label": "Perfil"
|
||||||
|
},
|
||||||
|
"select_a_settings": "Selecionar uma configuração",
|
||||||
|
"users": {
|
||||||
|
"export": "Exportar Tokens de Acesso",
|
||||||
|
"import": "Importar Tokens de Acesso",
|
||||||
|
"label": "Utilizadores conectados"
|
||||||
|
},
|
||||||
|
"wellness": {
|
||||||
|
"feature": {
|
||||||
|
"hide_boost_count": "Esconder contagem de partilhas",
|
||||||
|
"hide_favorite_count": "Esconder contagem de favoritos",
|
||||||
|
"hide_follower_count": "Esconder contagem de seguidores"
|
||||||
|
},
|
||||||
|
"label": "Bem-estar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share-target": {
|
||||||
|
"description": "Elk pode ser configurado para que possa partilhar conteúdos de outras aplicações, basta instalar Elk no seu dispositivo ou computador e iniciar sessão.",
|
||||||
|
"hint": "Para poder partilhar conteúdo com o Elk, este tem de estar instalado e você ter iniciado sessão.",
|
||||||
|
"title": "Partilhar com o Elk"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"attachments_exceed_server_limit": "O número de anexos excedeu o limite permitido por publicação.",
|
||||||
|
"attachments_limit_error": "Limite permitido por publicação excedido",
|
||||||
|
"edited": "(Editado)",
|
||||||
|
"editing": "Editando",
|
||||||
|
"loading": "Carregando...",
|
||||||
|
"publishing": "Publicando",
|
||||||
|
"upload_failed": "Falhou carregamento",
|
||||||
|
"uploading": "A carregar..."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"boosted_by": "Partilhada Por",
|
||||||
|
"edited": "Editada {0}",
|
||||||
|
"favourited_by": "Adicionada Aos Favoritos Por",
|
||||||
|
"filter_hidden_phrase": "Filtrada por",
|
||||||
|
"filter_removed_phrase": "Removida pelo filtro",
|
||||||
|
"filter_show_anyway": "Mostrar mesmo assim",
|
||||||
|
"img_alt": {
|
||||||
|
"desc": "Descrição",
|
||||||
|
"dismiss": "Dispensar"
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"count": "{0} votos|{0} voto|{0} votos",
|
||||||
|
"ends": "termina {0}",
|
||||||
|
"finished": "terminou {0}"
|
||||||
|
},
|
||||||
|
"reblogged": "{0} partilhou",
|
||||||
|
"replying_to": "Respondendo a {0}",
|
||||||
|
"show_full_thread": "Mostrar toda a conversa",
|
||||||
|
"someone": "alguém",
|
||||||
|
"spoiler_show_less": "Mostrar menos",
|
||||||
|
"spoiler_show_more": "Mostrar mais",
|
||||||
|
"thread": "Conversa",
|
||||||
|
"try_original_site": "Tentar o sítio original"
|
||||||
|
},
|
||||||
|
"status_history": {
|
||||||
|
"created": "criada {0}",
|
||||||
|
"edited": "editada {0}"
|
||||||
|
},
|
||||||
|
"tab": {
|
||||||
|
"for_you": "Para sí",
|
||||||
|
"hashtags": "Hashtags",
|
||||||
|
"media": "Media",
|
||||||
|
"news": "Notícias",
|
||||||
|
"notifications_all": "Todas",
|
||||||
|
"notifications_mention": "Menções",
|
||||||
|
"posts": "Publicações",
|
||||||
|
"posts_with_replies": "Publicações e Respostas"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"follow": "Seguir",
|
||||||
|
"follow_label": "Seguir hashtag {0}",
|
||||||
|
"unfollow": "Deixar de seguir",
|
||||||
|
"unfollow_label": "Deixar de seguir hashtag {0}"
|
||||||
|
},
|
||||||
|
"time_ago_options": {
|
||||||
|
"day_future": "em 0 dias|amanhã|em {n} diass",
|
||||||
|
"day_past": "0 dias atrás|ontem|{n} dias atrás",
|
||||||
|
"hour_future": "em 0 horas|em 1 hora|em {n} horas",
|
||||||
|
"hour_past": "0 horas atrás|1 hora atrás|{n} horas aatás",
|
||||||
|
"just_now": "agora mesmo",
|
||||||
|
"minute_future": "em 0 minutos|em 1 minuto|em {n} minutos",
|
||||||
|
"minute_past": "0 minutos atrás|1 minuto atrás|{n} minutos atrás",
|
||||||
|
"month_future": "em 0 mês|próximo mês|em {n} meses",
|
||||||
|
"month_past": "0 meses atrás|mês passado|{n} meses atrás",
|
||||||
|
"second_future": "agora mesmo|em {n} segundos|em {n} segundos",
|
||||||
|
"second_past": "agora mesmo|{n} segundo atrás|{n} segundos atrás",
|
||||||
|
"short_day_future": "em {n}d",
|
||||||
|
"short_day_past": "{n}d",
|
||||||
|
"short_hour_future": "em {n}h",
|
||||||
|
"short_hour_past": "{n}h",
|
||||||
|
"short_minute_future": "em {n}min",
|
||||||
|
"short_minute_past": "{n}min",
|
||||||
|
"short_month_future": "em {n}M",
|
||||||
|
"short_month_past": "{n}M",
|
||||||
|
"short_second_future": "em {n}s",
|
||||||
|
"short_second_past": "{n}s",
|
||||||
|
"short_week_future": "in {n}S",
|
||||||
|
"short_week_past": "{n}S",
|
||||||
|
"short_year_future": "in {n}A",
|
||||||
|
"short_year_past": "{n}A",
|
||||||
|
"week_future": "em 0 semanas|próxima semana|em {n} semanas",
|
||||||
|
"week_past": "0 semanas atrás|semana passada|{n} semanas atrás",
|
||||||
|
"year_future": "em 0 anos|próximo ano|em {n} anos",
|
||||||
|
"year_past": "0 anos atrás|ano passado|{n} anos atrás"
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"show_new_items": "Mostrar {v} novos itens|Mostrar {v} novo item|Mostrar {v} novos itens",
|
||||||
|
"view_older_posts": "Publicações antigas de outras instâncias podem não ser apresentadas."
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"federated_timeline": "Cronologia Federada",
|
||||||
|
"local_timeline": "Cronologia Local"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"add_content_warning": "Adicionar aviso de conteúdo",
|
||||||
|
"add_emojis": "Adicionar emojis",
|
||||||
|
"add_media": "Adicionar imagens, um video ou um ficheiro audio",
|
||||||
|
"add_publishable_content": "Adicionar conteúdo a publicar",
|
||||||
|
"change_content_visibility": "Alterar visibilidade do conteúdo",
|
||||||
|
"change_language": "Alterar idioma",
|
||||||
|
"emoji": "Emoji",
|
||||||
|
"explore_links_intro": "Estas notícias estão, neste momento, a ser faladas por pessoas neste e noutros servidores da rede descentralizada.",
|
||||||
|
"explore_posts_intro": "Estas publicações deste e de outros servidores na rede descentralizada estão, neste momento, a ganhar popularidade neste servidor.",
|
||||||
|
"explore_tags_intro": "Estes hashtags estão, neste momento, a ganhar popularidade entre as pessoas neste e noutros servidores da rede descentralizada.",
|
||||||
|
"toggle_code_block": "Alternar bloco de código"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"add_existing": "Adicionar uma conta existente",
|
||||||
|
"server_address_label": "Endereço do Servidor Mastodon",
|
||||||
|
"sign_in_desc": "Entre, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações, ou interagir a partir da sua conta de outro servidor.",
|
||||||
|
"sign_in_notice_title": "A visualizar os dados públicos de {0}",
|
||||||
|
"sign_out_account": "Desconectar {0}",
|
||||||
|
"tip_no_account": "Se ainda não tem uma conta Mastodon, {0}.",
|
||||||
|
"tip_register_account": "escolha um servidor e inscreva-se"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"direct": "Direta",
|
||||||
|
"direct_desc": "Visível apenas pelos utilizadores mencionados",
|
||||||
|
"private": "Apenas seguidores",
|
||||||
|
"private_desc": "Visível apenas pelos seus seguidores",
|
||||||
|
"public": "Publico",
|
||||||
|
"public_desc": "Visível por todos",
|
||||||
|
"unlisted": "Não listada",
|
||||||
|
"unlisted_desc": "Visível por todos, mas não incluida nas funcionalidades de divulgação"
|
||||||
|
}
|
||||||
|
}
|
479
locales/tr-TR.json
Normal file
479
locales/tr-TR.json
Normal file
|
@ -0,0 +1,479 @@
|
||||||
|
{
|
||||||
|
"a11y": {
|
||||||
|
"loading_page": "Sayfa yükleniyor, lütfen bekleyin",
|
||||||
|
"loading_titled_page": "Sayfa {0} yükleniyor, lütfen bekleyin",
|
||||||
|
"locale_changed": "Dil değiştirildi, yeni dil: {0}",
|
||||||
|
"locale_changing": "Dil değiştiriliyor, lütfen bekleyin",
|
||||||
|
"route_loaded": "Sayfa {0} yüklendi"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"avatar_description": "{0} avatarı",
|
||||||
|
"blocked_by": "Bu kullanıcı sizi engellemiş",
|
||||||
|
"blocked_domains": "Engellenen alan adları",
|
||||||
|
"blocked_users": "Engellenen kullanıcılar",
|
||||||
|
"blocking": "Engelli",
|
||||||
|
"bot": "BOT",
|
||||||
|
"favourites": "Favoriles",
|
||||||
|
"follow": "Takip et",
|
||||||
|
"follow_back": "Geri takip et",
|
||||||
|
"follow_requested": "İstek gönderildi",
|
||||||
|
"followers": "Takipçiler",
|
||||||
|
"followers_count": "{0} Takipçi",
|
||||||
|
"following": "Takip edilenler",
|
||||||
|
"following_count": "{0} takip edilen",
|
||||||
|
"follows_you": "Seni takip ediyor",
|
||||||
|
"go_to_profile": "Profile git",
|
||||||
|
"joined": "Katıldı",
|
||||||
|
"moved_title": "belirttiği yeni hesabı:",
|
||||||
|
"muted_users": "Susturulmuş kullanıcılar",
|
||||||
|
"muting": "Susturulmuş",
|
||||||
|
"mutuals": "Karşılıklı takip",
|
||||||
|
"pinned": "Sabitlendi",
|
||||||
|
"posts": "Gönderiler",
|
||||||
|
"posts_count": "{0} Gönderi",
|
||||||
|
"profile_description": "{0} profil başlığı",
|
||||||
|
"profile_unavailable": "Profil mevcut değil",
|
||||||
|
"unblock": "Engeli kaldır",
|
||||||
|
"unfollow": "Takibi bırak",
|
||||||
|
"unmute": "Susturulmayı kaldır"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"apply": "Uygula",
|
||||||
|
"bookmark": "Yer imlerine ekle",
|
||||||
|
"bookmarked": "Yer imlerine eklendi",
|
||||||
|
"boost": "Boost",
|
||||||
|
"boost_count": "{0}",
|
||||||
|
"boosted": "Boost edildi",
|
||||||
|
"clear_upload_failed": "Dosya yükleme hatalarını temizle",
|
||||||
|
"close": "Kapat",
|
||||||
|
"compose": "Oluştur",
|
||||||
|
"confirm": "Onayla",
|
||||||
|
"edit": "Düzenle",
|
||||||
|
"enter_app": "Uygulamaya gir",
|
||||||
|
"favourite": "Favorilere ekle",
|
||||||
|
"favourite_count": "{0}",
|
||||||
|
"favourited": "Favorilere eklendi",
|
||||||
|
"more": "Daha fazla",
|
||||||
|
"next": "Sonraki",
|
||||||
|
"prev": "Önceki",
|
||||||
|
"publish": "Yayımla",
|
||||||
|
"reply": "Cevap ver",
|
||||||
|
"reply_count": "{0}",
|
||||||
|
"reset": "Sıfırla",
|
||||||
|
"save": "Kaydet",
|
||||||
|
"save_changes": "Değişikleri kaydet",
|
||||||
|
"sign_in": "Giriş yap",
|
||||||
|
"switch_account": "Hesap değiştir",
|
||||||
|
"vote": "Oy ver"
|
||||||
|
},
|
||||||
|
"app_desc_short": "Hızlı bir Mastodon web istemcisi",
|
||||||
|
"app_logo": "Elk Logosu",
|
||||||
|
"app_name": "Elk",
|
||||||
|
"attachment": {
|
||||||
|
"edit_title": "Açıklama",
|
||||||
|
"remove_label": "Eki kaldır"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"activate": "Etkinleştir",
|
||||||
|
"complete": "Tamamla",
|
||||||
|
"compose_desc": "Yeni bir gönderi yaz",
|
||||||
|
"n-people-in-the-past-n-days": "geçen {1} gündeki {0} kişi",
|
||||||
|
"select_lang": "Dil seç",
|
||||||
|
"sign_in_desc": "Var olan bir hesap ekle",
|
||||||
|
"switch_account": "{0} hesabına geç",
|
||||||
|
"switch_account_desc": "Başka bir hesaba geç",
|
||||||
|
"toggle_dark_mode": "Karanlık mod durumunu değiştir",
|
||||||
|
"toggle_zen_mode": "Zen mod durumunu değiştir"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"confirm_dialog": {
|
||||||
|
"cancel": "Hayır",
|
||||||
|
"confirm": "Evet",
|
||||||
|
"title": "Emin misiniz?"
|
||||||
|
},
|
||||||
|
"end_of_list": "Listenin sonu",
|
||||||
|
"error": "HATA",
|
||||||
|
"in": "içinde",
|
||||||
|
"not_found": "404 Bulunamadı",
|
||||||
|
"offline_desc": "Çevrimdışısınız gibi görünüyor. Lütfen internet bağlantınızı kontrol edin."
|
||||||
|
},
|
||||||
|
"compose": {
|
||||||
|
"draft_title": "Taslak {0}",
|
||||||
|
"drafts": "Taslaklar ({v})"
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"with": "ile"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"account_not_found": "Hesap {0} bulunamadı",
|
||||||
|
"explore-list-empty": "Şu anda hiçbir şey trend değil. Daha sonra tekrar kontrol edin!",
|
||||||
|
"file_size_cannot_exceed_n_mb": "Dosya boyutu {0}MB'ı geçemez",
|
||||||
|
"sign_in_error": "Sunucuya bağlanılamadı.",
|
||||||
|
"status_not_found": "Gönderi bulunamadı",
|
||||||
|
"unsupported_file_format": "Desteklenmeyen dosya biçimi"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"desc_highlight": "Orada burada bir kaç hata ve eksik özellik bekleyin.",
|
||||||
|
"desc_para1": "Elk'i, bizim çalışması devam eden Mastodon web istemcimizi, denemedeki ilginiz için teşekkürler!",
|
||||||
|
"desc_para2": "Zaman içinde geliştirmek ve iyileştirmek için çok çalışıyoruz.",
|
||||||
|
"desc_para3": "Geliştirmemizi hızlandırmak için takıma Github Sponsors üzerinden sponsor olabilirsinizi. Umarız Elk'i beğenirsiniz!",
|
||||||
|
"desc_para4": "Elk açık kaynaklıdır. Test etmek, geri dönüş vermek veya katkıda bulunmak isterseniz,",
|
||||||
|
"desc_para5": "GitHub'da bize ulaşın",
|
||||||
|
"desc_para6": "ve dahil olun.",
|
||||||
|
"title": "Elk ön izlemede!"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"search": "Ara"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"block_account": "Engele {0}",
|
||||||
|
"block_domain": "Alan adı {0} engelle",
|
||||||
|
"copy_link_to_post": "Bu gönderinin linkini kopyala",
|
||||||
|
"delete": "Sil",
|
||||||
|
"delete_and_redraft": "Sil & yeniden taslak yap",
|
||||||
|
"delete_confirm": {
|
||||||
|
"cancel": "İptal et",
|
||||||
|
"confirm": "Sil",
|
||||||
|
"title": "Bu gönderiyi silmek istediğinizden emin misiniz?"
|
||||||
|
},
|
||||||
|
"direct_message_account": "{0} özel mesaj gönder",
|
||||||
|
"edit": "Düzenle",
|
||||||
|
"hide_reblogs": "{0} boostlarını gizle",
|
||||||
|
"mention_account": "{0} etiketle",
|
||||||
|
"mute_account": "{0} sustur",
|
||||||
|
"mute_conversation": "Bu gönderiyi sustur",
|
||||||
|
"open_in_original_site": "Orijinal sitede aç",
|
||||||
|
"pin_on_profile": "Profilde sabitle",
|
||||||
|
"share_post": "Bu gönderiyi paylaş",
|
||||||
|
"show_favourited_and_boosted_by": "Favoriye ekleyenleri ve boost edenleri göster",
|
||||||
|
"show_reblogs": "{0} boostlarını göster",
|
||||||
|
"show_untranslated": "Çevrilmemiş halini göster",
|
||||||
|
"toggle_theme": {
|
||||||
|
"dark": "Karanlık mod durumunu değiştir",
|
||||||
|
"light": "Aydınlık mod durumunu değiştir"
|
||||||
|
},
|
||||||
|
"translate_post": "Gönderiyi çevir",
|
||||||
|
"unblock_account": "{0} engelini kaldır",
|
||||||
|
"unblock_domain": "Alan adı {0} engelini kaldır",
|
||||||
|
"unmute_account": "{0} sesini aç",
|
||||||
|
"unmute_conversation": "Gönderinin sesini aç",
|
||||||
|
"unpin_on_profile": "Profildeki sabiti kaldır"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"back": "Geri git",
|
||||||
|
"blocked_domains": "Engellenen alan adları",
|
||||||
|
"blocked_users": "Engellenen kullanıcılar",
|
||||||
|
"bookmarks": "Yer imleri",
|
||||||
|
"built_at": "{0} derlendi",
|
||||||
|
"conversations": "Konuşmalar",
|
||||||
|
"explore": "Keşfet",
|
||||||
|
"favourites": "Favoriler",
|
||||||
|
"federated": "Federe",
|
||||||
|
"home": "Ev",
|
||||||
|
"local": "Yerel",
|
||||||
|
"muted_users": "Susturulmuş kullanıcılar",
|
||||||
|
"notifications": "Bildirimler",
|
||||||
|
"profile": "Profil",
|
||||||
|
"search": "Ara",
|
||||||
|
"select_feature_flags": "Özelliklerin Durumunu Değiştir",
|
||||||
|
"select_font_size": "Font Boyutu",
|
||||||
|
"select_language": "Görünüm Dili",
|
||||||
|
"settings": "Ayarlar",
|
||||||
|
"show_intro": "Girişi göster",
|
||||||
|
"toggle_theme": "Temayı Değiştir",
|
||||||
|
"zen_mode": "Zen Modu"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"favourited_post": "gönderini favoriledi",
|
||||||
|
"followed_you": "Seni takip etti",
|
||||||
|
"followed_you_count": "{0} kişi seni takip etti",
|
||||||
|
"missing_type": "EKSİK notification.type:",
|
||||||
|
"reblogged_post": "gönderini yeniden blogladı",
|
||||||
|
"request_to_follow": "Takip isteği attı",
|
||||||
|
"signed_up": "Kaydoldu",
|
||||||
|
"update_status": "Gönderisini güncelledi"
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"content_warning": "Uyarını buraya yaz",
|
||||||
|
"default_1": "Aklında ne var?",
|
||||||
|
"reply_to_account": "{0} cevap ver",
|
||||||
|
"replying": "Cevap veriliyor",
|
||||||
|
"the_thread": "konu"
|
||||||
|
},
|
||||||
|
"pwa": {
|
||||||
|
"dismiss": "Görmezden gel",
|
||||||
|
"title": "Yeni Elk güncellemesi mevcut!",
|
||||||
|
"update": "Güncelle",
|
||||||
|
"update_available_short": "Elk'i güncelle",
|
||||||
|
"webmanifest": {
|
||||||
|
"canary": {
|
||||||
|
"description": "Hızlı bir Mastodon web istemcisi (canary)",
|
||||||
|
"name": "Elk (canary)",
|
||||||
|
"short_name": "Elk (canary)"
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"description": "Hızlı bir Mastodon web istemcisi (dev)",
|
||||||
|
"name": "Elk (dev)",
|
||||||
|
"short_name": "Elk (dev)"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"description": "Hızlı bir Mastodon web istemcisi (preview)",
|
||||||
|
"name": "Elk (preview)",
|
||||||
|
"short_name": "Elk (preview)"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"description": "Hızlı bir Mastodon web istemcisi",
|
||||||
|
"name": "Elk",
|
||||||
|
"short_name": "Elk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"search_desc": "İnsanları & etiketleri ara",
|
||||||
|
"search_empty": "Bu arama için sonuç bulunamadı"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"about": {
|
||||||
|
"label": "Hakkında",
|
||||||
|
"meet_the_team": "Takım ile buluş",
|
||||||
|
"sponsor_action": "Bize spponsor ol",
|
||||||
|
"sponsor_action_desc": "Elk'i geliştiren takıma destek olmak için",
|
||||||
|
"sponsors": "Sponsorlar",
|
||||||
|
"sponsors_body_1": "Elk cömert sponsorluk ve şunların yardımı sayesinde mümkün oldu:",
|
||||||
|
"sponsors_body_2": "Ve Elk takımına ve üyelerine sponsor olan tüm şirketler ve şahıslar.",
|
||||||
|
"sponsors_body_3": "Eğer uygulamadan hoşlandıysanız bize sponsor olmayı düşünün:"
|
||||||
|
},
|
||||||
|
"account_settings": {
|
||||||
|
"description": "Mastodon UI'da hesap ayarlarını değiştir",
|
||||||
|
"label": "Hesap ayarları"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"color_mode": "Renk Modu",
|
||||||
|
"dark_mode": "Karanlık Mod",
|
||||||
|
"default": " (varsayılan)",
|
||||||
|
"font_size": "Font Boyutu",
|
||||||
|
"label": "Arayüz",
|
||||||
|
"light_mode": "Aydınlık Mod",
|
||||||
|
"size_label": {
|
||||||
|
"lg": "Büyük",
|
||||||
|
"md": "Orta",
|
||||||
|
"sm": "Küçük",
|
||||||
|
"xl": "Çok büyük",
|
||||||
|
"xs": "Çok küçük"
|
||||||
|
},
|
||||||
|
"system_mode": "Sistem"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"display_language": "Görünüm Dili",
|
||||||
|
"label": "Dil"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"label": "Bildirimler",
|
||||||
|
"notifications": {
|
||||||
|
"label": "Bildirim ayarları"
|
||||||
|
},
|
||||||
|
"push_notifications": {
|
||||||
|
"alerts": {
|
||||||
|
"favourite": "Favoriler",
|
||||||
|
"follow": "Yeni takipçiler",
|
||||||
|
"mention": "Bahsetmeler",
|
||||||
|
"poll": "Anketler",
|
||||||
|
"reblog": "Gönderinizi yeniden bloglamalar",
|
||||||
|
"title": "Hangi bildirimleri alacaksınız??"
|
||||||
|
},
|
||||||
|
"description": "Elk'i kullanmıyorken bile bildirimleri alın.",
|
||||||
|
"instructions": "@:settings.notifications.push_notifications.save_settings butonunu kullanarak değişikleri kaydetmeyi unutmayın!",
|
||||||
|
"label": "Anlık bildirim ayarları",
|
||||||
|
"policy": {
|
||||||
|
"all": "Herkesden",
|
||||||
|
"followed": "Takip ettiğim kişilerden",
|
||||||
|
"follower": "Takipçilerden",
|
||||||
|
"none": "Kimseden",
|
||||||
|
"title": "Kimden bildirim alabilirim??"
|
||||||
|
},
|
||||||
|
"save_settings": "Ayarları kaydet",
|
||||||
|
"subscription_error": {
|
||||||
|
"clear_error": "Hatayı temizle",
|
||||||
|
"permission_denied": "Erişim engellendi: tarayıcınızda bildirimleri etkinleştirin.",
|
||||||
|
"request_error": "Abonelik talep edilirken bir hata oluştu, tekrar deneyin ve hata devam ederse lütfen sorunu Elk deposuna bildirin.",
|
||||||
|
"title": "Anlık bildirimlere abone olunamadı",
|
||||||
|
"too_many_registrations": "Tarayıcı kısıtlamaları nedeniyle Elk, farklı sunuculardaki birden çok hesap için anlık bildirimler hizmetini kullanamaz. Başka bir hesaptaki anlık bildirim aboneliğinden çıkmalı ve tekrar denemelisiniz."
|
||||||
|
},
|
||||||
|
"title": "Anlık bildirim ayarları",
|
||||||
|
"undo_settings": "Değişiklikleri geri al",
|
||||||
|
"unsubscribe": "Anlık bildirimleri devre dışı bırak",
|
||||||
|
"unsupported": "Tarayıcınız anlık bildirimleri desteklemiyor.",
|
||||||
|
"warning": {
|
||||||
|
"enable_close": "Kapat",
|
||||||
|
"enable_description": "Elk açık değilken bildirim almak için anlık bildirimleri etkinleştirin. Etkinleştirildikten sonra yukarıdaki \"@:settings.notifications.show_btn{'\"'} düğmesini kullanarak tam olarak ne tür etkileşimlerin anlık bildirimler oluşturduğunu kontrol edebilirsiniz.",
|
||||||
|
"enable_description_desktop": "Elk açık değilken bildirim almak için anlık bildirimleri etkinleştirin. Etkinleştirildikten sonra \"Ayarlar > Bildirimler > Anlık bildirim ayarları\"nda hangi tür etkileşimlerin anlık bildirimler oluşturduğunu tam olarak kontrol edebilirsiniz.",
|
||||||
|
"enable_description_mobile": "Ayarlara \"Ayarlar > Bildirimler > Anlık bildirim ayarları\" gezinme menüsünü kullanarak da erişebilirsiniz.",
|
||||||
|
"enable_description_settings": "Elk açık değilken bildirim almak için anlık bildirimleri etkinleştirin. Etkinleştirdikten sonra, aynı ekranda hangi tür etkileşimlerin anlık bildirimleri oluşturduğunu tam olarak kontrol edebileceksiniz.",
|
||||||
|
"enable_desktop": "Anlık bildirimleri etkinleştir",
|
||||||
|
"enable_title": "Hiçbirşeyi kaçırma",
|
||||||
|
"re_auth": "Görünüşe göre sunucunuz anlık bildirimleri desteklemiyor. Çıkış yapmayı deneyin ve tekrar giriş yapın, bu mesaj hala görünüyorsa sunucu yöneticinizle iletişime geçin."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"show_btn": "Bildirim ayarlarına git"
|
||||||
|
},
|
||||||
|
"notifications_settings": "Bildirimler",
|
||||||
|
"preferences": {
|
||||||
|
"github_cards": "GitHub Cards",
|
||||||
|
"hide_boost_count": "Boost sayısını gizle",
|
||||||
|
"hide_favorite_count": "Favori sayısını gizle",
|
||||||
|
"hide_follower_count": "Takipçi sayısını gizle",
|
||||||
|
"label": "Ayarlar",
|
||||||
|
"title": "Deneysel Özellikler",
|
||||||
|
"user_picker": "Kullanıcı Seçici",
|
||||||
|
"virtual_scroll": "Görsel Kaydırma"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"appearance": {
|
||||||
|
"bio": "Açıklama",
|
||||||
|
"description": "avatar, kullanıcı adı, profil vb. düzenle",
|
||||||
|
"display_name": "Görünen ad",
|
||||||
|
"label": "Görünüm",
|
||||||
|
"profile_metadata": "Profil üstverisi",
|
||||||
|
"profile_metadata_desc": "Profilinizde bir tablo olarak görüntülenen en fazla {0} öğeye sahip olabilirsiniz.",
|
||||||
|
"title": "Profili düzenle"
|
||||||
|
},
|
||||||
|
"featured_tags": {
|
||||||
|
"description": "İnsanlar bu etiketlerler altında herkese açık gönderilerinize göz atabilir.",
|
||||||
|
"label": "Öne çıkan etiketler"
|
||||||
|
},
|
||||||
|
"label": "Profil"
|
||||||
|
},
|
||||||
|
"select_a_settings": "Bir ayar seç",
|
||||||
|
"users": {
|
||||||
|
"export": "Kullanıcı Tokenlerini Dışa Aktar",
|
||||||
|
"import": "Kullanıcı Tokenlerini İçe Aktar",
|
||||||
|
"label": "Giriş yapılan kullanıcılar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"attachments_exceed_server_limit": "Ek sayısı gönderi başına sınırı aştı.",
|
||||||
|
"attachments_limit_error": "Gönderi başına sınır aşıldı",
|
||||||
|
"edited": "(Düzenlendi)",
|
||||||
|
"editing": "Düzenleniyor",
|
||||||
|
"loading": "Yükleniyor...",
|
||||||
|
"publishing": "Yayımlanıyor",
|
||||||
|
"upload_failed": "Yükleme başarısız",
|
||||||
|
"uploading": "Yükleniyor..."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"boosted_by": "Tarafından boostlandı:",
|
||||||
|
"edited": "Düzenlendi {0}",
|
||||||
|
"favourited_by": "Tarafından favorilendi:",
|
||||||
|
"filter_hidden_phrase": "Tarafından filtrelendi:",
|
||||||
|
"filter_removed_phrase": "Filtre tarafından silindi",
|
||||||
|
"filter_show_anyway": "Yine de göster",
|
||||||
|
"img_alt": {
|
||||||
|
"desc": "Açıklama",
|
||||||
|
"dismiss": "Görmezden gel"
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"count": "{0} oy",
|
||||||
|
"ends": "{0} biter",
|
||||||
|
"finished": "{0} bitti"
|
||||||
|
},
|
||||||
|
"reblogged": "{0} yeniden blogladı",
|
||||||
|
"replying_to": "{0} cevap veriliyor",
|
||||||
|
"show_full_thread": "Tüm konuyu göster",
|
||||||
|
"someone": "biri",
|
||||||
|
"spoiler_show_less": "Daha az göster",
|
||||||
|
"spoiler_show_more": "Daha çok göster",
|
||||||
|
"thread": "Konu",
|
||||||
|
"try_original_site": "Orijinal siteyi dene"
|
||||||
|
},
|
||||||
|
"status_history": {
|
||||||
|
"created": "{0} oluşturuldu",
|
||||||
|
"edited": "{0} düzenlendi"
|
||||||
|
},
|
||||||
|
"tab": {
|
||||||
|
"for_you": "Senin için",
|
||||||
|
"hashtags": "Etiketler",
|
||||||
|
"media": "Medya",
|
||||||
|
"news": "Haberler",
|
||||||
|
"notifications_all": "Hepsi",
|
||||||
|
"notifications_mention": "Bahsetmeler",
|
||||||
|
"posts": "Gönderiler",
|
||||||
|
"posts_with_replies": "Gönderiler & Yanıtlar"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"follow": "Takip et",
|
||||||
|
"follow_label": "{0} etiketini takip et",
|
||||||
|
"unfollow": "Takibi bırak",
|
||||||
|
"unfollow_label": "{0} etiketini takibi bırak"
|
||||||
|
},
|
||||||
|
"time_ago_options": {
|
||||||
|
"day_future": "{n} gün içinde",
|
||||||
|
"day_past": "{n} gün önce",
|
||||||
|
"hour_future": "{n} saat içinde",
|
||||||
|
"hour_past": "{n} saat önce",
|
||||||
|
"just_now": "şimdi",
|
||||||
|
"minute_future": "{n} dakika içinde",
|
||||||
|
"minute_past": "{n} dakika önce",
|
||||||
|
"month_future": "{n} ay içinde",
|
||||||
|
"month_past": "{n} ay önce",
|
||||||
|
"second_future": "şimdi|{n} saniye içinde",
|
||||||
|
"second_past": "şimdi|{n} saniye önce",
|
||||||
|
"short_day_future": "{n} günde",
|
||||||
|
"short_day_past": "{n}d",
|
||||||
|
"short_hour_future": "{n} saatte",
|
||||||
|
"short_hour_past": "{n}h",
|
||||||
|
"short_minute_future": "{n} dakikada",
|
||||||
|
"short_minute_past": "{n}min",
|
||||||
|
"short_month_future": "{n} ayda",
|
||||||
|
"short_month_past": "{n}mo",
|
||||||
|
"short_second_future": "{n} saniyede",
|
||||||
|
"short_second_past": "{n}s",
|
||||||
|
"short_week_future": "{n} haftada",
|
||||||
|
"short_week_past": "{n}w",
|
||||||
|
"short_year_future": "{n} yılda",
|
||||||
|
"short_year_past": "{n}y",
|
||||||
|
"week_future": "{n} hafta içinde",
|
||||||
|
"week_past": "{n} hafta önce",
|
||||||
|
"year_future": "{n} yıl içinde",
|
||||||
|
"year_past": "{n} yıl önce"
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"show_new_items": "{v} yeni öğe göster",
|
||||||
|
"view_older_posts": "Diğer sunuculardan eski gönderiler görüntülenmeyebilir."
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"federated_timeline": "Federe Edilmiş Zaman Akışı",
|
||||||
|
"local_timeline": "Yerel Zaman Akışı"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"add_content_warning": "İçerik uyarısı ekle",
|
||||||
|
"add_emojis": "Emoji ekle",
|
||||||
|
"add_media": "resim, video yada ses dosyası ekle",
|
||||||
|
"add_publishable_content": "Yayımlanacak içerik ekle",
|
||||||
|
"change_content_visibility": "İçerik görünürlüğünü değiştir",
|
||||||
|
"change_language": "Dil değiştir",
|
||||||
|
"emoji": "Emoji",
|
||||||
|
"explore_links_intro": "Bu haberler, şu anda merkezi olmayan ağın bu ve diğer sunucularındaki insanlar tarafından konuşuluyor.",
|
||||||
|
"explore_posts_intro": "Bu gönderiler, şu anda merkezi olmayan ağın bu ve diğer sunucularındaki insanlar tarafından ilgi görüyor.",
|
||||||
|
"explore_tags_intro": "Bu etiketler, şu anda merkezi olmayan ağın bu ve diğer sunucularındaki insanlar tarafından ilgi görüyor.",
|
||||||
|
"toggle_code_block": "Kod bloğu durumunu değiştir"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"add_existing": "Var olan bir hesap ekle",
|
||||||
|
"server_address_label": "Mastodon Sunucu Adresi",
|
||||||
|
"sign_in_desc": "Profilleri veya hashtag'leri takip etmek, favorilere eklemek, gönderileri paylaşmak ve yanıtlamak veya farklı bir sunucudaki hesabınızdan etkileşim kurmak için oturum açın.",
|
||||||
|
"sign_in_notice_title": "{0} herkese açık veri görüntüleniyor",
|
||||||
|
"sign_out_account": "{0} çıkış yap",
|
||||||
|
"tip_no_account": "Eğer bir Mastodon hesabınız yoksa, {0}.",
|
||||||
|
"tip_register_account": "sunucunuzu seçin ve kaydolun"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"direct": "Direkt",
|
||||||
|
"direct_desc": "Sadece bahsedilen kullanıcılara görünür",
|
||||||
|
"private": "Sadece takipçiler",
|
||||||
|
"private_desc": "Sadece takipçilere görünür",
|
||||||
|
"public": "Herkese açık",
|
||||||
|
"public_desc": "Herkese görünür",
|
||||||
|
"unlisted": "Liste dışı",
|
||||||
|
"unlisted_desc": "Herkes tarafından görülebilir, ancak keşif özellikleri devre dışı bırakılmıştır"
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,9 @@
|
||||||
"profile_unavailable": "个人资料不可见",
|
"profile_unavailable": "个人资料不可见",
|
||||||
"unblock": "取消拉黑",
|
"unblock": "取消拉黑",
|
||||||
"unfollow": "取消关注",
|
"unfollow": "取消关注",
|
||||||
"unmute": "取消屏蔽"
|
"unmute": "取消屏蔽",
|
||||||
|
"view_other_followers": "其他站点上的关注者可能不会在这里显示。",
|
||||||
|
"view_other_following": "其他站点上正在关注的人可能不会在这里显示。"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"apply": "应用",
|
"apply": "应用",
|
||||||
|
@ -63,7 +65,7 @@
|
||||||
"switch_account": "切换帐号",
|
"switch_account": "切换帐号",
|
||||||
"vote": "投票"
|
"vote": "投票"
|
||||||
},
|
},
|
||||||
"app_desc_short": "用 🧡 制作的 Mastodon 客户端",
|
"app_desc_short": "一个灵巧的 Mastodon 客户端",
|
||||||
"app_logo": "应用图标",
|
"app_logo": "应用图标",
|
||||||
"app_name": "鹿鸣",
|
"app_name": "鹿鸣",
|
||||||
"attachment": {
|
"attachment": {
|
||||||
|
@ -86,7 +88,7 @@
|
||||||
"confirm_dialog": {
|
"confirm_dialog": {
|
||||||
"cancel": "否",
|
"cancel": "否",
|
||||||
"confirm": "是",
|
"confirm": "是",
|
||||||
"title": "你确定吗?"
|
"title": "你确定 {0} 吗?"
|
||||||
},
|
},
|
||||||
"end_of_list": "列表到底啦",
|
"end_of_list": "列表到底啦",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
|
@ -162,6 +164,7 @@
|
||||||
"blocked_users": "已拉黑的用户",
|
"blocked_users": "已拉黑的用户",
|
||||||
"bookmarks": "书签",
|
"bookmarks": "书签",
|
||||||
"built_at": "构建于 {0}",
|
"built_at": "构建于 {0}",
|
||||||
|
"compose": "撰写",
|
||||||
"conversations": "私信",
|
"conversations": "私信",
|
||||||
"explore": "探索",
|
"explore": "探索",
|
||||||
"favourites": "喜欢",
|
"favourites": "喜欢",
|
||||||
|
@ -204,22 +207,22 @@
|
||||||
"update_available_short": "更新鹿鸣",
|
"update_available_short": "更新鹿鸣",
|
||||||
"webmanifest": {
|
"webmanifest": {
|
||||||
"canary": {
|
"canary": {
|
||||||
"description": "用 🧡 制作的 Mastodon 客户端(Canary)",
|
"description": "一个灵巧的 Mastodon 客户端(Canary)",
|
||||||
"name": "鹿鸣 Canary",
|
"name": "鹿鸣 Canary",
|
||||||
"short_name": "鹿鸣 Canary"
|
"short_name": "鹿鸣 Canary"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"description": "用 🧡 制作的 Mastodon 客户端(开发版)",
|
"description": "一个灵巧的 Mastodon 客户端(开发版)",
|
||||||
"name": "鹿鸣 开发版",
|
"name": "鹿鸣 开发版",
|
||||||
"short_name": "鹿鸣 开发版"
|
"short_name": "鹿鸣 开发版"
|
||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"description": "用 🧡 制作的 Mastodon 客户端(预览版)",
|
"description": "一个灵巧的 Mastodon 客户端(预览版)",
|
||||||
"name": "鹿鸣 预览版",
|
"name": "鹿鸣 预览版",
|
||||||
"short_name": "鹿鸣 预览版"
|
"short_name": "鹿鸣 预览版"
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
"description": "用 🧡 制作的 Mastodon 客户端",
|
"description": "一个灵巧的 Mastodon 客户端",
|
||||||
"name": "鹿鸣",
|
"name": "鹿鸣",
|
||||||
"short_name": "鹿鸣"
|
"short_name": "鹿鸣"
|
||||||
}
|
}
|
||||||
|
@ -237,26 +240,21 @@
|
||||||
"description": "在 Mastodon UI 中编辑你的账号设置",
|
"description": "在 Mastodon UI 中编辑你的账号设置",
|
||||||
"label": "账号设置"
|
"label": "账号设置"
|
||||||
},
|
},
|
||||||
"feature_flags": {
|
|
||||||
"github_cards": "GitHub 卡片",
|
|
||||||
"title": "实验功能",
|
|
||||||
"user_picker": "用户选择器",
|
|
||||||
"virtual_scroll": "虚拟滚动"
|
|
||||||
},
|
|
||||||
"interface": {
|
"interface": {
|
||||||
"color_mode": "颜色",
|
"color_mode": "颜色",
|
||||||
"dark_mode": "深色模式",
|
"dark_mode": "深色",
|
||||||
"default": "(默认)",
|
"default": "(默认)",
|
||||||
"font_size": "字号",
|
"font_size": "字号",
|
||||||
"label": "外观",
|
"label": "外观",
|
||||||
"light_mode": "浅色模式",
|
"light_mode": "浅色",
|
||||||
"size_label": {
|
"size_label": {
|
||||||
"lg": "大",
|
"lg": "大",
|
||||||
"md": "中",
|
"md": "中",
|
||||||
"sm": "小",
|
"sm": "小",
|
||||||
"xl": "特大",
|
"xl": "特大",
|
||||||
"xs": "特小"
|
"xs": "特小"
|
||||||
}
|
},
|
||||||
|
"system_mode": "跟随系统"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "首选语言",
|
"display_language": "首选语言",
|
||||||
|
@ -312,7 +310,14 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "通知",
|
"notifications_settings": "通知",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"label": "首选项"
|
"github_cards": "GitHub 卡片",
|
||||||
|
"hide_boost_count": "隐藏转发数",
|
||||||
|
"hide_favorite_count": "隐藏收藏数",
|
||||||
|
"hide_follower_count": "隐藏关注者数",
|
||||||
|
"label": "首选项",
|
||||||
|
"title": "实验功能",
|
||||||
|
"user_picker": "用户选择器",
|
||||||
|
"virtual_scroll": "虚拟滚动"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -432,6 +437,7 @@
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"add_content_warning": "添加内容警告标识",
|
"add_content_warning": "添加内容警告标识",
|
||||||
|
"add_emojis": "添加表情符号",
|
||||||
"add_media": "添加图片、视频或者音频文件",
|
"add_media": "添加图片、视频或者音频文件",
|
||||||
"add_publishable_content": "添加要发布的内容",
|
"add_publishable_content": "添加要发布的内容",
|
||||||
"change_content_visibility": "修改内容是否可见",
|
"change_content_visibility": "修改内容是否可见",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue