forked from Mirrors/elk
Compare commits
146 commits
renovate/m
...
main
Author | SHA1 | Date | |
---|---|---|---|
b217b4dc25 | |||
f2edec194f | |||
087f8cc992 | |||
6efbf8768b | |||
cd934fcd55 | |||
7f1e97a37c | |||
58c84b2f9b | |||
322ffeef12 | |||
4a8110a52e | |||
d481c0ae7a | |||
68ffb627e7 | |||
b221687139 | |||
7b10620cca | |||
498face3cf | |||
672ff46072 | |||
81b5f8b8e7 | |||
721e55be06 | |||
eba4cc0757 | |||
80818ed52d | |||
|
25fb7c1c97 | ||
|
839aa52e86 | ||
|
9ff55289ea | ||
|
73293fbcd3 | ||
|
a27c218802 | ||
|
f8fc0efadc | ||
|
618a5b2df3 | ||
|
1146dca5f6 | ||
|
f86e856ee6 | ||
|
6d13d61227 | ||
|
0de9825bf2 | ||
|
3f0b234cc4 | ||
|
8f04ea8eee | ||
|
7dcafa3fe0 | ||
|
bead2183b2 | ||
|
59dda09cd4 | ||
|
d0b115751f | ||
|
c6787aae3f | ||
|
9025416ab3 | ||
|
aa28257754 | ||
|
d807e06fa0 | ||
|
611d556936 | ||
|
4313002950 | ||
|
de11a60b17 | ||
|
5064b269e7 | ||
|
d8d9975756 | ||
|
eee671cdc3 | ||
|
587c063aba | ||
|
28514e956d | ||
|
42aeb8fa35 | ||
|
f6f50a582e | ||
|
f86818867b | ||
|
82d962a54b | ||
|
1b189043e4 | ||
|
a4867566d9 | ||
|
0757db69b2 | ||
|
f0de25c992 | ||
|
660549b08b | ||
|
7807730118 | ||
|
b526db0860 | ||
|
0133324ded | ||
|
e9ab0cd40b | ||
|
9251ec496b | ||
|
bd4cd02b2b | ||
|
74ccfece5d | ||
35b4c80311 | |||
f6a081d199 | |||
5e419045ed | |||
cfdbb66b96 | |||
c717aace86 | |||
6713329a5e | |||
7470468077 | |||
9044b3bcad | |||
5bd9dce91e | |||
12e14cfc05 | |||
1ea4399765 | |||
991cb812da | |||
|
c89e499f96 | ||
|
89e3582dd7 | ||
|
48c013709a | ||
|
f90f0a2e61 | ||
|
c58b585855 | ||
|
ded2e0f3d7 | ||
9b77efe1d7 | |||
aec670a903 | |||
1218f92133 | |||
7c0986a005 | |||
ac01207283 | |||
ef169d682f | |||
|
21d5633233 | ||
|
7703565c75 | ||
|
5a9546ec0a | ||
|
bc30a8bd82 | ||
|
c432c2bd0d | ||
|
364fbd350b | ||
|
c64580f782 | ||
|
e7dfdafd59 | ||
|
b06ec9356d | ||
|
3b1a66c93c | ||
|
ed8a1811cc | ||
|
dfbe2e080d | ||
|
0fd9374e8c | ||
|
1c8e48bee4 | ||
|
3448335356 | ||
|
4954473f50 | ||
|
efa17caf5e | ||
|
df165f0023 | ||
|
0f583ece28 | ||
|
d579977790 | ||
|
8786c83db7 | ||
|
1ce913e69d | ||
|
48a8b74e7c | ||
|
1ff13952b0 | ||
|
02f7c4b291 | ||
|
9da77637b2 | ||
|
62f70250d5 | ||
|
873c62e9ef | ||
|
b1ff1e6277 | ||
|
f644148844 | ||
|
3120bbb77f | ||
|
6cbe65c9d8 | ||
|
1c908363cb | ||
|
c01a15c930 | ||
|
0c15aa55d8 | ||
|
9f04e17e57 | ||
|
308b50cbad | ||
|
e44833b18a | ||
|
0fa87f71a4 | ||
|
edfbe2c3ed | ||
|
70c7e93919 | ||
|
95e466146d | ||
|
efec212a9f | ||
|
1844af0a41 | ||
|
72b80d4984 | ||
|
6dc5a68c80 | ||
|
310b32c123 | ||
|
748dd5e19f | ||
|
c00d6f7bf8 | ||
|
fc5d248094 | ||
|
6f20ce5bba | ||
|
edcc8741bf | ||
|
3584151fab | ||
|
efb6967e6a | ||
|
eddbb1eee9 | ||
|
6b40319723 | ||
|
913e2892f7 | ||
|
a3c5272e07 |
156 changed files with 7120 additions and 3620 deletions
|
@ -1,15 +0,0 @@
|
|||
*.css
|
||||
*.png
|
||||
*.ico
|
||||
*.toml
|
||||
*.patch
|
||||
*.txt
|
||||
Dockerfile
|
||||
public/
|
||||
public-dev/
|
||||
public-staging/
|
||||
https-dev-config/localhost.crt
|
||||
https-dev-config/localhost.key
|
||||
Dockerfile
|
||||
elk-translation-status.json
|
||||
docs/translation-status.json
|
19
.eslintrc
19
.eslintrc
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"extends": "@antfu",
|
||||
"ignorePatterns": ["!pages/public"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["locales/**.json"],
|
||||
"rules": {
|
||||
"jsonc/sort-keys": "error"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"vue/no-restricted-syntax":["error", {
|
||||
"selector": "VElement[name='a']",
|
||||
"message": "Use NuxtLink instead."
|
||||
}],
|
||||
"n/prefer-global/process": "off"
|
||||
}
|
||||
}
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -2,4 +2,4 @@
|
|||
name: 🚀 New feature proposal
|
||||
about: Propose a new feature
|
||||
labels: 's: pending triage'
|
||||
---
|
||||
---
|
||||
|
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
|
@ -32,6 +32,7 @@ jobs:
|
|||
|
||||
- name: 🧪 Test project
|
||||
run: pnpm test:ci
|
||||
timeout-minutes: 10
|
||||
|
||||
- name: 📝 Lint
|
||||
run: pnpm lint
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
|||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.metal.outputs.tags }}
|
||||
labels: ${{ steps.metal.outputs.labels }}
|
||||
|
|
45
.vscode/settings.json
vendored
45
.vscode/settings.json
vendored
|
@ -5,10 +5,6 @@
|
|||
"unmute",
|
||||
"unstorage"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.formatOnSave": false,
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
},
|
||||
|
@ -23,7 +19,44 @@
|
|||
"i18n-ally.preferredDelimiter": "_",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"volar.completion.preferredTagNameCase": "pascal",
|
||||
"volar.completion.preferredAttrNameCase": "kebab"
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off" },
|
||||
{ "rule": "*-indent", "severity": "off" },
|
||||
{ "rule": "*-spacing", "severity": "off" },
|
||||
{ "rule": "*-spaces", "severity": "off" },
|
||||
{ "rule": "*-order", "severity": "off" },
|
||||
{ "rule": "*-dangle", "severity": "off" },
|
||||
{ "rule": "*-newline", "severity": "off" },
|
||||
{ "rule": "*quotes", "severity": "off" },
|
||||
{ "rule": "*semi", "severity": "off" }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
]
|
||||
}
|
||||
|
|
22
.woodpecker.yml
Normal file
22
.woodpecker.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
steps:
|
||||
- name: build docker
|
||||
image: docker:25-cli
|
||||
secrets: [user, pass]
|
||||
commands:
|
||||
- apk add git
|
||||
- REPO=$(echo "$CI_REPO" | tr '[:upper:]' '[:lower:]')
|
||||
- REGISTRY="dev.cat-enby.club"
|
||||
- MAJOR=$(echo ${CI_COMMIT_TAG} | cut -d '.' -f 1 | tr -d 'v')
|
||||
- MINOR=$(echo ${CI_COMMIT_TAG} | cut -d '.' -f 2)
|
||||
- PATCH=$(echo ${CI_COMMIT_TAG} | cut -d '.' -f 3 | cut -d '-' -f 1)
|
||||
- docker buildx build -t $${REGISTRY}/$${REPO}:v$${MAJOR:-0}.$${MINOR:-0}.$${PATCH-0} -t $${REGISTRY}/$${REPO}:v$${MAJOR:-0}.$${MINOR} -t $${REGISTRY}/$$REPO:v$${MAJOR:-0} -t $${REGISTRY}/$$REPO:latest .
|
||||
- docker login --username $USER --password $PASS $${REGISTRY}
|
||||
- docker push $${REGISTRY}/$${REPO}:v$${MAJOR:-0}.$${MINOR:-0}.$${PATCH-0}
|
||||
- docker push $${REGISTRY}/$${REPO}:v$${MAJOR:-0}.$${MINOR}
|
||||
- docker push $${REGISTRY}/$${REPO}:v$${MAJOR:-0}
|
||||
- docker push $${REGISTRY}/$${REPO}:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
when:
|
||||
- repo: nikurasu:elk-test-ci
|
||||
- event: tag
|
|
@ -8,7 +8,7 @@ For guidelines on contributing to the documentation, refer to the [docs README](
|
|||
|
||||
### Online
|
||||
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
|
||||
[![Open in Codeflow](https://developer.stackblitz.com/img/open_in_codeflow.svg)](https://pr.new/elk-zone/elk)
|
||||
|
||||
|
@ -21,7 +21,6 @@ To develop and test the Elk package:
|
|||
2. Ensure using the latest Node.js (16.x).
|
||||
If you have [nvm](https://github.com/nvm-sh/nvm), you can run `nvm i` to install the required version.
|
||||
|
||||
|
||||
3. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/) v7. To use it you must first enable [Corepack](https://github.com/nodejs/corepack) by running `corepack enable`. (Note: on Linux in a standard Node 16+ environment, you should follow the instructions to install via Node's `corepack` rather than using the `curl` command)
|
||||
|
||||
4. Check out a branch where you can work and commit your changes:
|
||||
|
@ -84,7 +83,7 @@ Simple approach used by most websites of relying on direction set in HTML elemen
|
|||
We've added some `UnoCSS` utilities styles to help you with that:
|
||||
- Do not use `left/right` padding and margin: for example `pl-1`. Use `padding-inline-start/end` instead. So `pl-1` should be `ps-1`, `pr-1` should be `pe-1`. The same rules apply to margin.
|
||||
- Do not use `rtl-` classes, such as `rtl-left-0`.
|
||||
- For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`, such as timeline, and is the only exception from the rule above. For icons inside the timeline, it might not work as expected.
|
||||
- For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`, such as timeline, and is the only exception to the rule above. For icons inside the timeline, it might not work as expected.
|
||||
- For absolute positioned elements, don't use `left/right`: for example `left-0`. Use `inset-inline-start/end` instead. `UnoCSS` shortcuts are `inset-is` for `inset-inline-start` and `inset-ie` for `inset-inline-end`. Example: `left-0` should be replaced with `inset-is-0`.
|
||||
- If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-ie-5`.
|
||||
- If you need to change the border radius for one corner, use `border-start-end-radius` and similar rules. `UnoCSS` shortcuts are `rounded` + top/bottom as either `-bs` (top) or `-be` (bottom) + left/right as either `-is` (left) or `-ie` (right). Example: `rounded-tl-0` should be replaced with `rounded-bs-is-0`.
|
||||
|
|
11
README.md
11
README.md
|
@ -39,8 +39,8 @@ The Elk team maintains a deployment at:
|
|||
|
||||
### Self-Host Docker Deployment
|
||||
|
||||
In order to host Elk yourself you can use the provided Dockerfile to build a container with elk. Be aware, that Elk only loads properly if the connection is done via SSL/TLS. The Docker container itself does not provide any SSL/TLS handling. You'll have to add this bit yourself.
|
||||
One could put Elk behind popular reverse proxies with SSL Handling like Traefik, NGINX etc.
|
||||
In order to host Elk yourself you can use the provided Dockerfile to build a container with elk. Be aware, that Elk only loads properly if the connection is done via SSL/TLS. The Docker container itself does not provide any SSL/TLS handling. You'll have to add this bit yourself.
|
||||
One could put Elk behind popular reverse proxies with SSL Handling like Traefik, NGINX etc.
|
||||
|
||||
1. checkout source ```git clone https://github.com/elk-zone/elk.git```
|
||||
1. got into new source dir: ```cd elk```
|
||||
|
@ -52,7 +52,6 @@ One could put Elk behind popular reverse proxies with SSL Handling like Traefik,
|
|||
> [!NOTE]
|
||||
> The provided Dockerfile creates a container which will eventually run Elk as non-root user and create a persistent named Docker volume upon first start (if that volume does not yet exist). This volume is always created with root permission. Failing to change the permissions of ```/elk/data``` inside this volume to UID:GID 911 (as specified for Elk in the Dockerfile) will prevent Elk from storing it's config for user accounts. You either have to fix the permission in the created named volume, or mount a directory with the correct permission to ```/elk/data``` into the container.
|
||||
|
||||
|
||||
### 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:
|
||||
|
@ -107,7 +106,7 @@ We're really excited that you're interested in contributing to Elk! Before submi
|
|||
|
||||
### Online
|
||||
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
|
||||
[![Open in Codeflow](https://developer.stackblitz.com/img/open_in_codeflow.svg)](https://pr.new/elk-zone/elk)
|
||||
|
||||
|
@ -158,8 +157,8 @@ You can consult the [PWA documentation](https://docs.elk.zone/pwa) to learn more
|
|||
## 👨💻 Contributors
|
||||
|
||||
<a href="https://github.com/elk-zone/elk/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=elk-zone/elk" />
|
||||
</a>
|
||||
<img src="https://contrib.rocks/image?repo=elk-zone/elk" />
|
||||
</a>
|
||||
|
||||
## 📄 License
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ const { t } = useI18n()
|
|||
const isSelf = useSelfAccount(() => account)
|
||||
const enable = computed(() => !isSelf.value && currentUser.value)
|
||||
const relationship = computed(() => props.relationship || useRelationship(account).value)
|
||||
const isLoading = computed(() => relationship.value === undefined)
|
||||
|
||||
const { client } = useMasto()
|
||||
|
||||
|
@ -62,6 +63,10 @@ const buttonStyle = computed(() => {
|
|||
if (relationship.value ? relationship.value.following : context === 'following')
|
||||
return `text-base ${relationship.value?.followedBy ? 'border-strong' : 'border-base'}`
|
||||
|
||||
// If loading, use a plain style
|
||||
if (isLoading.value)
|
||||
return 'text-base border-base'
|
||||
|
||||
// If not following, use a button style
|
||||
return 'text-inverted bg-primary border-primary'
|
||||
})
|
||||
|
@ -77,28 +82,33 @@ const buttonStyle = computed(() => {
|
|||
:hover="!relationship?.blocking && !relationship?.muting && relationship?.following ? 'border-red text-red' : 'bg-base border-primary text-primary'"
|
||||
@click="relationship?.blocking ? unblock() : relationship?.muting ? unmute() : toggleFollowAccount(relationship!, account)"
|
||||
>
|
||||
<template v-if="relationship?.blocking">
|
||||
<span elk-group-hover="hidden">{{ $t('account.blocking') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unblock') }}</span>
|
||||
</template>
|
||||
<template v-if="relationship?.muting">
|
||||
<span elk-group-hover="hidden">{{ $t('account.muting') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unmute') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship ? relationship.following : context === 'following'">
|
||||
<span elk-group-hover="hidden">{{ relationship?.followedBy ? $t('account.mutuals') : $t('account.following') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unfollow') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship?.requested">
|
||||
<span elk-group-hover="hidden">{{ $t('account.follow_requested') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.withdraw_follow_request') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship ? relationship.followedBy : context === 'followedBy'">
|
||||
<span elk-group-hover="hidden">{{ $t('account.follows_you') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ account.locked ? $t('account.request_follow') : $t('account.follow_back') }}</span>
|
||||
<template v-if="isLoading">
|
||||
<span i-svg-spinners-180-ring-with-bg />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ account.locked ? $t('account.request_follow') : $t('account.follow') }}</span>
|
||||
<template v-if="relationship?.blocking">
|
||||
<span elk-group-hover="hidden">{{ $t('account.blocking') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unblock') }}</span>
|
||||
</template>
|
||||
<template v-if="relationship?.muting">
|
||||
<span elk-group-hover="hidden">{{ $t('account.muting') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unmute') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship ? relationship.following : context === 'following'">
|
||||
<span elk-group-hover="hidden">{{ relationship?.followedBy ? $t('account.mutuals') : $t('account.following') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.unfollow') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship?.requested">
|
||||
<span elk-group-hover="hidden">{{ $t('account.follow_requested') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ $t('account.withdraw_follow_request') }}</span>
|
||||
</template>
|
||||
<template v-else-if="relationship ? relationship.followedBy : context === 'followedBy'">
|
||||
<span elk-group-hover="hidden">{{ $t('account.follows_you') }}</span>
|
||||
<span hidden elk-group-hover="inline">{{ account.locked ? $t('account.request_follow') : $t('account.follow_back') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ account.locked ? $t('account.request_follow') : $t('account.follow') }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
@ -2,39 +2,33 @@
|
|||
import type { mastodon } from 'masto'
|
||||
import { fetchAccountByHandle } from '~/composables/cache'
|
||||
|
||||
type WatcherType = [acc?: mastodon.v1.Account, h?: string, v?: boolean]
|
||||
type WatcherType = [acc?: mastodon.v1.Account | null, h?: string, v?: boolean]
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
account?: mastodon.v1.Account
|
||||
account?: mastodon.v1.Account | null
|
||||
handle?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const hoverCard = ref()
|
||||
const targetIsVisible = ref(false)
|
||||
const accountHover = ref()
|
||||
const hovered = useElementHover(accountHover)
|
||||
const account = ref<mastodon.v1.Account | null | undefined>(props.account)
|
||||
|
||||
useIntersectionObserver(
|
||||
hoverCard,
|
||||
([{ intersectionRatio }]) => {
|
||||
targetIsVisible.value = intersectionRatio <= 0.75
|
||||
},
|
||||
)
|
||||
watch(
|
||||
() => [props.account, props.handle, targetIsVisible.value] satisfies WatcherType,
|
||||
() => [props.account, props.handle, hovered.value] satisfies WatcherType,
|
||||
([newAccount, newHandle, newVisible], oldProps) => {
|
||||
if (!newVisible || process.test)
|
||||
return
|
||||
|
||||
if (newAccount) {
|
||||
account.value = newAccount
|
||||
return
|
||||
}
|
||||
|
||||
if (!newVisible)
|
||||
return
|
||||
|
||||
if (newHandle) {
|
||||
const [_oldAccount, oldHandle, _oldVisible] = oldProps ?? [undefined, undefined, false]
|
||||
if (!oldHandle || newHandle !== oldHandle || !account.value) {
|
||||
|
@ -49,15 +43,22 @@ watch(
|
|||
}
|
||||
|
||||
account.value = undefined
|
||||
}, { immediate: true, flush: 'post' },
|
||||
},
|
||||
{ immediate: true, flush: 'post' },
|
||||
)
|
||||
|
||||
const userSettings = useUserSettings()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="hoverCard">
|
||||
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
|
||||
<span ref="accountHover">
|
||||
<VMenu
|
||||
v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')"
|
||||
placement="bottom-start"
|
||||
:delay="{ show: 500, hide: 100 }"
|
||||
v-bind="$attrs"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<slot />
|
||||
<template #popper>
|
||||
<AccountHoverCard v-if="account" :account="account" />
|
||||
|
|
|
@ -25,13 +25,16 @@ function shareAccount() {
|
|||
}
|
||||
|
||||
async function toggleReblogs() {
|
||||
if (!relationship.value!.showingReblogs && await openConfirmDialog({
|
||||
title: t('confirm.show_reblogs.title'),
|
||||
description: t('confirm.show_reblogs.description', [account.acct]),
|
||||
confirm: t('confirm.show_reblogs.confirm'),
|
||||
cancel: t('confirm.show_reblogs.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
if (!relationship.value!.showingReblogs) {
|
||||
const dialogChoice = await openConfirmDialog({
|
||||
title: t('confirm.show_reblogs.title'),
|
||||
description: t('confirm.show_reblogs.description', [account.acct]),
|
||||
confirm: t('confirm.show_reblogs.confirm'),
|
||||
cancel: t('confirm.show_reblogs.cancel'),
|
||||
})
|
||||
if (dialogChoice.choice !== 'confirm')
|
||||
return
|
||||
}
|
||||
|
||||
const showingReblogs = !relationship.value?.showingReblogs
|
||||
relationship.value = await client.value.v1.accounts.$select(account.id).follow({ reblogs: showingReblogs })
|
||||
|
|
45
components/account/TagHoverWrapper.vue
Normal file
45
components/account/TagHoverWrapper.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const { tagName, disabled } = defineProps<{
|
||||
tagName?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const tag = ref<mastodon.v1.Tag>()
|
||||
const tagHover = ref()
|
||||
const hovered = useElementHover(tagHover)
|
||||
|
||||
watch(hovered, (newHovered) => {
|
||||
if (newHovered && tagName) {
|
||||
fetchTag(tagName).then((t) => {
|
||||
tag.value = t
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const userSettings = useUserSettings()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="tagHover">
|
||||
<VMenu
|
||||
v-if="!disabled && !getPreferences(userSettings, 'hideTagHoverCard')"
|
||||
placement="bottom-start"
|
||||
:delay="{ show: 500, hide: 100 }"
|
||||
v-bind="$attrs"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<slot />
|
||||
<template #popper>
|
||||
<TagCardSkeleton v-if="!tag" />
|
||||
<TagCard v-else :tag="tag" />
|
||||
</template>
|
||||
</VMenu>
|
||||
<slot v-else />
|
||||
</span>
|
||||
</template>
|
|
@ -22,7 +22,7 @@ onMounted(() => {
|
|||
|
||||
const commandMode = computed(() => input.value.startsWith('>'))
|
||||
|
||||
const query = computed(() => commandMode ? '' : input.value.trim())
|
||||
const query = computed(() => commandMode.value ? '' : input.value.trim())
|
||||
|
||||
const { accounts, hashtags, loading } = useSearch(query)
|
||||
|
||||
|
@ -61,7 +61,7 @@ const searchResult = computed<QueryResult>(() => {
|
|||
}
|
||||
})
|
||||
|
||||
const result = computed<QueryResult>(() => commandMode
|
||||
const result = computed<QueryResult>(() => commandMode.value
|
||||
? registry.query(scopes.value.map(s => s.id).join('.'), input.value.slice(1).trim())
|
||||
: searchResult.value,
|
||||
)
|
||||
|
|
|
@ -4,6 +4,8 @@ defineProps<{
|
|||
hover?: boolean
|
||||
iconChecked?: string
|
||||
iconUnchecked?: string
|
||||
checkedIconColor?: string
|
||||
prependCheckbox?: boolean
|
||||
}>()
|
||||
const modelValue = defineModel<boolean | null>()
|
||||
</script>
|
||||
|
@ -15,9 +17,12 @@ const modelValue = defineModel<boolean | null>()
|
|||
v-bind="$attrs"
|
||||
@click.prevent="modelValue = !modelValue"
|
||||
>
|
||||
<span v-if="label" flex-1 ms-2 pointer-events-none>{{ label }}</span>
|
||||
<span v-if="label && !prependCheckbox" flex-1 ms-2 pointer-events-none>{{ label }}</span>
|
||||
<span
|
||||
:class="modelValue ? (iconChecked ?? 'i-ri:checkbox-line') : (iconUnchecked ?? 'i-ri:checkbox-blank-line')"
|
||||
:class="[
|
||||
modelValue ? (iconChecked ?? 'i-ri:checkbox-line') : (iconUnchecked ?? 'i-ri:checkbox-blank-line'),
|
||||
modelValue && checkedIconColor,
|
||||
]"
|
||||
text-lg
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
@ -26,6 +31,7 @@ const modelValue = defineModel<boolean | null>()
|
|||
type="checkbox"
|
||||
sr-only
|
||||
>
|
||||
<span v-if="label && prependCheckbox" flex-1 ms-2 pointer-events-none>{{ label }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import type { UnwrapRef } from 'vue'
|
|||
const {
|
||||
paginator,
|
||||
stream,
|
||||
eventType,
|
||||
keyProp = 'id',
|
||||
virtualScroller = false,
|
||||
preprocess,
|
||||
|
@ -17,6 +18,7 @@ const {
|
|||
keyProp?: keyof T
|
||||
virtualScroller?: boolean
|
||||
stream?: mastodon.streaming.Subscription
|
||||
eventType?: 'update' | 'notification'
|
||||
preprocess?: (items: (U | T)[]) => U[]
|
||||
endMessage?: boolean | string
|
||||
}>()
|
||||
|
@ -44,7 +46,7 @@ defineSlots<{
|
|||
const { t } = useI18n()
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, toRef(() => stream), preprocess)
|
||||
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, toRef(() => stream), eventType, preprocess)
|
||||
|
||||
nuxtApp.hook('elk-logo:click', () => {
|
||||
update()
|
||||
|
|
|
@ -33,7 +33,7 @@ useCommands(() => command
|
|||
:to="option.to"
|
||||
:replace="replace"
|
||||
relative flex flex-auto cursor-pointer sm:px6 px2 rounded transition-all
|
||||
tabindex="1"
|
||||
tabindex="0"
|
||||
hover:bg-active transition-100
|
||||
exact-active-class="children:(text-secondary !border-primary !op100 !text-base)"
|
||||
@click="!preventScrollTop && $scrollToTop()"
|
||||
|
@ -46,7 +46,7 @@ useCommands(() => command
|
|||
</template>
|
||||
<template v-if="isHydrated && moreOptions?.options?.length">
|
||||
<CommonDropdown placement="bottom" flex cursor-pointer mx-1.25rem>
|
||||
<CommonTooltip placement="top" :content="moreOptions.tooltip || t('action.more')">
|
||||
<CommonTooltip placement="top" no-auto-focus :content="moreOptions.tooltip || t('action.more')">
|
||||
<button
|
||||
cursor-pointer
|
||||
flex
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
const { as = 'div', active } = defineProps<{ as: any; active: boolean }>()
|
||||
const { as = 'div', active } = defineProps<{
|
||||
as: any
|
||||
active: boolean
|
||||
}>()
|
||||
|
||||
const el = ref()
|
||||
|
||||
watch(() => active, (active) => {
|
||||
|
|
|
@ -19,8 +19,8 @@ const tabs = computed(() => {
|
|||
})
|
||||
})
|
||||
|
||||
function toValidName(otpion: string) {
|
||||
return otpion.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
|
||||
function toValidName(option: string) {
|
||||
return option.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
|
||||
}
|
||||
|
||||
useCommands(() => command
|
||||
|
@ -49,7 +49,7 @@ useCommands(() => command
|
|||
><label
|
||||
flex flex-auto cursor-pointer px3 m1 rounded transition-all
|
||||
:for="`tab-${toValidName(option.name)}`"
|
||||
tabindex="1"
|
||||
tabindex="0"
|
||||
hover:bg-active transition-100
|
||||
@keypress.enter="modelValue = option.name"
|
||||
><span
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
const emit = defineEmits<{
|
||||
(event: 'close'): void
|
||||
}>()
|
||||
|
||||
const vAutoFocus = (el: HTMLElement) => el.focus()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div my-8 px-3 sm:px-8 md:max-w-200 flex="~ col gap-4" relative>
|
||||
<button btn-action-icon absolute top--8 right-0 m1 aria-label="Close" @click="emit('close')">
|
||||
<div i-ri:close-line />
|
||||
<button v-auto-focus type="button" btn-action-icon absolute top--8 right-0 m1 aria-label="Close" @click="emit('close')">
|
||||
<span i-ri:close-line />
|
||||
</button>
|
||||
|
||||
<img :alt="$t('app_logo')" :src="`/${''}logo.svg`" w-20 h-20 height="80" width="80" mxa class="rtl-flip">
|
||||
|
@ -28,10 +30,12 @@ const emit = defineEmits<{
|
|||
</NuxtLink>
|
||||
{{ $t('help.desc_para6') }}
|
||||
</p>
|
||||
{{ $t('help.desc_para3') }}
|
||||
<p flex="~ gap-2 wrap" mxa>
|
||||
<NuxtLink hover:text-primary href="https://github.com/sponsors/elk-zone" target="_blank">
|
||||
{{ $t('help.desc_para3') }}
|
||||
</NuxtLink>
|
||||
<p flex="~ gap-2 wrap justify-center" mxa>
|
||||
<template v-for="team of elkTeamMembers" :key="team.github">
|
||||
<NuxtLink :href="`https://github.com/sponsors/${team.github}`" target="_blank" external rounded-full transition duration-300 border="~ transparent" hover="scale-105 border-primary">
|
||||
<NuxtLink :href="team.link" target="_blank" external rounded-full transition duration-300 border="~ transparent" hover="scale-105 border-primary">
|
||||
<img :src="`/avatars/${team.github}-100x100.png`" :alt="team.display" rounded-full w-15 h-15 height="60" width="60">
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
@ -42,7 +46,7 @@ const emit = defineEmits<{
|
|||
</NuxtLink>
|
||||
</p>
|
||||
|
||||
<button btn-solid mxa tabindex="2" @click="emit('close')">
|
||||
<button type="button" btn-solid mxa @click="emit('close')">
|
||||
{{ $t('action.enter_app') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -68,7 +68,7 @@ async function removeList() {
|
|||
actionError.value = undefined
|
||||
await nextTick()
|
||||
|
||||
if (confirmDelete === 'confirm') {
|
||||
if (confirmDelete.choice === 'confirm') {
|
||||
await nextTick()
|
||||
try {
|
||||
await client.v1.lists.$select(list.value.id).remove()
|
||||
|
@ -92,7 +92,7 @@ async function removeList() {
|
|||
async function clearError() {
|
||||
actionError.value = undefined
|
||||
await nextTick()
|
||||
if (isEditing)
|
||||
if (isEditing.value)
|
||||
input.value?.focus()
|
||||
else
|
||||
deleteBtn.value?.focus()
|
||||
|
|
|
@ -40,6 +40,10 @@ const shortcutItemGroups = computed<ShortcutItemGroup[]>(() => [
|
|||
// description: t('magic_keys.groups.navigation.previous_status'),
|
||||
// shortcut: { keys: ['k'], isSequence: false },
|
||||
// },
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_search'),
|
||||
shortcut: { keys: ['/'], isSequence: false },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_home'),
|
||||
shortcut: { keys: ['g', 'h'], isSequence: true },
|
||||
|
@ -48,6 +52,42 @@ const shortcutItemGroups = computed<ShortcutItemGroup[]>(() => [
|
|||
description: t('magic_keys.groups.navigation.go_to_notifications'),
|
||||
shortcut: { keys: ['g', 'n'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_conversations'),
|
||||
shortcut: { keys: ['g', 'c'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_favourites'),
|
||||
shortcut: { keys: ['g', 'f'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_bookmarks'),
|
||||
shortcut: { keys: ['g', 'b'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_explore'),
|
||||
shortcut: { keys: ['g', 'e'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_local'),
|
||||
shortcut: { keys: ['g', 'l'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_federated'),
|
||||
shortcut: { keys: ['g', 't'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_lists'),
|
||||
shortcut: { keys: ['g', 'i'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_settings'),
|
||||
shortcut: { keys: ['g', 's'], isSequence: true },
|
||||
},
|
||||
{
|
||||
description: t('magic_keys.groups.navigation.go_to_profile'),
|
||||
shortcut: { keys: ['g', 'p'], isSequence: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
45
components/modal/DurationPicker.vue
Normal file
45
components/modal/DurationPicker.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
const model = defineModel<number>()
|
||||
const isValid = defineModel<boolean>('isValid')
|
||||
|
||||
const days = ref<number | ''>(0)
|
||||
const hours = ref<number | ''>(1)
|
||||
const minutes = ref<number | ''>(0)
|
||||
|
||||
watchEffect(() => {
|
||||
if (days.value === '' || hours.value === '' || minutes.value === '') {
|
||||
isValid.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const duration
|
||||
= days.value * 24 * 60 * 60
|
||||
+ hours.value * 60 * 60
|
||||
+ minutes.value * 60
|
||||
|
||||
if (duration <= 0) {
|
||||
isValid.value = false
|
||||
return
|
||||
}
|
||||
|
||||
isValid.value = true
|
||||
model.value = duration
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex flex-grow-0 gap-2>
|
||||
<label flex items-center gap-2>
|
||||
<input v-model="days" type="number" min="0" max="1999" input-base :class="!isValid ? 'input-error' : null">
|
||||
{{ $t('confirm.mute_account.days', days === '' ? 0 : days) }}
|
||||
</label>
|
||||
<label flex items-center gap-2>
|
||||
<input v-model="hours" type="number" min="0" max="24" input-base :class="!isValid ? 'input-error' : null">
|
||||
{{ $t('confirm.mute_account.hours', hours === '' ? 0 : hours) }}
|
||||
</label>
|
||||
<label flex items-center gap-2>
|
||||
<input v-model="minutes" type="number" min="0" max="59" step="5" input-base :class="!isValid ? 'input-error' : null">
|
||||
{{ $t('confirm.mute_account.minute', minutes === '' ? 0 : minutes) }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
|
@ -1,11 +1,34 @@
|
|||
<script setup lang="ts">
|
||||
import type { ConfirmDialogChoice, ConfirmDialogLabel } from '~/types'
|
||||
import type { ConfirmDialogChoice, ConfirmDialogOptions } from '~/types'
|
||||
import DurationPicker from '~/components/modal/DurationPicker.vue'
|
||||
|
||||
defineProps<ConfirmDialogLabel>()
|
||||
const props = defineProps<ConfirmDialogOptions>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(evt: 'choice', choice: ConfirmDialogChoice): void
|
||||
}>()
|
||||
|
||||
const hasDuration = ref(false)
|
||||
const isValidDuration = ref(true)
|
||||
const duration = ref(60 * 60) // default to 1 hour
|
||||
const shouldMuteNotifications = ref(true)
|
||||
const isMute = computed(() => props.extraOptionType === 'mute')
|
||||
|
||||
function handleChoice(choice: ConfirmDialogChoice['choice']) {
|
||||
const dialogChoice = {
|
||||
choice,
|
||||
...isMute.value && {
|
||||
extraOptions: {
|
||||
mute: {
|
||||
duration: hasDuration.value ? duration.value : 0,
|
||||
notifications: shouldMuteNotifications.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
emit('choice', dialogChoice)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -16,11 +39,17 @@ const emit = defineEmits<{
|
|||
<div v-if="description">
|
||||
{{ description }}
|
||||
</div>
|
||||
<div v-if="isMute" flex-col flex gap-4>
|
||||
<CommonCheckbox v-model="hasDuration" :label="$t('confirm.mute_account.specify_duration')" prepend-checkbox checked-icon-color="text-primary" />
|
||||
<DurationPicker v-if="hasDuration" v-model="duration" v-model:is-valid="isValidDuration" />
|
||||
<CommonCheckbox v-model="shouldMuteNotifications" :label="$t('confirm.mute_account.notifications')" prepend-checkbox checked-icon-color="text-primary" />
|
||||
</div>
|
||||
|
||||
<div flex justify-end gap-2>
|
||||
<button btn-text @click="emit('choice', 'cancel')">
|
||||
<button btn-text @click="handleChoice('cancel')">
|
||||
{{ cancel || $t('confirm.common.cancel') }}
|
||||
</button>
|
||||
<button btn-solid @click="emit('choice', 'confirm')">
|
||||
<button btn-solid :disabled="!isValidDuration" @click="handleChoice('confirm')">
|
||||
{{ confirm || $t('confirm.common.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@ onUnmounted(() => locked.value = false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div relative h-full w-full flex pt-12 w-100vh @click="onClick">
|
||||
<div relative h-full w-full flex pt-12 @click="onClick">
|
||||
<button
|
||||
v-if="hasNext" pointer-events-auto btn-action-icon bg="black/20" :aria-label="$t('action.previous')"
|
||||
hover:bg="black/40" dark:bg="white/30" dark-hover:bg="white/20" absolute top="1/2" right-1 z5
|
||||
|
|
|
@ -15,7 +15,7 @@ const emit = defineEmits<{
|
|||
const modelValue = defineModel<number>({ required: true })
|
||||
|
||||
const slideGap = 20
|
||||
const doubleTapTreshold = 250
|
||||
const doubleTapThreshold = 250
|
||||
|
||||
const view = ref()
|
||||
const slider = ref()
|
||||
|
@ -36,6 +36,8 @@ const isPinching = ref(false)
|
|||
const maxZoomOut = ref(1)
|
||||
const isZoomedIn = computed(() => scale.value > 1)
|
||||
|
||||
const enableAutoplay = usePreferences('enableAutoplay')
|
||||
|
||||
function goToFocusedSlide() {
|
||||
scale.value = 1
|
||||
x.value = slide.value[modelValue.value].offsetLeft * scale.value
|
||||
|
@ -147,7 +149,7 @@ function handleLastDrag(tap: boolean, swipe: Vector2, movement: Vector2, positio
|
|||
let lastTapAt = 0
|
||||
function handleTap([positionX, positionY]: Vector2) {
|
||||
const now = Date.now()
|
||||
const isDoubleTap = now - lastTapAt < doubleTapTreshold
|
||||
const isDoubleTap = now - lastTapAt < doubleTapThreshold
|
||||
lastTapAt = now
|
||||
|
||||
if (!isDoubleTap)
|
||||
|
@ -218,7 +220,7 @@ function handleZoomDrag([deltaX, deltaY]: Vector2) {
|
|||
function handleSlideDrag([movementX, movementY]: Vector2) {
|
||||
goToFocusedSlide()
|
||||
|
||||
if (Math.abs(movementY) > Math.abs(movementX)) // vertical movement is more then horizontal
|
||||
if (Math.abs(movementY) > Math.abs(movementX)) // vertical movement is more than horizontal
|
||||
y.value -= movementY / scale.value
|
||||
else
|
||||
x.value -= movementX / scale.value
|
||||
|
@ -264,8 +266,12 @@ const imageStyle = computed(() => ({
|
|||
items-center
|
||||
justify-center
|
||||
>
|
||||
<img
|
||||
<component
|
||||
:is="item.type === 'gifv' ? 'video' : 'img'"
|
||||
ref="image"
|
||||
:autoplay="enableAutoplay"
|
||||
controls
|
||||
loop
|
||||
select-none
|
||||
max-w-full
|
||||
max-h-full
|
||||
|
@ -273,7 +279,7 @@ const imageStyle = computed(() => ({
|
|||
:draggable="false"
|
||||
:src="item.url || item.previewUrl"
|
||||
:alt="item.description || ''"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
// only one icon can be lit up at the same time
|
||||
import { STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE } from '~/constants'
|
||||
|
||||
const moreMenuVisible = ref(false)
|
||||
|
||||
const { notifications } = useNotifications()
|
||||
const lastAccessedNotificationRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE, '')
|
||||
const lastAccessedExploreRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -19,7 +23,7 @@ const { notifications } = useNotifications()
|
|||
<NuxtLink to="/search" :aria-label="$t('nav.search')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<div i-ri:search-line />
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/notifications" :aria-label="$t('nav.notifications')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<NuxtLink :to="`/notifications/${lastAccessedNotificationRoute}`" :aria-label="$t('nav.notifications')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<div flex relative>
|
||||
<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>
|
||||
|
@ -32,8 +36,8 @@ const { notifications } = useNotifications()
|
|||
</NuxtLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NuxtLink :to="`/${currentServer}/explore`" :aria-label="$t('nav.explore')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<div i-ri:hashtag />
|
||||
<NuxtLink :to="`/${currentServer}/explore/${lastAccessedExploreRoute}`" :aria-label="$t('nav.explore')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<div i-ri:compass-3-line />
|
||||
</NuxtLink>
|
||||
<NuxtLink group :to="`/${currentServer}/public/local`" :aria-label="$t('nav.local')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||
<div i-ri:group-2-line />
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE } from '~/constants'
|
||||
|
||||
const { command } = defineProps<{
|
||||
command?: boolean
|
||||
}>()
|
||||
const { notifications } = useNotifications()
|
||||
const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
|
||||
const lastAccessedNotificationRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE, '')
|
||||
const lastAccessedExploreRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -12,7 +16,7 @@ const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
|
|||
|
||||
<div class="spacer" shrink xl:hidden />
|
||||
<NavSideItem :text="$t('nav.home')" to="/home" icon="i-ri:home-5-line" user-only :command="command" />
|
||||
<NavSideItem :text="$t('nav.notifications')" to="/notifications" icon="i-ri:notification-4-line" user-only :command="command">
|
||||
<NavSideItem :text="$t('nav.notifications')" :to="`/notifications/${lastAccessedNotificationRoute}`" icon="i-ri:notification-4-line" user-only :command="command">
|
||||
<template #icon>
|
||||
<div flex relative>
|
||||
<div class="i-ri:notification-4-line" text-xl />
|
||||
|
@ -30,10 +34,11 @@ const useStarFavoriteIcon = usePreferences('useStarFavoriteIcon')
|
|||
<NavSideItem :text="$t('action.compose')" to="/compose" icon="i-ri:quill-pen-line" user-only :command="command" />
|
||||
|
||||
<div class="spacer" shrink hidden sm:block />
|
||||
<NavSideItem :text="$t('nav.explore')" :to="isHydrated ? `/${currentServer}/explore` : '/explore'" icon="i-ri:hashtag" :command="command" />
|
||||
<NavSideItem :text="$t('nav.explore')" :to="isHydrated ? `/${currentServer}/explore/${lastAccessedExploreRoute}` : `/explore/${lastAccessedExploreRoute}`" icon="i-ri:compass-3-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="isHydrated ? `/${currentServer}/public` : '/public'" icon="i-ri:earth-line" :command="command" />
|
||||
<NavSideItem :text="$t('nav.lists')" :to="isHydrated ? `/${currentServer}/lists` : '/lists'" icon="i-ri:list-check" user-only :command="command" />
|
||||
<NavSideItem :text="$t('nav.hashtags')" to="/hashtags" icon="i-ri:hashtag" user-only :command="command" />
|
||||
|
||||
<div class="spacer" shrink hidden sm:block />
|
||||
<NavSideItem :text="$t('nav.settings')" to="/settings" icon="i-ri:settings-3-line" :command="command" />
|
||||
|
|
|
@ -57,11 +57,21 @@ const noUserVisual = computed(() => isHydrated.value && props.userOnly && !curre
|
|||
<div
|
||||
class="item"
|
||||
flex items-center gap4
|
||||
w-fit rounded-3
|
||||
px2 mx3 sm:mxa
|
||||
xl="ml0 mr5 px5 w-auto"
|
||||
transition-100
|
||||
elk-group-hover="bg-active" group-focus-visible:ring="2 current"
|
||||
:class="isSmallScreen
|
||||
? `
|
||||
w-full
|
||||
px5 sm:mxa
|
||||
transition-colors duration-200 transform
|
||||
hover-bg-gray-100 hover-dark:(bg-gray-700 text-white)
|
||||
` : `
|
||||
w-fit rounded-3
|
||||
px2 mx3 sm:mxa
|
||||
transition-100
|
||||
elk-group-hover-bg-active
|
||||
group-focus-visible:ring-2
|
||||
group-focus-visible:ring-current
|
||||
`"
|
||||
>
|
||||
<slot name="icon">
|
||||
<div :class="icon" text-xl />
|
||||
|
|
|
@ -34,7 +34,13 @@ const { busy, oauth, singleInstanceServer } = useSignIn()
|
|||
<strong>{{ currentServer }}</strong>
|
||||
</i18n-t>
|
||||
</button>
|
||||
<button v-else btn-solid text-sm px-2 py-1 text-center xl:hidden @click="openSigninDialog()">
|
||||
<button
|
||||
v-else
|
||||
flex="~ row"
|
||||
gap-x-1 items-center justify-center btn-solid text-sm px-2 py-1 xl:hidden
|
||||
@click="openSigninDialog()"
|
||||
>
|
||||
<span aria-hidden="true" block i-ri:login-circle-line class="rtl-flip" />
|
||||
{{ $t('action.sign_in') }}
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
@ -4,6 +4,13 @@ import type { mastodon } from 'masto'
|
|||
const { notification } = defineProps<{
|
||||
notification: mastodon.v1.Notification
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// well-known emoji reactions types Elk does not support yet
|
||||
const unsupportedEmojiReactionTypes = ['pleroma:emoji_reaction', 'reaction']
|
||||
if (unsupportedEmojiReactionTypes.includes(notification.type))
|
||||
console.warn(`[DEV] ${t('notification.missing_type')} '${notification.type}' (notification.id: ${notification.id})`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -88,7 +95,8 @@ const { notification } = defineProps<{
|
|||
<template v-else-if="notification.type === 'mention' || notification.type === 'poll' || notification.type === 'status'">
|
||||
<StatusCard :status="notification.status!" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-else-if="!unsupportedEmojiReactionTypes.includes(notification.type)">
|
||||
<!-- prevent showing errors for dev for known emoji reaction types -->
|
||||
<!-- type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes -->
|
||||
<div text-red font-bold>
|
||||
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'
|
||||
|
|
|
@ -25,7 +25,7 @@ function includeNotificationsForStatusCard({ type, status }: mastodon.v1.Notific
|
|||
|
||||
// Group by type (and status when applicable)
|
||||
function groupId(item: mastodon.v1.Notification): string {
|
||||
// If the update is related to an status, group notifications from the same account (boost + favorite the same status)
|
||||
// If the update is related to a status, group notifications from the same account (boost + favorite the same status)
|
||||
const id = item.status
|
||||
? {
|
||||
status: item.status?.id,
|
||||
|
@ -171,6 +171,7 @@ const { formatNumber } = useHumanReadableNumber()
|
|||
:paginator="paginator"
|
||||
:preprocess="preprocess"
|
||||
:stream="stream"
|
||||
eventType="notification"
|
||||
:virtualScroller="virtualScroller"
|
||||
>
|
||||
<template #updater="{ number, update }">
|
||||
|
|
|
@ -35,8 +35,8 @@ const showWarning = computed(() => {
|
|||
return false
|
||||
|
||||
return isSupported
|
||||
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
|
||||
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''] === true)
|
||||
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
|
||||
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''])
|
||||
})
|
||||
|
||||
async function saveSettings() {
|
||||
|
|
|
@ -19,7 +19,8 @@ const emit = defineEmits<{
|
|||
const maxDescriptionLength = 1500
|
||||
|
||||
const isEditDialogOpen = ref(false)
|
||||
const description = computed(() => props.attachment.description ?? '')
|
||||
const description = ref(props.attachment.description ?? '')
|
||||
|
||||
function toggleApply() {
|
||||
isEditDialogOpen.value = false
|
||||
emit('setDescription', description.value)
|
||||
|
|
|
@ -30,8 +30,14 @@ const draftState = useDraft(draftKey, initial)
|
|||
const { draft } = draftState
|
||||
|
||||
const {
|
||||
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
|
||||
uploadAttachments, pickAttachments, setDescription, removeAttachment,
|
||||
isExceedingAttachmentLimit,
|
||||
isUploading,
|
||||
failedAttachments,
|
||||
isOverDropZone,
|
||||
uploadAttachments,
|
||||
pickAttachments,
|
||||
setDescription,
|
||||
removeAttachment,
|
||||
dropZoneRef,
|
||||
} = useUploadMediaAttachment(draft)
|
||||
|
||||
|
@ -68,7 +74,7 @@ function trimPollOptions() {
|
|||
const trimmedOptions = draft.value.params.poll!.options.slice(0, indexLastNonEmpty + 1)
|
||||
|
||||
if (currentInstance.value?.configuration
|
||||
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
|
||||
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
|
||||
draft.value.params.poll!.options = trimmedOptions
|
||||
else
|
||||
draft.value.params.poll!.options = [...trimmedOptions, '']
|
||||
|
@ -81,30 +87,32 @@ function editPollOptionDraft(event: Event, index: number) {
|
|||
}
|
||||
|
||||
function deletePollOption(index: number) {
|
||||
draft.value.params.poll!.options = draft.value.params.poll!.options.slice().splice(index, 1)
|
||||
const newPollOptions = draft.value.params.poll!.options.slice()
|
||||
newPollOptions.splice(index, 1)
|
||||
draft.value.params.poll!.options = newPollOptions
|
||||
trimPollOptions()
|
||||
}
|
||||
|
||||
const expiresInOptions = computed(() => [
|
||||
{
|
||||
seconds: 1 * 60 * 60,
|
||||
label: isHydrated.value ? t('time_ago_options.hour_future', 1) : '',
|
||||
label: t('time_ago_options.hour_future', 1),
|
||||
},
|
||||
{
|
||||
seconds: 2 * 60 * 60,
|
||||
label: isHydrated.value ? t('time_ago_options.hour_future', 2) : '',
|
||||
label: t('time_ago_options.hour_future', 2),
|
||||
},
|
||||
{
|
||||
seconds: 1 * 24 * 60 * 60,
|
||||
label: isHydrated.value ? t('time_ago_options.day_future', 1) : '',
|
||||
label: t('time_ago_options.day_future', 1),
|
||||
},
|
||||
{
|
||||
seconds: 2 * 24 * 60 * 60,
|
||||
label: isHydrated.value ? t('time_ago_options.day_future', 2) : '',
|
||||
label: t('time_ago_options.day_future', 2),
|
||||
},
|
||||
{
|
||||
seconds: 7 * 24 * 60 * 60,
|
||||
label: isHydrated.value ? t('time_ago_options.day_future', 7) : '',
|
||||
label: t('time_ago_options.day_future', 7),
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -132,7 +140,7 @@ const characterCount = computed(() => {
|
|||
length -= fullMatch.length - (before + username).length - 1 // - 1 for the @
|
||||
|
||||
if (draft.value.mentions) {
|
||||
// + 1 is needed as mentions always need a space seperator at the end
|
||||
// + 1 is needed as mentions always need a space separator at the end
|
||||
length += draft.value.mentions.map((mention) => {
|
||||
const [handle] = mention.split('@')
|
||||
return `@${handle}`
|
||||
|
@ -150,6 +158,8 @@ const isExceedingCharacterLimit = computed(() => {
|
|||
|
||||
const postLanguageDisplay = computed(() => languagesNameList.find(i => i.code === (draft.value.params.language || preferredLanguage))?.nativeName)
|
||||
|
||||
const isDM = computed(() => draft.value.params.visibility === 'direct')
|
||||
|
||||
async function handlePaste(evt: ClipboardEvent) {
|
||||
const files = evt.clipboardData?.files
|
||||
if (!files || files.length === 0)
|
||||
|
@ -269,12 +279,16 @@ onDeactivated(() => {
|
|||
</ol>
|
||||
</CommonErrorMessage>
|
||||
|
||||
<div relative flex-1 flex flex-col>
|
||||
<div relative flex-1 flex flex-col min-h-30>
|
||||
<EditorContent
|
||||
:editor="editor"
|
||||
flex max-w-full
|
||||
:class="shouldExpanded ? 'min-h-30 md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain' : ''"
|
||||
:class="{
|
||||
'md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain': shouldExpanded,
|
||||
'py2 px3.5 bg-dm rounded-4 me--1 ms--1 mt--1': isDM,
|
||||
}"
|
||||
@keydown="stopQuestionMarkPropagation"
|
||||
@keydown.esc.prevent="editor?.commands.blur()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -77,11 +77,12 @@ function activate() {
|
|||
ps-3
|
||||
pe-1
|
||||
ml-1
|
||||
:placeholder="isHydrated ? t('nav.search') : ''"
|
||||
:placeholder="t('nav.search')"
|
||||
pb="1px"
|
||||
placeholder-text-secondary
|
||||
@keydown.down.prevent="shift(1)"
|
||||
@keydown.up.prevent="shift(-1)"
|
||||
@keydown.esc.prevent="input?.blur()"
|
||||
@keypress.enter="activate"
|
||||
>
|
||||
<button v-if="query.length" btn-action-icon text-secondary @click="query = ''; input?.focus()">
|
||||
|
|
|
@ -10,20 +10,22 @@ const props = defineProps<{
|
|||
external?: true
|
||||
large?: true
|
||||
match?: boolean
|
||||
target?: string
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const scrollOnClick = computed(() => props.to && !(props.target === '_blank' || props.external))
|
||||
|
||||
useCommand({
|
||||
scope: 'Settings',
|
||||
|
||||
name: () => props.text
|
||||
?? (props.to
|
||||
? typeof props.to === 'string'
|
||||
? props.to
|
||||
: props.to.name
|
||||
: ''
|
||||
),
|
||||
?? (props.to
|
||||
? typeof props.to === 'string'
|
||||
? props.to
|
||||
: props.to.name
|
||||
: ''
|
||||
),
|
||||
description: () => props.description,
|
||||
icon: () => props.icon || '',
|
||||
visible: () => props.command && props.to,
|
||||
|
@ -39,11 +41,12 @@ useCommand({
|
|||
:disabled="disabled"
|
||||
:to="to"
|
||||
:external="external"
|
||||
:target="target"
|
||||
exact-active-class="text-primary"
|
||||
:class="disabled ? 'op25 pointer-events-none ' : match ? 'text-primary' : ''"
|
||||
block w-full group focus:outline-none
|
||||
:tabindex="disabled ? -1 : null"
|
||||
@click="to ? $scrollToTop() : undefined"
|
||||
@click="scrollOnClick ? $scrollToTop() : undefined"
|
||||
>
|
||||
<div
|
||||
w-full flex px5 py3 md:gap2 gap4 items-center
|
||||
|
|
|
@ -8,8 +8,7 @@ const dropdown = ref<any>()
|
|||
|
||||
const fieldIcons = computed(() =>
|
||||
Array.from({ length: maxAccountFieldCount.value }, (_, i) =>
|
||||
getAccountFieldIcon(form.value.fieldsAttributes[i].name),
|
||||
),
|
||||
getAccountFieldIcon(form.value.fieldsAttributes[i].name)),
|
||||
)
|
||||
|
||||
const fieldCount = computed(() => {
|
||||
|
|
|
@ -16,7 +16,7 @@ const { disabled = false } = defineProps<{
|
|||
:class="disabled ? 'opacity-50 cursor-not-allowed' : ''"
|
||||
>
|
||||
<div
|
||||
w-full flex w-fit px5 py3 md:gap2 gap4 items-center
|
||||
w-full flex px5 py3 md:gap2 gap4 items-center
|
||||
transition-250
|
||||
:class="disabled ? '' : 'group-hover:bg-active'"
|
||||
group-focus-visible:ring="2 current"
|
||||
|
|
|
@ -55,7 +55,7 @@ function reply() {
|
|||
|
||||
<div flex-1>
|
||||
<StatusActionButton
|
||||
:content="$t('action.boost')"
|
||||
:content="$t(status.reblogged ? 'action.boosted' : 'action.boost')"
|
||||
:text="!getPreferences(userSettings, 'hideBoostCount') && status.reblogsCount ? status.reblogsCount : ''"
|
||||
color="text-green" hover="text-green" elk-group-hover="bg-green/10"
|
||||
icon="i-ri:repeat-line"
|
||||
|
@ -77,7 +77,7 @@ function reply() {
|
|||
|
||||
<div flex-1>
|
||||
<StatusActionButton
|
||||
:content="$t('action.favourite')"
|
||||
:content="$t(status.favourited ? 'action.favourited' : 'action.favourite')"
|
||||
:text="!getPreferences(userSettings, 'hideFavoriteCount') && status.favouritesCount ? status.favouritesCount : ''"
|
||||
:color="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'"
|
||||
:hover="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'"
|
||||
|
@ -100,7 +100,7 @@ function reply() {
|
|||
|
||||
<div flex-none>
|
||||
<StatusActionButton
|
||||
:content="$t('action.bookmark')"
|
||||
:content="$t(status.bookmarked ? 'action.bookmarked' : 'action.bookmark')"
|
||||
:color="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'"
|
||||
:hover="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'"
|
||||
:elk-group-hover="useStarFavoriteIcon ? 'bg-rose/10' : 'bg-yellow/10' "
|
||||
|
|
|
@ -62,12 +62,13 @@ async function shareLink(status: mastodon.v1.Status) {
|
|||
}
|
||||
|
||||
async function deleteStatus() {
|
||||
if (await openConfirmDialog({
|
||||
const confirmDelete = await openConfirmDialog({
|
||||
title: t('confirm.delete_posts.title'),
|
||||
description: t('confirm.delete_posts.description'),
|
||||
confirm: t('confirm.delete_posts.confirm'),
|
||||
cancel: t('confirm.delete_posts.cancel'),
|
||||
}) !== 'confirm')
|
||||
})
|
||||
if (confirmDelete.choice !== 'confirm')
|
||||
return
|
||||
|
||||
removeCachedStatus(status.value.id)
|
||||
|
@ -80,12 +81,13 @@ async function deleteStatus() {
|
|||
}
|
||||
|
||||
async function deleteAndRedraft() {
|
||||
if (await openConfirmDialog({
|
||||
const confirmDelete = await openConfirmDialog({
|
||||
title: t('confirm.delete_posts.title'),
|
||||
description: t('confirm.delete_posts.description'),
|
||||
confirm: t('confirm.delete_posts.confirm'),
|
||||
cancel: t('confirm.delete_posts.cancel'),
|
||||
}) !== 'confirm')
|
||||
})
|
||||
if (confirmDelete.choice !== 'confirm')
|
||||
return
|
||||
|
||||
if (import.meta.dev) {
|
||||
|
@ -142,7 +144,7 @@ function showFavoritedAndBoostedBy() {
|
|||
|
||||
<template #popper>
|
||||
<div flex="~ col">
|
||||
<template v-if="getPreferences(userSettings, 'zenMode')">
|
||||
<template v-if="getPreferences(userSettings, 'zenMode') && !details">
|
||||
<CommonDropdownItem
|
||||
:text="$t('action.reply')"
|
||||
icon="i-ri:chat-1-line"
|
||||
|
|
|
@ -68,6 +68,7 @@ const video = ref<HTMLVideoElement | undefined>()
|
|||
const prefersReducedMotion = usePreferredReducedMotion()
|
||||
const isAudio = computed(() => attachment.type === 'audio')
|
||||
const isVideo = computed(() => attachment.type === 'video')
|
||||
const isGif = computed(() => attachment.type === 'gifv')
|
||||
|
||||
const enableAutoplay = usePreferences('enableAutoplay')
|
||||
|
||||
|
@ -164,7 +165,7 @@ watch(shouldLoadAttachment, () => {
|
|||
<button
|
||||
type="button"
|
||||
relative
|
||||
@click="!shouldLoadAttachment ? loadAttachment() : null"
|
||||
@click="!shouldLoadAttachment ? loadAttachment() : openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
|
||||
>
|
||||
<video
|
||||
ref="video"
|
||||
|
@ -248,12 +249,13 @@ watch(shouldLoadAttachment, () => {
|
|||
</button>
|
||||
</template>
|
||||
<div
|
||||
v-if="attachment.description && !getPreferences(userSettings, 'hideAltIndicatorOnPosts')" :class="isAudio ? [] : [
|
||||
:class="isAudio ? [] : [
|
||||
'absolute left-2',
|
||||
isVideo ? 'top-2' : 'bottom-2',
|
||||
]"
|
||||
flex gap-col-2
|
||||
>
|
||||
<VDropdown :distance="6" placement="bottom-start">
|
||||
<VDropdown v-if="attachment.description && !getPreferences(userSettings, 'hideAltIndicatorOnPosts')" :distance="6" placement="bottom-start">
|
||||
<button
|
||||
font-bold text-sm
|
||||
:class="isAudio
|
||||
|
@ -281,6 +283,14 @@ watch(shouldLoadAttachment, () => {
|
|||
</div>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<div v-if="isGif && !getPreferences(userSettings, 'hideGifIndicatorOnPosts')">
|
||||
<button
|
||||
aria-hidden font-bold text-sm
|
||||
rounded-1 bg-black:65 text-white px1.2 py0.2 pointer-events-none
|
||||
>
|
||||
{{ $t('status.gif') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -63,8 +63,8 @@ const isSelfReply = computed(() => status.value.inReplyToAccountId === status.va
|
|||
const collapseRebloggedBy = computed(() => rebloggedBy.value?.id === status.value.account.id)
|
||||
const isDM = computed(() => status.value.visibility === 'direct')
|
||||
|
||||
const showUpperBorder = computed(() => props.newer && !directReply)
|
||||
const showReplyTo = computed(() => !replyToMain && !directReply)
|
||||
const showUpperBorder = computed(() => props.newer && !directReply.value)
|
||||
const showReplyTo = computed(() => !replyToMain.value && !directReply.value)
|
||||
|
||||
const forceShow = ref(false)
|
||||
</script>
|
||||
|
|
|
@ -30,14 +30,14 @@ const hideAllMedia = computed(
|
|||
},
|
||||
)
|
||||
const embeddedMediaPreference = usePreferences('experimentalEmbeddedMedia')
|
||||
const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPreference)
|
||||
const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPreference.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
space-y-3
|
||||
:class="{
|
||||
'pt2 pb0.5 px3.5 bg-dm rounded-4 me--1': isDM,
|
||||
'py2 px3.5 bg-dm rounded-4 me--1': isDM,
|
||||
'ms--3.5 mt--1 ms--1': isDM && context !== 'details',
|
||||
}"
|
||||
>
|
||||
|
@ -68,7 +68,6 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
|
|||
:status="status.reblog" border="~ rounded"
|
||||
:actions="false"
|
||||
/>
|
||||
<div v-if="isDM" />
|
||||
</StatusSpoiler>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -31,7 +31,7 @@ useHydratedHead({
|
|||
|
||||
<template>
|
||||
<div :id="`status-${status.id}`" flex flex-col gap-2 pt2 pb1 ps-3 pe-4 relative :lang="status.language ?? undefined" aria-roledescription="status-details">
|
||||
<StatusActionsMore :status="status" absolute inset-ie-2 top-2 @after-edit="$emit('refetchStatus')" />
|
||||
<StatusActionsMore :status="status" :details="true" absolute inset-ie-2 top-2 @after-edit="$emit('refetchStatus')" />
|
||||
<NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pe5 me-a>
|
||||
<AccountHoverWrapper :account="status.account">
|
||||
<AccountInfo :account="status.account" />
|
||||
|
|
|
@ -42,7 +42,7 @@ const tabs = [
|
|||
>
|
||||
<div
|
||||
relative flex flex-auto cursor-pointer sm:px6 px2 rounded transition-all
|
||||
tabindex="1"
|
||||
tabindex="0"
|
||||
hover:bg-active transition-100
|
||||
@click="option.onClick"
|
||||
>
|
||||
|
|
|
@ -14,7 +14,8 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
|
|||
const path = evt.composedPath() as HTMLElement[]
|
||||
const el = path.find(el => ['A', 'BUTTON', 'IMG', 'VIDEO'].includes(el.tagName?.toUpperCase()))
|
||||
const text = window.getSelection()?.toString()
|
||||
if (!el && !text)
|
||||
const isCustomEmoji = el?.parentElement?.classList.contains('custom-emoji')
|
||||
if ((!el && !text) || isCustomEmoji)
|
||||
go(evt)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { fetchAccountById } from '~/composables/cache'
|
||||
|
||||
const {
|
||||
status,
|
||||
isSelfReply = false,
|
||||
} = defineProps<{
|
||||
type WatcherType = [status?: mastodon.v1.Status, v?: boolean]
|
||||
|
||||
const props = defineProps<{
|
||||
status: mastodon.v1.Status
|
||||
isSelfReply: boolean
|
||||
}>()
|
||||
|
||||
const isSelf = computed(() => status.inReplyToAccountId === status.account.id)
|
||||
const account = isSelf ? computed(() => status.account) : useAccountById(status.inReplyToAccountId)
|
||||
const link = ref()
|
||||
const targetIsVisible = ref(false)
|
||||
const isSelf = computed(() => props.status.inReplyToAccountId === props.status.account.id)
|
||||
const account = ref<mastodon.v1.Account | null | undefined>(isSelf.value ? props.status.account : undefined)
|
||||
|
||||
useIntersectionObserver(
|
||||
link,
|
||||
([{ intersectionRatio }]) => {
|
||||
targetIsVisible.value = intersectionRatio > 0.1
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [props.status, targetIsVisible.value] satisfies WatcherType,
|
||||
([newStatus, newVisible]) => {
|
||||
if (newStatus.account && newStatus.inReplyToAccountId === newStatus.account.id) {
|
||||
account.value = newStatus.account
|
||||
return
|
||||
}
|
||||
|
||||
if (!newVisible)
|
||||
return
|
||||
|
||||
const newId = newStatus.inReplyToAccountId
|
||||
|
||||
if (newId) {
|
||||
fetchAccountById(newStatus.inReplyToAccountId).then((acc) => {
|
||||
if (newId === props.status.inReplyToAccountId)
|
||||
account.value = acc
|
||||
})
|
||||
return
|
||||
}
|
||||
account.value = undefined
|
||||
},
|
||||
{ immediate: true, flush: 'post' },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
v-if="status.inReplyToId"
|
||||
ref="link"
|
||||
flex="~ gap2" items-center h-auto text-sm text-secondary
|
||||
:to="getStatusInReplyToRoute(status)"
|
||||
:title="$t('status.replying_to', [account ? getDisplayName(account) : $t('status.someone')])"
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{ enabled?: boolean; filter?: boolean; isDM?: boolean; sensitiveNonSpoiler?: boolean }>()
|
||||
const props = defineProps<{
|
||||
enabled?: boolean
|
||||
filter?: boolean
|
||||
isDM?: boolean
|
||||
sensitiveNonSpoiler?: boolean
|
||||
}>()
|
||||
|
||||
const expandSpoilers = computed(() => {
|
||||
const expandCW = currentUser.value ? getExpandSpoilersByDefault(currentUser.value.account) : false
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
|
||||
const {
|
||||
tag,
|
||||
} = defineProps<{
|
||||
const { tag } = defineProps<{
|
||||
tag: mastodon.v1.Tag
|
||||
}>()
|
||||
|
||||
|
@ -32,19 +30,21 @@ function go(evt: MouseEvent | KeyboardEvent) {
|
|||
|
||||
<template>
|
||||
<div
|
||||
block p4 hover:bg-active flex justify-between cursor-pointer
|
||||
block p4 hover:bg-active flex justify-between cursor-pointer flex-gap-2
|
||||
@click="onclick"
|
||||
@keydown.enter="onclick"
|
||||
>
|
||||
<div>
|
||||
<h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
|
||||
<TagActionButton :tag="tag" />
|
||||
<bdi>
|
||||
<span>#</span>
|
||||
<span hover:underline>{{ tag.name }}</span>
|
||||
</bdi>
|
||||
</h4>
|
||||
<CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
|
||||
<div flex flex-gap-2>
|
||||
<TagActionButton :tag="tag" />
|
||||
<div>
|
||||
<h4 flex items-center text-size-base leading-normal font-medium line-clamp-1 break-all ws-pre-wrap>
|
||||
<bdi>
|
||||
<span>#</span>
|
||||
<span hover:underline>{{ tag.name }}</span>
|
||||
</bdi>
|
||||
</h4>
|
||||
<CommonTrending v-if="tag.history" :history="tag.history" text-sm text-secondary line-clamp-1 ws-pre-wrap break-all />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tag.history" flex items-center>
|
||||
<CommonTrendingCharts :history="tag.history" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div p4 flex justify-between>
|
||||
<div p4 flex justify-between gap-4>
|
||||
<div flex="~ col 1 gap-2">
|
||||
<div flex class="skeleton-loading-bg" h-5 w-30 rounded />
|
||||
<div flex class="skeleton-loading-bg" h-4 w-45 rounded />
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE } from '~/constants'
|
||||
|
||||
const { filter } = defineProps<{
|
||||
filter?: mastodon.v1.NotificationType
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const lastAccessedNotificationRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE, '')
|
||||
|
||||
const options = { limit: 30, types: filter ? [filter] : [] }
|
||||
|
||||
// Default limit is 20 notifications, and servers are normally caped to 30
|
||||
const paginator = useMastoClient().v1.notifications.list(options)
|
||||
const stream = useStreaming(client => client.user.notification.subscribe())
|
||||
|
||||
lastAccessedNotificationRoute.value = route.path.replace(/\/notifications\/?/, '')
|
||||
|
||||
const { clearNotifications } = useNotifications()
|
||||
onActivated(clearNotifications)
|
||||
onActivated(() => {
|
||||
clearNotifications()
|
||||
lastAccessedNotificationRoute.value = route.path.replace(/\/notifications\/?/, '')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true })
|
||||
const stream = useStreaming(client => client.direct.subscribe())
|
||||
const stream = useStreaming(client => client.public.local.subscribe())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -72,7 +72,7 @@ function onEnter(e: KeyboardEvent) {
|
|||
}
|
||||
|
||||
function escapeAutocomplete(evt: KeyboardEvent) {
|
||||
if (!autocompleteShow)
|
||||
if (!autocompleteShow.value)
|
||||
return
|
||||
autocompleteShow.value = false
|
||||
evt.stopPropagation()
|
||||
|
|
|
@ -3,8 +3,10 @@ import type { BuildInfo } from '~~/types'
|
|||
export interface Team {
|
||||
github: string
|
||||
display: string
|
||||
twitter: string
|
||||
twitter?: string
|
||||
mastodon: string
|
||||
link: string
|
||||
sponsors?: string
|
||||
}
|
||||
|
||||
export const elkTeamMembers: Team[] = [
|
||||
|
@ -13,24 +15,43 @@ export const elkTeamMembers: Team[] = [
|
|||
display: 'Anthony Fu',
|
||||
twitter: 'antfu7',
|
||||
mastodon: 'antfu@webtoo.ls',
|
||||
link: '/m.webtoo.ls/@antfu',
|
||||
},
|
||||
{
|
||||
github: 'patak-dev',
|
||||
display: 'Patak',
|
||||
twitter: 'patak_dev',
|
||||
mastodon: 'patak@webtoo.ls',
|
||||
link: '/m.webtoo.ls/@patak',
|
||||
},
|
||||
{
|
||||
github: 'danielroe',
|
||||
display: 'Daniel Roe',
|
||||
twitter: 'danielcroe',
|
||||
mastodon: 'daniel@roe.dev',
|
||||
link: '/mastodon.roe.dev/@daniel',
|
||||
},
|
||||
{
|
||||
github: 'sxzz',
|
||||
display: '三咲智子 Kevin Deng',
|
||||
twitter: 'sanxiaozhizi',
|
||||
mastodon: 'sxzz@webtoo.ls',
|
||||
link: '/m.webtoo.ls/@sxzz',
|
||||
},
|
||||
{
|
||||
github: 'userquin',
|
||||
display: 'Joaquín Sánchez',
|
||||
twitter: 'userquin',
|
||||
mastodon: 'userquin@webtoo.ls',
|
||||
link: '/m.webtoo.ls/@userquin',
|
||||
sponsors: 'elk-zone', // sponsors/userquin isn't enabled
|
||||
},
|
||||
{
|
||||
github: 'shuuji3',
|
||||
display: 'TAKAHASHI Shuuji',
|
||||
mastodon: 'shuuji3@webtoo.ls',
|
||||
link: '/m.webtoo.ls/@shuuji3',
|
||||
sponsors: 'elk-zone', // sponsors/shuuji3 isn't enabled
|
||||
},
|
||||
].sort(() => Math.random() - 0.5)
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ export function fetchStatus(id: string, force = false): Promise<mastodon.v1.Stat
|
|||
const key = `${server}:${userId}:status:${id}`
|
||||
const cached = cache.get(key)
|
||||
if (cached && !force)
|
||||
return cached
|
||||
return Promise.resolve(cached)
|
||||
|
||||
const promise = useMastoClient().v1.statuses.$select(id).fetch()
|
||||
.then((status) => {
|
||||
cacheStatus(status)
|
||||
|
@ -42,7 +43,8 @@ export function fetchAccountById(id?: string | null): Promise<mastodon.v1.Accoun
|
|||
const key = `${server}:${userId}:account:${id}`
|
||||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
return Promise.resolve(cached)
|
||||
|
||||
const domain = getInstanceDomainFromServer(server)
|
||||
const promise = useMastoClient().v1.accounts.$select(id).fetch()
|
||||
.then((r) => {
|
||||
|
@ -64,7 +66,7 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
|
|||
const key = `${server}:${userId}:account:${userAcct}`
|
||||
const cached = cache.get(key)
|
||||
if (cached)
|
||||
return cached
|
||||
return Promise.resolve(cached)
|
||||
|
||||
async function lookupAccount() {
|
||||
const client = useMastoClient()
|
||||
|
@ -82,13 +84,30 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
|
|||
return account
|
||||
}
|
||||
|
||||
const account = lookupAccount()
|
||||
const promise = lookupAccount()
|
||||
.then((r) => {
|
||||
cacheAccount(r, server, true)
|
||||
return r
|
||||
})
|
||||
cache.set(key, account)
|
||||
return account
|
||||
cache.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
export function fetchTag(tagName: string, force = false): Promise<mastodon.v1.Tag> {
|
||||
const server = currentServer.value
|
||||
const userId = currentUser.value?.account.id
|
||||
const key = `${server}:${userId}:tag:${tagName}`
|
||||
const cached = cache.get(key)
|
||||
if (cached && !force)
|
||||
return Promise.resolve(cached)
|
||||
|
||||
const promise = useMastoClient().v1.tags.$select(tagName).fetch()
|
||||
.then((tag) => {
|
||||
cacheTag(tag)
|
||||
return tag
|
||||
})
|
||||
cache.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
export function useAccountById(id?: string | null) {
|
||||
|
@ -111,3 +130,8 @@ export function cacheAccount(account: mastodon.v1.Account, server = currentServe
|
|||
setCached(`${server}:${userId}:account:${account.id}`, account, override)
|
||||
setCached(`${server}:${userId}:account:${userAcct}`, account, override)
|
||||
}
|
||||
|
||||
export function cacheTag(tag: mastodon.v1.Tag, server = currentServer.value, override?: boolean) {
|
||||
const userId = currentUser.value?.account.id
|
||||
setCached(`${server}:${userId}:tag:${tag.name}`, tag, override)
|
||||
}
|
||||
|
|
|
@ -170,7 +170,8 @@ export const useCommandRegistry = defineStore('command', () => {
|
|||
const indexed = cmds.map((cmd, index) => ({ ...cmd, index }))
|
||||
|
||||
const grouped = new Map<CommandScopeNames, CommandQueryResultItem[]>(
|
||||
scopes.map(scope => [scope, []]))
|
||||
scopes.map(scope => [scope, []]),
|
||||
)
|
||||
for (const cmd of indexed) {
|
||||
const scope = cmd.scope ?? ''
|
||||
grouped.get(scope)!.push({
|
||||
|
@ -244,24 +245,12 @@ export function useCommands(cmds: () => CommandProvider[]) {
|
|||
export function provideGlobalCommands() {
|
||||
const { locale, t } = useI18n()
|
||||
const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
|
||||
const router = useRouter()
|
||||
const users = useUsers()
|
||||
const masto = useMasto()
|
||||
const colorMode = useColorMode()
|
||||
const userSettings = useUserSettings()
|
||||
const { singleInstanceServer, oauth } = useSignIn()
|
||||
|
||||
useCommand({
|
||||
scope: 'Navigation',
|
||||
|
||||
name: () => t('nav.settings'),
|
||||
icon: 'i-ri:settings-3-line',
|
||||
|
||||
onActivate() {
|
||||
router.push('/settings')
|
||||
},
|
||||
})
|
||||
|
||||
useCommand({
|
||||
scope: 'Preferences',
|
||||
|
||||
|
|
|
@ -494,7 +494,10 @@ function _markdownProcess(value: string) {
|
|||
|
||||
let start = 0
|
||||
while (true) {
|
||||
let found: { match: RegExpMatchArray; replacer: (c: (string | Node)[]) => Node } | undefined
|
||||
let found: {
|
||||
match: RegExpMatchArray
|
||||
replacer: (c: (string | Node)[]) => Node
|
||||
} | undefined
|
||||
|
||||
for (const [re, replacer] of _markdownReplacements) {
|
||||
re.lastIndex = start
|
||||
|
@ -524,10 +527,21 @@ function transformMarkdown(node: Node) {
|
|||
return _markdownProcess(node.value)
|
||||
}
|
||||
|
||||
function addBdiParagraphs(node: Node) {
|
||||
if (node.name === 'p' && !('dir' in node.attributes) && node.children?.length && node.children.length > 1)
|
||||
node.attributes.dir = 'auto'
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
function transformParagraphs(node: Node): Node | Node[] {
|
||||
// Add bdi to paragraphs
|
||||
addBdiParagraphs(node)
|
||||
|
||||
// For top level paragraphs, inject an empty <p> to preserve status paragraphs in our editor (except for the last one)
|
||||
if (node.parent?.type === DOCUMENT_NODE && node.name === 'p' && node.parent.children.at(-1) !== node)
|
||||
return [node, h('p')]
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
|
@ -609,7 +623,7 @@ function transformCollapseMentions(status?: mastodon.v1.Status, inReplyToStatus?
|
|||
|
||||
// We have a special case for single mentions that are part of a reply.
|
||||
// We already have the replying to badge in this case or the status is connected to the previous one.
|
||||
// This is needed because the status doesn't included the in Reply to handle, only the account id.
|
||||
// This is needed because the status doesn't include the in Reply to handle, only the account id.
|
||||
// But this covers the majority of cases.
|
||||
const showMentions = !(contextualMentionsCount === 0 || (mentionsCount === 1 && status?.inReplyToAccountId))
|
||||
const grouped = contextualMentionsCount > 2
|
||||
|
|
|
@ -10,6 +10,7 @@ import Emoji from '~/components/emoji/Emoji.vue'
|
|||
import ContentCode from '~/components/content/ContentCode.vue'
|
||||
import ContentMentionGroup from '~/components/content/ContentMentionGroup.vue'
|
||||
import AccountHoverWrapper from '~/components/account/AccountHoverWrapper.vue'
|
||||
import TagHoverWrapper from '~/components/account/TagHoverWrapper.vue'
|
||||
|
||||
function getTextualAstComponents(astChildren: Node[]): string {
|
||||
return astChildren
|
||||
|
@ -128,11 +129,13 @@ function handleMention(el: Node) {
|
|||
addBdiNode(el)
|
||||
return h(AccountHoverWrapper, { handle, class: 'inline-block' }, () => nodeToVNode(el))
|
||||
}
|
||||
|
||||
const matchTag = href.match(TagLinkRE)
|
||||
if (matchTag) {
|
||||
const [, , name] = matchTag
|
||||
const [, , tagName] = matchTag
|
||||
addBdiNode(el)
|
||||
el.attributes.href = `/${currentServer.value}/tags/${name}`
|
||||
el.attributes.href = `/${currentServer.value}/tags/${tagName}`
|
||||
return h(TagHoverWrapper, { tagName, class: 'inline-block' }, () => nodeToVNode(el))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { mastodon } from 'masto'
|
||||
import type { ConfirmDialogChoice, ConfirmDialogLabel, Draft, ErrorDialogData } from '~/types'
|
||||
import type { ConfirmDialogChoice, ConfirmDialogOptions, Draft, ErrorDialogData } from '~/types'
|
||||
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
|
||||
|
||||
export const confirmDialogChoice = ref<ConfirmDialogChoice>()
|
||||
export const confirmDialogLabel = ref<ConfirmDialogLabel>()
|
||||
export const confirmDialogLabel = ref<ConfirmDialogOptions>()
|
||||
export const errorDialogData = ref<ErrorDialogData>()
|
||||
|
||||
export const mediaPreviewList = ref<mastodon.v1.MediaAttachment[]>([])
|
||||
|
@ -39,7 +39,7 @@ export function openSigninDialog() {
|
|||
isSigninDialogOpen.value = true
|
||||
}
|
||||
|
||||
export async function openConfirmDialog(label: ConfirmDialogLabel | string): Promise<ConfirmDialogChoice> {
|
||||
export async function openConfirmDialog(label: ConfirmDialogOptions | string): Promise<ConfirmDialogChoice> {
|
||||
confirmDialogLabel.value = typeof label === 'string' ? { title: label } : label
|
||||
confirmDialogChoice.value = undefined
|
||||
isConfirmDialogOpen.value = true
|
||||
|
|
|
@ -29,8 +29,10 @@ export function useHumanReadableNumber() {
|
|||
}
|
||||
}
|
||||
|
||||
export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>,
|
||||
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
|
||||
export function useFormattedDateTime(
|
||||
value: MaybeRefOrGetter<string | number | Date | undefined | null>,
|
||||
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' },
|
||||
) {
|
||||
const { locale } = useI18n()
|
||||
const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
|
||||
return computed(() => {
|
||||
|
@ -75,3 +77,32 @@ export function useTimeAgoOptions(short = false): UseTimeAgoOptions<false> {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function useFileSizeFormatter() {
|
||||
const { locale } = useI18n()
|
||||
|
||||
const formatters = computed(() => ([
|
||||
Intl.NumberFormat(locale.value, {
|
||||
style: 'unit',
|
||||
unit: 'megabyte',
|
||||
unitDisplay: 'narrow',
|
||||
maximumFractionDigits: 0,
|
||||
}),
|
||||
Intl.NumberFormat(locale.value, {
|
||||
style: 'unit',
|
||||
unit: 'kilobyte',
|
||||
unitDisplay: 'narrow',
|
||||
maximumFractionDigits: 0,
|
||||
}),
|
||||
]))
|
||||
|
||||
const megaByte = 1024 * 1024
|
||||
|
||||
function formatFileSize(size: number) {
|
||||
return size >= megaByte
|
||||
? formatters.value[0].format(size / megaByte)
|
||||
: formatters.value[1].format(size / 1024)
|
||||
}
|
||||
|
||||
return { formatFileSize }
|
||||
}
|
||||
|
|
|
@ -36,9 +36,11 @@ export function useMagicSequence(keys: string[]): ComputedRef<boolean> {
|
|||
down = false
|
||||
success.value = true
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
return computed(() => success.value)
|
||||
}
|
||||
|
|
|
@ -36,26 +36,25 @@ export function usePublish(options: {
|
|||
const { params, attachments } = draft.value
|
||||
const firstEmptyInputIndex = params.poll?.options.findIndex(option => option.trim().length === 0)
|
||||
return isEmpty.value
|
||||
|| options.isUploading.value
|
||||
|| isSending.value
|
||||
|| (attachments.length === 0 && !params.status)
|
||||
|| failedMessages.value.length > 0
|
||||
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|
||||
|| ((params.poll !== null && params.poll !== undefined)
|
||||
&& (
|
||||
(firstEmptyInputIndex !== -1
|
||||
&& firstEmptyInputIndex !== params.poll.options.length - 1
|
||||
)
|
||||
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|
||||
|| (new Set(params.poll.options).size !== params.poll.options.length)
|
||||
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
|
||||
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
|
||||
)
|
||||
)
|
||||
)
|
||||
|| options.isUploading.value
|
||||
|| isSending.value
|
||||
|| (attachments.length === 0 && !params.status)
|
||||
|| failedMessages.value.length > 0
|
||||
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|
||||
|| ((params.poll !== null && params.poll !== undefined)
|
||||
&& (
|
||||
(firstEmptyInputIndex !== -1
|
||||
&& firstEmptyInputIndex !== params.poll.options.length - 1
|
||||
)
|
||||
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|
||||
|| (new Set(params.poll.options).size !== params.poll.options.length)
|
||||
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
|
||||
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
|
||||
)
|
||||
))
|
||||
})
|
||||
|
||||
watch(() => draft, () => {
|
||||
watch(draft, () => {
|
||||
if (failedMessages.value.length > 0)
|
||||
failedMessages.value.length = 0
|
||||
}, { deep: true })
|
||||
|
@ -156,6 +155,7 @@ export type MediaAttachmentUploadError = [filename: string, message: string]
|
|||
export function useUploadMediaAttachment(draft: Ref<Draft>) {
|
||||
const { client } = useMasto()
|
||||
const { t } = useI18n()
|
||||
const { formatFileSize } = useFileSizeFormatter()
|
||||
|
||||
const isUploading = ref<boolean>(false)
|
||||
const isExceedingAttachmentLimit = ref<boolean>(false)
|
||||
|
@ -164,7 +164,7 @@ export function useUploadMediaAttachment(draft: Ref<Draft>) {
|
|||
|
||||
const maxPixels = computed(() => {
|
||||
return currentInstance.value?.configuration?.mediaAttachments?.imageMatrixLimit
|
||||
?? 4096 ** 2
|
||||
?? 4096 ** 2
|
||||
})
|
||||
|
||||
const loadImage = (inputFile: Blob) => new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
|
@ -225,8 +225,32 @@ export function useUploadMediaAttachment(draft: Ref<Draft>) {
|
|||
// TODO: display some kind of message if too many media are selected
|
||||
// DONE
|
||||
const limit = currentInstance.value!.configuration?.statuses.maxMediaAttachments || 4
|
||||
const maxVideoSize = currentInstance.value!.configuration?.mediaAttachments.videoSizeLimit || 0
|
||||
const maxImageSize = currentInstance.value!.configuration?.mediaAttachments.imageSizeLimit || 0
|
||||
for (const file of files.slice(0, limit)) {
|
||||
if (draft.value.attachments.length < limit) {
|
||||
if (file.type.startsWith('image/')) {
|
||||
if (maxImageSize > 0 && file.size > maxImageSize) {
|
||||
failedAttachments.value = [...failedAttachments.value, [file.name, t('state.attachments_limit_image_error', [formatFileSize(maxImageSize)])]]
|
||||
continue
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (maxVideoSize > 0 && file.size > maxVideoSize) {
|
||||
const key
|
||||
= file.type.startsWith('audio/')
|
||||
? 'state.attachments_limit_audio_error'
|
||||
: file.type.startsWith('video/')
|
||||
? 'state.attachments_limit_video_error'
|
||||
: 'state.attachments_limit_unknown_error'
|
||||
const errorMessage = t(key, [formatFileSize(maxVideoSize)])
|
||||
failedAttachments.value = [
|
||||
...failedAttachments.value,
|
||||
[file.name, errorMessage],
|
||||
]
|
||||
continue
|
||||
}
|
||||
}
|
||||
isExceedingAttachmentLimit.value = false
|
||||
try {
|
||||
const attachment = await client.value.v1.media.create({
|
||||
|
|
|
@ -11,9 +11,12 @@ let timeoutHandle: NodeJS.Timeout | undefined
|
|||
export function useRelationship(account: mastodon.v1.Account): Ref<mastodon.v1.Relationship | undefined> {
|
||||
if (!currentUser.value)
|
||||
return ref()
|
||||
|
||||
let relationship = requestedRelationships.get(account.id)
|
||||
if (relationship)
|
||||
return relationship
|
||||
|
||||
// allow batch relationship requests
|
||||
relationship = ref<mastodon.v1.Relationship | undefined>()
|
||||
requestedRelationships.set(account.id, relationship)
|
||||
if (timeoutHandle)
|
||||
|
@ -22,14 +25,19 @@ export function useRelationship(account: mastodon.v1.Account): Ref<mastodon.v1.R
|
|||
timeoutHandle = undefined
|
||||
fetchRelationships()
|
||||
}, 100)
|
||||
|
||||
return relationship
|
||||
}
|
||||
|
||||
async function fetchRelationships() {
|
||||
const requested = Array.from(requestedRelationships.entries()).filter(([, r]) => !r.value)
|
||||
const relationships = await useMastoClient().v1.accounts.relationships.fetch({ id: requested.map(([id]) => id) })
|
||||
for (let i = 0; i < requested.length; i++)
|
||||
requested[i][1].value = relationships[i]
|
||||
for (const relationship of relationships) {
|
||||
const requestedToUpdate = requested.find(([id]) => id === relationship.id)
|
||||
if (!requestedToUpdate)
|
||||
continue
|
||||
requestedToUpdate[1].value = relationship
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleFollowAccount(relationship: mastodon.v1.Relationship, account: mastodon.v1.Account) {
|
||||
|
@ -39,12 +47,13 @@ export async function toggleFollowAccount(relationship: mastodon.v1.Relationship
|
|||
const unfollow = relationship!.following || relationship!.requested
|
||||
|
||||
if (unfollow) {
|
||||
if (await openConfirmDialog({
|
||||
const confirmUnfollow = await openConfirmDialog({
|
||||
title: i18n.t('confirm.unfollow.title'),
|
||||
description: i18n.t('confirm.unfollow.description', [`@${account.acct}`]),
|
||||
confirm: i18n.t('confirm.unfollow.confirm'),
|
||||
cancel: i18n.t('confirm.unfollow.cancel'),
|
||||
}) !== 'confirm')
|
||||
})
|
||||
if (confirmUnfollow.choice !== 'confirm')
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -66,18 +75,28 @@ export async function toggleMuteAccount(relationship: mastodon.v1.Relationship,
|
|||
const { client } = useMasto()
|
||||
const i18n = useNuxtApp().$i18n
|
||||
|
||||
if (!relationship!.muting && await openConfirmDialog({
|
||||
title: i18n.t('confirm.mute_account.title'),
|
||||
description: i18n.t('confirm.mute_account.description', [account.acct]),
|
||||
confirm: i18n.t('confirm.mute_account.confirm'),
|
||||
cancel: i18n.t('confirm.mute_account.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
let duration = 0 // default 0 == indefinite
|
||||
let notifications = true // default true = mute notifications
|
||||
if (!relationship!.muting) {
|
||||
const confirmMute = await openConfirmDialog({
|
||||
title: i18n.t('confirm.mute_account.title'),
|
||||
description: i18n.t('confirm.mute_account.description', [account.acct]),
|
||||
confirm: i18n.t('confirm.mute_account.confirm'),
|
||||
cancel: i18n.t('confirm.mute_account.cancel'),
|
||||
extraOptionType: 'mute',
|
||||
})
|
||||
if (confirmMute.choice !== 'confirm')
|
||||
return
|
||||
|
||||
duration = confirmMute.extraOptions!.mute.duration
|
||||
notifications = confirmMute.extraOptions!.mute.notifications
|
||||
}
|
||||
|
||||
relationship!.muting = !relationship!.muting
|
||||
relationship = relationship!.muting
|
||||
? await client.value.v1.accounts.$select(account.id).mute({
|
||||
// TODO support more options
|
||||
duration,
|
||||
notifications,
|
||||
})
|
||||
: await client.value.v1.accounts.$select(account.id).unmute()
|
||||
}
|
||||
|
@ -86,13 +105,16 @@ export async function toggleBlockAccount(relationship: mastodon.v1.Relationship,
|
|||
const { client } = useMasto()
|
||||
const i18n = useNuxtApp().$i18n
|
||||
|
||||
if (!relationship!.blocking && await openConfirmDialog({
|
||||
title: i18n.t('confirm.block_account.title'),
|
||||
description: i18n.t('confirm.block_account.description', [account.acct]),
|
||||
confirm: i18n.t('confirm.block_account.confirm'),
|
||||
cancel: i18n.t('confirm.block_account.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
if (!relationship!.blocking) {
|
||||
const confirmBlock = await openConfirmDialog({
|
||||
title: i18n.t('confirm.block_account.title'),
|
||||
description: i18n.t('confirm.block_account.description', [account.acct]),
|
||||
confirm: i18n.t('confirm.block_account.confirm'),
|
||||
cancel: i18n.t('confirm.block_account.cancel'),
|
||||
})
|
||||
if (confirmBlock.choice !== 'confirm')
|
||||
return
|
||||
}
|
||||
|
||||
relationship!.blocking = !relationship!.blocking
|
||||
relationship = await client.value.v1.accounts.$select(account.id)[relationship!.blocking ? 'block' : 'unblock']()
|
||||
|
@ -102,13 +124,16 @@ export async function toggleBlockDomain(relationship: mastodon.v1.Relationship,
|
|||
const { client } = useMasto()
|
||||
const i18n = useNuxtApp().$i18n
|
||||
|
||||
if (!relationship!.domainBlocking && await openConfirmDialog({
|
||||
title: i18n.t('confirm.block_domain.title'),
|
||||
description: i18n.t('confirm.block_domain.description', [getServerName(account)]),
|
||||
confirm: i18n.t('confirm.block_domain.confirm'),
|
||||
cancel: i18n.t('confirm.block_domain.cancel'),
|
||||
}) !== 'confirm')
|
||||
return
|
||||
if (!relationship!.domainBlocking) {
|
||||
const confirmDomainBlock = await openConfirmDialog({
|
||||
title: i18n.t('confirm.block_domain.title'),
|
||||
description: i18n.t('confirm.block_domain.description', [getServerName(account)]),
|
||||
confirm: i18n.t('confirm.block_domain.confirm'),
|
||||
cancel: i18n.t('confirm.block_domain.cancel'),
|
||||
})
|
||||
if (confirmDomainBlock.choice !== 'confirm')
|
||||
return
|
||||
}
|
||||
|
||||
relationship!.domainBlocking = !relationship!.domainBlocking
|
||||
await client.value.v1.domainBlocks[relationship!.domainBlocking ? 'create' : 'remove']({ domain: getServerName(account) })
|
||||
|
|
|
@ -59,11 +59,11 @@ export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOpt
|
|||
}
|
||||
|
||||
watch(() => resolveUnref(query), () => {
|
||||
loading.value = !!(q && isHydrated.value)
|
||||
loading.value = !!(q.value && isHydrated.value)
|
||||
})
|
||||
|
||||
debouncedWatch(() => resolveUnref(query), async () => {
|
||||
if (!q || !isHydrated.value)
|
||||
if (!q.value || !isHydrated.value)
|
||||
return
|
||||
|
||||
loading.value = true
|
||||
|
@ -87,7 +87,7 @@ export function useSearch(query: MaybeRefOrGetter<string>, options: UseSearchOpt
|
|||
}, { debounce: 300 })
|
||||
|
||||
const next = async () => {
|
||||
if (!q || !isHydrated.value || !paginator)
|
||||
if (!q.value || !isHydrated.value || !paginator)
|
||||
return
|
||||
|
||||
loading.value = true
|
||||
|
|
|
@ -20,7 +20,7 @@ function getDefaultVisibility(currentVisibility: mastodon.v1.StatusVisibility) {
|
|||
// the post more private than the replying to post
|
||||
const preferredVisibility = currentUser.value?.account.source.privacy || 'public'
|
||||
return ALL_VISIBILITY.indexOf(currentVisibility)
|
||||
> ALL_VISIBILITY.indexOf(preferredVisibility)
|
||||
> ALL_VISIBILITY.indexOf(preferredVisibility)
|
||||
? currentVisibility
|
||||
: preferredVisibility
|
||||
}
|
||||
|
|
|
@ -95,7 +95,12 @@ export async function translateText(text: string, from: string | null | undefine
|
|||
return status
|
||||
}
|
||||
|
||||
const translations = new WeakMap<mastodon.v1.Status | mastodon.v1.StatusEdit, { visible: boolean; text: string; success: boolean; error: string }>()
|
||||
const translations = new WeakMap<mastodon.v1.Status | mastodon.v1.StatusEdit, {
|
||||
visible: boolean
|
||||
text: string
|
||||
success: boolean
|
||||
error: string
|
||||
}>()
|
||||
|
||||
export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEdit, to: string) {
|
||||
if (!translations.has(status))
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { PaginatorState } from '~/types'
|
|||
export function usePaginator<T, P, U = T>(
|
||||
_paginator: mastodon.Paginator<T[], P>,
|
||||
stream: Ref<mastodon.streaming.Subscription | undefined>,
|
||||
eventType: 'update' | 'notification' = 'update',
|
||||
preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[],
|
||||
buffer = 10,
|
||||
) {
|
||||
|
@ -34,10 +35,10 @@ export function usePaginator<T, P, U = T>(
|
|||
return
|
||||
|
||||
for await (const entry of stream) {
|
||||
if (entry.event === 'update') {
|
||||
if (entry.event === eventType) {
|
||||
const status = entry.payload
|
||||
|
||||
if ('uri' in entry)
|
||||
if ('uri' in status)
|
||||
cacheStatus(status, undefined, true)
|
||||
|
||||
const index = prevItems.value.findIndex((i: any) => i.id === status.id)
|
||||
|
|
|
@ -6,10 +6,12 @@ import type {
|
|||
} from '~/composables/push-notifications/types'
|
||||
import { PushSubscriptionError } from '~/composables/push-notifications/types'
|
||||
|
||||
export async function createPushSubscription(user: RequiredUserLogin,
|
||||
export async function createPushSubscription(
|
||||
user: RequiredUserLogin,
|
||||
notificationData: CreatePushNotification,
|
||||
policy: mastodon.v1.WebPushSubscriptionPolicy = 'all',
|
||||
force = false): Promise<mastodon.v1.WebPushSubscription | undefined> {
|
||||
force = false,
|
||||
): Promise<mastodon.v1.WebPushSubscription | undefined> {
|
||||
const { server: serverEndpoint, vapidKey } = user
|
||||
|
||||
return await getRegistration()
|
||||
|
|
|
@ -64,7 +64,7 @@ export function usePushManager() {
|
|||
policy?: mastodon.v1.WebPushSubscriptionPolicy,
|
||||
force?: boolean,
|
||||
): Promise<SubscriptionResult> => {
|
||||
if (!isSupported)
|
||||
if (!isSupported.value)
|
||||
return 'not-supported'
|
||||
|
||||
if (!currentUser.value)
|
||||
|
@ -87,7 +87,10 @@ export function usePushManager() {
|
|||
|
||||
currentUser.value.pushSubscription = await createPushSubscription(
|
||||
{
|
||||
pushSubscription, server, token, vapidKey,
|
||||
pushSubscription,
|
||||
server,
|
||||
token,
|
||||
vapidKey,
|
||||
},
|
||||
notificationData ?? {
|
||||
alerts: {
|
||||
|
@ -109,7 +112,7 @@ export function usePushManager() {
|
|||
}
|
||||
|
||||
const unsubscribe = async () => {
|
||||
if (!isSupported || !isSubscribed || !currentUser.value)
|
||||
if (!isSupported.value || !isSubscribed.value || !currentUser.value)
|
||||
return false
|
||||
|
||||
await removePushNotifications(currentUser.value)
|
||||
|
|
|
@ -2,5 +2,6 @@ import { breakpointsTailwind } from '@vueuse/core'
|
|||
|
||||
export const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
export const isSmallScreen = breakpoints.smallerOrEqual('sm')
|
||||
export const isMediumOrLargeScreen = breakpoints.between('sm', 'xl')
|
||||
export const isExtraLargeScreen = breakpoints.smallerOrEqual('xl')
|
||||
|
|
|
@ -9,6 +9,7 @@ export type ColorMode = 'light' | 'dark' | 'system'
|
|||
|
||||
export interface PreferencesSettings {
|
||||
hideAltIndicatorOnPosts: boolean
|
||||
hideGifIndicatorOnPosts: boolean
|
||||
hideBoostCount: boolean
|
||||
hideReplyCount: boolean
|
||||
hideFavoriteCount: boolean
|
||||
|
@ -16,6 +17,7 @@ export interface PreferencesSettings {
|
|||
hideTranslation: boolean
|
||||
hideUsernameEmojis: boolean
|
||||
hideAccountHoverCard: boolean
|
||||
hideTagHoverCard: boolean
|
||||
hideNews: boolean
|
||||
grayscaleMode: boolean
|
||||
enableAutoplay: boolean
|
||||
|
@ -63,6 +65,7 @@ export function getDefaultLanguage(languages: string[]) {
|
|||
|
||||
export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
|
||||
hideAltIndicatorOnPosts: false,
|
||||
hideGifIndicatorOnPosts: false,
|
||||
hideBoostCount: false,
|
||||
hideReplyCount: false,
|
||||
hideFavoriteCount: false,
|
||||
|
@ -70,6 +73,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
|
|||
hideTranslation: false,
|
||||
hideUsernameEmojis: false,
|
||||
hideAccountHoverCard: false,
|
||||
hideTagHoverCard: false,
|
||||
hideNews: false,
|
||||
grayscaleMode: false,
|
||||
enableAutoplay: true,
|
||||
|
|
|
@ -5,7 +5,10 @@ const highlighter = ref<Highlighter>()
|
|||
const registeredLang = ref(new Map<string, boolean>())
|
||||
let shikiImport: Promise<void> | undefined
|
||||
|
||||
export function useHighlighter(lang: Lang): { promise?: Promise<void>; highlighter?: Highlighter } {
|
||||
export function useHighlighter(lang: Lang): {
|
||||
promise?: Promise<void>
|
||||
highlighter?: Highlighter
|
||||
} {
|
||||
if (!shikiImport) {
|
||||
shikiImport = import('shiki')
|
||||
.then(async ({ getHighlighter }) => {
|
||||
|
|
|
@ -55,11 +55,17 @@ export function useTiptap(options: UseTiptapOptions) {
|
|||
},
|
||||
}),
|
||||
Mention.configure({
|
||||
renderHTML({ options, node }) {
|
||||
return ['span', { 'data-type': 'mention', 'data-id': node.attrs.id }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
|
||||
},
|
||||
suggestion: TiptapMentionSuggestion,
|
||||
}),
|
||||
Mention
|
||||
.extend({ name: 'hashtag' })
|
||||
.configure({
|
||||
renderHTML({ options, node }) {
|
||||
return ['span', { 'data-type': 'hashtag', 'data-id': node.attrs.id }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
|
||||
},
|
||||
suggestion: TiptapHashtagSuggestion,
|
||||
}),
|
||||
Mention
|
||||
|
|
|
@ -16,7 +16,11 @@ declare module '@tiptap/core' {
|
|||
/**
|
||||
* Insert a custom emoji.
|
||||
*/
|
||||
insertCustomEmoji: (options: { src: string; alt?: string; title?: string }) => ReturnType
|
||||
insertCustomEmoji: (options: {
|
||||
src: string
|
||||
alt?: string
|
||||
title?: string
|
||||
}) => ReturnType
|
||||
/**
|
||||
* Insert a emoji.
|
||||
*/
|
||||
|
|
|
@ -1,36 +1,42 @@
|
|||
import type { ExtendedRegExpMatchArray, InputRuleFinder, nodeInputRule } from '@tiptap/core'
|
||||
import type { NodeType } from '@tiptap/pm/model'
|
||||
import {
|
||||
InputRule,
|
||||
Node,
|
||||
callOrReturn,
|
||||
mergeAttributes,
|
||||
nodeInputRule,
|
||||
nodePasteRule,
|
||||
} from '@tiptap/core'
|
||||
import { emojiRegEx, getEmojiAttributes } from '~/config/emojis'
|
||||
|
||||
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(nodeRule: NR,
|
||||
type: Parameters<NR>[0]['type']): ReturnType<NR>[] {
|
||||
const rule = nodeRule({
|
||||
find: emojiRegEx as RegExp,
|
||||
type,
|
||||
getAttributes: (match) => {
|
||||
const [native] = match
|
||||
return getEmojiAttributes(native)
|
||||
},
|
||||
}) as ReturnType<NR>
|
||||
|
||||
// Error catch for unsupported emoji
|
||||
const handler = rule.handler.bind(rule)
|
||||
rule.handler = (...args) => {
|
||||
function wrapHandler<T extends (...args: any[]) => any>(handler: T): T {
|
||||
return <T>((...args: any[]) => {
|
||||
try {
|
||||
return handler(...args)
|
||||
}
|
||||
catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [
|
||||
rule,
|
||||
]
|
||||
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(
|
||||
nodeRule: NR,
|
||||
type: Parameters<NR>[0]['type'],
|
||||
): ReturnType<NR>[] {
|
||||
const rule = nodeRule({
|
||||
find: emojiRegEx as RegExp,
|
||||
type,
|
||||
getAttributes: (match: ExtendedRegExpMatchArray) => {
|
||||
const [native] = match
|
||||
return getEmojiAttributes(native)
|
||||
},
|
||||
}) as ReturnType<NR>
|
||||
|
||||
// Error catch for unsupported emoji
|
||||
rule.handler = wrapHandler(rule.handler.bind(rule))
|
||||
|
||||
return [rule]
|
||||
}
|
||||
|
||||
export const TiptapPluginEmoji = Node.create({
|
||||
|
@ -78,7 +84,34 @@ export const TiptapPluginEmoji = Node.create({
|
|||
},
|
||||
|
||||
addInputRules() {
|
||||
return createEmojiRule(nodeInputRule, this.type)
|
||||
function emojiInputRule(config: {
|
||||
find: InputRuleFinder
|
||||
type: NodeType
|
||||
getAttributes?:
|
||||
| Record<string, any>
|
||||
| ((match: ExtendedRegExpMatchArray) => Record<string, any>)
|
||||
| false
|
||||
| null
|
||||
}) {
|
||||
return new InputRule({
|
||||
find: config.find,
|
||||
handler: ({ state, range, match }) => {
|
||||
const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
|
||||
const { tr } = state
|
||||
const start = range.from
|
||||
const end = range.to
|
||||
|
||||
tr.insert(start, config.type.create(attributes)).delete(
|
||||
tr.mapping.map(start),
|
||||
tr.mapping.map(end),
|
||||
)
|
||||
|
||||
tr.scrollIntoView()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return createEmojiRule(emojiInputRule, this.type)
|
||||
},
|
||||
|
||||
addPasteRules() {
|
||||
|
|
|
@ -14,7 +14,6 @@ export default defineI18nConfig(() => {
|
|||
missingWarn: true,
|
||||
datetimeFormats,
|
||||
numberFormats,
|
||||
// eslint-disable-next-line @typescript-eslint/comma-dangle
|
||||
pluralRules
|
||||
pluralRules,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -88,6 +88,26 @@ const locales: LocaleObjectData[] = [
|
|||
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
|
||||
},
|
||||
} satisfies LocaleObjectData),
|
||||
({
|
||||
code: 'ckb',
|
||||
file: 'ckb.json',
|
||||
name: 'کوردیی ناوەندی',
|
||||
dir: 'rtl',
|
||||
pluralRule: (choice: number) => {
|
||||
const name = new Intl.PluralRules('ckb').select(choice)
|
||||
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
|
||||
},
|
||||
} satisfies LocaleObjectData),
|
||||
({
|
||||
code: 'fa-IR',
|
||||
file: 'fa-IR.json',
|
||||
name: 'فارسی',
|
||||
dir: 'rtl',
|
||||
pluralRule: (choice: number) => {
|
||||
const name = new Intl.PluralRules('fa-IR').select(choice)
|
||||
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
|
||||
},
|
||||
} satisfies LocaleObjectData),
|
||||
{
|
||||
code: 'ca',
|
||||
file: 'ca.json',
|
||||
|
|
|
@ -22,7 +22,16 @@ export const STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS = 'elk-hide-explore-tags-tips'
|
|||
export const STORAGE_KEY_NOTIFICATION = 'elk-notification'
|
||||
export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy'
|
||||
export const STORAGE_KEY_PWA_HIDE_INSTALL = 'elk-pwa-hide-install'
|
||||
export const STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE = 'elk-last-accessed-notification-route'
|
||||
export const STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE = 'elk-last-accessed-explore-route'
|
||||
|
||||
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
||||
|
||||
export const NOTIFICATION_FILTER_TYPES: mastodon.v1.NotificationType[] = ['status', 'reblog', 'follow', 'follow_request', 'favourite', 'poll', 'update', 'admin.sign_up', 'admin.report']
|
||||
|
||||
export const THEME_COLORS = {
|
||||
themeDark: '#111111',
|
||||
themeLight: '#fafafa',
|
||||
backgroundDark: '#fafafa',
|
||||
backgroundLight: '#111111',
|
||||
} as const
|
||||
|
|
|
@ -30,7 +30,7 @@ We use [Docus](https://docus.dev) as the site generator and deploy through Netli
|
|||
|
||||
When you are ready to submit work back to the main Elk repo, create a PR.
|
||||
|
||||
1. If it has been a bit, synchronize your fork with the upstream repo on GitHub.
|
||||
1. If it has been a bit, synchronize your fork with the upstream repo on GitHub.
|
||||
2. Do your work in a branch on your fork
|
||||
Use `git checkout -b branchNameToUse` to create a working branch separate from `main`.
|
||||
3. Do your work in your preferred editor
|
||||
|
@ -44,7 +44,7 @@ When you are ready to submit work back to the main Elk repo, create a PR.
|
|||
- base branch should be **main**
|
||||
- Head repository should be your fork
|
||||
- Compare branch should be your working branch you want to submit
|
||||
If you don't see four drop downs, be sure you are comparing across forks.
|
||||
If you don't see four drop-downs, be sure you are comparing across forks.
|
||||
10. Add a description of the changes your request makes
|
||||
11. Select **Add Pull Request**
|
||||
|
||||
|
@ -65,7 +65,7 @@ Avoid screenshots until Elk reaches a stable release.
|
|||
|
||||
### Standards
|
||||
|
||||
Write in **American English** using spelling as found in [Merriam Webster](https://www.merriam-webster.com).
|
||||
Write in **American English** using spelling as found in [Merriam-Webster](https://www.merriam-webster.com).
|
||||
Translation and localization will be handled separately as/when availability or necessity allow.
|
||||
|
||||
Use [**semantic linefeeds**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/) with no more than one sentence per line.
|
||||
|
|
|
@ -14,10 +14,10 @@ css({
|
|||
gap: '0.5rem',
|
||||
fontSize: '1.5rem',
|
||||
},
|
||||
img: {
|
||||
'img': {
|
||||
flexShrink: 0,
|
||||
aspectRatio: '1/1',
|
||||
height: '2.5rem'
|
||||
}
|
||||
height: '2.5rem',
|
||||
},
|
||||
})
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'ToogleIcon',
|
||||
name: 'ToggleIcon',
|
||||
props: { up: Boolean },
|
||||
}
|
||||
</script>
|
|
@ -61,8 +61,8 @@ async function copyToClipboard() {
|
|||
try {
|
||||
await navigator.clipboard.writeText([
|
||||
`# ${localeTitle.value}`,
|
||||
(localeTab.value === 'missing' ? missingEntries.value : outdatedEntries.value).join('\n')].join('\n'),
|
||||
)
|
||||
(localeTab.value === 'missing' ? missingEntries.value : outdatedEntries.value).join('\n'),
|
||||
].join('\n'))
|
||||
copied.value = true
|
||||
setTimeout(() => copied.value = false, 750)
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ async function copyToClipboard() {
|
|||
>
|
||||
<td :class="[{ expandable: !isSource }]">
|
||||
<div>
|
||||
<ToogleIcon v-if="!isSource" :up="hidden || key !== locale" />
|
||||
<ToggleIcon v-if="!isSource" :up="hidden || key !== locale" />
|
||||
{{ title }}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -35,7 +35,6 @@ You can think of the service as something similar to Twitter, but with some key
|
|||
|
||||
For more details about Mastodon, see their [website](https://joinmastodon.org/), [blog](https://blog.joinmastodon.org), [docs](https://docs.joinmastodon.org), and [GitHub Repository](https://github.com/mastodon/mastodon).
|
||||
|
||||
|
||||
## What is a Mastodon Client?
|
||||
|
||||
A Mastodon Client is software that serves up the posts and activities from a Mastodon server using the [Mastodon API](https://docs.joinmastodon.org/api/).
|
||||
|
@ -80,7 +79,6 @@ Desktop
|
|||
[TheDesk](https://thedesk.top/en/), [Whalebird](https://whalebird.social/en), or [Sengi](https://nicolasconstant.github.io/sengi/)
|
||||
::
|
||||
|
||||
|
||||
All of these apps provide some combination of the features a typical Mastodon user expects for their account.
|
||||
|
||||
### Browser-based Mastodon Clients
|
||||
|
|
|
@ -67,7 +67,6 @@ Options include:
|
|||
Elk reorders timeline posts that are connected to each other to display them in a connected thread in the timelines.
|
||||
This helps keep the conversation context for a post and its replies.
|
||||
|
||||
|
||||
## Improved notifications
|
||||
|
||||
Elk groups boosts and likes to the same post when they occur together in the notification history.
|
||||
|
@ -76,7 +75,6 @@ This greatly simplifies your notification history.
|
|||
|
||||
<!-- ## GitHub HTML cards -->
|
||||
|
||||
|
||||
<!-- - markdown support
|
||||
- GitHub HTML cards
|
||||
- and so on... -->
|
||||
|
|
|
@ -4,7 +4,7 @@ We're really excited that you're interested in contributing to Elk! Before submi
|
|||
|
||||
## Online
|
||||
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
You can use [StackBlitz Codeflow](https://stackblitz.com/codeflow) to fix bugs or implement features. You'll also see a Codeflow button on PRs to review them without a local setup. Once the elk repo has been cloned in Codeflow, the dev server will start automatically and print the URL to open the App. You should receive a prompt in the bottom-right suggesting to open it in the Editor or in another Tab. To learn more, check out the [Codeflow docs](https://developer.stackblitz.com/codeflow/what-is-codeflow).
|
||||
|
||||
[![Open in Codeflow](https://developer.stackblitz.com/img/open_in_codeflow.svg)](https://pr.new/elk-zone/elk)
|
||||
|
||||
|
|
|
@ -56,6 +56,6 @@ On your project page open the Deploys tab, click on "Trigger deploy" and "Deploy
|
|||
## Use a custom domain
|
||||
If you want to use a custom domain, go to "Domain settings" on your Netlify project page, and press "Add custom domain". If your domain is not bought from Netlify, it will ask you to add a CNAME record. Do that.
|
||||
|
||||
Once the custom domain is added, you'll need to add an SSL/TLS certificate. At the bottom of the page press "Verify DNS configuration" and if it succeeds, press "Provision certificate". If that fails, you may need to wait some time until your DNS propagetes.
|
||||
Once the custom domain is added, you'll need to add an SSL/TLS certificate. At the bottom of the page press "Verify DNS configuration" and if it succeeds, press "Provision certificate". If that fails, you may need to wait some time until your DNS propagates.
|
||||
|
||||
And that's it! Enjoy your instance's Elk!
|
||||
|
|
|
@ -69,7 +69,7 @@ If you deleted any account from your backend with push notification subscription
|
|||
- Scroll down in the page and locate "Permissions > Notifications"
|
||||
- Click on "Configuration" button
|
||||
- Locate "Elk" website in the list and select it, then click on "Remove website" button
|
||||
- Safari (WIP)
|
||||
- Safari (WIP)
|
||||
|
||||
### Permission denied: enable notifications in your browser
|
||||
|
||||
|
@ -186,7 +186,7 @@ To debug the PWA service worker in your mobile browser, you will need to:
|
|||
- Cache: `[x]` Cache storage and `[x]` Application cache
|
||||
- Click on Clear site data button
|
||||
- Go to `Application > Service Workers` and check the current service worker is missing or has the status deleted or reduntant.
|
||||
6) Disable port forwarding: open the "Port forwarding..." modal again on the `chrome://inspect/#devices` page and either uncheck the "Enable port forwarding" option or remove the entry from the list and click "Done".
|
||||
6) Disable port forwarding: open the "Port forwarding..." modal again on the `chrome://inspect/#devices` page and either uncheck the "Enable port forwarding" option or remove the entry from the list and click "Done".
|
||||
|
||||
## PWA web manifest and related applications
|
||||
|
||||
|
|
|
@ -35,4 +35,3 @@ When we update this privacy notice, the updated version will be indicated by a n
|
|||
## Contact
|
||||
|
||||
If you have questions or comments about this notice, you may raise an issue at https://github.com/elk-zone/elk.
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
[build]
|
||||
publish = "dist"
|
||||
command = "pnpm generate"
|
||||
publish = "dist"
|
||||
command = "pnpm generate"
|
||||
|
||||
# Allow previewing docs
|
||||
[[redirects]]
|
||||
from = "/docs/*"
|
||||
to = "/:splat"
|
||||
status = 200
|
||||
force = true
|
||||
from = "/docs/*"
|
||||
to = "/:splat"
|
||||
status = 200
|
||||
force = true
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
export default defineNuxtConfig({
|
||||
extends: '@nuxt-themes/docus',
|
||||
vite: {
|
||||
optimizeDeps: {
|
||||
include: ['scule'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@nuxt-themes/docus": "^1.15.0",
|
||||
"nuxt": "^3.10.3"
|
||||
"nuxt": "^3.11.2"
|
||||
}
|
||||
}
|
||||
|
|
37
eslint.config.js
Normal file
37
eslint.config.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default await antfu(
|
||||
{
|
||||
unocss: false,
|
||||
vue: {
|
||||
overrides: {
|
||||
'vue/no-restricted-syntax': ['error', {
|
||||
selector: 'VElement[name=\'a\']',
|
||||
message: 'Use NuxtLink instead.',
|
||||
}],
|
||||
},
|
||||
},
|
||||
ignores: [
|
||||
'public/**',
|
||||
'public-dev/**',
|
||||
'public-staging/**',
|
||||
'https-dev-config/**',
|
||||
'elk-translation-status.json',
|
||||
'docs/translation-status.json',
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// TODO: migrate all process reference to `import.meta.env` and remove this rule
|
||||
'node/prefer-global/process': 'off',
|
||||
},
|
||||
},
|
||||
// Sort local files
|
||||
{
|
||||
files: ['locales/**.json'],
|
||||
rules: {
|
||||
'jsonc/sort-keys': 'error',
|
||||
},
|
||||
},
|
||||
)
|
687
locales/ckb.json
Normal file
687
locales/ckb.json
Normal file
|
@ -0,0 +1,687 @@
|
|||
{
|
||||
"a11y": {
|
||||
"loading_page": "خرمەخرمی لاپەڕە، تکایە چاوەڕوان بن",
|
||||
"loading_titled_page": "خرمەی لاپەڕەی {0}، تکایە چاوەڕوان بن",
|
||||
"locale_changed": "زمان گۆڕدرا بۆ {0}",
|
||||
"locale_changing": "گۆڕینی زمان، تکایە چاوەڕوان بن",
|
||||
"route_loaded": "لاپەڕەی {0} بارکراوە"
|
||||
},
|
||||
"account": {
|
||||
"avatar_description": "{0}بە ئاڤاتاری",
|
||||
"blocked_by": "تۆ لەلایەن ئەم بەکارهێنەرەوە بلۆک کراویت.",
|
||||
"blocked_domains": "دۆمەینەکانی بلۆککراو",
|
||||
"blocked_users": "بەکارهێنەرانی بلۆککراو",
|
||||
"blocking": "بلۆک کراوە",
|
||||
"bot": "بۆت",
|
||||
"favourites": "دڵخواز",
|
||||
"follow": "بەدواداچوون",
|
||||
"follow_back": "دوا بکەوە",
|
||||
"follow_requested": "داواکراوە",
|
||||
"followers": "هۆگران",
|
||||
"followers_count": "هۆگر {0}|هۆگر {0}|{0} هۆگر",
|
||||
"following": "شوێنکەوتوو",
|
||||
"following_count": "{0} شوێنکەوتوو",
|
||||
"follows_you": "شوێنت دەکەوێت",
|
||||
"go_to_profile": "بڕۆ بۆ پڕۆفایلی",
|
||||
"joined": "بەشداری کردووە",
|
||||
"moved_title": "ئاماژەی بەوە کردووە، لە ئێستادا هەژمارە نوێیەکەیان بەم شێوەیەیە:",
|
||||
"muted_users": "بەکارهێنەرە بێدەنگەکان",
|
||||
"muting": "بێدەنگ",
|
||||
"mutuals": "دوولایەنە",
|
||||
"notifications_on_post_disable": "واز لە ئاگادارکردنەوەم بێنە کاتێک {username} پۆست دەکات",
|
||||
"notifications_on_post_enable": "کاتێک {username} پۆست دەکات ئاگادارم بکەرەوە",
|
||||
"pinned": "سەنجاق کراوە",
|
||||
"posts": "بابەت",
|
||||
"posts_count": "{0} بابەت|{0} بابەت|{0} بابەت",
|
||||
"profile_description": "سەردێڕی پڕۆفایلی {0}",
|
||||
"profile_personal_note": "تێبینی کەسی",
|
||||
"profile_unavailable": "پڕۆفایلی بەردەست نییە",
|
||||
"request_follow": "داواکاری بۆ بەدواداچوون",
|
||||
"unblock": "بلۆک بکەرەوە",
|
||||
"unfollow": "بەدوایدا مەچۆ",
|
||||
"unmute": "بێدەنگی لابەرە",
|
||||
"view_other_followers": "لەوانەیە شوێنکەوتووانی حاڵەتەکانی تر نیشان نەدرێن.",
|
||||
"view_other_following": "لە حاڵەتەکانی تر ناتوانرێت ئاشکرا بکرێت."
|
||||
},
|
||||
"action": {
|
||||
"apply": "بهکاربردن",
|
||||
"bookmark": "نیشانەکردن",
|
||||
"bookmarked": "نیشانە کراوە",
|
||||
"boost": "هاوبەشکردن",
|
||||
"boost_count": "{0}",
|
||||
"boosted": "هاوبەشکرا",
|
||||
"clear_publish_failed": "هەڵەکانی بڵاوکردنەوە پاک بکەرەوە",
|
||||
"clear_save_failed": "پاککردنی هەڵەی پاشکەوتکراوە",
|
||||
"clear_upload_failed": "هەڵەکانی بارکردنی پەڕگە پاک بکەرەوە",
|
||||
"close": "داخستن",
|
||||
"compose": "نووسین",
|
||||
"confirm": "دڵنیاکردنەوە",
|
||||
"done": "تەواو",
|
||||
"edit": "دەستکاری",
|
||||
"enter_app": "چوونەناو ئەپ",
|
||||
"favourite": "پهسند",
|
||||
"favourite_count": "{0}",
|
||||
"favourited": "پەسەندکراوە",
|
||||
"more": "زیاتر",
|
||||
"next": "دواتر",
|
||||
"prev": "پێشتر",
|
||||
"publish": "بڵاوكردنهوه",
|
||||
"reply": "وەڵامدانەوە",
|
||||
"reply_count": "{0}",
|
||||
"reset": "ڕێکخستنهوه",
|
||||
"save": "پاشهکهوت",
|
||||
"save_changes": "پاشکەوتی گۆرانکاری",
|
||||
"sign_in": "چوونەژوورەوە",
|
||||
"sign_in_to": "چوونە ژوورەوە بۆ {0}",
|
||||
"switch_account": "گۆڕینی هەژمارە",
|
||||
"vote": "دەنگدان"
|
||||
},
|
||||
"app_desc_short": "ڕاژەخوازێکی وێبی ماستۆدۆن",
|
||||
"app_logo": "Elk لۆگۆی",
|
||||
"app_name": "Elk",
|
||||
"attachment": {
|
||||
"edit_title": "وەسف",
|
||||
"remove_label": "لابردنی لکێندراو"
|
||||
},
|
||||
"command": {
|
||||
"activate": "چالاککردن",
|
||||
"complete": "تەواو",
|
||||
"compose_desc": "نووسینی بابەتێکی نوێ",
|
||||
"select_lang": "زمان هەڵبژێرە",
|
||||
"sign_in_desc": "هەژمارەیێکی ئامادە زیاد بکە",
|
||||
"switch_account": "بیگۆڕە بۆ {0}",
|
||||
"switch_account_desc": "بیگۆڕە بۆ ئەژمێرێکی تر",
|
||||
"toggle_dark_mode": "گۆڕینی دۆخی تاریک",
|
||||
"toggle_zen_mode": "دۆخی zen بگۆڕە"
|
||||
},
|
||||
"common": {
|
||||
"end_of_list": "کۆتایی لیستەکە",
|
||||
"error": "هەڵە",
|
||||
"fetching": "هێنانی ...",
|
||||
"in": "لە",
|
||||
"no_bookmarks": "هێشتا هیچ بابەتێک نیشانە نەکراوە",
|
||||
"no_favourites": "هێشتا هیچ بابەتێک پەسەند نەکراوە",
|
||||
"not_found": "هەڵەی ٤٠٤، نەدۆزرایهوه",
|
||||
"offline_desc": "وا دیارە تۆ لە دەرەوەی هێڵ بیت. تکایە پەیوەندی تۆڕەکەت بپشکنە."
|
||||
},
|
||||
"compose": {
|
||||
"draft_title": "ڕەشنووس {0}",
|
||||
"drafts": "ڕەشنووسەکان ({v})"
|
||||
},
|
||||
"confirm": {
|
||||
"block_account": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "بلۆک",
|
||||
"title": "ئایا دڵنیای کە دەتەوێت {0} بلۆک بکەیت؟"
|
||||
},
|
||||
"block_domain": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "بلۆک",
|
||||
"title": "ئایا دڵنیای کە دەتەوێت {0} بلۆک بکەیت؟"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "نەخێر",
|
||||
"confirm": "بەڵێ"
|
||||
},
|
||||
"delete_list": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "سڕینەوە",
|
||||
"title": "ئایا دڵنیای کە دەتەوێت لیستی \"{0}\" بسڕیتەوە؟"
|
||||
},
|
||||
"delete_posts": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "سڕینەوە",
|
||||
"title": "دڵنیای دەتەوێت ئەم بابەتە بسڕیتەوە؟"
|
||||
},
|
||||
"mute_account": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "بێدەنگ",
|
||||
"title": "ئایا دڵنیای کە دەتەوێت {0} بێدەنگ بکەیت؟"
|
||||
},
|
||||
"show_reblogs": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "پیشاندان",
|
||||
"title": "ئایا دڵنیای کە دەتەوێت هاوبەشیەکان لە {0}ەوە پیشان بدەیت؟"
|
||||
},
|
||||
"unfollow": {
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"confirm": "شوێن نەکەوتن",
|
||||
"title": "دڵنیای کە دەتەوێت شوێنی نەکەویتەوە؟"
|
||||
}
|
||||
},
|
||||
"conversation": {
|
||||
"with": "لەگەڵ"
|
||||
},
|
||||
"custom_cards": {
|
||||
"stackblitz": {
|
||||
"lines": "هێڵەکان {0}",
|
||||
"open": "کراوە",
|
||||
"snippet_from": "پارچەیەک لە {0}"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"account_not_found": "هەژمار {0} نەدۆزرایەوە",
|
||||
"file_size_cannot_exceed_n_mb": "قەبارەی پەڕگە ناتوانرێت لە {0} مێگابایت تێپەڕێت",
|
||||
"sign_in_error": "ناتوانیت پەیوەندی بکەیت بە ڕاژەکار.",
|
||||
"status_not_found": "بابەت نەدۆزرایەوە",
|
||||
"unsupported_file_format": "پەڕەگە پشتگیری نەکراو"
|
||||
},
|
||||
"help": {
|
||||
"build_preview": {
|
||||
"desc1": "لە ئێستادا ئێوە وشانی پێشوەختەی Elk لە کۆمەڵگەوە دەبینن - {0}.",
|
||||
"desc2": "لەوانەیە گۆڕانکاری بێ پێداچوونەوە یان تەنانەت زیانبەخشی تێدابێت.",
|
||||
"desc3": "بە هەژمارە ڕاستەقینەکەتەوە مەچۆژوور",
|
||||
"title": "پێشبینینی بڵاوکردنەوە"
|
||||
},
|
||||
"desc_highlight": "چاوەڕوانی هەندێک هەڵە و تایبەتمەندی ونبوو لێرە و لەوێ بن.",
|
||||
"desc_para1": "سوپاس بۆ ئارەزووی ئێوە بۆ تاقیکردنەوەی Elk، ڕاژەخوازی ماستۆدۆنەکەمان لە پێشکەوتندایە!",
|
||||
"desc_para2": "ئێمە زۆر کار لەسەر گەشەپێدانەکە دەکەین و بە تێپەڕبوونی کات باشتری دەکەین.",
|
||||
"desc_para3": "بۆ بەرزکردنەوەی گەشەپێدان، دەتوانیت لە ڕێگەی پاڵپشتیەکانی گیتهاب پشتیوانی تاقمەکەمان بکەیت. هیوادارین چێژ لە Elk وەربگرن!",
|
||||
"desc_para4": "Elk سەرچاوە کراوەیە. ئەگەر دەتەوێت یارمەتی بدەیت لە تاقیکەرەوە، فیدباک بنێرە، یان بەشداری بکە،",
|
||||
"desc_para5": "لە گیتهاب نزیکمان بکەرەوە",
|
||||
"desc_para6": "و بەشداری بکەن.",
|
||||
"footer_team": "گەشەپێدارانی Elk",
|
||||
"title": "ئێلک(Elk) لە پێشبینیدایە!"
|
||||
},
|
||||
"language": {
|
||||
"search": "گەڕان"
|
||||
},
|
||||
"list": {
|
||||
"add_account": "هەژمارە زیاد بکە بۆ لیستەکە",
|
||||
"cancel_edit": "هەڵوەشاندنەوەی دەستکاریکردن",
|
||||
"clear_error": "هەڵە پاک بکەرەوە",
|
||||
"create": "دروستکردن",
|
||||
"delete": "ئەم لیستە بسڕەوە",
|
||||
"delete_error": "لە کاتی سڕینەوەی لیستەکەدا هەڵەیەک ڕوویدا",
|
||||
"edit": "دەستکاری ئەم لیستە بکە",
|
||||
"edit_error": "لە کاتی نوێکردنەوەی لیستەکەدا هەڵەیەک ڕوویدا",
|
||||
"error": "لە کاتی دروستکردنی لیستەکەدا هەڵەیەک ڕوویدا",
|
||||
"error_prefix": "هەڵە:",
|
||||
"list_title_placeholder": "ناونیشانی لیستەکە",
|
||||
"modify_account": "دەستکاری لیستەکان بکە بە هەژمارە",
|
||||
"remove_account": "هەژمارە لە لیستەکە دەربهێنە",
|
||||
"save": "پاشکەوتی گۆڕانکاری"
|
||||
},
|
||||
"magic_keys": {
|
||||
"dialog_header": "قەدبڕی تەختەکلیل",
|
||||
"groups": {
|
||||
"actions": {
|
||||
"boost": "هابەشیکردن",
|
||||
"command_mode": "دۆخی فەرمان",
|
||||
"compose": "نووسین",
|
||||
"favourite": "دڵخواز",
|
||||
"title": "کردارەکان"
|
||||
},
|
||||
"media": {
|
||||
"title": "ڕاگهیاندن"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_home": "سەرەتا",
|
||||
"go_to_notifications": "ئاگاداری",
|
||||
"next_status": "دۆخی داهاتوو",
|
||||
"previous_status": "دۆخی پێشوو",
|
||||
"shortcut_help": "یارمەتی کورتەڕێ",
|
||||
"title": "ڕێدۆز"
|
||||
}
|
||||
},
|
||||
"sequence_then": "ئیتر"
|
||||
},
|
||||
"menu": {
|
||||
"add_personal_note": "زیادکردنی تێبینی کەسی بۆ {0}",
|
||||
"block_account": "بلۆک {0}",
|
||||
"block_domain": " بلۆککردنی دۆمەینی {0}",
|
||||
"copy_link_to_post": "بەستەری ئەم بابەتە ڕوونووس بکە",
|
||||
"copy_original_link_to_post": "بەستەری ئەسڵی ڕوونووس بکە بۆ ئەم بابەتە",
|
||||
"delete": "سڕینەوە",
|
||||
"delete_and_redraft": "سڕینەوە یا دەستکاری",
|
||||
"direct_message_account": "پەیامی ڕاستەوخۆ {0}",
|
||||
"edit": "دەستکاری",
|
||||
"hide_reblogs": "شاردنەوەی هاوبەشیەکان لە {0}",
|
||||
"mention_account": "ئاماژە بە {0}",
|
||||
"mute_account": "بێدەنگکردنی {0}",
|
||||
"mute_conversation": "ئەم بابەتە بێدەنگ بکەن",
|
||||
"open_in_original_site": "کردنەوە لە ماڵپەڕی سەرەکی",
|
||||
"pin_on_profile": "سنجاقکردن لەسەر پرۆفایل",
|
||||
"remove_personal_note": "لابردنی تێبینی کەسی لە {0}",
|
||||
"report_account": "{0} گوزارشی",
|
||||
"share_post": "ئەم بابەتە هاوبەش بکە",
|
||||
"show_favourited_and_boosted_by": "نیشانی بدە کێ حەزی لێیەتی و هابەشیکردووە",
|
||||
"show_reblogs": "هاوبەشیەکان نیشان بدە لە {0}",
|
||||
"show_untranslated": "پیشاندانی ڕێکنەخراو",
|
||||
"toggle_theme": {
|
||||
"dark": "گۆڕینی شێوازی تاریک",
|
||||
"light": "گۆڕینی شێوازی ڕۆژ"
|
||||
},
|
||||
"translate_post": "بابەت وەربگێڕە",
|
||||
"unblock_account": "{0} بلۆکەکە لاببە",
|
||||
"unblock_domain": "ئازادکردنی {0} دۆمەین",
|
||||
"unfollow_account": "وازهێنان لە شوێنکەوتووان {0}",
|
||||
"unmute_account": "بێدەنگی {0} لابەرە",
|
||||
"unmute_conversation": "بێدەنگی لەم بابەتە لابەرە",
|
||||
"unpin_on_profile": "سنجاقکردن لابەرە لە پرۆفایل"
|
||||
},
|
||||
"modals": {
|
||||
"aria_label_close": "داخستن"
|
||||
},
|
||||
"nav": {
|
||||
"back": "بگەڕێوە",
|
||||
"blocked_domains": "دۆمەینە بلۆککراوەکان",
|
||||
"blocked_users": "بەکارهێنەرە بلۆککراوەکان",
|
||||
"bookmarks": "نیشانکراوەکان",
|
||||
"built_at": "{0} دروستکراو",
|
||||
"compose": "نووسین",
|
||||
"conversations": "گفتوگۆکان",
|
||||
"explore": "گەڕان",
|
||||
"favourites": "دڵخوازەکان",
|
||||
"federated": "یەکگرتن",
|
||||
"home": "سەرەتا",
|
||||
"list": "لیست",
|
||||
"lists": "لیستەکان",
|
||||
"local": "خۆماڵی",
|
||||
"muted_users": "بەکارهێنەرە بێدەنگەکان",
|
||||
"notifications": "ئاگادارییەکان",
|
||||
"privacy": "تایبەتێتی",
|
||||
"profile": "پرۆفایل",
|
||||
"search": "گەڕان",
|
||||
"select_feature_flags": "گۆڕینی تایبەتمەندی ئاڵاکان",
|
||||
"select_font_size": "ئەندازەی پێنووس",
|
||||
"select_language": "پیشاندانی زمان",
|
||||
"settings": "ڕێکخستنهکان",
|
||||
"show_intro": "پیشاندانی پێشەکی",
|
||||
"toggle_theme": "گۆڕینی ڕووکار",
|
||||
"zen_mode": "شێوەی Zen"
|
||||
},
|
||||
"notification": {
|
||||
"favourited_post": "بابەتەکەتی پەسەند کرد",
|
||||
"followed_you": "دوای تۆ کەوت",
|
||||
"followed_you_count": "{0} بەکارهێنەر دوای تۆ کەوتن{0} کەبەکارهێنەر بەدوات کەوتن|{0} بەکارهێنەر دوای تۆ کەوتن",
|
||||
"missing_type": "MISSING notification.type:",
|
||||
"reblogged_post": "هاوبەشیکردنی بابەتی تۆ",
|
||||
"reported": "{0} گوزارشی {1}",
|
||||
"request_to_follow": "داوای کردووە کە شوێنتان بکەوێت ",
|
||||
"signed_up": "بوو به ئه ندام",
|
||||
"update_status": "بابەتەکەیان نوێکردەوە"
|
||||
},
|
||||
"placeholder": {
|
||||
"content_warning": "لێرەدا هۆشدارییەکەت بنووسە",
|
||||
"default_1": "چی لە مێشکتدایە؟",
|
||||
"reply_to_account": "وەڵامی {0}",
|
||||
"replying": "وەڵامدانەوە"
|
||||
},
|
||||
"polls": {
|
||||
"allow_multiple": "ڕێگە بە چەندین هەڵبژاردن بدە",
|
||||
"cancel": "ههڵوهشاندنهوه",
|
||||
"create": "دروستکردنی ڕاپرسی",
|
||||
"disallow_multiple": "ڕێگەنەدان بە هەڵبژاردنی فرە",
|
||||
"expiration": "بەسەرچوونی ماوەی ڕاپرسی",
|
||||
"hide_votes": "Hide vote totals until the end",
|
||||
"option_placeholder": "هەڵبژاردنی ڕاپرسی {current}/{max}",
|
||||
"remove_option": "هەڵبژاردن لاببە",
|
||||
"settings": "بژاردەکانی ڕاپرسی",
|
||||
"show_votes": "هەمیشە کۆی دەنگەکان پیشان بدە"
|
||||
},
|
||||
"pwa": {
|
||||
"dismiss": " ڕێپێنەدان",
|
||||
"install": "دامەزراندن",
|
||||
"install_title": "دامەزراندنی Elk",
|
||||
"screenshots": {
|
||||
"dark": "وێنەی ڕوومێزی Elk کە لە دۆخی تاریکدا کاردەکات",
|
||||
"light": "وێنەی ڕوومێزی Elk کە لە دۆخی ڕووناکیدا کاردەکات"
|
||||
},
|
||||
"title": "نوێکردنەوەی نوێی Elk بەردەستە!",
|
||||
"update": "نوێکردنەوە",
|
||||
"update_available_short": "نوێکردنەوەی Elk",
|
||||
"webmanifest": {
|
||||
"canary": {
|
||||
"description": "ڕاژەخوازێکی ماستۆدۆن (کاناری)",
|
||||
"name": "Elk (کاناری)",
|
||||
"short_name": "Elk (کاناری)"
|
||||
},
|
||||
"dev": {
|
||||
"description": "ڕاژەخوازێکی وێبی ماستۆدۆن (پەرەپێدەر)",
|
||||
"name": "Elk (پەرەپێدەر)",
|
||||
"short_name": "Elk (پەرەپێدەر)"
|
||||
},
|
||||
"preview": {
|
||||
"description": "ڕاژەخوازێکی وێبی ماستۆدۆن (پێشبینین)",
|
||||
"name": "Elk (پێشبینین)",
|
||||
"short_name": "Elk (پێشبینین)"
|
||||
},
|
||||
"release": {
|
||||
"description": "ڕاژەخوازێکی وێبی ماستۆدۆن",
|
||||
"name": "Elk",
|
||||
"short_name": "Elk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"additional_comments": "سەرنجی زیادە",
|
||||
"another_server": "ئەو بەکارهێنەرەی کە تۆ ڕاپۆرتی دەکەیت لە ڕاژەیێکی ترەوەیە",
|
||||
"anything_else": "ئایا شتێکی تر هەیە کە پێت وایە پێویستە بزانین؟",
|
||||
"block_desc": "چیتر هیچ بابەتێک لەم بەکارهێنەرەوە نابینیت. ئەوان ناتوانن پۆستەکانت ببینن و شوێنتکەون بکەن. ئەوان دەتوانن بزانن کە بلۆک کراون.",
|
||||
"dontlike": "حەزم لێ نیە",
|
||||
"dontlike_desc": "شتێک نییە کە تۆ بتەوێت بیبینیت",
|
||||
"forward": "بەڵێ، ئەم گوزارشە بنێرە بۆ {0}",
|
||||
"forward_question": "ئایا دەتەوێت ڕوونووسێکی بێناو لەم گوزارشە بنێری بۆ ئەو ڕاژەکارەش",
|
||||
"further_actions": {
|
||||
"limit": {
|
||||
"description": "ئەمەی خوارەوە هەڵبژاردنەکانتە بۆ کۆنترۆڵکردنی ئەوەی دەیبینیت:",
|
||||
"title": "ناتەوێت ئەمە ببینیت؟"
|
||||
},
|
||||
"report": {
|
||||
"description": "لە کاتێکدا ئێمە پێداچوونەوە بۆ ئەمە دەکەین، ئەمە ئەو کردارانەن کە دەتوانیت ئەنجامی بدەیت:",
|
||||
"title": "سوپاس بۆ گوزارشەکەت ئێمە سەیری ئەمە دەکەین."
|
||||
}
|
||||
},
|
||||
"limiting": "سنووردارکردنی {0}",
|
||||
"mute_desc": "چیتر هیچ بابەتێکی ئەم بەکارهێنەرە نابینیت. ئەوان هێشتا دەتوانن شوێنت بکەون و بابەتەکانت ببینن. ئه وان نازانن که ئه وان هه ر ده بن.",
|
||||
"other": "شتێکی ترە",
|
||||
"other_desc": "کێشەکە ناگونجێت بۆ پۆلەکانی تر",
|
||||
"reporting": "{0} گوزارش",
|
||||
"select_many": "هەموو ئەو شتانە دیاریبکە کە جێبەجێ دەکرێن:",
|
||||
"select_one": "باشترین هاورێ هەڵبژێرە",
|
||||
"select_posts": "ئایا هیچ بابەتێک هەیە کە پشتگیری ئەم گوزارشە بکات؟",
|
||||
"select_posts_other": "ئایا هیچ بابەتێکی دیکە هەیە کە پشتگیری ئەم گوزارشە بکات؟",
|
||||
"spam": "ئەوە سپام",
|
||||
"spam_desc": "بەستەرە زیانبەخشەکان، پەیوەندی ساختە، یان وەڵامی دووبارەبوونەوە",
|
||||
"submit": "گوزارش بنێرە",
|
||||
"unfollow_desc": "چیتر بابەتەکانی ئەم بەکارهێنەرە لە پەڕەی ماڵەکەتدا نابینیت. هێشتا دەتوانیت بابەتەکانیان لە شوێنێکی تر ببینیت.",
|
||||
"violation": "یەکێک یان زیاتر لە یاساکانی ڕاژەکار پێشێل دەکات",
|
||||
"whats_wrong_account": "پێمان بڵێ کێشەی ئەم هەژمارە چییە",
|
||||
"whats_wrong_post": "پێمان بڵێ کێشەی ئەم بابەتە چییە"
|
||||
},
|
||||
"search": {
|
||||
"search_desc": "گەڕان بۆ بەکارهێنەران و هاشتاگەکان",
|
||||
"search_empty": "نەیتوانی هیچ شتێک بدۆزێتەوە بۆ ئەم مەرجە گەڕانە"
|
||||
},
|
||||
"settings": {
|
||||
"about": {
|
||||
"built_at": "بنیات نان",
|
||||
"label": "دەربارە",
|
||||
"meet_the_team": "تاقمەکە بناسە",
|
||||
"sponsor_action": "پشتیوانمان بە",
|
||||
"sponsor_action_desc": "بۆ پشتگیری کردنی تیمی گەشەپێدانی Elk",
|
||||
"sponsors": "پشتیوانەکان",
|
||||
"sponsors_body_1": "Elk بە هۆی پشتیوانە بەخشەندە و یارمەتییەکانی، پێزانینی هەیە بۆ :",
|
||||
"sponsors_body_2": "وە هەموو ئەو کۆمپانیا و کەسانەی کە پشتیوانی دەستەکەی Elk و ئەندامەکان دەکەن.",
|
||||
"sponsors_body_3": "ئەگەر چێژ لە ئەپەکە وەردەگریت، بیر لەوە بکەرەوە کە پشتیوانیمان بکەیت:",
|
||||
"version": "وشان"
|
||||
},
|
||||
"account_settings": {
|
||||
"description": "دەستکاری ڕێکخستنەکانی هەژمارەکەت بکە لە ڕوخساری ماستۆدۆن",
|
||||
"label": "ڕێکخستنەکانی هەژمارە"
|
||||
},
|
||||
"interface": {
|
||||
"color_mode": "شێوەی ڕەنگ",
|
||||
"dark_mode": "تاریک",
|
||||
"default": " (بنەڕەت)",
|
||||
"font_size": "ئەندازەی پێنووس",
|
||||
"label": "ڕووکار",
|
||||
"light_mode": "ڕۆشن",
|
||||
"system_mode": "سیستەم",
|
||||
"theme_color": "ڕەنگی ڕووکار"
|
||||
},
|
||||
"language": {
|
||||
"display_language": "زمانی پیشاندان",
|
||||
"label": "زمان",
|
||||
"post_language": "زمانی بڵاوکردنەوە",
|
||||
"status": "دۆخی وەرگێڕان: {0}/{1} ({2}٪)",
|
||||
"translations": {
|
||||
"add": "زیادکردن",
|
||||
"choose_language": "زمانێک هەڵبژێرە",
|
||||
"heading": "وەرگێڕان",
|
||||
"hide_specific": "وەرگێڕانی دیاریکراو بشارەوە",
|
||||
"remove": "لابردن"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"label": "ئاگادارییەکان",
|
||||
"notifications": {
|
||||
"label": "ڕێکخستنەکانی ئاگادارکردنەوەکان"
|
||||
},
|
||||
"push_notifications": {
|
||||
"alerts": {
|
||||
"favourite": "دڵخوازەکان",
|
||||
"follow": "شوێنکەوتووی نوێ",
|
||||
"mention": "ئاماژە",
|
||||
"poll": "دەنگ",
|
||||
"reblog": "بابەتەکەت دووبارە بنووسەوە",
|
||||
"title": "چ ئاگادارکردنەوەیەک وەربگرین؟"
|
||||
},
|
||||
"description": "تەنانەت کاتێک کە Elk بەکارناهێنیت ئاگادارکردنەوەکان وەربگرە.",
|
||||
"instructions": "لەبیرت نەچێت گۆڕانکارییەکانت بە بەکارهێنانی دوگمەی @:settings.notifications.push_notifications.save_settings پاشەکەوت بکەیت!",
|
||||
"label": "ڕێکخستنەکانی ئاگادارکردنەوەکانی پاڵدان",
|
||||
"policy": {
|
||||
"all": "لە هەر کەسێکەوە",
|
||||
"followed": "لە کەسانێک کە شوێنیان دەکەوم",
|
||||
"follower": "لە کەسانێک کە شوێنم دەکەون",
|
||||
"none": "لە هیچ کەس",
|
||||
"title": "لە کێ دەتوانم ئاگادارکردنەوە وەرگرم؟"
|
||||
},
|
||||
"save_settings": "ڕێکخستنەکان پاشەکەوت بکە",
|
||||
"subscription_error": {
|
||||
"clear_error": "هەڵە بسڕەوە",
|
||||
"error_hint": "دەتوانیت ڕاوێژ بە لیستی پرسیارە زۆرەکان بکەیت بۆ ئەوەی هەوڵی چارەسەرکردنی کێشەکە بدەیت: {0}.",
|
||||
"invalid_vapid_key": "پێدەچێت کلیلی گشتی VAPID نادروست بێت.",
|
||||
"permission_denied": "مۆڵەت ڕەتکرایەوە: ئاگادارکردنەوەکان لە وێبگەڕەکەتدا چالاک بکە.",
|
||||
"repo_link": "کۆگای Elk لە گیتهاب",
|
||||
"request_error": "هەڵەیەک لە کاتی داوای بەشداریکردندا ڕوویدا، دووبارە هەوڵبدەرەوە و ئەگەر هەڵەکە بەردەوام بوو، تکایە کێشەکە گوزارش بدە بە کۆگای Elk.",
|
||||
"title": "نەتوانرا بەشداری بکات بۆ ئاگادارکردنەوەکانی پاڵدان",
|
||||
"too_many_registrations": "بەهۆی سنووردارکردنی وێبگەڕەوە، Elk ناتوانێت خزمەتگوزاری ئاگادارکردنەوەی پاڵنەر بەکاربهێنێت بۆ چەندین هەژمار لەسەر ڕاژەکارە جیاوازەکان. پێویستە بەشداری بکەیت لە پاڵنانی ئاگانامەکان لەسەر هەژمارێکی تر و دووبارە هەوڵ بدە.",
|
||||
"vapid_not_supported": "وێبگەڕەکەت پشتگیری لە ئاگادارییەکانی Web Push دەکات، بەڵام پێناچێت پرۆتۆکۆڵی VAPID جێبەجێ بکات."
|
||||
},
|
||||
"title": "ڕێکخستنەکانی ئاگادارکردنەوەکانی پاڵدان",
|
||||
"undo_settings": "گۆڕانکارییەکان بگەڕێنەوە",
|
||||
"unsubscribe": "ئاگادارکردنەوەکانی پاڵدان لەکاربخە",
|
||||
"unsupported": "وێبگەڕەکەت پشتگیری لە ئاگادارکردنەوەکانی پاڵدان ناکات.",
|
||||
"warning": {
|
||||
"enable_close": "داخستن",
|
||||
"enable_description": "بۆ وەرگرتنی ئاگادارکردنەوەکان کاتێک Elk کراوە نییە، ئاگادارکردنەوەکانی پاڵدان چالاک بکە. دەتوانیت بە وردی کۆنتڕۆڵی ئەوە بکەیت کە چ جۆرە کارلێکێک ئاگادارییەکانی پاڵنەر دروست دەکات لە ڕێگەی دوگمەی \"@:settings.notifications.show_btn{'\"'}ی سەرەوە کاتێک چالاک کرا.",
|
||||
"enable_description_desktop": "بۆ وەرگرتنی ئاگادارکردنەوە کاتێک Elk کراوە نییە، ئاگادارکردنەوەکانی پاڵدان چالاک بکە. دەتوانیت بە وردی کۆنتڕۆڵی ئەوە بکەیت کە چ جۆرە کارلێکێک ئاگادارکردنەوەی پاڵدان دروست دەکات لە \"ڕێکخستنەکان > ئاگادارکردنەوەکان > ڕێکخستنەکانی ئاگادارکردنەوەکانی پاڵدان\" کاتێک چالاک کرا.",
|
||||
"enable_description_mobile": "هەروەها دەتوانیت بە بەکارهێنانی مینیوی گەشتکردن \"ڕێکخستنەکان > ئاگادارکردنەوەکان > ڕێکخستنەکانی ئاگادارکردنەوە پاڵدان\"، دەستت بگات بە ڕێکخستنەکان.",
|
||||
"enable_description_settings": "بۆ وەرگرتنی ئاگادارکردنەوەکان کاتێک Elk کراوە نییە، ئاگادارکردنەوەکانی پاڵدان چالاک بکە. دەتوانیت بە وردی کۆنتڕۆڵی ئەوە بکەیت کە چ جۆرە کارلێکێک ئاگادارکردنەوەی پاڵنەر لەسەر هەمان ئەم شاشەیە دروست دەکات کاتێک چالاکیان دەکەیت.",
|
||||
"enable_desktop": "ئاگادارکردنەوەکانی پاڵدان چالاک بکە",
|
||||
"enable_title": "هەرگیز هیچ شتێک لەدەست مەدە",
|
||||
"re_auth": "وا دیارە ڕاژەکارەکەت پشتگیری لە ئاگادارکردنەوەکانی پاڵدان ناکات. هەوڵبدە بچیتە دەرەوە و دووبارە بچۆرەوە ژوورەوە، ئەگەر ئەم پەیامە هێشتا دەرکەوت پەیوەندی بە بەڕێوەبەری سێرڤەرەکەتەوە بکە."
|
||||
}
|
||||
},
|
||||
"show_btn": "بڕۆ بۆ ڕێکخستنەکانی ئاگادارکردنەوەکان",
|
||||
"under_construction": "لەژێر دروستکردنەوەدایە"
|
||||
},
|
||||
"notifications_settings": "ئاگاداریەکان",
|
||||
"preferences": {
|
||||
"enable_autoplay": "چالاککردنی لێدانی خۆکار",
|
||||
"enable_data_saving": "چالاککردنی پاشەکەوتکردنی زانیاری",
|
||||
"enable_data_saving_description": "زانیاری پاشەکەوت بکە بە ڕێگریکردن لە لکێندراوەکان لە بارکردنی خۆکارانە.",
|
||||
"enable_pinch_to_zoom": "چالاککردنی پینچ بۆ گەورەنوێیی",
|
||||
"github_cards": "کاردەکانی گیتهاب",
|
||||
"github_cards_description": "کاتێک بەستەری گیتهاب بڵاودەکرێتەوە، کارتێکی HTML بە بەکارهێنانی مێتای گرافی کۆمەڵایەتی لە جیاتی وێنەی کۆمەڵایەتی پیشان دەدرێت.",
|
||||
"grayscale_mode": "دۆخی خۆڵەمێشی",
|
||||
"hide_account_hover_card": "شاردنەوەی کارتی هۆڤەری ئەکاونت",
|
||||
"hide_alt_indi_on_posts": "شاردنەوەی نیشاندەری سەردێر لەسەر بابەتەکان",
|
||||
"hide_boost_count": "شاردنەوەی ژمارەی هابەشکردنەوە",
|
||||
"hide_favorite_count": "ژمارەی دڵخوازەکان بشارەوە",
|
||||
"hide_follower_count": "ژمارەی شوێنکەوتوو/شوێنکەوتووان بشارەوە",
|
||||
"hide_news": "شاردنەوەی هەواڵەکان",
|
||||
"hide_reply_count": "ژمارەی وەڵامەکان بشارەوە",
|
||||
"hide_translation": "وەرگێڕان بشارەوە",
|
||||
"hide_username_emojis": "شاردنەوەی ئیمۆجیەکانی ناوی بەکارهێنەر",
|
||||
"hide_username_emojis_description": "شاردنەوەی ئیمۆجیەکان لە ناوی بەکارهێنەر لە هێڵی کات. ئیمۆجییەکان هێشتا لە پرۆفایلەکانیاندا دەبینرێن.",
|
||||
"label": "هەڵبژاردن",
|
||||
"title": "تایبەتمەندییە تاقیکارییەکان",
|
||||
"use_star_favorite_icon": "ئایکۆنی دڵخوازی ئەستێرە بەکاربهێنە",
|
||||
"user_picker": "هەڵبژێرەری بەکارهێنەر",
|
||||
"user_picker_description": "هەموو وێنۆچکەکانی هەژمارەکانی تۆمارکراوەکان لە خوارەوەی چەپ پیشان دەدات بۆ ئەوەی بتوانیت بە خێرایی لە نێوانیاندا بگۆڕیت.",
|
||||
"virtual_scroll": "سکڕۆڵکردنی مەجازی",
|
||||
"virtual_scroll_description": "لیستی مەجازی لە هێڵە کاتییەکاندا بەکاربهێنە، بۆ ئەوەی ژمارەیەکی زیاتر لە بابەتە بە شێوەیەکی کارا ڕەندەر بکرێت.",
|
||||
"wellbeing": "خۆشگوزەرانی",
|
||||
"zen_mode": "شێوەی Zen",
|
||||
"zen_mode_description": "لایەک بشارەوە مەگەر ئاماژەدەری ماوسەکە لەسەریان نەبێت. هەروەها هەندێک توخم لە هێڵی کاتی بشارەوە."
|
||||
},
|
||||
"profile": {
|
||||
"appearance": {
|
||||
"bio": "ژیاننامە",
|
||||
"description": "دەستکاری وێنۆچکە، ناوی بەکارهێنەر، پڕۆفایل و هتد.",
|
||||
"display_name": "ناوی پیشاندراو",
|
||||
"label": "پیشاندان",
|
||||
"profile_metadata": "مێتاداتای پڕۆفایلی",
|
||||
"profile_metadata_desc": "دەتوانیت تا {0} شت وەک خشتەیەک لە پڕۆفایلی خۆتدا پیشان بدەیت",
|
||||
"profile_metadata_label": "نیشانە",
|
||||
"profile_metadata_value": "ناوەڕۆک",
|
||||
"title": "دەستکاریکردنی پرۆفایل"
|
||||
},
|
||||
"featured_tags": {
|
||||
"description": "خەڵک دەتوانن لە ژێر ئەم هاشتاگانەدا لە بابەتە گشتیەکانتاندا بگەڕێن.",
|
||||
"label": "هاشتاگی تایبەت"
|
||||
},
|
||||
"label": "پرۆفایل"
|
||||
},
|
||||
"select_a_settings": "ڕێکخستنێک هەڵبژێرە",
|
||||
"users": {
|
||||
"export": "هەناردەکردنی تۆکنەکانی بەکارهێنەر",
|
||||
"import": "هاوردەکردنی تۆکنەکانی بەکارهێنەر",
|
||||
"label": "بەکارهێنەرانی چوونەژوورەوە"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"attachments_exceed_server_limit": "ژمارەی هاوپێچەکان سنووری بۆ هەر بابەتێک تێپەڕاند.",
|
||||
"attachments_limit_error": "سنوور بۆ هەر بابەتێک تێپەڕی",
|
||||
"edited": "(دەستکاری کراوە)",
|
||||
"editing": "دەستکاریکردن",
|
||||
"loading": "خرمەخرم ...",
|
||||
"publish_failed": "بڵاوکردنەوە سەرکەوتوو نەبوو",
|
||||
"publishing": "بڵاوکردنەوە",
|
||||
"save_failed": "هەڵگرتن سەرکەوتوو نەبوو",
|
||||
"upload_failed": "بارکردن سەرکەوتوو نەبوو",
|
||||
"uploading": "بارکردنەوە..."
|
||||
},
|
||||
"status": {
|
||||
"account": {
|
||||
"suspended_message": "هەژمارکردنی ئەم دۆخە ڕاگیراوە.",
|
||||
"suspended_show": "بەهەرحاڵ ناوەڕۆک نیشان بدە؟"
|
||||
},
|
||||
"boosted_by": "هاوبەشکردن لە لایەن",
|
||||
"edited": "{0} دەستکاریکراو",
|
||||
"favourited_by": "پەسەندکراوە لەلایەن",
|
||||
"filter_hidden_phrase": "پاڵێوراوە لەلایەن",
|
||||
"filter_show_anyway": "بەهەر حاڵ پیشان بدە",
|
||||
"img_alt": {
|
||||
"ALT": "ALT",
|
||||
"desc": "وەسف",
|
||||
"dismiss": "ڕێپێنەدان",
|
||||
"read": "وەسفی {0} بخوێنەوە"
|
||||
},
|
||||
"poll": {
|
||||
"count": "{0} دەنگ|{0} دەنگ|{0} دەنگ",
|
||||
"ends": "کۆتایی بە {0} دێت",
|
||||
"finished": "تەواو بوو {0}"
|
||||
},
|
||||
"replying_to": "وەڵامدانەوەی {0}",
|
||||
"show_full_thread": "تەوەرەی تەواو پیشان بدە",
|
||||
"someone": "کەسێک",
|
||||
"spoiler_media_hidden": "میدیای شاراوە",
|
||||
"spoiler_show_less": "کەمتر پیشان بدە",
|
||||
"spoiler_show_more": "زیاتر نیشان دە",
|
||||
"thread": "Thread",
|
||||
"try_original_site": "سایتی ئەسڵی تاقی بکەرەوە"
|
||||
},
|
||||
"status_history": {
|
||||
"created": "دروستکراوە {0}",
|
||||
"edited": "دەستکاری کراوە {0}"
|
||||
},
|
||||
"tab": {
|
||||
"accounts": "هەژمارەکان",
|
||||
"for_you": "بۆ تۆ",
|
||||
"hashtags": "هاشتاگەکان",
|
||||
"list": "لیست",
|
||||
"media": "مێدیا",
|
||||
"news": "هەواڵ",
|
||||
"notifications_all": "هەموو",
|
||||
"notifications_mention": "باسکردن",
|
||||
"posts": "بابەتەکان",
|
||||
"posts_with_replies": "بابەت و وەڵام"
|
||||
},
|
||||
"tag": {
|
||||
"follow": "بەدواداچوون",
|
||||
"follow_label": "شوێنکەوتنی تاگی {0} بکە",
|
||||
"unfollow": "شوێن نەکەوتن",
|
||||
"unfollow_label": "شوێنکەوتنی تاگی {0} مەکە"
|
||||
},
|
||||
"time_ago_options": {
|
||||
"day_future": "لە 0 ڕۆژدا|سبەی|لە {n} ڕۆژدا",
|
||||
"day_past": "0 ڕۆژ پێش ئێستا|دوێنێ|{n} ڕۆژ پێش ئێستا",
|
||||
"hour_future": "لە 0 کاتژمێردا|لە 1 کاتژمێردا|لە {n} کاتژمێردا",
|
||||
"hour_past": "0 کاتژمێر پێش ئێستا|1 کاتژمێر پێش ئێستا|{n} کاتژمێر پێش ئێستا",
|
||||
"just_now": "هەر ئێستا",
|
||||
"minute_future": "لە 0 خولەکدا|لە 1 خولەکدا|لە {n} خولەکدا",
|
||||
"minute_past": "0 خولەک پێش ئێستا|1 خولەک پێش ئێستا|{n} خولەک پێش ئێستا",
|
||||
"month_future": "لە 0 مانگ|مانگی داهاتوو|لە {n} مانگ",
|
||||
"month_past": "0 مانگ پێش ئێستا|مانگی ڕابردوو|{n} مانگ پێش ئێستا",
|
||||
"second_future": "تەنها ئێستا|لە {n} چرکە|لە {n} چرکە",
|
||||
"second_past": "تەنها ئێستا|{n} چرکە پێش ئێستا|{n} چرکە پێش ئێستا",
|
||||
"short_day_future": "لە {n}ڕۆژ",
|
||||
"short_day_past": "{n}ڕۆژ",
|
||||
"short_hour_future": "لە {n}کاژێر",
|
||||
"short_hour_past": "{n}کاژێر",
|
||||
"short_minute_future": "لە {n}خولەک",
|
||||
"short_minute_past": "{n}خولەک",
|
||||
"short_month_future": "لە {n}مانگ",
|
||||
"short_month_past": "{n}مانگ",
|
||||
"short_second_future": "لە {n}چرکه",
|
||||
"short_second_past": "{n}چرکه",
|
||||
"short_week_future": "لامەرکەزیدا {n}هەفتە",
|
||||
"short_week_past": "{n}هەفتە",
|
||||
"short_year_future": "لە {n}ساڵ",
|
||||
"short_year_past": "{n}ساڵ",
|
||||
"week_future": "لە 0 هەفتە|هەفتەی داهاتوو|لە {n} هەفتە",
|
||||
"week_past": "0 هەفتە پێش ئێستا|هەفتەی ڕابردوو|{n} هەفتە پێش ئێستا",
|
||||
"year_future": "لە 0 ساڵ|ساڵی داهاتوو|لە {n} ساڵ",
|
||||
"year_past": "0 ساڵ لەمەوبەر|ساڵی ڕابردوو|{n} ساڵ لەمەوبەر"
|
||||
},
|
||||
"timeline": {
|
||||
"show_new_items": "{v} بابەتی نوێ پیشان بدە|{v} بابەتی نوێ پیشان بدە|{v} بابەتی نوێ پیشان بدە",
|
||||
"view_older_posts": "ڕەنگە بابەتە کۆنەکانی نموونەکانی تر پیشان نەدرێت."
|
||||
},
|
||||
"title": {
|
||||
"federated_timeline": "هێڵی کاتی فێدرێرکراو",
|
||||
"local_timeline": "هێڵی کاتی ناوخۆیی"
|
||||
},
|
||||
"tooltip": {
|
||||
"add_content_warning": "ئاگادارکردنەوەی ناوەڕۆک زیاد بکە",
|
||||
"add_emojis": "ئیمۆجی زیاد بکە",
|
||||
"add_media": "زیادکردنی وێنە، ڤیدیۆ یان پەڕگەیێکی دەنگی",
|
||||
"add_publishable_content": "ناوەڕۆک زیاد بکە بۆ بڵاوکردنەوە",
|
||||
"change_content_visibility": "گۆڕینی بینینی ناوەڕۆک",
|
||||
"change_language": "گۆڕینی زمان",
|
||||
"emoji": "ئیمۆجی",
|
||||
"explore_links_intro": "ئەم هەواڵانە لەلایەن ئەندامان و ڕاژەکارەکانی دیکەی تۆڕی کۆمەڵایەتییەوە باس دەکرێن.",
|
||||
"explore_posts_intro": "ئەم بابەتانە لەم ڕاژەکارە و ڕاژەکارەکانی تر لە تۆڕی لامەرکەزی ئێستا لەسەر ئەم ڕاژەکارە ڕاکێشن.",
|
||||
"explore_tags_intro": "ئەم هاشتاگانە لە ئێستادا لە نێو ئەندامانی سەر ئەم ڕاژەکارە و ڕاژەکارەکانی تری تۆڕی لامەرکەزیدا جێگەی خۆیان دەگرن.",
|
||||
"open_editor_tools": "ئامرازەکانی سەرنوسەر",
|
||||
"pick_an_icon": "ئایکۆنێک هەڵبژێرە",
|
||||
"publish_failed": "نامە شکستخواردووەکان لە سەرەوەی سەرنوسەر دابخە بۆ دووبارە بڵاوکردنەوەی پۆستەکان",
|
||||
"toggle_bold": "قەڵەو کردن",
|
||||
"toggle_code_block": "بلۆکی کۆد",
|
||||
"toggle_italic": "لار کردن"
|
||||
},
|
||||
"user": {
|
||||
"add_existing": "هەژمارەیەکی بەردەست زیاد بکە",
|
||||
"server_address_label": "ناونیشانی ڕاژەکاری ماستۆدۆن",
|
||||
"sign_in_desc": "چوونەژوورەوە بۆ شوێنکەوتنی پڕۆفایل یان هاشتاگەکان، دڵخوازەکان، هاوبەشکردن و وەڵامدانەوەی بابەتەکان، یان کارلێککردن لە هەژمارەکەتەوە لەسەر ڕاژەکارێکی جیاواز.",
|
||||
"sign_in_notice_title": "بینینی {0} داتا گشتیەکان",
|
||||
"sign_out_account": "چوونە دەرەوە {0}",
|
||||
"single_instance_sign_in_desc": "چوونەژوورەوە بۆ شوێنکەوتنی پڕۆفایل یان هاشتاگەکان، دڵخوازەکان، هاوبەشکردن و وەڵامدانەوەی بابەتەکان،.",
|
||||
"tip_no_account": "ئەگەر هێشتا هەژمارەیێکی ماستۆدۆنت نییە، {0}.",
|
||||
"tip_register_account": "ڕاژەکارەکەت هەڵبژێرە و یەکێکیان تۆمار بکە"
|
||||
},
|
||||
"visibility": {
|
||||
"direct": "ڕاستەوخۆ",
|
||||
"direct_desc": "بینراو تەنها بۆ بەکارهێنەرە ئاماژەپێکراو",
|
||||
"private": "تەنها شوێنکەوتووان",
|
||||
"private_desc": "بینراو تەنها بۆ شوێنکەوتووان",
|
||||
"public": "گشتی",
|
||||
"public_desc": "بینراو بۆ هەمووان",
|
||||
"unlisted": "لیست نەکراوە",
|
||||
"unlisted_desc": "بینراو بۆ هەمووان، بەڵام هەڵبژێردراو لە تایبەتمەندییەکانی دۆزینەوە"
|
||||
}
|
||||
}
|
|
@ -218,8 +218,18 @@
|
|||
"title": "Medien"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_bookmarks": "Favoriten",
|
||||
"go_to_conversations": "Direktnachrichten",
|
||||
"go_to_explore": "Entdecken",
|
||||
"go_to_favourites": "Favoriten",
|
||||
"go_to_federated": "Föderiert",
|
||||
"go_to_home": "Startseite",
|
||||
"go_to_lists": "Listen",
|
||||
"go_to_local": "Lokal",
|
||||
"go_to_notifications": "Benachrichtigungen",
|
||||
"go_to_profile": "Profil",
|
||||
"go_to_search": "Suche",
|
||||
"go_to_settings": "Einstellungen",
|
||||
"next_status": "Nächster Status",
|
||||
"previous_status": "Vorheriger Status",
|
||||
"shortcut_help": "Tastaturkürzel-Hilfe",
|
||||
|
|
|
@ -149,7 +149,12 @@
|
|||
"mute_account": {
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Mute",
|
||||
"days": "days|day|days",
|
||||
"description": "Are you sure you want to mute {0}?",
|
||||
"hours": "hours|hour|hours",
|
||||
"minute": "minutes|minute|minutes",
|
||||
"notifications": "Mute notifications",
|
||||
"specify_duration": "Specify mute duration",
|
||||
"title": "Mute account"
|
||||
},
|
||||
"show_reblogs": {
|
||||
|
@ -191,14 +196,14 @@
|
|||
"title": "Preview deploy"
|
||||
},
|
||||
"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_para2": "we are working hard on the development and improving it over time.",
|
||||
"desc_para1": "Elk is a nimble Mastodon web client. You can login to your Mastodon account and use it to interact with the fediverse.",
|
||||
"desc_para2": "Elk is Open Source and we're actively improving it as a community project. Join us and let's build it together!",
|
||||
"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": "If you'd like to report a bug, help us testing, give feedback, or contribute,",
|
||||
"desc_para5": "reach out to us on GitHub",
|
||||
"desc_para6": "and get involved.",
|
||||
"footer_team": "The Elk Team",
|
||||
"title": "Elk is in Preview!"
|
||||
"title": "Welcome to Elk!"
|
||||
},
|
||||
"language": {
|
||||
"search": "Search"
|
||||
|
@ -226,7 +231,7 @@
|
|||
"boost": "Boost",
|
||||
"command_mode": "Command mode",
|
||||
"compose": "Compose",
|
||||
"favourite": "Favourite",
|
||||
"favourite": "Favorite",
|
||||
"search": "Search",
|
||||
"show_new_items": "Show new items",
|
||||
"title": "Actions"
|
||||
|
@ -235,8 +240,18 @@
|
|||
"title": "Media"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_bookmarks": "Bookmarks",
|
||||
"go_to_conversations": "Conversations",
|
||||
"go_to_explore": "Explore",
|
||||
"go_to_favourites": "Favorites",
|
||||
"go_to_federated": "Federated",
|
||||
"go_to_home": "Home",
|
||||
"go_to_lists": "Lists",
|
||||
"go_to_local": "Local",
|
||||
"go_to_notifications": "Notifications",
|
||||
"go_to_profile": "Profile",
|
||||
"go_to_search": "Search",
|
||||
"go_to_settings": "Settings",
|
||||
"next_status": "Next post",
|
||||
"previous_status": "Previous post",
|
||||
"shortcut_help": "Shortcut help",
|
||||
|
@ -291,9 +306,11 @@
|
|||
"built_at": "Built {0}",
|
||||
"compose": "Compose",
|
||||
"conversations": "Conversations",
|
||||
"docs": "Documentation",
|
||||
"explore": "Explore",
|
||||
"favourites": "Favorites",
|
||||
"federated": "Federated",
|
||||
"hashtags": "Hashtags",
|
||||
"home": "Home",
|
||||
"list": "List",
|
||||
"lists": "Lists",
|
||||
|
@ -443,6 +460,7 @@
|
|||
},
|
||||
"language": {
|
||||
"display_language": "Display Language",
|
||||
"how_to_contribute": "How to contribute?",
|
||||
"label": "Language",
|
||||
"post_language": "Posting Language",
|
||||
"status": "Translation status: {0}/{1} ({2}%)",
|
||||
|
@ -524,8 +542,10 @@
|
|||
"hide_boost_count": "Hide boost count",
|
||||
"hide_favorite_count": "Hide favorite count",
|
||||
"hide_follower_count": "Hide following/follower count",
|
||||
"hide_gif_indi_on_posts": "Hide gif indicator on posts",
|
||||
"hide_news": "Hide news",
|
||||
"hide_reply_count": "Hide reply count",
|
||||
"hide_tag_hover_card": "Hide tag hover card",
|
||||
"hide_translation": "Hide translation",
|
||||
"hide_username_emojis": "Hide username emojis",
|
||||
"hide_username_emojis_description": "Hides emojis from usernames in timelines. Emojis will still be visible in their profiles.",
|
||||
|
@ -574,7 +594,11 @@
|
|||
},
|
||||
"state": {
|
||||
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
|
||||
"attachments_limit_audio_error": "Maximum audio size exceeded: {0}",
|
||||
"attachments_limit_error": "Limit per post exceeded",
|
||||
"attachments_limit_image_error": "Maximum image size exceeded: {0}",
|
||||
"attachments_limit_unknown_error": "Maximum file size exceeded: {0}",
|
||||
"attachments_limit_video_error": "Maximum video size exceeded: {0}",
|
||||
"edited": "(Edited)",
|
||||
"editing": "Editing",
|
||||
"loading": "Loading...",
|
||||
|
@ -595,6 +619,7 @@
|
|||
"favourited_by": "Favorited By",
|
||||
"filter_hidden_phrase": "Filtered by",
|
||||
"filter_show_anyway": "Show anyway",
|
||||
"gif": "GIF",
|
||||
"img_alt": {
|
||||
"ALT": "ALT",
|
||||
"desc": "Description",
|
||||
|
|
|
@ -84,6 +84,16 @@
|
|||
"add_account": "Añadir cuenta a la lista",
|
||||
"remove_account": "Quitar cuenta de la lista"
|
||||
},
|
||||
"magic_keys": {
|
||||
"groups": {
|
||||
"navigation": {
|
||||
"go_to_conversations": "Mensajes directos",
|
||||
"go_to_federated": "Historia federada",
|
||||
"go_to_local": "Historia local",
|
||||
"go_to_settings": "Preferencias"
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"block_domain": "Ocultar dominio {0}",
|
||||
"delete_and_redraft": "Eliminar y volver a borrador",
|
||||
|
@ -174,6 +184,9 @@
|
|||
"label": "Usuarios en línea"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"attachments_limit_video_error": "Tamaño máximo de video excedido: {0}"
|
||||
},
|
||||
"status": {
|
||||
"spoiler_show_less": "Menos"
|
||||
},
|
||||
|
|
|
@ -149,7 +149,12 @@
|
|||
"mute_account": {
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Silenciar",
|
||||
"days": "días|día|días",
|
||||
"description": "¿Estás seguro que quieres silenciar a {0}?",
|
||||
"hours": "horas|hora|horas",
|
||||
"minute": "minutos|minuto|minutos",
|
||||
"notifications": "Silenciar notificaciones",
|
||||
"specify_duration": "Especificar la duración del silenciado",
|
||||
"title": "Silenciar cuenta"
|
||||
},
|
||||
"show_reblogs": {
|
||||
|
@ -235,8 +240,18 @@
|
|||
"title": "Multimedia"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_bookmarks": "Marcadores",
|
||||
"go_to_conversations": "Conversaciones",
|
||||
"go_to_explore": "Explorar",
|
||||
"go_to_favourites": "Favoritas",
|
||||
"go_to_federated": "Federados",
|
||||
"go_to_home": "Inicio",
|
||||
"go_to_lists": "Listas",
|
||||
"go_to_local": "Local",
|
||||
"go_to_notifications": "Notificaciones",
|
||||
"go_to_profile": "Perfil",
|
||||
"go_to_search": "Buscar",
|
||||
"go_to_settings": "Ajustes",
|
||||
"next_status": "Siguiente estado",
|
||||
"previous_status": "Anterior estado",
|
||||
"shortcut_help": "Atajo de ayuda",
|
||||
|
@ -291,9 +306,11 @@
|
|||
"built_at": "Compilado {0}",
|
||||
"compose": "Redactar",
|
||||
"conversations": "Conversaciones",
|
||||
"docs": "Documentación",
|
||||
"explore": "Explorar",
|
||||
"favourites": "Favoritas",
|
||||
"federated": "Federados",
|
||||
"hashtags": "Etiquetas",
|
||||
"home": "Inicio",
|
||||
"list": "Lista",
|
||||
"lists": "Listas",
|
||||
|
@ -443,6 +460,7 @@
|
|||
},
|
||||
"language": {
|
||||
"display_language": "Idioma de pantalla",
|
||||
"how_to_contribute": "¿Cómo contribuir?",
|
||||
"label": "Idioma",
|
||||
"post_language": "Idioma de publicación",
|
||||
"status": "Estado traducción: {0}/{1} ({2}%)",
|
||||
|
@ -524,8 +542,10 @@
|
|||
"hide_boost_count": "Ocultar contador de retoots",
|
||||
"hide_favorite_count": "Ocultar número de publicaciones favoritas",
|
||||
"hide_follower_count": "Ocultar número de seguidores",
|
||||
"hide_gif_indi_on_posts": "Ocultar indicador de gif en publicaciones",
|
||||
"hide_news": "Ocultar noticias",
|
||||
"hide_reply_count": "Ocultar número de respuestas",
|
||||
"hide_tag_hover_card": "Ocultar tarjeta flotante de etiqueta",
|
||||
"hide_translation": "Ocultar traducción",
|
||||
"hide_username_emojis": "Ocultar emojis en el nombre de usuario",
|
||||
"hide_username_emojis_description": "Oculta los emojis de los nombres de usuarios en la línea de tiempo. Los emojis permanecerán visibles en sus perfiles.",
|
||||
|
@ -574,7 +594,11 @@
|
|||
},
|
||||
"state": {
|
||||
"attachments_exceed_server_limit": "Número máximo de archivos adjuntos por publicación excedido.",
|
||||
"attachments_limit_audio_error": "Tamaño máximo de audio excedido: {0}",
|
||||
"attachments_limit_error": "Límite por publicación excedido",
|
||||
"attachments_limit_image_error": "Tamaño máximo de imagen excedido: {0}",
|
||||
"attachments_limit_unknown_error": "Tamaño máximo de archivo excedido: {0}",
|
||||
"attachments_limit_video_error": "Tamaño máximo de vídeo excedido: {0}",
|
||||
"edited": "(Editado)",
|
||||
"editing": "Editando",
|
||||
"loading": "Cargando...",
|
||||
|
@ -595,6 +619,7 @@
|
|||
"favourited_by": "Marcado como favorita por",
|
||||
"filter_hidden_phrase": "Filtrado por",
|
||||
"filter_show_anyway": "Mostrar de todas formas",
|
||||
"gif": "GIF",
|
||||
"img_alt": {
|
||||
"ALT": "ALT",
|
||||
"desc": "Descripción",
|
||||
|
@ -691,7 +716,7 @@
|
|||
"tooltip": {
|
||||
"add_content_warning": "Añadir advertencia de contenido",
|
||||
"add_emojis": "Agregar emojis",
|
||||
"add_media": "Añadir imágenes, video o audio",
|
||||
"add_media": "Añadir imágenes, vídeo o audio",
|
||||
"add_publishable_content": "Publicar contenido",
|
||||
"change_content_visibility": "Cambiar visibilidad de contenido",
|
||||
"change_language": "Cambiar idioma",
|
||||
|
|
|
@ -149,7 +149,12 @@
|
|||
"mute_account": {
|
||||
"cancel": "Utzi",
|
||||
"confirm": "Mututu",
|
||||
"days": "egun|egun|egun",
|
||||
"description": "Ziur {0} mututu nahi duzula?",
|
||||
"hours": "ordu|ordu|ordu",
|
||||
"minute": "minutu|minutu|minutu",
|
||||
"notifications": "Mututu jakinarazpenak",
|
||||
"specify_duration": "Zehaztu mututzearen iraupena",
|
||||
"title": "Mututu kontua"
|
||||
},
|
||||
"show_reblogs": {
|
||||
|
@ -228,16 +233,27 @@
|
|||
"compose": "Idatzi",
|
||||
"favourite": "Egin gogoko",
|
||||
"search": "Bilatu",
|
||||
"show_new_items": "Erakutsi elementu berriak",
|
||||
"title": "Ekintzak"
|
||||
},
|
||||
"media": {
|
||||
"title": "Multimedia"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_bookmarks": "Laster-markak",
|
||||
"go_to_conversations": "Elkarrizketak",
|
||||
"go_to_explore": "Arakatu",
|
||||
"go_to_favourites": "Gogokoak",
|
||||
"go_to_federated": "Federatua",
|
||||
"go_to_home": "Hasiera",
|
||||
"go_to_lists": "Zerrendak",
|
||||
"go_to_local": "Lokala",
|
||||
"go_to_notifications": "Jakinarazpenak",
|
||||
"next_status": "Hurrengo egoera",
|
||||
"previous_status": "Aurreko egoera",
|
||||
"go_to_profile": "Profila",
|
||||
"go_to_search": "Bilatu",
|
||||
"go_to_settings": "Ezarpenak",
|
||||
"next_status": "Hurrengo bidalketa",
|
||||
"previous_status": "Aurreko bidalketa",
|
||||
"shortcut_help": "Laster-teklak",
|
||||
"title": "Nabigazioa"
|
||||
}
|
||||
|
@ -293,6 +309,7 @@
|
|||
"explore": "Arakatu",
|
||||
"favourites": "Gogokoak",
|
||||
"federated": "Federatua",
|
||||
"hashtags": "Traolak",
|
||||
"home": "Hasiera",
|
||||
"list": "Zerrenda",
|
||||
"lists": "Zerrendak",
|
||||
|
@ -304,22 +321,22 @@
|
|||
"search": "Bilaketa",
|
||||
"select_feature_flags": "Aldatu ezaugarrien adierazleak (Toggle Feature Flags)",
|
||||
"select_font_size": "Letra-tipoaren tamaina",
|
||||
"select_language": "Hizkuntza",
|
||||
"select_language": "Interfazearen hizkuntza",
|
||||
"settings": "Ezarpenak",
|
||||
"show_intro": "Erakutsi aurkezpena",
|
||||
"toggle_theme": "Gai argia/iluna",
|
||||
"zen_mode": "ZEN modua"
|
||||
},
|
||||
"notification": {
|
||||
"favourited_post": "zure bidalketa gogoko egin du",
|
||||
"favourited_post": "(e)k zure bidalketa gogoko egin du",
|
||||
"followed_you": "(e)k jarraitu dizu",
|
||||
"followed_you_count": "{0} pertsonak jarraitu dizute|pertsona {0}ek jarraitu dizu|{0} pertsonak jarraitu dizute",
|
||||
"missing_type": "MISSING notification.type:",
|
||||
"reblogged_post": "zure bidalketari bultzada eman dio",
|
||||
"reblogged_post": "(e)k zure bidalketari bultzada eman dio",
|
||||
"reported": "{0}(e)k {1} salatu du",
|
||||
"request_to_follow": "jarraipen-eskaera bidali dizu",
|
||||
"signed_up": "izena eman du",
|
||||
"update_status": "bidalketa eguneratu du"
|
||||
"request_to_follow": "(e)k jarraipen-eskaera bidali dizu",
|
||||
"signed_up": "(e)k izena eman du",
|
||||
"update_status": "(e)k bidalketa eguneratu du"
|
||||
},
|
||||
"placeholder": {
|
||||
"content_warning": "Idatzi oharra hemen",
|
||||
|
@ -374,7 +391,7 @@
|
|||
}
|
||||
},
|
||||
"report": {
|
||||
"additional_comments": "Ohar gehigarriak",
|
||||
"additional_comments": "Iruzkin gehigarriak",
|
||||
"another_server": "Salatzen ari zaren erabiltzailea beste zerbitzari batekoa da",
|
||||
"anything_else": "Ba al dago jakin beharko genukeen beste ezer?",
|
||||
"block_desc": "Ez duzu erabiltzaile honen bidalketarik ikusiko. Ezin izango ditu zure bidalketak ikusi edo zuri jarraitu. Blokeatu duzula jakingo du.",
|
||||
|
@ -395,10 +412,10 @@
|
|||
"limiting": "Mugatu {0}",
|
||||
"mute_desc": "Ez duzu erabiltzaile honen bidalketarik ikusiko. Zure bidalketak ikusi eta zuri jarraitu diezazuke. Ez du jakingo mututu duzunik.",
|
||||
"other": "Beste zerbait da",
|
||||
"other_desc": "Arazoa ez da beste kategorietan sartzen",
|
||||
"other_desc": "Arazoa ez dator bat beste kategoriekin",
|
||||
"reporting": "{0} salatzen",
|
||||
"select_many": "Hautatu bat datozen guztiak:",
|
||||
"select_one": "Aukeratu hobekien datorrena:",
|
||||
"select_many": "Hautatu dagozkion guztiak:",
|
||||
"select_one": "Aukeratu hobekien doakiona:",
|
||||
"select_posts": "Salaketa hau sostengatzen duen bidalketarik al dago?",
|
||||
"select_posts_other": "Ba al dago salaketa sostengatzen duen beste bidalketarik?",
|
||||
"spam": "Spama da",
|
||||
|
@ -441,9 +458,10 @@
|
|||
"theme_color": "Gaiaren kolorea"
|
||||
},
|
||||
"language": {
|
||||
"display_language": "Hizkuntza",
|
||||
"display_language": "Interfazearen hizkuntza",
|
||||
"how_to_contribute": "Nola lagun dezaket?",
|
||||
"label": "Hizkuntza",
|
||||
"post_language": "Argitalpenaren hizkuntza",
|
||||
"post_language": "Bidalketen hizkuntza",
|
||||
"status": "Itzulpenaren egoera: {1} kateetatik {0} itzulita (%{2}a)",
|
||||
"translations": {
|
||||
"add": "Gehitu",
|
||||
|
@ -518,13 +536,14 @@
|
|||
"github_cards": "GitHub txartelak",
|
||||
"github_cards_description": "GitHub-eko esteka argitaratzean, HTML txartel irisgarri bat erakusten du gizarte-grafikoaren metadatuak erabiliz, irudi sozialaren ordez.",
|
||||
"grayscale_mode": "Gris modua",
|
||||
"hide_account_hover_card": "Ezkutatu kontuaren geruza (Hide account hover card)",
|
||||
"hide_account_hover_card": "Ezkutatu kontuen aurrebista-txartelak sagua gainetik pasatzean",
|
||||
"hide_alt_indi_on_posts": "Ezkutatu ALT adierazlea bidalketetan",
|
||||
"hide_boost_count": "Ezkutatu bultzaden kopurua",
|
||||
"hide_favorite_count": "Ezkutatu gogokoen kopurua",
|
||||
"hide_follower_count": "Ezkutatu jarraitzaile kopurua",
|
||||
"hide_news": "Ezkutatu berriak",
|
||||
"hide_reply_count": "Ezkutatu erantzunen kopurua",
|
||||
"hide_tag_hover_card": "Ezkutatu traolen aurrebista-txartelak sagua gainetik pasatzean",
|
||||
"hide_translation": "Ezkutatu itzulpenak",
|
||||
"hide_username_emojis": "Ezkutatu emojiak erabiltzaile izenetan",
|
||||
"hide_username_emojis_description": "Denbora-lerroetan erabiltzaile izenetako emojiak ezkutatzen ditu. Emojiak ikusgai egoten jarraituko dute euren profiletan.",
|
||||
|
@ -537,7 +556,7 @@
|
|||
"virtual_scroll": "Korritze birtuala",
|
||||
"virtual_scroll_description": "Zerrenda birtual bat erabiltzen du denbora-lerroetan, item kopuru handiagoa modu eraginkorrean erakutsi ahal izateko.",
|
||||
"wellbeing": "Ongizatea",
|
||||
"zen_mode": "Zen modua",
|
||||
"zen_mode": "ZEN modua",
|
||||
"zen_mode_description": "Aldamenetako elementuak ezkutatzen ditu xagua gainean jarri arte. Denbora-lerroko elementu batzuk ere ezkutatzen ditu."
|
||||
},
|
||||
"profile": {
|
||||
|
@ -585,7 +604,7 @@
|
|||
},
|
||||
"status": {
|
||||
"account": {
|
||||
"suspended_message": "Argitalpen honen egileari zigorra jarri zaio.",
|
||||
"suspended_message": "Bidalketa honen egileari zigorra jarri zaio.",
|
||||
"suspended_show": "Erakutsi edukia hala ere?"
|
||||
},
|
||||
"boosted_by": "Bultzatu dute:",
|
||||
|
|
715
locales/fa-IR.json
Normal file
715
locales/fa-IR.json
Normal file
|
@ -0,0 +1,715 @@
|
|||
{
|
||||
"a11y": {
|
||||
"loading_page": "در حال بارگذاری صفحه، لطفا صبر کنید",
|
||||
"loading_titled_page": "در حال بارگذاری صفحه {0}، لطفا صبر کنید",
|
||||
"locale_changed": "زبان به {0} تغییر کرد",
|
||||
"locale_changing": "در حال تغییر زبان، لطفا صبر کنید",
|
||||
"route_loaded": "صفحه {0} بارگذاری شد"
|
||||
},
|
||||
"account": {
|
||||
"authorize": "اجازه دنبال کردن بدهید",
|
||||
"authorized": "شما درخواست را تأیید کردید",
|
||||
"avatar_description": "آواتار {0}",
|
||||
"blocked_by": "شما توسط این کاربر مسدود شدهاید.",
|
||||
"blocked_domains": "دامنههای مسدود شده",
|
||||
"blocked_users": "کاربران مسدود شده",
|
||||
"blocking": "مسدود شده",
|
||||
"bot": "ربات",
|
||||
"copy_account_name": "رونوشت از نام حساب",
|
||||
"favourites": "موارد مورد علاقه",
|
||||
"follow": "دنبال کنید",
|
||||
"follow_back": "دنبال کردن متقابل",
|
||||
"follow_requested": "درخواست شده",
|
||||
"followers": "دنبالکنندگان",
|
||||
"followers_count": "{0} دنبالکننده",
|
||||
"following": "دنبالشده",
|
||||
"following_count": "{0} در حال دنبال کردن",
|
||||
"follows_you": "شما را دنبال میکند",
|
||||
"go_to_profile": "به نمایه بروید",
|
||||
"joined": "پیوسته",
|
||||
"moved_title": "اعلام کرده که این حساب کاربری جدید ایشان است:",
|
||||
"muted_users": "کاربران بیصدا شده",
|
||||
"muting": "بی صدا",
|
||||
"mutuals": "مشترکات",
|
||||
"notifications_on_post_disable": "دیگر هنگامی که {username} فرسته ای ارسال میکند مرا با خبر نکن",
|
||||
"notifications_on_post_enable": "هنگامی که {username} فرسته ای را ارسال میکند مرا با خبر کن",
|
||||
"pinned": "سنجاق شده",
|
||||
"posts": "فرستهها",
|
||||
"posts_count": "{0} فرسته|{0} فرسته|{0} فرسته",
|
||||
"profile_description": "توضیحات بالای صفحه نمایه {0}",
|
||||
"profile_personal_note": "یادداشت شخصی",
|
||||
"profile_unavailable": "نمایه در دسترس نیست",
|
||||
"reject": "رد درخواست",
|
||||
"rejected": "درخواست را رد کردید",
|
||||
"request_follow": "درخواست برای دنبال کردن",
|
||||
"requested": "{0} درخواست دنبال کردن شما را دارد",
|
||||
"unblock": "رفع مسدودیت",
|
||||
"unfollow": "دنبال نکردن",
|
||||
"unmute": "باصدا",
|
||||
"view_other_followers": "دنبالکنندگان از نمونههای دیگر ممکن است نمایش داده نشوند.",
|
||||
"view_other_following": "دنبال شوندگان از نمونههای دیگر ممکن است نمایش داده نشوند.",
|
||||
"withdraw_follow_request": "درخواست دنبالکردن را پس بگیرید"
|
||||
},
|
||||
"action": {
|
||||
"apply": "اعمال",
|
||||
"bookmark": "نشانک",
|
||||
"bookmarked": "نشانک زده شده",
|
||||
"boost": "تقویت",
|
||||
"boost_count": "{0}",
|
||||
"boosted": "تقویت شده",
|
||||
"clear_publish_failed": "پاک کردن خطاهای انتشار",
|
||||
"clear_save_failed": "پاک کردن خطاهای ذخیرهسازی",
|
||||
"clear_upload_failed": "پاک کردن خطاهای بارگذاری فایل",
|
||||
"close": "بستن",
|
||||
"compose": "نوشتن",
|
||||
"confirm": "تایید",
|
||||
"done": "انجام شد",
|
||||
"edit": "ویرایش",
|
||||
"enter_app": "ورود به برنامه",
|
||||
"favourite": "مورد علاقه",
|
||||
"favourite_count": "{0}",
|
||||
"favourited": "به علاقهمندیها افزوده شد",
|
||||
"more": "بیشتر",
|
||||
"next": "بعدی",
|
||||
"prev": "قبلی",
|
||||
"publish": "انتشار",
|
||||
"reply": "پاسخ",
|
||||
"reply_count": "{0}",
|
||||
"reset": "بازنشانی",
|
||||
"save": "ذخیره",
|
||||
"save_changes": "ذخیره تغییرات",
|
||||
"sign_in": "ورود",
|
||||
"sign_in_to": "ورود به {0}",
|
||||
"switch_account": "تغییر حساب کاربری",
|
||||
"vote": "رای"
|
||||
},
|
||||
"app_desc_short": "یک کارخواه وب چابک برای ماستودون",
|
||||
"app_logo": "لوگوی نرمافزار",
|
||||
"app_name": "Elk",
|
||||
"attachment": {
|
||||
"edit_title": "توضیحات",
|
||||
"remove_label": "حذف پیوست"
|
||||
},
|
||||
"command": {
|
||||
"activate": "فعالسازی",
|
||||
"complete": "کامل کردن",
|
||||
"compose_desc": "نوشتن فرسته جدید",
|
||||
"n_people_in_the_past_n_days": "{0} نفر در {1} روز گذشته",
|
||||
"select_lang": "انتخاب زبان",
|
||||
"sign_in_desc": "افزودن حساب کاربری موجود",
|
||||
"switch_account": "تغییر به {0}",
|
||||
"switch_account_desc": "تغییر حساب به حساب دیگری",
|
||||
"toggle_dark_mode": "تغییر حالت تاریک",
|
||||
"toggle_zen_mode": "تغییر حالت آرام"
|
||||
},
|
||||
"common": {
|
||||
"end_of_list": "پایان لیست",
|
||||
"error": "خطا",
|
||||
"fetching": "در حال بارگیری...",
|
||||
"in": "در",
|
||||
"no_bookmarks": "هنوز هیچ فرسته نشانه گذاری شده ای وجود ندارد",
|
||||
"no_favourites": "فرسته پسندیده شده ای هنوز وجود ندارد",
|
||||
"not_found": "۴۰۴ پیدا نشد",
|
||||
"offline_desc": "به نظر میرسد شما آفلاین هستید. لطفا اتصال شبکه خود را بررسی کنید."
|
||||
},
|
||||
"confirm": {
|
||||
"block_account": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "مسدود کردن",
|
||||
"title": "آیا از مسدود کردن {0} اطمینان دارید؟"
|
||||
},
|
||||
"block_domain": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "مسدود کردن",
|
||||
"title": "آیا از مسدود کردن دامنه {0} اطمینان دارید؟"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "خیر",
|
||||
"confirm": "بله"
|
||||
},
|
||||
"delete_list": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "حذف",
|
||||
"title": "آیا از حذف لیست {0} اطمینان دارید؟"
|
||||
},
|
||||
"delete_posts": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "حذف",
|
||||
"title": "آیا از حذف این فرسته اطمینان دارید؟"
|
||||
},
|
||||
"mute_account": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "بیصدا کردن",
|
||||
"title": "آیا از بیصدا کردن {0} اطمینان دارید؟"
|
||||
},
|
||||
"show_reblogs": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "نمایش",
|
||||
"title": "آیا میخواهید بازنشرها از {0} را نمایش دهید؟"
|
||||
},
|
||||
"unfollow": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "دنبال نکردن",
|
||||
"title": "آیا از دنبال نکردن اطمینان دارید؟"
|
||||
}
|
||||
},
|
||||
"conversation": {
|
||||
"with": "با"
|
||||
},
|
||||
"custom_cards": {
|
||||
"stackblitz": {
|
||||
"lines": "خطوط {0}",
|
||||
"open": "باز کردن",
|
||||
"snippet_from": "تکه کد از {0}"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"account_not_found": "حساب {0} یافت نشد",
|
||||
"explore_list_empty": "هیچ چیز تازهای وجود ندارد. بعداً بررسی کنید!",
|
||||
"file_size_cannot_exceed_n_mb": "حجم فایل نمیتواند از {0}MB بیشتر باشد",
|
||||
"sign_in_error": "امکان برقراری ارتباط با سرور وجود ندارد.",
|
||||
"status_not_found": "فرسته یافت نشد",
|
||||
"unsupported_file_format": "قالب فایل پشتیبانی نمیشود"
|
||||
},
|
||||
"help": {
|
||||
"build_preview": {
|
||||
"desc1": "شما در حال مشاهده نسخه پیشنمایش جامعه نرمافزار هستید - {0}.",
|
||||
"desc2": "ممکن است شامل تغییرات بررسی نشده یا حتی مخرب باشد.",
|
||||
"desc3": "با حساب کاربری واقعی خود وارد نشوید.",
|
||||
"title": "استقرار پیشنمایش"
|
||||
},
|
||||
"desc_highlight": "انتظار برخی اشکالات و ویژگیهای ناقص را داشته باشید.",
|
||||
"desc_para1": "از علاقه شما به امتحان کردن نرمافزار، کارخواه وب در دست توسعه ماستودون تشکر میکنیم!",
|
||||
"desc_para2": "ما به سختی در حال کار بر روی توسعه و بهبود آن با گذر زمان هستیم.",
|
||||
"desc_para3": "برای تقویت توسعه، میتوانید از طریق GitHub Sponsors از تیم حمایت کنید. امیدواریم از نرمافزار لذت ببرید!",
|
||||
"desc_para4": "نرمافزار یک منبع باز است. اگر دوست دارید در تستکردن، ارائه بازخورد، یا مشارکت کمک کنید،",
|
||||
"desc_para5": "از طریق GitHub با ما تماس بگیرید",
|
||||
"desc_para6": "و مشارکت کنید.",
|
||||
"footer_team": "تیم نرمافزار",
|
||||
"title": "نرمافزار در حالت پیش نمایش است!"
|
||||
},
|
||||
"language": {
|
||||
"search": "جستجو"
|
||||
},
|
||||
"list": {
|
||||
"add_account": "اضافه کردن حساب به لیست",
|
||||
"cancel_edit": "لغو ویرایش",
|
||||
"clear_error": "پاک کردن خطا",
|
||||
"create": "ایجاد کردن",
|
||||
"delete": "حذف این لیست",
|
||||
"delete_error": "خطایی هنگام حذف لیست رخ داد",
|
||||
"edit": "ویرایش این لیست",
|
||||
"edit_error": "خطایی هنگام بهروزرسانی لیست رخ داد",
|
||||
"error": "خطایی هنگام ایجاد لیست رخ داد",
|
||||
"error_prefix": "خطا: ",
|
||||
"list_title_placeholder": "عنوان لیست",
|
||||
"modify_account": "تغییر دادن لیستها با حساب کاربری",
|
||||
"remove_account": "حذف حساب از لیست",
|
||||
"save": "ذخیره تغییرات"
|
||||
},
|
||||
"magic_keys": {
|
||||
"dialog_header": "میانبرهای صفحهکلید",
|
||||
"groups": {
|
||||
"actions": {
|
||||
"boost": "تقویت",
|
||||
"command_mode": "حالت فرمان",
|
||||
"compose": "تهیه فرسته",
|
||||
"favourite": "مورد علاقه",
|
||||
"search": "جستجو",
|
||||
"title": "اقدامات"
|
||||
},
|
||||
"media": {
|
||||
"title": "رسانه"
|
||||
},
|
||||
"navigation": {
|
||||
"go_to_home": "خانه",
|
||||
"go_to_notifications": "اعلانها",
|
||||
"next_status": "فرسته بعدی",
|
||||
"previous_status": "فرسته قبلی",
|
||||
"shortcut_help": "راهنمای میانبرها",
|
||||
"title": "ناوبری"
|
||||
}
|
||||
},
|
||||
"sequence_then": "در ادامه دنباله"
|
||||
},
|
||||
"menu": {
|
||||
"add_personal_note": "افزودن یادداشت شخصی به {0}",
|
||||
"block_account": "مسدود کردن {0}",
|
||||
"block_domain": "مسدود کردن دامنه {0}",
|
||||
"copy_link_to_post": "کپی لینک این فرسته",
|
||||
"copy_original_link_to_post": "کپی لینک اصلی این فرسته",
|
||||
"delete": "حذف",
|
||||
"delete_and_redraft": "حذف و بازنویسی",
|
||||
"direct_message_account": "پیام خصوصی {0}",
|
||||
"edit": "ویرایش",
|
||||
"hide_reblogs": "پنهان کردن بوستها از {0}",
|
||||
"mention_account": "ذکر {0}",
|
||||
"mute_account": "ساکت کردن {0}",
|
||||
"mute_conversation": "ساکت کردن این فرسته",
|
||||
"open_in_original_site": "باز کردن در سایت اصلی",
|
||||
"pin_on_profile": "به نمایه سنجاق کنید",
|
||||
"remove_personal_note": "یادداشت شخصی را از {0} حذف کنید",
|
||||
"report_account": "گزارش {0}",
|
||||
"share_account": "اشتراکگذاری {0}",
|
||||
"share_post": "اشتراکگذاری این فرسته",
|
||||
"show_favourited_and_boosted_by": "نمایش کسانی که پسند و به اشتراک گذاشتهاند",
|
||||
"show_reblogs": "نمایش به اشتراکگذاریها از {0}",
|
||||
"show_untranslated": "نمایش بدون ترجمه",
|
||||
"toggle_theme": {
|
||||
"dark": "حالت تاریک را تغییر دهید",
|
||||
"light": "حالت روشن را تغییر دهید"
|
||||
},
|
||||
"translate_post": "ترجمه فرسته",
|
||||
"unblock_account": "رفع انسداد {0}",
|
||||
"unblock_domain": "رفع انسداد دامنه {0}",
|
||||
"unfollow_account": "دنبال نکردن {0}",
|
||||
"unmute_account": "بیصدا نکردن {0}",
|
||||
"unmute_conversation": "بیصدا نکردن این فرسته",
|
||||
"unpin_on_profile": "از سنجاق خارج کردن در نمایه"
|
||||
},
|
||||
"modals": {
|
||||
"aria_label_close": "بستن"
|
||||
},
|
||||
"nav": {
|
||||
"back": "بازگشت",
|
||||
"blocked_domains": "دامنههای مسدود شده",
|
||||
"blocked_users": "کاربران مسدود شده",
|
||||
"bookmarks": "نشانها",
|
||||
"built_at": "ساخته شده {0}",
|
||||
"compose": "نوشتن",
|
||||
"conversations": "گفتگوها",
|
||||
"explore": "کاوش کنید",
|
||||
"favourites": "موارد مورد علاقه",
|
||||
"federated": "عمومی",
|
||||
"home": "خانه",
|
||||
"list": "لیست",
|
||||
"lists": "لیستها",
|
||||
"local": "محلی",
|
||||
"muted_users": "کاربران با صدای خاموش",
|
||||
"notifications": "اعلانات",
|
||||
"privacy": "حریم خصوصی",
|
||||
"profile": "نمایه",
|
||||
"search": "جستجو",
|
||||
"select_feature_flags": "تغییر ویژگیهای پرچم",
|
||||
"select_font_size": "اندازه قلم",
|
||||
"select_language": "زبان نمایش",
|
||||
"settings": "تنظیمات",
|
||||
"show_intro": "نمایش مقدمه",
|
||||
"toggle_theme": "تغییر تم",
|
||||
"zen_mode": "حالت آرام"
|
||||
},
|
||||
"notification": {
|
||||
"favourited_post": "فرسته شما را پسندید",
|
||||
"followed_you": "شما را دنبال کرد",
|
||||
"followed_you_count": "{0} نفر شما را دنبال کردند|{0} نفر شما را دنبال کرد|{0} نفر شما را دنبال کردند",
|
||||
"missing_type": "اعلان از نوع ناقص:",
|
||||
"reblogged_post": "فرسته شما را باز نشر کرد",
|
||||
"reported": "{0} گزارش داد {1}",
|
||||
"request_to_follow": "درخواست دنبال کردن شما را داد",
|
||||
"signed_up": "ثبت نام کرد",
|
||||
"update_status": "فرسته خود را بهروزرسانی کرد"
|
||||
},
|
||||
"placeholder": {
|
||||
"content_warning": "اخطار خود را اینجا بنویسید",
|
||||
"default_1": "چه فکری میکنید؟",
|
||||
"reply_to_account": "پاسخ به {0}",
|
||||
"replying": "در حال پاسخ دادن"
|
||||
},
|
||||
"polls": {
|
||||
"allow_multiple": "امکان انتخاب چندگزینهای",
|
||||
"cancel": "لغو",
|
||||
"create": "ایجاد نظرسنجی",
|
||||
"disallow_multiple": "عدم امکان انتخاب چندگزینهای",
|
||||
"expiration": "انقضای نظرسنجی",
|
||||
"hide_votes": "مخفی کردن تعداد آرا تا پایان",
|
||||
"option_placeholder": "گزینه نظرسنجی {current}/{max}",
|
||||
"remove_option": "حذف گزینه",
|
||||
"settings": "تنظیمات نظرسنجی",
|
||||
"show_votes": "نمایش دائمی تعداد آرا"
|
||||
},
|
||||
"pwa": {
|
||||
"dismiss": "رد کردن",
|
||||
"install": "نصب کردن",
|
||||
"install_title": "نصب Elk",
|
||||
"screenshots": {
|
||||
"dark": "تصویری از Elk در حالت تیره",
|
||||
"light": "تصویری از Elk در حالت روشن"
|
||||
},
|
||||
"title": "بهروزرسانی جدید Elk موجود است!",
|
||||
"update": "بهروزرسانی",
|
||||
"update_available_short": "بهروزرسانی Elk",
|
||||
"webmanifest": {
|
||||
"canary": {
|
||||
"description": "یک مشتری وب سبک و پیشرفته مستودون (canary)",
|
||||
"name": "Elk (پیشانتشار)",
|
||||
"short_name": "Elk (پیشانتشار)"
|
||||
},
|
||||
"dev": {
|
||||
"description": "یک کارخواه سبک و پیشرفته ماستودون (پیشنمایش)",
|
||||
"name": "Elk (dev)",
|
||||
"short_name": "Elk (dev)"
|
||||
},
|
||||
"preview": {
|
||||
"description": "یک کارخواه وب چابک برای ماستودون (پیش نمایش)",
|
||||
"name": "نرمافزار (پیش نمایش)",
|
||||
"short_name": "نرمافزار (پیش نمایش)"
|
||||
},
|
||||
"release": {
|
||||
"description": "یک کارخواه وب چابک برای ماستودون",
|
||||
"name": "نرمافزار",
|
||||
"short_name": "نرمافزار"
|
||||
}
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"additional_comments": "نظرات اضافی",
|
||||
"another_server": "کاربری که در حال گزارش آن هستید از سرور دیگری است",
|
||||
"anything_else": "آیا چیز دیگری هست که فکر میکنید ما باید بدانیم؟",
|
||||
"block_desc": "دیگر فرستههای این کاربر را مشاهده نخواهید کرد. آنها نمیتوانند فرستههای شما را ببینند و یا شما را دنبال کنند. آنها قادر خواهند بود درک کنند که مسدود شدهاند.",
|
||||
"dontlike": "من دوست ندارم",
|
||||
"dontlike_desc": "چیزی نیست که مایل به دیدن آن باشید",
|
||||
"forward": "بله، این گزارش را برای {0} ارسال کنید",
|
||||
"forward_question": "آیا میخواهید یک نسخه ناشناس از این گزارش را نیز برای آن سرور ارسال کنید؟",
|
||||
"further_actions": {
|
||||
"limit": {
|
||||
"description": "در اینجا گزینههای شما برای کنترل آنچه میبینید موجود است:",
|
||||
"title": "نمیخواهید این را ببینید؟"
|
||||
},
|
||||
"report": {
|
||||
"description": "در حالی که ما این موضوع را بررسی میکنیم، در اینجا اقداماتی هست که میتوانید انجام دهید:",
|
||||
"title": "از گزارش شما متشکریم، ما به آن رسیدگی خواهیم کرد."
|
||||
}
|
||||
},
|
||||
"limiting": "محدود کردن {0}",
|
||||
"mute_desc": "دیگر هیچ فرستهی از این کاربر را نخواهید دید. آنها همچنان میتوانند شما را دنبال کنند و فرستههای شما را ببینند. آنها متوجه بیصدا شدن نخواهند شد.",
|
||||
"other": "مورد دیگری است",
|
||||
"other_desc": "مشکلی که مطرح شده در دستهبندیهای دیگر نمیگنجد",
|
||||
"reporting": "گزارش {0}",
|
||||
"select_many": "تمام موارد مرتبط را انتخاب کنید:",
|
||||
"select_one": "بهترین تطابق را انتخاب کنید:",
|
||||
"select_posts": "آیا فرستههایی وجود دارد که این گزارش را پشتیبانی میکند؟",
|
||||
"select_posts_other": "آیا فرستههای دیگری وجود دارد که این گزارش را پشتیبانی میکند؟",
|
||||
"spam": "هرزنامه است",
|
||||
"spam_desc": "پیوندهای مخرب، تعاملات تقلبی، یا پاسخهای تکراری",
|
||||
"submit": "ارسال گزارش",
|
||||
"unfollow_desc": "دیگر فرستههای این کاربر را در فید خانگی خود نخواهید دید. ممکن است در جاهای دیگر فرستههای آنها را ببینید.",
|
||||
"violation": "یک یا چند قوانین سرور را نقض میکند",
|
||||
"whats_wrong_account": "بگویید چه مشکلی با این حساب کاربری وجود دارد",
|
||||
"whats_wrong_post": "بگویید چه مشکلی با این فرسته وجود دارد"
|
||||
},
|
||||
"search": {
|
||||
"search_desc": "جستجوی افراد و برچسبها",
|
||||
"search_empty": "هیچ موردی برای اصطلاحات جستجو شده پیدا نشد"
|
||||
},
|
||||
"settings": {
|
||||
"about": {
|
||||
"built_at": "ساخته شده در",
|
||||
"label": "درباره",
|
||||
"meet_the_team": "آشنایی با تیم",
|
||||
"sponsor_action": "حمایت از ما",
|
||||
"sponsor_action_desc": "برای حمایت از تیم توسعهدهنده Elk,",
|
||||
"sponsors": "حامیان",
|
||||
"sponsors_body_1": "Elk با حمایت سخاوتمندانه و کمک های:",
|
||||
"sponsors_body_2": "و تمام شرکتها و افرادی که از تیم Elk و اعضاء آن حمایت میکنند، امکانپذیر شده است.",
|
||||
"sponsors_body_3": "اگر از برنامه لذت میبرید، لطفا از ما حمایت کنید:",
|
||||
"version": "نسخه"
|
||||
},
|
||||
"account_settings": {
|
||||
"description": "تنظیمات حساب کاربری خود را در رابط کاربری ماستودون ویرایش کنید",
|
||||
"label": "تنظیمات حساب کاربری"
|
||||
},
|
||||
"interface": {
|
||||
"color_mode": "حالت رنگ",
|
||||
"dark_mode": "تاریک",
|
||||
"default": " (پیشفرض)",
|
||||
"font_size": "اندازه فونت",
|
||||
"label": "رابط کاربری",
|
||||
"light_mode": "روشن",
|
||||
"system_mode": "سیستم",
|
||||
"theme_color": "رنگ تم"
|
||||
},
|
||||
"language": {
|
||||
"display_language": "زبان نمایش",
|
||||
"label": "زبان",
|
||||
"post_language": "زبان ارسال",
|
||||
"status": "وضعیت ترجمه: {0}/{1} ({2}٪)",
|
||||
"translations": {
|
||||
"add": "اضافه کردن",
|
||||
"choose_language": "انتخاب زبان",
|
||||
"heading": "ترجمهها",
|
||||
"hide_specific": "پنهان کردن ترجمههای خاص",
|
||||
"remove": "حذف کردن"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"label": "اعلانها",
|
||||
"notifications": {
|
||||
"label": "تنظیمات اعلانها"
|
||||
},
|
||||
"push_notifications": {
|
||||
"alerts": {
|
||||
"favourite": "مورد علاقهها",
|
||||
"follow": "دنبالکنندگان جدید",
|
||||
"mention": "اشارهها",
|
||||
"poll": "نظرسنجیها",
|
||||
"reblog": "انتشارات",
|
||||
"title": "کدام اعلانها را دریافت کنیم؟"
|
||||
},
|
||||
"description": "اعلانها را حتی زمانی که از Elk استفاده نمیکنید دریافت کنید.",
|
||||
"instructions": "فراموش نکنید که تغییرات خود را با استفاده از دکمه ذخیره تنظیمات اعلانهای سریع بالا ذخیره کنید!",
|
||||
"label": "تنظیمات اعلانهای سریع",
|
||||
"policy": {
|
||||
"all": "از همه",
|
||||
"followed": "از افرادی که دنبال میکنم",
|
||||
"follower": "از افرادی که من را دنبال میکنند",
|
||||
"none": "از هیچکس",
|
||||
"title": "از چه کسی میتوانم اعلانها را دریافت کنم؟"
|
||||
},
|
||||
"save_settings": "ذخیره تنظیمات",
|
||||
"subscription_error": {
|
||||
"clear_error": "پاک کردن خطا",
|
||||
"error_hint": "میتوانید به لیست سوالات متداول مراجعه کنید تا مشکل را حل کنید: {0}.",
|
||||
"invalid_vapid_key": "کلید عمومی VAPID به نظر نامعتبر است.",
|
||||
"permission_denied": "اجازه دسترسی رد شد: اعلانها را در مرورگر خود فعال کنید.",
|
||||
"repo_link": "مخزن Elk در گیتهاب",
|
||||
"request_error": "هنگام درخواست اشتراکگذاری خطایی رخ داد، دوباره تلاش کنید و اگر مشکل ادامه یافت، لطفاً موضوع را در مخزن Elk گزارش دهید.",
|
||||
"title": "توانایی اشتراکگذاری در اعلانهای سریع وجود ندارد",
|
||||
"too_many_registrations": "به دلیل محدودیتهای مرورگر، Elk نمیتواند از سرویس اعلانهای سریع برای حسابهای متعدد در سرورهای مختلف استفاده کند. باید از اعلانهای سریع در یک حساب دیگر لغو اشتراک کنید و دوباره تلاش کنید.",
|
||||
"vapid_not_supported": "مرورگر شما از اعلانهای وب پشتیبانی میکند، اما به نظر میرسد پروتکل VAPID را پیادهسازی نمیکند."
|
||||
},
|
||||
"title": "تنظیمات اعلانهای سریع",
|
||||
"undo_settings": "لغو تغییرات",
|
||||
"unsubscribe": "غیرفعال کردن اعلانهای سریع",
|
||||
"unsupported": "مرورگر شما از اعلانهای سریع پشتیبانی نمیکند.",
|
||||
"warning": {
|
||||
"enable_close": "بستن",
|
||||
"enable_description": "برای دریافت اعلانها وقتی Elk باز نیست، اعلانهای سریع را فعال کنید. شما میتوانید دقیقاً کنترل کنید که چه نوع تعاملاتی اعلانهای سریع ایجاد کند از طریق دکمه فوق که بعد از فعالسازی نشان داده میشود.",
|
||||
"enable_description_desktop": "برای دریافت اعلانها وقتی Elk باز نیست، اعلانهای سریع را فعال کنید. شما میتوانید دقیقاً کنترل کنید که چه نوع تعاملاتی اعلانهای سریع ایجاد کند در تنظیمات > اعلانها > تنظیمات اعلانهای سریع پس از فعالسازی.",
|
||||
"enable_description_mobile": "همچنین میتوانید از منوی ناوبری به تنظیمات > اعلانها > تنظیمات اعلانهای سریع دسترسی پیدا کنید.",
|
||||
"enable_description_settings": "برای دریافت اعلانها وقتی Elk باز نیست، اعلانهای سریع را فعال کنید. شما خواهید توانست دقیقاً کنترل کنید که چه نوع تعاملاتی اعلانهای سریع ایجاد کند در همین صفحه پس از فعالسازی آنها.",
|
||||
"enable_desktop": "فعالسازی اعلانهای سریع",
|
||||
"enable_title": "هیچچیزی را از دست ندهید",
|
||||
"re_auth": "به نظر میرسد سرور شما از اعلانهای سریع پشتیبانی نمیکند. خروج و ورود مجدد را امتحان کنید، اگر این پیام همچنان ظاهر شد با مدیر سرور خود تماس بگیرید."
|
||||
}
|
||||
},
|
||||
"show_btn": "رفتن به تنظیمات اعلانها",
|
||||
"under_construction": "در دست ساخت"
|
||||
},
|
||||
"notifications_settings": "اعلانها",
|
||||
"preferences": {
|
||||
"embedded_media": "پخشکننده رسانه توکار",
|
||||
"embedded_media_description": "نمایش یک پخشکننده توکار به جای کارت پیشنمایش معمولی هنگام گسترش پیوندهای رسانهای به اشتراک گذاشته شده.",
|
||||
"enable_autoplay": "فعالسازی خودکارپخش",
|
||||
"enable_data_saving": "فعالسازی صرفهجویی در مصرف داده",
|
||||
"enable_data_saving_description": "صرفهجویی در دادهها با جلوگیری از بارگذاری خودکار پیوستها.",
|
||||
"enable_pinch_to_zoom": "فعالسازی بزرگنمایی با دو انگشت",
|
||||
"github_cards": "کارتهای گیتهاب",
|
||||
"github_cards_description": "هنگام ارسال پیوند گیتهاب، یک کارت HTML قابل دسترس با استفاده از نمایش شبکههای اجتماعی به نمایش درمیآید به جای تصویر اجتماعی.",
|
||||
"grayscale_mode": "حالت خاکستری",
|
||||
"hide_account_hover_card": "پنهان کردن کارت شناور حساب",
|
||||
"hide_alt_indi_on_posts": "پنهان کردن نشانگر جایگزین در فرستهها",
|
||||
"hide_boost_count": "پنهان کردن شمارش انتشارات",
|
||||
"hide_favorite_count": "پنهان کردن شمارش موردهای علاقهمندی",
|
||||
"hide_follower_count": "پنهان کردن شمارش دنبالکنندگان و دنبالشوندگان",
|
||||
"hide_news": "پنهان کردن خبرها",
|
||||
"hide_reply_count": "پنهان کردن شمارش پاسخها",
|
||||
"hide_translation": "پنهان کردن ترجمه",
|
||||
"hide_username_emojis": "پنهان کردن اموجیهای نام کاربری",
|
||||
"hide_username_emojis_description": "پنهان کردن اموجیها از نامهای کاربری در زمان خطوط. اموجیها هنوز در نمایههای آنها قابل مشاهده خواهند بود.",
|
||||
"label": "ترجیحات",
|
||||
"title": "ویژگیهای تجربی",
|
||||
"use_star_favorite_icon": "استفاده از نماد ستاره موردهای علاقهمندی",
|
||||
"user_picker": "انتخابکننده کاربر",
|
||||
"user_picker_description": "نمایش تمام آواتارهای حسابهای ورود شده در پایین-چپ تا شما بتوانید به سرعت بین آنها تعویض کنید.",
|
||||
"virtual_scroll": "پیمایش مجازی",
|
||||
"virtual_scroll_description": "استفاده از لیست مجازی در خطوط زمانی، برای نمایش تعداد بیشتری آیتم با عملکرد بهتر.",
|
||||
"wellbeing": "سلامتی",
|
||||
"zen_mode": "حالت آرامش",
|
||||
"zen_mode_description": "پنهان سازی حواشی مگر آنکه نشانگر موس روی آنها باشد. همچنین پنهانسازی بعضی المانها از خط زمانی."
|
||||
},
|
||||
"profile": {
|
||||
"appearance": {
|
||||
"bio": "بیوگرافی",
|
||||
"description": "ویرایش آواتار، نام کاربری، نمایه و غیره.",
|
||||
"display_name": "نام نمایشی",
|
||||
"label": "ظاهر",
|
||||
"profile_metadata": "توضیحات نمایه",
|
||||
"profile_metadata_desc": "شما میتوانید تا {0} مورد را به صورت جدول در نمایه خود نمایش دهید",
|
||||
"profile_metadata_label": "برچسب",
|
||||
"profile_metadata_value": "محتوا",
|
||||
"title": "ویرایش نمایه"
|
||||
},
|
||||
"featured_tags": {
|
||||
"description": "افراد میتوانند فرستههای عمومی شما را زیر این برچسبها مرور کنند.",
|
||||
"label": "برچسبهای برجسته",
|
||||
"under_construction": "در دست ساخت"
|
||||
},
|
||||
"label": "نمایه"
|
||||
},
|
||||
"select_a_settings": "یک تنظیم را انتخاب کنید",
|
||||
"users": {
|
||||
"export": "صادر کردن توکنهای کاربر",
|
||||
"import": "وارد کردن توکنهای کاربر",
|
||||
"label": "کاربران وارد شده"
|
||||
}
|
||||
},
|
||||
"share_target": {
|
||||
"description": "نرمافزار را میتوانید طوری پیکربندی کنید که از سایر برنامهها محتوا را به اشتراک بگذارید، تنها کافیست نرمافزار را روی دستگاه یا کامپیوتر خود نصب کرده و وارد شوید.",
|
||||
"hint": "برای به اشتراک گذاشتن محتوا با نرمافزار، باید نرمافزار نصب شده باشد و شما وارد حساب کاربری خود شده باشید.",
|
||||
"title": "به اشتراک گذاشتن با نرمافزار"
|
||||
},
|
||||
"state": {
|
||||
"attachments_exceed_server_limit": "تعداد پیوستها از حد مجاز هر فرسته تجاوز کرده است.",
|
||||
"attachments_limit_error": "حد مجاز فرسته تجاوز شده",
|
||||
"edited": "(ویرایش شده)",
|
||||
"editing": "در حال ویرایش",
|
||||
"loading": "در حال بارگذاری...",
|
||||
"publish_failed": "انتشار ناموفق بود",
|
||||
"publishing": "در حال انتشار",
|
||||
"save_failed": "ذخیره ناموفق بود",
|
||||
"upload_failed": "بارگذاری ناموفق بود",
|
||||
"uploading": "در حال بارگذاری..."
|
||||
},
|
||||
"status": {
|
||||
"account": {
|
||||
"suspended_message": "حساب کاربری این فرسته تعلیق شده است.",
|
||||
"suspended_show": "با این حال محتوا را نشان دهم؟"
|
||||
},
|
||||
"boosted_by": "تقویت شده توسط",
|
||||
"edited": "ویرایش شده {0}",
|
||||
"embedded_warning": "پخش این ممکن است آدرس IP شما را برای دیگران آشکار کند.",
|
||||
"favourited_by": "مورد علاقه توسط",
|
||||
"filter_hidden_phrase": "مخفی شده توسط فیلتر",
|
||||
"filter_show_anyway": "با این حال نشان بده",
|
||||
"img_alt": {
|
||||
"ALT": "ALT",
|
||||
"desc": "توصیف",
|
||||
"dismiss": "رد کردن",
|
||||
"read": "خواندن توصیف {0}"
|
||||
},
|
||||
"poll": {
|
||||
"count": "{0} رای|{0} رای|{0} رای",
|
||||
"ends": "پایان {0}",
|
||||
"finished": "به پایان رسید {0}"
|
||||
},
|
||||
"replying_to": "در پاسخ به {0}",
|
||||
"show_full_thread": "نمایش کامل رشته",
|
||||
"someone": "یک نفر",
|
||||
"spoiler_media_hidden": "رسانه مخفی شده",
|
||||
"spoiler_show_less": "نمایش کمتر",
|
||||
"spoiler_show_more": "نمایش بیشتر",
|
||||
"thread": "رشته",
|
||||
"try_original_site": "سایت اصلی را امتحان کنید"
|
||||
},
|
||||
"status_history": {
|
||||
"created": "ایجاد شده {0}",
|
||||
"edited": "ویرایش شده {0}"
|
||||
},
|
||||
"tab": {
|
||||
"accounts": "حسابها",
|
||||
"for_you": "برای شما",
|
||||
"hashtags": "برچسبها",
|
||||
"list": "لیست",
|
||||
"media": "رسانه",
|
||||
"news": "اخبار",
|
||||
"notifications_admin": {
|
||||
"report": "گزارش",
|
||||
"sign_up": "ثبت نام"
|
||||
},
|
||||
"notifications_all": "همه",
|
||||
"notifications_favourite": "مورد علاقه",
|
||||
"notifications_follow": "دنبال کردن",
|
||||
"notifications_follow_request": "درخواست دنبال کردن",
|
||||
"notifications_mention": "اشاره",
|
||||
"notifications_more_tooltip": "نوع اعلانها را بر اساس نوع فیلتر کنید",
|
||||
"notifications_poll": "نظرسنجی",
|
||||
"notifications_reblog": "بازنشر",
|
||||
"notifications_status": "وضعیت",
|
||||
"notifications_update": "بروزرسانی",
|
||||
"posts": "فرستهها",
|
||||
"posts_with_replies": "فرستهها و پاسخها"
|
||||
},
|
||||
"tag": {
|
||||
"follow": "دنبال کردن",
|
||||
"follow_label": "دنبال کردن برچسب {0}",
|
||||
"unfollow": "انصراف از دنبال کردن",
|
||||
"unfollow_label": "انصراف از دنبال کردن برچسب {0}"
|
||||
},
|
||||
"time_ago_options": {
|
||||
"day_future": "در 0 روز|فردا|در {n} روز",
|
||||
"day_past": "0 روز پیش|دیروز|{n} روز پیش",
|
||||
"hour_future": "در 0 ساعت|در 1 ساعت|در {n} ساعت",
|
||||
"hour_past": "0 ساعت پیش|1 ساعت پیش|{n} ساعت پیش",
|
||||
"just_now": "همین الان",
|
||||
"minute_future": "در 0 دقیقه|در 1 دقیقه|در {n} دقیقه",
|
||||
"minute_past": "0 دقیقه پیش|1 دقیقه پیش|{n} دقیقه پیش",
|
||||
"month_future": "در 0 ماه|ماه بعد|در {n} ماه",
|
||||
"month_past": "0 ماه پیش|ماه گذشته|{n} ماه پیش",
|
||||
"second_future": "همین الان|در {n} ثانیه|در {n} ثانیه",
|
||||
"second_past": "همین الان|{n} ثانیه پیش|{n} ثانیه پیش",
|
||||
"short_day_future": "در {n}روز",
|
||||
"short_day_past": "{n}روز",
|
||||
"short_hour_future": "در {n}س",
|
||||
"short_hour_past": "{n}س",
|
||||
"short_minute_future": "در {n}دقیقه",
|
||||
"short_minute_past": "{n}دقیقه",
|
||||
"short_month_future": "در {n}م",
|
||||
"short_month_past": "{n}م",
|
||||
"short_second_future": "در {n}ث",
|
||||
"short_second_past": "{n}ث",
|
||||
"short_week_future": "در {n}هفته",
|
||||
"short_week_past": "{n}هفته",
|
||||
"short_year_future": "در {n}سال",
|
||||
"short_year_past": "{n}سال",
|
||||
"week_future": "در 0 هفته|هفته بعد|در {n} هفته",
|
||||
"week_past": "0 هفته پیش|هفته گذشته|{n} هفته پیش",
|
||||
"year_future": "در 0 سال|سال بعد|در {n} سال",
|
||||
"year_past": "0 سال پیش|سال گذشته|{n} سال پیش"
|
||||
},
|
||||
"timeline": {
|
||||
"show_new_items": "نمایش {v} مورد جدید|نمایش {v} مورد جدید|نمایش {v} مورد جدید",
|
||||
"view_older_posts": "فرستههای قدیمیتر از دیگر موارد ممکن است نمایش داده نشوند."
|
||||
},
|
||||
"title": {
|
||||
"federated_timeline": "جریان اتحادی",
|
||||
"local_timeline": "جریان محلی"
|
||||
},
|
||||
"tooltip": {
|
||||
"add_content_warning": "اضافه کردن هشدار محتوا",
|
||||
"add_emojis": "اضافه کردن شکلکها",
|
||||
"add_media": "اضافه کردن تصاویر، ویدیو یا فایل صوتی",
|
||||
"add_publishable_content": "اضافه کردن محتوای قابل انتشار",
|
||||
"change_content_visibility": "تغییر دیدهبانی محتوا",
|
||||
"change_language": "تغییر زبان",
|
||||
"emoji": "شکلک",
|
||||
"explore_links_intro": "این داستانهای خبری، در حال حاضر توسط افراد در این و دیگر نمونههای شبکه غیرمتمرکز مورد بحث قرار میگیرد.",
|
||||
"explore_posts_intro": "این فرستهها از این و دیگر نمونههای در شبکه غیرمتمرکز هم اکنون در این سرور محبوبیت دارند.",
|
||||
"explore_tags_intro": "این برچسبها در حال حاضر میان افراد در این و دیگر سرورهای شبکه غیرمتمرکز مشهور شدهاند.",
|
||||
"open_editor_tools": "ابزار ویراستار",
|
||||
"pick_an_icon": "انتخاب یک آیکون",
|
||||
"publish_failed": "پیامهای ناموفق را در بالای ویرایشگر ببندید تا مجدد فرستهها را انتشار دهید",
|
||||
"toggle_bold": "تغییر به حالت توپر",
|
||||
"toggle_code_block": "تغییر به بلوک کد",
|
||||
"toggle_italic": "تغییر به حالت کج"
|
||||
},
|
||||
"user": {
|
||||
"add_existing": "اضافه کردن حساب کاربری موجود",
|
||||
"server_address_label": "آدرس سرور ماستودون",
|
||||
"sign_in_desc": "برای دنبال کردن نمایهها یا برچسبها، پسندیدن، به اشتراکگذاری و پاسخ دادن به فرستهها وارد شوید، یا از حساب کاربری خود در سرور دیگر ارتباط برقرار کنید.",
|
||||
"sign_in_notice_title": "مشاهده دادههای عمومی {0}",
|
||||
"sign_out_account": "خروج از حساب کاربری {0}",
|
||||
"single_instance_sign_in_desc": "برای دنبال کردن نمایهها یا برچسبها، پسندیدن، به اشتراکگذاری و پاسخ دادن به فرستهها وارد شوید.",
|
||||
"tip_no_account": "اگر هنوز حساب کاربری ماستودون ندارید، {0}.",
|
||||
"tip_register_account": "سرور خود را انتخاب کرده و یکی بسازید"
|
||||
},
|
||||
"visibility": {
|
||||
"direct": "مستقیم",
|
||||
"direct_desc": "فقط برای کاربران منشنشده قابل مشاهده",
|
||||
"private": "فقط دنبالکنندگان",
|
||||
"private_desc": "فقط برای دنبالکنندگان قابل مشاهده",
|
||||
"public": "عمومی",
|
||||
"public_desc": "برای همه قابل مشاهده",
|
||||
"unlisted": "فهرست نشده",
|
||||
"unlisted_desc": "برای همه قابل مشاهده اما از ویژگیهای کشف خارج شدهاست"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue