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 7ff5f1f72e
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
# Reserved usernames that only the administrator can register with
reservedUsernames:
- root
- admin
- administrator
- me
- system
reservedUsernames: [
'root',
'admin',
'administrator',
'me',
'system'
]
# Whether disable HSTS
#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/example.yml
!/.config/devenv.yml
!/.config/docker_example.env
!/.config/helm_values_example.yml
#docker dev config
/dev/docker-compose.yml
@ -56,3 +58,7 @@ packages/backend/assets/sounds/None.mp3
# old yarn
.yarn
yarn*
# Nix Development shell items
.devenv
.direnv

View file

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

View file

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

View file

@ -19,7 +19,6 @@
- MFM button
- Personal notes for all accounts
- Fully revamp non-logged-in screen
- Classic mode make instance icon bring up new context menu
- Lookup/details for post/file/instance
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
@ -118,6 +117,13 @@
- Sonic search
- Popular color schemes, including Nord, Gruvbox, and Catppuccin
- 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)

View file

@ -62,7 +62,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
@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.
All complaints will be reviewed and investigated promptly and fairly.

View file

@ -1,8 +1,8 @@
# 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.
## Localization (l10n)
Calckey uses [Weblate](hhttps://hosted.weblate.org/engage/calckey/) for localization management.
## Translation (i18n)
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.

View file

@ -35,7 +35,7 @@ FROM node:19-alpine
WORKDIR /calckey
# 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 . ./

View file

@ -7,6 +7,7 @@
[![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)
[![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)
[![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)
@ -46,6 +47,7 @@
# 🥂 Links
- 💸 OpenCollective: <https://opencollective.com/Calckey>
- 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
- Donate publicly to get your name on the Patron list!
- 🚢 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)
### 🐋 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
@ -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)
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)
- Web Proxy (one of the following)
- 🍀 Nginx (recommended)
- 🪶 Apache
- 🦦 Caddy
### 😗 Optional dependencies
- [FFmpeg](https://ffmpeg.org/) for video transcoding
- Full text search (choost one of the following)
- 🦔 [Sonic](https://crates.io/crates/sonic-server) (highly recommended!)
- Full text search (one of the following)
- 🦔 [Sonic](https://crates.io/crates/sonic-server) (recommended)
- [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
@ -116,6 +119,17 @@ corepack prepare pnpm@latest --activate
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
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
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/`
- 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.
</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!

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
name: misskey
version: 0.0.0
name: calckey
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.
*/}}
{{- define "misskey.name" -}}
{{- define "calckey.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- 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).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "misskey.fullname" -}}
{{- define "calckey.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- 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.
*/}}
{{- define "misskey.chart" -}}
{{- define "calckey.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "misskey.labels" -}}
helm.sh/chart: {{ include "misskey.chart" . }}
{{ include "misskey.selectorLabels" . }}
{{- define "calckey.labels" -}}
helm.sh/chart: {{ include "calckey.chart" . }}
{{ include "calckey.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
@ -45,18 +45,274 @@ app.kubernetes.io/managed-by: {{ .Release.Service }}
{{/*
Selector labels
*/}}
{{- define "misskey.selectorLabels" -}}
app.kubernetes.io/name: {{ include "misskey.name" . }}
{{- define "calckey.selectorLabels" -}}
app.kubernetes.io/name: {{ include "calckey.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "misskey.serviceAccountName" -}}
{{- define "calckey.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "misskey.fullname" .) .Values.serviceAccount.name }}
{{- default (include "calckey.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- 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(() => {
cy.resetState();
cy.viewport('macbook-16');
// インスタンス初期セットアップ
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à"
headlineMisskey: "Una xarxa social de codi obert, descentralitzada i gratuita per\
\ sempre \U0001F680"
introMisskey: "Benvinguts! Calckey es una plataforma social de codi obert, descentralitzada\
\ i gratuita per sempre! \U0001F680"
headlineMisskey: "Una xarxa social de codi obert, descentralitzada i gratuïta per\
\ a sempre! \U0001F680"
introMisskey: "Benvinguts! Calckey és una plataforma social de codi obert, descentralitzada\
\ i gratuïta per a sempre! \U0001F680"
monthAndDay: "{day}/{month}"
search: "Cercar"
search: "Cerca"
notifications: "Notificacions"
username: "Nom d'usuari"
password: "Contrasenya"
forgotPassword: "Contrasenya oblidada"
fetchingAsApObject: "Cercant en el Fediverse"
ok: "OK"
ok: "D'acord"
gotIt: "Ho he entès!"
cancel: "Cancel·lar"
cancel: "Cancel·la"
enterUsername: "Introdueix el teu nom d'usuari"
renotedBy: "Impulsat per {user}"
noNotes: "Cap publicació"
@ -21,69 +21,69 @@ instance: "Instància"
settings: "Preferències"
basicSettings: "Configuració bàsica"
otherSettings: "Altres opcions"
openInWindow: "Obrir en una finestra nova"
openInWindow: "Obre en una finestra nova"
profile: "Perfil"
timeline: "Línia de temps"
noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
login: "Iniciar sessió"
login: "Inicia sessió"
loggingIn: "Iniciant sessió"
logout: "Tancar sessió"
signup: "Registrar-se"
uploading: "Pujant..."
save: "Desar"
logout: "Tanca la sessió"
signup: "Registra'm"
uploading: "S'està pujant…"
save: "Desa"
users: "Usuaris"
addUser: "Afegir un usuari"
favorite: "Afegir a favorits"
favorites: "Favorits"
unfavorite: "Eliminar de favorits"
favorited: "Afegit a favorits."
alreadyFavorited: "Ja s'ha afegit a favorits."
cantFavorite: "No s'ha pogut afegir a favorits."
pin: "Fixar al perfil"
unpin: "Deixar de fixar al perfil"
copyContent: "Còpia el contingut"
copyLink: "Còpia l'enllaç"
delete: "Esborra"
deleteAndEdit: "Esborrar i edita"
deleteAndEditConfirm: "Estàs segur que vols esborrar aquesta publicació i editar-la?\
\ Perdràs totes les reaccions, impulsos i respostes."
addToList: "Afegir a la llista"
sendMessage: "Enviar un missatge"
copyUsername: "Còpia nom d'usuari"
searchUser: "Cercar un usuari"
addUser: "Afegeix un usuari"
favorite: "Afegeix als marcadors"
favorites: "Marcadors"
unfavorite: "Elimina dels marcadors"
favorited: "S'ha afegit el marcador."
alreadyFavorited: "Ja està afegida als marcadors."
cantFavorite: "No s'ha pogut afegir als marcadors."
pin: "Fixa al perfil"
unpin: "Deixa de fixar al perfil"
copyContent: "Copia el contingut"
copyLink: "Copia l'enllaç"
delete: "Elimina"
deleteAndEdit: "Elimina i edita"
deleteAndEditConfirm: "Segur que vols eliminar la publicació i editar-la? Perdràs\
\ totes les reaccions, impulsos i respostes."
addToList: "Afegeix a la llista"
sendMessage: "Envia un missatge"
copyUsername: "Copia el nom d'usuari"
searchUser: "Cerca un usuari"
reply: "Respon"
loadMore: "Carregar més"
showMore: "Veure més"
loadMore: "Carrega'n més"
showMore: "Mostra'n més"
youGotNewFollower: "t'ha seguit"
receiveFollowRequest: "Sol·licitud de seguiment rebuda"
followRequestAccepted: "Sol·licitud de seguiment acceptada"
mention: "Menció"
mentions: "Mencions"
directNotes: "Missatges directes"
importAndExport: "Importar / Exportar Dades"
import: "Importar"
export: "Exportar"
importAndExport: "Importa/exporta dades"
import: "Importa"
export: "Exporta"
files: "Fitxers"
download: "Descarregar"
driveFileDeleteConfirm: "Estàs segur que vols esborrar el fitxer \"{nom}\"? S'eliminarà\
\ de totes les notes que el continguin com a fitxer adjunt."
unfollowConfirm: "Estàs segur que vols deixar de seguir {name}?"
download: "Baixa"
driveFileDeleteConfirm: "Segur que vols eliminar el fitxer «{name}»? S'eliminarà de\
\ totes les notes que el continguin com a fitxer adjunt."
unfollowConfirm: "Segur que vols deixar de seguir {name}?"
exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà\
\ al teu Disc un cop completada."
importRequested: "Has sol·licitat una importació. Això pot trigar una estona."
lists: "Llistes"
noLists: "No tens cap llista"
noLists: "No teniu cap llista"
note: "Publicació"
notes: "Notes"
notes: "Publicacions"
following: "Seguint"
followers: "Seguidors"
followsYou: "Et segueix"
createList: "Crear llista"
manageLists: "Gestionar les llistes"
createList: "Crea una llista"
manageLists: "Gestiona les llistes"
error: "Error"
somethingHappened: "S'ha produït un error"
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\
\ cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després\
\ d'esperar una estona."
@ -94,32 +94,32 @@ enterListName: "Introdueix un nom per a la llista"
privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte"
follow: "Seguir"
followRequest: "Enviar sol·licitud de seguiment"
follow: "Segueix"
followRequest: "Segueix"
followRequests: "Sol·licituds de seguiment"
unfollow: "Deixar de seguir"
unfollow: "Deixa de seguir"
followRequestPending: "Sol·licituds de seguiment pendents"
enterEmoji: "Introduir un emoji"
renote: "Impuls"
unrenote: "Anul·lar impuls"
renoted: "Impulsat."
enterEmoji: "Introdueix un emoji"
renote: "Impulsa"
unrenote: "Anul·la l'impuls"
renoted: "S'ha impulsat."
cantRenote: "Aquesta publicació no es pot impulsar."
cantReRenote: "No es pot impulsar un impuls."
quote: "Cita"
pinnedNote: "Publicació fixada"
pinned: "Fixar al perfil"
pinned: "Fixa al perfil"
you: "Tu"
clickToShow: "Fes clic per mostrar"
clickToShow: "Fes clic per a mostrar"
sensitive: "NSFW"
add: "Afegir"
add: "Afegeix"
reaction: "Reaccions"
reactionSetting: "Reaccions a mostrar al selector de reaccions"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem\
\ \"+\" per afegir."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
attachCancel: "Eliminar el fitxer adjunt"
markAsSensitive: "Marcar com a NSFW"
unmarkAsSensitive: "Desmarcar com a NSFW"
attachCancel: "Elimina el fitxer adjunt"
markAsSensitive: "Marca com a NSFW"
unmarkAsSensitive: "Desmarca com a NSFW"
enterFileName: "Introdueix un nom de fitxer"
mute: "Silencia"
unmute: "Deixa de silenciar"
@ -128,7 +128,7 @@ unblock: "Desbloqueja"
suspend: "Suspèn"
unsuspend: "Treu la suspensió"
instances: "Instàncies"
remove: "Eliminar"
remove: "Elimina"
nsfw: "NSFW"
pinnedNotes: "Publicacions fixades"
userList: "Llistes"
@ -738,39 +738,39 @@ _deck:
swapLeft: Canvia amb la columna de l'esquerra
renameProfile: Canvia el nom de l'espai de treball
nameAlreadyExists: Aquest nom d'espai de treball ja existeix.
blockConfirm: Estás segur que vols bloquejar aquest compte?
unsuspendConfirm: Estás segur que vols treure la suspensió d'aquest compte?
unblockConfirm: Estás segur que vols treure el bloqueig d'aquest compte?
suspendConfirm: Estás segur que vols suspendre aquest compte?
blockConfirm: Segur que vols bloquejar aquest compte?
unsuspendConfirm: Segur que vols treure la suspensió d'aquest compte?
unblockConfirm: Segur que vols treure el bloqueig d'aquest compte?
suspendConfirm: Segur que vols suspendre aquest compte?
selectList: Selecciona una llista
selectAntenna: Selecciona una antena
selectWidget: Selecciona un giny
editWidgets: Edita ginys
editWidgets: Edita els ginys
editWidgetsExit: Fet
customEmojis: Emoji personalitzat
cacheRemoteFilesDescription: Quant aquesta opció es deshabilitada, els fitxers remots
es carregant directament de l'instància remota. Deshabilitar això farà que baixi
l'ús d'emmagatzematge, però incrementa el tràfic, perquè les miniatures no es generarán.
flagAsBot: Marca aquest compte com un bot
customEmojis: Emojis personalitzats
cacheRemoteFilesDescription: Quan aquesta opció està desactivada, els fitxers remots
es carreguin directament de la instància remota. Desactivar-la farà que baixi l'ús
d'emmagatzematge, però incrementa el tràfic, perquè les miniatures no es generaran.
flagAsBot: Marca aquest compte com a bot
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
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.
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!
flagShowTimelineRepliesDescription: Mostrará respostes d'usuaris a notes d'altres
usuaris si s'activa.
flagShowTimelineRepliesDescription: Si s'activa, es mostraran les respostes d'usuaris
a publicacions d'altres usuaris.
general: General
autoAcceptFollowed: Aprova automàticament les peticions de seguiment d'usuaris que
segueixes
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ó
showOnRemote: Veure a l'instància remota
showOnRemote: Mostra a la instància remota
wallpaper: 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}?
proxyAccount: Compte proxy
proxyAccountDescription: Un compte proxy es un compte que actua com un seguidor remot
@ -781,127 +781,127 @@ host: Amfitrió
selectUser: Selecciona un usuari
latestStatus: Últim estat
storageUsage: Ús del emmagatzematge
metadata: Metadata
metadata: Metadades
withNFiles: '{n} fitxer(s)'
monitor: Seguiment
software: Programari
version: Versió
jobQueue: Cua de Feina
cpuAndMemory: CPU i Memòria
jobQueue: Cua de feina
cpuAndMemory: CPU i memòria
network: Xarxa
disk: Disc
instanceInfo: Informació de l'instància
instanceInfo: Informació de la instància
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
será federada. Normalment aquesta operació no es necessària.
clearCachedFilesConfirm: Segur que vols esborrar els fitxers remots de la memòria
cau?
blockedUsers: Usuaris blocats
noUsers: No hi han usuaris
editProfile: Editar perfil
noteDeleteConfirm: Segur que vols esborrar aquesta publicació?
noUsers: No hi ha cap usuari
editProfile: Edita el perfil
noteDeleteConfirm: Segur que vols eliminar la publicació?
pinLimitExceeded: No pots fixar més notes
muteAndBlock: Silenciats i Bloquejats
muteAndBlock: Silenciats i blocats
mutedUsers: Usuaris silenciats
done: Fet
preview: Vista prèvia
default: Per defecte
intro: La instalació de Calckey a acabat! Si us plau crea un compte d'usuari.
processing: Processant...
noCustomEmojis: No hi ha emoji
noJobs: No hi han feines
intro: La instal·lació de Calckey ha acabat! Crea un compte d'usuari d'administració.
processing: S'està processant…
noCustomEmojis: No hi ha cap emoji
noJobs: No hi ha cap feina
federating: Federant
blocked: Bloquejat
subscribing: Subscrivint
publishing: Publicant
notResponding: Sense resposta
instanceUsers: Usuaris d'aquesta instància
instanceFollowing: Seguint a l'instància
instanceFollowing: Seguint a la instància
instanceFollowers: Seguidors de l'instància
security: Seguretat
newPasswordRetype: Torna a entrar la nova contrasenya
more: Més!
featured: Destacat
usernameOrUserId: Nom d'usuari o id d'usuari
usernameOrUserId: Nom o ID d'usuari
noSuchUser: No s'ha trobat l'usuari
lookup: Cercar
attachFile: Afegeix un arxiu
lookup: Cerca
attachFile: Afegeix un fitxer
currentPassword: Contrasenya actual
newPassword: Nova contrasenya
announcements: Anuncis
imageUrl: URL de la imatge
removed: S'ha esborrat correctament
removeAreYouSure: Segur que vols esborrar "{x}"?
deleteAreYouSure: Segur que vols esborrar "{x}"?
resetAreYouSure: Restablir? Segur?
fromUrl: Des de URL
saved: Desat
removed: S'ha eliminat correctament
removeAreYouSure: Segur que vols eliminar «{x}»?
deleteAreYouSure: Segur que vols eliminar «{x}»?
resetAreYouSure: Segur que vols restablir?
fromUrl: Des d'una URL
saved: S'ha desat
messaging: Xat
upload: Pujar
keepOriginalUploading: Desa imatge original
upload: Puja
keepOriginalUploading: Desa la imatge original
keepOriginalUploadingDescription: Desa la imatge original pujada tal com es. Si es
desactiva, es generarà una versió per mostrar en la web al pujar.
fromDrive: Des de Drive
uploadFromUrl: Puja des de una adreça URL
fromDrive: Des del Disc
uploadFromUrl: Puja des d'una adreça URL
uploadFromUrlDescription: Adreça URL del fitxer que vols pujar
uploadFromUrlRequested: Pujada demanada
noMoreHistory: S'ha acabat la historia
tos: Termes d'us
start: Començar
startMessaging: Comença un nou xat
noMoreHistory: No hi ha més historial
tos: Condicions d'ús
start: Comença
startMessaging: Comença una conversa
manageGroups: Gestiona els grups
nUsersRead: llegit per {n}
agreeTo: Estic d'acord amb {0}
activity: Activitat
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
light: Clar
registeredDate: Data de registre
dark: Fosc
lightThemes: Temes clars
location: Lloc
location: Ubicació
theme: Temes
themeForLightMode: Tema a fer servir en mode clar
drive: Disc
selectFile: Tria un fitxer
selectFiles: Tria fitxers
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
createFolder: Crea una carpeta
renameFolder: Posa un nom nou a aquesta carpeta
deleteFolder: Esborra aquesta carpeta
renameFolder: Canvia-li el nom a la carpeta
deleteFolder: Elimina la carpeta
selectFolder: Tria una carpeta
selectFolders: Tria carpetes
renameFile: Canvia el nom del fitxer
folderName: Nom de la carpeta
inputNewFolderName: Escriu un nou nom per la carpeta
inputNewFolderName: Escriu un nom de carpeta nou
addFile: Afegeix un fitxer
emptyDrive: El teu Disc és buit
emptyFolder: Aquesta carpeta és buida
unableToDelete: No es pot esborrar
unableToDelete: No es pot eliminar
inputNewFileName: Escriu un nou nom per al fitxer
inputNewDescription: Escriu una nova descripció
circularReferenceFolder: La carpeta de destí es una subcarpeta de la carpeta que vols
inputNewDescription: Escriu una descripció nova
circularReferenceFolder: La carpeta de destí és una subcarpeta de la carpeta que vols
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
disconnectedFromServer: S'ha perdut la conexió al servidor
reload: Torna a carregar
avatar: Avatar
banner: Banner
banner: Bàner
doNothing: Ignora
reloadConfirm: Vols tornar a carregar la línea temporal?
watch: Veure
maintainerName: Administrador
maintainerEmail: Correu electrònic de l'administrador
instanceName: Nom de l'instància
instanceDescription: Descripció de l'instància
instanceName: Nom de la instància
instanceDescription: Descripció de la instància
today: Avui
dayX: '{day}'
tosUrl: Adreça URL dels terminis d'ús
tosUrl: URL de les Condicions d'ús
thisYear: Any
thisMonth: Mes
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
les líneas temporals, inclòs si hi són desactivades.
showLess: Tanca
clearQueue: Neteja la cua
clearQueue: Esborra la cua
uploadFromUrlMayTakeTime: Pot trigar un temps fins que la pujada es completi.
noThankYou: No, gràcies
addInstance: Afegir una instància
emoji: Emoji
emojis: Emoji
addInstance: Afegeix una instància
emoji: Emojis
emojis: Emojis
emojiName: Nom del emoji
emojiUrl: URL del emoji
addEmoji: Afegir
emojiUrl: URL de l'emoji
addEmoji: Afegeix
settingGuide: Configuració recomenada
searchWith: 'Cercar: {q}'
searchWith: 'Cerca: {q}'
youHaveNoLists: No tens cap llista
flagSpeakAsCat: Parla com un gat
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
recipient: Destinatari(s)
annotation: Comentaris
blockedInstances: Instàncies Bloquejades
blockedInstances: Instàncies bloquejades
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.
hiddenTags: Etiquetes Ocultes
hiddenTags: Etiquetes amagades
hiddenTagsDescription: 'Enumereu les etiquetes (sense el #) que voleu ocultar de tendències
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í.'
noInstances: No hi han instàncies
noInstances: No hi ha cap instància
defaultValueIs: 'Per defecte: {value}'
suspended: Suspès
all: Tot
changePassword: Canvia la contrasenya
clearQueueConfirmTitle: Segur que vols netejar la cua?
clearQueueConfirmTitle: Segur que vols esborrar la cua?
retypedNotMatch: Els camps no coincideixen.
normal: Normal
monthX: '{month}'
@ -975,25 +975,25 @@ registration: Registre
showEmojisInReactionNotifications: Mostra els emojis a les notificacions de les reaccions
renoteMute: Silencia els impulsos
renoteUnmute: Treu el silenci als impulsos
cacheRemoteFiles: Fitxers remots a la memoria cau
cacheRemoteFiles: Fitxers remots a la memòria cau
federation: Federació
registeredAt: Registrat a
latestRequestSentAt: Última petició enviada
latestRequestReceivedAt: Última petició rebuda
charts: Gràfics
perHour: Per Hora
perDay: Per Dia
perHour: Per hora
perDay: Per dia
stopActivityDelivery: Para d'enviar activitats
operations: Operacions
explore: Explorar
messageRead: Llegir
explore: Explora
messageRead: Llegit
images: Imatges
birthday: Aniversari
yearsOld: '{age} anys'
copyUrl: Copiar l'adreça URL
copyUrl: Copia l'adreça URL
rename: Renombrar
unwatch: Deixa de veure
accept: Acceptar
accept: Accepta
reject: Rebutja
yearX: '{year}'
pages: Pàgines
@ -1511,7 +1511,7 @@ seperateRenoteQuote: Botons d'impuls i de citació separats
searchResult: Resultats de la cerca
hashtags: Etiquetes
troubleshooting: Resolució de problemes
learnMore: Aprèn més
learnMore: Més informació
misskeyUpdated: Calckey s'ha actualitzat!
translate: Tradueix
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
una prova.
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
accountDeletionInProgress: La supressió del compte està en curs
unmuteThread: Desfés el silenci al fil
@ -1815,6 +1815,8 @@ _channel:
usersCount: '{n} Participants'
following: Seguit
notesCount: '{n} Notes'
nameAndDescription: Nom i descripció
nameOnly: Només nom
_instanceMute:
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
@ -2020,24 +2022,7 @@ _relayStatus:
requesting: Pendent
accepted: Acceptat
rejected: Rebutjat
_apps:
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
deleted: Eliminat
editNote: Edita la nota
edited: Editat
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}'
userSaysSomethingReasonReply: '{name} ha respost a 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"
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}."
search: "Vyhledávání"
notifications: "Oznámení"
@ -44,7 +46,8 @@ copyContent: "Zkopírovat obsah"
copyLink: "Kopírovat odkaz"
delete: "Smazat"
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"
sendMessage: "Odeslat zprávu"
copyUsername: "Kopírovat uživatelské jméno"
@ -63,9 +66,11 @@ import: "Importovat"
export: "Exportovat"
files: "Soubor(ů)"
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}?"
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."
lists: "Seznamy"
noLists: "Nemáte žádné seznamy"
@ -81,7 +86,8 @@ somethingHappened: "Jejda. Něco se nepovedlo."
retry: "Opakovat"
pageLoadError: "Nepodařilo se načíst stránku"
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"
privacy: "Soukromí"
makeFollowManuallyApprove: "Žádosti o sledování vyžadují potvrzení"
@ -105,7 +111,8 @@ clickToShow: "Klikněte pro zobrazení"
sensitive: "NSFW"
add: "Přidat"
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"
attachCancel: "Odstranit přílohu"
markAsSensitive: "Označit jako NSFW"
@ -134,13 +141,18 @@ emojiUrl: "URL obrázku"
addEmoji: "Přidat emoji"
settingGuide: "Doporučené nastavení"
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"
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"
flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako kočka."
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"
addAccount: "Přidat účet"
loginFailed: "Přihlášení se nezdařilo."
@ -153,7 +165,10 @@ searchWith: "Hledat: {q}"
youHaveNoLists: "Nemáte žádné seznamy"
followConfirm: "Jste si jisti, že chcete sledovat {name}?"
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"
selectUser: "Vyberte uživatele"
recipient: "Pro"
@ -239,7 +254,8 @@ agreeTo: "Souhlasím s {0}"
tos: "Podmínky užívání"
start: "Začít"
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"
images: "Obrázky"
birthday: "Datum narození"
@ -548,7 +564,8 @@ info: "Informace"
unknown: "Neznámý"
onlineStatus: "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"
active: "Aktivní"
offline: "Offline"
@ -928,3 +945,66 @@ _deck:
antenna: "Antény"
list: "Seznamy"
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."
privateMode: "Private Mode"
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"
allowedInstancesDescription: "Hosts of instances to be whitelisted for federation,\
\ each separated by a new line (only applies in private mode)."
@ -886,7 +886,6 @@ global: "Global"
recommended: "Recommended"
squareAvatars: "Display squared avatars"
seperateRenoteQuote: "Separate boost and quote buttons"
highlightCw: "Highlight content warned posts"
sent: "Sent"
received: "Received"
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."
enableCustomKaTeXMacro: "Enable custom KaTeX macros"
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"
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:
description: "Reduces the effort of server moderation through automatically recognizing\
@ -1187,6 +1193,10 @@ _nsfw:
ignore: "Don't hide NSFW media"
force: "Hide all media"
_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"
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."
@ -1998,20 +2008,3 @@ _deck:
list: "List"
mentions: "Mentions"
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!"
cancel: "Cancelar"
enterUsername: "Introduce el nombre de usuario"
renotedBy: "Reposteado por {user}"
renotedBy: "Impulsado por {user}"
noNotes: "No hay publicaciones"
noNotifications: "No hay notificaciones"
instance: "Instancia"
@ -46,7 +46,7 @@ copyLink: "Copiar enlace"
delete: "Borrar"
deleteAndEdit: "Borrar y editar"
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"
sendMessage: "Enviar un mensaje"
copyUsername: "Copiar nombre de usuario"
@ -342,7 +342,7 @@ dayX: "Día {day}"
monthX: "Mes {month}"
yearX: "Año {year}"
pages: "Páginas"
integration: "Integración"
integration: "Integraciones"
connectService: "Conectar"
disconnectService: "Desconectar"
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\
\ principal de la instancia, separadas por lineas nuevas"
pinnedClipId: "Id del clip fijado"
pinnedNotes: "Nota fijada"
pinnedNotes: "Publicación fijada"
hcaptcha: "hCaptcha"
enableHcaptcha: "Habilitar hCaptcha"
hcaptchaSiteKey: "Clave del sitio"
@ -386,14 +386,14 @@ antennaKeywords: "Palabras clave para recibir"
antennaExcludeKeywords: "Palabras clave para excluir"
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar\
\ con una linea nueva es una declaración OR"
notifyAntenna: "Notificar nueva nota"
withFileAntenna: "Sólo notas con archivos adjuntados"
notifyAntenna: "Notificar nueva publicación"
withFileAntenna: "Sólo publicaciones con archivos adjuntados"
enableServiceworker: "Activar ServiceWorker"
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
caseSensitive: "Distinguir mayúsculas de minúsculas"
withReplies: "Incluir respuestas"
connectedTo: "Estas cuentas están conectadas"
notesAndReplies: "Notas y respuestas"
notesAndReplies: "Publicaciones y respuestas"
withFiles: "Adjuntos"
silence: "Silenciar"
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"
cacheClear: "Borrar caché"
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"
help: "Ayuda"
inputMessageHere: "Escribe el mensaje aquí"
@ -451,7 +451,7 @@ text: "Texto"
enable: "Activar"
next: "Siguiente"
retype: "Intentar de nuevo"
noteOf: "Notas de {user}"
noteOf: "Publicaciones de {user}"
inviteToGroup: "Invitar al grupo"
quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?"
@ -511,8 +511,8 @@ accountSettings: "Ajustes de cuenta"
promotion: "Promovido"
promote: "Promover"
numberOfDays: "Cantidad de dias"
hideThisNote: "Ocultar esta nota"
showFeaturedNotesInTimeline: "Mostrar notas destacadas en la línea de tiempo"
hideThisNote: "Ocultar esta publicación"
showFeaturedNotesInTimeline: "Mostrar publicaciones destacadas en la línea de tiempo"
objectStorage: "Almacenamiento de objetos"
useObjectStorage: "Usar almacenamiento de objetos"
objectStorageBaseUrl: "Base URL"
@ -542,7 +542,7 @@ objectStorageSetPublicRead: "Seleccionar \"public-read\" al subir "
serverLogs: "Registros del servidor"
deleteAll: "Eliminar todos"
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"
listen: "Escuchar"
none: "Ninguna"
@ -575,7 +575,7 @@ deleteAllFiles: "Borrar todos los archivos"
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
removeAllFollowing: "Retener todos los siguientes"
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."
userSilenced: "Este usuario ha sido silenciado."
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
@ -590,8 +590,8 @@ addRelay: "Agregar relé"
inboxUrl: "Inbox URL"
addedRelays: "Relés añadidos"
serviceworkerInfo: "Se necesita activar para usar las notificaciones push"
deletedNote: "Nota eliminada"
invisibleNote: "Nota oculta"
deletedNote: "Publicación eliminada"
invisibleNote: "Publicación oculta"
enableInfiniteScroll: "Activar scroll infinito"
visibility: "Visibilidad"
poll: "Encuesta"
@ -1154,6 +1154,7 @@ _mfm:
plain: "Plano"
plainDescription: "Desactiva los efectos de todo el contenido MFM con este efecto\
\ MFM."
position: Posición
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"
@ -1173,6 +1174,8 @@ _channel:
following: "Siguiendo"
usersCount: "{n} participantes"
notesCount: "{n} notas"
nameOnly: Nombre solamente
nameAndDescription: Nombre y descripción
_menuDisplay:
sideFull: "Horizontal"
sideIcon: "Horizontal (ícono)"
@ -1900,18 +1903,6 @@ moveFrom: Mueve a esta cuenta de una cuenta antigua
moveFromLabel: 'La cuenta que estás moviendo de:'
moveAccountDescription: ''
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
userSaysSomethingReason: '{name} dijo {reason}'
hiddenTags: Etiquetas Ocultas
@ -1928,3 +1919,12 @@ _messaging:
groups: Grupos
dms: Privado
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"
download: "Télécharger"
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} ?"
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."
@ -1931,7 +1931,8 @@ flagSpeakAsCatDescription: Vos messages seront nyanifiés en mode chat
hiddenTags: Hashtags cachés
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\
\ 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
userSaysSomethingReason: '{name} a dit {reason}'
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\
\ syntaxe avancée, telle que la ramification conditionnelle, ne peut pas être utilisée\
\ 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"
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}"
search: "Penelusuran"
notifications: "Pemberitahuan"
@ -44,7 +46,8 @@ copyContent: "Salin konten"
copyLink: "Salin tautan"
delete: "Hapus"
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"
sendMessage: "Kirim pesan"
copyUsername: "Salin nama pengguna"
@ -66,7 +69,8 @@ files: "Berkas"
download: "Unduh"
driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus."
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."
lists: "Daftar"
noLists: "Kamu tidak memiliki daftar apapun"
@ -81,9 +85,12 @@ error: "Galat"
somethingHappened: "Terjadi kesalahan"
retry: "Coba lagi"
pageLoadError: "Gagal memuat halaman."
pageLoadErrorDescription: "Umumnya disebabkan jaringan atau tembolok perambah. Cobalah bersihkan tembolok peramban lalu tunggu sesaat sebelum mencoba kembali."
serverIsDead: "Tidak ada respon dari peladen. Mohon tunggu dan coba beberapa saat lagi."
youShouldUpgradeClient: "Untuk melihat halaman ini, mohon muat ulang untuk memutakhirkan klienmu."
pageLoadErrorDescription: "Umumnya disebabkan jaringan atau tembolok perambah. Cobalah\
\ bersihkan tembolok peramban lalu tunggu sesaat sebelum mencoba kembali."
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"
privacy: "Privasi"
makeFollowManuallyApprove: "Permintaan mengikuti membutuhkan persetujuan"
@ -108,7 +115,8 @@ sensitive: "Konten sensitif"
add: "Tambahkan"
reaction: "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"
attachCancel: "Hapus lampiran"
markAsSensitive: "Tandai sebagai konten sensitif"
@ -137,14 +145,22 @@ emojiUrl: "URL Emoji"
addEmoji: "Tambahkan emoji"
settingGuide: "Pengaturan rekomendasi"
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"
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"
flagAsCatDescription: "Nyalakan tanda ini untuk menandai akun ini sebagai kucing."
flagShowTimelineReplies: "Tampilkan balasan di linimasa"
flagShowTimelineRepliesDescription: "Menampilkan balasan pengguna dari note pengguna lain di linimasa apabila dinyalakan."
autoAcceptFollowed: "Setujui otomatis permintaan mengikuti dari pengguna yang kamu ikuti"
flagShowTimelineRepliesDescription: "Menampilkan balasan pengguna dari note pengguna\
\ lain di linimasa apabila dinyalakan."
autoAcceptFollowed: "Setujui otomatis permintaan mengikuti dari pengguna yang kamu\
\ ikuti"
addAccount: "Tambahkan akun"
loginFailed: "Gagal untuk masuk"
showOnRemote: "Lihat profil asli"
@ -156,7 +172,11 @@ searchWith: "Cari: {q}"
youHaveNoLists: "Kamu tidak memiliki daftar apapun"
followConfirm: "Apakah kamu yakin ingin mengikuti {name}?"
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"
selectUser: "Pilih pengguna"
recipient: "Penerima"
@ -187,11 +207,15 @@ instanceInfo: "Informasi Instansi"
statistics: "Statistik"
clearQueue: "Bersihkan 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"
clearCachedFilesConfirm: "Apakah kamu yakin ingin menghapus seluruh tembolok berkas remote?"
clearCachedFilesConfirm: "Apakah kamu yakin ingin menghapus seluruh tembolok berkas\
\ remote?"
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"
mutedUsers: "Pengguna yang dibisukan"
blockedUsers: "Pengguna yang diblokir"
@ -239,7 +263,8 @@ saved: "Telah disimpan"
messaging: "Pesan"
upload: "Unggah"
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"
fromUrl: "Dari URL"
uploadFromUrl: "Unggah dari URL"
@ -255,7 +280,8 @@ agreeTo: "Saya setuju kepada {0}"
tos: "Syarat dan ketentuan"
start: "Mulai"
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"
images: "Gambar"
birthday: "Tanggal lahir"
@ -288,7 +314,8 @@ unableToDelete: "Tidak dapat menghapus"
inputNewFileName: "Masukkan nama berkas yang baru"
inputNewDescription: "Masukkan keterangan disini"
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."
copyUrl: "Salin tautan"
rename: "Ubah nama"
@ -322,7 +349,8 @@ connectService: "Sambungkan"
disconnectService: "Putuskan"
enableLocalTimeline: "Nyalakan linimasa lokal"
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"
enableRegistration: "Nyalakan pendaftaran pengguna baru"
invite: "Undang"
@ -334,9 +362,11 @@ bannerUrl: "URL Banner"
backgroundImageUrl: "URL Gambar latar"
basicInfo: "Informasi Umum"
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"
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"
pinnedNotes: "Catatan yang disematkan"
hcaptcha: "hCaptcha"
@ -347,14 +377,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Nyalakan reCAPTCHA"
recaptchaSiteKey: "Site 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"
manageAntennas: "Pengelola Antena"
name: "Nama"
antennaSource: "Sumber Antenna"
antennaKeywords: "Kata kunci yang diterima"
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"
withFileAntenna: "Hanya tampilkan catatan dengan berkas yang dilampirkan"
enableServiceworker: "Aktifkan ServiceWorker"
@ -441,7 +474,8 @@ strongPassword: "Kata sandi kuat"
passwordMatched: "Kata sandi sama"
passwordNotMatched: "Kata sandi tidak sama"
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"
or: "atau"
language: "Bahasa"
@ -482,19 +516,29 @@ showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di linimasa"
objectStorage: "Object Storage"
useObjectStorage: "Gunakan object storage"
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"
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"
objectStoragePrefixDesc: "Berkas tidak akan disimpan dalam direktori dari prefix ini."
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"
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"
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"
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"
serverLogs: "Log Peladen"
deleteAll: "Hapus semua"
@ -522,7 +566,9 @@ sort: "Urutkan"
ascendingOrder: "Urutkan naik"
descendingOrder: "Urutkan menurun"
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"
script: "Script"
disablePagesScript: "Nonaktifkan script pada halaman"
@ -530,11 +576,14 @@ updateRemoteUser: "Perbaharui informasi pengguna luar"
deleteAllFiles: "Hapus semua berkas"
deleteAllFilesConfirm: "Apakah kamu yakin ingin menghapus semua berkas?"
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."
userSilenced: "Pengguna ini telah dibungkam."
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"
divider: "Pembagi"
addItem: "Tambahkan item"
@ -579,7 +628,8 @@ notificationType: "Jenis pemberitahuan"
edit: "Sunting"
emailServer: "Peladen 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"
emailAddress: "Alamat surel"
smtpConfig: "Konfigurasi peladen SMTP"
@ -587,13 +637,15 @@ smtpHost: "Host"
smtpPort: "Port"
smtpUser: "Nama Pengguna"
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"
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
testEmail: "Tes pengiriman surel"
wordMute: "Bisukan kata"
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"
userSaysSomething: "{name} mengatakan sesuatu"
makeActive: "Aktifkan"
@ -609,30 +661,37 @@ create: "Buat"
notificationSetting: "Pengaturan Pemberitahuan"
notificationSettingDesc: "Pilih tipe pemberitahuan untuk ditampilkan"
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"
regenerateLoginToken: "Perbarui token login"
regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan dilogout."
setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya menggunakan spasi."
regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat\
\ 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"
behavior: "Perilaku"
sample: "Contoh"
abuseReports: "Laporkan"
reportAbuse: "Laporkan"
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."
reporter: "Pelapor"
reporteeOrigin: "Yang dilaporkan"
reporterOrigin: "Pelapor"
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"
abuseMarkAsResolved: "Tandai laporan sebagai selesai"
openInNewTab: "Buka di tab baru"
openInSideView: "Buka di tampilan samping"
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"
waitingFor: "Menunggu untuk {x}"
random: "Acak"
@ -644,9 +703,11 @@ createNew: "Buat baru"
optional: "Opsional"
createNewClip: "Buat klip baru"
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"
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"
accountInfo: "Informasi akun"
notesCount: "Jumlah catatan"
@ -665,12 +726,16 @@ no: "Tidak"
driveFilesCount: "Jumlah berkas drive"
driveUsage: "Penggunaan ruang penyimpanan drive"
noCrawle: "Tolak pengindeksan crawler"
noCrawleDescription: "Meminta mesin pencari untuk tidak mengindeks halaman profil 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."
noCrawleDescription: "Meminta mesin pencari untuk tidak mengindeks halaman profil\
\ 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"
loadRawImages: "Tampilkan lampiran gambar secara penuh daripada thumbnail"
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"
emailVerified: "Surel telah diverifikasi"
noteFavoritesCount: "Jumlah catatan yang difavoritkan"
@ -682,14 +747,16 @@ clips: "Klip"
experimentalFeatures: "Fitur eksperimental"
developer: "Pengembang"
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"
duplicate: "Duplikat"
left: "Kiri"
center: "Tengah"
wide: "Lebar"
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."
showTitlebar: "Tampilkan bilah judul"
clearCache: "Hapus tembolok"
@ -697,7 +764,10 @@ onlineUsersCount: "{n} orang sedang daring"
nUsers: "{n} Pengguna"
nNotes: "{n} Catatan"
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"
backgroundColor: "Latar Belakang"
accentColor: "Aksen"
@ -736,14 +806,17 @@ unlikeConfirm: "Yakin ingin hapus sukamu?"
fullView: "Tampilan penuh"
quitFullView: "Keluar tampilan penuh"
addDescription: "Tambahkan deskripsi"
userPagePinTip: "Kamu dapat membuat catatan untuk ditampilkan disini dengan memilih \"Sematkan ke profil\" dari menu pada catatan individu."
notSpecifiedMentionWarning: "Catatan ini mengandung sebutan dari pengguna yang tidak dimuat sebagai penerima"
userPagePinTip: "Kamu dapat membuat catatan untuk ditampilkan disini dengan memilih\
\ \"Sematkan ke profil\" dari menu pada catatan individu."
notSpecifiedMentionWarning: "Catatan ini mengandung sebutan dari pengguna yang tidak\
\ dimuat sebagai penerima"
info: "Informasi"
userInfo: "Informasi pengguna"
unknown: "Tidak diketahui"
onlineStatus: "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"
active: "Aktif"
offline: "Luring"
@ -778,7 +851,8 @@ emailNotConfiguredWarning: "Alamat surel tidak disetel."
ratio: "Rasio"
previewNoteText: "Tampilkan pratinjau"
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"
squareAvatars: "Tampilkan avatar sebagai persegi"
sent: "Kirim"
@ -793,7 +867,9 @@ whatIsNew: "Lihat perubahan pemutakhiran"
translate: "Terjemahkan"
translatedFrom: "Terjemahkan dari {x}"
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"
keepCw: "Biarkan Peringatan Konten"
pubSub: "Akun Pub/Sub"
@ -809,12 +885,14 @@ filter: "Saring"
controlPanel: "Panel kendali"
manageAccounts: "Kelola Akun"
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"
muteThread: "Bisukan thread"
unmuteThread: "Suarakan thread"
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"
deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?"
incorrectPassword: "Kata sandi salah."
@ -824,7 +902,8 @@ leaveGroup: "Keluar grup"
leaveGroupConfirm: "Apakah kamu yakin untuk keluar dari \"{name}\"?"
useDrawerReactionPickerForMobile: "Tampilkan bilah reaksi sebagai laci di ponsel"
welcomeBackWithName: "Selamat datang kembali, {name}."
clickToFinishEmailVerification: "Mohon klik [{ok}] untuk menyelesaikan verifikasi email."
clickToFinishEmailVerification: "Mohon klik [{ok}] untuk menyelesaikan verifikasi\
\ email."
overridedDeviceKind: "Tipe perangkat"
smartphone: "Ponsel"
tablet: "Tablet"
@ -866,11 +945,16 @@ _ffVisibility:
_signup:
almostThere: "Hampir selesai"
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: "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."
sendEmail: "Setelah penghapusan akun selesai, pemberitahuan akan dikirimkan ke alamat surel yang terdaftarkan pada akun ini."
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."
sendEmail: "Setelah penghapusan akun selesai, pemberitahuan akan dikirimkan ke alamat\
\ surel yang terdaftarkan pada akun ini."
requestAccountDelete: "Minta penghapusan akun"
started: "Penghapusan telah dimulai"
inProgress: "Penghapusan sedang dalam proses"
@ -878,9 +962,13 @@ _ad:
back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
_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."
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."
enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah\
\ tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel\
\ 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:
my: "Postingan saya"
liked: "Postingan yang disukai"
@ -902,13 +990,15 @@ _registry:
domain: "Domain"
createKey: "Buat kunci"
_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"
allContributors: "Seluruh kontributor"
source: "Sumber kode"
translation: "Terjemahkan 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"
_nsfw:
respect: "Sembunyikan media NSFW"
@ -916,10 +1006,12 @@ _nsfw:
force: "Sembunyikan semua media"
_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"
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"
hashtagDescription: "Kamu dapat menentukan tagar dengan menggunakan angka dan teks."
url: "URL"
@ -935,15 +1027,18 @@ _mfm:
inlineCode: "Kode (Dalam baris)"
inlineCodeDescription: "Menampilkan sorotan sintaks dalam baris untuk kode(program-)."
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)"
inlineMathDescription: "Menampilkan formula matematika (KaTeX) dalam baris."
blockMath: "Matematika (Blok)"
blockMathDescription: "Menampilkan formula matematika (KaTeX) multibaris dalam sebuah blok."
blockMathDescription: "Menampilkan formula matematika (KaTeX) multibaris dalam sebuah\
\ blok."
quote: "Kutip"
quoteDescription: "Menampilkan konten sebagai kutipan."
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"
searchDescription: "Menampilkan kotak pencarian dengan teks yang sudah dimasukkan."
flip: "Balik"
@ -969,7 +1064,8 @@ _mfm:
x4: "Sangat besar"
x4Description: "Tampilka konten menjadi sangat besar."
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"
fontDescription: "Setel font yang ditampilkan untuk konten."
rainbow: "Pelangi"
@ -1003,15 +1099,21 @@ _menuDisplay:
hide: "Sembunyikan"
_wordMute:
muteWords: "Kata yang dibisukan"
muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan baris baru untuk kondisi OR."
muteWordsDescription2: "Kurung kata kunci dengan garis miring untuk menggunakan regular expressions."
muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan\
\ 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."
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"
hard: "Keras"
mutedNotes: "Catatan yang dibisukan"
_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"
title: "Sembunyikan note dari instansi terdaftar."
heading: "Daftar instansi yang akan dibisukan"
@ -1043,7 +1145,8 @@ _theme:
darken: "Mengelamkan"
lighten: "Menerangkan"
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}?"
keys:
accent: "Aksen"
@ -1115,36 +1218,56 @@ _time:
_tutorial:
title: "Cara menggunakan Misskey"
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_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti siapapun."
step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti seseorang."
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain mudah untuk mengikutimu kembali."
step3_1: "Selesai menyetel profil kamu?"
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\"!"
step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\"\
\ yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"."
step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti\
\ siapapun."
step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti\
\ seseorang."
step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain\
\ 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_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_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!"
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."
step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti\
\ orang lain."
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!"
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_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon dengan cepat."
step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka."
step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon\
\ 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_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung ke bagian {help}."
step7_3: "Semoga berhasil dan bersenang-senanglah! 🚀"
step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung\
\ ke bagian {help}."
step7_3: "Semoga berhasil dan bersenang-senanglah! \U0001F680"
_2fa:
alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
registerDevice: "Daftarkan perangkat 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."
step2Url: "Di aplikasi desktop, masukkan URL berikut:"
step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan."
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."
step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan\
\ pemasangan."
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:
"read:account": "Lihat informasi akun"
"write:account": "Sunting informasi akun"
@ -1180,7 +1303,8 @@ _permissions:
"write:gallery-likes": "Sunting daftar postingan galeri yang disukai"
_auth:
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:"
pleaseGoBack: "Mohon kembali ke aplikasi kamu"
callback: "Mengembalikan kamu ke aplikasi"
@ -1275,7 +1399,8 @@ _profile:
youCanIncludeHashtags: "Kamu juga dapat menambahkan tagar ke dalam bio."
metadata: "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"
metadataContent: "Isi"
changeAvatar: "Ubah avatar"
@ -1596,7 +1721,8 @@ _pages:
_for:
arg1: "Jumlah angka untuk diulangi"
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!"
types:
string: "Teks"

View file

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

View file

@ -66,8 +66,8 @@ import: "Importuj"
export: "Eksportuj"
files: "Pliki"
download: "Pobierz"
driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Zniknie również wpis,\
\ do której dołączony jest ten plik."
driveFileDeleteConfirm: "Czy chcesz usunąć plik \"{name}\"? Wszystkie wpisy zawierające\
\ ten plik również zostaną usunięte."
unfollowConfirm: "Czy na pewno chcesz przestać obserwować {name}?"
exportRequested: "Zażądałeś eksportu. Może to zająć chwilę. Po zakończeniu eksportu\
\ zostanie on dodany do Twojego dysku."
@ -787,7 +787,7 @@ active: "Aktywny"
offline: "Offline"
notRecommended: "Nie zalecane"
botProtection: "Zabezpieczenie przed botami"
instanceBlocking: "Zablokowane instancje"
instanceBlocking: "Zablokowane/wyciszone instancje"
selectAccount: "Wybierz konto"
switchAccount: "Przełącz konto"
enabled: "Właczono"
@ -1075,6 +1075,14 @@ _mfm:
inlineMath: Matematyka (Inline)
inlineMathDescription: Pokaż formuły matematyczne (KaTeX) w linii
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:
none: "Nigdy nie pokazuj"
remote: "Pokaż dla zdalnych użytkowników"
@ -1094,6 +1102,8 @@ _channel:
following: "Śledzeni"
usersCount: "{n} uczestnicy"
notesCount: "{n} wpisy"
nameAndDescription: Nazwa i opis
nameOnly: Tylko nazwa
_menuDisplay:
top: "Góra"
hide: "Ukryj"
@ -1222,13 +1232,13 @@ _tutorial:
step2_1: "Najpierw, proszę wypełnij swój profil."
step2_2: "Podanie kilku informacji o tym, kim jesteś, ułatwi innym stwierdzenie,\
\ 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,\
\ więc spróbuj śledzić kilka kont, aby zacząć.\nKliknij kółko z plusem w prawym\
\ górnym rogu profilu, aby go śledzić."
step4_1: "Pozwól, że zabierzemy Cię tam."
step4_2: "Dla twojego pierwszego postu, niektórzy ludzie lubią zrobić {introduction}\
\ post lub prosty \"Hello world!\""
step4_2: "W pierwszym wpisie możesz się przedstawić lub wysłać powitanie - \"Witaj,\
\ świecie!\""
step5_1: "Osie czasu, wszędzie widzę 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\
@ -1811,8 +1821,8 @@ privateMode: Tryb prywatny
allowedInstances: Dopuszczone instancje
recommended: Polecane
allowedInstancesDescription: Hosty instancji które mają być dopuszczone do federacji,
każda separowana nową linią (dotyczy tylko trybu prywatnego).
seperateRenoteQuote: Oddziel przyciski podbicia i cytatów
każdy separowany nową linią (dotyczy tylko trybu prywatnego).
seperateRenoteQuote: Oddziel przyciski podbicia i cytowania
refreshInterval: 'Częstotliwość aktualizacji '
slow: Wolna
_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
z tamtego konta na to. Zrób to ZANIM rozpoczniesz przenoszenie się z tamtego konta.
Proszę wpisz tag konta w formacie @person@instance.com
migrationConfirm: "Czy jesteś na 200% pewn* tego, że chcesz przenieść swoje konto\
\ na {account}? Gdy to zrobisz, odwrócenie tego będzie nie możliwe, i nie będziesz\
\ w stanie ponownie używać normalnie z tego konta.\nUpewnij się, że to konto zostało\
\ ustawione jako konto z którego się przenosisz."
migrationConfirm: "Czy jesteś absolutnie pewn* tego, że chcesz przenieść swoje konto\
\ na {account}? Tego działania nie można odwrócić. Nieodwracalnie stracisz możliwość\
\ normalnego korzystania z konta.\nUpewnij się, że to konto zostało ustawione jako\
\ konto z którego się przenosisz."
noThankYou: Nie, dziękuję
addInstance: Dodaj instancję
renoteMute: Wycisz podbicia
@ -1894,25 +1904,9 @@ indexNotice: Indeksuję. Zapewne zajmie to chwilę, nie restartuj serwera przez
customKaTeXMacro: Niestandardowe makra KaTeX
enableCustomKaTeXMacro: Włącz niestandardowe makra KaTeX
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
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
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
@ -1927,7 +1921,7 @@ sendErrorReportsDescription: "Gdy ta opcja jest włączona, szczegółowe inform
\ Calckey.\nZawrze to informacje takie jak wersja twojego systemu operacyjnego,\
\ przeglądarki, Twoja aktywność na Calckey itd."
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
oneDay: Dzień
oneWeek: Tydzień
@ -1999,3 +1993,23 @@ themeColor: Kolor znacznika instancji
instanceDefaultLightTheme: Domyślny jasny motyw instancji
enableEmojiReactions: Włącz reakcje emoji
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: Загрузка не удалась
invalidFile: Неправильный формат файла
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}?\
\ Как только вы сделаете, вы не сможете отменить это и не сможете нормально использовать\
\ аккаунт снова.\nТакже, пожалуйста, убедитесь, что вы установили эту текущую учетную\
@ -2000,3 +1983,4 @@ customKaTeXMacroDescription: 'Настройте макросы чтобы ле
ветвление, здесь использоваться не может.'
cannotUploadBecauseExceedsFileSizeLimit: Этот файл не может быть загружен так как
он превышает максимально разрешённый размер.
apps: Приложения

View file

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

View file

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

View file

@ -5,7 +5,6 @@
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<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

View file

@ -4,10 +4,10 @@ export class AddSomeUrls1557761316509 {
`ALTER TABLE "meta" ADD "ToSUrl" character varying(512)`,
);
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(
`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) {

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,6 @@ export default async function renderNote(
dive = true,
isTalk = false,
): Promise<Record<string, unknown>> {
note.visibility = note.visibility === "hidden" ? "home" : note.visibility;
const getPromisedFiles = async (ids: string[]) => {
if (!ids || ids.length === 0) return [];
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_search_indexAll from "./endpoints/admin/search/index-all.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_showModerationLogs from "./endpoints/admin/show-moderation-logs.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/search/index-all", ep___admin_search_indexAll],
["admin/send-email", ep___admin_sendEmail],
["admin/send-mod-mail", ep___admin_sendModMail],
["admin/server-info", ep___admin_serverInfo],
["admin/show-moderation-logs", ep___admin_showModerationLogs],
["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,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive,
carefulBot: profile.carefulBot,

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,9 @@ block meta
unless privateMode
if profile.noCrawle
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-id' content=user.id)

View file

@ -24,6 +24,9 @@ block meta
unless privateMode
if user.host || profile.noCrawle
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-id' content=user.id)

View file

@ -36,6 +36,9 @@ block meta
unless privateMode
if user.host || isRenote || profile.noCrawle
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-id' content=user.id)

View file

@ -24,6 +24,9 @@ block meta
unless privateMode
if profile.noCrawle
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-id' content=user.id)

View file

@ -23,6 +23,9 @@ block meta
unless privateMode
if user.host || profile.noCrawle
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-id' content=user.id)

View file

@ -80,6 +80,9 @@ export async function createNotification(
setTimeout(async () => {
const fresh = await Notifications.findOneBy({ id: notification.id });
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;
//#region ただしミュートしているユーザーからの通知なら無視
@ -95,7 +98,6 @@ export async function createNotification(
//#endregion
publishMainStream(notifieeId, "unreadNotification", packed);
pushNotification(notifieeId, "notification", packed);
if (type === "follow")
sendEmailNotification.follow(

View file

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

View file

@ -144,7 +144,11 @@ export default async (
});
//#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 dm = new DeliverManager(user, content);
if (note.userHost !== null) {

View file

@ -45,21 +45,21 @@ export async function sendEmail(
<title>${subject}</title>
</head>
<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;">
<header style="padding: 32px; background: #31748f; display: flex;">
<main style="max-width: 500px; margin: 0 auto; background: #1f1d2e; color: #e0def4; border-radius: 20px;">
<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;"/>
<h1 style="margin: 0 0 1em 0;">${meta.name}</h1>
</header>
<article style="padding: 32px;">
<h1>${subject}</h1>
<div>${html}</div>
<h1 style="color: #ebbcba !important;">${subject}</h1>
<div style="color: #e0def4;">${html}</div>
</article>
<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>
</main>
<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>
</body>
</html>`,

File diff suppressed because it is too large Load diff

View file

@ -55,33 +55,33 @@ describe("fromHtml", () => {
it("link with different text", () => {
assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/b">c</a> d</p>'),
"a [c](https://example.com/b) d",
fromHtml('<p>a <a href="https://calckey.org/b">c</a> d</p>'),
"a [c](https://calckey.org/b) d",
);
});
it("link with different text, but not encoded", () => {
assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/ä">c</a> d</p>'),
"a [c](<https://example.com/ä>) d",
fromHtml('<p>a <a href="https://calckey.org/ä">c</a> d</p>'),
"a [c](<https://calckey.org/ä>) d",
);
});
it("link with same text", () => {
assert.deepStrictEqual(
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", () => {
assert.deepStrictEqual(
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", () => {
assert.deepStrictEqual(
fromHtml('<p>a <a href="https://example.com/b"></a> d</p>'),
"a https://example.com/b d",
fromHtml('<p>a <a href="https://calckey.org/b"></a> d</p>'),
"a https://calckey.org/b d",
);
});
@ -110,15 +110,15 @@ describe("fromHtml", () => {
it("mention", () => {
assert.deepStrictEqual(
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", () => {
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 d",

View file

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

View file

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

View file

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

View file

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

View file

@ -144,6 +144,20 @@ export default defineComponent({
padding: var(--x-padding);
-webkit-backdrop-filter: var(--blur, blur(8px));
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 {
margin: 0;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,19 +17,17 @@
:max-height="maxHeight"
:as-drawer="type === 'drawer'"
class="sfhdhdhq"
:class="{
drawer: type === 'drawer',
...classMap(classes),
}"
:class="{ drawer: type === 'drawer' }"
@close="modal.close()"
/>
</MkModal>
</template>
<script lang="ts" setup>
import {} from "vue";
import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue";
import { MenuClasses, MenuItem } from "@/types/menu";
import { MenuItem } from "@/types/menu";
defineProps<{
items: MenuItem[];
@ -37,7 +35,6 @@ defineProps<{
width?: number;
viaKeyboard?: boolean;
src?: any;
classes?: MenuClasses;
}>();
const emit = defineEmits<{
@ -45,18 +42,6 @@ const emit = defineEmits<{
}>();
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>
<style lang="scss" scoped>

View file

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

View file

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

View file

@ -55,7 +55,7 @@ export default defineComponent({
this.$i ? this.$i.username : "guest"
}.\nAlso, here is ${config.url} and [example link](${
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>
<div
:class="{
hasCw: !!cw,
cwHighlight,
}"
>
<p v-if="cw != null" class="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>
<Mfm
v-if="cw != ''"
class="text"
:text="cw"
:author="appearNote.user"
:i="$i"
:custom-emojis="appearNote.emojis"
<p v-if="note.cw != null" class="cw">
<MkA
v-if="!detailed && note.replyId"
:to="`/notes/${note.replyId}`"
class="reply-icon"
@click.stop
>
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
</MkA>
<MkA
v-if="
conversation &&
note.renoteId &&
note.renoteId != parentId &&
!note.replyId
"
:to="`/notes/${note.renoteId}`"
class="reply-icon"
@click.stop
>
<i class="ph-quotes ph-bold ph-lg"></i>
</MkA>
<Mfm
v-if="note.cw != ''"
class="text"
:text="note.cw"
:author="note.user"
:i="$i"
:custom-emojis="note.emojis"
/>
</p>
<div class="wrmlmaau">
<div
class="content"
: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
class="content"
:class="{ collapsed, isLong, showContent: cw && !showContent }"
class="body"
v-bind="{
'aria-label': !showContent ? '' : null,
tabindex: !showContent ? '-1' : null,
}"
>
<XCwButton
ref="cwButton"
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="note.deletedAt" style="opacity: 0.5"
>({{ i18n.ts.deleted }})</span
>
<span v-if="appearNote.deletedAt" style="opacity: 0.5"
>({{ 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"
/>
<template v-if="!note.cw">
<MkA
v-if="!detailed && appearNote.renoteId"
class="rp"
:to="`/notes/${appearNote.renoteId}`"
>{{ i18n.ts.quoteAttached }}: ...</MkA
v-if="!detailed && note.replyId"
:to="`/notes/${note.replyId}`"
class="reply-icon"
@click.stop
>
<div v-if="appearNote.files.length > 0" class="files">
<XMediaList :media-list="appearNote.files" />
</div>
<XPoll
v-if="appearNote.poll"
:note="appearNote"
class="poll"
/>
<template v-if="detailed">
<MkUrlPreview
v-for="url in urls"
:key="url"
:url="url"
:compact="true"
:detail="false"
class="url-preview"
/>
<div
v-if="appearNote.renote"
class="renote"
@click.stop="emit('push', appearNote.renote)"
>
<XNoteSimple :note="appearNote.renote" />
</div>
</template>
<div
v-if="cw && !showContent"
tabindex="0"
v-on:focus="cwButton?.focus()"
></div>
<i class="ph-arrow-bend-left-up ph-bold ph-lg"></i>
</MkA>
<MkA
v-if="
conversation &&
note.renoteId &&
note.renoteId != parentId &&
!note.replyId
"
:to="`/notes/${note.renoteId}`"
class="reply-icon"
@click.stop
>
<i class="ph-quotes ph-bold ph-lg"></i>
</MkA>
</template>
<Mfm
v-if="note.text"
:text="note.text"
:author="note.user"
:i="$i"
:custom-emojis="note.emojis"
/>
<MkA
v-if="!detailed && note.renoteId"
class="rp"
:to="`/notes/${note.renoteId}`"
>{{ i18n.ts.quoteAttached }}: ...</MkA
>
<div v-if="note.files.length > 0" class="files">
<XMediaList :media-list="note.files" />
</div>
<XShowMoreButton
v-if="isLong"
v-model="collapsed"
></XShowMoreButton>
<XCwButton v-if="cw" v-model="showContent" :note="appearNote" />
<XPoll v-if="note.poll" :note="note" class="poll" />
<template v-if="detailed">
<MkUrlPreview
v-for="url in urls"
: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>
<XShowMoreButton
v-if="isLong"
v-model="collapsed"
></XShowMoreButton>
<XCwButton v-if="note.cw" v-model="showContent" :note="note" />
</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>
</template>
@ -139,13 +144,16 @@
import { ref } from "vue";
import * as misskey from "calckey-js";
import * as mfm from "mfm-js";
import * as os from "@/os";
import XNoteSimple from "@/components/MkNoteSimple.vue";
import XMediaList from "@/components/MkMediaList.vue";
import XPoll from "@/components/MkPoll.vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue";
import XShowMoreButton from "@/components/MkShowMoreButton.vue";
import XCwButton from "@/components/MkCwButton.vue";
import MkButton from "@/components/MkButton.vue";
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
@ -162,34 +170,46 @@ const emit = defineEmits<{
(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 isLong =
!props.detailedView &&
!cw &&
appearNote.text != null &&
(appearNote.text.split("\n").length > 9 || appearNote.text.length > 500);
const collapsed = $ref(!cw && isLong);
props.note.cw == null &&
props.note.text != null &&
(props.note.text.split("\n").length > 9 || props.note.text.length > 500);
const collapsed = $ref(props.note.cw == null && isLong);
const urls = appearNote.text
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5)
const urls = props.note.text
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
: null;
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) {
if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
emit("focusfooter");
@ -219,25 +239,9 @@ function focusFooter(ev) {
overflow-wrap: break-word;
> .text {
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 {
.content {
overflow-wrap: break-word;
@ -267,9 +271,11 @@ function focusFooter(ev) {
> .url-preview {
margin-top: 8px;
}
> .poll {
font-size: 80%;
}
> .renote {
padding-top: 8px;
> * {
@ -284,6 +290,7 @@ function focusFooter(ev) {
}
}
}
&.collapsed,
&.showContent {
position: relative;
@ -326,6 +333,13 @@ function focusFooter(ev) {
}
}
}
&.disableAnim :deep(span) {
animation: none !important;
}
}
> :deep(button) {
margin-top: 10px;
}
}
</style>

View file

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

View file

@ -41,16 +41,27 @@
{{ i18n.ts.next }}</MkButton
>
</div>
<h2 class="_title title">
<i class="ph-info ph-bold ph-lg"></i>
{{ i18n.ts._tutorial.title }}
</h2>
<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>
<div>{{ i18n.ts._tutorial.step1_2 }}</div>
</div>
<div
<!-- TODO: move to own slide -->
<!-- <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"
key="2"
class="_content"
@ -60,8 +71,8 @@
<br />
<XSettings :save-button="true" />
<br />
</div>
<div
</section>
<section
v-else-if="tutorial === 2"
key="3"
class="_content"
@ -74,8 +85,8 @@
><i class="ph-check ph-bold ph-lg"></i>
{{ i18n.ts.next }}</MkButton
>
</div>
<div
</section>
<section
v-else-if="tutorial === 3"
key="4"
class="_content"
@ -90,8 +101,8 @@
</I18n>
<br />
<XPostForm class="post-form _block" />
</div>
<div
</section>
<section
v-else-if="tutorial === 4"
key="5"
class="_content"
@ -160,8 +171,8 @@
</I18n>
</li>
</ul>
</div>
<div
</section>
<section
v-else-if="tutorial === 5"
key="6"
class="_content"
@ -180,7 +191,7 @@
primary
show-only-to-register
/>
</div>
</section>
</Transition>
</div>
</div>
@ -189,7 +200,7 @@
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { reactive, computed } from "vue";
import XSettings from "@/pages/settings/profile.vue";
import XModalWindow from "@/components/MkModalWindow.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 MkSparkle from "@/components/MkSparkle.vue";
import MkPushNotificationAllowButton from "@/components/MkPushNotificationAllowButton.vue";
import FormSwitch from "@/components/form/switch.vue";
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
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) {
tutorial.value = -1;
dialog.close();

View file

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

View file

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

View file

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

View file

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