Merge branch 'develop' into refine-api-doc

This commit is contained in:
syuilo 2021-01-23 20:35:43 +09:00
commit 6db7aef322
102 changed files with 3498 additions and 709 deletions

View File

@ -1,18 +0,0 @@
name: Docker build
on:
push:
branches:
- master
- develop
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Pull previous build result (for cache)
run: docker pull misskey/misskey:latest
- name: Build docker container
run: docker build --cache-from misskey/misskey:latest -t misskey/misskey .

View File

@ -62,6 +62,8 @@ Organize and store your files! Want to post a picture you have already uploaded?
...and more! Experience Misskey with your own eyes at [misskey.io](https://misskey.io/) or join one of the [other instances](https://joinmisskey.github.io/) that are available.
To recive updates of this repo, follow [@repo@misskey.io](https://misskey.io/@repo) on fediverse.
Screen shots
----------------------------------------------------------------
### Profile page

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.413372,0,0,0.469741,64.564,40.5821)">
<rect x="-156.189" y="-86.393" width="619.297" height="544.981" style="fill:rgb(27,30,31);"/>
</g>
<g transform="matrix(0.898356,0,0,0.898356,-130.722,-120.968)">
<g transform="matrix(0.5,0.866025,-0.866025,0.5,288,-166.277)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
</g>
<g transform="matrix(1,0,0,1,-96,166.277)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
</g>
<g transform="matrix(0.5,-0.866025,0.866025,0.5,-96,498.831)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653Z" style="fill:white;"/>
</g>
<g transform="matrix(1,0,0,1,-95.9902,55.4086)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
</g>
<g transform="matrix(0.5,-0.866025,0.866025,0.5,-2.64322e-11,554.256)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
</g>
<g transform="matrix(0.5,0.866025,-0.866025,0.5,192,-110.851)">
<path d="M390.877,136.653C389.457,134.193 386.831,132.677 383.99,132.677C381.149,132.677 378.524,134.193 377.103,136.653C373.093,143.599 368.146,152.168 364.604,158.303C361.749,163.248 361.749,169.34 364.604,174.285C368.142,180.414 373.084,188.972 377.092,195.915C378.515,198.379 381.144,199.898 383.99,199.898C386.836,199.898 389.466,198.379 390.889,195.915C394.897,188.972 399.838,180.414 403.377,174.284C406.232,169.34 406.232,163.248 403.377,158.303C399.835,152.168 394.888,143.599 390.877,136.653ZM385.681,139.653C385.332,139.049 384.688,138.677 383.99,138.677C383.293,138.677 382.648,139.049 382.299,139.653C378.289,146.599 373.342,155.168 369.8,161.303C368.017,164.391 368.017,168.196 369.8,171.285C373.339,177.414 378.28,185.972 382.288,192.915C382.639,193.523 383.288,193.898 383.99,193.898C384.692,193.898 385.341,193.523 385.692,192.915C389.701,185.972 394.642,177.414 398.181,171.284C399.964,168.196 399.964,164.391 398.181,161.303L385.681,139.653Z" style="fill:rgb(150,208,74);"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/mi-white.afdesign Normal file

Binary file not shown.

BIN
assets/mi.afdesign Normal file

Binary file not shown.

View File

@ -50,11 +50,13 @@ server {
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_redirect off;
# If it's behind another reverse proxy or CDN, remove the following.
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_http_version 1.1;
proxy_redirect off;
# For WebSocket
proxy_set_header Upgrade $http_upgrade;

View File

@ -176,16 +176,23 @@ remove: "حذف"
removed: "تم حذفه بنجاح"
removeAreYouSure: "متأكد من أنك تريد حذف {x}؟"
deleteAreYouSure: "متأكد من أنك تريد حذف {x}؟"
resetAreYouSure: "هل تريد إعادة التعيين؟"
saved: "تم حفظه"
messaging: "الدردشة"
upload: "تحميل"
fromDrive: "من المخزن"
fromUrl: "من عنوان URL"
uploadFromUrl: "التحميل عبر URL"
uploadFromUrlDescription: "رابط الملف المراد تحميله "
uploadFromUrlRequested: "الرفع مطلوب"
uploadFromUrlMayTakeTime: "سيستغرق بعض الوقت لاتمام الرفع "
explore: "استكشاف"
games: "ألعاب Misskey"
messageRead: "مقروءة"
noMoreHistory: "لا يوجد المزيد من التاريخ"
startMessaging: "ابدأ الدردشة"
nUsersRead: "تمت القراءة من {n}"
agreeTo: "اوافق على {0}"
tos: "شروط الخدمة"
start: "البداية"
home: "الرئيسي"
@ -202,6 +209,7 @@ light: "فاتح"
dark: "داكن"
lightThemes: "الحلة الفاتحة"
darkThemes: "الحلة الداكنة"
syncDeviceDarkMode: "مطابقة الوضع المضلمومع اعدادات الجهاز"
drive: "قرص التخرين"
fileName: "اسم الملف"
selectFile: "اختر ملفًا"
@ -219,14 +227,17 @@ emptyFolder: "هذا المجلد فارغ"
unableToDelete: "لا يمكن حذفه"
inputNewFileName: "ادخل الإسم الجديد للملف"
inputNewFolderName: "ادخل الإسم الجديد للمجلد"
hasChildFilesOrFolders: "الان الملف غير فارغ. لا يمكن حذفه"
copyUrl: "انسخ عنوان URL"
rename: "إعادة التسمية"
avatar: "الصورة الرمزية"
banner: "الصورة الرأسية"
nsfw: "محتوى حساس"
whenServerDisconnected: "عند فقدان الاتصال بالخادم"
disconnectedFromServer: "قُطِع الإتصال بالخادم"
reload: "انعش"
doNothing: "تجاهل"
reloadConfirm: "هل ترغب في تحديث الجدول الزمني؟"
watch: "راقب"
unwatch: "إلغاء المراقبة"
accept: "السماح"
@ -244,10 +255,12 @@ dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "الصفحات"
integration: "دمج"
connectSerice: "أوصل"
disconnectSerice: "قطع الاتصال"
enableLocalTimeline: "تفعيل الخيط المحلي"
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
disablingTimelinesInfo: "سيتمكن المسؤولون ومن تعديل دائمًا و من الوصول إلى جميع المخططات الزمنية ، حتى إذا لم يتم تمكينها."
registration: "إنشاء حساب"
enableRegistration: "تفعيل إنشاء الحسابات الجديدة"
invite: "دعوة"
@ -289,6 +302,8 @@ resetPassword: "أعد تعيين كلمتك السرية"
newPasswordIs: "كلمتك السرية الجديدة هي {password}"
share: "شارِك"
notFound: "غير موجود"
cacheClear: "مسح ذاكرة التخزين المؤقت"
markAsReadAllNotifications: "وضع جميع الإشعارات كأنها مقروءة"
help: "المساعدة"
inputMessageHere: "اكتب رسالتك هنا"
close: "اغلق"
@ -329,6 +344,7 @@ aboutX: "عن {x}"
useOsNativeEmojis: "استخدم الإيموجيات الخاصة بنظام التشغيل"
youHaveNoGroups: "لا تمتلك أية فِرَق"
noHistory: "السجل فارغ"
signinHistory: "تاريخ تسجيل الدخول"
doing: "انتظر لحظة"
category: "الفئات"
tags: "الوسوم"
@ -337,6 +353,7 @@ createAccount: "أنشئ حسابًا"
existingAcount: "الحسابات الموجودة"
regenerate: "أعِد التوليد"
fontSize: "حجم الخط"
openImageInNewTab: "إفتح الصورة بصفحة جديدة"
dashboard: "لوحة التحكم"
local: "المحلي"
remote: "بُعدي"
@ -365,6 +382,9 @@ state: "الحالة"
sort: "ترتيب حسب"
output: "الخارجة"
updateRemoteUser: "تحديث المعلومات عن المستخدم البعيد"
deleteAllFiles: "حذف كافة الملفات"
userSuspended: "تم تعليق هذا المستخدم."
userSilenced: "تم إسكات هذا المستخدم."
sidebar: "الشريط الجانبي"
addItem: "إضافة عنصر"
rooms: "الغرفة"
@ -374,14 +394,35 @@ addedRelays: "المرحلات التي تم إضافتها"
deletedNote: "ملاحظة محذوفة"
invisibleNote: "ملاحظة مخفية"
poll: "استطلاع رأي"
useCw: "إخفاء المحتوى"
themeEditor: "مصمم القوالب"
manage: "إدارة "
plugins: "الإضافات"
pluginInstallWarn: "يرجى تنصيب إضافات ذات مصدر موثوق منه فقط."
width: "العرض"
height: "الإرتفاع"
large: "كبير"
medium: "متوسط"
small: "صغير"
permission: "أذونات"
enableAll: "تشغيل الكل"
disableAll: "تعطيل الكل"
tokenRequested: "منح حق الوصول إلى الحساب"
notificationType: "أنواع الإشعارات"
edit: "التعديل"
email: "البريد الإلكتروني "
emailAddress: "عنوان البريد الالكتروني"
smtpHost: "المضيف"
smtpPort: "المنفذ"
smtpUser: "اسم المستخدم"
smtpPass: "الكلمة السرية"
makeActive: "تفعيل"
display: "المظهر"
copy: "نسخ"
metrics: "المقاييس"
public: "للعامة"
currentVersion: "الإصدار الحالي"
latestVersion: "آخر نسخة مستقرة"
_mfm:
mention: "أشر الى"
quote: "اقتبس"
@ -454,6 +495,7 @@ _widgets:
activity: "النشاط"
photos: "الصور"
federation: "الفديرالية"
jobQueue: "قائمة الانتظار"
_cw:
hide: "إخفاء"
show: "عرض المزيد"

View File

@ -677,6 +677,27 @@ clearCache: "Cache leeren"
onlineUsersCount: "{n} Benutzer sind online"
nUsers: "{n} Benutzer"
nNotes: "{n} Notizen"
sendErrorReports: "Fehlerberichte senden"
sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt."
myTheme: "Mein Farbthema"
backgroundColor: "Hintergrund"
accentColor: "Akzentfarbe"
textColor: "Text"
saveAs: "Speichern als…"
advanced: "Fortgeschritten"
value: "Wert"
updatedAt: "Zuletzt geändert am"
saveConfirm: "Änderungen speichern?"
deleteConfirm: "Wirklich löschen?"
invalidValue: "Ungültiger Wert."
registry: "Registry"
closeAccount: "Benutzerkonto schließen"
_registry:
scope: "Scope"
key: "Schlüssel"
keys: "Schlüssel"
domain: "Domain"
createKey: "Schlüssel erstellen"
_aboutMisskey:
about: "Misskey ist Open-Source-Software die von syuilo seit 2014 entwickelt wird."
contributors: "Hauptmitwirkende"
@ -915,7 +936,7 @@ _tutorial:
title: "Wie du Misskey verwendest"
step1_1: "Willkommen!"
step1_2: "Diese Seite ist die \"Chronik\". Sie zeigt dir deine geschrieben \"Notizen\" sowie die aller Benutzer, denen du \"folgst\" in chronologischer Reihenfolge."
step1_3: "Deine Chronik sollte momentan leer sein, da du bis jetzt nocht keine Notizen geschrieben hast und auch noch keinen Benutzern folgst."
step1_3: "Deine Chronik sollte momentan leer sein, da du bis jetzt noch keine Notizen geschrieben hast und auch noch keinen Benutzern folgst."
step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst oder jemandem folgst."
step2_2: "Informationen darüber, wer du bist, macht es anderen leichter zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten."
step3_1: "Mit dem Einrichten deines Profils fertig?"
@ -1008,7 +1029,8 @@ _widgets:
postForm: "Neue Notiz anfertigen"
slideshow: "Diashow"
button: "Knopf"
onlineUsers: "Benutzer die Online sind"
onlineUsers: "Benutzer Online"
jobQueue: "Job-Warteschlange"
serverMetric: "Servermetriken"
_cw:
hide: "Ausblenden"
@ -1501,6 +1523,7 @@ _deck:
swapDown: "Nach unten verschieben"
stackLeft: "Auf linke Spalte stapeln"
popRight: "Nach rechts vom Stapel nehmen"
profile: "Profil"
_columns:
main: "Hauptspalte"
widgets: "Widgets"

View File

@ -1,7 +1,7 @@
---
_lang_: "English"
headlineMisskey: "Network connected by notes"
introMisskey: "Welcome! Misskey is an open source, and also a decentralized microblogging service.\nCreate \"notes\" to share what is happening now, or to share it with everyone around you 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes 👍\nLet's explore a new world 🚀"
introMisskey: "Welcome! Misskey is an open source, decentralized microblogging service.\nCreate \"notes\" to share what is happening now, or to share it with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀"
monthAndDay: "{month}/{day}"
search: "Search"
notifications: "Notifications"
@ -314,7 +314,7 @@ enableLocalTimeline: "Enable local timeline"
enableGlobalTimeline: "Enable global timeline"
disablingTimelinesInfo: "Admins and Mods will always have access to all timelines, even if they are not enabled."
registration: "Register"
enableRegistration: "Enable new user registeration"
enableRegistration: "Enable new user registration"
invite: "Invite"
proxyRemoteFiles: "Proxy remote files"
proxyRemoteFilesDescription: "If enabled, remote files that (1) are not stored locally or (2) got deleted from exceeding storage limit will be locally proxied (with thumbnails). This does not affect the server's storage."
@ -346,8 +346,8 @@ antennaSource: "Antenna source"
antennaKeywords: "Keywords to receive"
antennaExcludeKeywords: "Keywords to exclude"
antennaKeywordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
notifyAntenna: "Notify newer notes"
withFileAntenna: "Filter only notes with file attached"
notifyAntenna: "Notify for new notes"
withFileAntenna: "Filter only notes with file(s) attached"
serviceworker: "ServiceWorker"
enableServiceworker: "Enable ServiceWorker"
antennaUsersDescription: "List one username per line"
@ -646,8 +646,8 @@ no: "No"
driveFilesCount: "Amount of drive files"
driveUsage: "Drive space usage"
noCrawle: "Reject crawler indexing"
noCrawleDescription: "Ask search engines not to index your profile page, notes, Pages, etc"
lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", your notes are visible to anyone even if you require followers to be manually approved."
noCrawleDescription: "Ask search engines not to index your profile page, notes, Pages, etc."
lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", your notes are visible to anyone, even if you require followers to be manually approved."
alwaysMarkSensitive: "Mark NSFW by default"
loadRawImages: "Display image attachments fully instead of thumbnails"
disableShowingAnimatedImages: "Don't play animated images"
@ -677,6 +677,27 @@ clearCache: "Clear cache"
onlineUsersCount: "{n} people are online"
nUsers: "{n} Users"
nNotes: "{n} Notes"
sendErrorReports: "Send error reports"
sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey."
myTheme: "My theme"
backgroundColor: "Background"
accentColor: "Accent"
textColor: "Text"
saveAs: "Save as..."
advanced: "Advanced"
value: "Value"
updatedAt: "Updated at"
saveConfirm: "Save changes?"
deleteConfirm: "Really delete?"
invalidValue: "Invalid value."
registry: "Registry"
closeAccount: "Close account"
_registry:
scope: "Scope"
key: "Key"
keys: "Keys"
domain: "Domain"
createKey: "Create key"
_aboutMisskey:
about: "Misskey is open-source software being developed by syuilo since 2014."
contributors: "Main contributors"
@ -1009,6 +1030,7 @@ _widgets:
slideshow: "Slideshow"
button: "Button"
onlineUsers: "Online users"
jobQueue: "Job Queue"
serverMetric: "Server metrics"
_cw:
hide: "Hide"
@ -1501,6 +1523,7 @@ _deck:
swapDown: "Swap with below"
stackLeft: "Stack on left column"
popRight: "Pop to the right"
profile: "Profile"
_columns:
main: "Main"
widgets: "Widgets"

View File

@ -648,6 +648,13 @@ noCrawle: "Rechazar indexación del crawler"
noCrawleDescription: "Pedir a los motores de búsqueda que no indexen tu perfil, notas, páginas, etc."
clips: "Clip"
clearCache: "Limpiar caché"
backgroundColor: "Fondo"
accentColor: "Acento"
textColor: "Texto"
value: "Valores"
_registry:
key: "Clave"
keys: "Clave"
_mfm:
cheatSheet: "Hoja de referencia de MFM"
intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquí puede ver una lista de sintaxis disponibles en MFM."
@ -921,6 +928,7 @@ _widgets:
federation: "Federación"
postForm: "Formulario"
button: "Botón"
jobQueue: "Cola de trabajos"
_cw:
hide: "Ocultar"
show: "Ver más"

View File

@ -1,5 +1,6 @@
---
_lang_: "Français"
headlineMisskey: "Réseau relié par des notes"
introMisskey: "Bienvenue ! Misskey est un service de microblogage décentralisé, libre et ouvert.\nÉcrivez des « notes » et partagez ce qui se passe à linstant présent, autour de vous avec les autres 📡\nLa fonction « réactions », vous permet également dajouter une réaction rapide aux notes des autres utilisateur·rice·s 👍\nExplorons un nouveau monde 🚀"
monthAndDay: "{day}/{month}"
search: "Rechercher"
@ -33,6 +34,9 @@ addUser: "Ajouter un·e utilisateur·rice"
favorite: "Ajouter aux favoris"
favorites: "Favoris"
unfavorite: "Retirer des favoris"
favorited: "Ajouter à mes favoris"
alreadyFavorited: "Déjà ajouté aux favoris."
cantFavorite: "Impossible d'ajouter aux favoris."
pin: "Épingler sur le profil"
unpin: "Désépingler"
copyContent: "Copier le contenu"
@ -75,6 +79,7 @@ error: "Erreur"
somethingHappened: "Une erreur est survenue"
retry: "Réessayer"
pageLoadError: "Le chargement de la page a échoué"
pageLoadErrorDescription: "Cela est généralement causé par le cache du navigateur ou par un problème réseau. Veuillez vider votre cache ou attendre un peu et réessayer."
enterListName: "Nom de la liste"
privacy: "Confidentialité"
makeFollowManuallyApprove: "Accepter manuellement les demandes dabonnement"
@ -87,6 +92,8 @@ followRequestPending: "En attente dapprobation"
enterEmoji: "ou entrez un émoji"
renote: "Renote"
unrenote: "Annuler Renote"
renoted: "Republier"
cantRenote: "Ce message ne peut pas être republié."
quote: "Citer"
pinnedNote: "Note épinglée"
you: "Vous"
@ -124,7 +131,9 @@ settingGuide: "Configuration proposée"
cacheRemoteFiles: "Mise en cache des fichiers distants"
cacheRemoteFilesDescription: "Lorsque cette option est désactivée, les fichiers distants sont chargés directement depuis linstance distante. La désactiver diminuera certes lutilisation de lespace de stockage local mais augmentera le trafic réseau puisque les miniatures ne seront plus générées."
flagAsBot: "Ce compte est un robot"
flagAsBotDescription: "Si ce compte est contrôlé par un programme, définissez cette option. Si elle est activée, elle agira comme un marqueur pour les autres développeurs afin d'éviter des chaînes d'interaction sans fin avec d'autres robots et d'ajuster les systèmes internes de Misskey pour traiter ce compte comme un robot."
flagAsCat: "Ce compte est un chat"
flagAsCatDescription: "Activez cette option pour que ce compte soit marqué comme un chat."
autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s que vous suivez"
addAcount: "Ajouter un compte"
loginFailed: "Échec de la connexion"
@ -215,6 +224,7 @@ remove: "Supprimer"
removed: "Supprimé"
removeAreYouSure: "Supprimer «{x}» ?"
deleteAreYouSure: "Supprimer «{x}» ?"
resetAreYouSure: "Réinitialiser ?"
saved: "Enregistré"
messaging: "Discuter"
upload: "Téléverser"
@ -429,6 +439,7 @@ useOsNativeEmojis: "Utiliser les émojis natifs du système"
youHaveNoGroups: "Vous navez aucun groupe"
joinOrCreateGroup: "Soyez invité à rejoindre les groupes ou vous pouvez créer votre propre groupe."
noHistory: "Pas d'historique"
signinHistory: "Historique de connexion"
disableAnimatedMfm: "Désactiver MFM ayant des animations"
doing: "Attends une seconde"
category: "Catégories"
@ -479,6 +490,7 @@ listen: "Écouter"
none: "Rien"
popout: "Fenêtre contextuelle"
volume: "Volume"
masterVolume: "Volume"
details: "Détails"
chooseEmoji: "Choisissez un émoji"
unableToProcess: "Lopération na pas pu être complétée."
@ -535,6 +547,9 @@ undeck: "Quitter le deck"
useBlurEffectForModal: "Utiliser un effet de flou pour les modals"
width: "Largeur"
height: "Hauteur"
large: "Grand"
medium: "Moyen"
small: "Petit"
generateAccessToken: "Générer un jeton d'accès"
permission: "Autorisations "
enableAll: "Tout activer"
@ -547,6 +562,8 @@ useStarForReactionFallback: "Utiliser ★ comme alternative si lémoji de ré
emailConfig: "Configuration du serveur email"
enableEmail: "Activer la distribution de courriel"
emailConfigInfo: "Utilisé pour confirmer votre adresse de courriel et la réinitialisation de votre mot de passe en cas doubli."
email: "E-mail "
emailAddress: "Adresses e-mail"
smtpConfig: "Paramètres du serveur SMTP"
smtpHost: "Hôte"
smtpPort: "Port"
@ -571,20 +588,76 @@ create: "Créer"
notificationSetting: "Paramètres des notifications "
notificationSettingDesc: "Sélectionnez le type de notification à afficher"
useGlobalSetting: "Utiliser paramètre général"
useGlobalSettingDesc: "S'il est activé, les paramètres de notification de votre compte seront utilisés. S'il est désactivé, des configurations individuelles peuvent être effectuées."
other: "Autre"
regenerateLoginToken: "Régénérer le jeton de connexion"
setMultipleBySeparatingWithSpace: "Vous pouvez définir plus dun, séparés par des espaces."
fileIdOrUrl: "ID du fichier ou URL"
chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouverture"
abuseReports: "Signalements"
reportAbuse: "Signalements"
reportAbuseOf: "Signaler {name}"
send: "Envoyer"
abuseMarkAsResolved: "Marquer le signalement comme résolu"
openInNewTab: "Ouvrir dans un nouvel onglet"
waitingFor: "En attente de {x}"
random: "Aléatoire"
clip: "Clip"
createNewClip: "Créer un nouveau clip"
public: "Public"
manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte "
notesCount: "Nombre de notes"
repliesCount: "Nombre de réponses envoyées"
renotesCount: "Nombre de notes repartagées"
repliedCount: "Nombre de réponses reçues"
followingCount: "Nombre de comptes suivis"
driveFilesCount: "Nombre de fichiers dans le Drive"
noCrawle: "Refuser l'indexation par les robots"
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page de profil, vos notes, vos pages, etc."
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre note sur \"Abonné-e-s\", vos notes sont visibles par tous, même si vous exigez que les followers soient approuvés manuellement."
loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au lien pour compléter la vérification."
notSet: "Non défini"
emailVerified: "Votre adresse e-mail a été vérifiée."
clips: "Clip"
experimentalFeatures: "Fonctionnalités expérimentales"
makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"."
makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra pas sur la page \"Découvrir\"."
showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline"
left: "Gauche"
showTitlebar: "Afficher la barre de titre"
clearCache: "Vider le cache"
onlineUsersCount: "{n} utilisateur(s) en ligne"
nNotes: "{n} Notes"
sendErrorReports: "Envoyer les rapports derreur"
sendErrorReportsDescription: "Lorsqu'il est activé, des informations détaillées sur les erreurs sont partagées avec Misskey lorsqu'un problème survient, ce qui contribue à améliorer la qualité de Misskey."
myTheme: "Mes thèmes"
backgroundColor: "Arrière-plan"
textColor: "Texte"
saveAs: "Enregistrer sous ..."
value: "Valeur"
saveConfirm: "Voulez-vous sauvegarder les modifications?"
closeAccount: "Fermer le compte"
_registry:
key: "Clé "
keys: "Clé "
_aboutMisskey:
about: "Misskey est un logiciel libre et ouvert, développé par syuilo depuis 2014."
contributors: "Principaux contributeurs"
allContributors: "Tous les contributeurs"
source: "Code source"
translation: "Traduire Misskey"
donate: "Soutenir Misskey"
morePatrons: "Nous apprécions vraiment le soutien de nombreuses autres personnes non mentionnées ici. Merci à toutes et à tous ! 🥰"
_mfm:
mention: "Mentionner"
hashtag: "Hashtags"
link: "Lien"
center: "Centrée"
quote: "Citer"
quoteDescription: "Affiche le contenu sous forme de citation."
emoji: "Émojis personnalisés"
search: "Rechercher"
font: "Police de caractères"
@ -592,6 +665,7 @@ _reversi:
total: "Total"
_serverDisconnectedBehavior:
reload: "Rechargement automatique"
quiet: "Afficher un avertissement discret"
_channel:
create: "Créer un canal"
edit: "Éditer le canal"
@ -613,6 +687,7 @@ _theme:
manage: "Gestion des thèmes"
code: "Code du thème"
installed: "{name} a été installé"
installedThemes: "Thèmes installés"
alreadyInstalled: "Ce thème est déjà installé"
invalid: "Le format du thème n'est pas valide"
make: "Créer un thème"
@ -766,6 +841,7 @@ _widgets:
federation: "Fédération"
postForm: "Formulaire à publier"
button: "Bouton"
jobQueue: "File dattente"
_cw:
hide: "Masquer"
show: "Afficher plus …"

View File

@ -175,6 +175,17 @@ basicInfo: "Informasi Umum"
pinnedUsers: "Pengguna yang disematkan"
pinnedPages: "Halaman yang disematkan"
pinnedNotes: "Note yang disematkan"
hcaptcha: "hCaptcha"
enableHcaptcha: "Nyalakan hCaptcha"
hcaptchaSiteKey: "Site Key"
hcaptchaSecretKey: "Secret Key"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Nyalakan reCAPTCHA"
recaptchaSiteKey: "Site key"
recaptchaSecretKey: "Secret Key"
antennas: "Antena"
manageAntennas: "Pengelola Antena"
notFound: "Tidak dapat ditemukan"
invites: "Undang"
invitations: "Undang"
smtpUser: "Nama Pengguna"
@ -205,3 +216,4 @@ _deck:
_columns:
notifications: "Notifikasi"
tl: "Linimasa"
antenna: "Antena"

View File

@ -678,13 +678,34 @@ onlineUsersCount: "{n}人がオンライン"
nUsers: "{n}ユーザー"
nNotes: "{n}ノート"
sendErrorReports: "エラーリポートを送信"
sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。"
sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。"
myTheme: "マイテーマ"
backgroundColor: "背景"
accentColor: "アクセント"
textColor: "文字"
saveAs: "名前を付けて保存"
advanced: "高度"
value: "値"
updatedAt: "更新日時"
saveConfirm: "保存しますか?"
deleteConfirm: "削除しますか?"
invalidValue: "有効な値ではありません。"
registry: "レジストリ"
closeAccount: "アカウントを閉鎖する"
currentVersion: "現在のバージョン"
latestVersion: "最新のバージョン"
youAreRunningUpToDateClient: "お使いのクライアントは最新です。"
newVersionOfClientAvailable: "新しいバージョンのクライアントが利用可能です。"
usageAmount: "使用量"
capacity: "容量"
inUse: "使用中"
_registry:
scope: "スコープ"
key: "キー"
keys: "キー"
domain: "ドメイン"
createKey: "キーを作成"
_aboutMisskey:
about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。"
@ -1558,6 +1579,7 @@ _deck:
swapDown: "下に移動"
stackLeft: "左に重ねる"
popRight: "右に出す"
profile: "プロファイル"
_columns:
main: "メイン"

View File

@ -499,6 +499,7 @@ _widgets:
timeline: "タイムライン"
activity: "アクティビティ"
federation: "連合"
jobQueue: "ジョブキュー"
_cw:
show: "もっとあるやろ!"
_poll:

View File

@ -33,6 +33,9 @@ addUser: "유저 추가"
favorite: "즐겨찾기"
favorites: "즐겨찾기"
unfavorite: "즐겨찾기에서 제거"
favorited: "즐겨찾기에 등록했습니다"
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다"
cantFavorite: "즐겨찾기에 등록하지 못했습니다"
pin: "프로필에 고정"
unpin: "프로필에서 고정 해제"
copyContent: "내용 복사"
@ -88,6 +91,9 @@ followRequestPending: "팔로우 허가 대기중"
enterEmoji: "이모지 입력"
renote: "Renote"
unrenote: "Renote 취소"
renoted: "Renote 하였습니다"
cantRenote: "이 게시물은 Renote할 수 없습니다."
cantReRenote: "Renote를 Renote할 수 없습니다."
quote: "인용"
pinnedNote: "고정해놓은 노트"
you: "당신"
@ -96,6 +102,7 @@ sensitive: "열람주의"
add: "추가"
reaction: "리액션"
reactionSettingDescription: "리액션 선택 상자에 표시할 리액션을 설정합니다."
reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
rememberNoteVisibility: "공개 범위를 기억하기"
attachCancel: "첨부 취소"
markAsSensitive: "열람주의로 설정"
@ -320,6 +327,7 @@ pinnedUsers: "고정된 유저"
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
pinnedPages: "고정한 페이지"
pinnedPagesDescription: "인스턴스의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
pinnedClipId: "고정할 클립의 ID"
pinnedNotes: "고정해놓은 노트"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptcha 활성화"
@ -453,6 +461,7 @@ remote: "리모트"
total: "합계"
weekOverWeekChanges: "지난주보다"
dayOverDayChanges: "어제보다"
appearance: "모양"
clientSettings: "클라이언트 설정"
accountSettings: "계정 설정"
promotion: "프로모션"
@ -556,7 +565,10 @@ notificationType: "알림 유형"
edit: "편집"
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
emailConfig: "메일 서버 설정"
enableEmail: "이메일 송신 기능 활성화"
emailConfigInfo: "가입 시 메일 주소 확인이나 비밀번호 초기화 시에 사용합니다."
email: "이메일"
emailAddress: "메일 주소"
smtpConfig: "SMTP 서버 설정"
smtpHost: "호스트"
smtpPort: "포트"
@ -567,9 +579,11 @@ smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다."
testEmail: "이메일 전송 테스트"
wordMute: "단어 뮤트"
userSaysSomething: "{name}님이 무언가를 말했습니다"
makeActive: "활성화"
display: "표시"
copy: "복사"
metrics: "통계"
overview: "요약"
logs: "로그"
delayed: "지연"
@ -578,6 +592,7 @@ channel: "채널"
create: "생성"
notificationSetting: "알림 설정"
notificationSettingDesc: "표시할 알림의 종류를 선택해 주세요."
useGlobalSetting: "글로벌 설정을 사용하기"
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
other: "기타"
regenerateLoginToken: "로그인 토큰을 재생성"
@ -593,7 +608,12 @@ reportAbuseOf: "{name}을 신고하기"
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
send: "전송"
abuseMarkAsResolved: "해결됨으로 표시"
openInNewTab: "새 탭에서 열기"
openInSideView: "사이드뷰로 열기"
editTheseSettingsMayBreakAccount: "이 설정을 변경하면 계정이 손상될 수 있습니다."
instanceTicker: "노트의 인스턴스 정보"
waitingFor: "{x}을(를) 기다리고 있습니다"
random: "랜덤"
system: "시스템"
switchUi: "UI 전환"
@ -603,22 +623,94 @@ createNew: "새로 만들기"
optional: "옵션"
createNewClip: "새 클립 만들기"
public: "공개"
i18nInfo: "Misskey는 자원봉사자들에 의해 다양한 언어로 번역되고 있습니다. {link}에서 번역에 참가할 수 있습니다."
manageAccessTokens: "액세스 토큰 관리"
accountInfo: "계정 정보"
notesCount: "노트 수"
repliesCount: "답글 수"
renotesCount: "Renote 수"
repliedCount: "받은 답글 수"
renotedCount: "받은 Renote 수"
followingCount: "팔로우 수"
followersCount: "팔로워 수"
sentReactionsCount: "보낸 리액션 수"
receivedReactionsCount: "받은 리액션 수"
pollVotesCount: "투표한 횟수"
pollVotedCount: "투표받은 횟수"
yes: "예"
no: "아니오"
driveFilesCount: "드라이브 파일 개수"
driveUsage: "드라이브 사용량"
noCrawle: "검색엔진의 인덱싱 거부"
noCrawleDescription: "검색엔진에 사용자 페이지, 노트, 페이지 등의 콘텐츠를 인덱싱되지 않게 합니다."
lockedAccountInfo: "팔로우를 승인으로 승인받더라도 노트의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 노트를 볼 수 있습니다."
alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
disableShowingAnimatedImages: "움직이는 이미지를 자동으로 재생하지 않음"
verificationEmailSent: "확인 메일을 발송하였습니다. 설정을 완료하려면 메일에 첨부된 링크를 확인해 주세요."
notSet: "설정되지 않음"
emailVerified: "메일 주소가 확인되었습니다."
noteFavoritesCount: "즐겨찾기한 노트 수"
pageLikesCount: "좋아요 한 Page 수"
pageLikedCount: "Page에 받은 좋아요 수"
reversiCount: "리버시 대국 횟수"
contact: "연락처"
useSystemFont: "시스템 기본 글꼴을 사용"
clips: "클립"
experimentalFeatures: "실험실"
developer: "개발자"
makeExplorable: "\"발견하기\"에 내 계정 보이기"
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
duplicate: "복제"
left: "왼쪽"
center: "가운데"
wide: "넓게"
narrow: "좁게"
reloadToApplySetting: "이 설정을 적용하려면 페이지를 새로고침해야 합니다. 바로 새로고침하시겠습니까?"
showTitlebar: "타이틀 바를 표시하기"
clearCache: "캐시 비우기"
onlineUsersCount: "{n}명이 접속 중"
nUsers: "{n} 유저"
nNotes: "{n} 노트"
sendErrorReports: "오류 보고서 보내기"
sendErrorReportsDescription: "이 설정을 활성화하면, 문제가 발생했을 때 오류에 대한 상세 정보를 Misskey에 보내어 더 나은 소프트웨어를 만드는 데에 도움을 줄 수 있습니다."
myTheme: "내 테마"
backgroundColor: "배경 색"
accentColor: "강조 색상"
textColor: "문자 색"
saveAs: "다른 이름으로 저장"
advanced: "고급"
value: "값"
updatedAt: "수정한 날짜"
saveConfirm: "저장하시겠습니까?"
deleteConfirm: "삭제하시겠습니까?"
invalidValue: "올바른 값이 아닙니다."
registry: "레지스트리"
closeAccount: "계정 폐쇄"
_registry:
scope: "범위"
key: "키"
keys: "키"
domain: "도메인"
createKey: "키 생성"
_aboutMisskey:
about: "Misskey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
contributors: "주요 기여자"
allContributors: "모든 기여자"
source: "소스 코드"
translation: "Misskey를 번역하기"
donate: "Misskey에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자"
_nsfw:
respect: "열람주의로 설정된 미디어 숨기기"
ignore: "열람 주의 미디어 항상 표시"
force: "미디어 항상 숨기기"
_mfm:
cheatSheet: "MFM 도움말"
intro: "MFM는 Misskey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 수 있습니다."
dummy: "Misskey로 연합우주의 세계가 펼쳐집니다"
mention: "멘션"
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
hashtag: "해시태그"
@ -626,12 +718,16 @@ _mfm:
url: "URL"
urlDescription: "URL을 나타낼 수 있습니다."
link: "링크"
linkDescription: "문장의 특정 범위를 URL로 표시합니다."
bold: "굵음/볼드체"
boldDescription: "문자를 굵게 강조합니다."
smallDescription: "내용을 작고 연하게 보이게 합니다."
center: "가운데 정렬"
centerDescription: "내용을 가운데 정렬로 보이게 합니다."
inlineCode: "코드(인라인)"
inlineCodeDescription: "여러 행의 코드를 문법 강조를 적용하여 인라인으로 표시합니다."
blockCode: "코드(블록)"
blockCodeDescription: "여러 행의 코드를 문법 강조를 적용하여 블록으로 표시합니다."
inlineMath: "수식(인라인)"
inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다."
blockMath: "수식(블록)"
@ -644,7 +740,14 @@ _mfm:
flip: "플립"
flipDescription: "내용을 상하 또는 좌우로 반전시킵니다."
jump: "애니메이션(점프)"
x2: "크게"
x2Description: "내용을 크게 표시합니다."
x3: "더 크게"
x3Description: "내용을 더 크게 표시합니다."
x4: "매우 크게"
x4Description: "내용을 매우 크게 표시합니다."
font: "폰트"
fontDescription: "내용의 글꼴을 지정할 수 있습니다."
_reversi:
reversi: "리버시"
gameSettings: "대국 설정"
@ -657,6 +760,8 @@ _reversi:
waitingForOther: "상대의 준비가 완료될 때까지 기다리고 있습니다"
waitingForMe: "당신의 준비 완료를 기다리고 있습니다"
ready: "준비 완료"
cancelReady: "준비 취소"
opponentTurn: "상대의 차례입니다"
myTurn: "당신의 차례입니다"
turnOf: "{name}님의 차례입니다"
pastTurnOf: "{name}님의 차례"
@ -679,11 +784,17 @@ _instanceTicker:
none: "보이지 않음"
remote: "리모트 유저에게만 보이기"
always: "항상 보이기"
_serverDisconnectedBehavior:
reload: "자동으로 새로고침"
dialog: "경고창 표시"
quiet: "조용히 경고"
_channel:
create: "채널 생성"
edit: "채널 편집"
setBanner: "배너 설정"
removeBanner: "배너 삭제"
featured: "트렌드"
owned: "관리중"
following: "팔로잉"
usersCount: "{n}명 참여 중"
notesCount: "{n}노트"
@ -693,6 +804,9 @@ _sidebar:
_wordMute:
muteWords: "뮤트할 단어"
muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다。"
muteWordsDescription2: "정규 표현식을 사용하려면 키워드를 빗금표(/)로 감싸 주세요."
softDescription: "지정한 조건의 노트를 타임라인에서 숨깁니다."
hardDescription: "지정한 조건의 노트를 타임라인에 추가하지 않습니다. 타임라인에 추가되지 않은 노트는 조건을 변경해도 표시되지 않습니다."
mutedNotes: "뮤트된 노트"
_theme:
explore: "테마 찾아보기"
@ -700,6 +814,8 @@ _theme:
manage: "테마 관리"
code: "테마 코드"
installed: "{name} 테마가 설치되었습니다"
installedThemes: "설치된 테마"
builtinThemes: "표준 테마"
alreadyInstalled: "이미 설치된 테마입니다"
invalid: "테마 형식이 올바르지 않습니다"
make: "테마 만들기"
@ -714,8 +830,11 @@ _theme:
func: "함수"
funcKind: "함수 종류"
argument: "매개변수"
inputConstantName: "상수 이름을 입력하세요"
importInfo: "여기에 테마 코드를 붙여 넣어 에디터로 불러올 수 있습니다."
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
keys:
accent: "강조 색상"
link: "링크"
hashtag: "해시태그"
mention: "멘션"
@ -728,6 +847,9 @@ _sfx:
chat: "대화"
chatBg: "대화 (백그라운드)"
antenna: "안테나 수신"
channel: "채널 알림"
reversiPutBlack: "리버시: 흑돌을 두었을 때"
reversiPutWhite: "리버시: 백돌을 두었을 때"
_ago:
unknown: "알 수 없음"
future: "미래"
@ -803,6 +925,8 @@ _permissions:
"write:page-likes": "페이지의 좋아요를 추가하거나 삭제합니다"
"read:user-groups": "유저 그룹을 조회합니다"
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
"read:channels": "채널을 보기"
"write:channels": "채널을 변경하기"
_auth:
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
@ -837,7 +961,10 @@ _widgets:
digitalClock: "디지털 시계"
federation: "연합"
postForm: "글 입력란"
slideshow: "슬라이드 쇼"
button: "버튼"
jobQueue: "작업 대기열"
serverMetric: "서버 통계"
_cw:
hide: "숨기기"
show: "더 보기"
@ -879,6 +1006,7 @@ _visibility:
_postForm:
replyPlaceholder: "이 노트에 답글..."
quotePlaceholder: "이 노트를 인용..."
channelPlaceholder: "채널에 게시하기..."
_placeholders:
a: "지금 무엇을 하고 있나요?"
b: "무슨 일이 일어나고 있나요?"

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,31 @@
---
_lang_: "Português"
monthAndDay: "{day}/{month}"
search: "Pesquisar"
notifications: "Notificações"
username: "Nome de usuário"
password: "Senha"
ok: "OK"
cancel: "Cancelar"
enterUsername: "Digite o nome de usuário"
renotedBy: "Repostado por {user}"
settings: "Configurações"
basicSettings: "Configurações básicas"
otherSettings: "Outras configurações"
profile: "Perfil"
timeline: "Timeline"
smtpUser: "Nome de usuário"
smtpPass: "Senha"
_mfm:
search: "Pesquisar"
_sfx:
notification: "Notificações"
_widgets:
notifications: "Notificações"
timeline: "Timeline"
_profile:
username: "Nome de usuário"
_deck:
_columns:
notifications: "Notificações"
tl: "Timeline"

View File

@ -674,6 +674,13 @@ clearCache: "Очистить кэш"
onlineUsersCount: "Пользователей сейчас в сети: {n}"
nUsers: "Пользователей: {n}"
nNotes: "Заметок: {n}"
backgroundColor: "Фон"
accentColor: "Акцент"
textColor: "Текст"
value: "Значения"
_registry:
key: "Ключ"
keys: "Ключ"
_aboutMisskey:
about: "Misskey — программа с открытым исходным кодом, которую разрабатывает syuilo с 2014 года."
contributors: "Основные соавторы"
@ -997,6 +1004,7 @@ _widgets:
slideshow: "Показ слайдов"
button: "Кнопка"
onlineUsers: "Пользователи сейчас с сети"
jobQueue: "Очередь заданий"
_cw:
hide: "Спрятать"
show: "Показать еще"

View File

@ -1,6 +1,7 @@
---
_lang_: "Українська"
introMisskey: "Ласкаво просимо! Misskey - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо нотаток інших 👍\nДавайте досліджувати новий світ 🚀"
headlineMisskey: "Мережа об'єднана записами"
introMisskey: "Ласкаво просимо! Misskey - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"записи\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо записів інших 👍\nДавайте досліджувати новий світ 🚀"
monthAndDay: "{month}/{day}"
search: "Пошук"
notifications: "Сповіщення"
@ -33,6 +34,9 @@ addUser: "Додати користувача"
favorite: "Обране"
favorites: "Обране"
unfavorite: "Видалити з обраного"
favorited: "Додано до вподобаних."
alreadyFavorited: "Вже додано до вподобаних."
cantFavorite: "Неможливо вподобати."
pin: "Закріпити"
unpin: "Відкріпити"
copyContent: "Скопіювати контент"
@ -60,12 +64,12 @@ files: "Файли"
download: "Завантажити"
driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Нотатки із цим файлом також буде видалено."
unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?"
exportRequested: "Ви запросили експорт. Це може зайняти деякий час. Після завершення експорту отриманий файл буде додано на диск."
importRequested: "Ви запросили імпорт. Це може зайняти деякий час."
exportRequested: "Експортування розпочато. Це може зайняти деякий час. Після завершення експорту отриманий файл буде додано на диск."
importRequested: "Імпортування розпочато. Це може зайняти деякий час."
lists: "Списки"
noLists: "Немає списків"
note: "Нотатки"
notes: "Нотатки"
note: "Запис"
notes: "Записи"
following: "Підписки"
followers: "Підписники"
followsYou: "Підписаний(-а) на вас"
@ -77,10 +81,10 @@ retry: "Спробувати знову"
pageLoadError: "Помилка при завантаженні сторінки"
pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера. Очистіть кеш або почекайте трохи й спробуйте ще раз."
enterListName: "Введіть назву списку"
privacy: "Приватність"
privacy: "Конфіденційність"
makeFollowManuallyApprove: "Підтверджувати підписників уручну"
defaultNoteVisibility: "Видимість за замовчуванням"
follow: "Підписка"
follow: "Підписатись"
followRequest: "Запит на підписку"
followRequests: "Запити на підписку"
unfollow: "Відписатись"
@ -88,14 +92,18 @@ followRequestPending: "Очікуючі запити на підписку"
enterEmoji: "Введіть емодзі"
renote: "Поширити"
unrenote: "Відміна поширення"
renoted: "Поширити запис."
cantRenote: "Неможливо поширити."
cantReRenote: "Поширення не можливо поширити."
quote: "Цитата"
pinnedNote: "Закріплена нотатка"
pinnedNote: "Закріплений запис"
you: "Ви"
clickToShow: "Натисніть для перегляду"
sensitive: "NSFW"
add: "Додати"
reaction: "Реакції"
reactionSettingDescription: "Виберіть свої улюблені реакції, які хочете закріпити в селекторі реакцій."
reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати."
rememberNoteVisibility: "Пам’ятати параметри видимісті"
attachCancel: "Видалити вкладення"
markAsSensitive: "Позначити як NSFW"
@ -125,7 +133,9 @@ settingGuide: "Рекомендована конфігурація"
cacheRemoteFiles: "Кешувати дані з інших інстансів"
cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються безпосередньо з віддаленого інстансу. Це зменшує використання сховища, але збільшує трафік, оскільки не генеруются ескізи."
flagAsBot: "Акаунт бота"
flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом. Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну інтеракцію між ботами а також відповідного підлаштування Misskey."
flagAsCat: "Акаунт кота"
flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком."
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані"
addAcount: "Додати акаунт"
loginFailed: "Не вдалося увійти"
@ -140,15 +150,15 @@ followConfirm: "Підписатися на {name}?"
proxyAccount: "Проксі-акаунт"
host: "Хост"
selectUser: "Виберіть користувача"
recipient: "Кому"
annotation: "Коментар"
recipient: "Отримувач"
annotation: "Коментарі"
federation: "Федіверс"
instances: "Інстанс"
registeredAt: "Приєднався(лась)"
latestRequestSentAt: "Останній запит надіслано"
latestRequestReceivedAt: "Останній запит прийнято"
latestStatus: "Останній статус"
storageUsage: "Використання простіру"
storageUsage: "Використання простору"
charts: "Графіки"
perHour: "Щогодинно"
perDay: "Щоденно"
@ -173,19 +183,19 @@ clearCachedFiles: "Очистити кеш"
clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?"
blockedInstances: "Заблоковані інстанси"
blockedInstancesDescription: "Вкажіть інстанси, які потрібно заблокувати. Перелічені інстанси більше не зможуть спілкуватися з цим інстансом."
muteAndBlock: "Ігнор і блокування"
mutedUsers: "Ігноровані користувачі"
muteAndBlock: "Заглушення і блокування"
mutedUsers: "Заглушені користувачі"
blockedUsers: "Заблоковані користувачі"
noUsers: "Немає користувачів"
editProfile: "Редагувати профіль"
noteDeleteConfirm: "Ви дійсно хочете видалити цю нотатку?"
pinLimitExceeded: "Більше нотаток не можна закріпити"
intro: "Встановлення Misskey завершено! Будь ласка, створіть акаунт адміністратора."
editProfile: "Редагувати обліковий запис"
noteDeleteConfirm: "Ви дійсно хочете видалити цей запис?"
pinLimitExceeded: "Більше записів не можна закріпити"
intro: "Встановлення Misskey завершено! Будь ласка, створіть обліковий запис адміністратора."
done: "Готово"
processing: "Обробка"
preview: ередогляд"
preview: опередній перегляд"
default: "За умовчанням"
noCustomEmojis: "Немає кастомних емоджі"
noCustomEmojis: "Немає нетипових емоджі"
noJobs: "Немає завдань"
federating: "Федерується"
blocked: "Заблоковано"
@ -203,27 +213,27 @@ retypedNotMatch: "Введені дані не збігаються."
currentPassword: "Поточний пароль"
newPassword: "Новий пароль"
newPasswordRetype: "Новий пароль (повторно)"
attachFile: "Вкласти файл"
attachFile: "Прикріпити файл"
more: "Бiльше!"
featured: "Популярні"
usernameOrUserId: "Ім'я або ID користувача"
noSuchUser: "Користувача не знайдено"
lookup: "Пошук"
announcements: "Оголошення"
imageUrl: "URL зображення"
imageUrl: "Посилання на зображення"
remove: "Видалити"
removed: "Видалено"
removeAreYouSure: "Ви впевнені, що хочете видалити \"{x}\"?"
deleteAreYouSure: "Ви впевнені, що хочете видалити \"{x}\"?"
resetAreYouSure: "Дійсно ресет?"
resetAreYouSure: "Справді скинути?"
saved: "Збережено"
messaging: "Чати"
upload: "Завантажити"
fromDrive: "З диска"
fromUrl: "З URL"
uploadFromUrl: "Завантажити з URL"
uploadFromUrlDescription: "URL-адреса файлу для завантаження"
uploadFromUrlRequested: "Обрано завантаження"
fromUrl: "З посилання"
uploadFromUrl: "Завантажити з посилання"
uploadFromUrlDescription: "Посилання на файл для завантаження"
uploadFromUrlRequested: "Завантаження розпочалось"
uploadFromUrlMayTakeTime: "Завантаження може зайняти деякий час."
explore: "Огляд"
games: "Ігри Misskey"
@ -283,6 +293,7 @@ watch: "Стежити"
unwatch: "Не стежити"
accept: "Прийняти"
reject: "Відхилити"
normal: "Нормальний"
instanceName: "Назва інстансу"
instanceDescription: "Описання інстансу"
maintainerName: "Ім'я адміністратора"
@ -305,6 +316,7 @@ registration: "Реєстрація"
enableRegistration: "Дозволити реєстрацію"
invite: "Запросити"
proxyRemoteFiles: "Проксувати файли з інших інстансів"
proxyRemoteFilesDescription: "При увімкненні віддалені файли, які не зберігаються локально або були видалені через недостатнії обсяг пам'яті, будуть локально проксовані включаючи обкладинки. Це налаштування не впливає на пам'ять серверу. "
driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача"
driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача"
inMb: "В мегабайтах"
@ -312,8 +324,10 @@ iconUrl: "URL аватара"
bannerUrl: "URL банера"
basicInfo: "Основна інформація"
pinnedUsers: "Закріплені користувачі"
pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці \"Знайти\", ім'я в стовпчик."
pinnedPages: "Закріплені сторінки"
pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній сторінці цього інстанса, розділені новими рядками."
pinnedClipId: "Ідентифікатор закріпленої замітки."
pinnedNotes: "Закріплена нотатка"
hcaptcha: "hCaptcha"
enableHcaptcha: "Увімкнути hCaptcha"
@ -329,10 +343,12 @@ name: "Ім'я"
antennaSource: "Джерело антени"
antennaKeywords: "Ключові слова антени"
antennaExcludeKeywords: "Винятки"
antennaKeywordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
notifyAntenna: "Сповіщати про нові нотатки"
withFileAntenna: "Тільки нотатки з вкладеними файлами"
serviceworker: "ServiceWorker"
enableServiceworker: "Ввімкнути ServiceWorker"
antennaUsersDescription: "Список імя користувачів в стопчик"
caseSensitive: "З урахуванням регістру"
withReplies: "Включаючи відповіді"
connectedTo: "Наступні акаунти під'єднані"
@ -456,13 +472,21 @@ objectStorage: "Object Storage"
useObjectStorage: "Використовувати object storage"
objectStorageBaseUrl: "Base URL"
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Будь ласка вкажіть назву відра в налаштованому сервісі."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Файли будуть зберігатись у розташуванні з цим префіксом."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Залиште пустим при використанні AWS S3. Інакше введіть кінцевий пункт як '<host>' або '<host>:<port>' слідуючи інструкціям сервісу, який використовується."
objectStorageRegion: "Region"
objectStorageRegionDesc: "Введіть регіон у формі 'xx-east-1'. Залиште пустим, якщо ваш сервіс не різниться відповідно до регіонів, або введіть 'us-east-1'."
objectStorageUseSSL: "Використовувати SSL"
objectStorageUseSSLDesc: "Вимкніть коли не використовується HTTPS для з'єднання API"
objectStorageUseProxy: "Використовувати Proxy"
objectStorageUseProxyDesc: "Вимкніть коли проксі не використовується для з'єднання ObjectStorage"
objectStorageSetPublicRead: "Встановіть 'публічне читання' при завантаженні"
serverLogs: "Журнал сервера"
deleteAll: "Видалити все"
showFixedPostForm: "Показати форму запису над стрічкою новин."
newNoteRecived: "Є нові нотатки"
sounds: "Звуки"
listen: "Слухати"
@ -495,6 +519,8 @@ deleteAllFiles: "Видалити всі файли"
deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?"
removeAllFollowing: "Скасувати всі підписки"
removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка, робіть це, якщо інстанс більше не існує."
userSuspended: "Обліковий запис заблокований."
userSilenced: "Обліковий запис приглушений."
sidebar: "Бокова панель"
divider: "Розділювач"
addItem: "Додати елемент"
@ -505,6 +531,7 @@ inboxUrl: "Inbox URL"
addedRelays: "Додані ретранслятори"
serviceworkerInfo: "Повинен бути ввімкнений для push-сповіщень."
deletedNote: "Видалена нотатка"
invisibleNote: "Приховані записи"
enableInfiniteScroll: "Увімкнути нескінченну прокрутку"
visibility: "Видимість"
poll: "Опитування"
@ -515,8 +542,10 @@ expandTweet: "Розгорнути твіт"
themeEditor: "Редактор тем"
description: "Опис"
author: "Автор"
leaveConfirm: "Зміни не збережені. Ви дійсно хочете скасувати зміни?"
manage: "Управління"
plugins: "Плагіни"
pluginInstallWarn: "Будь ласка не встановлюйте плагінів яким ви не довіряєте."
deck: "Дек"
undeck: "Залишити Дек"
useBlurEffectForModal: "Ефект розмиття під модальними діалогами"
@ -531,18 +560,26 @@ permission: "Права"
enableAll: "Увімкнути все"
disableAll: "Вимкнути все"
tokenRequested: "Надати доступ до акаунту"
pluginTokenRequestedDescription: "Цей плагін зможе використовувати дозволи які тут вказані."
notificationType: "Тип сповіщення"
edit: "Редагувати"
useStarForReactionFallback: "Використовувати ★ як запасний варіант, якщо емодзі реакції невідомий"
emailConfig: "Налаштування email сервера"
enableEmail: "Увімкнути функцію доставки пошти"
emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю."
email: "E-mail"
emailAddress: "E-mail адреса"
smtpConfig: "Налаштування сервера SMTP"
smtpHost: "Хост"
smtpPort: "Порт"
smtpUser: "Ім'я користувача"
smtpPass: "Пароль"
emptyToDisableSmtpAuth: "Залиште назву користувача і пароль пустими для вимкнення підтвердження SMTP"
smtpSecure: "Використовувати безумовне шифрування SSL/TLS для з'єднань SMTP"
smtpSecureInfo: "Вимкніть при використанні STARTTLS "
testEmail: "Тестовий email"
wordMute: "Блокування слів"
userSaysSomething: "{name} щось сказав(ла)"
makeActive: "Активувати"
display: "Відображення"
copy: "Скопіювати"
@ -556,16 +593,24 @@ create: "Створити"
notificationSetting: "Параметри сповіщень"
notificationSettingDesc: "Виберіть типи сповіщень для відображення"
useGlobalSetting: "Застосувати глобальнi параметри"
useGlobalSettingDesc: "Якщо увімкнено, то будуть використовуватись налаштування повідомлень облікового запису, інакше можливо налаштувати індивідуально."
other: "Інше"
regenerateLoginToken: "Оновити Login Token"
regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть з системи."
setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом."
fileIdOrUrl: "Ідентифікатор файлу або посилання"
chatOpenBehavior: "Поводження вікна переписки під час відкриття"
behavior: "Поведінка"
sample: "Приклад"
abuseReports: "Скарги"
reportAbuse: "Поскаржитись"
reportAbuseOf: "Поскаржитись на {name}"
fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується запису, вкажіть посилання на нього."
abuseReported: "Дякуємо, вашу скаргу було відправлено. "
send: "Відправити"
abuseMarkAsResolved: "Позначити скаргу як вирішену"
openInNewTab: "Відкрити в новій вкладці"
openInSideView: "Відкрити збоку"
defaultNavigationBehaviour: "Поведінка навігації за замовчуванням"
editTheseSettingsMayBreakAccount: "Зміна цих параметрів може призвести до пошкодження вашого акаунта."
instanceTicker: "Мітка з назвою інстанса в нотатках"
@ -577,6 +622,7 @@ desktop: "Десктоп"
clip: "Добірка"
createNew: "Створити новий"
optional: "Необов'язково"
createNewClip: "Створити нотатку"
public: "Публічний"
i18nInfo: "Misskey перекладається на різні мови волонтерами. Ви можете допомогти: {link}"
manageAccessTokens: "Керування токенами доступу"
@ -598,9 +644,11 @@ driveFilesCount: "Кількість файлів на диску"
driveUsage: "Використання місця на диску"
noCrawle: "Заборонити індексацію"
noCrawleDescription: "Просити пошукові системи не індексувати ваш профіль, нотатки, сторінки тощо."
lockedAccountInfo: "Якщо видимість вашого запису не встановлена як \"Тільки підписники\", то кожен зможе побачити ваш запис, навіть якщо ви вимагаєте підтвердження підписок вручну."
alwaysMarkSensitive: "Позначати NSFW за замовчуванням"
loadRawImages: "Відображати вкладені зображення повністю замість ескізів"
disableShowingAnimatedImages: "Не програвати анімовані зображення"
verificationEmailSent: "Електронний лист з підтвердженням відісланий. Будь ласка перейдіть по посиланню в листі для підтвердження."
notSet: "Не налаштовано"
emailVerified: "Електронну пошту підтверджено."
noteFavoritesCount: "Кількість улюблених нотаток"
@ -612,7 +660,40 @@ useSystemFont: "Використовувати стандартний шрифт
clips: "Добірка"
experimentalFeatures: "Експериментальні функції"
developer: "Розробник"
makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\""
makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі \"Огляд\"."
showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин"
duplicate: "Дублікат"
left: "Лівий"
center: "Центр"
wide: "Широкий"
narrow: "Вузький"
reloadToApplySetting: "Налаштування ввійде в дію при перезавантаженні. Перезавантажити?"
showTitlebar: "Показати титульний рядок"
clearCache: "Очистити кеш"
onlineUsersCount: "{n} користувачів онлайн"
nUsers: "{n} Користувачів"
nNotes: "{n} Записів"
sendErrorReports: "Надіслати звіт про помилки"
sendErrorReportsDescription: "При увімкненні детальна інформація про помилки буде надана Misskey у разі виникнення проблем, що дасть можливість покращити Misskey."
myTheme: "Моя тема"
backgroundColor: "Фон"
accentColor: "Акцент"
textColor: "Текст"
saveAs: "Зберегти як…"
advanced: "Розширені"
value: "Значення"
updatedAt: "Останнє оновлення"
saveConfirm: "Зберегти зміни?"
deleteConfirm: "Ви дійсно бажаєте це видалити?"
invalidValue: "Некоректне значення."
registry: "Реєстр"
closeAccount: "Закрити обліковий запис"
_registry:
key: "Ключ"
keys: "Ключі"
domain: "Домен"
createKey: "Створити ключ"
_aboutMisskey:
about: "Misskey - це програмне забезпечення з відкритим кодом, яке розробляє syuilo з 2014 року."
contributors: "Головні помічники"
@ -651,10 +732,14 @@ _mfm:
inlineMath: "Формула (у рядку)"
inlineMathDescription: "Відображення математичних формул (KaTeX) у рядку"
blockMath: "Формули (блок)"
blockMathDescription: "Відображати багаторядкові формули (KaTeX) блоками"
quote: "Цитата"
quoteDescription: "Відображає зміст як цитату."
emoji: "Кастомні емоджі"
emojiDescription: "Щоб показати нетиповий емоджі, потрібно ввести його назву в двокрапках."
search: "Пошук"
searchDescription: "Відображає вікно пошуку з попередньо введеним текстом"
flip: "Перевернути"
flipDescription: "Віддзеркалює вміст по горизонталі або вертикалі"
jelly: "Анімація (желе)"
jellyDescription: "Створює желеподібну анімацію"
@ -666,7 +751,16 @@ _mfm:
shake: "Анімація (Shake)"
twitch: "Анімація (Twitch)"
spin: "Анімація (Spin)"
x2: "Великий"
x2Description: "Показує контент збільшеним."
x3: "Дуже великий"
x3Description: "Показує контент ще більшим."
x4: "Надзвичайно великий"
x4Description: "Показує контент надзвичайно великим."
blur: "Розмиття"
blurDescription: "Цей ефект зробить контент розмитим. Контент можна зробити чітким, якщо навести на нього вказівник миші."
font: "Шрифт"
fontDescription: "Встановлює шрифт для контенту."
_reversi:
reversi: "Реверсі"
gameSettings: "Налаштування гри"
@ -695,6 +789,9 @@ _reversi:
myGames: "Мої ігри"
allGames: "Усі ігри"
ended: "Завершено"
playing: "В даний момент у процесі гри"
isLlotheo: "Гравець з найменшою кількістю фігур виграє (Llotheo)"
canPutEverywhere: "Фігури можна ставити в будь якії позиції"
_instanceTicker:
none: "Не відображати"
remote: "Відображати для віддалених користувачів"
@ -709,6 +806,7 @@ _channel:
setBanner: "Встановити банер"
removeBanner: "Видалити банер"
featured: "Тренди"
following: "Підписки"
usersCount: "{n} учасників"
notesCount: "{n} дописів"
_sidebar:
@ -716,12 +814,32 @@ _sidebar:
icon: "Аватар"
hide: "Сховати"
_wordMute:
muteWords: "Заглушені слова"
muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж слешів \"/\"."
softDescription: "Приховати записи які відповідають критеріям зі стрічки подій."
hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій. Також приховані записи не будуть додані до стрічки подій навіть якщо критерії буде змінено."
soft: "М'яко"
hard: "Жорстко"
mutedNotes: "Заблоковані нотатки"
_theme:
explore: "Оглянути теми"
install: "Встановити тему"
manage: "Керування темами"
code: "Код теми"
installed: "Тему {name} встановлено"
installedThemes: "Встановлені теми"
builtinThemes: "Вбудоваі теми"
alreadyInstalled: "Тему вже встановлено"
invalid: "Неправильний формат теми"
make: "Створити тему"
base: "Основа"
defaultValue: "Значення за замовчуванням"
func: "Функції"
lighten: "Яскравість"
inputConstantName: "Введіть назву константи"
importInfo: "Вставляючи сюди код теми, ви можете добавити її до редактору тем"
deleteConstantConfirm: "Ви дійсно бажаєте видалити константу \"{const}\"?"
keys:
accent: "Акцент"
bg: "Фон"
@ -741,13 +859,41 @@ _theme:
mention: "Згадка"
mentionMe: "Згадки (мене)"
renote: "Поширити"
modalBg: "Модальний фон"
divider: "Розділювач"
scrollbarHandle: "Ручка смуги прокрутки"
scrollbarHandleHover: "Ручка смуги прокрутки (при наведенні)"
dateLabelFg: "Текст позначок дати"
infoBg: "Фон інформації"
infoFg: "Текст інформації"
infoWarnBg: "Фон попередження"
infoWarnFg: "Текст попередження"
cwBg: "Фон чутливого змісту"
cwFg: "Текст чутливого змісту"
cwHoverBg: "Фон чутливого змісту (при наведенні)"
toastBg: "Фон повідомлення"
toastFg: "Текст повідомлення"
buttonBg: "Фон кнопки"
buttonHoverBg: "Фон кнопки (при наведенні)"
inputBorder: "Край поля вводу"
listItemHoverBg: "Фон елементу в списку (при наведенні)"
driveFolderBg: "Фон папки на диску"
wallpaperOverlay: "Накладання шпалер"
badge: "Значок"
messageBg: "Фон переписки"
accentDarken: "Акцент (Затемлений)"
accentLighten: "Акцент (Освітлений)"
fgHighlighted: "Виділений текст"
_sfx:
note: "Нотатки"
noteMy: "Мої нотатки"
notification: "Сповіщення"
chat: "Чати"
chatBg: "Чати (фон)"
antenna: "Прийом антени"
channel: "Повідомлення каналу"
reversiPutBlack: "Реверсі: хід Чорного"
reversiPutWhite: "Реверсі: хід Білого"
_ago:
unknown: "Невідомо"
future: "Майбутнє"
@ -767,8 +913,26 @@ _time:
_tutorial:
title: "Як користуватись Misskey"
step1_1: "Ласкаво просимо!"
step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів на яких ви підписані."
step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки і не підписані на інших."
step2_1: "Перш ніж зробити запис або підписатись на когось, спочатку заповніть свій обліковий запис."
step2_2: "Надання деякої інформації про себе дозволить іншим користувачам підписатись на вас."
step3_1: "Ви успішно налаштували свій обліковий запис?"
step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення олівця на екрані."
step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку у верхньому правому куті форми."
step3_4: "Не знаєте що написати? Спробуйте \"налаштовую свій msky\"!"
step4_1: "Ви розмістили свій перший запис?"
step4_2: "Ура! Ваш перший запис відображається на вашій стрічці подій."
step5_1: "Настав час оживити вашу стрічку подій підписавшись на інших користувачів."
step5_2: "{featured} показує популярні записи , а {explore} популярних користувачів з цього інстансу. Спробуйте підписатись на користувача, який вам сподобався!"
step5_3: "Щоб підписатись на інших користувачів, нажміть на їхнє зображення, а потім на кнопку \"підписатись\"."
step5_4: "Якщо користувач має замок при імені, то йому потрібно буде вручну підтвердити вашу заявку на підписку."
step6_1: "Тепер ви повинні бачити записи інших користувачів на вашій стрічці подій."
step6_2: "Також ви можете швидко відповісти, або \"відреагувати\" на записи інших користувачів."
step6_3: "Щоб \"відреагувати\", нажміть на знак плюс \"+\" на записі і виберіть емоджі яким ви хочете \"відреагувати\"."
step7_1: "Вітаю! Ви пройшли ознайомлення з Misskey."
step7_2: "Якщо ви хочете більше дізнатись про Misskey, зайдіть в розділ {help}."
step7_3: "Насолоджуйтесь Misskey! 🚀"
_2fa:
registerKey: "Зареєструвати новий ключ безпеки"
_permissions:
@ -828,6 +992,7 @@ _widgets:
postForm: "Створення нотатки"
slideshow: "Слайд-шоу"
button: "Кнопка"
jobQueue: "Черга завдань"
_cw:
hide: "Сховати"
show: "Показати більше"
@ -902,8 +1067,12 @@ _charts:
_instanceCharts:
requests: "Запити"
usersTotal: "Сумарна кількість користувачів"
notes: "Різниця кількості зроблених записів"
notesTotal: "Сумарна кількість нотаток"
ff: "Різниця кількості підписників"
ffTotal: "Кількість підписників"
cacheSizeTotal: "Сумарний розмір кешу"
files: "Різниця в кількості файлів"
filesTotal: "Сумарна кількість файлів"
_timelines:
home: "Домівка"
@ -918,6 +1087,7 @@ _rooms:
exit: "Назад"
remove: "Видалити"
clear: "Видалити все"
clearConfirm: "Ви дійсно хочете позбутись усіх речей у вашій кімнаті?"
leaveConfirm: "Є незбережені зміни. Ви дійсно хочете вийти?"
chooseImage: "Виберіть зображення"
roomType: "Тип кімнати"
@ -966,6 +1136,7 @@ _rooms:
sofa: "Диван"
spiral: "Гвинтові сходи"
bin: "Смітник"
cup-noodle: "Локшина в чашці"
holo-display: "Голографічний дисплей"
energy-drink: "Енергетичний напій"
doll-ai: "Лялька Аі-тян"
@ -991,6 +1162,7 @@ _pages:
featured: "Популярні"
inspector: "Інспектор"
contents: "Вміст"
content: "Блок сторінки"
variables: "Змінні"
title: "Заголовок"
url: "URL сторінки"
@ -1005,6 +1177,7 @@ _pages:
chooseBlock: "Додати блок"
selectType: "Виберіть тип"
enterVariableName: "Введіть назву для змінної"
variableNameIsAlreadyUsed: "Ця назва вже використовується іншою змінною"
contentBlocks: "Контент"
inputBlocks: "Ввід"
specialBlocks: "Особливе"
@ -1020,6 +1193,7 @@ _pages:
post: "Створення нотатки"
_post:
text: "Вміст"
canvasId: "Ідентифікатор полотна"
textInput: "Введення тексту"
_textInput:
name: "Ім'я змінної"
@ -1035,7 +1209,9 @@ _pages:
name: "Ім'я змінної"
text: "Назва"
default: "Значення за замовчуванням"
canvas: "Полотно"
_canvas:
id: "Ідентифікатор полотна"
width: "Ширина"
height: "Висота"
note: "Вбудована нотатка"
@ -1055,7 +1231,10 @@ _pages:
inc: "Збільшити на"
_button:
text: "Напис"
colored: "Кольоровий"
action: "Дія кнопки"
_action:
dialog: "Показати повідомлення"
_dialog:
content: "Вміст"
resetRandom: "Скидання генератора випадковості"
@ -1090,11 +1269,15 @@ _pages:
text: "Текст"
multiLineText: "Текст (багаторядковий)"
textList: "Текстовий список"
_textList:
info: "Використовувати новий рядок як роздільник для вводу"
strLen: "Довжина тексту"
_strLen:
arg1: "Текст"
strPick: "Вибрати символ"
_strPick:
arg1: "Текст"
arg2: "Розташування символу"
strReplace: "Заміна тексту"
_strReplace:
arg1: "Текст"
@ -1227,17 +1410,25 @@ _pages:
aiScriptVar: "Змінна AiScript"
fn: "Функції"
_fn:
slots: "Паз"
slots-info: "Використовувати нову лінію як роздільник пазів"
arg1: "Вивід"
for: "Повторення"
_for:
arg1: "Кількість повторень"
arg2: "Дія"
typeError: "Паз {slot} приймає \"{expect}\" тип, але надана змінна має тип \"{actual}\"!"
thereIsEmptySlot: "Паз {slot} пустий!"
types:
string: "Текст"
number: "Число"
boolean: "Прапорець"
array: "Списки"
stringArray: "Текстовий список"
emptySlot: "Пустий паз"
enviromentVariables: "Змінні середовища"
pageVariables: "Елемент сторінки"
argVariables: "Стрічка вводу"
_relayStatus:
requesting: "Очікує затвердження"
accepted: "Затверджено"
@ -1271,6 +1462,8 @@ _notification:
_deck:
alwaysShowMainColumn: "Завжди показувати головну колонку"
columnAlign: "Вирівняти стовпці"
columnMargin: "Відступ між стовпцями"
columnHeaderHeight: "Висота заголовку колони"
addColumn: "Додати стовпець"
swapLeft: "Пересунути ліворуч"
swapRight: "Пересунути праворуч"
@ -1278,7 +1471,9 @@ _deck:
swapDown: "Пересунути вниз"
stackLeft: "У стовпчик вліво"
popRight: "Витягнути вправо"
profile: "Обліковий запис"
_columns:
main: "Головна"
widgets: "Віджети"
notifications: "Сповіщення"
tl: "Стрічка"

View File

@ -639,10 +639,10 @@ followingCount: "正在关注数量"
followersCount: "关注者数量"
sentReactionsCount: "发送反应数"
receivedReactionsCount: "收到反应数"
pollVotesCount: "发起投票数"
pollVotedCount: "投票数"
yes: "确定"
no: "取消"
pollVotesCount: "问卷调查的投票数"
pollVotedCount: "问卷调查的被投票数"
yes: ""
no: ""
driveFilesCount: "磁盘文件数"
driveUsage: "磁盘空间用量"
noCrawle: "拒绝搜索器的索引"
@ -677,6 +677,27 @@ clearCache: "清除缓存"
onlineUsersCount: "{n}人在线"
nUsers: "{n}用户"
nNotes: "{n}帖子"
sendErrorReports: "发送错误报告"
sendErrorReportsDescription: "启用后如果出现问题可以与Misskey共享详细的错误信息从而帮助提高软件的质量。"
myTheme: "我的主题"
backgroundColor: "背景"
accentColor: "强调色"
textColor: "文本"
saveAs: "另存为"
advanced: "高级"
value: "值"
updatedAt: "更新时间"
saveConfirm: "确定保存?"
deleteConfirm: "确定删除?"
invalidValue: "无效值。"
registry: "注册表"
closeAccount: "关闭账户"
_registry:
scope: "范围"
key: "主要"
keys: "主要"
domain: "域"
createKey: "创建键"
_aboutMisskey:
about: "Misskey是由syuilo于2014年开发的开源软件。"
contributors: "主要贡献者"
@ -1009,6 +1030,7 @@ _widgets:
slideshow: "幻灯片展示"
button: "按钮"
onlineUsers: "在线用户"
jobQueue: "作业队列"
serverMetric: "服务器指标"
_cw:
hide: "隐藏"
@ -1484,7 +1506,7 @@ _notification:
renote: "转发"
quote: "引用"
reaction: "回应"
pollVote: "问卷调查投票"
pollVote: "问卷调查投票"
receiveFollowRequest: "收到关注请求"
followRequestAccepted: "关注请求已接受"
groupInvited: "加入群组邀请"
@ -1501,6 +1523,7 @@ _deck:
swapDown: "向下移动"
stackLeft: "向左折叠"
popRight: "向右弹出"
profile: "个人资料"
_columns:
main: "主列"
widgets: "小工具"

View File

@ -1,5 +1,6 @@
---
_lang_: "繁體中文"
headlineMisskey: "箋文連繫網絡"
introMisskey: "歡迎! Misskey是一個開源且去中心化的社群網絡。\n通過「箋文」分享周邊新鮮事並告訴其他人您的想法📡\n透過「情感」功能對大家的箋文表達情感👍\n一起來探索這個新的世界吧🚀"
monthAndDay: "{month}月 {day}日"
search: "搜尋"
@ -309,7 +310,7 @@ integration: "整合"
connectSerice: "連線"
disconnectSerice: "中斷連線"
enableLocalTimeline: "開啟本地時間軸"
enableGlobalTimeline: "開啟全球時間軸"
enableGlobalTimeline: "啟用公開時間軸"
disablingTimelinesInfo: "即使您禁用了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"
registration: "註冊"
enableRegistration: "開啟新用戶註冊"
@ -579,7 +580,6 @@ channel: "頻道"
create: "新增"
notificationSetting: "通知設定"
notificationSettingDesc: "選擇顯示通知的類型"
useGlobalSetting: "使用全域設定"
other: "其他"
regenerateLoginTokenDescription: "再生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦再生,所有裝置將會被登出。"
fileIdOrUrl: "文檔ID或者URL"
@ -607,6 +607,7 @@ createNew: "新建"
optional: "可選"
public: "公開"
i18nInfo: "Misskey已經被志願者們翻譯成各種語言版本如果想要幫忙的話可以進入{link}幫助翻譯。"
manageAccessTokens: "管理存取權杖"
accountInfo: "帳戶資訊"
notesCount: "箋文數量"
repliesCount: "回覆數量\n"
@ -648,6 +649,16 @@ showTitlebar: "顯示標題列"
clearCache: "清除快取資料"
onlineUsersCount: "{n}人正在線上"
nUsers: "{n}用戶"
nNotes: "{n}箋文"
backgroundColor: "背景"
textColor: "文本"
advanced: "進階"
value: "數值 "
updatedAt: "最後更新"
_registry:
scope: "範圍"
key: "主要"
keys: "主要"
_aboutMisskey:
about: "Misskey是由syuilo於2014年開發的開源軟件。"
contributors: "主要貢獻者"
@ -663,12 +674,17 @@ _nsfw:
force: "隱藏所有內容"
_mfm:
cheatSheet: "MFM代碼小抄"
intro: "MFM是Misskey專用的標記語言可以在Misskey中的各個位置使用。 您可以這裏看到MFM可用語法列表。"
mention: "提及"
hashtag: "#tag"
url: "URL"
link: "鏈接"
bold: "粗體"
small: "縮小"
center: "置中"
inlineMath: "數學公式(內嵌)"
inlineMathDescription: "顯示內嵌的KaTex數學公式。"
blockMath: "數學公式(方塊)"
quote: "引用"
emoji: "自訂表情符號"
search: "搜尋"
@ -681,6 +697,11 @@ _mfm:
twitch: "動畫(顫抖)"
spin: "動畫(旋轉)"
spinDescription: "顯示旋轉的動畫效果。"
x2: "大"
x3: "較大"
x3Description: "放大顯示內容。"
x4: "最大"
x4Description: "將顯示內容放至最大。"
blur: "模糊"
font: "字型"
fontDescription: "可設置顯示內容所使用的字型"
@ -703,6 +724,7 @@ _reversi:
turnOf: "{name}的回合"
pastTurnOf: "{name}的回合"
surrender: "認輸"
surrendered: "對手認輸"
drawn: "平局"
won: "{name}獲勝"
black: "黑"
@ -756,7 +778,7 @@ _theme:
color: "顏色"
func: "函数"
funcKind: "功能類型"
argument: "數"
argument: "數"
alpha: "透明度"
darken: "暗度"
lighten: "亮度"
@ -901,10 +923,11 @@ _widgets:
photos: "照片"
digitalClock: "電子時鐘"
federation: "聯邦宇宙"
postForm: "篆文窗口"
postForm: "發佈窗口"
slideshow: "幻燈片"
button: "按鈕"
onlineUsers: "在線上的用戶"
jobQueue: "佇列"
_cw:
hide: "隱藏"
show: "瀏覽更多"
@ -952,6 +975,8 @@ _profile:
username: "使用者名稱"
description: "關於我"
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
metadata: "進階資訊"
metadataEdit: "編輯進階資訊"
metadataLabel: "標籤"
metadataContent: "内容"
_exportOrImport:
@ -988,7 +1013,7 @@ _timelines:
home: "首頁"
local: "本地"
social: "社群"
global: "全域"
global: "公開"
_rooms:
roomOf: "{user}的房間"
addFurniture: "擺放家具"
@ -1025,17 +1050,23 @@ _rooms:
book: "讀物"
book2: "讀物2"
piano: "鋼琴"
server: "伺服器"
moon: "月亮"
corkboard: "木栓板"
mousepad: "滑鼠墊"
monitor: "監視器"
keyboard: "鍵盤"
carpet-stripe: "條紋地毯"
mat: "地毯"
wall-clock: "壁鐘"
photoframe: "相框"
cube: "立方體"
tv: "電視"
pinguin: "企鵝蠟像"
poster-h: "海報(橫向)"
poster-v: "海報(直向)"
sofa: " 沙發"
spiral: "螺旋式樓梯"
bin: "垃圾箱"
cup-noodle: "杯面"
holo-display: "投影機"
@ -1091,6 +1122,7 @@ _pages:
if: "如果"
_if:
variable: "變數"
post: "發佈窗口"
_post:
text: "内容"
canvasId: "畫布ID"
@ -1150,6 +1182,7 @@ _pages:
default: "預設值"
script:
categories:
flow: "控制"
logical: "邏輯運算"
operation: "計算"
comparison: "對比"
@ -1165,6 +1198,7 @@ _pages:
textList: "文本列表"
_strLen:
arg1: "文本"
strPick: "提取字元"
_strPick:
arg1: "文本"
arg2: "字元位置"
@ -1286,7 +1320,6 @@ _pages:
aiScriptVar: "AiScript的變數"
fn: "函数"
_fn:
slots: "欄位"
arg1: "輸出"
_for:
arg1: "重複次數"
@ -1321,6 +1354,7 @@ _notification:
renote: "轉發箋文"
quote: "引用"
reaction: "情感"
pollVote: "統計已投票數"
receiveFollowRequest: "已收到追隨請求"
followRequestAccepted: "追隨請求已接受"
groupInvited: "加入社群邀請"
@ -1329,6 +1363,7 @@ _deck:
alwaysShowMainColumn: "總是顯示主欄"
columnAlign: "對齊欄位"
columnMargin: "列之間的邊距"
columnHeaderHeight: "欄位標題高度"
addColumn: "新增欄位"
swapLeft: "向左移動"
swapRight: "向右移動"
@ -1336,6 +1371,7 @@ _deck:
swapDown: "往下移動"
stackLeft: "向左折疊"
popRight: "向右彈出"
profile: "個人檔案"
_columns:
main: "主列"
widgets: "小工具"

View File

@ -0,0 +1,22 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class registry1610277136869 implements MigrationInterface {
name = 'registry1610277136869'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "registry_item" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "key" character varying(1024) NOT NULL, "scope" character varying(1024) array NOT NULL DEFAULT '{}'::varchar[], "domain" character varying(512), CONSTRAINT "PK_64b3f7e6008b4d89b826cd3af95" PRIMARY KEY ("id")); COMMENT ON COLUMN "registry_item"."createdAt" IS 'The created date of the RegistryItem.'; COMMENT ON COLUMN "registry_item"."updatedAt" IS 'The updated date of the RegistryItem.'; COMMENT ON COLUMN "registry_item"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "registry_item"."key" IS 'The key of the RegistryItem.'`);
await queryRunner.query(`CREATE INDEX "IDX_fb9d21ba0abb83223263df6bcb" ON "registry_item" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_22baca135bb8a3ea1a83d13df3" ON "registry_item" ("scope") `);
await queryRunner.query(`CREATE INDEX "IDX_0a72bdfcdb97c0eca11fe7ecad" ON "registry_item" ("domain") `);
await queryRunner.query(`ALTER TABLE "registry_item" ADD CONSTRAINT "FK_fb9d21ba0abb83223263df6bcb3" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "registry_item" DROP CONSTRAINT "FK_fb9d21ba0abb83223263df6bcb3"`);
await queryRunner.query(`DROP INDEX "IDX_0a72bdfcdb97c0eca11fe7ecad"`);
await queryRunner.query(`DROP INDEX "IDX_22baca135bb8a3ea1a83d13df3"`);
await queryRunner.query(`DROP INDEX "IDX_fb9d21ba0abb83223263df6bcb"`);
await queryRunner.query(`DROP TABLE "registry_item"`);
}
}

View File

@ -0,0 +1,16 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class registry21610277585759 implements MigrationInterface {
name = 'registry21610277585759'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "registry_item" ADD "value" jsonb NOT NULL DEFAULT '{}'`);
await queryRunner.query(`COMMENT ON COLUMN "registry_item"."value" IS 'The value of the RegistryItem.'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`COMMENT ON COLUMN "registry_item"."value" IS 'The value of the RegistryItem.'`);
await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "value"`);
}
}

View File

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class registry31610283021566 implements MigrationInterface {
name = 'registry31610283021566'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "value" DROP NOT NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "value" SET NOT NULL`);
}
}

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.66.0",
"version": "12.68.0",
"codename": "indigo",
"repository": {
"type": "git",
@ -98,6 +98,7 @@
"@types/sharp": "0.26.1",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/speakeasy": "2.0.5",
"@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.2",
"@types/tmp": "0.2.0",
"@types/uuid": "8.3.0",
@ -109,7 +110,7 @@
"@typescript-eslint/parser": "4.10.0",
"@vue/compiler-sfc": "3.0.5",
"abort-controller": "3.0.0",
"apexcharts": "3.22.3",
"apexcharts": "3.23.1",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@ -133,12 +134,12 @@
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1",
"eslint": "7.16.0",
"eslint-plugin-vue": "7.3.0",
"eslint": "7.17.0",
"eslint-plugin-vue": "7.4.1",
"eventemitter3": "4.0.7",
"feed": "4.2.1",
"fibers": "5.0.0",
"file-type": "16.0.1",
"file-type": "16.1.0",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"got": "11.8.1",
@ -147,7 +148,7 @@
"gulp-rename": "2.0.0",
"gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.5",
"gulp-terser": "2.0.0",
"gulp-terser": "2.0.1",
"gulp-tslint": "8.1.4",
"gulp-typescript": "6.0.0-alpha.1",
"hard-source-webpack-plugin": "0.13.1",
@ -166,7 +167,7 @@
"jsonld": "3.2.0",
"jsrsasign": "8.0.20",
"katex": "0.12.0",
"koa": "2.13.0",
"koa": "2.13.1",
"koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0",
"koa-json-body": "5.3.0",
@ -194,9 +195,9 @@
"parsimmon": "1.16.0",
"pg": "8.5.1",
"portscanner": "2.2.0",
"postcss": "8.2.1",
"postcss": "8.2.4",
"postcss-loader": "4.1.0",
"prismjs": "1.22.0",
"prismjs": "1.23.0",
"probe-image-size": "6.0.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
@ -219,26 +220,27 @@
"rimraf": "3.0.2",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.29.0",
"sass-loader": "10.1.0",
"sass": "1.32.4",
"sass-loader": "10.1.1",
"seedrandom": "3.0.5",
"sharp": "0.26.2",
"sharp": "0.27.0",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "4.31.1",
"systeminformation": "4.34.6",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.11",
"ts-node": "9.1.0",
"ts-loader": "8.0.14",
"ts-node": "9.1.1",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.29",
"typeorm": "0.2.30",
"typescript": "4.1.3",
"ulid": "2.3.0",
"url-loader": "4.1.1",
@ -254,10 +256,10 @@
"vue-style-loader": "4.1.2",
"vuedraggable": "4.0.1",
"web-push": "3.4.4",
"webpack": "5.10.1",
"webpack-cli": "4.3.0",
"webpack": "5.13.0",
"webpack-cli": "4.3.1",
"websocket": "1.0.33",
"ws": "7.4.1",
"ws": "7.4.2",
"xev": "2.0.1"
},
"devDependencies": {

View File

@ -7,7 +7,6 @@ import { waiting } from '@/os';
type Account = {
id: string;
token: string;
clientData: Record<string, any>;
};
const data = localStorage.getItem('account');

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -17,7 +17,7 @@
@dragenter="onDragenter"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop"
@contextmenu="onContextmenu"
@contextmenu.stop="onContextmenu"
>
<div class="contents" ref="contents">
<div class="folders" ref="foldersContainer" v-show="folders.length > 0">

View File

@ -52,10 +52,10 @@
<span class="localOnly" v-if="appearNote.localOnly"><Fa :icon="faBiohazard"/></span>
</div>
<div class="username"><MkAcct :user="appearNote.user"/></div>
<MkInstanceTicker class="ticker" :instance="appearNote.user.instance"/>
</div>
</header>
<div class="main">
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
<div class="body">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
@ -1005,6 +1005,7 @@ export default defineComponent({
flex-direction: column;
justify-content: center;
padding-left: 16px;
font-size: 0.95em;
> .top {
> .name {

View File

@ -1,17 +1,20 @@
<template>
<div class="voxdxuby">
<XNote v-if="note" v-model:note="note" :key="note.id" :detail="value.detailed"/>
<XNote v-if="note && !value.detailed" v-model:note="note" :key="note.id + ':normal'"/>
<XNoteDetailed v-if="note && value.detailed" v-model:note="note" :key="note.id + ':detail'"/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XNote
XNote,
XNoteDetailed,
},
props: {
value: {

View File

@ -9,7 +9,7 @@
<header>
<button v-if="!fixed" class="cancel _button" @click="cancel"><Fa :icon="faTimes"/></button>
<div>
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
<span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span>
<span class="local-only" v-if="localOnly"><Fa :icon="faBiohazard"/></span>
<button class="_button visibility" @click="setVisibility" ref="visibilityButton" v-tooltip="$ts.visibility" :disabled="channel != null">
<span v-if="visibility === 'public'"><Fa :icon="faGlobe"/></span>
@ -35,7 +35,7 @@
</div>
</div>
<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown">
<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste"></textarea>
<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" />
<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
<footer>
@ -142,6 +142,7 @@ export default defineComponent({
draghover: false,
quoteId: null,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
imeText: '',
postFormActions,
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
};
@ -190,10 +191,14 @@ export default defineComponent({
: this.$ts.note;
},
textLength(): number {
return length((this.text + this.imeText).trim());
},
canPost(): boolean {
return !this.posting &&
(1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) &&
(length(this.text.trim()) <= this.max) &&
(1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) &&
(this.textLength <= this.max) &&
(!this.poll || this.poll.choices.length >= 2);
},
@ -262,7 +267,7 @@ export default defineComponent({
}
// keep cw when reply
if (this.$store.keepCw && this.reply && this.reply.cw) {
if (this.$store.state.keepCw && this.reply && this.reply.cw) {
this.useCw = true;
this.cw = this.reply.cw;
}
@ -339,10 +344,6 @@ export default defineComponent({
}
},
trimmedLength(text: string) {
return length(text.trim());
},
addTag(tag: string) {
insertTextAtCursor(this.$refs.text, ` #${tag} `);
},
@ -429,11 +430,19 @@ export default defineComponent({
this.quoteId = null;
},
onKeydown(e) {
onKeydown(e: KeyboardEvent) {
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
if (e.which === 27) this.$emit('esc');
},
onCompositionUpdate(e: CompositionEvent) {
this.imeText = e.data;
},
onCompositionEnd(e: CompositionEvent) {
this.imeText = '';
},
async onPaste(e: ClipboardEvent) {
for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) {
if (item.kind == 'file') {

View File

@ -34,7 +34,7 @@ export default defineComponent({
font-size: 90%;
background: var(--infoBg);
color: var(--infoFg);
border-radius: 5px;
border-radius: var(--radius);
&.warn {
background: var(--infoWarnBg);

View File

@ -56,11 +56,10 @@ import { defaultStore, ColdDeviceStorage } from '@/store';
import { fetchInstance, instance } from '@/instance';
import { makeHotkey } from './scripts/hotkey';
import { search } from './scripts/search';
import { getThemes } from './theme-store';
console.info(`Misskey v${version}`);
window.clearTimeout((window as any).mkBootTimer);
if (_DEV_) {
console.warn('Development mode!!!');
@ -169,6 +168,8 @@ if ($i && $i.token) {
//#endregion
fetchInstance().then(() => {
localStorage.setItem('v', instance.version);
// Init service worker
//if (this.store.state.instance.meta.swPublickey) this.registerSw(this.store.state.instance.meta.swPublickey);
});
@ -211,7 +212,7 @@ app.mount('body');
watch(defaultStore.reactiveState.darkMode, (darkMode) => {
import('@/scripts/theme').then(({ builtinThemes }) => {
const themes = builtinThemes.concat(ColdDeviceStorage.get('themes'));
const themes = builtinThemes.concat(getThemes());
applyTheme(themes.find(x => x.id === (darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'))));
});
}, { immediate: localStorage.theme == null });
@ -347,14 +348,6 @@ if ($i) {
updateAccount({ hasUnreadAnnouncement: false });
});
main.on('clientSettingUpdated', x => {
updateAccount({
clientData: {
[x.key]: x.value
}
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {

View File

@ -4,7 +4,7 @@
<div id="debug"></div>
<section class="_formItem about">
<div class="_formPanel panel" :class="{ playing: easterEggEngine != null }" ref="about">
<img src="/assets/icons/512.png" alt="" class="icon" ref="icon" @load="iconLoaded" draggable="false"/>
<img src="/assets/about-icon.png" alt="" class="icon" ref="icon" @load="iconLoaded" draggable="false"/>
<div class="misskey">Misskey</div>
<div class="version">v{{ version }}</div>
<span class="emoji" v-for="emoji in easterEggEmojis" :key="emoji.id" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span>

View File

@ -108,6 +108,7 @@ import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } f
import { host } from '@/config';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { addTheme } from '@/theme-store';
export default defineComponent({
components: {
@ -212,8 +213,7 @@ export default defineComponent({
save() {
const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme);
const themes = ColdDeviceStorage.get('themes').concat(theme);
ColdDeviceStorage.set('themes', themes);
addTheme(theme);
os.dialog({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })

View File

@ -45,6 +45,13 @@ export default defineComponent({
MkButton
},
props: {
groupId: {
type: String,
required: true,
},
},
data() {
return {
INFO: computed(() => this.group ? {
@ -58,7 +65,7 @@ export default defineComponent({
},
watch: {
$route: 'fetch'
groupId: 'fetch',
},
created() {
@ -69,7 +76,7 @@ export default defineComponent({
fetch() {
Progress.start();
os.api('users/groups/show', {
groupId: this.$route.params.group
groupId: this.groupId
}).then(group => {
this.group = group;
os.api('users/show', {

View File

@ -9,7 +9,8 @@
</MkInput>
<MkSwitch v-model:value="value.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
<XNote v-if="note" v-model:note="note" :key="note.id + ':' + (value.detailed ? 'detailed' : 'normal')" :detail="value.detailed" style="margin-bottom: 16px;"/>
<XNote v-if="note && !value.detailed" v-model:note="note" :key="note.id + ':normal'" style="margin-bottom: 16px;"/>
<XNoteDetailed v-if="note && value.detailed" v-model:note="note" :key="note.id + ':detail'" style="margin-bottom: 16px;"/>
</section>
</XContainer>
</template>
@ -21,11 +22,12 @@ import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/ui/input.vue';
import MkSwitch from '@/components/ui/switch.vue';
import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XContainer, MkInput, MkSwitch, XNote
XContainer, MkInput, MkSwitch, XNote, XNoteDetailed,
},
props: {

View File

@ -24,6 +24,8 @@
<span>{{ $ts._deck.columnMargin }}</span>
<template #suffix>px</template>
</FormInput>
<FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
</FormBase>
</template>
@ -31,7 +33,7 @@
import { defineComponent } from 'vue';
import { faImage, faCog, faColumns } from '@fortawesome/free-solid-svg-icons';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormRadios from '@/components/form/radios.vue';
import FormInput from '@/components/form/input.vue';
import FormBase from '@/components/form/base.vue';
@ -42,7 +44,7 @@ import * as os from '@/os';
export default defineComponent({
components: {
FormSwitch,
FormSelect,
FormLink,
FormInput,
FormRadios,
FormBase,
@ -67,6 +69,7 @@ export default defineComponent({
columnAlign: deckStore.makeGetterSetter('columnAlign'),
columnMargin: deckStore.makeGetterSetter('columnMargin'),
columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'),
profile: deckStore.makeGetterSetter('profile'),
},
watch: {
@ -85,5 +88,19 @@ export default defineComponent({
mounted() {
this.$emit('info', this.INFO);
},
methods: {
async setProfile() {
const { canceled, result: name } = await os.dialog({
title: this.$ts._deck.profile,
input: {
allowEmpty: false
}
});
if (canceled) return;
this.profile = name;
location.reload();
}
}
});
</script>

View File

@ -1,33 +1,97 @@
<template>
<section class="uawsfosz _section">
<div class="_title"><Fa :icon="faCloud"/> {{ $ts.drive }}</div>
<div class="_content">
<span>{{ $ts.uploadFolder }}: {{ uploadFolder ? uploadFolder.name : '-' }}</span>
<MkButton primary @click="chooseUploadFolder()"><Fa :icon="faFolderOpen"/> {{ $ts.selectFolder }}</MkButton>
<FormBase class="">
<FormGroup v-if="!fetching">
<template #label>{{ $ts.usageAmount }}</template>
<div class="_formItem uawsfosz">
<div class="_formPanel">
<div class="meter"><div :style="meterStyle"></div></div>
</div>
</div>
<FormKeyValueView>
<template #key>{{ $ts.capacity }}</template>
<template #value>{{ bytes(capacity, 1) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts.inUse }}</template>
<template #value>{{ bytes(usage, 1) }}</template>
</FormKeyValueView>
</FormGroup>
<div class="_formItem">
<div class="_formLabel">{{ $ts.statistics }}</div>
<div class="_formPanel">
<div ref="chart"></div>
</div>
</div>
</section>
<FormButton :center="false" @click="chooseUploadFolder()" primary>
{{ $ts.uploadFolder }}
<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
<template #suffixIcon><Fa :icon="faFolderOpen"/></template>
</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import * as tinycolor from 'tinycolor2';
import ApexCharts from 'apexcharts';
import { faCloud, faFolderOpen } from '@fortawesome/free-solid-svg-icons';
import { faClock, faEyeSlash, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
import MkButton from '@/components/ui/button.vue';
import FormButton from '@/components/form/button.vue';
import FormGroup from '@/components/form/group.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import FormBase from '@/components/form/base.vue';
import * as os from '@/os';
import bytes from '@/filters/bytes';
export default defineComponent({
components: {
MkButton,
FormBase,
FormButton,
FormGroup,
FormKeyValueView,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$ts.drive,
icon: faCloud
},
fetching: true,
usage: null,
capacity: null,
uploadFolder: null,
faCloud, faClock, faEyeSlash, faFolderOpen, faTrashAlt
}
},
computed: {
meterStyle(): any {
return {
width: `${this.usage / this.capacity * 100}%`,
background: tinycolor({
h: 180 - (this.usage / this.capacity * 180),
s: 0.7,
l: 0.5
})
};
}
},
async created() {
os.api('drive').then(info => {
this.capacity = info.capacity;
this.usage = info.usage;
this.fetching = false;
this.$nextTick(() => {
this.renderChart();
});
});
if (this.$store.state.uploadFolder) {
this.uploadFolder = await os.api('drive/folders/show', {
folderId: this.$store.state.uploadFolder
@ -35,6 +99,10 @@ export default defineComponent({
}
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
chooseUploadFolder() {
os.selectDriveFolder(false).then(async folder => {
@ -48,13 +116,127 @@ export default defineComponent({
this.uploadFolder = null;
}
});
}
},
renderChart() {
os.api('charts/user/drive', {
userId: this.$i.id,
span: 'day',
limit: 21
}).then(stats => {
const addition = [];
const deletion = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
for (let i = 0; i < 21; i++) {
const x = new Date(y, m, d - i);
addition.push([
x,
stats.incSize[i]
]);
deletion.push([
x,
-stats.decSize[i]
]);
}
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'bar',
stacked: true,
height: 150,
toolbar: {
show: false
},
zoom: {
enabled: false
}
},
plotOptions: {
bar: {
columnWidth: '80%'
}
},
grid: {
clipMarkers: false,
borderColor: 'rgba(0, 0, 0, 0.1)',
xaxis: {
lines: {
show: true,
}
},
},
tooltip: {
shared: true,
intersect: false,
theme: this.$store.state.darkMode ? 'dark' : 'light',
},
dataLabels: {
enabled: false
},
legend: {
show: false
},
series: [{
name: 'Additions',
data: addition
}, {
name: 'Deletions',
data: deletion
}],
xaxis: {
type: 'datetime',
labels: {
style: {
colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--fg')).toRgbString()
}
},
axisBorder: {
color: 'rgba(0, 0, 0, 0.1)'
},
axisTicks: {
color: 'rgba(0, 0, 0, 0.1)'
},
crosshairs: {
width: 1,
opacity: 1
}
},
yaxis: {
labels: {
formatter: v => bytes(v, 0),
style: {
colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--fg')).toRgbString()
}
}
}
});
chart.render();
});
},
bytes
}
});
</script>
<style lang="scss" scoped>
.uawsfosz {
> div {
padding: 24px;
> .meter {
$size: 12px;
background: rgba(0, 0, 0, 0.1);
border-radius: ($size / 2);
overflow: hidden;
> div {
height: $size;
border-radius: ($size / 2);
}
}
}
}
</style>

View File

@ -6,6 +6,7 @@
<FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $ts.profile }}</FormLink>
<FormLink :active="page === 'privacy'" replace to="/settings/privacy"><template #icon><Fa :icon="faLockOpen"/></template>{{ $ts.privacy }}</FormLink>
<FormLink :active="page === 'reaction'" replace to="/settings/reaction"><template #icon><Fa :icon="faLaugh"/></template>{{ $ts.reaction }}</FormLink>
<FormLink :active="page === 'drive'" replace to="/settings/drive"><template #icon><Fa :icon="faCloud"/></template>{{ $ts.drive }}</FormLink>
<FormLink :active="page === 'notifications'" replace to="/settings/notifications"><template #icon><Fa :icon="faBell"/></template>{{ $ts.notifications }}</FormLink>
<FormLink :active="page === 'email'" replace to="/settings/email"><template #icon><Fa :icon="faEnvelope"/></template>{{ $ts.email }}</FormLink>
<FormLink :active="page === 'integration'" replace to="/settings/integration"><template #icon><Fa :icon="faShareAlt"/></template>{{ $ts.integration }}</FormLink>
@ -35,14 +36,14 @@
</FormGroup>
</FormBase>
<div class="main">
<component :is="component" @info="onInfo"/>
<component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, ref, watch } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes } from '@fortawesome/free-solid-svg-icons';
import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes, faCloud } from '@fortawesome/free-solid-svg-icons';
import { faLaugh, faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { i18n } from '@/i18n';
import FormLink from '@/components/form/link.vue';
@ -78,11 +79,14 @@ export default defineComponent({
const onInfo = (viewInfo) => {
INFO.value = viewInfo;
};
const pageProps = ref({});
const component = computed(() => {
if (props.page == null) return null;
switch (props.page) {
case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
case 'drive': return defineAsyncComponent(() => import('./drive.vue'));
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
@ -104,16 +108,36 @@ export default defineComponent({
case 'plugins': return defineAsyncComponent(() => import('./plugins.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
case 'update': return defineAsyncComponent(() => import('./update.vue'));
case 'registry': return defineAsyncComponent(() => import('./registry.vue'));
case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue'));
default: return null;
}
if (props.page.startsWith('registry/keys/system/')) {
return defineAsyncComponent(() => import('./registry.keys.vue'));
}
if (props.page.startsWith('registry/value/system/')) {
return defineAsyncComponent(() => import('./registry.value.vue'));
}
});
watch(component, () => {
pageProps.value = {};
if (props.page) {
if (props.page.startsWith('registry/keys/system/')) {
pageProps.value.scope = props.page.replace('registry/keys/system/', '').split('/');
}
if (props.page.startsWith('registry/value/system/')) {
const path = props.page.replace('registry/value/system/', '').split('/');
pageProps.value.xKey = path.pop();
pageProps.value.scope = path;
}
}
nextTick(() => {
scroll(el.value, 0);
});
});
}, { immediate: true });
onMounted(() => {
narrow.value = el.value.offsetWidth < 1025;
@ -125,6 +149,7 @@ export default defineComponent({
view,
el,
onInfo,
pageProps,
component,
logout: () => {
signout();
@ -134,7 +159,7 @@ export default defineComponent({
localStorage.removeItem('theme');
location.reload();
},
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes, faEnvelope,
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes, faEnvelope, faCloud,
};
},
});

View File

@ -1,5 +1,7 @@
<template>
<FormBase>
<FormLink to="/settings/update">Misskey Update</FormLink>
<FormSwitch :value="$i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $ts.showFeaturedNotesInTimeline }}
</FormSwitch>
@ -15,16 +17,19 @@
DEBUG MODE
</FormSwitch>
<template v-if="debug">
<FormLink to="/settings/regedit">RegEdit</FormLink>
<FormButton @click="taskmanager">Task Manager</FormButton>
</template>
</FormGroup>
<FormLink to="/settings/registry"><template #icon><Fa :icon="faCogs"/></template>{{ $ts.registry }}</FormLink>
<FormButton @click="closeAccount" danger>{{ $ts.closeAccount }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
import { faEllipsisH, faCogs } from '@fortawesome/free-solid-svg-icons';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
@ -34,6 +39,7 @@ import FormButton from '@/components/form/button.vue';
import * as os from '@/os';
import { debug } from '@/config';
import { defaultStore } from '@/store';
import { signout } from '@/account';
export default defineComponent({
components: {
@ -53,7 +59,8 @@ export default defineComponent({
title: this.$ts.other,
icon: faEllipsisH
},
debug
debug,
faCogs
}
},
@ -81,6 +88,22 @@ export default defineComponent({
taskmanager() {
os.popup(import('@/components/taskmanager.vue'), {
}, {}, 'closed');
},
closeAccount() {
os.dialog({
title: this.$ts.password,
input: {
type: 'password'
}
}).then(({ canceled, result: password }) => {
if (canceled) return;
os.api('i/delete-account', {
password: password
}).then(() => {
signout();
});
});
}
}
});

View File

@ -0,0 +1,115 @@
<template>
<FormBase>
<FormGroup>
<FormKeyValueView>
<template #key>{{ $ts._registry.domain }}</template>
<template #value>{{ $ts.system }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts._registry.scope }}</template>
<template #value>{{ scope.join('/') }}</template>
</FormKeyValueView>
</FormGroup>
<FormGroup v-if="keys">
<template #label>{{ $ts._registry.keys }}</template>
<FormLink v-for="key in keys" :to="`/settings/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
</FormGroup>
<FormButton @click="createKey" primary>{{ $ts._registry.createKey }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faCogs } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkInfo from '@/components/ui/info.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkInfo,
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
},
props: {
scope: {
required: true
}
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$ts.registry,
icon: faCogs
},
keys: null,
}
},
watch: {
scope() {
this.fetch();
}
},
mounted() {
this.$emit('info', this.INFO);
this.fetch();
},
methods: {
fetch() {
os.api('i/registry/keys-with-type', {
scope: this.scope
}).then(keys => {
this.keys = Object.entries(keys).sort((a, b) => a[0].localeCompare(b[0]));
});
},
async createKey() {
const { canceled, result } = await os.form(this.$ts._registry.createKey, {
key: {
type: 'string',
label: this.$ts._registry.key,
},
value: {
type: 'string',
multiline: true,
label: this.$ts.value,
},
scope: {
type: 'string',
label: this.$ts._registry.scope,
default: this.scope.join('/')
}
});
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope: result.scope.split('/'),
key: result.key,
value: JSON5.parse(result.value),
}).then(() => {
this.fetch();
});
}
}
});
</script>

View File

@ -0,0 +1,149 @@
<template>
<FormBase>
<MkInfo warn>{{ $ts.editTheseSettingsMayBreakAccount }}</MkInfo>
<template v-if="value">
<FormGroup>
<FormKeyValueView>
<template #key>{{ $ts._registry.domain }}</template>
<template #value>{{ $ts.system }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts._registry.scope }}</template>
<template #value>{{ scope.join('/') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts._registry.key }}</template>
<template #value>{{ xKey }}</template>
</FormKeyValueView>
</FormGroup>
<FormGroup>
<FormTextarea tall v-model:value="valueForEditor" class="_monospace" style="tab-size: 2;">
<span>{{ $ts.value }} (JSON)</span>
</FormTextarea>
<FormButton @click="save" primary><Fa :icon="faSave"/> {{ $ts.save }}</FormButton>
</FormGroup>
<FormKeyValueView>
<template #key>{{ $ts.updatedAt }}</template>
<template #value><MkTime :time="value.updatedAt" mode="detail"/></template>
</FormKeyValueView>
<FormButton danger @click="del"><Fa :icon="faTrash"/> {{ $ts.delete }}</FormButton>
</template>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faCogs, faSave, faTrash } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkInfo from '@/components/ui/info.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkInfo,
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormTextarea,
FormGroup,
FormKeyValueView,
},
props: {
scope: {
required: true
},
xKey: {
required: true
},
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$ts.registry,
icon: faCogs
},
value: null,
valueForEditor: null,
faSave, faTrash,
}
},
watch: {
key() {
this.fetch();
},
},
mounted() {
this.$emit('info', this.INFO);
this.fetch();
},
methods: {
fetch() {
os.api('i/registry/get-detail', {
scope: this.scope,
key: this.xKey
}).then(value => {
this.value = value;
this.valueForEditor = JSON5.stringify(this.value.value, null, '\t');
});
},
save() {
try {
JSON5.parse(this.valueForEditor);
} catch (e) {
os.dialog({
type: 'error',
text: this.$ts.invalidValue
});
return;
}
os.dialog({
type: 'warning',
text: this.$ts.saveConfirm,
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope: this.scope,
key: this.xKey,
value: JSON5.parse(this.valueForEditor)
});
});
},
del() {
os.dialog({
type: 'warning',
text: this.$ts.deleteConfirm,
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
os.apiWithDialog('i/registry/remove', {
scope: this.scope,
key: this.xKey
});
});
}
}
});
</script>

View File

@ -0,0 +1,91 @@
<template>
<FormBase>
<FormGroup v-if="scopes">
<template #label>{{ $ts.system }}</template>
<FormLink v-for="scope in scopes" :to="`/settings/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink>
</FormGroup>
<FormButton @click="createKey" primary>{{ $ts._registry.createKey }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faCogs } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkInfo from '@/components/ui/info.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkInfo,
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$ts.registry,
icon: faCogs
},
scopes: null,
}
},
created() {
this.fetch();
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
fetch() {
os.api('i/registry/scopes').then(scopes => {
this.scopes = scopes.slice().sort((a, b) => a.join('/').localeCompare(b.join('/')));
});
},
async createKey() {
const { canceled, result } = await os.form(this.$ts._registry.createKey, {
key: {
type: 'string',
label: this.$ts._registry.key,
},
value: {
type: 'string',
multiline: true,
label: this.$ts.value,
},
scope: {
type: 'string',
label: this.$ts._registry.scope,
}
});
if (canceled) return;
os.apiWithDialog('i/registry/set', {
scope: result.scope.split('/'),
key: result.key,
value: JSON5.parse(result.value),
}).then(() => {
this.fetch();
});
}
}
});
</script>

View File

@ -25,6 +25,7 @@ import FormButton from '@/components/form/button.vue';
import { applyTheme, validateTheme } from '@/scripts/theme';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { addTheme, getThemes } from '@/theme-store';
export default defineComponent({
components: {
@ -74,7 +75,7 @@ export default defineComponent({
});
return false;
}
if (ColdDeviceStorage.get('themes').some(t => t.id === theme.id)) {
if (getThemes().some(t => t.id === theme.id)) {
os.dialog({
type: 'info',
text: this.$ts._theme.alreadyInstalled
@ -90,11 +91,10 @@ export default defineComponent({
if (theme) applyTheme(theme, false);
},
install(code) {
async install(code) {
const theme = this.parseThemeCode(code);
if (!theme) return;
const themes = ColdDeviceStorage.get('themes').concat(theme);
ColdDeviceStorage.set('themes', themes);
await addTheme(theme);
os.dialog({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })

View File

@ -37,6 +37,7 @@ import { Theme, builtinThemes } from '@/scripts/theme';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { getThemes, removeTheme } from '@/theme-store';
export default defineComponent({
components: {
@ -57,7 +58,7 @@ export default defineComponent({
title: this.$ts._theme.manage,
icon: faFolderOpen
},
installedThemes: ColdDeviceStorage.ref('themes'),
installedThemes: getThemes(),
builtinThemes,
selectedThemeId: null,
faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye
@ -91,10 +92,7 @@ export default defineComponent({
},
uninstall() {
const theme = this.selectedTheme;
const themes = ColdDeviceStorage.get('themes').filter(t => t.id != theme.id);
ColdDeviceStorage.set('themes', themes);
os.success();
removeTheme(this.selectedTheme);
},
}
});

View File

@ -77,6 +77,7 @@ import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
import { ColdDeviceStorage } from '@/store';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { fetchThemes, getThemes } from '@/theme-store';
export default defineComponent({
components: {
@ -96,7 +97,7 @@ export default defineComponent({
icon: faPalette
};
const installedThemes = ColdDeviceStorage.ref('themes');
const installedThemes = ref(getThemes());
const themes = computed(() => builtinThemes.concat(installedThemes.value));
const darkThemes = computed(() => themes.value.filter(t => t.base == 'dark' || t.kind == 'dark'));
const lightThemes = computed(() => themes.value.filter(t => t.base == 'light' || t.kind == 'light'));
@ -137,6 +138,10 @@ export default defineComponent({
emit('info', INFO);
});
fetchThemes().then(() => {
installedThemes.value = getThemes();
});
return {
INFO,
darkThemes,

View File

@ -0,0 +1,94 @@
<template>
<FormBase>
<template v-if="meta">
<MkInfo v-if="version === meta.version">{{ $ts.youAreRunningUpToDateClient }}</MkInfo>
<MkInfo v-else warn>{{ $ts.newVersionOfClientAvailable }}</MkInfo>
</template>
<FormGroup>
<template #label>{{ instanceName }}</template>
<FormKeyValueView>
<template #key>{{ $ts.currentVersion }}</template>
<template #value>{{ version }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $ts.latestVersion }}</template>
<template #value v-if="meta">{{ meta.version }}</template>
<template #value v-else><MkEllipsis/></template>
</FormKeyValueView>
</FormGroup>
<FormGroup>
<template #label>Misskey</template>
<FormKeyValueView>
<template #key>{{ $ts.latestVersion }}</template>
<template #value v-if="releases">{{ releases[0].tag_name }}</template>
<template #value v-else><MkEllipsis/></template>
</FormKeyValueView>
<template #caption v-if="releases"><MkTime :time="releases[0].published_at" mode="detail"/></template>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faInfoCircle, faSyncAlt } from '@fortawesome/free-solid-svg-icons';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import MkInfo from '@/components/ui/info.vue';
import * as os from '@/os';
import { version, instanceName } from '@/config';
export default defineComponent({
components: {
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
MkInfo,
},
emits: ['info'],
data() {
return {
INFO: {
title: 'Misskey Update',
icon: faSyncAlt
},
version,
instanceName,
releases: null,
meta: null
}
},
mounted() {
this.$emit('info', this.INFO);
os.api('meta', {
detail: false
}).then(meta => {
this.meta = meta;
localStorage.setItem('v', meta.version);
});
fetch('https://api.github.com/repos/syuilo/misskey/releases', {
method: 'GET',
})
.then(res => res.json())
.then(res => {
this.releases = res;
});
},
methods: {
}
});
</script>

View File

@ -41,7 +41,7 @@
<MkSample class="preview"/>
</div>
</div>
<FormButton @click="saveAs">{{ $ts.saveAs }}</FormButton>
<FormButton @click="saveAs" primary>{{ $ts.saveAs }}</FormButton>
</FormBase>
</template>
@ -60,6 +60,7 @@ import { Theme, applyTheme, validateTheme } from '@/scripts/theme';
import { host } from '@/config';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { addTheme } from '@/theme-store';
export default defineComponent({
components: {
@ -105,6 +106,7 @@ export default defineComponent({
{ color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
],
fgColor: null,
changed: false,
faPalette,
}
},
@ -123,12 +125,39 @@ export default defineComponent({
this.$watch('bgColor', this.apply);
this.$watch('accentColor', this.apply);
this.$watch('fgColor', this.apply);
window.addEventListener('beforeunload', this.beforeunload);
},
beforeUnmount() {
window.removeEventListener('beforeunload', this.beforeunload);
},
async beforeRouteLeave(to, from) {
if (this.changed && !(await this.leaveConfirm())) {
return false;
}
},
methods: {
convert() {
beforeunload(e: BeforeUnloadEvent) {
if (this.changed) {
e.preventDefault();
e.returnValue = '';
}
},
async leaveConfirm(): Promise<boolean> {
const { canceled } = await os.dialog({
type: 'warning',
text: this.$ts.leaveConfirm,
showCancelButton: true
});
return !canceled;
},
convert(): Theme {
return {
id: '#MY_THEME#',
name: this.$ts.myTheme,
base: this.bgColor.kind,
props: {
@ -145,12 +174,8 @@ export default defineComponent({
if (this.fgColor == null) this.fgColor = this.fgColors[0];
const theme = this.convert();
applyTheme(theme, true);
const themes = ColdDeviceStorage.get('themes').filter(t => t.id != '#MY_THEME#').concat(theme);
ColdDeviceStorage.set('themes', themes);
ColdDeviceStorage.set('lightTheme', theme.id);
ColdDeviceStorage.set('darkTheme', theme.id);
applyTheme(theme, false);
this.changed = true;
},
async saveAs() {
@ -166,10 +191,14 @@ export default defineComponent({
theme.id = uuid();
theme.name = name;
theme.author = `@${this.$i.username}@${toUnicode(host)}`;
const themes = ColdDeviceStorage.get('themes').concat(theme);
ColdDeviceStorage.set('themes', themes);
ColdDeviceStorage.set('lightTheme', theme.id);
ColdDeviceStorage.set('darkTheme', theme.id);
addTheme(theme);
applyTheme(theme);
if (this.$store.state.darkMode) {
ColdDeviceStorage.set('darkTheme', theme.id);
} else {
ColdDeviceStorage.set('lightTheme', theme.id);
}
this.changed = false;
os.dialog({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })

View File

@ -97,7 +97,8 @@ export default defineComponent({
},
tooltip: {
shared: true,
intersect: false
intersect: false,
theme: this.$store.state.darkMode ? 'dark' : 'light',
},
series: [{
name: 'Normal',

View File

@ -11,6 +11,7 @@ type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
export class Storage<T extends StateDef> {
public readonly key: string;
public readonly keyForLocalStorage: string;
public readonly def: T;
@ -19,20 +20,22 @@ export class Storage<T extends StateDef> {
public readonly reactiveState: { [K in keyof T]: Ref<T[K]['default']> };
constructor(key: string, def: T) {
this.key = 'pizzax::' + key;
this.key = key;
this.keyForLocalStorage = 'pizzax::' + key;
this.def = def;
// TODO: indexedDBにする
const deviceState = JSON.parse(localStorage.getItem(this.key) || '{}');
const deviceAccountState = $i ? JSON.parse(localStorage.getItem(this.key + '::' + $i.id) || '{}') : {};
const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}');
const deviceAccountState = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}') : {};
const registryCache = $i ? JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}') : {};
const state = {};
const reactiveState = {};
for (const [k, v] of Object.entries(def)) {
if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
state[k] = deviceState[k];
} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call($i.clientData, k)) {
state[k] = $i.clientData[k];
} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
state[k] = registryCache[k];
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
state[k] = deviceAccountState[k];
} else {
@ -47,16 +50,27 @@ export class Storage<T extends StateDef> {
this.reactiveState = reactiveState as any;
if ($i) {
watch($i, () => {
if (_DEV_) console.log('$i updated');
for (const [k, v] of Object.entries(def)) {
if (v.where === 'account' && Object.prototype.hasOwnProperty.call($i!.clientData, k)) {
state[k] = $i!.clientData[k];
reactiveState[k].value = $i!.clientData[k];
// なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう)
setTimeout(() => {
api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => {
const cache = {};
for (const [k, v] of Object.entries(def)) {
if (v.where === 'account') {
if (Object.prototype.hasOwnProperty.call(kvs, k)) {
state[k] = kvs[k];
reactiveState[k].value = kvs[k];
cache[k] = kvs[k];
} else {
state[k] = v.default;
reactiveState[k].value = v.default;
}
}
}
}
});
localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache));
});
}, 1);
// TODO: streamingのuser storage updateイベントを監視して更新
}
}
@ -68,21 +82,26 @@ export class Storage<T extends StateDef> {
switch (this.def[key].where) {
case 'device': {
const deviceState = JSON.parse(localStorage.getItem(this.key) || '{}');
const deviceState = JSON.parse(localStorage.getItem(this.keyForLocalStorage) || '{}');
deviceState[key] = value;
localStorage.setItem(this.key, JSON.stringify(deviceState));
localStorage.setItem(this.keyForLocalStorage, JSON.stringify(deviceState));
break;
}
case 'deviceAccount': {
if ($i == null) break;
const deviceAccountState = JSON.parse(localStorage.getItem(this.key + '::' + $i.id) || '{}');
const deviceAccountState = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::' + $i.id) || '{}');
deviceAccountState[key] = value;
localStorage.setItem(this.key + '::' + $i.id, JSON.stringify(deviceAccountState));
localStorage.setItem(this.keyForLocalStorage + '::' + $i.id, JSON.stringify(deviceAccountState));
break;
}
case 'account': {
api('i/update-client-setting', {
name: key,
if ($i == null) break;
const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}');
cache[key] = value;
localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache));
api('i/registry/set', {
scope: ['client', this.key],
key: key,
value: value
});
break;

View File

@ -58,7 +58,7 @@ export const router = createRouter({
{ path: '/my/lists', component: page('my-lists/index') },
{ path: '/my/lists/:list', component: page('my-lists/list') },
{ path: '/my/groups', component: page('my-groups/index') },
{ path: '/my/groups/:group', component: page('my-groups/group') },
{ path: '/my/groups/:group', component: page('my-groups/group'), props: route => ({ groupId: route.params.group }) },
{ path: '/my/antennas', component: page('my-antennas/index') },
{ path: '/my/clips', component: page('my-clips/index') },
{ path: '/scratchpad', component: page('scratchpad') },
@ -84,7 +84,6 @@ export const router = createRouter({
{ path: '/miauth/:session', component: page('miauth') },
{ path: '/authorize-follow', component: page('follow') },
{ path: '/share', component: page('share') },
{ path: '/test', component: page('test') },
{ path: '/:catchAll(.*)', component: page('not-found') }
],
// なんかHacky

View File

@ -208,7 +208,7 @@ type Plugin = {
*/
export class ColdDeviceStorage {
public static default = {
themes: [] as Theme[],
themes: [] as Theme[], // TODO: そのうち消す
darkTheme: '8050783a-7f63-445a-b270-36d0f6ba1677',
lightTheme: '4eea646f-7afa-4645-83e9-83af0333cd37',
syncDeviceDarkMode: true,

View File

@ -29,6 +29,7 @@ html {
font-family: "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
line-height: 1.35;
text-size-adjust: 100%;
tab-size: 2;
&, * {
scrollbar-color: var(--scrollbarHandle) inherit;

62
src/client/theme-store.ts Normal file
View File

@ -0,0 +1,62 @@
import { api } from '@/os';
import { $i } from '@/account';
import { ColdDeviceStorage } from './store';
import { Theme } from './scripts/theme';
const lsCacheKey = $i ? `themes:${$i.id}` : '';
export function getThemes(): Theme[] {
return JSON.parse(localStorage.getItem(lsCacheKey) || '[]');
}
export async function fetchThemes(): Promise<void> {
if ($i == null) return;
try {
const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
} catch (e) {
if (e.code === 'NO_SUCH_KEY') return;
throw e;
}
}
export async function addTheme(theme: Theme): Promise<void> {
await fetchThemes();
const themes = getThemes().concat(theme);
await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
}
export async function removeTheme(theme: Theme): Promise<void> {
const themes = getThemes().filter(t => t.id != theme.id);
await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
}
// TODO: そのうち消す
if (ColdDeviceStorage.get('themes').length > 0) {
const lsThemes = ColdDeviceStorage.get('themes');
let registryThemes;
try {
registryThemes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
} catch (e) {
if (e.code === 'NO_SUCH_KEY') {
registryThemes = [];
} else {
throw e;
}
}
const themes = [] as Theme[];
for (const theme of lsThemes) {
if (themes.some(x => x.id === theme.id)) continue;
themes.push(theme);
}
for (const theme of registryThemes) {
if (themes.some(x => x.id === theme.id)) continue;
themes.push(theme);
}
await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
localStorage.setItem(lsCacheKey, JSON.stringify(themes));
ColdDeviceStorage.set('themes', []);
}

View File

@ -16,5 +16,6 @@
mention: '@accent',
hashtag: '@accent',
inputBorder: 'rgba(0, 0, 0, 0.1)',
infoBg: 'rgb(226, 235, 241)',
},
}

View File

@ -41,7 +41,7 @@ import { getScrollContainer } from '@/scripts/scroll';
import * as os from '@/os';
import { sidebarDef } from '@/sidebar';
import XCommon from './_common_/common.vue';
import { deckStore, addColumn } from './deck/deck-store';
import { deckStore, addColumn, loadDeck } from './deck/deck-store';
export default defineComponent({
components: {
@ -88,6 +88,7 @@ export default defineComponent({
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', this.onWheel);
loadDeck();
},
mounted() {

View File

@ -70,7 +70,6 @@ export default defineComponent({
data() {
return {
deckStore,
active: true,
dragging: false,
draghover: false,
dropready: false,
@ -83,6 +82,10 @@ export default defineComponent({
return this.column.type === 'main';
},
active(): boolean {
return this.column.active !== false;
},
keymap(): any {
return {
'shift+up': () => this.$parent.$emit('parent-focus', 'up'),
@ -124,7 +127,9 @@ export default defineComponent({
toggleActive() {
if (!this.isStacked) return;
this.active = !this.active;
updateColumn(this.column.id, {
active: !this.column.active
});
},
getMenu() {

View File

@ -1,5 +1,7 @@
import { throttle } from 'throttle-debounce';
import { i18n } from '@/i18n';
import { markRaw } from 'vue';
import { api } from '@/os';
import { markRaw, watch } from 'vue';
import { Storage } from '../../pizzax';
type ColumnWidget = {
@ -14,6 +16,7 @@ type Column = {
name: string | null;
width: number;
widgets?: ColumnWidget[];
active?: boolean;
};
function copy<T>(x: T): T {
@ -21,23 +24,17 @@ function copy<T>(x: T): T {
}
export const deckStore = markRaw(new Storage('deck', {
profile: {
where: 'deviceAccount',
default: 'default'
},
columns: {
where: 'deviceAccount',
default: [{
id: 'a',
type: 'main',
name: i18n.locale._deck._columns.main,
width: 350,
}, {
id: 'b',
type: 'notifications',
name: i18n.locale._deck._columns.notifications,
width: 330,
}] as Column[]
default: [] as Column[]
},
layout: {
where: 'deviceAccount',
default: [['a'], ['b']] as Column['id'][][]
default: [] as Column['id'][][]
},
columnAlign: {
where: 'deviceAccount',
@ -61,10 +58,60 @@ export const deckStore = markRaw(new Storage('deck', {
},
}));
export const loadDeck = async () => {
let deck;
try {
deck = await api('i/registry/get', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
});
} catch (e) {
if (e.code === 'NO_SUCH_KEY') {
// 後方互換性のため
if (deckStore.state.profile === 'default') {
saveDeck();
return;
}
deckStore.set('columns', [{
id: 'a',
type: 'main',
name: i18n.locale._deck._columns.main,
width: 350,
}, {
id: 'b',
type: 'notifications',
name: i18n.locale._deck._columns.notifications,
width: 330,
}]);
deckStore.set('layout', [['a'], ['b']]);
return;
}
throw e;
}
deckStore.set('columns', deck.columns);
deckStore.set('layout', deck.layout);
};
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
export const saveDeck = throttle(1000, () => {
api('i/registry/set', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
value: {
columns: deckStore.reactiveState.columns.value,
layout: deckStore.reactiveState.layout.value,
}
});
});
export function addColumn(column: Column) {
if (column.name == undefined) column.name = null;
deckStore.push('columns', column);
deckStore.push('layout', [column.id]);
saveDeck();
}
export function removeColumn(id: Column['id']) {
@ -72,6 +119,7 @@ export function removeColumn(id: Column['id']) {
deckStore.set('layout', deckStore.state.layout
.map(ids => ids.filter(_id => _id !== id))
.filter(ids => ids.length > 0));
saveDeck();
}
export function swapColumn(a: Column['id'], b: Column['id']) {
@ -83,6 +131,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) {
layout[aX][aY] = b;
layout[bX][bY] = a;
deckStore.set('layout', layout);
saveDeck();
}
export function swapLeftColumn(id: Column['id']) {
@ -98,6 +147,7 @@ export function swapLeftColumn(id: Column['id']) {
return true;
}
});
saveDeck();
}
export function swapRightColumn(id: Column['id']) {
@ -113,6 +163,7 @@ export function swapRightColumn(id: Column['id']) {
return true;
}
});
saveDeck();
}
export function swapUpColumn(id: Column['id']) {
@ -132,6 +183,7 @@ export function swapUpColumn(id: Column['id']) {
return true;
}
});
saveDeck();
}
export function swapDownColumn(id: Column['id']) {
@ -151,6 +203,7 @@ export function swapDownColumn(id: Column['id']) {
return true;
}
});
saveDeck();
}
export function stackLeftColumn(id: Column['id']) {
@ -160,6 +213,7 @@ export function stackLeftColumn(id: Column['id']) {
layout[i - 1].push(id);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
saveDeck();
}
export function popRightColumn(id: Column['id']) {
@ -169,6 +223,7 @@ export function popRightColumn(id: Column['id']) {
layout.splice(i + 1, 0, [id]);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
saveDeck();
}
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
@ -180,6 +235,7 @@ export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
column.widgets.unshift(widget);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
@ -190,6 +246,7 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
column.widgets = column.widgets.filter(w => w.id != widget.id);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
@ -200,6 +257,7 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
column.widgets = widgets;
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) {
@ -213,6 +271,7 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, data: any
} : w);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumn(id: Column['id'], column: Partial<Column>) {
@ -225,4 +284,5 @@ export function updateColumn(id: Column['id'], column: Partial<Column>) {
}
columns[columnIndex] = currentColumn;
deckStore.set('columns', columns);
saveDeck();
}

View File

@ -58,6 +58,7 @@ export default defineComponent({
onContextmenu(e) {
if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return;
if (window.getSelection().toString() !== '') return;
const path = this.$route.path;
os.contextMenu([{
type: 'label',

View File

@ -188,6 +188,7 @@ export default defineComponent({
onContextmenu(e) {
if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return;
if (window.getSelection().toString() !== '') return;
const path = this.$route.path;
os.contextMenu([{
type: 'label',

View File

@ -63,6 +63,7 @@ import { MutedNote } from '../models/entities/muted-note';
import { Channel } from '../models/entities/channel';
import { ChannelFollowing } from '../models/entities/channel-following';
import { ChannelNotePining } from '../models/entities/channel-note-pining';
import { RegistryItem } from '../models/entities/registry-item';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
@ -159,6 +160,7 @@ export const entities = [
Channel,
ChannelFollowing,
ChannelNotePining,
RegistryItem,
...charts as any
];

View File

@ -1,4 +1,4 @@
# AiScript
## 関数
## وظائف
デフォルトで値渡しです。

View File

@ -1,15 +1,15 @@
# タイムラインの比較
# مقارنة الجدول الزمني
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## الرئيسي
自分のフォローしているユーザーの投稿
## الواجهة الرئيسية
مشاركات المستخدمين الذين تتابعهم
## المحلي
全てのローカルユーザーの「ホーム」指定されていない投稿
جميع مشاركات المستخدمين المحليين التي لم يتم وضع علامة عليها على أنها "صفحة رئيسية فقط "
## الاجتماعي
自分のフォローしているユーザーの投稿と、全てのローカルユーザーの「ホーム」指定されていない投稿
## إجتماعي
مشاركات المستخدمين الذين تتابعهم بالإضافة إلى جميع مشاركات المستخدمين المحليين التي لم يتم وضع علامة عليها كـ "الصفحة الرئيسية فقط"
## الشامل
## عالمي
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿

View File

@ -1,4 +1,4 @@
# AiScript
## 함수
デフォルトで値渡しです。
기본값은 값에 의한 호출입니다.

View File

@ -1,6 +1,6 @@
# キーボードショートカット
## グローバル
## Globalna
これらのショートカットは基本的にどこでも使えます。
<table>
<thead>

View File

@ -110,7 +110,7 @@ y = Math.floor(pos / mapWidth)
```
### フォームコントロールの種類
#### スイッチ
#### Przełącznik
type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。
##### プロパティ

View File

@ -31,7 +31,7 @@
**ストリームでのやり取りはすべてJSONです。**
## チャンネル
## Kanały
MisskeyのストリーミングAPIにはチャンネルという概念があります。これは、送受信する情報を分離するための仕組みです。 Misskeyのストリームに接続しただけでは、まだリアルタイムでタイムラインの投稿を受信したりはできません。 ストリーム上でチャンネルに接続することで、様々な情報を受け取ったり情報を送信したりすることができるようになります。
### チャンネルに接続する

View File

@ -1,12 +1,12 @@
# Motywy
テーマを設定して、Misskeyクライアントの見た目を変更できます。
Możesz zmienić wygląd klienta Misskey, ustawiając motyw.
## テーマの設定
設定 > テーマ
## Ustawienia motywu
Ustawienia > Motywy
## テーマを作成する
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。
## Tworzenie motywu
Kod motywów jest zapisywany jako obiekt JSON5 z opcjami motywu. Motywy składają się z następujących opcji.
``` js
{
id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -33,26 +33,26 @@
```
* `id` ... テーマの一意なID。UUIDをおすすめします。
* `name` ... テーマ名
* `author` ... テーマの作者
* `desc` ... テーマの説明(オプション)
* `base` ... 明るいテーマか、暗いテーマか
* `id` ... Unikatowe ID motywu.Zalecane jest użycie UUID.
* `name` ... Nazwa motywu
* `author` ... Twórca motywu
* `desc` ... Opis motywu (nieobowiązkowy)
* `base` ... Określa, czy motyw jest oparty na jasnym, czy ciemnym motywie
* `light`にすると明るいテーマになり、`dark`にすると暗いテーマになります。
* テーマはここで設定されたベーステーマを継承します。
* `props` ... テーマのスタイル定義。これから説明します。
* Motyw będzie dziedziczył domyślne wartości określonego tu motywu.
* `props` ... Definicje stylów motywu.これから説明します。
### テーマのスタイル定義
### Definicje stylów motywu.
`props`下にはテーマのスタイルを定義します。 キーがCSSの変数名になり、バリューで中身を指定します。 なお、この`props`オブジェクトはベーステーマから継承されます。 ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)です。 つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます。
#### バリューで使える構文
* 16進数で表された色
* : `#00ff00`
* `rgb(r, g, b)`形式で表された色
* : `rgb(0, 255, 0)`
* `rgb(r, g, b, a)`形式で表された透明度を含む色
* : `rgba(0, 255, 0, 0.5)`
* 他のキーの値の参照
#### Składnia wartości
* Kolory Hex
* Np.: `#00ff00`
* Kolory RGB w składni `rgb(r, g, b)`
* Np.: `rgb(0, 255, 0)`
* Kolory RGBA w składni `rgb(r, g, b)`
* Np.: `rgba(0, 255, 0, 0.5)`
* Nawiązania do wartości innych kluczy
* `@{キー名}`と書くと他のキーの値の参照になります。`{キー名}`は参照したいキーの名前に置き換えます。
* 例: `@panel`
* 定数(後述)の参照

View File

@ -1,9 +1,9 @@
# タイムラインの比較
# Porównanie osi czasu
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## Strona główna
自分のフォローしているユーザーの投稿
Wpisy użytkowników, których obserwujesz
## Lokalne
全てのローカルユーザーの「ホーム」指定されていない投稿
@ -11,5 +11,5 @@ https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOBy
## ソーシャル
自分のフォローしているユーザーの投稿と、全てのローカルユーザーの「ホーム」指定されていない投稿
## グローバル
## Globalne
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿

View File

@ -10,7 +10,7 @@
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>検索</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>Pesquisar</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
</tbody>
</table>

View File

@ -1,4 +1,4 @@
# ミュート
# Silenciar
ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります:

View File

@ -118,11 +118,11 @@
其中:
* `id`为前文所述连接到频道时想要设置的ID。
## ストリームを経由してAPIリクエストする
## 通过流发送API请求
ストリームを経由してAPIリクエストすると、HTTPリクエストを発生させずにAPIを利用できます。そのため、コードを簡潔にできたり、パフォーマンスの向上を見込めるかもしれません
使用流的方式可以在不使用http请求的条件下来发送API请求。因此您可以使用更简洁的代码来提高效率
ストリームを経由してAPIリクエストするには、次のようなデータをJSONでストリームに送信します:
要通过流发送API请求请将如下所示的JSON格式数据发送到流
```json
{
type: 'api',
@ -137,17 +137,17 @@
```
其中:
* `id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません
* `endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します
* `data`には、エンドポイントのパラメータを含めます
* `id`是一个唯一的ID用来识别不同请求所对应的回应。可以使用UUID或者简单的随机数生成方法
* `endpoint`包含请求要指定发送的API终端
* `data`包含需要发送的终端参数
<div class="ui info">
<p><i class="fas fa-info-circle"></i> APIのエンドポイントやパラメータについてはAPIリファレンスをご確認ください</p>
<p><i class="fas fa-info-circle"></i> 详见API参考中的API终端和参数</p>
</div>
### レスポンスの受信
### 接收回应
APIへリクエストすると、レスポンスがストリームから次のような形式で流れてきます。
当你向API发送请求时会受到流发送的如下格式的回应
```json
{
@ -159,22 +159,22 @@ APIへリクエストすると、レスポンスがストリームから次の
```
其中:
* `xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます
* `body`には、レスポンスが含まれています
* `xxxxxxxxxxxxxxxx`部分包含该请求之前设置过的`id`。因此,可以判断出回应是对应的哪个请求
* `body`包含回应的数据
## 投稿のキャプチャ
## 帖子抓取
Misskeyは投稿のキャプチャと呼ばれる仕組みを提供しています。これは、指定した投稿のイベントをストリームで受け取る機能です
Misskey提供一种被称为“帖子抓取”的机制。该功能以流的形式接受指定帖子的事件
えばタイムラインを取得してユーザーに表示したとします。ここで誰かがそのタイムラインに含まれるどれかの投稿に対してリアクションしたとします
如,假设您获得了时间线的数据并将其显示给用户。而现在有人对时间线中的某一个帖子做出了回应
しかし、クライアントからするとある投稿にリアクションが付いたことなどは知る由がないため、リアルタイムでリアクションをタイムライン上の投稿に反映して表示するといったことができません
但是,由于客户端无法知道某个帖子有回应,因此无法在时间线上的帖子中反映并实时显示出来
この問題を解決するために、Misskeyは投稿のキャプチャ機構を用意しています。投稿をキャプチャすると、その投稿に関するイベントを受け取ることができるため、リアルタイムでリアクションを反映させたりすることが可能になります
为了解决此问题Misskey引入了帖子抓取的机制。抓取帖子时您可以接收与该帖子相关的事件因此您可以将帖子的回应实时反映出来
### 投稿をキャプチャする
### 抓取帖子
投稿をキャプチャするには、ストリームに次のようなメッセージを送信します:
要抓取帖子,请向流发送下列格式的消息:
```json
{
@ -186,11 +186,11 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```
其中:
* `id`にキャプチャしたい投稿の`id`を設定します。
* 请将`id`的值设置为需要抓取的帖子`id`值
このメッセージを送信すると、Misskeyにキャプチャを要請したことになり、以後、その投稿に関するイベントが流れてくるようになります
发送此消息表示您已请求Misskey抓取该贴子并且您将收到与该帖子有关的事件
えば投稿にリアクションが付いたとすると、次のようなメッセージが流れてきます:
如,如果帖子有回应,您将收到以下消息:
```json
{
@ -207,17 +207,17 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```
其中:
* `body`内の`id`に、イベントを発生させた投稿のIDが設定されます
* `body`内の`type`に、イベントの種類が設定されます
* `body`内の`body`に、イベントの詳細が設定されます
* `body`里的`id`用来表示触发事件的帖子的ID
* `body`里的`type`用来表示事件类型
* `body`里的`body`用来表示事件详细内容
#### イベントの種類
#### 事件类型
##### `reacted`
その投稿にリアクションがされた時に発生します
在帖子有回应时触发
* `reaction`に、リアクションの種類が設定されます
* `userId`に、リアクションを行ったユーザーのIDが設定されます
* `reaction`用来表示回应的类型
* `userId`用来表示做出回应的用户的ID
例:
```json
@ -235,9 +235,9 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```
##### `deleted`
その投稿が削除された時に発生します
帖子删除时触发
* `deletedAt`に、削除日時が設定されます
* `deletedAt`表示删除的日期和时间
例:
```json
@ -254,10 +254,10 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```
##### `pollVoted`
その投稿に添付されたアンケートに投票された時に発生します
帖子附带的问卷调查被投票时触发
* `choice`に、選択肢IDが設定されます
* `userId`に、投票を行ったユーザーのIDが設定されます
* `choice`表示选择项ID
* `userId`表示投票的用户ID
例:
```json
@ -274,11 +274,11 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
}
```
### 投稿のキャプチャを解除する
### 取消帖子抓取
その投稿がもう画面に表示されなくなったりして、その投稿に関するイベントをもう受け取る必要がなくなったときは、キャプチャの解除を申請してください
如果希望该帖子不再出现在屏幕上,并且您不再需要接收与该帖子相关的事件,可以发送取消帖子抓取的请求
次のメッセージを送信します:
请发送以下消息:
```json
{
@ -290,65 +290,65 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
```
其中:
* `id`にキャプチャを解除したい投稿の`id`を設定します
* 请将`id`的值设置为需要取消抓取的帖子`id`值
このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります
发送此消息后,将不再接收与该帖子相关的其他事件
# チャンネル一覧
# 频道列表
## `main`
アカウントに関する基本的な情報が流れてきます。このチャンネルにパラメータはありません
将会发送帐户的基本信息。该频道没有参数
### 流れてくるイベント一覧
### 发送的事件列表
#### `转发`
自分の投稿がRenoteされた時に発生するイベントです。自分自身の投稿をRenoteしたときは発生しません
#### `renote`
当您的帖子被转发时会触发该事件。转发自己的帖子不会触发
#### `mention`
誰かからメンションされたときに発生するイベントです
有人提及您时会触发该事件
#### `readAllNotifications`
自分宛ての通知がすべて既読になったことを表すイベントです。このイベントを利用して、「通知があることを示すアイコン」のようなものをオフにしたりする等のケースが想定されます
这个事件表示您的所有通知都被设为已读。此事件可用于关闭“未读通知图标”等情况
#### `meUpdated`
自分の情報が更新されたことを表すイベントです
该事件表示您的个人信息已更新
#### `follow`
自分が誰かをフォローしたときに発生するイベントです
当您关注某人时会触发该事件
#### `unfollow`
自分が誰かのフォローを解除したときに発生するイベントです
当您取消关注某人时会触发该事件
#### `followed`
自分が誰かにフォローされたときに発生するイベントです
当您被某人关注时会触发该事件
## `homeTimeline`
ホームタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません
ホームタイムラインの投稿情報が流れてきます。该频道没有参数
### 流れてくるイベント一覧
### 发送的事件列表
#### `note`
タイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `localTimeline`
ローカルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません
ローカルタイムラインの投稿情報が流れてきます。该频道没有参数
### 流れてくるイベント一覧
### 发送的事件列表
#### `note`
ローカルタイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `hybridTimeline`
ソーシャルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません
ソーシャルタイムラインの投稿情報が流れてきます。该频道没有参数
### 流れてくるイベント一覧
### 发送的事件列表
#### `note`
ソーシャルタイムラインに新しい投稿が流れてきたときに発生するイベントです。
## `globalTimeline`
グローバルタイムラインの投稿情報が流れてきます。このチャンネルにパラメータはありません
グローバルタイムラインの投稿情報が流れてきます。该频道没有参数
### 流れてくるイベント一覧
### 发送的事件列表
#### `note`
グローバルタイムラインに新しい投稿が流れてきたときに発生するイベントです。

View File

@ -1,12 +1,12 @@
# 主题
テーマを設定して、Misskeyクライアントの見た目を変更できます
您可以设置主题来改变您的Misskey客户端的外观和质感
## テーマの設定
設定 > テーマ
## 设置主题
设置 > 主题
## テーマを作成する
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。
## 创建主题
主题代码是一个由 JSON5 编写和构成的对象。下面是一个主题对象,它看起来像是这样:
``` js
{
id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -31,38 +31,40 @@
},
}
```
* `id` ... テーマの一意なID。UUIDをおすすめします
* `name` ... テーマ名
* `author` ... テーマの作者
* `desc` ... テーマの説明(オプション)
* `base` ... 明るいテーマか、暗いテーマか
* `light`にすると明るいテーマになり、`dark`にすると暗いテーマになります
* テーマはここで設定されたベーステーマを継承します
* `props` ... テーマのスタイル定義。これから説明します
* `id` ... 该主题的唯一 ID推荐采用 UUID
* `name` ... 主题名称
* `author` ... 主题作者
* `desc` ... 主题的描述说明(可选)
* `base` ... 浅色主题还是深色主题
* `light` 为浅色主题,`dark` 为深色主题
* 该主题将继承使用的基础主题集
* `props` ... 关于主题样式的定义,下面是详细介绍
### テーマのスタイル定義
`props`下にはテーマのスタイルを定義します。 キーがCSSの変数名になり、バリューで中身を指定します。 なお、この`props`オブジェクトはベーステーマから継承されます。 ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)です。 つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます
### 主题样式定义
`props` 下,你可以定义主题的样式。 键是 CSS 变量名,值是指定的内容。 请注意,`props` 对象是从基础主题集继承的。 如果这个主题的 `base``light`,则基础主题为 [_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5);如果 `dark`,则基础主题为 [_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)。 换句话说,即使这个主题中的 `props` 中没有定义关键的 `panel`,也会继承在基础主题中所拥有 `panel`
#### バリューで使える構文
* 16進数で表された
#### 可以在值中使用的语法
* 以十六进制表示的颜
* 例: `#00ff00`
* `rgb(r, g, b)`形式で表された
* `rgb(r, g, b)` 形式表示的颜
* 例: `rgb(0, 255, 0)`
* `rgb(r, g, b, a)`形式で表された透明度を含む
* `rgb(r, g, b, a)` 形式表示的包含透明度的颜
* 例: `rgba(0, 255, 0, 0.5)`
* 他のキーの値の参照
* `@{キー名}`と書くと他のキーの値の参照になります。`{キー名}`は参照したいキーの名前に置き換えます
* 引用其他键的值
* `@{键名}` 对另一个键值的引用。请将 `{键名}` 替换为您要引用键名
* 例: `@panel`
* 定数(後述)の参照
* `${定数名}`と書くと定数の参照になります。`{定数名}`は参照したい定数の名前に置き換えます
* 参照常量(见下文)
* `${常量名}` 对一个常量进行引用。请将 `{常量名}` 替换为您要引用常量名
* 例: `$main`
* 関数(後述)
* `:{関数名}<{引数}<{色}`
* 函数(见下文)
* `:{函数名}<{参数}<{颜色}`
#### 常量
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません
“我不想把它作为一个CSS变量输出但我想把它作为另一个CSS变量的值。” 你可以使用常量。如果你的键名以`$`开头该键将不会作为CSS变量输出
#### 函数
wip

View File

@ -1,2 +1,2 @@
# 追隨
當你追隨其他使用者時,你的時間軸上將出現他們的箋文。但是,他們對其他用戶的回覆不會被顯示。 若要追隨一個使用者,請點選其使用者頁面上的「追隨」按鈕。若要解除追隨,請再次點選按鈕。
# 追隨/解除追隨
當你追隨其他使用者時,你的時間軸上將出現他們的箋文。但是,他們對其他用戶的回覆不會被顯示。 若要追隨一個使用者,請點選其使用者頁面上的「追隨」按鈕。若要解除追隨,請再次點選「追隨」按鈕。

View File

@ -1,17 +1,17 @@
# キーボードショートカット
## 全域
## 公開
これらのショートカットは基本的にどこでも使えます。
<table>
<thead>
<tr><th>快速鍵</th><th>功能</th><th>由來</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>發佈箋文</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>顯示/隱藏通知</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>搜尋</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>取得說明</td><td><b>H</b>elp</td></tr>
</tbody>
</table>
@ -30,23 +30,23 @@
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
<tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>お気に入りに登録</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr>
<tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>投稿を削除</td><td><b>D</b>elete</tr>
<tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>刪除箋文</td><td><b>D</b>elete</tr>
<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
</tbody>
</table>
## Renoteフォーム
## 轉發選單
<table>
<thead>
<tr><th>快速鍵</th><th>功能</th><th>由來</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">Enter</kbd></td><td>Renoteする</td><td>-</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>フォームを展開する</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>フォームを閉じる</td><td>-</td></tr>
<tr><td><kbd class="key">Enter</kbd></td><td>轉發箋文</td><td>-</td></tr>
<tr><td><kbd class="key">Q</kbd></td><td>展開選單</td><td><b>Q</b>uote</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>關閉選單</td><td>-</td></tr>
</tbody>
</table>

View File

@ -1,2 +1,2 @@
# MFM
MFMは、Misskey Flavored Markdownの略で、Misskeyの様々な場所で使用できる専用のマークアップ言語です。 MFMで使用可能な構文は[MFMチートシート](/mfm-cheat-sheet)で確認できます
MFM是Misskey Favored Markdown的縮寫通行於Misskey的專用標記語言。 其語法可於[MFM代碼小抄](/mfm-cheat-sheet)中找到

View File

@ -1,4 +1,4 @@
# Pages
# 頁面
## 變數
変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。

View File

@ -2,10 +2,10 @@
テーマを設定して、Misskeyクライアントの見た目を変更できます。
## テーマの設定
設定 > テーマ
## 佈景主題設定
設定 > 佈景主題
## テーマを作成する
## 製造佈景主題
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。
``` js
{
@ -31,13 +31,15 @@
},
}
```
* `id` ... テーマの一意なID。UUIDをおすすめします
* `name` ... テーマ名
* `author` ... テーマの作者
* `desc` ... テーマの説明(オプション)
* `base` ... 明るいテーマか、暗いテーマか
* `id` ... 佈景主題唯一識別碼。建議使用UUID
* `name` ... 佈景主題名稱
* `author` ... 佈景主題作者
* `desc` ... 佈景主題説明(可選)
* `base` ... 採用淺色底色/深色底色
* `light`にすると明るいテーマになり、`dark`にすると暗いテーマになります。
* テーマはここで設定されたベーステーマを継承します。
* `props` ... テーマのスタイル定義。これから説明します。
@ -46,11 +48,11 @@
`props`下にはテーマのスタイルを定義します。 キーがCSSの変数名になり、バリューで中身を指定します。 なお、この`props`オブジェクトはベーステーマから継承されます。 ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/syuilo/misskey/blob/develop/src/client/themes/_dark.json5)です。 つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます。
#### バリューで使える構文
* 16進数で表された色
* 以十六進位色碼標示
* 例: `#00ff00`
* `rgb(r, g, b)`形式で表された
* 以`rgb(r, g, b)`形式標示RGB顏
* 例: `rgb(0, 255, 0)`
* `rgb(r, g, b, a)`形式で表された透明度を含む
* 以`rgb(r, g, b, a)`形式標示RGBA顏
* 例: `rgba(0, 255, 0, 0.5)`
* 他のキーの値の参照
* `@{キー名}`と書くと他のキーの値の参照になります。`{キー名}`は参照したいキーの名前に置き換えます。
@ -58,8 +60,8 @@
* 定数(後述)の参照
* `${定数名}`と書くと定数の参照になります。`{定数名}`は参照したい定数の名前に置き換えます。
* 例: `$main`
* 関数(後述)
* `:{関数名}<{引数}<{色}`
* 函數(見下文)
* `:{函數名稱}<{參數}<{顏色}`
#### 常數
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。

View File

@ -11,5 +11,5 @@ https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOBy
## 社群
自分のフォローしているユーザーの投稿と、全てのローカルユーザーの「ホーム」指定されていない投稿
## 全域
## 公開
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿

View File

@ -0,0 +1,58 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user';
import { id } from '../id';
// TODO: 同じdomain、同じscope、同じkeyのレコードは二つ以上存在しないように制約付けたい
@Entity()
export class RegistryItem {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the RegistryItem.'
})
public createdAt: Date;
@Column('timestamp with time zone', {
comment: 'The updated date of the RegistryItem.'
})
public updatedAt: Date;
@Index()
@Column({
...id(),
comment: 'The owner ID.'
})
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE'
})
@JoinColumn()
public user: User | null;
@Column('varchar', {
length: 1024,
comment: 'The key of the RegistryItem.'
})
public key: string;
@Column('jsonb', {
default: {}, nullable: true,
comment: 'The value of the RegistryItem.'
})
public value: any | null;
@Index()
@Column('varchar', {
length: 1024, array: true, default: '{}'
})
public scope: string[];
// サードパーティアプリに開放するときのためのカラム
@Index()
@Column('varchar', {
length: 512, nullable: true
})
public domain: string | null;
}

View File

@ -94,6 +94,7 @@ export class UserProfile {
})
public password: string | null;
// TODO: そのうち消す
@Column('jsonb', {
default: {},
comment: 'The client-specific data of the User.'

View File

@ -57,6 +57,7 @@ import { ChannelRepository } from './repositories/channel';
import { MutedNote } from './entities/muted-note';
import { ChannelFollowing } from './entities/channel-following';
import { ChannelNotePining } from './entities/channel-note-pining';
import { RegistryItem } from './entities/registry-item';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
@ -116,3 +117,4 @@ export const MutedNotes = getRepository(MutedNote);
export const Channels = getCustomRepository(ChannelRepository);
export const ChannelFollowings = getRepository(ChannelFollowing);
export const ChannelNotePinings = getRepository(ChannelNotePining);
export const RegistryItems = getRepository(RegistryItem);

View File

@ -261,7 +261,6 @@ export class UserRepository extends Repository<User> {
} : {}),
...(opts.includeSecrets ? {
clientData: profile!.clientData,
email: profile!.email,
emailVerified: profile!.emailVerified,
securityKeysList: profile!.twoFactorEnabled

View File

@ -11,7 +11,7 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => {
const reply = (x?: any, y?: ApiError) => {
if (x == null) {
ctx.status = 204;
} else if (typeof x === 'number') {
} else if (typeof x === 'number' && y) {
ctx.status = x;
ctx.body = {
error: {
@ -23,7 +23,8 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => {
}
};
} else {
ctx.body = x;
// 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
ctx.body = typeof x === 'string' ? JSON.stringify(x) : x;
}
res();
};

View File

@ -1,5 +1,7 @@
import define from '../define';
import { Users } from '../../../models';
import { RegistryItems, UserProfiles, Users } from '../../../models';
import { ensure } from '../../../prelude/ensure';
import { genId } from '../../../misc/gen-id';
export const meta = {
desc: {
@ -22,6 +24,27 @@ export const meta = {
export default define(meta, async (ps, user, token) => {
const isSecure = token == null;
// TODO: そのうち消す
const profile = await UserProfiles.findOne(user.id).then(ensure);
for (const [k, v] of Object.entries(profile.clientData)) {
await RegistryItems.insert({
id: genId(),
createdAt: new Date(),
updatedAt: new Date(),
userId: user.id,
domain: null,
scope: ['client', 'base'],
key: k,
value: v
});
}
await UserProfiles.createQueryBuilder().update()
.set({
clientData: {},
})
.where('userId = :id', { id: user.id })
.execute();
return await Users.pack(user, user, {
detail: true,
includeSecrets: isSecure

View File

@ -80,7 +80,7 @@ export default define(meta, async (ps, user) => {
.where('muting.muterId = :muterId', { muterId: user.id });
const suspendedQuery = Users.createQueryBuilder('users')
.select('id')
.select('users.id')
.where('users.isSuspended = TRUE');
const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId)

View File

@ -0,0 +1,33 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
}
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.scope = :scope', { scope: ps.scope });
const items = await query.getMany();
const res = {} as Record<string, any>;
for (const item of items) {
res[item.key] = item.value;
}
return res;
});

View File

@ -0,0 +1,48 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
import { ApiError } from '../../../error';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
key: {
validator: $.str
},
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
},
errors: {
noSuchKey: {
message: 'No such key.',
code: 'NO_SUCH_KEY',
id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a'
},
},
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.key = :key', { key: ps.key })
.andWhere('item.scope = :scope', { scope: ps.scope });
const item = await query.getOne();
if (item == null) {
throw new ApiError(meta.errors.noSuchKey);
}
return {
updatedAt: item.updatedAt,
value: item.value,
};
});

View File

@ -0,0 +1,45 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
import { ApiError } from '../../../error';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
key: {
validator: $.str
},
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
},
errors: {
noSuchKey: {
message: 'No such key.',
code: 'NO_SUCH_KEY',
id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a'
},
},
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.key = :key', { key: ps.key })
.andWhere('item.scope = :scope', { scope: ps.scope });
const item = await query.getOne();
if (item == null) {
throw new ApiError(meta.errors.noSuchKey);
}
return item.value;
});

View File

@ -0,0 +1,41 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
}
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.scope = :scope', { scope: ps.scope });
const items = await query.getMany();
const res = {} as Record<string, string>;
for (const item of items) {
const type = typeof item.value;
res[item.key] =
item.value === null ? 'null' :
Array.isArray(item.value) ? 'array' :
type === 'number' ? 'number' :
type === 'string' ? 'string' :
type === 'boolean' ? 'boolean' :
type === 'object' ? 'object' :
null as never;
}
return res;
});

View File

@ -0,0 +1,28 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
}
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.select('item.key')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.scope = :scope', { scope: ps.scope });
const items = await query.getMany();
return items.map(x => x.key);
});

View File

@ -0,0 +1,45 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
import { ApiError } from '../../../error';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
key: {
validator: $.str
},
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
},
errors: {
noSuchKey: {
message: 'No such key.',
code: 'NO_SUCH_KEY',
id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019'
},
},
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.key = :key', { key: ps.key })
.andWhere('item.scope = :scope', { scope: ps.scope });
const item = await query.getOne();
if (item == null) {
throw new ApiError(meta.errors.noSuchKey);
}
await RegistryItems.remove(item);
});

View File

@ -0,0 +1,30 @@
import $ from 'cafy';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
}
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.select('item.scope')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id });
const items = await query.getMany();
const res = [] as string[][];
for (const item of items) {
if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue;
res.push(item.scope);
}
return res;
});

View File

@ -0,0 +1,61 @@
import $ from 'cafy';
import { publishMainStream } from '../../../../../services/stream';
import define from '../../../define';
import { RegistryItems } from '../../../../../models';
import { genId } from '../../../../../misc/gen-id';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
key: {
validator: $.str.min(1)
},
value: {
validator: $.nullable.any
},
scope: {
validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)),
default: [],
},
}
};
export default define(meta, async (ps, user) => {
const query = RegistryItems.createQueryBuilder('item')
.where('item.domain IS NULL')
.andWhere('item.userId = :userId', { userId: user.id })
.andWhere('item.key = :key', { key: ps.key })
.andWhere('item.scope = :scope', { scope: ps.scope });
const existingItem = await query.getOne();
if (existingItem) {
await RegistryItems.update(existingItem.id, {
updatedAt: new Date(),
value: ps.value
});
} else {
await RegistryItems.insert({
id: genId(),
createdAt: new Date(),
updatedAt: new Date(),
userId: user.id,
domain: null,
scope: ps.scope,
key: ps.key,
value: ps.value
});
}
// TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
publishMainStream(user.id, 'registryUpdated', {
scope: ps.scope,
key: ps.key,
value: ps.value
});
});

View File

@ -1,40 +0,0 @@
import $ from 'cafy';
import { publishMainStream } from '../../../../services/stream';
import define from '../../define';
import { UserProfiles } from '../../../../models';
import { ensure } from '../../../../prelude/ensure';
export const meta = {
requireCredential: true as const,
secure: true,
params: {
name: {
validator: $.str.match(/^[a-zA-Z]+$/)
},
value: {
validator: $.nullable.any
}
}
};
export default define(meta, async (ps, user) => {
const profile = await UserProfiles.findOne(user.id).then(ensure);
await UserProfiles.createQueryBuilder().update()
.set({
clientData: Object.assign(profile.clientData, {
[ps.name]: ps.value
}),
})
.where('userId = :id', { id: user.id })
.execute();
// Publish event
publishMainStream(user.id, 'clientSettingUpdated', {
key: ps.name,
value: ps.value
});
});

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