Merge branch 'develop' of https://codeberg.org/calckey/calckey into feature/edits

This commit is contained in:
Kaity A 2023-05-13 22:13:02 +10:00
commit f0a0a657b9
No known key found for this signature in database
GPG key ID: 5A797B97C2A490AD
135 changed files with 4674 additions and 2544 deletions

38
.config/devenv.yml Normal file
View file

@ -0,0 +1,38 @@
url: http://localhost:3000
port: 3000
db:
host: 127.0.0.1
port: 5432
db: calckey
user: calckey
pass: calckey
redis:
host: localhost
port: 6379
family: 4
#sonic:
# host: localhost
# port: 1491
# auth: SecretPassword
# collection: notes
# bucket: default
#elasticsearch:
# host: localhost
# port: 9200
# ssl: false
# user:
# pass:
id: 'aid'
reservedUsernames:
- root
- admin
- administrator
- me
- system

View file

@ -110,12 +110,13 @@ id: 'aid'
#maxCaptionLength: 1500 #maxCaptionLength: 1500
# Reserved usernames that only the administrator can register with # Reserved usernames that only the administrator can register with
reservedUsernames: reservedUsernames: [
- root 'root',
- admin 'admin',
- administrator 'administrator',
- me 'me',
- system 'system'
]
# Whether disable HSTS # Whether disable HSTS
#disableHsts: true #disableHsts: true

View file

@ -0,0 +1,82 @@
replicaCount: 1
resources:
requests:
cpu: 0.5
memory: 512Mi
limits:
cpu: 1
memory: 1Gi
calckey:
domain: example.tld
smtp:
from_address: noreply@example.tld
port: 587
server: smtp.gmail.com
useImplicitSslTls: false
login: me@example.tld
password: CHANGEME
objectStorage:
baseUrl: https://example-bucket.nyc3.cdn.digitaloceanspaces.com
access_key: CHANGEME
access_secret: CHANGEME
bucket: example-bucket
endpoint: nyc3.digitaloceanspaces.com:443
region: nyc3
allowedPrivateNetworks: []
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt
hosts:
- host: example.tld
paths:
- path: /
pathType: ImplementationSpecific
tls:
- secretName: example-tld-certificate
hosts:
- example.tld
elasticsearch:
enabled: false
postgresql:
auth:
password: CHANGEME
postgresPassword: CHANGEME
primary:
persistence:
enabled: true
storageClass: vultr-block-storage
size: 25Gi
resources:
requests:
cpu: 0.25
memory: 256Mi
limits:
cpu: 0.5
memory: 512Mi
metrics:
enabled: true
redis:
auth:
password: CHANGEME
master:
resources:
requests:
cpu: 0.25
memory: 256Mi
limits:
cpu: 0.5
memory: 256Mi
persistence:
storageclass: vultr-block-storage
size: 10Gi
replica:
replicaCount: 0
metrics:
enabled: true

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
fi
use flake . --impure

6
.gitignore vendored
View file

@ -22,7 +22,9 @@ coverage
# config # config
/.config/* /.config/*
!/.config/example.yml !/.config/example.yml
!/.config/devenv.yml
!/.config/docker_example.env !/.config/docker_example.env
!/.config/helm_values_example.yml
#docker dev config #docker dev config
/dev/docker-compose.yml /dev/docker-compose.yml
@ -56,3 +58,7 @@ packages/backend/assets/sounds/None.mp3
# old yarn # old yarn
.yarn .yarn
yarn* yarn*
# Nix Development shell items
.devenv
.direnv

View file

@ -11,5 +11,4 @@ pipeline:
password: password:
# Secret 'docker_password' needs to be set in the CI settings # Secret 'docker_password' needs to be set in the CI settings
from_secret: docker_password from_secret: docker_password
branches: beta branches: beta

View file

@ -16,4 +16,3 @@ pipeline:
# Push new version when version tag is created # Push new version when version tag is created
event: tag event: tag
tag: v* tag: v*

View file

@ -19,7 +19,6 @@
- MFM button - MFM button
- Personal notes for all accounts - Personal notes for all accounts
- Fully revamp non-logged-in screen - Fully revamp non-logged-in screen
- Classic mode make instance icon bring up new context menu
- Lookup/details for post/file/instance - Lookup/details for post/file/instance
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd) - [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
@ -118,6 +117,13 @@
- Sonic search - Sonic search
- Popular color schemes, including Nord, Gruvbox, and Catppuccin - Popular color schemes, including Nord, Gruvbox, and Catppuccin
- Non-nyaify cat mode - Non-nyaify cat mode
- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma instances
- Improve Classic mode
- Proper Helm/Kubernetes config
- Multiple boost visibilities
- Improve system emails
- Mod mail
- Focus trapping and button labels
## Implemented (remote) ## Implemented (remote)

View file

@ -62,7 +62,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at reported to the community leaders responsible for enforcement at
@thatonecalculator on Codeberg, @thatonecalculator on Codeberg,
`@thatonecalculator@stop.voring.me` or `@t1c@i.calckey.cloud` on the Fediverse, `@kainoa@calckey.social` on the Fediverse,
or kainoa@t1c.dev via email. or kainoa@t1c.dev via email.
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.

View file

@ -1,8 +1,8 @@
# Contribution guide # Contribution guide
We're glad you're interested in contributing Calckey! In this document you will find the information you need to contribute to the project. We're glad you're interested in contributing Calckey! In this document you will find the information you need to contribute to the project.
## Localization (l10n) ## Translation (i18n)
Calckey uses [Weblate](hhttps://hosted.weblate.org/engage/calckey/) for localization management. Calckey uses [Weblate](hhttps://hosted.weblate.org/engage/calckey/) for translation and internationalization management.
If your language is not listed in Weblate, please open an issue. If your language is not listed in Weblate, please open an issue.

View file

@ -35,7 +35,7 @@ FROM node:19-alpine
WORKDIR /calckey WORKDIR /calckey
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache --no-progress tini ffmpeg vips-dev RUN apk add --no-cache --no-progress tini ffmpeg vips-dev zip unzip
COPY . ./ COPY . ./

View file

@ -7,6 +7,7 @@
[![no github badge](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page/) [![no github badge](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page/)
[![status badge](https://ci.codeberg.org/api/badges/calckey/calckey/status.svg)](https://ci.codeberg.org/calckey/calckey) [![status badge](https://ci.codeberg.org/api/badges/calckey/calckey/status.svg)](https://ci.codeberg.org/calckey/calckey)
[![opencollective badge](https://opencollective.com/calckey/tiers/badge.svg)](https://opencollective.com/Calckey)
[![liberapay badge](https://img.shields.io/liberapay/receives/ThatOneCalculator?logo=liberapay)](https://liberapay.com/ThatOneCalculator) [![liberapay badge](https://img.shields.io/liberapay/receives/ThatOneCalculator?logo=liberapay)](https://liberapay.com/ThatOneCalculator)
[![translate-badge](https://hosted.weblate.org/widgets/calckey/-/svg-badge.svg)](https://hosted.weblate.org/engage/calckey/) [![translate-badge](https://hosted.weblate.org/widgets/calckey/-/svg-badge.svg)](https://hosted.weblate.org/engage/calckey/)
[![docker badge](https://img.shields.io/docker/pulls/thatonecalculator/calckey?logo=docker)](https://hub.docker.com/r/thatonecalculator/calckey) [![docker badge](https://img.shields.io/docker/pulls/thatonecalculator/calckey?logo=docker)](https://hub.docker.com/r/thatonecalculator/calckey)
@ -46,6 +47,7 @@
# 🥂 Links # 🥂 Links
- 💸 OpenCollective: <https://opencollective.com/Calckey>
- 💸 Liberapay: <https://liberapay.com/ThatOneCalculator> - 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
- Donate publicly to get your name on the Patron list! - Donate publicly to get your name on the Patron list!
- 🚢 Flagship instance: <https://calckey.social> - 🚢 Flagship instance: <https://calckey.social>
@ -67,9 +69,10 @@ If you have access to a server that supports one of the sources below, I recomme
[![Install on Ubuntu](https://pool.jortage.com/voringme/misskey/3b62a443-1b44-45cf-8f9e-f1c588f803ed.png)](https://codeberg.org/calckey/ubuntu-bash-install)  [![Install on the Arch User Repository](https://pool.jortage.com/voringme/misskey/ba2a5c07-f078-43f1-8483-2e01acca9c40.png)](https://aur.archlinux.org/packages/calckey)  [![Install Calckey with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=calckey) [![Install on Ubuntu](https://pool.jortage.com/voringme/misskey/3b62a443-1b44-45cf-8f9e-f1c588f803ed.png)](https://codeberg.org/calckey/ubuntu-bash-install)  [![Install on the Arch User Repository](https://pool.jortage.com/voringme/misskey/ba2a5c07-f078-43f1-8483-2e01acca9c40.png)](https://aur.archlinux.org/packages/calckey)  [![Install Calckey with YunoHost](https://install-app.yunohost.org/install-with-yunohost.svg)](https://install-app.yunohost.org/?app=calckey)
### 🐋 Docker ## 🛳️ Containerization
[How to run Calckey with Docker](./docs/docker.md). - [🐳 How to run Calckey with Docker](https://codeberg.org/calckey/calckey/src/branch/develop/docs/docker.md)
- [🛞 How to run Calckey with Kubernetes/Helm](https://codeberg.org/calckey/calckey/src/branch/develop/docs/kubernetes.md)
## 🧑‍💻 Dependencies ## 🧑‍💻 Dependencies
@ -77,17 +80,17 @@ If you have access to a server that supports one of the sources below, I recomme
- Install with [nvm](https://github.com/nvm-sh/nvm) - Install with [nvm](https://github.com/nvm-sh/nvm)
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 - 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend) - 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)
- Web Proxy (one of the following)
- 🍀 Nginx (recommended)
- 🪶 Apache
- 🦦 Caddy
### 😗 Optional dependencies ### 😗 Optional dependencies
- [FFmpeg](https://ffmpeg.org/) for video transcoding - [FFmpeg](https://ffmpeg.org/) for video transcoding
- Full text search (choost one of the following) - Full text search (one of the following)
- 🦔 [Sonic](https://crates.io/crates/sonic-server) (highly recommended!) - 🦔 [Sonic](https://crates.io/crates/sonic-server) (recommended)
- [ElasticSearch](https://www.elastic.co/elasticsearch/) - [ElasticSearch](https://www.elastic.co/elasticsearch/)
- Management (choose one of the following)
- 🛰️ [pm2](https://pm2.io/)
- 🐳 [Docker](https://docker.com)
- Service manager (systemd, openrc, etc)
### 🏗️ Build dependencies ### 🏗️ Build dependencies
@ -116,6 +119,17 @@ corepack prepare pnpm@latest --activate
pnpm i # --no-optional pnpm i # --no-optional
``` ```
### pm2
To install pm2 run:
```
npm i -g pm2
pm2 install pm2-logrotate
```
[`pm2-logrotate`](https://github.com/keymetrics/pm2-logrotate/blob/master/README.md) ensures that log files don't infinitely gather size, as Calckey produces a lot of logs.
## 🐘 Create database ## 🐘 Create database
Assuming you set up PostgreSQL correctly, all you have to run is: Assuming you set up PostgreSQL correctly, all you have to run is:
@ -152,16 +166,33 @@ In Calckey's directory, fill out the `sonic` section of `.config/default.yml` wi
## 🚚 Migrating from Misskey to Calckey ## 🚚 Migrating from Misskey to Calckey
For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](./docs/migrate.md). For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md).
## 🍀 NGINX ## 🌐 Web proxy
### 🍀 Nginx (recommended)
- Run `sudo cp ./calckey.nginx.conf /etc/nginx/sites-available/ && cd /etc/nginx/sites-available/` - Run `sudo cp ./calckey.nginx.conf /etc/nginx/sites-available/ && cd /etc/nginx/sites-available/`
- Edit `calckey.nginx.conf` to reflect your instance properly - Edit `calckey.nginx.conf` to reflect your instance properly
- Run `sudo cp ./calckey.nginx.conf ../sites-enabled/` - Run `sudo ln -s ./calckey.nginx.conf ../sites-enabled/calckey.nginx.conf`
- Run `sudo nginx -t` to validate that the config is valid, then restart the NGINX service. - Run `sudo nginx -t` to validate that the config is valid, then restart the NGINX service.
</details> ### 🪶 Apache
- Run `sudo cp ./calckey.apache.conf /etc/apache2/sites-available/ && cd /etc/apache2/sites-available/`
- Edit `calckey.apache.conf` to reflect your instance properly
- Run `sudo a2ensite calckey.apache` to enable the site
- Run `sudo service apache2 restart` to reload apache2 configuration
### 🦦 Caddy
- Add the following block to your `Caddyfile`, replacing `example.tld` with your own domain:
```caddy
example.tld {
reverse_proxy http://127.0.0.1:3000
}
```
- Reload your caddy configuration
## 🚀 Build and launch! ## 🚀 Build and launch!
@ -180,7 +211,7 @@ pm2 start "NODE_ENV=production pnpm run start" --name Calckey
- When editing the config file, please don't fill out the settings at the bottom. They're designed *only* for managed hosting, not self hosting. Those settings are much better off being set in Calckey's control panel. - When editing the config file, please don't fill out the settings at the bottom. They're designed *only* for managed hosting, not self hosting. Those settings are much better off being set in Calckey's control panel.
- Port 3000 (used in the default config) might be already used on your server for something else. To find an open port for Calckey, run `for p in {3000..4000}; do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1`. Replace 3000 with the minimum port and 4000 with the maximum port if you need it. - Port 3000 (used in the default config) might be already used on your server for something else. To find an open port for Calckey, run `for p in {3000..4000}; do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1`. Replace 3000 with the minimum port and 4000 with the maximum port if you need it.
- I'd recommend you use a S3 Bucket/CDN for Object Storage, especially if you use Docker. - I'd recommend you use a S3 Bucket/CDN for Object Storage, especially if you use Docker.
- I'd ***strongly*** recommend against using CloudFlare, but if you do, make sure to turn code minification off. - I'd ***strongly*** recommend against using CloudFlare, but if you do, make sure to turn code minification off.
- For push notifications, run `npx web-push generate-vapid-keys`, then put the public and private keys into Control Panel > General > ServiceWorker. - For push notifications, run `npx web-push generate-vapid-keys`, then put the public and private keys into Control Panel > General > ServiceWorker.
- For translations, make a [DeepL](https://deepl.com) account and generate an API key, then put it into Control Panel > General > DeepL Translation. - For translations, make a [DeepL](https://deepl.com) account and generate an API key, then put it into Control Panel > General > DeepL Translation.

13
calckey.apache.conf Normal file
View file

@ -0,0 +1,13 @@
# Replace example.tld with your domain
<VirtualHost *:80>
ServerName example.tld
# For WebSocket
ProxyPass "/streaming" "ws://127.0.0.1:3000/streaming/"
# Proxy to Node
ProxyPass "/" "http://127.0.0.1:3000/"
ProxyPassReverse "/" "http://127.0.0.1:3000/"
ProxyPreserveHost On
# For files proxy
AllowEncodedSlashes On
</VirtualHost>

23
chart/.helmignore Normal file
View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -1,3 +1,38 @@
apiVersion: v2 apiVersion: v2
name: misskey name: calckey
version: 0.0.0 description: A fun, new, open way to experience social media https://calckey.org
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "rc"
dependencies:
- name: elasticsearch
version: 19.0.1
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
condition: elasticsearch.enabled
- name: postgresql
version: 11.1.3
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
condition: postgresql.enabled
- name: redis
version: 16.13.2
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
condition: redis.enabled

83
chart/README.md Normal file
View file

@ -0,0 +1,83 @@
# calckey
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: rc](https://img.shields.io/badge/AppVersion-rc-informational?style=flat-square)
A fun, new, open way to experience social media https://calckey.org
## Requirements
| Repository | Name | Version |
|------------|------|---------|
| https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami | elasticsearch | 19.0.1 |
| https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami | postgresql | 11.1.3 |
| https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami | redis | 16.13.2 |
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| autoscaling.enabled | bool | `false` | |
| autoscaling.maxReplicas | int | `100` | |
| autoscaling.minReplicas | int | `1` | |
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| calckey.allowedPrivateNetworks | list | `[]` | If you want to allow calckey to connect to private ips, enter the cidrs here. |
| calckey.domain | string | `"calckey.local"` | |
| calckey.isManagedHosting | bool | `true` | |
| calckey.objectStorage.access_key | string | `""` | |
| calckey.objectStorage.access_secret | string | `""` | |
| calckey.objectStorage.baseUrl | string | `""` | |
| calckey.objectStorage.bucket | string | `""` | |
| calckey.objectStorage.endpoint | string | `""` | |
| calckey.objectStorage.managed | bool | `true` | |
| calckey.objectStorage.prefix | string | `"files"` | |
| calckey.objectStorage.region | string | `""` | |
| calckey.reservedUsernames[0] | string | `"root"` | |
| calckey.reservedUsernames[1] | string | `"admin"` | |
| calckey.reservedUsernames[2] | string | `"administrator"` | |
| calckey.reservedUsernames[3] | string | `"me"` | |
| calckey.reservedUsernames[4] | string | `"system"` | |
| calckey.smtp.from_address | string | `"notifications@example.com"` | |
| calckey.smtp.login | string | `""` | |
| calckey.smtp.managed | bool | `true` | |
| calckey.smtp.password | string | `""` | |
| calckey.smtp.port | int | `587` | |
| calckey.smtp.server | string | `"smtp.mailgun.org"` | |
| calckey.smtp.useImplicitSslTls | bool | `false` | |
| elasticsearch | object | `{"auth":null,"enabled":false,"hostname":"","port":9200,"ssl":false}` | https://github.com/bitnami/charts/tree/master/bitnami/elasticsearch#parameters |
| fullnameOverride | string | `""` | |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"docker.io/thatonecalculator/calckey"` | |
| image.tag | string | `""` | |
| imagePullSecrets | list | `[]` | |
| ingress.annotations | object | `{}` | |
| ingress.className | string | `""` | |
| ingress.enabled | bool | `false` | |
| ingress.hosts[0].host | string | `"chart-example.local"` | |
| ingress.hosts[0].paths[0].path | string | `"/"` | |
| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | |
| ingress.tls | list | `[]` | |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | |
| podAnnotations | object | `{}` | |
| podSecurityContext | object | `{}` | |
| postgresql.auth.database | string | `"calckey_production"` | |
| postgresql.auth.password | string | `""` | |
| postgresql.auth.username | string | `"calckey"` | |
| postgresql.enabled | bool | `true` | disable if you want to use an existing db; in which case the values below must match those of that external postgres instance |
| redis.auth.password | string | `""` | you must set a password; the password generated by the redis chart will be rotated on each upgrade: |
| redis.enabled | bool | `true` | |
| redis.hostname | string | `""` | |
| redis.port | int | `6379` | |
| replicaCount | int | `1` | |
| resources | object | `{}` | |
| securityContext | object | `{}` | |
| service.port | int | `80` | |
| service.type | string | `"ClusterIP"` | |
| serviceAccount.annotations | object | `{}` | |
| serviceAccount.create | bool | `true` | |
| serviceAccount.name | string | `""` | |
| tolerations | list | `[]` | |
----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0)

View file

@ -1,162 +0,0 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
# url: https://example.tld/
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey supports two deployment options for public.
#
# Option 1: With Reverse Proxy
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to setup reverse proxy. (eg. nginx)
# You do not define 'https' section.
# Option 2: Standalone
#
# +- https://example.tld/ -+
# +------+ | +---------------+ |
# | User | ---> | | Misskey (443) | |
# +------+ | +---------------+ |
# +------------------------+
#
# You need to run Misskey as root.
# You need to set Certificate in 'https' section.
# To use option 1, uncomment below line.
port: 3000 # A port that your Misskey server should listen.
# To use option 2, uncomment below lines.
#port: 443
#https:
# # path for certification
# key: /etc/letsencrypt/live/example.tld/privkey.pem
# cert: /etc/letsencrypt/live/example.tld/fullchain.pem
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: localhost
port: 5432
# Database name
db: misskey
# Auth
user: example-misskey-user
pass: example-misskey-pass
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: localhost
port: 6379
#pass: example-pass
#prefix: example-prefix
#db: 1
# ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └─────────────────────────────
#elasticsearch:
# host: localhost
# port: 9200
# ssl: false
# user:
# pass:
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: "aid"
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 16
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Syslog option
#syslog:
# host: localhost
# port: 514
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View file

@ -1,8 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "misskey.fullname" . }}-configuration
data:
default.yml: |-
{{ .Files.Get "files/default.yml"|nindent 4 }}
url: {{ .Values.url }}

View file

@ -1,47 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "misskey.fullname" . }}
labels:
{{- include "misskey.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "misskey.selectorLabels" . | nindent 6 }}
replicas: 1
template:
metadata:
labels:
{{- include "misskey.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: misskey
image: {{ .Values.image }}
env:
- name: NODE_ENV
value: {{ .Values.environment }}
volumeMounts:
- name: {{ include "misskey.fullname" . }}-configuration
mountPath: /misskey/.config
readOnly: true
ports:
- containerPort: 3000
- name: postgres
image: postgres:14-alpine
env:
- name: POSTGRES_USER
value: "example-misskey-user"
- name: POSTGRES_PASSWORD
value: "example-misskey-pass"
- name: POSTGRES_DB
value: "misskey"
ports:
- containerPort: 5432
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
volumes:
- name: {{ include "misskey.fullname" . }}-configuration
configMap:
name: {{ include "misskey.fullname" . }}-configuration

22
chart/templates/NOTES.txt Normal file
View file

@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "calckey.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "calckey.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "calckey.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "calckey.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View file

@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "misskey.fullname" . }}
annotations:
dev.okteto.com/auto-ingress: "true"
spec:
type: ClusterIP
ports:
- port: 3000
protocol: TCP
name: http
selector:
{{- include "misskey.selectorLabels" . | nindent 4 }}

View file

@ -1,7 +1,7 @@
{{/* {{/*
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "misskey.name" -}} {{- define "calckey.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
@ -10,7 +10,7 @@ Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name. If release name contains chart name it will be used as a full name.
*/}} */}}
{{- define "misskey.fullname" -}} {{- define "calckey.fullname" -}}
{{- if .Values.fullnameOverride }} {{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }} {{- else }}
@ -26,16 +26,16 @@ If release name contains chart name it will be used as a full name.
{{/* {{/*
Create chart name and version as used by the chart label. Create chart name and version as used by the chart label.
*/}} */}}
{{- define "misskey.chart" -}} {{- define "calckey.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
{{/* {{/*
Common labels Common labels
*/}} */}}
{{- define "misskey.labels" -}} {{- define "calckey.labels" -}}
helm.sh/chart: {{ include "misskey.chart" . }} helm.sh/chart: {{ include "calckey.chart" . }}
{{ include "misskey.selectorLabels" . }} {{ include "calckey.selectorLabels" . }}
{{- if .Chart.AppVersion }} {{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }} {{- end }}
@ -45,18 +45,274 @@ app.kubernetes.io/managed-by: {{ .Release.Service }}
{{/* {{/*
Selector labels Selector labels
*/}} */}}
{{- define "misskey.selectorLabels" -}} {{- define "calckey.selectorLabels" -}}
app.kubernetes.io/name: {{ include "misskey.name" . }} app.kubernetes.io/name: {{ include "calckey.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} {{- end }}
{{/* {{/*
Create the name of the service account to use Create the name of the service account to use
*/}} */}}
{{- define "misskey.serviceAccountName" -}} {{- define "calckey.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }} {{- if .Values.serviceAccount.create }}
{{- default (include "misskey.fullname" .) .Values.serviceAccount.name }} {{- default (include "calckey.fullname" .) .Values.serviceAccount.name }}
{{- else }} {{- else }}
{{- default "default" .Values.serviceAccount.name }} {{- default "default" .Values.serviceAccount.name }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{/*
Create a default fully qualified name for dependent services.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "calckey.elasticsearch.fullname" -}}
{{- printf "%s-%s" .Release.Name "elasticsearch" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "calckey.redis.fullname" -}}
{{- printf "%s-%s" .Release.Name "redis" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "calckey.postgresql.fullname" -}}
{{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
config/default.yml content
*/}}
{{- define "calckey.configDir.default.yml" -}}
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Calckey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: "https://{{ .Values.calckey.domain }}/"
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 3000
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
{{- if .Values.postgresql.enabled }}
host: {{ template "calckey.postgresql.fullname" . }}
port: '5432'
{{- else }}
host: {{ .Values.postgresql.postgresqlHostname }}
port: {{ .Values.postgresql.postgresqlPort | default "5432" | quote }}
{{- end }}
# Database name
db: {{ .Values.postgresql.auth.database }}
# Auth
user: {{ .Values.postgresql.auth.username }}
pass: "{{ .Values.postgresql.auth.password }}"
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
{{- if .Values.redis.enabled }}
host: {{ template "calckey.redis.fullname" . }}-master
{{- else }}
host: {{ required "When the redis chart is disabled .Values.redis.hostname is required" .Values.redis.hostname }}
{{- end }}
port: {{ .Values.redis.port | default "6379" | quote }}
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
pass: {{ .Values.redis.auth.password | quote }}
#prefix: example-prefix
#db: 1
# ┌─────────────────────┐
#───┘ Sonic configuration └─────────────────────────────────────
#sonic:
# host: localhost
# port: 1491
# auth: SecretPassword
# collection: notes
# bucket: default
# ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └─────────────────────────────
{{- if .Values.elasticsearch.enabled }}
elasticsearch:
host: {{ template "mastodon.elasticsearch.fullname" . }}-master-hl
port: 9200
ssl: false
{{- else if .Values.elasticsearch.hostname }}
elasticsearch:
host: {{ .Values.elasticsearch.hostname | quote }}
port: {{ .Values.elasticsearch.port }}
ssl: {{ .Values.elasticsearch.ssl }}
{{- if .Values.elasticsearch.auth }}
user: {{ .Values.elasticsearch.auth.username | quote }}
pass: {{ .Values.elasticsearch.auth.password | quote }}
{{- end }}
{{- end }}
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Max note length, should be < 8000.
#maxNoteLength: 3000
# Maximum lenght of an image caption or file comment (default 1500, max 8192)
#maxCaptionLength: 1500
# Reserved usernames that only the administrator can register with
reservedUsernames:
{{ .Values.calckey.reservedUsernames | toYaml }}
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 16
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Syslog option
#syslog:
# host: localhost
# port: 514
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
# Proxy remote files (default: false)
#proxyRemoteFiles: true
allowedPrivateNetworks:
{{ .Values.calckey.allowedPrivateNetworks | toYaml }}
# TWA
#twa:
# nameSpace: android_app
# packageName: tld.domain.twa
# sha256CertFingerprints: ['AB:CD:EF']
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
# Managed hosting settings
# !!!!!!!!!!
# >>>>>> NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
# >>>>>> YOU DON'T NEED THIS! <<<<<<
# !!!!!!!!!!
# Each category is optional, but if each item in each category is mandatory!
# If you mess this up, that's on you, you've been warned...
#maxUserSignups: 100
isManagedHosting: {{ .Values.calckey.isManagedHosting }}
deepl:
managed: false
# authKey: ''
# isPro: false
#
email:
managed: {{ .Values.calckey.smtp.managed }}
address: {{ .Values.calckey.smtp.from_address | quote }}
host: {{ .Values.calckey.smtp.server | quote }}
port: {{ .Values.calckey.smtp.port }}
user: {{ .Values.calckey.smtp.login | quote }}
pass: {{ .Values.calckey.smtp.password | quote }}
useImplicitSslTls: {{ .Values.calckey.smtp.useImplicitSslTls }}
objectStorage:
managed: {{ .Values.calckey.objectStorage.managed }}
baseUrl: {{ .Values.calckey.objectStorage.baseUrl | quote }}
bucket: {{ .Values.calckey.objectStorage.bucket | quote }}
prefix: {{ .Values.calckey.objectStorage.prefix | quote }}
endpoint: {{ .Values.calckey.objectStorage.endpoint | quote }}
region: {{ .Values.calckey.objectStorage.region | quote }}
accessKey: {{ .Values.calckey.objectStorage.access_key | quote }}
secretKey: {{ .Values.calckey.objectStorage.access_secret | quote }}
useSsl: true
connnectOverProxy: false
setPublicReadOnUpload: true
s3ForcePathStyle: true
# !!!!!!!!!!
# >>>>>> AGAIN, NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
# >>>>>> YOU DON'T NEED THIS, ABOVE SETTINGS ARE FOR MANAGED HOSTING ONLY! <<<<<<
# !!!!!!!!!!
# Seriously. Do NOT fill out the above settings if you're self-hosting.
# They're much better off being set from the control panel.
{{- end }}

View file

@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "calckey.fullname" . }}
labels:
{{- include "calckey.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "calckey.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/secret-config: {{ include ( print $.Template.BasePath "/secret-config.yaml" ) . | sha256sum | quote }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "calckey.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "calckey.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
volumes:
- name: config-volume
secret:
secretName: {{ template "calckey.fullname" . }}-config
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: "NODE_ENV"
value: "production"
volumeMounts:
- name: config-volume
mountPath: /calckey/.config
ports:
- name: http
containerPort: 3000
protocol: TCP
startupProbe:
httpGet:
path: /
port: http
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

28
chart/templates/hpa.yaml Normal file
View file

@ -0,0 +1,28 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "calckey.fullname" . }}
labels:
{{- include "calckey.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "calckey.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,61 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "calckey.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "calckey.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ template "calckey.fullname" . }}-config
labels:
{{- include "calckey.labels" . | nindent 4 }}
type: Opaque
data:
default.yml: {{ include "calckey.configDir.default.yml" . | b64enc }}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "calckey.fullname" . }}
labels:
{{- include "calckey.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "calckey.selectorLabels" . | nindent 4 }}

View file

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "calckey.serviceAccountName" . }}
labels:
{{- include "calckey.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "calckey.fullname" . }}-test-connection"
labels:
{{- include "calckey.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "calckey.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

158
chart/values.yaml Normal file
View file

@ -0,0 +1,158 @@
# Default values for calckey.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: docker.io/thatonecalculator/calckey
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
calckey:
isManagedHosting: true
domain: calckey.local
smtp:
managed: true
from_address: notifications@example.com
port: 587
server: smtp.mailgun.org
useImplicitSslTls: false
login: ""
password: ""
objectStorage:
managed: true
access_key: ""
access_secret: ""
baseUrl: "" # e.g. "https://my-bucket.nyc3.cdn.digitaloceanspaces.com"
bucket: "" # e.g. "my-bucket"
prefix: files
endpoint: "" # e.g. "nyc3.digitaloceanspaces.com:443"
region: "" # e.g. "nyc3"
# -- If you want to allow calckey to connect to private ips, enter the cidrs here.
allowedPrivateNetworks: []
# - "10.0.0.0/8"
reservedUsernames:
- root
- admin
- administrator
- me
- system
# https://github.com/bitnami/charts/tree/master/bitnami/postgresql#parameters
postgresql:
# -- disable if you want to use an existing db; in which case the values below
# must match those of that external postgres instance
enabled: true
# postgresqlHostname: preexisting-postgresql
# postgresqlPort: 5432
auth:
database: calckey_production
username: calckey
# you must set a password; the password generated by the postgresql chart will
# be rotated on each upgrade:
# https://github.com/bitnami/charts/tree/master/bitnami/postgresql#upgrade
password: ""
# https://github.com/bitnami/charts/tree/master/bitnami/redis#parameters
redis:
# disable if you want to use an existing redis instance; in which case the
# values below must match those of that external redis instance
enabled: true
hostname: ""
port: 6379
auth:
# -- you must set a password; the password generated by the redis chart will be
# rotated on each upgrade:
password: ""
# -- https://github.com/bitnami/charts/tree/master/bitnami/elasticsearch#parameters
elasticsearch:
# disable if you want to use an existing redis instance; in which case the
# values below must match those of that external elasticsearch instance
enabled: false
hostname: ""
port: 9200
ssl: false
auth: {}
# username: ""
# password: ""
# @ignored
image:
tag: 7
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}

View file

@ -1,3 +0,0 @@
url: https://example.tld/
image: okteto.dev/misskey
environment: production

View file

@ -2,7 +2,6 @@ describe('After user signed in', () => {
beforeEach(() => { beforeEach(() => {
cy.resetState(); cy.resetState();
cy.viewport('macbook-16'); cy.viewport('macbook-16');
// インスタンス初期セットアップ // インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true); cy.registerUser('admin', 'pass', true);

22
docs/development.md Normal file
View file

@ -0,0 +1,22 @@
# 🌎 Calckey Developer Docs
## Nix Dev Environment
The Calckey repo comes with a Nix-based shell environment to help make development as easy as possible!
Please note, however, that this environment will not work on Windows outside of a WSL2 environment.
### Prerequisites
- Installed the [Nix Package Manager](https://nixos.org/download.html)
- Installed [direnv](https://direnv.net/docs/installation.html) and added its hook to your shell.
Once the repo is cloned to your computer, follow these next few steps inside the Calckey folder:
- Run `direnv allow`. This will build the environment and install all needed tools.
- Run `install-deps`, then `prepare-config`, to install the node dependencies and prepare the needed config files.
- In a second terminal, run `devenv up`. This will spawn a **Redis** server, a **Postgres** server, and the **Calckey** server in dev mode.
- Once you see the Calckey banner printed in your second terminal, run `migrate` in the first.
- Once migrations finish, open http://localhost:3000 in your web browser.
- You should now see the admin user creation screen!
Note: When you want to restart a dev server, all you need to do is run `devenv up`, no other steps are necessary.

45
docs/kubernetes.md Normal file
View file

@ -0,0 +1,45 @@
# Running a Calckey instance with Kubernetes and Helm
This is a [Helm](https://helm.sh/) chart directory in the root of the project
that you can use to deploy calckey to a Kubernetes cluster
## Deployment
1. Copy the example helm values and make your changes:
```shell
cp .config/helm_values_example.yml .config/helm_values.yml
```
2. Update helm dependencies:
```shell
cd chart
helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print "helm repo add " $1 " " $3 }' | while read cmd; do $cmd; done;
cd ../
```
3. Create the calckey helm release (also used to update existing deployment):
```shell
helm upgrade \
--install \
--namespace calckey \
--create-namespace \
calckey chart/ \
-f .config/helm_values.yml
```
4. Watch your calckey instance spin up:
```shell
kubectl -n calckey get po -w
```
5. Initial the admin user and managed config:
```shell
export CALCKEY_USERNAME="my_desired_admin_handle" && \
export CALCKEY_PASSWORD="myDesiredInitialPassword" && \
export CALCKEY_HOST="calckey.example.com" && \
export CALCKEY_TOKEN=$(curl -X POST https://$CALCKEY_HOST/api/admin/accounts/create -H "Content-Type: application/json" -d "{ \"username\":\"$CALCKEY_USERNAME\", \"password\":\"$CALCKEY_PASSWORD\" }" | jq -r '.token') && \
echo "Save this token: ${CALCKEY_TOKEN}" && \
curl -X POST -H "Authorization: Bearer $CALCKEY_TOKEN" https://$CALCKEY_HOST/api/admin/accounts/hosted
```
6. Enjoy!

294
flake.lock Normal file
View file

@ -0,0 +1,294 @@
{
"nodes": {
"devenv": {
"inputs": {
"flake-compat": "flake-compat",
"nix": "nix",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1682953188,
"narHash": "sha256-MFH6yK7QnEV6+T96Pt++lH8ozDn4YqzaOXAS6u5h3mM=",
"owner": "cachix",
"repo": "devenv",
"rev": "c388b8c57116a71174d26b09c0c38b4b6b5bac3a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1682922129,
"narHash": "sha256-qnhkfksuuSLbN5UJM+KSCMSRC13bXosr6Ed3NwerRno=",
"owner": "nix-community",
"repo": "fenix",
"rev": "c1f90f80ba4d60bea60685dd4515fb22d53279cc",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1680392223,
"narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"nix": {
"inputs": {
"lowdown-src": "lowdown-src",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1676545802,
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
"owner": "domenkozar",
"repo": "nix",
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "relaxed-flakes",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1677534593,
"narHash": "sha256-PuZSAHeq4/9pP/uYH1FcagQ3nLm/DrDrvKi/xC9glvw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3ad64d9e2d5bf80c877286102355b1625891ae9a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1680213900,
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1673800717,
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1682929865,
"narHash": "sha256-jxVrgnf5QNjO+XoxDxUWtN2G5xyJSGZ5SWDQFxMuHxc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f2e9a130461950270f87630b11132323706b4d91",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1677160285,
"narHash": "sha256-tBzpCjMP+P3Y3nKLYvdBkXBg3KvTMo3gvi8tLQaqXVY=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "2bd861ab81469428d9c823ef72c4bb08372dd2c4",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"fenix": "fenix",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs_2"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1682886915,
"narHash": "sha256-FPQKPvlHIU2DsDF6GMoRtrZhil0vHi6MFd8vpKEx/n8=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "3a27518fee5a723005299cf49e2d58a842a261ca",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

86
flake.nix Normal file
View file

@ -0,0 +1,86 @@
{
description = "Calckey development flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# Flake Parts framework(https://flake.parts)
flake-parts.url = "github:hercules-ci/flake-parts";
# Devenv for better devShells(https://devenv.sh)
devenv.url = "github:cachix/devenv";
# Fenix for rust development
fenix.url = "github:nix-community/fenix";
fenix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devenv.flakeModule
];
# Define the systems that this works on. Only tested with x66_64-linux, add more if you test and it works.
systems = [
"x86_64-linux"
];
# Expose these attributes for every system defined above.
perSystem = { config, pkgs, ... }: {
# Devenv shells
devenv = {
shells = {
# The default shell, used by nix-direnv
default = {
name = "calckey-dev-shell";
# Add additional packages to our environment
packages = [
pkgs.nodePackages.pnpm
pkgs.python3
];
# No need to warn on a new version, we'll update as needed.
devenv.warnOnNewVersion = false;
# Enable typescript support
languages.typescript.enable = true;
# Enable javascript for NPM and PNPM
languages.javascript.enable = true;
languages.javascript.package = pkgs.nodejs_19;
# Enable stable Rust for the backend
languages.rust.enable = true;
languages.rust.version = "stable";
processes = {
dev-server.exec = "pnpm run dev";
};
scripts = {
build.exec = "pnpm run build";
clean.exec = "pnpm run clean";
clear-state.exec = "rm -rf .devenv/state/redis .devenv/state/postgres";
format.exec = "pnpm run format";
install-deps.exec = "pnpm install";
migrate.exec = "pnpm run migrate";
prepare-config.exec = "cp .config/devenv.yml .config/default.yml";
};
services = {
postgres = {
enable = true;
package = pkgs.postgresql_12;
initialDatabases = [{
name = "calckey";
}];
initialScript = ''
CREATE USER calckey WITH PASSWORD 'calckey';
ALTER USER calckey WITH SUPERUSER;
GRANT ALL ON DATABASE calckey TO calckey;
'';
listen_addresses = "127.0.0.1";
port = 5432;
};
redis = {
enable = true;
bind = "127.0.0.1";
port = 6379;
};
};
};
};
};
};
};
}

View file

@ -1,18 +1,18 @@
_lang_: "Català" _lang_: "Català"
headlineMisskey: "Una xarxa social de codi obert, descentralitzada i gratuita per\ headlineMisskey: "Una xarxa social de codi obert, descentralitzada i gratuïta per\
\ sempre \U0001F680" \ a sempre! \U0001F680"
introMisskey: "Benvinguts! Calckey es una plataforma social de codi obert, descentralitzada\ introMisskey: "Benvinguts! Calckey és una plataforma social de codi obert, descentralitzada\
\ i gratuita per sempre! \U0001F680" \ i gratuïta per a sempre! \U0001F680"
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Cercar" search: "Cerca"
notifications: "Notificacions" notifications: "Notificacions"
username: "Nom d'usuari" username: "Nom d'usuari"
password: "Contrasenya" password: "Contrasenya"
forgotPassword: "Contrasenya oblidada" forgotPassword: "Contrasenya oblidada"
fetchingAsApObject: "Cercant en el Fediverse" fetchingAsApObject: "Cercant en el Fediverse"
ok: "OK" ok: "D'acord"
gotIt: "Ho he entès!" gotIt: "Ho he entès!"
cancel: "Cancel·lar" cancel: "Cancel·la"
enterUsername: "Introdueix el teu nom d'usuari" enterUsername: "Introdueix el teu nom d'usuari"
renotedBy: "Impulsat per {user}" renotedBy: "Impulsat per {user}"
noNotes: "Cap publicació" noNotes: "Cap publicació"
@ -21,69 +21,69 @@ instance: "Instància"
settings: "Preferències" settings: "Preferències"
basicSettings: "Configuració bàsica" basicSettings: "Configuració bàsica"
otherSettings: "Altres opcions" otherSettings: "Altres opcions"
openInWindow: "Obrir en una finestra nova" openInWindow: "Obre en una finestra nova"
profile: "Perfil" profile: "Perfil"
timeline: "Línia de temps" timeline: "Línia de temps"
noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia." noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
login: "Iniciar sessió" login: "Inicia sessió"
loggingIn: "Iniciant sessió" loggingIn: "Iniciant sessió"
logout: "Tancar sessió" logout: "Tanca la sessió"
signup: "Registrar-se" signup: "Registra'm"
uploading: "Pujant..." uploading: "S'està pujant…"
save: "Desar" save: "Desa"
users: "Usuaris" users: "Usuaris"
addUser: "Afegir un usuari" addUser: "Afegeix un usuari"
favorite: "Afegir a favorits" favorite: "Afegeix als marcadors"
favorites: "Favorits" favorites: "Marcadors"
unfavorite: "Eliminar de favorits" unfavorite: "Elimina dels marcadors"
favorited: "Afegit a favorits." favorited: "S'ha afegit el marcador."
alreadyFavorited: "Ja s'ha afegit a favorits." alreadyFavorited: "Ja està afegida als marcadors."
cantFavorite: "No s'ha pogut afegir a favorits." cantFavorite: "No s'ha pogut afegir als marcadors."
pin: "Fixar al perfil" pin: "Fixa al perfil"
unpin: "Deixar de fixar al perfil" unpin: "Deixa de fixar al perfil"
copyContent: "Còpia el contingut" copyContent: "Copia el contingut"
copyLink: "Còpia l'enllaç" copyLink: "Copia l'enllaç"
delete: "Esborra" delete: "Elimina"
deleteAndEdit: "Esborrar i edita" deleteAndEdit: "Elimina i edita"
deleteAndEditConfirm: "Estàs segur que vols esborrar aquesta publicació i editar-la?\ deleteAndEditConfirm: "Segur que vols eliminar la publicació i editar-la? Perdràs\
\ Perdràs totes les reaccions, impulsos i respostes." \ totes les reaccions, impulsos i respostes."
addToList: "Afegir a la llista" addToList: "Afegeix a la llista"
sendMessage: "Enviar un missatge" sendMessage: "Envia un missatge"
copyUsername: "Còpia nom d'usuari" copyUsername: "Copia el nom d'usuari"
searchUser: "Cercar un usuari" searchUser: "Cerca un usuari"
reply: "Respon" reply: "Respon"
loadMore: "Carregar més" loadMore: "Carrega'n més"
showMore: "Veure més" showMore: "Mostra'n més"
youGotNewFollower: "t'ha seguit" youGotNewFollower: "t'ha seguit"
receiveFollowRequest: "Sol·licitud de seguiment rebuda" receiveFollowRequest: "Sol·licitud de seguiment rebuda"
followRequestAccepted: "Sol·licitud de seguiment acceptada" followRequestAccepted: "Sol·licitud de seguiment acceptada"
mention: "Menció" mention: "Menció"
mentions: "Mencions" mentions: "Mencions"
directNotes: "Missatges directes" directNotes: "Missatges directes"
importAndExport: "Importar / Exportar Dades" importAndExport: "Importa/exporta dades"
import: "Importar" import: "Importa"
export: "Exportar" export: "Exporta"
files: "Fitxers" files: "Fitxers"
download: "Descarregar" download: "Baixa"
driveFileDeleteConfirm: "Estàs segur que vols esborrar el fitxer \"{nom}\"? S'eliminarà\ driveFileDeleteConfirm: "Segur que vols eliminar el fitxer «{name}»? S'eliminarà de\
\ de totes les notes que el continguin com a fitxer adjunt." \ totes les notes que el continguin com a fitxer adjunt."
unfollowConfirm: "Estàs segur que vols deixar de seguir {name}?" unfollowConfirm: "Segur que vols deixar de seguir {name}?"
exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà\ exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà\
\ al teu Disc un cop completada." \ al teu Disc un cop completada."
importRequested: "Has sol·licitat una importació. Això pot trigar una estona." importRequested: "Has sol·licitat una importació. Això pot trigar una estona."
lists: "Llistes" lists: "Llistes"
noLists: "No tens cap llista" noLists: "No teniu cap llista"
note: "Publicació" note: "Publicació"
notes: "Notes" notes: "Publicacions"
following: "Seguint" following: "Seguint"
followers: "Seguidors" followers: "Seguidors"
followsYou: "Et segueix" followsYou: "Et segueix"
createList: "Crear llista" createList: "Crea una llista"
manageLists: "Gestionar les llistes" manageLists: "Gestiona les llistes"
error: "Error" error: "Error"
somethingHappened: "S'ha produït un error" somethingHappened: "S'ha produït un error"
retry: "Torna-ho a intentar" retry: "Torna-ho a intentar"
pageLoadError: "Alguna cosa a sortit malament al carregar la pàgina." pageLoadError: "S'ha produït un error en carregar la pàgina."
pageLoadErrorDescription: "Això normalment es deu a errors de xarxa o a la memòria\ pageLoadErrorDescription: "Això normalment es deu a errors de xarxa o a la memòria\
\ cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després\ \ cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després\
\ d'esperar una estona." \ d'esperar una estona."
@ -94,32 +94,32 @@ enterListName: "Introdueix un nom per a la llista"
privacy: "Privadesa" privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació" makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte" defaultNoteVisibility: "Visibilitat per defecte"
follow: "Seguir" follow: "Segueix"
followRequest: "Enviar sol·licitud de seguiment" followRequest: "Segueix"
followRequests: "Sol·licituds de seguiment" followRequests: "Sol·licituds de seguiment"
unfollow: "Deixar de seguir" unfollow: "Deixa de seguir"
followRequestPending: "Sol·licituds de seguiment pendents" followRequestPending: "Sol·licituds de seguiment pendents"
enterEmoji: "Introduir un emoji" enterEmoji: "Introdueix un emoji"
renote: "Impuls" renote: "Impulsa"
unrenote: "Anul·lar impuls" unrenote: "Anul·la l'impuls"
renoted: "Impulsat." renoted: "S'ha impulsat."
cantRenote: "Aquesta publicació no es pot impulsar." cantRenote: "Aquesta publicació no es pot impulsar."
cantReRenote: "No es pot impulsar un impuls." cantReRenote: "No es pot impulsar un impuls."
quote: "Cita" quote: "Cita"
pinnedNote: "Publicació fixada" pinnedNote: "Publicació fixada"
pinned: "Fixar al perfil" pinned: "Fixa al perfil"
you: "Tu" you: "Tu"
clickToShow: "Fes clic per mostrar" clickToShow: "Fes clic per a mostrar"
sensitive: "NSFW" sensitive: "NSFW"
add: "Afegir" add: "Afegeix"
reaction: "Reaccions" reaction: "Reaccions"
reactionSetting: "Reaccions a mostrar al selector de reaccions" reactionSetting: "Reaccions a mostrar al selector de reaccions"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem\ reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem\
\ \"+\" per afegir." \ \"+\" per afegir."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
attachCancel: "Eliminar el fitxer adjunt" attachCancel: "Elimina el fitxer adjunt"
markAsSensitive: "Marcar com a NSFW" markAsSensitive: "Marca com a NSFW"
unmarkAsSensitive: "Desmarcar com a NSFW" unmarkAsSensitive: "Desmarca com a NSFW"
enterFileName: "Introdueix un nom de fitxer" enterFileName: "Introdueix un nom de fitxer"
mute: "Silencia" mute: "Silencia"
unmute: "Deixa de silenciar" unmute: "Deixa de silenciar"
@ -128,7 +128,7 @@ unblock: "Desbloqueja"
suspend: "Suspèn" suspend: "Suspèn"
unsuspend: "Treu la suspensió" unsuspend: "Treu la suspensió"
instances: "Instàncies" instances: "Instàncies"
remove: "Eliminar" remove: "Elimina"
nsfw: "NSFW" nsfw: "NSFW"
pinnedNotes: "Publicacions fixades" pinnedNotes: "Publicacions fixades"
userList: "Llistes" userList: "Llistes"
@ -738,39 +738,39 @@ _deck:
swapLeft: Canvia amb la columna de l'esquerra swapLeft: Canvia amb la columna de l'esquerra
renameProfile: Canvia el nom de l'espai de treball renameProfile: Canvia el nom de l'espai de treball
nameAlreadyExists: Aquest nom d'espai de treball ja existeix. nameAlreadyExists: Aquest nom d'espai de treball ja existeix.
blockConfirm: Estás segur que vols bloquejar aquest compte? blockConfirm: Segur que vols bloquejar aquest compte?
unsuspendConfirm: Estás segur que vols treure la suspensió d'aquest compte? unsuspendConfirm: Segur que vols treure la suspensió d'aquest compte?
unblockConfirm: Estás segur que vols treure el bloqueig d'aquest compte? unblockConfirm: Segur que vols treure el bloqueig d'aquest compte?
suspendConfirm: Estás segur que vols suspendre aquest compte? suspendConfirm: Segur que vols suspendre aquest compte?
selectList: Selecciona una llista selectList: Selecciona una llista
selectAntenna: Selecciona una antena selectAntenna: Selecciona una antena
selectWidget: Selecciona un giny selectWidget: Selecciona un giny
editWidgets: Edita ginys editWidgets: Edita els ginys
editWidgetsExit: Fet editWidgetsExit: Fet
customEmojis: Emoji personalitzat customEmojis: Emojis personalitzats
cacheRemoteFilesDescription: Quant aquesta opció es deshabilitada, els fitxers remots cacheRemoteFilesDescription: Quan aquesta opció està desactivada, els fitxers remots
es carregant directament de l'instància remota. Deshabilitar això farà que baixi es carreguin directament de la instància remota. Desactivar-la farà que baixi l'ús
l'ús d'emmagatzematge, però incrementa el tràfic, perquè les miniatures no es generarán. d'emmagatzematge, però incrementa el tràfic, perquè les miniatures no es generaran.
flagAsBot: Marca aquest compte com un bot flagAsBot: Marca aquest compte com a bot
flagAsBotDescription: Activa aquesta opció si aquest compte és controlat per un programa. flagAsBotDescription: Activa aquesta opció si aquest compte és controlat per un programa.
Si s'activa, això actuarà com una bandera per a altres desenvolupadors i ajuda a Si s'activa, això actuarà com una bandera per a altres desenvolupadors i ajuda a
prevenir cadenes de interaccions infinites amb altres bots a més d'ajustar els sistemes prevenir cadenes de interaccions infinites amb altres bots a més d'ajustar els sistemes
interns de Calckey per tractar aquest compte com un bot. interns de Calckey per tractar aquest compte com un bot.
flagAsCat: Ets un gat? 🐱 flagAsCat: Ets un gat? 🐱
flagShowTimelineReplies: Mostrar respostes a la línea de temps flagShowTimelineReplies: Mostra respostes a la línia de temps
flagAsCatDescription: Guanyaràs unes orelles de gat i parlares com un gat! flagAsCatDescription: Guanyaràs unes orelles de gat i parlares com un gat!
flagShowTimelineRepliesDescription: Mostrará respostes d'usuaris a notes d'altres flagShowTimelineRepliesDescription: Si s'activa, es mostraran les respostes d'usuaris
usuaris si s'activa. a publicacions d'altres usuaris.
general: General general: General
autoAcceptFollowed: Aprova automàticament les peticions de seguiment d'usuaris que autoAcceptFollowed: Aprova automàticament les peticions de seguiment d'usuaris que
segueixes segueixes
accountMoved: "L'usuari s'ha mogut a un compte nou:" accountMoved: "L'usuari s'ha mogut a un compte nou:"
addAccount: Afegir un compte addAccount: Afegeix un compte
loginFailed: No s'ha pogut iniciar sessió loginFailed: No s'ha pogut iniciar sessió
showOnRemote: Veure a l'instància remota showOnRemote: Mostra a la instància remota
wallpaper: Fons de pantalla wallpaper: Fons de pantalla
setWallpaper: Estableix fons de pantalla setWallpaper: Estableix fons de pantalla
removeWallpaper: Esborra fons de pantalla removeWallpaper: Elimina el fons de pantalla
followConfirm: Segur que vols seguir a l'usuari {name}? followConfirm: Segur que vols seguir a l'usuari {name}?
proxyAccount: Compte proxy proxyAccount: Compte proxy
proxyAccountDescription: Un compte proxy es un compte que actua com un seguidor remot proxyAccountDescription: Un compte proxy es un compte que actua com un seguidor remot
@ -781,127 +781,127 @@ host: Amfitrió
selectUser: Selecciona un usuari selectUser: Selecciona un usuari
latestStatus: Últim estat latestStatus: Últim estat
storageUsage: Ús del emmagatzematge storageUsage: Ús del emmagatzematge
metadata: Metadata metadata: Metadades
withNFiles: '{n} fitxer(s)' withNFiles: '{n} fitxer(s)'
monitor: Seguiment monitor: Seguiment
software: Programari software: Programari
version: Versió version: Versió
jobQueue: Cua de Feina jobQueue: Cua de feina
cpuAndMemory: CPU i Memòria cpuAndMemory: CPU i memòria
network: Xarxa network: Xarxa
disk: Disc disk: Disc
instanceInfo: Informació de l'instància instanceInfo: Informació de la instància
statistics: Estadístiques statistics: Estadístiques
clearCachedFiles: Neteja la memòria cau clearCachedFiles: Esborra la memòria cau
clearQueueConfirmText: Qualsevol publicació que continuï a la cua sense entregar no clearQueueConfirmText: Qualsevol publicació que continuï a la cua sense entregar no
será federada. Normalment aquesta operació no es necessària. será federada. Normalment aquesta operació no es necessària.
clearCachedFilesConfirm: Segur que vols esborrar els fitxers remots de la memòria clearCachedFilesConfirm: Segur que vols esborrar els fitxers remots de la memòria
cau? cau?
blockedUsers: Usuaris blocats blockedUsers: Usuaris blocats
noUsers: No hi han usuaris noUsers: No hi ha cap usuari
editProfile: Editar perfil editProfile: Edita el perfil
noteDeleteConfirm: Segur que vols esborrar aquesta publicació? noteDeleteConfirm: Segur que vols eliminar la publicació?
pinLimitExceeded: No pots fixar més notes pinLimitExceeded: No pots fixar més notes
muteAndBlock: Silenciats i Bloquejats muteAndBlock: Silenciats i blocats
mutedUsers: Usuaris silenciats mutedUsers: Usuaris silenciats
done: Fet done: Fet
preview: Vista prèvia preview: Vista prèvia
default: Per defecte default: Per defecte
intro: La instalació de Calckey a acabat! Si us plau crea un compte d'usuari. intro: La instal·lació de Calckey ha acabat! Crea un compte d'usuari d'administració.
processing: Processant... processing: S'està processant…
noCustomEmojis: No hi ha emoji noCustomEmojis: No hi ha cap emoji
noJobs: No hi han feines noJobs: No hi ha cap feina
federating: Federant federating: Federant
blocked: Bloquejat blocked: Bloquejat
subscribing: Subscrivint subscribing: Subscrivint
publishing: Publicant publishing: Publicant
notResponding: Sense resposta notResponding: Sense resposta
instanceUsers: Usuaris d'aquesta instància instanceUsers: Usuaris d'aquesta instància
instanceFollowing: Seguint a l'instància instanceFollowing: Seguint a la instància
instanceFollowers: Seguidors de l'instància instanceFollowers: Seguidors de l'instància
security: Seguretat security: Seguretat
newPasswordRetype: Torna a entrar la nova contrasenya newPasswordRetype: Torna a entrar la nova contrasenya
more: Més! more: Més!
featured: Destacat featured: Destacat
usernameOrUserId: Nom d'usuari o id d'usuari usernameOrUserId: Nom o ID d'usuari
noSuchUser: No s'ha trobat l'usuari noSuchUser: No s'ha trobat l'usuari
lookup: Cercar lookup: Cerca
attachFile: Afegeix un arxiu attachFile: Afegeix un fitxer
currentPassword: Contrasenya actual currentPassword: Contrasenya actual
newPassword: Nova contrasenya newPassword: Nova contrasenya
announcements: Anuncis announcements: Anuncis
imageUrl: URL de la imatge imageUrl: URL de la imatge
removed: S'ha esborrat correctament removed: S'ha eliminat correctament
removeAreYouSure: Segur que vols esborrar "{x}"? removeAreYouSure: Segur que vols eliminar «{x}»?
deleteAreYouSure: Segur que vols esborrar "{x}"? deleteAreYouSure: Segur que vols eliminar «{x}»?
resetAreYouSure: Restablir? Segur? resetAreYouSure: Segur que vols restablir?
fromUrl: Des de URL fromUrl: Des d'una URL
saved: Desat saved: S'ha desat
messaging: Xat messaging: Xat
upload: Pujar upload: Puja
keepOriginalUploading: Desa imatge original keepOriginalUploading: Desa la imatge original
keepOriginalUploadingDescription: Desa la imatge original pujada tal com es. Si es keepOriginalUploadingDescription: Desa la imatge original pujada tal com es. Si es
desactiva, es generarà una versió per mostrar en la web al pujar. desactiva, es generarà una versió per mostrar en la web al pujar.
fromDrive: Des de Drive fromDrive: Des del Disc
uploadFromUrl: Puja des de una adreça URL uploadFromUrl: Puja des d'una adreça URL
uploadFromUrlDescription: Adreça URL del fitxer que vols pujar uploadFromUrlDescription: Adreça URL del fitxer que vols pujar
uploadFromUrlRequested: Pujada demanada uploadFromUrlRequested: Pujada demanada
noMoreHistory: S'ha acabat la historia noMoreHistory: No hi ha més historial
tos: Termes d'us tos: Condicions d'ús
start: Començar start: Comença
startMessaging: Comença un nou xat startMessaging: Comença una conversa
manageGroups: Gestiona els grups manageGroups: Gestiona els grups
nUsersRead: llegit per {n} nUsersRead: llegit per {n}
agreeTo: Estic d'acord amb {0} agreeTo: Estic d'acord amb {0}
activity: Activitat activity: Activitat
home: Inici home: Inici
remoteUserCaution: L'informació d'usuaris remots pot estar incompleta. remoteUserCaution: La informació dels usuaris remots pot estar incompleta.
themeForDarkMode: Tema a fer servir en mode fosc themeForDarkMode: Tema a fer servir en mode fosc
light: Clar light: Clar
registeredDate: Data de registre registeredDate: Data de registre
dark: Fosc dark: Fosc
lightThemes: Temes clars lightThemes: Temes clars
location: Lloc location: Ubicació
theme: Temes theme: Temes
themeForLightMode: Tema a fer servir en mode clar themeForLightMode: Tema a fer servir en mode clar
drive: Disc drive: Disc
selectFile: Tria un fitxer selectFile: Tria un fitxer
selectFiles: Tria fitxers selectFiles: Tria fitxers
darkThemes: Temes foscos darkThemes: Temes foscos
syncDeviceDarkMode: Sincronitza el Mode Fosc amb la configuració del teu dispositiu syncDeviceDarkMode: Sincronitza el mode fosc amb la configuració del teu dispositiu
fileName: Nom del fitxer fileName: Nom del fitxer
createFolder: Crea una carpeta createFolder: Crea una carpeta
renameFolder: Posa un nom nou a aquesta carpeta renameFolder: Canvia-li el nom a la carpeta
deleteFolder: Esborra aquesta carpeta deleteFolder: Elimina la carpeta
selectFolder: Tria una carpeta selectFolder: Tria una carpeta
selectFolders: Tria carpetes selectFolders: Tria carpetes
renameFile: Canvia el nom del fitxer renameFile: Canvia el nom del fitxer
folderName: Nom de la carpeta folderName: Nom de la carpeta
inputNewFolderName: Escriu un nou nom per la carpeta inputNewFolderName: Escriu un nom de carpeta nou
addFile: Afegeix un fitxer addFile: Afegeix un fitxer
emptyDrive: El teu Disc és buit emptyDrive: El teu Disc és buit
emptyFolder: Aquesta carpeta és buida emptyFolder: Aquesta carpeta és buida
unableToDelete: No es pot esborrar unableToDelete: No es pot eliminar
inputNewFileName: Escriu un nou nom per al fitxer inputNewFileName: Escriu un nou nom per al fitxer
inputNewDescription: Escriu una nova descripció inputNewDescription: Escriu una descripció nova
circularReferenceFolder: La carpeta de destí es una subcarpeta de la carpeta que vols circularReferenceFolder: La carpeta de destí és una subcarpeta de la carpeta que vols
moure. moure.
hasChildFilesOrFolders: Degut a que aquesta carpeta no es buida, no es pot esborrar. hasChildFilesOrFolders: Aquesta carpeta no es pot eliminar perquè no és buida.
whenServerDisconnected: Quant es perd la conexió amb el servidor whenServerDisconnected: Quant es perd la conexió amb el servidor
disconnectedFromServer: S'ha perdut la conexió al servidor disconnectedFromServer: S'ha perdut la conexió al servidor
reload: Torna a carregar reload: Torna a carregar
avatar: Avatar avatar: Avatar
banner: Banner banner: Bàner
doNothing: Ignora doNothing: Ignora
reloadConfirm: Vols tornar a carregar la línea temporal? reloadConfirm: Vols tornar a carregar la línea temporal?
watch: Veure watch: Veure
maintainerName: Administrador maintainerName: Administrador
maintainerEmail: Correu electrònic de l'administrador maintainerEmail: Correu electrònic de l'administrador
instanceName: Nom de l'instància instanceName: Nom de la instància
instanceDescription: Descripció de l'instància instanceDescription: Descripció de la instància
today: Avui today: Avui
dayX: '{day}' dayX: '{day}'
tosUrl: Adreça URL dels terminis d'ús tosUrl: URL de les Condicions d'ús
thisYear: Any thisYear: Any
thisMonth: Mes thisMonth: Mes
integration: Integracions integration: Integracions
@ -928,37 +928,37 @@ enableGlobalTimeline: Activa la línia de temps global
disablingTimelinesInfo: Els Administradors i Moderadors sempre tenen accés a totes disablingTimelinesInfo: Els Administradors i Moderadors sempre tenen accés a totes
les líneas temporals, inclòs si hi són desactivades. les líneas temporals, inclòs si hi són desactivades.
showLess: Tanca showLess: Tanca
clearQueue: Neteja la cua clearQueue: Esborra la cua
uploadFromUrlMayTakeTime: Pot trigar un temps fins que la pujada es completi. uploadFromUrlMayTakeTime: Pot trigar un temps fins que la pujada es completi.
noThankYou: No, gràcies noThankYou: No, gràcies
addInstance: Afegir una instància addInstance: Afegeix una instància
emoji: Emoji emoji: Emojis
emojis: Emoji emojis: Emojis
emojiName: Nom del emoji emojiName: Nom del emoji
emojiUrl: URL del emoji emojiUrl: URL de l'emoji
addEmoji: Afegir addEmoji: Afegeix
settingGuide: Configuració recomenada settingGuide: Configuració recomenada
searchWith: 'Cercar: {q}' searchWith: 'Cerca: {q}'
youHaveNoLists: No tens cap llista youHaveNoLists: No tens cap llista
flagSpeakAsCat: Parla com un gat flagSpeakAsCat: Parla com un gat
selectInstance: Selecciona una instància selectInstance: Selecciona una instància
flagSpeakAsCatDescription: Les teves notes es transformaran en miols quant estiguis flagSpeakAsCatDescription: Les teves publicacions es transformaran en miols quan estiguis
en mode gat en mode gat
recipient: Destinatari(s) recipient: Destinatari(s)
annotation: Comentaris annotation: Comentaris
blockedInstances: Instàncies Bloquejades blockedInstances: Instàncies bloquejades
blockedInstancesDescription: Llista les adreces de les instàncies que vols bloquejar. blockedInstancesDescription: Llista les adreces de les instàncies que vols bloquejar.
Les instàncies de la llista no podrán comunicarse amb aquesta instància. Les instàncies de la llista no podrán comunicarse amb aquesta instància.
hiddenTags: Etiquetes Ocultes hiddenTags: Etiquetes amagades
hiddenTagsDescription: 'Enumereu les etiquetes (sense el #) que voleu ocultar de tendències hiddenTagsDescription: 'Enumereu les etiquetes (sense el #) que voleu ocultar de tendències
i explorar. Les etiquetes ocultes encara es poden descobrir per altres mitjans. i explorar. Les etiquetes ocultes encara es poden descobrir per altres mitjans.
Les instàncies bloquejades no es veuen afectades encara que s''enumerin aquí.' Les instàncies bloquejades no es veuen afectades encara que s''enumerin aquí.'
noInstances: No hi han instàncies noInstances: No hi ha cap instància
defaultValueIs: 'Per defecte: {value}' defaultValueIs: 'Per defecte: {value}'
suspended: Suspès suspended: Suspès
all: Tot all: Tot
changePassword: Canvia la contrasenya changePassword: Canvia la contrasenya
clearQueueConfirmTitle: Segur que vols netejar la cua? clearQueueConfirmTitle: Segur que vols esborrar la cua?
retypedNotMatch: Els camps no coincideixen. retypedNotMatch: Els camps no coincideixen.
normal: Normal normal: Normal
monthX: '{month}' monthX: '{month}'
@ -975,25 +975,25 @@ registration: Registre
showEmojisInReactionNotifications: Mostra els emojis a les notificacions de les reaccions showEmojisInReactionNotifications: Mostra els emojis a les notificacions de les reaccions
renoteMute: Silencia els impulsos renoteMute: Silencia els impulsos
renoteUnmute: Treu el silenci als impulsos renoteUnmute: Treu el silenci als impulsos
cacheRemoteFiles: Fitxers remots a la memoria cau cacheRemoteFiles: Fitxers remots a la memòria cau
federation: Federació federation: Federació
registeredAt: Registrat a registeredAt: Registrat a
latestRequestSentAt: Última petició enviada latestRequestSentAt: Última petició enviada
latestRequestReceivedAt: Última petició rebuda latestRequestReceivedAt: Última petició rebuda
charts: Gràfics charts: Gràfics
perHour: Per Hora perHour: Per hora
perDay: Per Dia perDay: Per dia
stopActivityDelivery: Para d'enviar activitats stopActivityDelivery: Para d'enviar activitats
operations: Operacions operations: Operacions
explore: Explorar explore: Explora
messageRead: Llegir messageRead: Llegit
images: Imatges images: Imatges
birthday: Aniversari birthday: Aniversari
yearsOld: '{age} anys' yearsOld: '{age} anys'
copyUrl: Copiar l'adreça URL copyUrl: Copia l'adreça URL
rename: Renombrar rename: Renombrar
unwatch: Deixa de veure unwatch: Deixa de veure
accept: Acceptar accept: Accepta
reject: Rebutja reject: Rebutja
yearX: '{year}' yearX: '{year}'
pages: Pàgines pages: Pàgines
@ -1511,7 +1511,7 @@ seperateRenoteQuote: Botons d'impuls i de citació separats
searchResult: Resultats de la cerca searchResult: Resultats de la cerca
hashtags: Etiquetes hashtags: Etiquetes
troubleshooting: Resolució de problemes troubleshooting: Resolució de problemes
learnMore: Aprèn més learnMore: Més informació
misskeyUpdated: Calckey s'ha actualitzat! misskeyUpdated: Calckey s'ha actualitzat!
translate: Tradueix translate: Tradueix
translatedFrom: Traduït per {x} translatedFrom: Traduït per {x}
@ -1599,7 +1599,7 @@ squareAvatars: Mostra avatars quadrats
secureModeInfo: Quan es faci una solicitut d'altres instàncies no contestar sense secureModeInfo: Quan es faci una solicitut d'altres instàncies no contestar sense
una prova. una prova.
privateModeInfo: Quan està activat, només les instàncies de la llista blanca es poden privateModeInfo: Quan està activat, només les instàncies de la llista blanca es poden
federar amb les vostres instàncies. Totes les notes s'amagaran al públic. federar amb la vostra instància. Totes les publicacions s'amagaran al públic.
useBlurEffect: Utilitzeu efectes de desenfocament a la interfície d'usuari useBlurEffect: Utilitzeu efectes de desenfocament a la interfície d'usuari
accountDeletionInProgress: La supressió del compte està en curs accountDeletionInProgress: La supressió del compte està en curs
unmuteThread: Desfés el silenci al fil unmuteThread: Desfés el silenci al fil
@ -1815,6 +1815,8 @@ _channel:
usersCount: '{n} Participants' usersCount: '{n} Participants'
following: Seguit following: Seguit
notesCount: '{n} Notes' notesCount: '{n} Notes'
nameAndDescription: Nom i descripció
nameOnly: Només nom
_instanceMute: _instanceMute:
instanceMuteDescription: Això silenciarà les notes o els impulsos de les instàncies instanceMuteDescription: Això silenciarà les notes o els impulsos de les instàncies
indicades, incloses les dels usuaris que responguin a un usuari des d'una instància indicades, incloses les dels usuaris que responguin a un usuari des d'una instància
@ -2020,24 +2022,7 @@ _relayStatus:
requesting: Pendent requesting: Pendent
accepted: Acceptat accepted: Acceptat
rejected: Rebutjat rejected: Rebutjat
_apps: deleted: Eliminat
crossPlatform: Multiplataforma
mobile: Mòbil
firstParty: Primer partit
secondClass: Segona classe
thirdClass: Tercera classe
pwa: Instal·lar PWA
kaiteki: Kaiteki
milktea: Milktea
missLi: MissLi
mona: Mona
lesskey: Lesskey
firstClass: Primera classe
free: Gratuït
paid: Pagament
theDesk: TheDesk
apps: Aplicacions
deleted: Esborrat
editNote: Edita la nota editNote: Edita la nota
edited: Editat edited: Editat
findOtherInstance: Cercar un altre servidor findOtherInstance: Cercar un altre servidor
@ -2047,3 +2032,10 @@ signupsDisabled: Actualment, les inscripcions en aquest servidor estan desactiva
userSaysSomethingReasonQuote: '{name} ha citat una publicació que conté {reason}' userSaysSomethingReasonQuote: '{name} ha citat una publicació que conté {reason}'
userSaysSomethingReasonReply: '{name} ha respost a una publicació que conté {reason}' userSaysSomethingReasonReply: '{name} ha respost a una publicació que conté {reason}'
userSaysSomethingReasonRenote: '{name} ha impulsat una publicació que conté {reason}' userSaysSomethingReasonRenote: '{name} ha impulsat una publicació que conté {reason}'
highlightCw: Ressalta el contingut de les publicacions advertides
apps: Aplicacions
sendModMail: Envia avís de moderació
preventAiLearning: Evita l'indexació dels bots
preventAiLearningDescription: Sol·liciteu que els models de llenguatge d'IA de tercers
no estudiïn el contingut que pengeu, com ara publicacions i imatges.
pwa: Instal·lar PWA

View file

@ -1,7 +1,9 @@
---
_lang_: "Čeština" _lang_: "Čeština"
headlineMisskey: "Síť propojená poznámkami" headlineMisskey: "Síť propojená poznámkami"
introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový servis.\n\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. 📡\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. 👍\nPojďte objevovat nový svět! 🚀" introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový servis.\n\
\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. \U0001F4E1\
\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. \U0001F44D\
\nPojďte objevovat nový svět! \U0001F680"
monthAndDay: "{day}. {month}." monthAndDay: "{day}. {month}."
search: "Vyhledávání" search: "Vyhledávání"
notifications: "Oznámení" notifications: "Oznámení"
@ -44,7 +46,8 @@ copyContent: "Zkopírovat obsah"
copyLink: "Kopírovat odkaz" copyLink: "Kopírovat odkaz"
delete: "Smazat" delete: "Smazat"
deleteAndEdit: "Smazat a upravit" deleteAndEdit: "Smazat a upravit"
deleteAndEditConfirm: "Jste si jistí že chcete smazat tuto poznámku a editovat ji? Ztratíte tím všechny reakce, sdílení a odpovědi na ni." deleteAndEditConfirm: "Jste si jistí že chcete smazat tuto poznámku a editovat ji?\
\ Ztratíte tím všechny reakce, sdílení a odpovědi na ni."
addToList: "Přidat do seznamu" addToList: "Přidat do seznamu"
sendMessage: "Odeslat zprávu" sendMessage: "Odeslat zprávu"
copyUsername: "Kopírovat uživatelské jméno" copyUsername: "Kopírovat uživatelské jméno"
@ -63,9 +66,11 @@ import: "Importovat"
export: "Exportovat" export: "Exportovat"
files: "Soubor(ů)" files: "Soubor(ů)"
download: "Stáhnout" download: "Stáhnout"
driveFileDeleteConfirm: "Opravdu chcete smazat soubor \"{name}\"? Poznámky, ke kterým je tento soubor připojen, budou také smazány." driveFileDeleteConfirm: "Opravdu chcete smazat soubor \"{name}\"? Soubor bude odstraněn\
\ ze všech příspěvků, které ji obsahují jako přílohu."
unfollowConfirm: "Jste si jisti že už nechcete sledovat {name}?" unfollowConfirm: "Jste si jisti že už nechcete sledovat {name}?"
exportRequested: "Požádali jste o export. To může chvíli trvat. Přidáme ho na váš Disk až bude dokončen." exportRequested: "Požádali jste o export. To může chvíli trvat. Přidáme ho na váš\
\ Disk až bude dokončen."
importRequested: "Požádali jste o export. To může chvilku trvat." importRequested: "Požádali jste o export. To může chvilku trvat."
lists: "Seznamy" lists: "Seznamy"
noLists: "Nemáte žádné seznamy" noLists: "Nemáte žádné seznamy"
@ -81,7 +86,8 @@ somethingHappened: "Jejda. Něco se nepovedlo."
retry: "Opakovat" retry: "Opakovat"
pageLoadError: "Nepodařilo se načíst stránku" pageLoadError: "Nepodařilo se načíst stránku"
serverIsDead: "Server neodpovídá. Počkejte chvíli a zkuste to znovu." serverIsDead: "Server neodpovídá. Počkejte chvíli a zkuste to znovu."
youShouldUpgradeClient: "Pro zobrazení této stránky obnovte stránku pro aktualizaci klienta." youShouldUpgradeClient: "Pro zobrazení této stránky obnovte stránku pro aktualizaci\
\ klienta."
enterListName: "Jméno seznamu" enterListName: "Jméno seznamu"
privacy: "Soukromí" privacy: "Soukromí"
makeFollowManuallyApprove: "Žádosti o sledování vyžadují potvrzení" makeFollowManuallyApprove: "Žádosti o sledování vyžadují potvrzení"
@ -105,7 +111,8 @@ clickToShow: "Klikněte pro zobrazení"
sensitive: "NSFW" sensitive: "NSFW"
add: "Přidat" add: "Přidat"
reaction: "Reakce" reaction: "Reakce"
reactionSettingDescription2: "Přetažením změníte pořadí, kliknutím smažete, zmáčkněte \"+\" k přidání" reactionSettingDescription2: "Přetažením změníte pořadí, kliknutím smažete, zmáčkněte\
\ \"+\" k přidání"
rememberNoteVisibility: "Zapamatovat nastavení zobrazení poznámky" rememberNoteVisibility: "Zapamatovat nastavení zobrazení poznámky"
attachCancel: "Odstranit přílohu" attachCancel: "Odstranit přílohu"
markAsSensitive: "Označit jako NSFW" markAsSensitive: "Označit jako NSFW"
@ -134,13 +141,18 @@ emojiUrl: "URL obrázku"
addEmoji: "Přidat emoji" addEmoji: "Přidat emoji"
settingGuide: "Doporučené nastavení" settingGuide: "Doporučené nastavení"
cacheRemoteFiles: "Ukládání vzdálených souborů do mezipaměti" cacheRemoteFiles: "Ukládání vzdálených souborů do mezipaměti"
cacheRemoteFilesDescription: "Zakázání tohoto nastavení způsobí, že vzdálené soubory budou odkazovány přímo, místo aby byly ukládány do mezipaměti. Tím se ušetří úložiště na serveru, ale zvýší se provoz, protože se negenerují miniatury." cacheRemoteFilesDescription: "Zakázání tohoto nastavení způsobí, že vzdálené soubory\
\ budou odkazovány přímo, místo aby byly ukládány do mezipaměti. Tím se ušetří úložiště\
\ na serveru, ale zvýší se provoz, protože se negenerují miniatury."
flagAsBot: "Tento účet je bot" flagAsBot: "Tento účet je bot"
flagAsBotDescription: "Pokud je tento účet kontrolován programem zaškrtněte tuto možnost. To označí tento účet jako bot pro ostatní vývojáře a zabrání tak nekonečným interakcím s ostatními boty a upraví Misskey systém aby se choval k tomuhle účtu jako bot." flagAsBotDescription: "Pokud je tento účet kontrolován programem zaškrtněte tuto možnost.\
\ To označí tento účet jako bot pro ostatní vývojáře a zabrání tak nekonečným interakcím\
\ s ostatními boty a upraví Misskey systém aby se choval k tomuhle účtu jako bot."
flagAsCat: "Tenhle účet je kočka" flagAsCat: "Tenhle účet je kočka"
flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako kočka." flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako kočka."
flagShowTimelineReplies: "Zobrazovat odpovědi na časové ose" flagShowTimelineReplies: "Zobrazovat odpovědi na časové ose"
flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazí odpovědi uživatelů na poznámky jiných uživatelů na vaší časové ose." flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazí odpovědi uživatelů na\
\ poznámky jiných uživatelů na vaší časové ose."
autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete" autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete"
addAccount: "Přidat účet" addAccount: "Přidat účet"
loginFailed: "Přihlášení se nezdařilo." loginFailed: "Přihlášení se nezdařilo."
@ -153,7 +165,10 @@ searchWith: "Hledat: {q}"
youHaveNoLists: "Nemáte žádné seznamy" youHaveNoLists: "Nemáte žádné seznamy"
followConfirm: "Jste si jisti, že chcete sledovat {name}?" followConfirm: "Jste si jisti, že chcete sledovat {name}?"
proxyAccount: "Proxy účet" proxyAccount: "Proxy účet"
proxyAccountDescription: "Proxy účet je účet, který za určitých podmínek sleduje uživatele na dálku vaším jménem. Například když uživatel zařadí vzdáleného uživatele do seznamu, pokud nikdo nesleduje uživatele na seznamu, aktivita nebude doručena instanci, takže místo toho bude uživatele sledovat účet proxy." proxyAccountDescription: "Proxy účet je účet, který za určitých podmínek sleduje uživatele\
\ na dálku vaším jménem. Například když uživatel zařadí vzdáleného uživatele do\
\ seznamu, pokud nikdo nesleduje uživatele na seznamu, aktivita nebude doručena\
\ instanci, takže místo toho bude uživatele sledovat účet proxy."
host: "Hostitel" host: "Hostitel"
selectUser: "Vyberte uživatele" selectUser: "Vyberte uživatele"
recipient: "Pro" recipient: "Pro"
@ -239,7 +254,8 @@ agreeTo: "Souhlasím s {0}"
tos: "Podmínky užívání" tos: "Podmínky užívání"
start: "Začít" start: "Začít"
home: "Domů" home: "Domů"
remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je ze vzdálené instance." remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je ze vzdálené\
\ instance."
activity: "Aktivita" activity: "Aktivita"
images: "Obrázky" images: "Obrázky"
birthday: "Datum narození" birthday: "Datum narození"
@ -548,7 +564,8 @@ info: "Informace"
unknown: "Neznámý" unknown: "Neznámý"
onlineStatus: "Online status" onlineStatus: "Online status"
hideOnlineStatus: "Skrýt Váš online status" hideOnlineStatus: "Skrýt Váš online status"
hideOnlineStatusDescription: "Skrytí vašeho online stavu může snížit funkcionalitu některých funkcí, například vyhledávání." hideOnlineStatusDescription: "Skrytí vašeho online stavu může snížit funkcionalitu\
\ některých funkcí, například vyhledávání."
online: "Online" online: "Online"
active: "Aktivní" active: "Aktivní"
offline: "Offline" offline: "Offline"
@ -928,3 +945,66 @@ _deck:
antenna: "Antény" antenna: "Antény"
list: "Seznamy" list: "Seznamy"
mentions: "Zmínění" mentions: "Zmínění"
noteDeleteConfirm: Chcete opravdu smazat tento příspěvek?
defaultValueIs: 'Výchozí: {value}'
lookup: Hledat
keepOriginalUploading: Ponechat originální obrázek
uploadFromUrlRequested: Vyžádáno nahrání souboru
manageGroups: Spravovat skupiny
reloadConfirm: Znovu načíst časovou osu?
driveCapacityPerRemoteAccount: Místo na disku pro vzdálené uživatele
silenceThisInstance: Ztlumit tuto instance
silencedInstances: Ztlumené instance
blockedInstancesDescription: Zadejte seznam domén instancí, jež chcete blokovat. Uvedené
instance nebudou moci s touto instancí komunikovat.
hiddenTags: Skryté hashtagy
noInstances: Nejsou zde žádné instance
silenced: Ztlumené
disablingTimelinesInfo: Administrátoři a moderátoři budou vždy mít přístup ke všem
časovým osám, i pokud jsou vypnuté.
deleted: Vymazáno
editNote: Upravit poznámku
edited: Upraveno
silencedInstancesDescription: Vypište hostnames instancí, které chcete ztlumit. Účty
v uvedených instancích jsou považovány za "ztlumené", mohou pouze zadávat požadavky
na sledování a nemohou zmiňovat místní účty, pokud nejsou sledovány. Na blokované
instance toto nebude mít vliv.
hiddenTagsDescription: 'Vypište hashtagy (bez #), které chcete skrýt před trendy a
prozkoumat. Skryté hashtagy jsou stále zjistitelné jinými způsoby. Blokované případy
nejsou ovlivněny, i když jsou zde uvedeny.'
circularReferenceFolder: Cílová složka je podsložka přesouvané složky.
whenServerDisconnected: Při ztrátě spojení se serverem
pinnedUsersDescription: Uveďte uživatelská jména uživatelů připnutých na stránce "Procházet",
jedno na řádek.
pinnedPagesDescription: Zadejte cesty ke stránkám, které chcete připnout na horní
stránku této instance, oddělené zlomy řádků.
pageLoadErrorDescription: Toto je obvykle způsobeno chybami sítě nebo mezipaměti prohlížeče.
Zkuste vymazat mezipaměť a po chvíli čekání to zkuste znovu.
emptyDrive: Váš disk je prázdný
inputNewDescription: Zadejte nový popisek
hasChildFilesOrFolders: Složka nemůže být smazána, protože není prázdná.
noThankYou: Ne, děkuji
addInstance: Přidat instance
selectInstance: Vybrat si instance
blockedUsers: Zablokovaní uživatelé
muteAndBlock: Ztlumení a blokace
noJobs: Žádné úlohy
federating: Federace
clearQueueConfirmText: Nedoručené příspěvky, které zůstanou ve frontě, nebudou federovány.
Obvykle tato operace není potřeba.
clearCachedFilesConfirm: Chcete opravdu vymazat mezipaměť všech vzdálených souborů?
accountMoved: 'Uživatel/ka se přesunul/a na nový účet:'
keepOriginalUploadingDescription: Ponechá originálně nahraný obrázek tak, jak je.
Pokud vypnuto, verze pro zobrazení na webu bude vygenerována při nahrání.
mutedUsers: Ztlumení uživatelé
enableRecommendedTimeline: Povolit doporučenou časovou osu
driveCapacityPerLocalAccount: Místo na disku pro místní uživatele
pinnedPages: Připnuté Stránky
directNotes: Přímé zprávy
enableEmojiReactions: Povolit reakce pomocí emoji
showEmojisInReactionNotifications: Zobrazit emotikony v oznámeních o reakcích
reactionSetting: Reakce, které se mají zobrazit v seznamu reakcí
renoteMute: Ztlumit přeposílání
renoteUnmute: Zrušit ztlumení přeposílání
flagSpeakAsCat: Mluvit jako kočka
flagSpeakAsCatDescription: Vaše příspěvky budou v kočičím režimu nyanifikovány.

File diff suppressed because it is too large Load diff

View file

@ -874,7 +874,7 @@ instanceSecurity: "Instance Security"
secureModeInfo: "When requesting from other instances, do not send back without proof." secureModeInfo: "When requesting from other instances, do not send back without proof."
privateMode: "Private Mode" privateMode: "Private Mode"
privateModeInfo: "When enabled, only whitelisted instances can federate with your\ privateModeInfo: "When enabled, only whitelisted instances can federate with your\
\ instances. All posts will be hidden from the public." \ instance. All posts will be hidden from the public."
allowedInstances: "Whitelisted Instances" allowedInstances: "Whitelisted Instances"
allowedInstancesDescription: "Hosts of instances to be whitelisted for federation,\ allowedInstancesDescription: "Hosts of instances to be whitelisted for federation,\
\ each separated by a new line (only applies in private mode)." \ each separated by a new line (only applies in private mode)."
@ -886,7 +886,6 @@ global: "Global"
recommended: "Recommended" recommended: "Recommended"
squareAvatars: "Display squared avatars" squareAvatars: "Display squared avatars"
seperateRenoteQuote: "Separate boost and quote buttons" seperateRenoteQuote: "Separate boost and quote buttons"
highlightCw: "Highlight content warned posts"
sent: "Sent" sent: "Sent"
received: "Received" received: "Received"
searchResult: "Search results" searchResult: "Search results"
@ -1078,8 +1077,15 @@ customKaTeXMacroDescription: "Set up macros to write mathematical expressions ea
\ supported; advanced syntax, such as conditional branching, cannot be used here." \ supported; advanced syntax, such as conditional branching, cannot be used here."
enableCustomKaTeXMacro: "Enable custom KaTeX macros" enableCustomKaTeXMacro: "Enable custom KaTeX macros"
noteId: "Post ID" noteId: "Post ID"
signupsDisabled: "Signups on this server are currently disabled, but you can always sign up at another server! If you have an invitation code for this server, please enter it below." signupsDisabled: "Signups on this server are currently disabled, but you can always\
\ sign up at another server! If you have an invitation code for this server, please\
\ enter it below."
findOtherInstance: "Find another server" findOtherInstance: "Find another server"
apps: "Apps"
sendModMail: "Send Moderation Notice"
preventAiLearning: "Prevent AI bot scraping"
preventAiLearningDescription: "Request third-party AI language models not to study content you upload, such as posts and images."
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing\ description: "Reduces the effort of server moderation through automatically recognizing\
@ -1187,6 +1193,10 @@ _nsfw:
ignore: "Don't hide NSFW media" ignore: "Don't hide NSFW media"
force: "Hide all media" force: "Hide all media"
_mfm: _mfm:
play: "Play MFM"
stop: "Stop MFM"
warn: "MFM may contain rapidly moving or flashy animations"
alwaysPlay: "Always autoplay all animated MFM"
cheatSheet: "MFM Cheatsheet" cheatSheet: "MFM Cheatsheet"
intro: "MFM is a markup language used on Misskey, Calckey, Akkoma, and more that\ intro: "MFM is a markup language used on Misskey, Calckey, Akkoma, and more that\
\ can be used in many places. Here you can view a list of all available MFM syntax." \ can be used in many places. Here you can view a list of all available MFM syntax."
@ -1998,20 +2008,3 @@ _deck:
list: "List" list: "List"
mentions: "Mentions" mentions: "Mentions"
direct: "Direct messages" direct: "Direct messages"
_apps:
apps: "Apps"
crossPlatform: "Cross platform"
mobile: "Mobile"
firstParty: "First party"
firstClass: "First class"
secondClass: "Second class"
thirdClass: "Third class"
free: "Free"
paid: "Paid"
pwa: "Install PWA"
kaiteki: "Kaiteki"
milktea: "Milktea"
missLi: "MissLi"
mona: "Mona"
theDesk: "TheDesk"
lesskey: "Lesskey"

View file

@ -14,7 +14,7 @@ ok: "OK"
gotIt: "¡Lo tengo!" gotIt: "¡Lo tengo!"
cancel: "Cancelar" cancel: "Cancelar"
enterUsername: "Introduce el nombre de usuario" enterUsername: "Introduce el nombre de usuario"
renotedBy: "Reposteado por {user}" renotedBy: "Impulsado por {user}"
noNotes: "No hay publicaciones" noNotes: "No hay publicaciones"
noNotifications: "No hay notificaciones" noNotifications: "No hay notificaciones"
instance: "Instancia" instance: "Instancia"
@ -46,7 +46,7 @@ copyLink: "Copiar enlace"
delete: "Borrar" delete: "Borrar"
deleteAndEdit: "Borrar y editar" deleteAndEdit: "Borrar y editar"
deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta publicación y editarla?\ deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta publicación y editarla?\
\ Perderás todas las reacciones, impulsados y respuestas." \ Perderás todas las reacciones, impulsos y respuestas."
addToList: "Agregar a lista" addToList: "Agregar a lista"
sendMessage: "Enviar un mensaje" sendMessage: "Enviar un mensaje"
copyUsername: "Copiar nombre de usuario" copyUsername: "Copiar nombre de usuario"
@ -342,7 +342,7 @@ dayX: "Día {day}"
monthX: "Mes {month}" monthX: "Mes {month}"
yearX: "Año {year}" yearX: "Año {year}"
pages: "Páginas" pages: "Páginas"
integration: "Integración" integration: "Integraciones"
connectService: "Conectar" connectService: "Conectar"
disconnectService: "Desconectar" disconnectService: "Desconectar"
enableLocalTimeline: "Habilitar linea de tiempo local" enableLocalTimeline: "Habilitar linea de tiempo local"
@ -366,7 +366,7 @@ pinnedPages: "Páginas fijadas"
pinnedPagesDescription: "Describa las rutas de las páginas que desea fijar a la página\ pinnedPagesDescription: "Describa las rutas de las páginas que desea fijar a la página\
\ principal de la instancia, separadas por lineas nuevas" \ principal de la instancia, separadas por lineas nuevas"
pinnedClipId: "Id del clip fijado" pinnedClipId: "Id del clip fijado"
pinnedNotes: "Nota fijada" pinnedNotes: "Publicación fijada"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
enableHcaptcha: "Habilitar hCaptcha" enableHcaptcha: "Habilitar hCaptcha"
hcaptchaSiteKey: "Clave del sitio" hcaptchaSiteKey: "Clave del sitio"
@ -386,14 +386,14 @@ antennaKeywords: "Palabras clave para recibir"
antennaExcludeKeywords: "Palabras clave para excluir" antennaExcludeKeywords: "Palabras clave para excluir"
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar\ antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar\
\ con una linea nueva es una declaración OR" \ con una linea nueva es una declaración OR"
notifyAntenna: "Notificar nueva nota" notifyAntenna: "Notificar nueva publicación"
withFileAntenna: "Sólo notas con archivos adjuntados" withFileAntenna: "Sólo publicaciones con archivos adjuntados"
enableServiceworker: "Activar ServiceWorker" enableServiceworker: "Activar ServiceWorker"
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva" antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
caseSensitive: "Distinguir mayúsculas de minúsculas" caseSensitive: "Distinguir mayúsculas de minúsculas"
withReplies: "Incluir respuestas" withReplies: "Incluir respuestas"
connectedTo: "Estas cuentas están conectadas" connectedTo: "Estas cuentas están conectadas"
notesAndReplies: "Notas y respuestas" notesAndReplies: "Publicaciones y respuestas"
withFiles: "Adjuntos" withFiles: "Adjuntos"
silence: "Silenciar" silence: "Silenciar"
silenceConfirm: "¿Desea silenciar al usuario?" silenceConfirm: "¿Desea silenciar al usuario?"
@ -430,7 +430,7 @@ notFoundDescription: "No se encontró la página correspondiente a la URL elegid
uploadFolder: "Carpeta de subidas por defecto" uploadFolder: "Carpeta de subidas por defecto"
cacheClear: "Borrar caché" cacheClear: "Borrar caché"
markAsReadAllNotifications: "Marcar todas las notificaciones como leídas" markAsReadAllNotifications: "Marcar todas las notificaciones como leídas"
markAsReadAllUnreadNotes: "Marcar todas las notas como leídas" markAsReadAllUnreadNotes: "Marcar todas las publicaciones como leídas"
markAsReadAllTalkMessages: "Marcar todos los chats como leídos" markAsReadAllTalkMessages: "Marcar todos los chats como leídos"
help: "Ayuda" help: "Ayuda"
inputMessageHere: "Escribe el mensaje aquí" inputMessageHere: "Escribe el mensaje aquí"
@ -451,7 +451,7 @@ text: "Texto"
enable: "Activar" enable: "Activar"
next: "Siguiente" next: "Siguiente"
retype: "Intentar de nuevo" retype: "Intentar de nuevo"
noteOf: "Notas de {user}" noteOf: "Publicaciones de {user}"
inviteToGroup: "Invitar al grupo" inviteToGroup: "Invitar al grupo"
quoteAttached: "Cita añadida" quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?" quoteQuestion: "¿Quiere añadir una cita?"
@ -511,8 +511,8 @@ accountSettings: "Ajustes de cuenta"
promotion: "Promovido" promotion: "Promovido"
promote: "Promover" promote: "Promover"
numberOfDays: "Cantidad de dias" numberOfDays: "Cantidad de dias"
hideThisNote: "Ocultar esta nota" hideThisNote: "Ocultar esta publicación"
showFeaturedNotesInTimeline: "Mostrar notas destacadas en la línea de tiempo" showFeaturedNotesInTimeline: "Mostrar publicaciones destacadas en la línea de tiempo"
objectStorage: "Almacenamiento de objetos" objectStorage: "Almacenamiento de objetos"
useObjectStorage: "Usar almacenamiento de objetos" useObjectStorage: "Usar almacenamiento de objetos"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
@ -542,7 +542,7 @@ objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir "
serverLogs: "Registros del servidor" serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos" deleteAll: "Eliminar todos"
showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo" showFixedPostForm: "Mostrar el formulario de las entradas encima de la línea de tiempo"
newNoteRecived: "Tienes una nota nuevo" newNoteRecived: "Tienes unas publicaciones nuevas"
sounds: "Sonidos" sounds: "Sonidos"
listen: "Escuchar" listen: "Escuchar"
none: "Ninguna" none: "Ninguna"
@ -575,7 +575,7 @@ deleteAllFiles: "Borrar todos los archivos"
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?" deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
removeAllFollowing: "Retener todos los siguientes" removeAllFollowing: "Retener todos los siguientes"
removeAllFollowingDescription: "Cancelar todos los siguientes del servidor {host}.\ removeAllFollowingDescription: "Cancelar todos los siguientes del servidor {host}.\
\ Ejecutar en caso de que esta instancia haya dejado de existir" \ Ejecutar en caso de que esta instancia haya dejado de existir."
userSuspended: "Este usuario ha sido suspendido." userSuspended: "Este usuario ha sido suspendido."
userSilenced: "Este usuario ha sido silenciado." userSilenced: "Este usuario ha sido silenciado."
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida" yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
@ -590,8 +590,8 @@ addRelay: "Agregar relé"
inboxUrl: "Inbox URL" inboxUrl: "Inbox URL"
addedRelays: "Relés añadidos" addedRelays: "Relés añadidos"
serviceworkerInfo: "Se necesita activar para usar las notificaciones push" serviceworkerInfo: "Se necesita activar para usar las notificaciones push"
deletedNote: "Nota eliminada" deletedNote: "Publicación eliminada"
invisibleNote: "Nota oculta" invisibleNote: "Publicación oculta"
enableInfiniteScroll: "Activar scroll infinito" enableInfiniteScroll: "Activar scroll infinito"
visibility: "Visibilidad" visibility: "Visibilidad"
poll: "Encuesta" poll: "Encuesta"
@ -1154,6 +1154,7 @@ _mfm:
plain: "Plano" plain: "Plano"
plainDescription: "Desactiva los efectos de todo el contenido MFM con este efecto\ plainDescription: "Desactiva los efectos de todo el contenido MFM con este efecto\
\ MFM." \ MFM."
position: Posición
_instanceTicker: _instanceTicker:
none: "No mostrar" none: "No mostrar"
remote: "Mostrar a usuarios remotos" remote: "Mostrar a usuarios remotos"
@ -1173,6 +1174,8 @@ _channel:
following: "Siguiendo" following: "Siguiendo"
usersCount: "{n} participantes" usersCount: "{n} participantes"
notesCount: "{n} notas" notesCount: "{n} notas"
nameOnly: Nombre solamente
nameAndDescription: Nombre y descripción
_menuDisplay: _menuDisplay:
sideFull: "Horizontal" sideFull: "Horizontal"
sideIcon: "Horizontal (ícono)" sideIcon: "Horizontal (ícono)"
@ -1900,18 +1903,6 @@ moveFrom: Mueve a esta cuenta de una cuenta antigua
moveFromLabel: 'La cuenta que estás moviendo de:' moveFromLabel: 'La cuenta que estás moviendo de:'
moveAccountDescription: '' moveAccountDescription: ''
license: Licencia license: Licencia
_apps:
apps: Aplicaciones
crossPlatform: Plataforma Cruzada
mobile: Teléfono móvil
secondClass: Segunda clase
lesskey: ''
firstClass: Primera clase
thirdClass: Tercera clase
theDesk: ''
pwa: Instalar PWA
free: Gratis
paid: Pagado
noThankYou: No gracias noThankYou: No gracias
userSaysSomethingReason: '{name} dijo {reason}' userSaysSomethingReason: '{name} dijo {reason}'
hiddenTags: Etiquetas Ocultas hiddenTags: Etiquetas Ocultas
@ -1928,3 +1919,12 @@ _messaging:
groups: Grupos groups: Grupos
dms: Privado dms: Privado
pushNotification: Notificaciones pushNotification: Notificaciones
apps: Aplicaciones
migration: Migración
silenced: Silenciado
deleted: Eliminado
edited: Editado
editNote: Editar nota
silenceThisInstance: Silenciar esta instancia
findOtherInstance: Buscar otro servidor
userSaysSomethingReasonRenote: '{name} impulsó una publicación que contiene {reason]'

View file

@ -70,7 +70,7 @@ export: "Exporter"
files: "Fichiers" files: "Fichiers"
download: "Télécharger" download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\"\ driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\"\
\ ? Les notes liées à ce fichier seront aussi supprimées." \ ? Il sera retiré de toutes ses notes liées."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?" unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre\ exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre\
\ un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive." \ un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive."
@ -1931,7 +1931,8 @@ flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat
hiddenTags: Hashtags cachés hiddenTags: Hashtags cachés
hiddenTagsDescription: "Lister les hashtags (sans le #) que vous souhaitez cacher\ hiddenTagsDescription: "Lister les hashtags (sans le #) que vous souhaitez cacher\
\ de tendances et explorer. Les hashtags cachés sont toujours découvrables par d'autres\ \ de tendances et explorer. Les hashtags cachés sont toujours découvrables par d'autres\
\ moyens." \ moyens. Les instances bloqués ne sont pas ne sont pas affectés, même si ils sont\
\ présent dans cette liste."
antennaInstancesDescription: Lister un hôte d'instance par ligne antennaInstancesDescription: Lister un hôte d'instance par ligne
userSaysSomethingReason: '{name} a dit {reason}' userSaysSomethingReason: '{name} a dit {reason}'
breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ? breakFollowConfirm: Êtes vous sur de vouloir retirer l'abonné ?
@ -2000,3 +2001,12 @@ customKaTeXMacroDescription: "Définissez des macros pour écrire des expression
\ Seulement de simples fonctions de substitution de chaines sont supportées; la\ \ Seulement de simples fonctions de substitution de chaines sont supportées; la\
\ syntaxe avancée, telle que la ramification conditionnelle, ne peut pas être utilisée\ \ syntaxe avancée, telle que la ramification conditionnelle, ne peut pas être utilisée\
\ ici." \ ici."
enableRecommendedTimeline: Activer la chronologie recommandée
silenceThisInstance: Ne plus montrer cet instance
silencedInstances: Instances silencieuses
silenced: Silencieux
deleted: Effacé
editNote: Modifier note
edited: Modifié
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
personnes aux publications des autres.

View file

@ -1,7 +1,9 @@
---
_lang_: "Bahasa Indonesia" _lang_: "Bahasa Indonesia"
headlineMisskey: "Jaringan terhubung melalui catatan" headlineMisskey: "Jaringan terhubung melalui catatan"
introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersifat sumber terbuka.\nMulailah menuliskan catatan, bagikan peristiwa terkini, serta ceritakan segala tentangmu.📡\nTunjukkan juga reaksimu pada catatan pengguna lain.👍\nMari jelajahi dunia baru🚀" introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersifat\
\ sumber terbuka.\nMulailah menuliskan catatan, bagikan peristiwa terkini, serta\
\ ceritakan segala tentangmu.\U0001F4E1\nTunjukkan juga reaksimu pada catatan pengguna\
\ lain.\U0001F44D\nMari jelajahi dunia baru\U0001F680"
monthAndDay: "{day} {month}" monthAndDay: "{day} {month}"
search: "Penelusuran" search: "Penelusuran"
notifications: "Pemberitahuan" notifications: "Pemberitahuan"
@ -44,7 +46,8 @@ copyContent: "Salin konten"
copyLink: "Salin tautan" copyLink: "Salin tautan"
delete: "Hapus" delete: "Hapus"
deleteAndEdit: "Hapus dan sunting" deleteAndEdit: "Hapus dan sunting"
deleteAndEditConfirm: "Apakah kamu yakin ingin menghapus note ini dan menyuntingnya? Kamu akan kehilangan semua reaksi, renote dan balasan di note ini." deleteAndEditConfirm: "Apakah kamu yakin ingin menghapus note ini dan menyuntingnya?\
\ Kamu akan kehilangan semua reaksi, renote dan balasan di note ini."
addToList: "Tambahkan ke daftar" addToList: "Tambahkan ke daftar"
sendMessage: "Kirim pesan" sendMessage: "Kirim pesan"
copyUsername: "Salin nama pengguna" copyUsername: "Salin nama pengguna"
@ -66,7 +69,8 @@ files: "Berkas"
download: "Unduh" download: "Unduh"
driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus." driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus."
unfollowConfirm: "Berhenti mengikuti {name}?" unfollowConfirm: "Berhenti mengikuti {name}?"
exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Setelah ekspor selesai, berkas yang dihasilkan akan ditambahkan ke Drive" exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Setelah\
\ ekspor selesai, berkas yang dihasilkan akan ditambahkan ke Drive"
importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat." importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
lists: "Daftar" lists: "Daftar"
noLists: "Kamu tidak memiliki daftar apapun" noLists: "Kamu tidak memiliki daftar apapun"
@ -81,9 +85,12 @@ error: "Galat"
somethingHappened: "Terjadi kesalahan" somethingHappened: "Terjadi kesalahan"
retry: "Coba lagi" retry: "Coba lagi"
pageLoadError: "Gagal memuat halaman." pageLoadError: "Gagal memuat halaman."
pageLoadErrorDescription: "Umumnya disebabkan jaringan atau tembolok perambah. Cobalah bersihkan tembolok peramban lalu tunggu sesaat sebelum mencoba kembali." pageLoadErrorDescription: "Umumnya disebabkan jaringan atau tembolok perambah. Cobalah\
serverIsDead: "Tidak ada respon dari peladen. Mohon tunggu dan coba beberapa saat lagi." \ bersihkan tembolok peramban lalu tunggu sesaat sebelum mencoba kembali."
youShouldUpgradeClient: "Untuk melihat halaman ini, mohon muat ulang untuk memutakhirkan klienmu." serverIsDead: "Tidak ada respon dari peladen. Mohon tunggu dan coba beberapa saat\
\ lagi."
youShouldUpgradeClient: "Untuk melihat halaman ini, mohon muat ulang untuk memutakhirkan\
\ klienmu."
enterListName: "Masukkan nama daftar" enterListName: "Masukkan nama daftar"
privacy: "Privasi" privacy: "Privasi"
makeFollowManuallyApprove: "Permintaan mengikuti membutuhkan persetujuan" makeFollowManuallyApprove: "Permintaan mengikuti membutuhkan persetujuan"
@ -108,7 +115,8 @@ sensitive: "Konten sensitif"
add: "Tambahkan" add: "Tambahkan"
reaction: "Reaksi" reaction: "Reaksi"
reactionSetting: "Reaksi untuk dimunculkan di bilah reaksi" reactionSetting: "Reaksi untuk dimunculkan di bilah reaksi"
reactionSettingDescription2: "Geser untuk memindah urutkan, klik untuk menghapus, tekan \"+\" untuk menambahkan" reactionSettingDescription2: "Geser untuk memindah urutkan, klik untuk menghapus,\
\ tekan \"+\" untuk menambahkan"
rememberNoteVisibility: "Ingat pengaturan visibilitas catatan" rememberNoteVisibility: "Ingat pengaturan visibilitas catatan"
attachCancel: "Hapus lampiran" attachCancel: "Hapus lampiran"
markAsSensitive: "Tandai sebagai konten sensitif" markAsSensitive: "Tandai sebagai konten sensitif"
@ -137,14 +145,22 @@ emojiUrl: "URL Emoji"
addEmoji: "Tambahkan emoji" addEmoji: "Tambahkan emoji"
settingGuide: "Pengaturan rekomendasi" settingGuide: "Pengaturan rekomendasi"
cacheRemoteFiles: "Tembolokkan berkas remote" cacheRemoteFiles: "Tembolokkan berkas remote"
cacheRemoteFilesDescription: "Ketika pengaturan ini dinonaktifkan, berkas luar akan dimuat langsung dari instansi luar. Menonaktifkan ini akan mengurangi penggunaan penyimpanan, namun dapat menyebabkan meningkatkan lalu lintas bandwidth, karena thumbnail tidak dihasilkan." cacheRemoteFilesDescription: "Ketika pengaturan ini dinonaktifkan, berkas luar akan\
\ dimuat langsung dari instansi luar. Menonaktifkan ini akan mengurangi penggunaan\
\ penyimpanan, namun dapat menyebabkan meningkatkan lalu lintas bandwidth, karena\
\ thumbnail tidak dihasilkan."
flagAsBot: "Atur akun ini sebagai Bot" flagAsBot: "Atur akun ini sebagai Bot"
flagAsBotDescription: "Jika akun ini dikendalikan oleh program, tetapkanlah opsi ini. Jika diaktifkan, ini akan berfungsi sebagai tanda bagi pengembang lain untuk mencegah interaksi berantai dengan bot lain dan menyesuaikan sistem internal Misskey untuk memperlakukan akun ini sebagai bot." flagAsBotDescription: "Jika akun ini dikendalikan oleh program, tetapkanlah opsi ini.\
\ Jika diaktifkan, ini akan berfungsi sebagai tanda bagi pengembang lain untuk mencegah\
\ interaksi berantai dengan bot lain dan menyesuaikan sistem internal Misskey untuk\
\ memperlakukan akun ini sebagai bot."
flagAsCat: "Atur akun ini sebagai kucing" flagAsCat: "Atur akun ini sebagai kucing"
flagAsCatDescription: "Nyalakan tanda ini untuk menandai akun ini sebagai kucing." flagAsCatDescription: "Nyalakan tanda ini untuk menandai akun ini sebagai kucing."
flagShowTimelineReplies: "Tampilkan balasan di linimasa" flagShowTimelineReplies: "Tampilkan balasan di linimasa"
flagShowTimelineRepliesDescription: "Menampilkan balasan pengguna dari note pengguna lain di linimasa apabila dinyalakan." flagShowTimelineRepliesDescription: "Menampilkan balasan pengguna dari note pengguna\
autoAcceptFollowed: "Setujui otomatis permintaan mengikuti dari pengguna yang kamu ikuti" \ lain di linimasa apabila dinyalakan."
autoAcceptFollowed: "Setujui otomatis permintaan mengikuti dari pengguna yang kamu\
\ ikuti"
addAccount: "Tambahkan akun" addAccount: "Tambahkan akun"
loginFailed: "Gagal untuk masuk" loginFailed: "Gagal untuk masuk"
showOnRemote: "Lihat profil asli" showOnRemote: "Lihat profil asli"
@ -156,7 +172,11 @@ searchWith: "Cari: {q}"
youHaveNoLists: "Kamu tidak memiliki daftar apapun" youHaveNoLists: "Kamu tidak memiliki daftar apapun"
followConfirm: "Apakah kamu yakin ingin mengikuti {name}?" followConfirm: "Apakah kamu yakin ingin mengikuti {name}?"
proxyAccount: "Akun proksi" proxyAccount: "Akun proksi"
proxyAccountDescription: "Akun proksi merupakan sebuah akun yang bertindak sebagai pengikut luar untuk pengguna dalam kondisi tertentu. Sebagai contoh, ketika pengguna menambahkan seorang pengguna luar ke dalam daftar, aktivitas dari pengguna luar tidak akan disampaikan ke instansi apabila tidak ada pengguna lokal yang mengikuti pengguna tersebut, dengan begitu akun proksilah yang akan mengikutinya." proxyAccountDescription: "Akun proksi merupakan sebuah akun yang bertindak sebagai\
\ pengikut luar untuk pengguna dalam kondisi tertentu. Sebagai contoh, ketika pengguna\
\ menambahkan seorang pengguna luar ke dalam daftar, aktivitas dari pengguna luar\
\ tidak akan disampaikan ke instansi apabila tidak ada pengguna lokal yang mengikuti\
\ pengguna tersebut, dengan begitu akun proksilah yang akan mengikutinya."
host: "Host" host: "Host"
selectUser: "Pilih pengguna" selectUser: "Pilih pengguna"
recipient: "Penerima" recipient: "Penerima"
@ -187,11 +207,15 @@ instanceInfo: "Informasi Instansi"
statistics: "Statistik" statistics: "Statistik"
clearQueue: "Bersihkan antrian" clearQueue: "Bersihkan antrian"
clearQueueConfirmTitle: "Apakah kamu yakin ingin membersihkan antrian?" clearQueueConfirmTitle: "Apakah kamu yakin ingin membersihkan antrian?"
clearQueueConfirmText: "Seluruh sisa catatan yang tidak tersampaikan di dalam antrian tidak akan difederasi. Biasanya operasi ini TIDAK dibutuhkan." clearQueueConfirmText: "Seluruh sisa catatan yang tidak tersampaikan di dalam antrian\
\ tidak akan difederasi. Biasanya operasi ini TIDAK dibutuhkan."
clearCachedFiles: "Hapus tembolok" clearCachedFiles: "Hapus tembolok"
clearCachedFilesConfirm: "Apakah kamu yakin ingin menghapus seluruh tembolok berkas remote?" clearCachedFilesConfirm: "Apakah kamu yakin ingin menghapus seluruh tembolok berkas\
\ remote?"
blockedInstances: "Instansi terblokir" blockedInstances: "Instansi terblokir"
blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi ini." blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk\
\ diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi\
\ ini."
muteAndBlock: "Bisukan / Blokir" muteAndBlock: "Bisukan / Blokir"
mutedUsers: "Pengguna yang dibisukan" mutedUsers: "Pengguna yang dibisukan"
blockedUsers: "Pengguna yang diblokir" blockedUsers: "Pengguna yang diblokir"
@ -239,7 +263,8 @@ saved: "Telah disimpan"
messaging: "Pesan" messaging: "Pesan"
upload: "Unggah" upload: "Unggah"
keepOriginalUploading: "Simpan gambar asli" keepOriginalUploading: "Simpan gambar asli"
keepOriginalUploadingDescription: "Simpan gambar yang diunggah sebagaimana gambar aslinya. Bila dimatikan, versi tampilan web akan dihasilkan pada saat diunggah." keepOriginalUploadingDescription: "Simpan gambar yang diunggah sebagaimana gambar\
\ aslinya. Bila dimatikan, versi tampilan web akan dihasilkan pada saat diunggah."
fromDrive: "Dari Drive" fromDrive: "Dari Drive"
fromUrl: "Dari URL" fromUrl: "Dari URL"
uploadFromUrl: "Unggah dari URL" uploadFromUrl: "Unggah dari URL"
@ -255,7 +280,8 @@ agreeTo: "Saya setuju kepada {0}"
tos: "Syarat dan ketentuan" tos: "Syarat dan ketentuan"
start: "Mulai" start: "Mulai"
home: "Beranda" home: "Beranda"
remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar." remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal\
\ dari instansi luar."
activity: "Aktivitas" activity: "Aktivitas"
images: "Gambar" images: "Gambar"
birthday: "Tanggal lahir" birthday: "Tanggal lahir"
@ -288,7 +314,8 @@ unableToDelete: "Tidak dapat menghapus"
inputNewFileName: "Masukkan nama berkas yang baru" inputNewFileName: "Masukkan nama berkas yang baru"
inputNewDescription: "Masukkan keterangan disini" inputNewDescription: "Masukkan keterangan disini"
inputNewFolderName: "Masukkan nama folder yang baru" inputNewFolderName: "Masukkan nama folder yang baru"
circularReferenceFolder: "Folder tujuan adalah subfolder dari folder yang ingin kamu pindahkan." circularReferenceFolder: "Folder tujuan adalah subfolder dari folder yang ingin kamu\
\ pindahkan."
hasChildFilesOrFolders: "Karena folder ini tidak kosong, maka tidak dapat dihapus." hasChildFilesOrFolders: "Karena folder ini tidak kosong, maka tidak dapat dihapus."
copyUrl: "Salin tautan" copyUrl: "Salin tautan"
rename: "Ubah nama" rename: "Ubah nama"
@ -322,7 +349,8 @@ connectService: "Sambungkan"
disconnectService: "Putuskan" disconnectService: "Putuskan"
enableLocalTimeline: "Nyalakan linimasa lokal" enableLocalTimeline: "Nyalakan linimasa lokal"
enableGlobalTimeline: "Nyalakan linimasa global" enableGlobalTimeline: "Nyalakan linimasa global"
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa meskipun linimasa tersebut tidak diaktifkan." disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa\
\ meskipun linimasa tersebut tidak diaktifkan."
registration: "Pendaftaran" registration: "Pendaftaran"
enableRegistration: "Nyalakan pendaftaran pengguna baru" enableRegistration: "Nyalakan pendaftaran pengguna baru"
invite: "Undang" invite: "Undang"
@ -334,9 +362,11 @@ bannerUrl: "URL Banner"
backgroundImageUrl: "URL Gambar latar" backgroundImageUrl: "URL Gambar latar"
basicInfo: "Informasi Umum" basicInfo: "Informasi Umum"
pinnedUsers: "Pengguna yang disematkan" pinnedUsers: "Pengguna yang disematkan"
pinnedUsersDescription: "Tuliskan satu nama pengguna dalam satu baris. Pengguna yang dituliskan disini akan disematkan dalam bilah \"Jelajahi\"." pinnedUsersDescription: "Tuliskan satu nama pengguna dalam satu baris. Pengguna yang\
\ dituliskan disini akan disematkan dalam bilah \"Jelajahi\"."
pinnedPages: "Halaman yang disematkan" pinnedPages: "Halaman yang disematkan"
pinnedPagesDescription: "Masukkan tautan dari halaman yang kamu ingin sematkan ke halaman utama dari instansi ini, dipisah dengan membuat baris baru." pinnedPagesDescription: "Masukkan tautan dari halaman yang kamu ingin sematkan ke\
\ halaman utama dari instansi ini, dipisah dengan membuat baris baru."
pinnedClipId: "ID dari klip yang disematkan" pinnedClipId: "ID dari klip yang disematkan"
pinnedNotes: "Catatan yang disematkan" pinnedNotes: "Catatan yang disematkan"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -347,14 +377,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Nyalakan reCAPTCHA" enableRecaptcha: "Nyalakan reCAPTCHA"
recaptchaSiteKey: "Site key" recaptchaSiteKey: "Site key"
recaptchaSecretKey: "Secret Key" recaptchaSecretKey: "Secret Key"
avoidMultiCaptchaConfirm: "Menggunakan banyak Captcha dapat menyebabkan gangguan. Apakah kamu ingin untuk menonaktifkan Captcha yang lain? Kamu dapat membiarkan fitur ini tetap aktif dengan menekan tombol batal." avoidMultiCaptchaConfirm: "Menggunakan banyak Captcha dapat menyebabkan gangguan.\
\ Apakah kamu ingin untuk menonaktifkan Captcha yang lain? Kamu dapat membiarkan\
\ fitur ini tetap aktif dengan menekan tombol batal."
antennas: "Antena" antennas: "Antena"
manageAntennas: "Pengelola Antena" manageAntennas: "Pengelola Antena"
name: "Nama" name: "Nama"
antennaSource: "Sumber Antenna" antennaSource: "Sumber Antenna"
antennaKeywords: "Kata kunci yang diterima" antennaKeywords: "Kata kunci yang diterima"
antennaExcludeKeywords: "Kata kunci yang dikecualikan" antennaExcludeKeywords: "Kata kunci yang dikecualikan"
antennaKeywordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan baris baru untuk kondisi OR." antennaKeywordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan\
\ baris baru untuk kondisi OR."
notifyAntenna: "Beritahu untuk catatan baru" notifyAntenna: "Beritahu untuk catatan baru"
withFileAntenna: "Hanya tampilkan catatan dengan berkas yang dilampirkan" withFileAntenna: "Hanya tampilkan catatan dengan berkas yang dilampirkan"
enableServiceworker: "Aktifkan ServiceWorker" enableServiceworker: "Aktifkan ServiceWorker"
@ -441,7 +474,8 @@ strongPassword: "Kata sandi kuat"
passwordMatched: "Kata sandi sama" passwordMatched: "Kata sandi sama"
passwordNotMatched: "Kata sandi tidak sama" passwordNotMatched: "Kata sandi tidak sama"
signinWith: "Masuk dengan {x}" signinWith: "Masuk dengan {x}"
signinFailed: "Tidak dapat masuk. Nama pengguna atau kata sandi yang kamu masukkan salah." signinFailed: "Tidak dapat masuk. Nama pengguna atau kata sandi yang kamu masukkan\
\ salah."
tapSecurityKey: "Ketuk kunci keamanan kamu" tapSecurityKey: "Ketuk kunci keamanan kamu"
or: "atau" or: "atau"
language: "Bahasa" language: "Bahasa"
@ -482,19 +516,29 @@ showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di linimasa"
objectStorage: "Object Storage" objectStorage: "Object Storage"
useObjectStorage: "Gunakan object storage" useObjectStorage: "Gunakan object storage"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengkonstruksi URL ke object (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy, jika tidak tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan yang akan kamu gunakan, contohnya. 'https://<bucket>.s3.amazonaws.com' untuk AWS S3, dan 'https://storage.googleapis.com/<bucket>' untuk GCS." objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengkonstruksi URL ke object\
\ (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy, jika tidak\
\ tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan\
\ yang akan kamu gunakan, contohnya. 'https://<bucket>.s3.amazonaws.com' untuk AWS\
\ S3, dan 'https://storage.googleapis.com/<bucket>' untuk GCS."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang telah dikonfigurasi." objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang\
\ telah dikonfigurasi."
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Berkas tidak akan disimpan dalam direktori dari prefix ini." objectStoragePrefixDesc: "Berkas tidak akan disimpan dalam direktori dari prefix ini."
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Kosongkan bagian ini jika kamu menggunakan AWS S3, jika tidak tentukan endpoint sebagai '<host>' atau '<host>:<port>' sesuai dengan panduan dari layanan yang akan kamu gunakan." objectStorageEndpointDesc: "Kosongkan bagian ini jika kamu menggunakan AWS S3, jika\
\ tidak tentukan endpoint sebagai '<host>' atau '<host>:<port>' sesuai dengan panduan\
\ dari layanan yang akan kamu gunakan."
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "Tentukan region seperti 'xx-east-1'. Jika layanan kamu tidak memiliki perbedaan mengenai region, kosongkan saja atau isi dengan 'us-east-1'." objectStorageRegionDesc: "Tentukan region seperti 'xx-east-1'. Jika layanan kamu tidak\
\ memiliki perbedaan mengenai region, kosongkan saja atau isi dengan 'us-east-1'."
objectStorageUseSSL: "Gunakan SSL" objectStorageUseSSL: "Gunakan SSL"
objectStorageUseSSLDesc: "Matikan ini jika kamu tidak akan menggunakan HTTPS untuk koneksi API" objectStorageUseSSLDesc: "Matikan ini jika kamu tidak akan menggunakan HTTPS untuk\
\ koneksi API"
objectStorageUseProxy: "Hubungkan melalui Proxy" objectStorageUseProxy: "Hubungkan melalui Proxy"
objectStorageUseProxyDesc: "Matikan ini jika kamu tidak akan menggunakan Proxy untuk koneksi ObjectStorage" objectStorageUseProxyDesc: "Matikan ini jika kamu tidak akan menggunakan Proxy untuk\
\ koneksi ObjectStorage"
objectStorageSetPublicRead: "Setel \"public-read\" disaat mengunggah" objectStorageSetPublicRead: "Setel \"public-read\" disaat mengunggah"
serverLogs: "Log Peladen" serverLogs: "Log Peladen"
deleteAll: "Hapus semua" deleteAll: "Hapus semua"
@ -522,7 +566,9 @@ sort: "Urutkan"
ascendingOrder: "Urutkan naik" ascendingOrder: "Urutkan naik"
descendingOrder: "Urutkan menurun" descendingOrder: "Urutkan menurun"
scratchpad: "Scratchpad" scratchpad: "Scratchpad"
scratchpadDescription: "Scratchpad menyediakan lingkungan eksperimen untuk AiScript. Kamu bisa menulis, mengeksuksi, serta mengecek hasil yang berinteraksi dengan Misskey." scratchpadDescription: "Scratchpad menyediakan lingkungan eksperimen untuk AiScript.\
\ Kamu bisa menulis, mengeksuksi, serta mengecek hasil yang berinteraksi dengan\
\ Misskey."
output: "Keluaran" output: "Keluaran"
script: "Script" script: "Script"
disablePagesScript: "Nonaktifkan script pada halaman" disablePagesScript: "Nonaktifkan script pada halaman"
@ -530,11 +576,14 @@ updateRemoteUser: "Perbaharui informasi pengguna luar"
deleteAllFiles: "Hapus semua berkas" deleteAllFiles: "Hapus semua berkas"
deleteAllFilesConfirm: "Apakah kamu yakin ingin menghapus semua berkas?" deleteAllFilesConfirm: "Apakah kamu yakin ingin menghapus semua berkas?"
removeAllFollowing: "Tahan semua mengikuti" removeAllFollowing: "Tahan semua mengikuti"
removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi." removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan\
\ ini ketika instansi sudah tidak ada lagi."
userSuspended: "Pengguna ini telah dibekukan." userSuspended: "Pengguna ini telah dibekukan."
userSilenced: "Pengguna ini telah dibungkam." userSilenced: "Pengguna ini telah dibungkam."
yourAccountSuspendedTitle: "Akun ini dibekukan" yourAccountSuspendedTitle: "Akun ini dibekukan"
yourAccountSuspendedDescription: "Akun ini dibekukan karena melanggar ketentuan penggunaan layanan peladen atau semacamnya. Hubungi admin apabila ingin tahu alasan lebih lanjut. Mohon untuk tidak membuat akun baru." yourAccountSuspendedDescription: "Akun ini dibekukan karena melanggar ketentuan penggunaan\
\ layanan peladen atau semacamnya. Hubungi admin apabila ingin tahu alasan lebih\
\ lanjut. Mohon untuk tidak membuat akun baru."
menu: "Menu" menu: "Menu"
divider: "Pembagi" divider: "Pembagi"
addItem: "Tambahkan item" addItem: "Tambahkan item"
@ -579,7 +628,8 @@ notificationType: "Jenis pemberitahuan"
edit: "Sunting" edit: "Sunting"
emailServer: "Peladen surel" emailServer: "Peladen surel"
enableEmail: "Nyalakan distribusi surel" enableEmail: "Nyalakan distribusi surel"
emailConfigInfo: "Digunakan untuk mengonfirmasi surel kamu disaat mendaftar dan lupa kata sandi" emailConfigInfo: "Digunakan untuk mengonfirmasi surel kamu disaat mendaftar dan lupa\
\ kata sandi"
email: "Surel" email: "Surel"
emailAddress: "Alamat surel" emailAddress: "Alamat surel"
smtpConfig: "Konfigurasi peladen SMTP" smtpConfig: "Konfigurasi peladen SMTP"
@ -587,13 +637,15 @@ smtpHost: "Host"
smtpPort: "Port" smtpPort: "Port"
smtpUser: "Nama Pengguna" smtpUser: "Nama Pengguna"
smtpPass: "Kata sandi" smtpPass: "Kata sandi"
emptyToDisableSmtpAuth: "Kosongkan nama pengguna dan kata sandi untuk menonaktifkan verifikasi SMTP" emptyToDisableSmtpAuth: "Kosongkan nama pengguna dan kata sandi untuk menonaktifkan\
\ verifikasi SMTP"
smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP" smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP"
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS" smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
testEmail: "Tes pengiriman surel" testEmail: "Tes pengiriman surel"
wordMute: "Bisukan kata" wordMute: "Bisukan kata"
regexpError: "Kesalahan ekspresi reguler" regexpError: "Kesalahan ekspresi reguler"
regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:" regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab}\
\ kata yang dibisukan:"
instanceMute: "Bisuka instansi" instanceMute: "Bisuka instansi"
userSaysSomething: "{name} mengatakan sesuatu" userSaysSomething: "{name} mengatakan sesuatu"
makeActive: "Aktifkan" makeActive: "Aktifkan"
@ -609,30 +661,37 @@ create: "Buat"
notificationSetting: "Pengaturan Pemberitahuan" notificationSetting: "Pengaturan Pemberitahuan"
notificationSettingDesc: "Pilih tipe pemberitahuan untuk ditampilkan" notificationSettingDesc: "Pilih tipe pemberitahuan untuk ditampilkan"
useGlobalSetting: "Gunakan setelan global" useGlobalSetting: "Gunakan setelan global"
useGlobalSettingDesc: "Jika dinyalakan, setelan pemberitahuan akun kamu akan digunakan. Jika dimatikan, konfigurasi secara individu dapat dibuat." useGlobalSettingDesc: "Jika dinyalakan, setelan pemberitahuan akun kamu akan digunakan.\
\ Jika dimatikan, konfigurasi secara individu dapat dibuat."
other: "Lainnya" other: "Lainnya"
regenerateLoginToken: "Perbarui token login" regenerateLoginToken: "Perbarui token login"
regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan dilogout." regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat\
setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya menggunakan spasi." \ login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan\
\ dilogout."
setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya\
\ menggunakan spasi."
fileIdOrUrl: "File-ID atau URL" fileIdOrUrl: "File-ID atau URL"
behavior: "Perilaku" behavior: "Perilaku"
sample: "Contoh" sample: "Contoh"
abuseReports: "Laporkan" abuseReports: "Laporkan"
reportAbuse: "Laporkan" reportAbuse: "Laporkan"
reportAbuseOf: "Laporkan {name}" reportAbuseOf: "Laporkan {name}"
fillAbuseReportDescription: "Mohon isi rincian laporan. Jika laporan ini mengenai catatan yang spesifik, mohon lampirkan serta URL catatan tersebut." fillAbuseReportDescription: "Mohon isi rincian laporan. Jika laporan ini mengenai\
\ catatan yang spesifik, mohon lampirkan serta URL catatan tersebut."
abuseReported: "Laporan kamu telah dikirimkan. Terima kasih." abuseReported: "Laporan kamu telah dikirimkan. Terima kasih."
reporter: "Pelapor" reporter: "Pelapor"
reporteeOrigin: "Yang dilaporkan" reporteeOrigin: "Yang dilaporkan"
reporterOrigin: "Pelapor" reporterOrigin: "Pelapor"
forwardReport: "Teruskan laporan ke instansi luar" forwardReport: "Teruskan laporan ke instansi luar"
forwardReportIsAnonymous: "Untuk melindungi privasi akun kamu, akun anonim dari sistem akan digunakan sebagai pelapor pada instansi luar." forwardReportIsAnonymous: "Untuk melindungi privasi akun kamu, akun anonim dari sistem\
\ akan digunakan sebagai pelapor pada instansi luar."
send: "Kirim" send: "Kirim"
abuseMarkAsResolved: "Tandai laporan sebagai selesai" abuseMarkAsResolved: "Tandai laporan sebagai selesai"
openInNewTab: "Buka di tab baru" openInNewTab: "Buka di tab baru"
openInSideView: "Buka di tampilan samping" openInSideView: "Buka di tampilan samping"
defaultNavigationBehaviour: "Navigasi bawaan" defaultNavigationBehaviour: "Navigasi bawaan"
editTheseSettingsMayBreakAccount: "Menyunting pengaturan ini memiliki kemungkinan untuk merusak akun kamu." editTheseSettingsMayBreakAccount: "Menyunting pengaturan ini memiliki kemungkinan\
\ untuk merusak akun kamu."
instanceTicker: "Informasi pengguna pada instansi" instanceTicker: "Informasi pengguna pada instansi"
waitingFor: "Menunggu untuk {x}" waitingFor: "Menunggu untuk {x}"
random: "Acak" random: "Acak"
@ -644,9 +703,11 @@ createNew: "Buat baru"
optional: "Opsional" optional: "Opsional"
createNewClip: "Buat klip baru" createNewClip: "Buat klip baru"
unclip: "Batalkan klip" unclip: "Batalkan klip"
confirmToUnclipAlreadyClippedNote: "Catatan ini sudah disertakan di klip \"{name}\". Yakin ingin membatalkan catatan dari klip ini?" confirmToUnclipAlreadyClippedNote: "Catatan ini sudah disertakan di klip \"{name}\"\
. Yakin ingin membatalkan catatan dari klip ini?"
public: "Publik" public: "Publik"
i18nInfo: "Calckey diterjemahkan ke dalam banyak bahasa oleh sukarelawan. Kamu dapat ikut membantu di {link}." i18nInfo: "Calckey diterjemahkan ke dalam banyak bahasa oleh sukarelawan. Kamu dapat\
\ ikut membantu di {link}."
manageAccessTokens: "Kelola access token" manageAccessTokens: "Kelola access token"
accountInfo: "Informasi akun" accountInfo: "Informasi akun"
notesCount: "Jumlah catatan" notesCount: "Jumlah catatan"
@ -665,12 +726,16 @@ no: "Tidak"
driveFilesCount: "Jumlah berkas drive" driveFilesCount: "Jumlah berkas drive"
driveUsage: "Penggunaan ruang penyimpanan drive" driveUsage: "Penggunaan ruang penyimpanan drive"
noCrawle: "Tolak pengindeksan crawler" noCrawle: "Tolak pengindeksan crawler"
noCrawleDescription: "Meminta mesin pencari untuk tidak mengindeks halaman profil kamu, catatan, Halaman, dll." noCrawleDescription: "Meminta mesin pencari untuk tidak mengindeks halaman profil\
lockedAccountInfo: "Kecuali kamu menyetel visibilitas catatan milikmu ke \"Hanya pengikut\", catatan milikmu akan dapat dilihat oleh siapa saja, bahkan jika kamu memerlukan pengikut untuk disetujui secara manual." \ kamu, catatan, Halaman, dll."
lockedAccountInfo: "Kecuali kamu menyetel visibilitas catatan milikmu ke \"Hanya pengikut\"\
, catatan milikmu akan dapat dilihat oleh siapa saja, bahkan jika kamu memerlukan\
\ pengikut untuk disetujui secara manual."
alwaysMarkSensitive: "Tandai media dalam catatan sebagai media sensitif" alwaysMarkSensitive: "Tandai media dalam catatan sebagai media sensitif"
loadRawImages: "Tampilkan lampiran gambar secara penuh daripada thumbnail" loadRawImages: "Tampilkan lampiran gambar secara penuh daripada thumbnail"
disableShowingAnimatedImages: "Jangan mainkan gambar bergerak" disableShowingAnimatedImages: "Jangan mainkan gambar bergerak"
verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang telah disertakan untuk menyelesaikan verifikasi." verificationEmailSent: "Surel verifikasi telah dikirimkan. Mohon akses tautan yang\
\ telah disertakan untuk menyelesaikan verifikasi."
notSet: "Tidak disetel" notSet: "Tidak disetel"
emailVerified: "Surel telah diverifikasi" emailVerified: "Surel telah diverifikasi"
noteFavoritesCount: "Jumlah catatan yang difavoritkan" noteFavoritesCount: "Jumlah catatan yang difavoritkan"
@ -682,14 +747,16 @@ clips: "Klip"
experimentalFeatures: "Fitur eksperimental" experimentalFeatures: "Fitur eksperimental"
developer: "Pengembang" developer: "Pengembang"
makeExplorable: "Buat akun tampil di \"Jelajahi\"" makeExplorable: "Buat akun tampil di \"Jelajahi\""
makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di bagian \"Jelajahi:" makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di\
\ bagian \"Jelajahi:"
showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa" showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa"
duplicate: "Duplikat" duplicate: "Duplikat"
left: "Kiri" left: "Kiri"
center: "Tengah" center: "Tengah"
wide: "Lebar" wide: "Lebar"
narrow: "Sempit" narrow: "Sempit"
reloadToApplySetting: "Pengaturan ini akan diterapkan saat memuat halaman kembali. Apakah kamu ingin memuat halaman kembali sekarang?" reloadToApplySetting: "Pengaturan ini akan diterapkan saat memuat halaman kembali.\
\ Apakah kamu ingin memuat halaman kembali sekarang?"
needReloadToApply: "Pengaturan ini hanya akan diterapkan setelah memuat ulang halaman." needReloadToApply: "Pengaturan ini hanya akan diterapkan setelah memuat ulang halaman."
showTitlebar: "Tampilkan bilah judul" showTitlebar: "Tampilkan bilah judul"
clearCache: "Hapus tembolok" clearCache: "Hapus tembolok"
@ -697,7 +764,10 @@ onlineUsersCount: "{n} orang sedang daring"
nUsers: "{n} Pengguna" nUsers: "{n} Pengguna"
nNotes: "{n} Catatan" nNotes: "{n} Catatan"
sendErrorReports: "Kirim laporan kesalahan" sendErrorReports: "Kirim laporan kesalahan"
sendErrorReportsDescription: "Ketika dinyalakan, informasi kesalahan rinci akan dibagikan dengan Misskey ketika masalah terjadi, hal ini untuk membantu kualitas Misskey. Fitur ini memungkinkan memuat informasi seperti sistem operasi yang kamu gunakan dan versinya, aplikasi peramban yang kamu gunakan, riwayat aktivitas kamu, dll." sendErrorReportsDescription: "Ketika dinyalakan, informasi kesalahan rinci akan dibagikan\
\ dengan Misskey ketika masalah terjadi, hal ini untuk membantu kualitas Misskey.\
\ Fitur ini memungkinkan memuat informasi seperti sistem operasi yang kamu gunakan\
\ dan versinya, aplikasi peramban yang kamu gunakan, riwayat aktivitas kamu, dll."
myTheme: "Tema saya" myTheme: "Tema saya"
backgroundColor: "Latar Belakang" backgroundColor: "Latar Belakang"
accentColor: "Aksen" accentColor: "Aksen"
@ -736,14 +806,17 @@ unlikeConfirm: "Yakin ingin hapus sukamu?"
fullView: "Tampilan penuh" fullView: "Tampilan penuh"
quitFullView: "Keluar tampilan penuh" quitFullView: "Keluar tampilan penuh"
addDescription: "Tambahkan deskripsi" addDescription: "Tambahkan deskripsi"
userPagePinTip: "Kamu dapat membuat catatan untuk ditampilkan disini dengan memilih \"Sematkan ke profil\" dari menu pada catatan individu." userPagePinTip: "Kamu dapat membuat catatan untuk ditampilkan disini dengan memilih\
notSpecifiedMentionWarning: "Catatan ini mengandung sebutan dari pengguna yang tidak dimuat sebagai penerima" \ \"Sematkan ke profil\" dari menu pada catatan individu."
notSpecifiedMentionWarning: "Catatan ini mengandung sebutan dari pengguna yang tidak\
\ dimuat sebagai penerima"
info: "Informasi" info: "Informasi"
userInfo: "Informasi pengguna" userInfo: "Informasi pengguna"
unknown: "Tidak diketahui" unknown: "Tidak diketahui"
onlineStatus: "Status daring" onlineStatus: "Status daring"
hideOnlineStatus: "Sembunyikan status daring" hideOnlineStatus: "Sembunyikan status daring"
hideOnlineStatusDescription: "Menyembunyikan status daring kamu umengurangi kenyamanan untuk beberapa fungsi seperti contohnya pencarian." hideOnlineStatusDescription: "Menyembunyikan status daring kamu umengurangi kenyamanan\
\ untuk beberapa fungsi seperti contohnya pencarian."
online: "Daring" online: "Daring"
active: "Aktif" active: "Aktif"
offline: "Luring" offline: "Luring"
@ -778,7 +851,8 @@ emailNotConfiguredWarning: "Alamat surel tidak disetel."
ratio: "Rasio" ratio: "Rasio"
previewNoteText: "Tampilkan pratinjau" previewNoteText: "Tampilkan pratinjau"
customCss: "Custom CSS" customCss: "Custom CSS"
customCssWarn: "Pengaturan ini seharusnya digunakan jika kamu tahu cara kerjanya. Memasukkan nilai yang tidak tepat dapat menyebabkan klien tidak berfungsi semestinya." customCssWarn: "Pengaturan ini seharusnya digunakan jika kamu tahu cara kerjanya.\
\ Memasukkan nilai yang tidak tepat dapat menyebabkan klien tidak berfungsi semestinya."
global: "Global" global: "Global"
squareAvatars: "Tampilkan avatar sebagai persegi" squareAvatars: "Tampilkan avatar sebagai persegi"
sent: "Kirim" sent: "Kirim"
@ -793,7 +867,9 @@ whatIsNew: "Lihat perubahan pemutakhiran"
translate: "Terjemahkan" translate: "Terjemahkan"
translatedFrom: "Terjemahkan dari {x}" translatedFrom: "Terjemahkan dari {x}"
accountDeletionInProgress: "Penghapusan akun sedang dalam proses" accountDeletionInProgress: "Penghapusan akun sedang dalam proses"
usernameInfo: "Nama yang mengidentifikasikan akun kamu dari yang lain pada peladen ini. Kamu dapat menggunakan alfabet (a~z, A~Z), digit (0~9) atau garis bawah (_). Username tidak dapat diubah setelahnya." usernameInfo: "Nama yang mengidentifikasikan akun kamu dari yang lain pada peladen\
\ ini. Kamu dapat menggunakan alfabet (a~z, A~Z), digit (0~9) atau garis bawah (_).\
\ Username tidak dapat diubah setelahnya."
aiChanMode: "Mode Ai" aiChanMode: "Mode Ai"
keepCw: "Biarkan Peringatan Konten" keepCw: "Biarkan Peringatan Konten"
pubSub: "Akun Pub/Sub" pubSub: "Akun Pub/Sub"
@ -809,12 +885,14 @@ filter: "Saring"
controlPanel: "Panel kendali" controlPanel: "Panel kendali"
manageAccounts: "Kelola Akun" manageAccounts: "Kelola Akun"
makeReactionsPublic: "Tampilkan riwayat reaksi ke publik" makeReactionsPublic: "Tampilkan riwayat reaksi ke publik"
makeReactionsPublicDescription: "Pengaturan ini akan membuat daftar dari semua reaksi masa lalu kamu ditampilkan secara publik." makeReactionsPublicDescription: "Pengaturan ini akan membuat daftar dari semua reaksi\
\ masa lalu kamu ditampilkan secara publik."
classic: "Klasik" classic: "Klasik"
muteThread: "Bisukan thread" muteThread: "Bisukan thread"
unmuteThread: "Suarakan thread" unmuteThread: "Suarakan thread"
ffVisibility: "Visibilitas Mengikuti/Pengikut" ffVisibility: "Visibilitas Mengikuti/Pengikut"
ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu ikuti." ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu\
\ ikuti."
continueThread: "Lihat lanjutan thread" continueThread: "Lihat lanjutan thread"
deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?" deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?"
incorrectPassword: "Kata sandi salah." incorrectPassword: "Kata sandi salah."
@ -824,7 +902,8 @@ leaveGroup: "Keluar grup"
leaveGroupConfirm: "Apakah kamu yakin untuk keluar dari \"{name}\"?" leaveGroupConfirm: "Apakah kamu yakin untuk keluar dari \"{name}\"?"
useDrawerReactionPickerForMobile: "Tampilkan bilah reaksi sebagai laci di ponsel" useDrawerReactionPickerForMobile: "Tampilkan bilah reaksi sebagai laci di ponsel"
welcomeBackWithName: "Selamat datang kembali, {name}." welcomeBackWithName: "Selamat datang kembali, {name}."
clickToFinishEmailVerification: "Mohon klik [{ok}] untuk menyelesaikan verifikasi email." clickToFinishEmailVerification: "Mohon klik [{ok}] untuk menyelesaikan verifikasi\
\ email."
overridedDeviceKind: "Tipe perangkat" overridedDeviceKind: "Tipe perangkat"
smartphone: "Ponsel" smartphone: "Ponsel"
tablet: "Tablet" tablet: "Tablet"
@ -866,11 +945,16 @@ _ffVisibility:
_signup: _signup:
almostThere: "Hampir selesai" almostThere: "Hampir selesai"
emailAddressInfo: "Mohon masukkan alamat surel kamu." emailAddressInfo: "Mohon masukkan alamat surel kamu."
emailSent: "Konfirmasi surel telah dikirimkan ke alamat surel kamu ({email}). Mohon klik tautan yang tercantum di dalamnya untuk menyelesaikan pembuatan akun." emailSent: "Konfirmasi surel telah dikirimkan ke alamat surel kamu ({email}). Mohon\
\ klik tautan yang tercantum di dalamnya untuk menyelesaikan pembuatan akun."
_accountDelete: _accountDelete:
accountDelete: "Hapus akun" accountDelete: "Hapus akun"
mayTakeTime: "Karena penghapusan akun merupakan proses yang berat dan intensif, kemungkinan dapat membutuhkan waktu untuk menyelesaikan tergantung daripada berapa banyak konten yang kamu buat dan berapa banyak berkas yang telah kamu unggah." mayTakeTime: "Karena penghapusan akun merupakan proses yang berat dan intensif,\
sendEmail: "Setelah penghapusan akun selesai, pemberitahuan akan dikirimkan ke alamat surel yang terdaftarkan pada akun ini." \ kemungkinan dapat membutuhkan waktu untuk menyelesaikan tergantung daripada\
\ berapa banyak konten yang kamu buat dan berapa banyak berkas yang telah kamu\
\ unggah."
sendEmail: "Setelah penghapusan akun selesai, pemberitahuan akan dikirimkan ke alamat\
\ surel yang terdaftarkan pada akun ini."
requestAccountDelete: "Minta penghapusan akun" requestAccountDelete: "Minta penghapusan akun"
started: "Penghapusan telah dimulai" started: "Penghapusan telah dimulai"
inProgress: "Penghapusan sedang dalam proses" inProgress: "Penghapusan sedang dalam proses"
@ -878,9 +962,13 @@ _ad:
back: "Kembali" back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
_forgotPassword: _forgotPassword:
enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel tersebut." enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah\
ifNoEmail: "Apabila kamu tidak menggunakan surel pada saat pendaftaran, mohon hubungi admin segera." \ tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel\
contactAdmin: "Instansi ini tidak mendukung menggunakan alamat surel, mohon kontak admin untuk mengatur ulang password kamu." \ tersebut."
ifNoEmail: "Apabila kamu tidak menggunakan surel pada saat pendaftaran, mohon hubungi\
\ admin segera."
contactAdmin: "Instansi ini tidak mendukung menggunakan alamat surel, mohon kontak\
\ admin untuk mengatur ulang password kamu."
_gallery: _gallery:
my: "Postingan saya" my: "Postingan saya"
liked: "Postingan yang disukai" liked: "Postingan yang disukai"
@ -902,13 +990,15 @@ _registry:
domain: "Domain" domain: "Domain"
createKey: "Buat kunci" createKey: "Buat kunci"
_aboutMisskey: _aboutMisskey:
about: "Misskey adalah perangkat lunak sumber terbuka yang sedang dikembangkan oleh syuilo sejak 2014." about: "Misskey adalah perangkat lunak sumber terbuka yang sedang dikembangkan oleh\
\ syuilo sejak 2014."
contributors: "Kontributor utama" contributors: "Kontributor utama"
allContributors: "Seluruh kontributor" allContributors: "Seluruh kontributor"
source: "Sumber kode" source: "Sumber kode"
translation: "Terjemahkan Misskey" translation: "Terjemahkan Misskey"
donate: "Donasi ke Misskey" donate: "Donasi ke Misskey"
morePatrons: "Kami sangat mengapresiasi dukungan dari banyak penolong lain yang tidak tercantum disini. Terima kasih! 🥰" morePatrons: "Kami sangat mengapresiasi dukungan dari banyak penolong lain yang\
\ tidak tercantum disini. Terima kasih! \U0001F970"
patrons: "Pendukung" patrons: "Pendukung"
_nsfw: _nsfw:
respect: "Sembunyikan media NSFW" respect: "Sembunyikan media NSFW"
@ -916,10 +1006,12 @@ _nsfw:
force: "Sembunyikan semua media" force: "Sembunyikan semua media"
_mfm: _mfm:
cheatSheet: "Contekan MFM" cheatSheet: "Contekan MFM"
intro: "MFM adalah Misskey-exclusive Markup Language yang dapat digunakan di banyak tempat. Berikut kamu bisa melihat daftar dari syntax MFM yang ada." intro: "MFM adalah Misskey-exclusive Markup Language yang dapat digunakan di banyak\
\ tempat. Berikut kamu bisa melihat daftar dari syntax MFM yang ada."
dummy: "Misskey membentangkan dunia Fediverse" dummy: "Misskey membentangkan dunia Fediverse"
mention: "Sebut" mention: "Sebut"
mentionDescription: "Kamu dapat menentukan pengguna tertentu dengan menggunakan simbol-At dan nama engguna mereka." mentionDescription: "Kamu dapat menentukan pengguna tertentu dengan menggunakan\
\ simbol-At dan nama engguna mereka."
hashtag: "Tagar" hashtag: "Tagar"
hashtagDescription: "Kamu dapat menentukan tagar dengan menggunakan angka dan teks." hashtagDescription: "Kamu dapat menentukan tagar dengan menggunakan angka dan teks."
url: "URL" url: "URL"
@ -935,15 +1027,18 @@ _mfm:
inlineCode: "Kode (Dalam baris)" inlineCode: "Kode (Dalam baris)"
inlineCodeDescription: "Menampilkan sorotan sintaks dalam baris untuk kode(program-)." inlineCodeDescription: "Menampilkan sorotan sintaks dalam baris untuk kode(program-)."
blockCode: "Kode (Blok)" blockCode: "Kode (Blok)"
blockCodeDescription: "Menampilkan sorotan sintaks untuk kode(program-) multi baris dalam sebuah blok." blockCodeDescription: "Menampilkan sorotan sintaks untuk kode(program-) multi baris\
\ dalam sebuah blok."
inlineMath: "Matematika (Dalam baris)" inlineMath: "Matematika (Dalam baris)"
inlineMathDescription: "Menampilkan formula matematika (KaTeX) dalam baris." inlineMathDescription: "Menampilkan formula matematika (KaTeX) dalam baris."
blockMath: "Matematika (Blok)" blockMath: "Matematika (Blok)"
blockMathDescription: "Menampilkan formula matematika (KaTeX) multibaris dalam sebuah blok." blockMathDescription: "Menampilkan formula matematika (KaTeX) multibaris dalam sebuah\
\ blok."
quote: "Kutip" quote: "Kutip"
quoteDescription: "Menampilkan konten sebagai kutipan." quoteDescription: "Menampilkan konten sebagai kutipan."
emoji: "Emoji kustom" emoji: "Emoji kustom"
emojiDescription: "Emoji kustom dapat ditampilkan dengan mengurung nama emoji kustom menggunakan tanda titik dua." emojiDescription: "Emoji kustom dapat ditampilkan dengan mengurung nama emoji kustom\
\ menggunakan tanda titik dua."
search: "Penelusuran" search: "Penelusuran"
searchDescription: "Menampilkan kotak pencarian dengan teks yang sudah dimasukkan." searchDescription: "Menampilkan kotak pencarian dengan teks yang sudah dimasukkan."
flip: "Balik" flip: "Balik"
@ -969,7 +1064,8 @@ _mfm:
x4: "Sangat besar" x4: "Sangat besar"
x4Description: "Tampilka konten menjadi sangat besar." x4Description: "Tampilka konten menjadi sangat besar."
blur: "Buram" blur: "Buram"
blurDescription: "Konten dapat diburamkan dengan efek ini. Konten dapat ditampilkan dengan jelas dengan melayangkan kursor tetikus di atasnya." blurDescription: "Konten dapat diburamkan dengan efek ini. Konten dapat ditampilkan\
\ dengan jelas dengan melayangkan kursor tetikus di atasnya."
font: "Font" font: "Font"
fontDescription: "Setel font yang ditampilkan untuk konten." fontDescription: "Setel font yang ditampilkan untuk konten."
rainbow: "Pelangi" rainbow: "Pelangi"
@ -1003,15 +1099,21 @@ _menuDisplay:
hide: "Sembunyikan" hide: "Sembunyikan"
_wordMute: _wordMute:
muteWords: "Kata yang dibisukan" muteWords: "Kata yang dibisukan"
muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan baris baru untuk kondisi OR." muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan\
muteWordsDescription2: "Kurung kata kunci dengan garis miring untuk menggunakan regular expressions." \ baris baru untuk kondisi OR."
muteWordsDescription2: "Kurung kata kunci dengan garis miring untuk menggunakan\
\ regular expressions."
softDescription: "Sembunyikan catatan yang memenuhi aturan kondisi dari linimasa." softDescription: "Sembunyikan catatan yang memenuhi aturan kondisi dari linimasa."
hardDescription: "Cegah catatan memenuhi aturan kondisi dari ditambahkan ke linimasa. Dengan tambahan, catatan berikut tidak akan ditambahkan ke linimasa meskipun jika kondisi tersebut diubah." hardDescription: "Cegah catatan memenuhi aturan kondisi dari ditambahkan ke linimasa.\
\ Dengan tambahan, catatan berikut tidak akan ditambahkan ke linimasa meskipun\
\ jika kondisi tersebut diubah."
soft: "Lembut" soft: "Lembut"
hard: "Keras" hard: "Keras"
mutedNotes: "Catatan yang dibisukan" mutedNotes: "Catatan yang dibisukan"
_instanceMute: _instanceMute:
instanceMuteDescription: "Pengaturan ini akan membisukan note/renote apa saja dari instansi yang terdaftar, termasuk pengguna yang membalas pengguna lain dalam instansi yang dibisukan." instanceMuteDescription: "Pengaturan ini akan membisukan note/renote apa saja dari\
\ instansi yang terdaftar, termasuk pengguna yang membalas pengguna lain dalam\
\ instansi yang dibisukan."
instanceMuteDescription2: "Pisah dengan baris baru" instanceMuteDescription2: "Pisah dengan baris baru"
title: "Sembunyikan note dari instansi terdaftar." title: "Sembunyikan note dari instansi terdaftar."
heading: "Daftar instansi yang akan dibisukan" heading: "Daftar instansi yang akan dibisukan"
@ -1043,7 +1145,8 @@ _theme:
darken: "Mengelamkan" darken: "Mengelamkan"
lighten: "Menerangkan" lighten: "Menerangkan"
inputConstantName: "Masukkan nama untuk konstanta" inputConstantName: "Masukkan nama untuk konstanta"
importInfo: "Jika kamu memasukkan kode tema disini, kamu dapat mengimpornya ke penyunting tema" importInfo: "Jika kamu memasukkan kode tema disini, kamu dapat mengimpornya ke penyunting\
\ tema"
deleteConstantConfirm: "apakah kamu ingin menghapus konstanta {const}?" deleteConstantConfirm: "apakah kamu ingin menghapus konstanta {const}?"
keys: keys:
accent: "Aksen" accent: "Aksen"
@ -1115,36 +1218,56 @@ _time:
_tutorial: _tutorial:
title: "Cara menggunakan Misskey" title: "Cara menggunakan Misskey"
step1_1: "Selamat datang!" step1_1: "Selamat datang!"
step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\" yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"." step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\"\
step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti siapapun." \ yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"."
step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti seseorang." step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti\
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain mudah untuk mengikutimu kembali." \ siapapun."
step3_1: "Selesai menyetel profil kamu?" step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti\
step3_2: "Langkah selanjutnya adalah membuat catatan. Kamu bisa lakukan ini dengan mengklik ikon pensil pada layar kamu." \ seseorang."
step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat catatan kamu." step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain\
step3_4: "Bingung tidak berpikiran untuk mengatakan sesuatu? Coba saja \"baru aja ikutan bikin akun misskey punyaku\"!" \ mudah untuk mengikutimu kembali."
step3_1: "Sekarang saatnya mengikuti beberapa orang!"
step3_2: "Langkah selanjutnya adalah membuat catatan. Kamu bisa lakukan ini dengan\
\ mengklik ikon pensil pada layar kamu."
step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat\
\ catatan kamu."
step3_4: "Bingung tidak berpikiran untuk mengatakan sesuatu? Coba saja \"baru aja\
\ ikutan bikin akun misskey punyaku\"!"
step4_1: "Selesai mencatat catatan pertamamu?" step4_1: "Selesai mencatat catatan pertamamu?"
step4_2: "Horee! Sekarang catatan pertamamu sudah ditampilkan di linimasa milikmu." step4_2: "Horee! Sekarang catatan pertamamu sudah ditampilkan di linimasa milikmu."
step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti orang lain." step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti\
step5_2: "{featured} akan memperlihatkan catatan yang sedang tren saat ini untuk kamu. {explore} akan membantumu untuk mencari pengguna yang sedang tren juga saat ini. Coba ikuti seseorang yang kamu suka!" \ orang lain."
step5_3: "Untuk mengikuti pengguna lain, klik pada ikon mereka dan tekan tombol follow pada profil mereka." step5_2: "{featured} akan memperlihatkan catatan yang sedang tren saat ini untuk\
step5_4: "Jika pengguna lain memiliki ikon gembok di sebelah nama mereka, maka pengguna rersebut harus menyetujui permintaan mengikuti dari kamu secara manual." \ kamu. {explore} akan membantumu untuk mencari pengguna yang sedang tren juga\
\ saat ini. Coba ikuti seseorang yang kamu suka!"
step5_3: "Untuk mengikuti pengguna lain, klik pada ikon mereka dan tekan tombol\
\ follow pada profil mereka."
step5_4: "Jika pengguna lain memiliki ikon gembok di sebelah nama mereka, maka pengguna\
\ rersebut harus menyetujui permintaan mengikuti dari kamu secara manual."
step6_1: "Sekarang kamu dapat melihat catatan pengguna lain pada linimasamu." step6_1: "Sekarang kamu dapat melihat catatan pengguna lain pada linimasamu."
step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon dengan cepat." step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon\
step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka." \ dengan cepat."
step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain\
\ dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka."
step7_1: "Yay, Selamat! Kamu sudah menyelesaikan tutorial dasar Misskey." step7_1: "Yay, Selamat! Kamu sudah menyelesaikan tutorial dasar Misskey."
step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung ke bagian {help}." step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung\
step7_3: "Semoga berhasil dan bersenang-senanglah! 🚀" \ ke bagian {help}."
step7_3: "Semoga berhasil dan bersenang-senanglah! \U0001F680"
_2fa: _2fa:
alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor." alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
registerDevice: "Daftarkan perangkat baru" registerDevice: "Daftarkan perangkat baru"
registerKey: "Daftarkan kunci keamanan baru" registerKey: "Daftarkan kunci keamanan baru"
step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu." step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat\
\ kamu."
step2: "Lalu, pindai kode QR yang ada di layar." step2: "Lalu, pindai kode QR yang ada di layar."
step2Url: "Di aplikasi desktop, masukkan URL berikut:" step2Url: "Di aplikasi desktop, masukkan URL berikut:"
step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan." step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan\
step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu." \ pemasangan."
securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu." step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi\
\ otentikasi kamu."
securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses\
\ login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung\
\ FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
_permissions: _permissions:
"read:account": "Lihat informasi akun" "read:account": "Lihat informasi akun"
"write:account": "Sunting informasi akun" "write:account": "Sunting informasi akun"
@ -1180,7 +1303,8 @@ _permissions:
"write:gallery-likes": "Sunting daftar postingan galeri yang disukai" "write:gallery-likes": "Sunting daftar postingan galeri yang disukai"
_auth: _auth:
shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?" shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
shareAccessAsk: "Apakah kamu ingin mengijinkan aplikasi ini untuk mengakses akun kamu?" shareAccessAsk: "Apakah kamu ingin mengijinkan aplikasi ini untuk mengakses akun\
\ kamu?"
permissionAsk: "Aplikasi ini membutuhkan beberapa ijin, yaitu:" permissionAsk: "Aplikasi ini membutuhkan beberapa ijin, yaitu:"
pleaseGoBack: "Mohon kembali ke aplikasi kamu" pleaseGoBack: "Mohon kembali ke aplikasi kamu"
callback: "Mengembalikan kamu ke aplikasi" callback: "Mengembalikan kamu ke aplikasi"
@ -1275,7 +1399,8 @@ _profile:
youCanIncludeHashtags: "Kamu juga dapat menambahkan tagar ke dalam bio." youCanIncludeHashtags: "Kamu juga dapat menambahkan tagar ke dalam bio."
metadata: "Informasi tambahan" metadata: "Informasi tambahan"
metadataEdit: "Sunting informasi tambahan" metadataEdit: "Sunting informasi tambahan"
metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan ke dalam profilmu." metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan\
\ ke dalam profilmu."
metadataLabel: "Label" metadataLabel: "Label"
metadataContent: "Isi" metadataContent: "Isi"
changeAvatar: "Ubah avatar" changeAvatar: "Ubah avatar"
@ -1596,7 +1721,8 @@ _pages:
_for: _for:
arg1: "Jumlah angka untuk diulangi" arg1: "Jumlah angka untuk diulangi"
arg2: "Aksi" arg2: "Aksi"
typeError: "Slot {slot} menerima tipe \"{expect}\", sayangnya nilai yang disediakan adalah \"{actual}\"!" typeError: "Slot {slot} menerima tipe \"{expect}\", sayangnya nilai yang disediakan\
\ adalah \"{actual}\"!"
thereIsEmptySlot: "Slot {slot} kosong!" thereIsEmptySlot: "Slot {slot} kosong!"
types: types:
string: "Teks" string: "Teks"

View file

@ -973,6 +973,8 @@ customKaTeXMacroDescription: "数式入力を楽にするためのマクロを
name}{content} または \\newcommand{\\add}[2]{#1 + #2} のように記述します。後者の例では \\add{3}{foo}\ name}{content} または \\newcommand{\\add}[2]{#1 + #2} のように記述します。後者の例では \\add{3}{foo}\
\ が 3 + foo に展開されます。また、マクロの名前を囲む波括弧を丸括弧 () および角括弧 [] に変更した場合、マクロの引数に使用する括弧が変更されます。マクロの定義は一行に一つのみで、途中で改行はできません。マクロの定義が無効な行は無視されます。文字列を単純に置換する機能のみに対応していて、条件分岐などの高度な構文は使用できません。" \ が 3 + foo に展開されます。また、マクロの名前を囲む波括弧を丸括弧 () および角括弧 [] に変更した場合、マクロの引数に使用する括弧が変更されます。マクロの定義は一行に一つのみで、途中で改行はできません。マクロの定義が無効な行は無視されます。文字列を単純に置換する機能のみに対応していて、条件分岐などの高度な構文は使用できません。"
enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする" enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする"
preventAiLearning: "AIによる学習を防止"
preventAiLearningDescription: "投稿したート、添付した画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
@ -1837,23 +1839,7 @@ _deck:
list: "リスト" list: "リスト"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" direct: "ダイレクト"
_apps:
apps: "アプリ"
crossPlatform: "クロスプラットフォーム"
mobile: "モバイル"
firstParty: "ファーストパーティ"
firstClass: "対応度◎"
secondClass: "対応度○"
thirdClass: "対応度△"
free: "無料"
paid: "有料"
pwa: "PWAをインストール"
kaiteki: "Kaiteki"
milktea: "Milktea"
missLi: "MissLi"
mona: "Mona"
theDesk: "TheDesk"
lesskey: "Lesskey"
noteId: 投稿のID noteId: 投稿のID
hiddenTagsDescription: 'トレンドと「みつける」から除外したいハッシュタグを(先頭の # を除いて)改行区切りで入力してください。この設定はトレンドと「みつける」以外には影響しません。' hiddenTagsDescription: 'トレンドと「みつける」から除外したいハッシュタグを(先頭の # を除いて)改行区切りで入力してください。この設定はトレンドと「みつける」以外には影響しません。'
hiddenTags: 非表示にするハッシュタグ hiddenTags: 非表示にするハッシュタグ
apps: "アプリ"

View file

@ -66,8 +66,8 @@ import: "Importuj"
export: "Eksportuj" export: "Eksportuj"
files: "Pliki" files: "Pliki"
download: "Pobierz" download: "Pobierz"
driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Zniknie również wpis,\ driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Wszystkie wpisy zawierające\
\ do której dołączony jest ten plik." \ ten plik również zostaną usunięte."
unfollowConfirm: "Czy na pewno chcesz przestać obserwować {name}?" unfollowConfirm: "Czy na pewno chcesz przestać obserwować {name}?"
exportRequested: "Zażądałeś eksportu. Może to zająć chwilę. Po zakończeniu eksportu\ exportRequested: "Zażądałeś eksportu. Może to zająć chwilę. Po zakończeniu eksportu\
\ zostanie on dodany do Twojego dysku." \ zostanie on dodany do Twojego dysku."
@ -787,7 +787,7 @@ active: "Aktywny"
offline: "Offline" offline: "Offline"
notRecommended: "Nie zalecane" notRecommended: "Nie zalecane"
botProtection: "Zabezpieczenie przed botami" botProtection: "Zabezpieczenie przed botami"
instanceBlocking: "Zablokowane instancje" instanceBlocking: "Zablokowane/wyciszone instancje"
selectAccount: "Wybierz konto" selectAccount: "Wybierz konto"
switchAccount: "Przełącz konto" switchAccount: "Przełącz konto"
enabled: "Właczono" enabled: "Właczono"
@ -1075,6 +1075,14 @@ _mfm:
inlineMath: Matematyka (Inline) inlineMath: Matematyka (Inline)
inlineMathDescription: Pokaż formuły matematyczne (KaTeX) w linii inlineMathDescription: Pokaż formuły matematyczne (KaTeX) w linii
blockMathDescription: Pokaż wieloliniowe formuły matematyczne (KaTeX) w bloku blockMathDescription: Pokaż wieloliniowe formuły matematyczne (KaTeX) w bloku
background: Kolor tła
backgroundDescription: Zmień kolor tła tekstu.
foregroundDescription: Zmień kolor pierwszoplanowy tekstu.
positionDescription: Przesuń treść o określoną odległość.
position: Pozycjonuj
foreground: Kolor pierwszoplanowy
scaleDescription: Skaluj treść o określoną wielkość.
scale: Skaluj
_instanceTicker: _instanceTicker:
none: "Nigdy nie pokazuj" none: "Nigdy nie pokazuj"
remote: "Pokaż dla zdalnych użytkowników" remote: "Pokaż dla zdalnych użytkowników"
@ -1094,6 +1102,8 @@ _channel:
following: "Śledzeni" following: "Śledzeni"
usersCount: "{n} uczestnicy" usersCount: "{n} uczestnicy"
notesCount: "{n} wpisy" notesCount: "{n} wpisy"
nameAndDescription: Nazwa i opis
nameOnly: Tylko nazwa
_menuDisplay: _menuDisplay:
top: "Góra" top: "Góra"
hide: "Ukryj" hide: "Ukryj"
@ -1222,13 +1232,13 @@ _tutorial:
step2_1: "Najpierw, proszę wypełnij swój profil." step2_1: "Najpierw, proszę wypełnij swój profil."
step2_2: "Podanie kilku informacji o tym, kim jesteś, ułatwi innym stwierdzenie,\ step2_2: "Podanie kilku informacji o tym, kim jesteś, ułatwi innym stwierdzenie,\
\ czy chcą zobaczyć Twoje wpisy lub śledzić Cię." \ czy chcą zobaczyć Twoje wpisy lub śledzić Cię."
step3_1: "Teraz czas na śledzenie niektórych osób!" step3_1: "Pora znaleźć osoby do śledzenia!"
step3_2: "Twoje domowe i społeczne linie czasu opierają się na tym, kogo śledzisz,\ step3_2: "Twoje domowe i społeczne linie czasu opierają się na tym, kogo śledzisz,\
\ więc spróbuj śledzić kilka kont, aby zacząć.\nKliknij kółko z plusem w prawym\ \ więc spróbuj śledzić kilka kont, aby zacząć.\nKliknij kółko z plusem w prawym\
\ górnym rogu profilu, aby go śledzić." \ górnym rogu profilu, aby go śledzić."
step4_1: "Pozwól, że zabierzemy Cię tam." step4_1: "Pozwól, że zabierzemy Cię tam."
step4_2: "Dla twojego pierwszego postu, niektórzy ludzie lubią zrobić {introduction}\ step4_2: "W pierwszym wpisie możesz się przedstawić lub wysłać powitanie - \"Witaj,\
\ post lub prosty \"Hello world!\"" \ świecie!\""
step5_1: "Osie czasu, wszędzie widzę osie czasu!" step5_1: "Osie czasu, wszędzie widzę osie czasu!"
step5_2: "Twoja instancja ma włączone {timelines} różne osie czasu." step5_2: "Twoja instancja ma włączone {timelines} różne osie czasu."
step5_3: "Główna {icon} oś czasu to miejsce, w którym możesz zobaczyć posty od użytkowników\ step5_3: "Główna {icon} oś czasu to miejsce, w którym możesz zobaczyć posty od użytkowników\
@ -1811,8 +1821,8 @@ privateMode: Tryb prywatny
allowedInstances: Dopuszczone instancje allowedInstances: Dopuszczone instancje
recommended: Polecane recommended: Polecane
allowedInstancesDescription: Hosty instancji które mają być dopuszczone do federacji, allowedInstancesDescription: Hosty instancji które mają być dopuszczone do federacji,
każda separowana nową linią (dotyczy tylko trybu prywatnego). każdy separowany nową linią (dotyczy tylko trybu prywatnego).
seperateRenoteQuote: Oddziel przyciski podbicia i cytatów seperateRenoteQuote: Oddziel przyciski podbicia i cytowania
refreshInterval: 'Częstotliwość aktualizacji ' refreshInterval: 'Częstotliwość aktualizacji '
slow: Wolna slow: Wolna
_messaging: _messaging:
@ -1845,10 +1855,10 @@ swipeOnDesktop: Zezwól na przeciąganie w stylu mobilnym na desktopie
moveFromDescription: To utworzy alias twojego starego konta, w celu umożliwienia migracji moveFromDescription: To utworzy alias twojego starego konta, w celu umożliwienia migracji
z tamtego konta na to. Zrób to ZANIM rozpoczniesz przenoszenie się z tamtego konta. z tamtego konta na to. Zrób to ZANIM rozpoczniesz przenoszenie się z tamtego konta.
Proszę wpisz tag konta w formacie @person@instance.com Proszę wpisz tag konta w formacie @person@instance.com
migrationConfirm: "Czy jesteś na 200% pewn* tego, że chcesz przenieść swoje konto\ migrationConfirm: "Czy jesteś absolutnie pewn* tego, że chcesz przenieść swoje konto\
\ na {account}? Gdy to zrobisz, odwrócenie tego będzie nie możliwe, i nie będziesz\ \ na {account}? Tego działania nie można odwrócić. Nieodwracalnie stracisz możliwość\
\ w stanie ponownie używać normalnie z tego konta.\nUpewnij się, że to konto zostało\ \ normalnego korzystania z konta.\nUpewnij się, że to konto zostało ustawione jako\
\ ustawione jako konto z którego się przenosisz." \ konto z którego się przenosisz."
noThankYou: Nie, dziękuję noThankYou: Nie, dziękuję
addInstance: Dodaj instancję addInstance: Dodaj instancję
renoteMute: Wycisz podbicia renoteMute: Wycisz podbicia
@ -1894,25 +1904,9 @@ indexNotice: Indeksuję. Zapewne zajmie to chwilę, nie restartuj serwera przez
customKaTeXMacro: Niestandardowe makra KaTeX customKaTeXMacro: Niestandardowe makra KaTeX
enableCustomKaTeXMacro: Włącz niestandardowe makra KaTeX enableCustomKaTeXMacro: Włącz niestandardowe makra KaTeX
noteId: ID wpisu noteId: ID wpisu
_apps:
apps: Aplikacje
crossPlatform: Wieloplatformowe
mobile: Mobilne
firstParty: Oficjalne
firstClass: Pierwszej klasy
secondClass: Drugiej klasy
thirdClass: Trzeciej klasy
free: Darmowe
paid: Płatne
pwa: Zainstaluj PWA
kaiteki: Kaiteki
milktea: Milktea
missLi: MissLi
mona: Mona
theDesk: TheDesk
lesskey: Lesskey
hiddenTagsDescription: 'Wypisz tagi (bez #) hashtagów które masz zamiar ukryć z "Na hiddenTagsDescription: 'Wypisz tagi (bez #) hashtagów które masz zamiar ukryć z "Na
czasie" i "Eksploruj". Na ukryte hashtagi można dalej wejść innymi sposobami.' czasie" i "Eksploruj". Na ukryte hashtagi można dalej wejść innymi sposobami. Ta
lista nie ma wpływu na zablokowane instancje.'
proxyAccountDescription: Konto proxy jest kontem które w określonych sytuacjach zachowuje proxyAccountDescription: Konto proxy jest kontem które w określonych sytuacjach zachowuje
się jak zdalny obserwujący. Na przykład, kiedy użytkownik dodaje zdalnego użytkownika się jak zdalny obserwujący. Na przykład, kiedy użytkownik dodaje zdalnego użytkownika
do listy, oraz żaden lokalny użytkownik nie obserwuje tego konta, aktywność owego do listy, oraz żaden lokalny użytkownik nie obserwuje tego konta, aktywność owego
@ -1927,7 +1921,7 @@ sendErrorReportsDescription: "Gdy ta opcja jest włączona, szczegółowe inform
\ Calckey.\nZawrze to informacje takie jak wersja twojego systemu operacyjnego,\ \ Calckey.\nZawrze to informacje takie jak wersja twojego systemu operacyjnego,\
\ przeglądarki, Twoja aktywność na Calckey itd." \ przeglądarki, Twoja aktywność na Calckey itd."
privateModeInfo: Jeśli włączone, tylko dopuszczone instancje będą mogły federować privateModeInfo: Jeśli włączone, tylko dopuszczone instancje będą mogły federować
z Twoją instancją. Wszystkie posty będą ukryte przed publiką. z Twoją instancją. Wszystkie posty będą jedynie widoczne na Twojej instancji.
oneHour: Godzina oneHour: Godzina
oneDay: Dzień oneDay: Dzień
oneWeek: Tydzień oneWeek: Tydzień
@ -1999,3 +1993,23 @@ themeColor: Kolor znacznika instancji
instanceDefaultLightTheme: Domyślny jasny motyw instancji instanceDefaultLightTheme: Domyślny jasny motyw instancji
enableEmojiReactions: Włącz reakcje emoji enableEmojiReactions: Włącz reakcje emoji
showEmojisInReactionNotifications: Pokazuj emoji w powiadomieniach reakcyjnych showEmojisInReactionNotifications: Pokazuj emoji w powiadomieniach reakcyjnych
apps: Aplikacje
silenceThisInstance: Wycisz tę instancję
silencedInstances: Wyciszone instancje
deleted: Usunięte
editNote: Edytuj wpis
edited: Edytowany
silenced: Wyciszony
findOtherInstance: Znajdź inny serwer
userSaysSomethingReasonReply: '{name} odpowiedział na wpis zawierający {reason}'
userSaysSomethingReasonRenote: '{name} podbił post zawierający {reason}'
signupsDisabled: Rejestracja na tym serwerze jest obecnie zamknięta, ale zawsze możesz
się zapisać na innym! Jeśli masz kod zaproszeniowy na ten serwer, wpisz go poniżej.
userSaysSomethingReasonQuote: '{name} zacytował wpis zawierający {reason}'
silencedInstancesDescription: Wymień nazwy domenowe instancji, które chcesz wyciszyć.
Profile w wyciszonych instancjach są traktowane jako "Wyciszony", mogą jedynie wysyłać
prośby obserwacji, i nie mogą oznaczać w wzmiankach profili lokalnych jeśli nie
są obserwowane. To nie będzie miało wpływu na zablokowane instancje.
cannotUploadBecauseExceedsFileSizeLimit: Ten plik nie mógł być przesłany, ponieważ
jego wielkość przekracza dozwolony limit.
sendModMail: Wyślij Powiadomienie Moderacyjne

View file

@ -1937,23 +1937,6 @@ _preferencesBackups:
cannotLoad: Загрузка не удалась cannotLoad: Загрузка не удалась
invalidFile: Неправильный формат файла invalidFile: Неправильный формат файла
enableEmojiReactions: Включить эмодзи реакции enableEmojiReactions: Включить эмодзи реакции
_apps:
paid: Платные
lesskey: Lesskey
pwa: Установить PWA
free: Бесплатные
apps: Приложения
crossPlatform: Кроссплатформенные
mobile: Мобильные
firstParty: От разработчиков
firstClass: Первый класс
thirdClass: Третий класс
kaiteki: Kaiteki
milktea: Milktea
missLi: MissLi
mona: Mona
theDesk: TheDesk
secondClass: Второй класс
migrationConfirm: "Вы абсолютно уверены что хотите мигрировать ваш аккаунт на {account}?\ migrationConfirm: "Вы абсолютно уверены что хотите мигрировать ваш аккаунт на {account}?\
\ Как только вы сделаете, вы не сможете отменить это и не сможете нормально использовать\ \ Как только вы сделаете, вы не сможете отменить это и не сможете нормально использовать\
\ аккаунт снова.\nТакже, пожалуйста, убедитесь, что вы установили эту текущую учетную\ \ аккаунт снова.\nТакже, пожалуйста, убедитесь, что вы установили эту текущую учетную\
@ -2000,3 +1983,4 @@ customKaTeXMacroDescription: 'Настройте макросы чтобы ле
ветвление, здесь использоваться не может.' ветвление, здесь использоваться не может.'
cannotUploadBecauseExceedsFileSizeLimit: Этот файл не может быть загружен так как cannotUploadBecauseExceedsFileSizeLimit: Этот файл не может быть загружен так как
он превышает максимально разрешённый размер. он превышает максимально разрешённый размер.
apps: Приложения

View file

@ -1790,10 +1790,6 @@ moveAccountDescription: '這個過程是不可逆的。 在遷移前,請確保
moveFrom: 由舊帳戶移至此帳戶 moveFrom: 由舊帳戶移至此帳戶
moveFromDescription: '這將為你的舊帳戶設置一個別名(Alias),以便你可以從該帳戶轉移到當前帳戶。 在你的舊帳戶移動之前請執行此操作。 請輸入帳戶標籤 moveFromDescription: '這將為你的舊帳戶設置一個別名(Alias),以便你可以從該帳戶轉移到當前帳戶。 在你的舊帳戶移動之前請執行此操作。 請輸入帳戶標籤
(格式: @person@instance.com)' (格式: @person@instance.com)'
_apps:
crossPlatform: 跨平台
free: 免費
paid: 付費
enableEmojiReactions: 啟用表情符號反應 enableEmojiReactions: 啟用表情符號反應
breakFollowConfirm: 您確定要移除該關注者嗎? breakFollowConfirm: 您確定要移除該關注者嗎?
socialTimeline: 社交時間軸 socialTimeline: 社交時間軸

View file

@ -1,12 +1,12 @@
{ {
"name": "calckey", "name": "calckey",
"version": "14.0.0-dev3", "version": "14.0.0-dev10",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://codeberg.org/calckey/calckey.git" "url": "https://codeberg.org/calckey/calckey.git"
}, },
"packageManager": "pnpm@8.3.1", "packageManager": "pnpm@8.5.0",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp", "rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",

View file

@ -5,7 +5,6 @@
<!-- needed for adaptive design --> <!-- needed for adaptive design -->
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!-- <!--
ReDoc doesn't change outer page styles ReDoc doesn't change outer page styles

View file

@ -4,10 +4,10 @@ export class AddSomeUrls1557761316509 {
`ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`, `ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`,
); );
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://github.com/misskey-dev/misskey'`, `ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) NOT NULL DEFAULT 'https://codeberg.org/calckey/calckey'`,
); );
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://github.com/misskey-dev/misskey/issues/new'`, `ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) DEFAULT 'https://codeberg.org/calckey/calckey/issues'`,
); );
} }
async down(queryRunner) { async down(queryRunner) {

View file

@ -0,0 +1,15 @@
export class PreventAiLearning1683682889948 {
name = "PreventAiLearning1683682889948";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "user_profile" ADD "preventAiLearning" boolean NOT NULL DEFAULT true`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "user_profile" DROP COLUMN "preventAiLearning"`,
);
}
}

View file

@ -37,6 +37,7 @@
"@sinonjs/fake-timers": "9.1.2", "@sinonjs/fake-timers": "9.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0", "@tensorflow/tfjs": "^4.2.0",
"adm-zip": "^0.5.10",
"ajv": "8.11.2", "ajv": "8.11.2",
"archiver": "5.3.1", "archiver": "5.3.1",
"argon2": "^0.30.3", "argon2": "^0.30.3",
@ -126,7 +127,6 @@
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.11",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "9.0.0", "uuid": "9.0.0",
"web-push": "3.5.0", "web-push": "3.5.0",
"websocket": "1.0.34", "websocket": "1.0.34",
@ -135,6 +135,7 @@
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.50", "@swc/core": "^1.3.50",
"@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9", "@types/bull": "3.15.9",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",

View file

@ -154,6 +154,11 @@ export class UserProfile {
}) })
public noCrawle: boolean; public noCrawle: boolean;
@Column('boolean', {
default: true,
})
public preventAiLearning: boolean;
@Column('boolean', { @Column('boolean', {
default: false, default: false,
}) })

View file

@ -535,6 +535,7 @@ export const UserRepository = db.getRepository(User).extend({
carefulBot: profile!.carefulBot, carefulBot: profile!.carefulBot,
autoAcceptFollowed: profile!.autoAcceptFollowed, autoAcceptFollowed: profile!.autoAcceptFollowed,
noCrawle: profile!.noCrawle, noCrawle: profile!.noCrawle,
preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable, isExplorable: user.isExplorable,
isDeleted: user.isDeleted, isDeleted: user.isDeleted,
hideOnlineStatus: user.hideOnlineStatus, hideOnlineStatus: user.hideOnlineStatus,

View file

@ -394,6 +394,11 @@ export const packedMeDetailedOnlySchema = {
nullable: true, nullable: true,
optional: false, optional: false,
}, },
preventAiLearning: {
type: "boolean",
nullable: true,
optional: false,
},
isExplorable: { isExplorable: {
type: "boolean", type: "boolean",
nullable: false, nullable: false,

View file

@ -1,6 +1,6 @@
import type Bull from "bull"; import type Bull from "bull";
import * as fs from "node:fs"; import * as fs from "node:fs";
import unzipper from "unzipper"; import AdmZip from "adm-zip";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import { createTempDir } from "@/misc/create-temp.js"; import { createTempDir } from "@/misc/create-temp.js";
@ -47,8 +47,9 @@ export async function importCustomEmojis(
const outputPath = `${path}/emojis`; const outputPath = `${path}/emojis`;
const unzipStream = fs.createReadStream(destPath); const unzipStream = fs.createReadStream(destPath);
const extractor = unzipper.Extract({ path: outputPath }); const zip = new AdmZip(destPath);
extractor.on("close", async () => { zip.extractAllToAsync(outputPath, true, false, async (error) => {
if (error) throw error;
const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8"); const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8");
const meta = JSON.parse(metaRaw); const meta = JSON.parse(metaRaw);
@ -86,6 +87,5 @@ export async function importCustomEmojis(
logger.succ("Imported"); logger.succ("Imported");
done(); done();
}); });
unzipStream.pipe(extractor);
logger.succ(`Unzipping to ${outputPath}`); logger.succ(`Unzipping to ${outputPath}`);
} }

View file

@ -9,6 +9,8 @@ import type { DbUserImportPostsJobData } from "@/queue/types.js";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import type Bull from "bull"; import type Bull from "bull";
import { htmlToMfm } from "@/remote/activitypub/misc/html-to-mfm.js"; import { htmlToMfm } from "@/remote/activitypub/misc/html-to-mfm.js";
import { resolveNote } from "@/remote/activitypub/models/note.js";
import { Note } from "@/models/entities/note.js";
const logger = queueLogger.createSubLogger("import-posts"); const logger = queueLogger.createSubLogger("import-posts");
@ -79,46 +81,49 @@ export async function importPosts(
} else if (parsed instanceof Object) { } else if (parsed instanceof Object) {
logger.info("Parsing animal style posts"); logger.info("Parsing animal style posts");
for (const post of parsed.orderedItems) { for (const post of parsed.orderedItems) {
try { async () => {
linenum++;
if (post.object.inReplyTo != null) {
continue;
}
if (post.directMessage) {
continue;
}
if (job.data.signatureCheck) {
if (!post.signature) {
continue;
}
}
let text;
try { try {
text = htmlToMfm(post.object.content, post.object.tag); linenum++;
} catch (e) { let reply: Note | null = null;
continue; if (post.object.inReplyTo != null) {
} reply = await resolveNote(post.object.inReplyTo);
logger.info(`Posting[${linenum}] ...`); }
if (post.directMessage) {
return;
}
if (job.data.signatureCheck) {
if (!post.signature) {
return;
}
}
let text;
try {
text = htmlToMfm(post.object.content, post.object.tag);
} catch (e) {
return;
}
logger.info(`Posting[${linenum}] ...`);
const note = await create(user, { const note = await create(user, {
createdAt: new Date(post.object.published), createdAt: new Date(post.object.published),
files: undefined, files: undefined,
poll: undefined, poll: undefined,
text: text || undefined, text: text || undefined,
reply: null, reply,
renote: null, renote: null,
cw: post.sensitive, cw: post.sensitive,
localOnly: false, localOnly: false,
visibility: "hidden", visibility: "hidden",
visibleUsers: [], visibleUsers: [],
channel: null, channel: null,
apMentions: new Array(0), apMentions: new Array(0),
apHashtags: undefined, apHashtags: undefined,
apEmojis: undefined, apEmojis: undefined,
}); });
} catch (e) { } catch (e) {
logger.warn(`Error in line:${linenum} ${e}`); logger.warn(`Error in line:${linenum} ${e}`);
} }
};
} }
} }
} catch (e) { } catch (e) {

View file

@ -16,7 +16,6 @@ export default async function renderNote(
dive = true, dive = true,
isTalk = false, isTalk = false,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
note.visibility = note.visibility === "hidden" ? "home" : note.visibility;
const getPromisedFiles = async (ids: string[]) => { const getPromisedFiles = async (ids: string[]) => {
if (!ids || ids.length === 0) return []; if (!ids || ids.length === 0) return [];
const items = await DriveFiles.findBy({ id: In(ids) }); const items = await DriveFiles.findBy({ id: In(ids) });

View file

@ -53,6 +53,7 @@ import * as ep___admin_resetPassword from "./endpoints/admin/reset-password.js";
import * as ep___admin_resolveAbuseUserReport from "./endpoints/admin/resolve-abuse-user-report.js"; import * as ep___admin_resolveAbuseUserReport from "./endpoints/admin/resolve-abuse-user-report.js";
import * as ep___admin_search_indexAll from "./endpoints/admin/search/index-all.js"; import * as ep___admin_search_indexAll from "./endpoints/admin/search/index-all.js";
import * as ep___admin_sendEmail from "./endpoints/admin/send-email.js"; import * as ep___admin_sendEmail from "./endpoints/admin/send-email.js";
import * as ep___admin_sendModMail from "./endpoints/admin/send-mod-mail.js";
import * as ep___admin_serverInfo from "./endpoints/admin/server-info.js"; import * as ep___admin_serverInfo from "./endpoints/admin/server-info.js";
import * as ep___admin_showModerationLogs from "./endpoints/admin/show-moderation-logs.js"; import * as ep___admin_showModerationLogs from "./endpoints/admin/show-moderation-logs.js";
import * as ep___admin_showUser from "./endpoints/admin/show-user.js"; import * as ep___admin_showUser from "./endpoints/admin/show-user.js";
@ -404,6 +405,7 @@ const eps = [
["admin/resolve-abuse-user-report", ep___admin_resolveAbuseUserReport], ["admin/resolve-abuse-user-report", ep___admin_resolveAbuseUserReport],
["admin/search/index-all", ep___admin_search_indexAll], ["admin/search/index-all", ep___admin_search_indexAll],
["admin/send-email", ep___admin_sendEmail], ["admin/send-email", ep___admin_sendEmail],
["admin/send-mod-mail", ep___admin_sendModMail],
["admin/server-info", ep___admin_serverInfo], ["admin/server-info", ep___admin_serverInfo],
["admin/show-moderation-logs", ep___admin_showModerationLogs], ["admin/show-moderation-logs", ep___admin_showModerationLogs],
["admin/show-user", ep___admin_showUser], ["admin/show-user", ep___admin_showUser],

View file

@ -0,0 +1,68 @@
import * as sanitizeHtml from "sanitize-html";
import define from "../../define.js";
import { Users, UserProfiles } from "@/models/index.js";
import { ApiError } from "../../error.js";
import { sendEmail } from "@/services/send-email.js";
import { createNotification } from "@/services/create-notification.js";
export const meta = {
tags: ["users"],
requireCredential: true,
requireModerator: true,
description: "Send a moderation notice.",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "1acefcb5-0959-43fd-9685-b48305736cb5",
},
noEmail: {
message: "No email for user.",
code: "NO_EMAIL",
id: "ac9d2d22-ef73-11ed-a05b-0242ac120003",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
userId: { type: "string", format: "misskey:id" },
comment: { type: "string", minLength: 1, maxLength: 2048 },
},
required: ["userId", "comment"],
} as const;
export default define(meta, paramDef, async (ps) => {
const [user, profile] = await Promise.all([
Users.findOneBy({ id: ps.userId }),
UserProfiles.findOneBy({ userId: ps.userId }),
]);
if (user == null || profile == null) {
throw new ApiError(meta.errors.noSuchUser);
}
createNotification(user.id, "app", {
customBody: ps.comment,
customHeader: "Moderation Notice",
customIcon: "/static-assets/badges/info.png",
});
setImmediate(async () => {
const email = profile.email;
if (email == null) {
throw new ApiError(meta.errors.noEmail);
}
sendEmail(
email,
"Moderation notice",
sanitizeHtml(ps.comment),
sanitizeHtml(ps.comment),
);
});
});

View file

@ -59,6 +59,7 @@ export default define(meta, paramDef, async (ps, me) => {
emailVerified: profile.emailVerified, emailVerified: profile.emailVerified,
autoAcceptFollowed: profile.autoAcceptFollowed, autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle, noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw, alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive, autoSensitive: profile.autoSensitive,
carefulBot: profile.carefulBot, carefulBot: profile.carefulBot,

View file

@ -102,6 +102,7 @@ export const paramDef = {
carefulBot: { type: "boolean" }, carefulBot: { type: "boolean" },
autoAcceptFollowed: { type: "boolean" }, autoAcceptFollowed: { type: "boolean" },
noCrawle: { type: "boolean" }, noCrawle: { type: "boolean" },
preventAiLearning: { type: "boolean" },
isBot: { type: "boolean" }, isBot: { type: "boolean" },
isCat: { type: "boolean" }, isCat: { type: "boolean" },
speakAsCat: { type: "boolean" }, speakAsCat: { type: "boolean" },
@ -191,6 +192,8 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (typeof ps.autoAcceptFollowed === "boolean") if (typeof ps.autoAcceptFollowed === "boolean")
profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle; if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle;
if (typeof ps.preventAiLearning === "boolean")
profileUpdates.preventAiLearning = ps.preventAiLearning;
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat; if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat; if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
if (typeof ps.injectFeaturedNote === "boolean") if (typeof ps.injectFeaturedNote === "boolean")

View file

@ -28,7 +28,7 @@ export const meta = {
}, },
userId: { userId: {
type: "string", type: "string",
optional: false, optional: true,
nullable: false, nullable: false,
}, },
endpoint: { endpoint: {

View file

@ -53,7 +53,7 @@ const nodeinfo2 = async () => {
name: "calckey", name: "calckey",
version: config.version, version: config.version,
repository: meta.repositoryUrl, repository: meta.repositoryUrl,
homepage: "https://calckey.cloud", homepage: "https://calckey.org/",
}, },
protocols: ["activitypub"], protocols: ["activitypub"],
services: { services: {

View file

@ -1,4 +1,4 @@
html, body { html {
background-color: var(--bg); background-color: var(--bg);
color: var(--fg); color: var(--fg);
} }

View file

@ -24,6 +24,9 @@ block meta
unless privateMode unless privateMode
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning
meta(name='robots' content='noai')
meta(name='robots' content='noimageai')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View file

@ -24,6 +24,9 @@ block meta
unless privateMode unless privateMode
if user.host || profile.noCrawle if user.host || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning
meta(name='robots' content='noai')
meta(name='robots' content='noimageai')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View file

@ -36,6 +36,9 @@ block meta
unless privateMode unless privateMode
if user.host || isRenote || profile.noCrawle if user.host || isRenote || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning
meta(name='robots' content='noai')
meta(name='robots' content='noimageai')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View file

@ -24,6 +24,9 @@ block meta
unless privateMode unless privateMode
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning
meta(name='robots' content='noai')
meta(name='robots' content='noimageai')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View file

@ -23,6 +23,9 @@ block meta
unless privateMode unless privateMode
if user.host || profile.noCrawle if user.host || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLearning
meta(name='robots' content='noai')
meta(name='robots' content='noimageai')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id) meta(name='misskey:user-id' content=user.id)

View file

@ -80,6 +80,9 @@ export async function createNotification(
setTimeout(async () => { setTimeout(async () => {
const fresh = await Notifications.findOneBy({ id: notification.id }); const fresh = await Notifications.findOneBy({ id: notification.id });
if (fresh == null) return; // 既に削除されているかもしれない if (fresh == null) return; // 既に削除されているかもしれない
// We execute this before, because the server side "read" check doesnt work well with push notifications, the app and service worker will decide themself
// when it is best to show push notifications
pushNotification(notifieeId, "notification", packed);
if (fresh.isRead) return; if (fresh.isRead) return;
//#region ただしミュートしているユーザーからの通知なら無視 //#region ただしミュートしているユーザーからの通知なら無視
@ -95,7 +98,6 @@ export async function createNotification(
//#endregion //#endregion
publishMainStream(notifieeId, "unreadNotification", packed); publishMainStream(notifieeId, "unreadNotification", packed);
pushNotification(notifieeId, "notification", packed);
if (type === "follow") if (type === "follow")
sendEmailNotification.follow( sendEmailNotification.follow(

View file

@ -170,6 +170,9 @@ export default async (
) => ) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME // rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
const dontFederateInitially =
data.localOnly || data.visibility === "hidden";
// If you reply outside the channel, match the scope of the target. // If you reply outside the channel, match the scope of the target.
// TODO (I think it's a process that could be done on the client side, but it's server side for now.) // TODO (I think it's a process that could be done on the client side, but it's server side for now.)
if ( if (
@ -196,6 +199,7 @@ export default async (
if (data.channel != null) data.visibility = "public"; if (data.channel != null) data.visibility = "public";
if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
if (data.visibility === "hidden") data.visibility = "public";
// enforce silent clients on server // enforce silent clients on server
if ( if (
@ -447,7 +451,9 @@ export default async (
} }
} }
publishNotesStream(note); if (!dontFederateInitially) {
publishNotesStream(note);
}
if (note.replyId != null) { if (note.replyId != null) {
// Only provide the reply note id here as the recipient may not be authorized to see the note. // Only provide the reply note id here as the recipient may not be authorized to see the note.
publishNoteStream(note.replyId, "replied", { publishNoteStream(note.replyId, "replied", {
@ -546,7 +552,7 @@ export default async (
}); });
//#region AP deliver //#region AP deliver
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user) && !dontFederateInitially) {
(async () => { (async () => {
const noteActivity = await renderNoteOrRenoteActivity(data, note); const noteActivity = await renderNoteOrRenoteActivity(data, note);
const dm = new DeliverManager(user, noteActivity); const dm = new DeliverManager(user, noteActivity);
@ -606,7 +612,7 @@ export default async (
}); });
async function renderNoteOrRenoteActivity(data: Option, note: Note) { async function renderNoteOrRenoteActivity(data: Option, note: Note) {
if (data.localOnly || note.visibility !== "hidden") return null; if (data.localOnly) return null;
const content = const content =
data.renote && data.renote &&

View file

@ -144,7 +144,11 @@ export default async (
}); });
//#region deliver //#region deliver
if (Users.isLocalUser(user) && !note.localOnly) { if (
Users.isLocalUser(user) &&
!note.localOnly &&
note.visibility !== "hidden"
) {
const content = renderActivity(await renderLike(record, note)); const content = renderActivity(await renderLike(record, note));
const dm = new DeliverManager(user, content); const dm = new DeliverManager(user, content);
if (note.userHost !== null) { if (note.userHost !== null) {

View file

@ -45,21 +45,21 @@ export async function sendEmail(
<title>${subject}</title> <title>${subject}</title>
</head> </head>
<body style="background: #191724; padding: 16px; margin: 0; font-family: sans-serif; font-size: 14px;"> <body style="background: #191724; padding: 16px; margin: 0; font-family: sans-serif; font-size: 14px;">
<main style="max-width: 500px; margin: 0 auto; background: #1f1d2e; color: #e0def4;"> <main style="max-width: 500px; margin: 0 auto; background: #1f1d2e; color: #e0def4; border-radius: 20px;">
<header style="padding: 32px; background: #31748f; display: flex;"> <header style="padding: 32px; background: #31748f; color: #e0def4; display: flex; border-radius: 20px;">
<img src="${meta.logoImageUrl || meta.iconUrl || iconUrl}" style="max-width: 128px; max-height: 72px; vertical-align: bottom; margin-right: 16px;"/> <img src="${meta.logoImageUrl || meta.iconUrl || iconUrl}" style="max-width: 128px; max-height: 72px; vertical-align: bottom; margin-right: 16px;"/>
<h1 style="margin: 0 0 1em 0;">${meta.name}</h1> <h1 style="margin: 0 0 1em 0;">${meta.name}</h1>
</header> </header>
<article style="padding: 32px;"> <article style="padding: 32px;">
<h1>${subject}</h1> <h1 style="color: #ebbcba !important;">${subject}</h1>
<div>${html}</div> <div style="color: #e0def4;">${html}</div>
</article> </article>
<footer style="padding: 32px; border-top: solid 1px #26233a;"> <footer style="padding: 32px; border-top: solid 1px #26233a;">
<a href="${emailSettingUrl}" style="color: #31748f !important;">${"Email setting"}</a> <a href="${emailSettingUrl}" style="color: #9ccfd8 !important;">${"Email Settings"}</a>
</footer> </footer>
</main> </main>
<nav style="box-sizing: border-box; max-width: 500px; margin: 16px auto 0 auto; padding: 0 32px;"> <nav style="box-sizing: border-box; max-width: 500px; margin: 16px auto 0 auto; padding: 0 32px;">
<a href="${config.url}" style="color: #6e6a86 !important;">${config.host}</a> <a href="${config.url}" style="color: #9ccfd8 !important;">${config.host}</a>
</nav> </nav>
</body> </body>
</html>`, </html>`,

File diff suppressed because it is too large Load diff

View file

@ -55,33 +55,33 @@ describe("fromHtml", () => {
it("link with different text", () => { it("link with different text", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/b">c</a> d</p>'), fromHtml('<p>a <a href="https://calckey.org/b">c</a> d</p>'),
"a [c](https://example.com/b) d", "a [c](https://calckey.org/b) d",
); );
}); });
it("link with different text, but not encoded", () => { it("link with different text, but not encoded", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/ä">c</a> d</p>'), fromHtml('<p>a <a href="https://calckey.org/ä">c</a> d</p>'),
"a [c](<https://example.com/ä>) d", "a [c](<https://calckey.org/ä>) d",
); );
}); });
it("link with same text", () => { it("link with same text", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml( fromHtml(
'<p>a <a href="https://example.com/b">https://example.com/b</a> d</p>', '<p>a <a href="https://calckey.org/b">https://calckey.org/b</a> d</p>',
), ),
"a https://example.com/b d", "a https://calckey.org/b d",
); );
}); });
it("link with same text, but not encoded", () => { it("link with same text, but not encoded", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml( fromHtml(
'<p>a <a href="https://example.com/ä">https://example.com/ä</a> d</p>', '<p>a <a href="https://calckey.org/ä">https://calckey.org/ä</a> d</p>',
), ),
"a <https://example.com/ä> d", "a <https://calckey.org/ä> d",
); );
}); });
@ -98,8 +98,8 @@ describe("fromHtml", () => {
it("link without text", () => { it("link without text", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/b"></a> d</p>'), fromHtml('<p>a <a href="https://calckey.org/b"></a> d</p>'),
"a https://example.com/b d", "a https://calckey.org/b d",
); );
}); });
@ -110,15 +110,15 @@ describe("fromHtml", () => {
it("mention", () => { it("mention", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml( fromHtml(
'<p>a <a href="https://example.com/@user" class="u-url mention">@user</a> d</p>', '<p>a <a href="https://calckey.org/@user" class="u-url mention">@user</a> d</p>',
), ),
"a @user@example.com d", "a @user@calckey.org d",
); );
}); });
it("hashtag", () => { it("hashtag", () => {
assert.deepStrictEqual( assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/tags/a">#a</a> d</p>', [ fromHtml('<p>a <a href="https://calckey.org/tags/a">#a</a> d</p>', [
"#a", "#a",
]), ]),
"a #a d", "a #a d",

View file

@ -707,6 +707,7 @@ export type Endpoints = {
carefulBot?: boolean; carefulBot?: boolean;
autoAcceptFollowed?: boolean; autoAcceptFollowed?: boolean;
noCrawle?: boolean; noCrawle?: boolean;
preventAiLearning?: boolean;
isBot?: boolean; isBot?: boolean;
isCat?: boolean; isCat?: boolean;
injectFeaturedNote?: boolean; injectFeaturedNote?: boolean;

View file

@ -104,6 +104,7 @@ export type MeDetailed = UserDetailed & {
mutedWords: string[][]; mutedWords: string[][];
mutingNotificationTypes: string[]; mutingNotificationTypes: string[];
noCrawle: boolean; noCrawle: boolean;
preventAiLearning: boolean;
receiveAnnouncementEmail: boolean; receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean; usePasswordLessLogin: boolean;
[other: string]: any; [other: string]: any;

View file

@ -9,7 +9,6 @@
}, },
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.0.2", "@discordapp/twemoji": "14.0.2",
"@khmyznikov/pwa-install": "^0.2.0",
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9", "@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0", "@rollup/plugin-json": "4.1.0",

View file

@ -3,7 +3,7 @@
<template #empty> <template #empty>
<div class="_fullinfo"> <div class="_fullinfo">
<img <img
src="/static-assets/badges/info.png" src="/static-assets/badges/not-found.png"
class="_ghost" class="_ghost"
:alt="i18n.ts.notFound" :alt="i18n.ts.notFound"
/> />

View file

@ -144,6 +144,20 @@ export default defineComponent({
padding: var(--x-padding); padding: var(--x-padding);
-webkit-backdrop-filter: var(--blur, blur(8px)); -webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(20px)); backdrop-filter: var(--blur, blur(20px));
margin-inline: -12px;
padding-inline: 12px;
mask: linear-gradient(
to right,
transparent,
black 12px calc(100% - 12px),
transparent
);
-webkit-mask: linear-gradient(
to right,
transparent,
black 12px calc(100% - 12px),
transparent
);
> .title { > .title {
margin: 0; margin: 0;

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-google"> <div class="mk-google" @click.stop>
<input v-model="query" type="search" :placeholder="q" /> <input v-model="query" type="search" :placeholder="q" />
<button @click="search"> <button @click="search">
<i class="ph-magnifying-glass ph-bold ph-lg"></i> <i class="ph-magnifying-glass ph-bold ph-lg"></i>

View file

@ -1,5 +1,5 @@
<template> <template>
<div v-if="hide" class="qjewsnkg" @click="hide = false"> <button v-if="hide" class="qjewsnkg" @click="hide = false">
<ImgWithBlurhash <ImgWithBlurhash
class="bg" class="bg"
:hash="image.blurhash" :hash="image.blurhash"
@ -15,7 +15,7 @@
<span style="display: block">{{ i18n.ts.clickToShow }}</span> <span style="display: block">{{ i18n.ts.clickToShow }}</span>
</div> </div>
</div> </div>
</div> </button>
<div v-else class="gqnyydlz"> <div v-else class="gqnyydlz">
<a :href="image.url" :title="image.name"> <a :href="image.url" :title="image.name">
<ImgWithBlurhash <ImgWithBlurhash
@ -79,6 +79,7 @@ watch(
<style lang="scss" scoped> <style lang="scss" scoped>
.qjewsnkg { .qjewsnkg {
all: unset;
position: relative; position: relative;
> .bg { > .bg {
@ -103,6 +104,10 @@ watch(
color: #fff; color: #fff;
} }
} }
&:focus-visible {
border: 2px solid var(--accent);
}
} }
.gqnyydlz { .gqnyydlz {

View file

@ -188,6 +188,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
margin-top: 4px; margin-top: 4px;
border-radius: var(--radius); border-radius: var(--radius);
overflow: hidden; overflow: hidden;
pointer-events: none;
&:before { &:before {
content: ""; content: "";
@ -207,6 +208,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
> * { > * {
overflow: hidden; overflow: hidden;
border-radius: 6px; border-radius: 6px;
pointer-events: all;
} }
&[data-count="1"] { &[data-count="1"] {

View file

@ -13,12 +13,6 @@
> >
<template v-for="(item, i) in items2"> <template v-for="(item, i) in items2">
<div v-if="item === null" class="divider"></div> <div v-if="item === null" class="divider"></div>
<template
v-else-if="
item.hidden ||
(item.visible !== undefined && !item.visible)
"
/>
<span v-else-if="item.type === 'label'" class="label item"> <span v-else-if="item.type === 'label'" class="label item">
<span :style="item.textStyle || ''">{{ <span :style="item.textStyle || ''">{{
item.text item.text
@ -27,7 +21,6 @@
<span <span
v-else-if="item.type === 'pending'" v-else-if="item.type === 'pending'"
class="pending item" class="pending item"
:class="classMap(item.classes)"
> >
<span><MkEllipsis /></span> <span><MkEllipsis /></span>
</span> </span>
@ -35,7 +28,6 @@
v-else-if="item.type === 'link'" v-else-if="item.type === 'link'"
:to="item.to" :to="item.to"
class="_button item" class="_button item"
:class="classMap(item.classes)"
@click.passive="close(true)" @click.passive="close(true)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
@ -64,7 +56,6 @@
:target="item.target" :target="item.target"
:download="item.download" :download="item.download"
class="_button item" class="_button item"
:class="classMap(item.classes)"
@click="close(true)" @click="close(true)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
@ -82,12 +73,9 @@
></span> ></span>
</a> </a>
<button <button
v-else-if="item.type === 'user'" v-else-if="item.type === 'user' && !items.hidden"
class="_button item" class="_button item"
:class="{ :class="{ active: item.active }"
active: item.active,
...classMap(item.classes),
}"
:disabled="item.active" :disabled="item.active"
@click="clicked(item.action, $event)" @click="clicked(item.action, $event)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@ -105,7 +93,6 @@
<span <span
v-else-if="item.type === 'switch'" v-else-if="item.type === 'switch'"
class="item" class="item"
:class="classMap(item.classes)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
> >
@ -117,29 +104,10 @@
>{{ item.text }}</FormSwitch >{{ item.text }}</FormSwitch
> >
</span> </span>
<span
v-else-if="item.type === 'input'"
:tabindex="i"
class="item"
:class="classMap(item.classes)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<FormInput
v-model="item.ref"
:disabled="item.disabled"
class="form-input"
:required="item.required"
:placeholder="item.placeholder"
/>
</span>
<button <button
v-else-if="item.type === 'parent'" v-else-if="item.type === 'parent'"
class="_button item parent" class="_button item parent"
:class="{ :class="{ childShowing: childShowingItem === item }"
childShowing: childShowingItem === item,
...classMap(item.classes),
}"
@mouseenter="showChildren(item, $event)" @mouseenter="showChildren(item, $event)"
@click="showChildren(item, $event)" @click="showChildren(item, $event)"
> >
@ -158,13 +126,9 @@
></span> ></span>
</button> </button>
<button <button
v-else v-else-if="!item.hidden"
class="_button item" class="_button item"
:class="{ :class="{ danger: item.danger, active: item.active }"
danger: item.danger,
active: item.active,
...classMap(item.classes),
}"
:disabled="item.active" :disabled="item.active"
@click="clicked(item.action, $event)" @click="clicked(item.action, $event)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@ -222,14 +186,7 @@ import {
} from "vue"; } from "vue";
import { focusPrev, focusNext } from "@/scripts/focus"; import { focusPrev, focusNext } from "@/scripts/focus";
import FormSwitch from "@/components/form/switch.vue"; import FormSwitch from "@/components/form/switch.vue";
import FormInput from "@/components/form/input.vue"; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
import {
MenuItem,
InnerMenuItem,
MenuPending,
MenuAction,
MenuClasses,
} from "@/types/menu";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { FocusTrap } from "focus-trap-vue"; import { FocusTrap } from "focus-trap-vue";
@ -287,18 +244,6 @@ watch(
let childMenu = $ref<MenuItem[] | null>(); let childMenu = $ref<MenuItem[] | null>();
let childTarget = $ref<HTMLElement | null>(); let childTarget = $ref<HTMLElement | null>();
function classMap(classes?: MenuClasses) {
if (!classes) return {};
return (Array.isArray(classes) ? classes : classes.value).reduce(
(acc, cls) => {
acc[cls] = true;
return acc;
},
{}
);
}
function closeChild() { function closeChild() {
childMenu = null; childMenu = null;
childShowingItem = null; childShowingItem = null;

View file

@ -79,7 +79,7 @@
<div class="body"> <div class="body">
<MkSubNoteContent <MkSubNoteContent
class="text" class="text"
:note="note" :note="appearNote"
:detailed="true" :detailed="true"
:detailedView="detailedView" :detailedView="detailedView"
:parentId="appearNote.parentId" :parentId="appearNote.parentId"
@ -139,7 +139,6 @@
class="button" class="button"
:note="appearNote" :note="appearNote"
:count="appearNote.renoteCount" :count="appearNote.renoteCount"
:renoteCw="note.cw"
/> />
<XStarButtonNoEmoji <XStarButtonNoEmoji
v-if="!enableEmojiReactions" v-if="!enableEmojiReactions"
@ -198,7 +197,7 @@
</div> </div>
</article> </article>
</div> </div>
<div v-else class="muted" @click="muted.muted = false"> <button v-else class="muted _button" @click="muted.muted = false">
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small"> <I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
<template #name> <template #name>
<MkA <MkA
@ -213,7 +212,7 @@
<b class="_blur_text">{{ muted.matched.join(", ") }}</b> <b class="_blur_text">{{ muted.matched.join(", ") }}</b>
</template> </template>
</I18n> </I18n>
</div> </button>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -748,5 +747,6 @@ function readPromo() {
padding: 8px; padding: 8px;
text-align: center; text-align: center;
opacity: 0.7; opacity: 0.7;
width: 100%;
} }
</style> </style>

View file

@ -12,7 +12,7 @@
<MkNoteSub <MkNoteSub
v-for="note in conversation" v-for="note in conversation"
:key="note.id" :key="note.id"
class="reply-to-more" class="reply-to"
:note="note" :note="note"
/> />
<MkNoteSub <MkNoteSub
@ -345,7 +345,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
replies.value.splice(found, 0, replyNote); replies.value.splice(found, 0, replyNote);
if (found === 0) { if (found === 0) {
directReplies.value.unshift(replyNote); directReplies.value.push(replyNote);
} }
break; break;
@ -414,15 +414,7 @@ onUnmounted(() => {
} }
> .reply-to { > .reply-to {
margin-bottom: -16px; margin-bottom: -16px;
} padding-bottom: 16px;
> .reply-to-more {
// opacity: 0.7;
cursor: pointer;
@media (pointer: coarse) {
cursor: default;
}
} }
> .renote { > .renote {
@ -478,16 +470,17 @@ onUnmounted(() => {
> .article { > .article {
padding-block: 28px 6px; padding-block: 28px 6px;
&:last-child { padding-top: 12px;
padding-bottom: 24px; font-size: 1.1rem;
}
font-size: 1.1em;
overflow: clip; overflow: clip;
outline: none; outline: none;
scroll-margin-top: calc(var(--stickyTop) + 20vh); scroll-margin-top: calc(var(--stickyTop) + 20vh);
:deep(.article) { :deep(.article) {
cursor: unset; cursor: unset;
} }
&:first-of-type {
padding-top: 28px;
}
} }
> .reply { > .reply {
@ -503,7 +496,6 @@ onUnmounted(() => {
// Hover // Hover
.reply :deep(.main), .reply :deep(.main),
.reply-to, .reply-to,
.reply-to-more,
:deep(.more) { :deep(.more) {
position: relative; position: relative;
&::before { &::before {
@ -517,14 +509,19 @@ onUnmounted(() => {
transition: opacity 0.2s; transition: opacity 0.2s;
z-index: -1; z-index: -1;
} }
&.reply-to, &.reply-to {
&.reply-to-more {
&::before { &::before {
inset: 0px 8px; inset: 0px 8px;
} }
&:not(.max-width_450px)::before {
bottom: 12px;
}
&:first-of-type::before { &:first-of-type::before {
top: 12px; top: 12px;
} }
&.reply.max-width_500px:first-of-type::before {
top: 4px;
}
} }
// &::after { // &::after {
// content: ""; // content: "";
@ -557,8 +554,11 @@ onUnmounted(() => {
// } // }
} }
&.max-width_500px {
font-size: 0.9em;
}
&.max-width_450px { &.max-width_450px {
> .reply-to-more:first-child { > .reply-to:first-child {
padding-top: 14px; padding-top: 14px;
} }
> .renote { > .renote {

View file

@ -69,7 +69,6 @@
class="button" class="button"
:note="appearNote" :note="appearNote"
:count="appearNote.renoteCount" :count="appearNote.renoteCount"
:renoteCw="note.cw"
/> />
<XStarButtonNoEmoji <XStarButtonNoEmoji
v-if="!enableEmojiReactions" v-if="!enableEmojiReactions"
@ -362,6 +361,7 @@ function noteClick(e) {
> .main { > .main {
display: flex; display: flex;
cursor: pointer;
> .avatar-container { > .avatar-container {
margin-right: 8px; margin-right: 8px;
@ -377,7 +377,6 @@ function noteClick(e) {
> .body { > .body {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
cursor: pointer;
margin: 0 -200px; margin: 0 -200px;
padding: 0 200px; padding: 0 200px;
overflow: clip; overflow: clip;

View file

@ -58,13 +58,12 @@ defineExpose({
.giivymft { .giivymft {
&.noGap { &.noGap {
> .notes { > .notes {
background: var(--panel); background: var(--panel) !important;
border-radius: var(--radius); border-radius: var(--radius);
} }
} }
&:not(.noGap) { &:not(.noGap) {
> .notes { > .notes {
background: var(--bg);
.qtqtichx { .qtqtichx {
background: var(--panel); background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);

View file

@ -17,19 +17,17 @@
:max-height="maxHeight" :max-height="maxHeight"
:as-drawer="type === 'drawer'" :as-drawer="type === 'drawer'"
class="sfhdhdhq" class="sfhdhdhq"
:class="{ :class="{ drawer: type === 'drawer' }"
drawer: type === 'drawer',
...classMap(classes),
}"
@close="modal.close()" @close="modal.close()"
/> />
</MkModal> </MkModal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuClasses, MenuItem } from "@/types/menu"; import { MenuItem } from "@/types/menu";
defineProps<{ defineProps<{
items: MenuItem[]; items: MenuItem[];
@ -37,7 +35,6 @@ defineProps<{
width?: number; width?: number;
viaKeyboard?: boolean; viaKeyboard?: boolean;
src?: any; src?: any;
classes?: MenuClasses;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -45,18 +42,6 @@ const emit = defineEmits<{
}>(); }>();
let modal = $ref<InstanceType<typeof MkModal>>(); let modal = $ref<InstanceType<typeof MkModal>>();
function classMap(classes?: MenuClasses) {
if (!classes) return {};
return (Array.isArray(classes) ? classes : classes.value).reduce(
(acc, cls) => {
acc[cls] = true;
return acc;
},
{}
);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,8 +1,9 @@
<template> <template>
<div <section
v-size="{ max: [310, 500] }" v-size="{ max: [310, 500] }"
class="gafaadew" class="gafaadew"
:class="{ modal, _popup: modal }" :class="{ modal, _popup: modal }"
:aria-label="i18n.ts._pages.blocks.post"
@dragover.stop="onDragover" @dragover.stop="onDragover"
@dragenter="onDragenter" @dragenter="onDragenter"
@dragleave="onDragleave" @dragleave="onDragleave"
@ -82,7 +83,7 @@
<div v-if="quoteId" class="with-quote"> <div v-if="quoteId" class="with-quote">
<i class="ph-quotes ph-bold ph-lg"></i> <i class="ph-quotes ph-bold ph-lg"></i>
{{ i18n.ts.quoteAttached {{ i18n.ts.quoteAttached
}}<button @click="quoteId = null"> }}<button class="_button" @click="quoteId = null">
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
</div> </div>
@ -218,7 +219,7 @@
/> />
</datalist> </datalist>
</div> </div>
</div> </section>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -1120,11 +1121,16 @@ onMounted(() => {
} }
> .with-quote { > .with-quote {
margin: 0 0 8px 0; display: flex;
align-items: center;
gap: 0.4em;
margin-inline: 24px;
margin-bottom: 12px;
color: var(--accent); color: var(--accent);
> button { > button {
padding: 4px 8px; display: flex;
padding: 0;
color: var(--accentAlpha04); color: var(--accentAlpha04);
&:hover { &:hover {

View file

@ -4,7 +4,6 @@
ref="buttonRef" ref="buttonRef"
v-tooltip.noDelay.bottom="i18n.ts.renote" v-tooltip.noDelay.bottom="i18n.ts.renote"
class="eddddedb _button canRenote" class="eddddedb _button canRenote"
:class="{ addCw }"
@click="renote(false, $event)" @click="renote(false, $event)"
> >
<i class="ph-repeat ph-bold ph-lg"></i> <i class="ph-repeat ph-bold ph-lg"></i>
@ -27,29 +26,20 @@ import { useTooltip } from "@/scripts/use-tooltip";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { MenuItem } from "@/types/menu"; import { MenuItem } from "@/types/menu";
import { add } from "date-fns";
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
count: number; count: number;
renoteCw?: string | null;
}>(); }>();
const buttonRef = ref<HTMLElement>(); const buttonRef = ref<HTMLElement>();
const addCw = ref<boolean>(!!props.renoteCw);
const cwInput = ref<string>(props.renoteCw ?? "");
const canRenote = computed( const canRenote = computed(
() => () =>
["public", "home", "hidden"].includes(props.note.visibility) || ["public", "home"].includes(props.note.visibility) ||
props.note.userId === $i.id props.note.userId === $i.id
); );
const getCw = () =>
addCw.value && cwInput.value !== ""
? cwInput.value
: props.note.cw ?? undefined;
useTooltip(buttonRef, async (showing) => { useTooltip(buttonRef, async (showing) => {
const renotes = await os.api("notes/renotes", { const renotes = await os.api("notes/renotes", {
noteId: props.note.id, noteId: props.note.id,
@ -86,10 +76,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
let buttonActions: Array<MenuItem> = []; let buttonActions: Array<MenuItem> = [];
if ( if (props.note.visibility === "public") {
props.note.visibility === "public" ||
props.note.visibility === "hidden"
) {
buttonActions.push({ buttonActions.push({
text: i18n.ts.renote, text: i18n.ts.renote,
textStyle: "font-weight: bold", textStyle: "font-weight: bold",
@ -99,7 +86,6 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
os.api("notes/create", { os.api("notes/create", {
renoteId: props.note.id, renoteId: props.note.id,
visibility: "public", visibility: "public",
cw: getCw(),
}); });
const el = const el =
ev && ev &&
@ -117,7 +103,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
}); });
} }
if (["public", "home", "hidden"].includes(props.note.visibility)) { if (["public", "home"].includes(props.note.visibility)) {
buttonActions.push({ buttonActions.push({
text: `${i18n.ts.renote} (${i18n.ts._visibility.home})`, text: `${i18n.ts.renote} (${i18n.ts._visibility.home})`,
icon: "ph-house ph-bold ph-lg", icon: "ph-house ph-bold ph-lg",
@ -126,7 +112,6 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
os.api("notes/create", { os.api("notes/create", {
renoteId: props.note.id, renoteId: props.note.id,
visibility: "home", visibility: "home",
cw: getCw(),
}); });
const el = const el =
ev && ev &&
@ -154,7 +139,6 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id, renoteId: props.note.id,
visibility: "specified", visibility: "specified",
visibleUserIds: props.note.visibleUserIds, visibleUserIds: props.note.visibleUserIds,
cw: getCw(),
}); });
const el = const el =
ev && ev &&
@ -179,7 +163,6 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
os.api("notes/create", { os.api("notes/create", {
renoteId: props.note.id, renoteId: props.note.id,
visibility: "followers", visibility: "followers",
cw: getCw(),
}); });
const el = const el =
ev && ev &&
@ -197,30 +180,44 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
}); });
} }
const showQuote = !defaultStore.state.seperateRenoteQuote; if (canRenote) {
if (!props.note.cw || props.note.cw === "") {
buttonActions.push({ buttonActions.push({
type: "switch", text: `${i18n.ts.renote} (${i18n.ts.local})`,
ref: addCw, icon: "ph-hand-fist ph-bold ph-lg",
text: "Add content warning", danger: false,
hidden: addCw, action: () => {
os.api(
"notes/create",
props.note.visibility === "specified"
? {
renoteId: props.note.id,
visibility: props.note.visibility,
visibleUserIds: props.note.visibleUserIds,
localOnly: true,
}
: {
renoteId: props.note.id,
visibility: props.note.visibility,
localOnly: true,
}
);
const el =
ev &&
((ev.currentTarget ?? ev.target) as
| HTMLElement
| null
| undefined);
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + el.offsetWidth / 2;
const y = rect.top + el.offsetHeight / 2;
os.popup(Ripple, { x, y }, {}, "end");
}
},
}); });
buttonActions.push({
type: "input",
ref: cwInput,
placeholder: "Content warning",
required: true,
visible: addCw,
});
if (showQuote || hasRenotedBefore) {
buttonActions.push(null);
}
} }
if (showQuote) { if (!defaultStore.state.seperateRenoteQuote) {
buttonActions.push({ buttonActions.push({
text: i18n.ts.quote, text: i18n.ts.quote,
icon: "ph-quotes ph-bold ph-lg", icon: "ph-quotes ph-bold ph-lg",
@ -245,10 +242,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
}, },
}); });
} }
os.popupMenu(buttonActions, buttonRef.value, { viaKeyboard });
os.popupMenu(buttonActions, buttonRef.value, {
viaKeyboard,
});
}; };
</script> </script>

View file

@ -55,7 +55,7 @@ export default defineComponent({
this.$i ? this.$i.username : "guest" this.$i ? this.$i.username : "guest"
}.\nAlso, here is ${config.url} and [example link](${ }.\nAlso, here is ${config.url} and [example link](${
config.url config.url
}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`, }). for more details, see https://calckey.org.\nAs you know #misskey is open-source software.`,
}; };
}, },

View file

@ -1,137 +1,142 @@
<template> <template>
<div <p v-if="note.cw != null" class="cw">
:class="{ <MkA
hasCw: !!cw, v-if="!detailed && note.replyId"
cwHighlight, :to="`/notes/${note.replyId}`"
}" class="reply-icon"
> @click.stop
<p v-if="cw != null" class="cw"> >
<MkA <i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
v-if="!detailed && appearNote.replyId" </MkA>
:to="`/notes/${appearNote.replyId}`" <MkA
class="reply-icon" v-if="
@click.stop conversation &&
> note.renoteId &&
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i> note.renoteId != parentId &&
</MkA> !note.replyId
<MkA "
v-if=" :to="`/notes/${note.renoteId}`"
conversation && class="reply-icon"
appearNote.renoteId && @click.stop
appearNote.renoteId != parentId && >
!appearNote.replyId <i class="ph-quotes ph-bold ph-lg"></i>
" </MkA>
:to="`/notes/${appearNote.renoteId}`" <Mfm
class="reply-icon" v-if="note.cw != ''"
@click.stop class="text"
> :text="note.cw"
<i class="ph-quotes ph-bold ph-lg"></i> :author="note.user"
</MkA> :i="$i"
<Mfm :custom-emojis="note.emojis"
v-if="cw != ''" />
class="text" </p>
:text="cw" <div class="wrmlmaau">
:author="appearNote.user" <div
:i="$i" class="content"
:custom-emojis="appearNote.emojis" :class="{
collapsed,
isLong,
showContent: note.cw && !showContent,
disableAnim: disableMfm,
}"
>
<XCwButton
ref="cwButton"
v-if="note.cw && !showContent"
v-model="showContent"
:note="note"
v-on:keydown="focusFooter"
/> />
</p>
<div class="wrmlmaau">
<div <div
class="content" class="body"
:class="{ collapsed, isLong, showContent: cw && !showContent }" v-bind="{
'aria-label': !showContent ? '' : null,
tabindex: !showContent ? '-1' : null,
}"
> >
<XCwButton <span v-if="note.deletedAt" style="opacity: 0.5"
ref="cwButton" >({{ i18n.ts.deleted }})</span
v-if="cw && !showContent"
v-model="showContent"
:note="appearNote"
v-on:keydown="focusFooter"
/>
<div
class="body"
v-bind="{
'aria-label': !showContent ? '' : null,
tabindex: !showContent ? '-1' : null,
}"
> >
<span v-if="appearNote.deletedAt" style="opacity: 0.5" <template v-if="!note.cw">
>({{ i18n.ts.deleted }})</span
>
<template v-if="!cw">
<MkA
v-if="!detailed && appearNote.replyId"
:to="`/notes/${appearNote.replyId}`"
class="reply-icon"
@click.stop
>
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
</MkA>
<MkA
v-if="
conversation &&
appearNote.renoteId &&
appearNote.renoteId != parentId &&
!appearNote.replyId
"
:to="`/notes/${appearNote.renoteId}`"
class="reply-icon"
@click.stop
>
<i class="ph-quotes ph-bold ph-lg"></i>
</MkA>
</template>
<Mfm
v-if="appearNote.text"
:text="appearNote.text"
:author="appearNote.user"
:i="$i"
:custom-emojis="appearNote.emojis"
/>
<MkA <MkA
v-if="!detailed && appearNote.renoteId" v-if="!detailed && note.replyId"
class="rp" :to="`/notes/${note.replyId}`"
:to="`/notes/${appearNote.renoteId}`" class="reply-icon"
>{{ i18n.ts.quoteAttached }}: ...</MkA @click.stop
> >
<div v-if="appearNote.files.length > 0" class="files"> <i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
<XMediaList :media-list="appearNote.files" /> </MkA>
</div> <MkA
<XPoll v-if="
v-if="appearNote.poll" conversation &&
:note="appearNote" note.renoteId &&
class="poll" note.renoteId != parentId &&
/> !note.replyId
<template v-if="detailed"> "
<MkUrlPreview :to="`/notes/${note.renoteId}`"
v-for="url in urls" class="reply-icon"
:key="url" @click.stop
:url="url" >
:compact="true" <i class="ph-quotes ph-bold ph-lg"></i>
:detail="false" </MkA>
class="url-preview" </template>
/> <Mfm
<div v-if="note.text"
v-if="appearNote.renote" :text="note.text"
class="renote" :author="note.user"
@click.stop="emit('push', appearNote.renote)" :i="$i"
> :custom-emojis="note.emojis"
<XNoteSimple :note="appearNote.renote" /> />
</div> <MkA
</template> v-if="!detailed && note.renoteId"
<div class="rp"
v-if="cw && !showContent" :to="`/notes/${note.renoteId}`"
tabindex="0" >{{ i18n.ts.quoteAttached }}: ...</MkA
v-on:focus="cwButton?.focus()" >
></div> <div v-if="note.files.length > 0" class="files">
<XMediaList :media-list="note.files" />
</div> </div>
<XShowMoreButton <XPoll v-if="note.poll" :note="note" class="poll" />
v-if="isLong" <template v-if="detailed">
v-model="collapsed" <MkUrlPreview
></XShowMoreButton> v-for="url in urls"
<XCwButton v-if="cw" v-model="showContent" :note="appearNote" /> :key="url"
:url="url"
:compact="true"
:detail="false"
class="url-preview"
/>
<div
v-if="note.renote"
class="renote"
@click.stop="emit('push', note.renote)"
>
<XNoteSimple :note="note.renote" />
</div>
</template>
<div
v-if="note.cw && !showContent"
tabindex="0"
v-on:focus="cwButton?.focus()"
></div>
</div> </div>
<XShowMoreButton
v-if="isLong"
v-model="collapsed"
></XShowMoreButton>
<XCwButton v-if="note.cw" v-model="showContent" :note="note" />
</div> </div>
<MkButton
v-if="hasMfm && defaultStore.state.animatedMfm"
@click.stop="toggleMfm"
>
<template v-if="disableMfm">
<i class="ph-play ph-bold"></i> {{ i18n.ts._mfm.play }}
</template>
<template v-else>
<i class="ph-stop ph-bold"></i> {{ i18n.ts._mfm.stop }}
</template>
</MkButton>
</div> </div>
</template> </template>
@ -139,13 +144,16 @@
import { ref } from "vue"; import { ref } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import * as os from "@/os";
import XNoteSimple from "@/components/MkNoteSimple.vue"; import XNoteSimple from "@/components/MkNoteSimple.vue";
import XMediaList from "@/components/MkMediaList.vue"; import XMediaList from "@/components/MkMediaList.vue";
import XPoll from "@/components/MkPoll.vue"; import XPoll from "@/components/MkPoll.vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue"; import MkUrlPreview from "@/components/MkUrlPreview.vue";
import XShowMoreButton from "@/components/MkShowMoreButton.vue"; import XShowMoreButton from "@/components/MkShowMoreButton.vue";
import XCwButton from "@/components/MkCwButton.vue"; import XCwButton from "@/components/MkCwButton.vue";
import MkButton from "@/components/MkButton.vue";
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm"; import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -162,34 +170,46 @@ const emit = defineEmits<{
(ev: "focusfooter"): void; (ev: "focusfooter"): void;
}>(); }>();
const note = props.note;
const isRenote =
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null;
let appearNote = $computed(() =>
isRenote ? (note.renote as misskey.entities.Note) : note
);
let cw = $computed(() => appearNote.cw || note.cw);
const cwHighlight = defaultStore.state.highlightCw;
const cwButton = ref<HTMLElement>(); const cwButton = ref<HTMLElement>();
const isLong = const isLong =
!props.detailedView && !props.detailedView &&
!cw && props.note.cw == null &&
appearNote.text != null && props.note.text != null &&
(appearNote.text.split("\n").length > 9 || appearNote.text.length > 500); (props.note.text.split("\n").length > 9 || props.note.text.length > 500);
const collapsed = $ref(!cw && isLong); const collapsed = $ref(props.note.cw == null && isLong);
const urls = appearNote.text const urls = props.note.text
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5) ? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
: null; : null;
let showContent = $ref(false); let showContent = $ref(false);
const mfms = props.note.text
? extractMfmWithAnimation(mfm.parse(props.note.text))
: null;
const hasMfm = $ref(mfms.length > 0);
let disableMfm = $ref(hasMfm && defaultStore.state.animatedMfm);
async function toggleMfm() {
if (disableMfm) {
if (!defaultStore.state.animatedMfmWarnShown) {
const { canceled } = await os.confirm({
type: "warning",
text: i18n.ts._mfm.warn,
});
if (canceled) return;
defaultStore.set("animatedMfmWarnShown", true);
}
disableMfm = false;
} else {
disableMfm = true;
}
}
function focusFooter(ev) { function focusFooter(ev) {
if (ev.key == "Tab" && !ev.getModifierState("Shift")) { if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
emit("focusfooter"); emit("focusfooter");
@ -219,25 +239,9 @@ function focusFooter(ev) {
overflow-wrap: break-word; overflow-wrap: break-word;
> .text { > .text {
margin-right: 8px; margin-right: 8px;
padding-inline-start: 0.25em;
}
}
.cwHighlight.hasCw {
outline: 1px dotted var(--cwFg);
border-radius: 5px;
> .wrmlmaau {
padding-inline-start: 0.25em;
}
> .cw {
background-color: var(--cwFg);
color: var(--cwBg);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
> .reply-icon {
color: var(--cwBg);
}
} }
} }
.wrmlmaau { .wrmlmaau {
.content { .content {
overflow-wrap: break-word; overflow-wrap: break-word;
@ -267,9 +271,11 @@ function focusFooter(ev) {
> .url-preview { > .url-preview {
margin-top: 8px; margin-top: 8px;
} }
> .poll { > .poll {
font-size: 80%; font-size: 80%;
} }
> .renote { > .renote {
padding-top: 8px; padding-top: 8px;
> * { > * {
@ -284,6 +290,7 @@ function focusFooter(ev) {
} }
} }
} }
&.collapsed, &.collapsed,
&.showContent { &.showContent {
position: relative; position: relative;
@ -326,6 +333,13 @@ function focusFooter(ev) {
} }
} }
} }
&.disableAnim :deep(span) {
animation: none !important;
}
}
> :deep(button) {
margin-top: 10px;
} }
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="rrevdjwu" :class="{ grid }"> <nav class="rrevdjwu" :class="{ grid }">
<div v-for="group in def" class="group"> <section v-for="group in def" class="group">
<div v-if="group.title" class="title">{{ group.title }}</div> <div v-if="group.title" class="title">{{ group.title }}</div>
<div class="items"> <div class="items">
@ -48,8 +48,8 @@
</MkA> </MkA>
</template> </template>
</div> </div>
</div> </section>
</div> </nav>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -41,16 +41,27 @@
{{ i18n.ts.next }}</MkButton {{ i18n.ts.next }}</MkButton
> >
</div> </div>
<h2 class="_title title">
<i class="ph-info ph-bold ph-lg"></i>
{{ i18n.ts._tutorial.title }}
</h2>
<Transition name="fade"> <Transition name="fade">
<div v-if="tutorial === 0" key="1" class="_content"> <section v-if="tutorial === 0" key="1" class="_content">
<h2 class="_title title">
<i class="ph-info ph-bold ph-lg"></i>
{{ i18n.ts._tutorial.title }}
</h2>
<h3>{{ i18n.ts._tutorial.step1_1 }}</h3> <h3>{{ i18n.ts._tutorial.step1_1 }}</h3>
<div>{{ i18n.ts._tutorial.step1_2 }}</div> <div>{{ i18n.ts._tutorial.step1_2 }}</div>
</div> <!-- TODO: move to own slide -->
<div <!-- <FormSwitch v-model="autoplayMfm" class="_formBlock">
{{ i18n.ts._mfm.alwaysPlay }}
<template #caption>
<i class="ph-warning ph-bold ph-lg" style="color: var(--warn)"></i>
{{ i18n.ts._mfm.warn }}
</template>
</FormSwitch> -->
<FormSwitch v-model="reduceAnimation" class="_formBlock">
{{ i18n.ts.reduceUiAnimation }}
</FormSwitch>
</section>
<section
v-else-if="tutorial === 1" v-else-if="tutorial === 1"
key="2" key="2"
class="_content" class="_content"
@ -60,8 +71,8 @@
<br /> <br />
<XSettings :save-button="true" /> <XSettings :save-button="true" />
<br /> <br />
</div> </section>
<div <section
v-else-if="tutorial === 2" v-else-if="tutorial === 2"
key="3" key="3"
class="_content" class="_content"
@ -74,8 +85,8 @@
><i class="ph-check ph-bold ph-lg"></i> ><i class="ph-check ph-bold ph-lg"></i>
{{ i18n.ts.next }}</MkButton {{ i18n.ts.next }}</MkButton
> >
</div> </section>
<div <section
v-else-if="tutorial === 3" v-else-if="tutorial === 3"
key="4" key="4"
class="_content" class="_content"
@ -90,8 +101,8 @@
</I18n> </I18n>
<br /> <br />
<XPostForm class="post-form _block" /> <XPostForm class="post-form _block" />
</div> </section>
<div <section
v-else-if="tutorial === 4" v-else-if="tutorial === 4"
key="5" key="5"
class="_content" class="_content"
@ -160,8 +171,8 @@
</I18n> </I18n>
</li> </li>
</ul> </ul>
</div> </section>
<div <section
v-else-if="tutorial === 5" v-else-if="tutorial === 5"
key="6" key="6"
class="_content" class="_content"
@ -180,7 +191,7 @@
primary primary
show-only-to-register show-only-to-register
/> />
</div> </section>
</Transition> </Transition>
</div> </div>
</div> </div>
@ -189,7 +200,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { reactive, computed } from "vue";
import XSettings from "@/pages/settings/profile.vue"; import XSettings from "@/pages/settings/profile.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
@ -197,6 +208,7 @@ import XFeaturedUsers from "@/pages/explore.users.vue";
import XPostForm from "@/components/MkPostForm.vue"; import XPostForm from "@/components/MkPostForm.vue";
import MkSparkle from "@/components/MkSparkle.vue"; import MkSparkle from "@/components/MkSparkle.vue";
import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue"; import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue";
import FormSwitch from "@/components/form/switch.vue";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { $i } from "@/account"; import { $i } from "@/account";
@ -243,6 +255,21 @@ const tutorial = computed({
}, },
}); });
const autoplayMfm = computed(
defaultStore.makeGetterSetter(
"animatedMfm",
(v) => !v,
(v) => !v
)
);
const reduceAnimation = computed(
defaultStore.makeGetterSetter(
"animation",
(v) => !v,
(v) => !v
)
);
function close(res) { function close(res) {
tutorial.value = -1; tutorial.value = -1;
dialog.close(); dialog.close();

View file

@ -57,7 +57,8 @@ export default defineComponent({
MkRadio, MkRadio,
{ {
key: option.key, key: option.key,
value: option.props.value, value: option.props?.value,
disabled: option.props?.disabled,
modelValue: this.value, modelValue: this.value,
"onUpdate:modelValue": (value) => "onUpdate:modelValue": (value) =>
(this.value = value), (this.value = value),

View file

@ -1,10 +1,10 @@
<template> <template>
<div class="vrtktovh _formBlock"> <section class="vrtktovh _formBlock">
<div class="label"><slot name="label"></slot></div> <h3 class="label"><slot name="label"></slot></h3>
<div class="main _formRoot"> <div class="main _formRoot">
<slot></slot> <slot></slot>
</div> </div>
</div> </section>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup></script>
@ -29,6 +29,7 @@
> .label { > .label {
font-weight: bold; font-weight: bold;
margin: 1.5em 0 16px 0; margin: 1.5em 0 16px 0;
font-size: 1em;
&:empty { &:empty {
display: none; display: none;

View file

@ -1,5 +1,5 @@
<template> <template>
<div <header
v-if="show" v-if="show"
ref="el" ref="el"
class="fdidabkb" class="fdidabkb"
@ -7,12 +7,15 @@
:style="{ background: bg }" :style="{ background: bg }"
@click="onClick" @click="onClick"
> >
<i <button
@click="goBack()"
v-if="props.displayBackButton" v-if="props.displayBackButton"
class="_button button icon backButton"
@click.stop="goBack()"
@touchstart="preventDrag"
v-tooltip.noDelay="i18n.ts.goBack" v-tooltip.noDelay="i18n.ts.goBack"
class="icon backButton ph-caret-left ph-bold ph-lg" >
></i> <i class="ph-caret-left ph-bold ph-lg"></i>
</button>
<div v-if="narrow" class="buttons left" @click="openAccountMenu"> <div v-if="narrow" class="buttons left" @click="openAccountMenu">
<MkAvatar <MkAvatar
v-if="props.displayMyAvatar && $i" v-if="props.displayMyAvatar && $i"
@ -63,7 +66,7 @@
</div> </div>
</div> </div>
</div> </div>
<div ref="tabsEl" v-if="hasTabs" class="tabs"> <nav ref="tabsEl" v-if="hasTabs" class="tabs">
<button <button
v-for="tab in tabs" v-for="tab in tabs"
:ref="(el) => (tabRefs[tab.key] = el)" :ref="(el) => (tabRefs[tab.key] = el)"
@ -79,7 +82,7 @@
<span class="title">{{ tab.title }}</span> <span class="title">{{ tab.title }}</span>
</button> </button>
<div ref="tabHighlightEl" class="highlight"></div> <div ref="tabHighlightEl" class="highlight"></div>
</div> </nav>
</template> </template>
<div class="buttons right"> <div class="buttons right">
<template v-for="action in actions"> <template v-for="action in actions">
@ -94,7 +97,7 @@
</button> </button>
</template> </template>
</div> </div>
</div> </header>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -377,7 +380,8 @@ onUnmounted(() => {
display: none; display: none;
} }
> .button { > .button/*, @at-root .backButton*/ {
/* I don't know how to get this to work */
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View file

@ -102,35 +102,22 @@ export default defineComponent({
switch (token.props.name) { switch (token.props.name) {
case "tada": { case "tada": {
const speed = validTime(token.props.args.speed) || "1s"; const speed = validTime(token.props.args.speed) || "1s";
style = `font-size: 150%;${ style = `font-size: 150%; animation: tada ${speed} linear infinite both;`;
defaultStore.state.animatedMfm
? `animation: tada ${speed} linear infinite both;`
: ""
}`;
break; break;
} }
case "jelly": { case "jelly": {
const speed = validTime(token.props.args.speed) || "1s"; const speed = validTime(token.props.args.speed) || "1s";
style = style = `animation: mfm-rubberBand ${speed} linear infinite both;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-rubberBand ${speed} linear infinite both;`
: "";
break; break;
} }
case "twitch": { case "twitch": {
const speed = validTime(token.props.args.speed) || "0.5s"; const speed = validTime(token.props.args.speed) || "0.5s";
style = style = `animation: mfm-twitch ${speed} ease infinite;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-twitch ${speed} ease infinite;`
: "";
break; break;
} }
case "shake": { case "shake": {
const speed = validTime(token.props.args.speed) || "0.5s"; const speed = validTime(token.props.args.speed) || "0.5s";
style = style = `animation: mfm-shake ${speed} ease infinite;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-shake ${speed} ease infinite;`
: "";
break; break;
} }
case "spin": { case "spin": {
@ -145,38 +132,26 @@ export default defineComponent({
? "mfm-spinY" ? "mfm-spinY"
: "mfm-spin"; : "mfm-spin";
const speed = validTime(token.props.args.speed) || "1.5s"; const speed = validTime(token.props.args.speed) || "1.5s";
style = style = `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};`
: "";
break; break;
} }
case "jump": { case "jump": {
const speed = validTime(token.props.args.speed) || "0.75s"; const speed = validTime(token.props.args.speed) || "0.75s";
style = style = `animation: mfm-jump ${speed} linear infinite;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-jump ${speed} linear infinite;`
: "";
break; break;
} }
case "bounce": { case "bounce": {
const speed = validTime(token.props.args.speed) || "0.75s"; const speed = validTime(token.props.args.speed) || "0.75s";
style = style = `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;`
: "";
break; break;
} }
case "rainbow": { case "rainbow": {
const speed = validTime(token.props.args.speed) || "1s"; const speed = validTime(token.props.args.speed) || "1s";
style = style = `animation: mfm-rainbow ${speed} linear infinite;`;
defaultStore.state.animatedMfm && !reducedMotion()
? `animation: mfm-rainbow ${speed} linear infinite;`
: "";
break; break;
} }
case "sparkle": { case "sparkle": {
if (!(defaultStore.state.animatedMfm || reducedMotion())) { if (reducedMotion()) {
return genEl(token.children); return genEl(token.children);
} }
return h(MkSparkle, {}, genEl(token.children)); return h(MkSparkle, {}, genEl(token.children));

Some files were not shown because too many files have changed in this diff Show more