Merge branch 'develop' into fetch

This commit is contained in:
tamaina 2023-01-07 10:05:09 +00:00
commit 64f749b2c5
279 changed files with 5484 additions and 2428 deletions

View File

@ -11,19 +11,34 @@ You should also include the user name that made the change.
## 13.0.0 (unreleased) ## 13.0.0 (unreleased)
### TL;DR
- New features (Play, new widgets, new charts, etc)
- Rewriten backend
- Better performance (backend and frontend)
- Various usability improvements
- Various UI tweaks
### Changes ### Changes
#### For server admins
- Node.js 18.x or later is required - Node.js 18.x or later is required
- PostgreSQL 15.x is required
- Misskey not using 15 specific features at 13.0.0, but may do so in the future.
- Elasticsearchのサポートが削除されました - Elasticsearchのサポートが削除されました
- 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます - 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます
- ノートのウォッチ機能が削除されました
- Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator - Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator
- You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic - You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic
#### For users
- ノートのウォッチ機能が削除されました
- 新たに動的なPagesを作ることはできなくなりました - 新たに動的なPagesを作ることはできなくなりました
- 代わりに今後AiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能の実装を予定しています。 - 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
- AiScriptが0.12.0にアップデートされました - AiScriptが0.12.2にアップデートされました
- 0.12.0の変更点についてはこちら https://github.com/syuilo/aiscript/blob/master/CHANGELOG.md#0120 - 0.12.xの変更点についてはこちら https://github.com/syuilo/aiscript/blob/master/CHANGELOG.md#0120
- 0.12.0未満のプラグインは読み込むことはできません - 0.12.x未満のプラグインは読み込むことはできません
- iOS15以下のデバイスはサポートされなくなりました - iOS15以下のデバイスはサポートされなくなりました
- Firefox109以下はサポートされなくなりました
#### For app developers
- API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。 - 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
- e.g. `https://p1.a9z.dev/emoji/misskey.webp` - e.g. `https://p1.a9z.dev/emoji/misskey.webp`
@ -33,12 +48,13 @@ You should also include the user name that made the change.
- API: `instance`エンティティに`latestStatus`、`lastCommunicatedAt`、`latestRequestSentAt`プロパティが含まれなくなりました - API: `instance`エンティティに`latestStatus`、`lastCommunicatedAt`、`latestRequestSentAt`プロパティが含まれなくなりました
### Improvements ### Improvements
- Push notification of Antenna note @tamaina - Misskey Play @syuilo
- AVIF support @tamaina
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
- Introduce retention-rate aggregation @syuilo - Introduce retention-rate aggregation @syuilo
- Make possible to export favorited notes @syuilo - Make possible to export favorited notes @syuilo
- Add per user pv chart @syuilo - Add per user pv chart @syuilo
- Push notification of Antenna note @tamaina
- AVIF support @tamaina
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
- Server: signToActivityPubGet is set to true by default @syuilo - Server: signToActivityPubGet is set to true by default @syuilo
- Server: improve syslog performance @syuilo - Server: improve syslog performance @syuilo
- Server: improve note scoring for featured notes @CyberRex0 - Server: improve note scoring for featured notes @CyberRex0
@ -47,6 +63,7 @@ You should also include the user name that made the change.
- Server: delete outdated notes of antenna regularly to improve db performance @syuilo - Server: delete outdated notes of antenna regularly to improve db performance @syuilo
- Server: improve activitypub deliver performance @syuilo - Server: improve activitypub deliver performance @syuilo
- Client: use tabler-icons instead of fontawesome to better design @syuilo - Client: use tabler-icons instead of fontawesome to better design @syuilo
- Client: Add AiScript App widget
- Client: Add new gabber kick sounds (thanks for noizenecio) - Client: Add new gabber kick sounds (thanks for noizenecio)
- Client: Add link to user RSS feed in profile menu @ssmucny - Client: Add link to user RSS feed in profile menu @ssmucny
- Client: Compress non-animated PNG files @saschanaz - Client: Compress non-animated PNG files @saschanaz
@ -57,11 +74,13 @@ You should also include the user name that made the change.
- Client: Make widgets of universal/classic sync between devices @tamaina - Client: Make widgets of universal/classic sync between devices @tamaina
- Client: Implement the button to subscribe push notification @tamaina - Client: Implement the button to subscribe push notification @tamaina
- Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina - Client: Implement the toggle to or not to close push notifications when notifications or messages are read @tamaina
- Client: Improve RSS widget @tamaina
- Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz - Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
- Client: OpenSearch support @SoniEx2 @chaoticryptidz - Client: OpenSearch support @SoniEx2 @chaoticryptidz
- Client: add user list widget @syuilo - Client: add user list widget @syuilo
- Client: add heatmap of daily active users to about page @syuilo - Client: add heatmap of daily active users to about page @syuilo
- Client: introduce fluent emoji @syuilo - Client: introduce fluent emoji @syuilo
- Client: show fireworks when visit user who today is birthday @syuilo
- Client: show bot warning on screen when logged in as bot account @syuilo - Client: show bot warning on screen when logged in as bot account @syuilo
- Client: improve overall performance of client @syuilo - Client: improve overall performance of client @syuilo
- Client: ui tweaks @syuilo - Client: ui tweaks @syuilo
@ -73,10 +92,13 @@ You should also include the user name that made the change.
- Server: trim long text of note from ap @syuilo - Server: trim long text of note from ap @syuilo
- Server: Ap inboxの最大ペイロードサイズを64kbに制限 @syuilo - Server: Ap inboxの最大ペイロードサイズを64kbに制限 @syuilo
- Server: アンテナの作成数上限を追加 @syuilo - Server: アンテナの作成数上限を追加 @syuilo
- Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
- Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
- Client: case insensitive emoji search @saschanaz - Client: case insensitive emoji search @saschanaz
- Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
- Client: use proxied image for instance icon @syuilo - Client: use proxied image for instance icon @syuilo
- Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa - Client: Webhookの編集画面で、内容を保存することができない問題を修正 @m-hayabusa
- Client: Page編集でブロックの移動が行えない問題を修正 @syuilo
- Client: update emoji picker immediately on all input @saschanaz - Client: update emoji picker immediately on all input @saschanaz
- Client: チャートのツールチップが画面に残ることがあるのを修正 @syuilo - Client: チャートのツールチップが画面に残ることがあるのを修正 @syuilo
- Client: fix wrong link in tutorial @syuilo - Client: fix wrong link in tutorial @syuilo

View File

@ -164,7 +164,6 @@ annotation: "التعليقات"
federation: "الفديرالية" federation: "الفديرالية"
instances: "مثيل الخادم" instances: "مثيل الخادم"
registeredAt: "مسجل منذ" registeredAt: "مسجل منذ"
latestRequestSentAt: "آخر طلب أرسِل في"
latestRequestReceivedAt: "آخر طلب تُلقي في" latestRequestReceivedAt: "آخر طلب تُلقي في"
latestStatus: "الحالات الأخيرة" latestStatus: "الحالات الأخيرة"
storageUsage: "مساحة التخزين المستخدمة" storageUsage: "مساحة التخزين المستخدمة"
@ -381,6 +380,7 @@ administrator: "المدير"
token: "الرمز المميز" token: "الرمز المميز"
twoStepAuthentication: "الإستيثاق بعاملَيْن" twoStepAuthentication: "الإستيثاق بعاملَيْن"
moderator: "مشرِف" moderator: "مشرِف"
moderation: "الإشراف"
nUsersMentioned: "{n} مستخدمين أُشير إليهم" nUsersMentioned: "{n} مستخدمين أُشير إليهم"
securityKey: "مفتاح الأمان" securityKey: "مفتاح الأمان"
securityKeyName: "اسم المفتاح" securityKeyName: "اسم المفتاح"
@ -814,6 +814,9 @@ colored: "ملوّن"
label: "التسمية" label: "التسمية"
localOnly: "المحلي فقط" localOnly: "المحلي فقط"
account: "الحسابات" account: "الحسابات"
cannotLoad: "تعذر التحميل"
like: "أعجبني"
show: "المظهر"
_emailUnavailable: _emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم" used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة" format: "صيغة البريد الإلكتروني غير صالحة"
@ -1229,6 +1232,11 @@ _timelines:
local: "المحلي" local: "المحلي"
social: "الاجتماعي" social: "الاجتماعي"
global: "الشامل" global: "الشامل"
_play:
viewSource: "اظهر المصدر"
featured: "الأكثر شعبية"
title: "العنوان"
summary: "الوصف"
_pages: _pages:
newPage: "أنشئ صفحة جديدة" newPage: "أنشئ صفحة جديدة"
editPage: "عدّل الصفحة" editPage: "عدّل الصفحة"
@ -1294,6 +1302,7 @@ _notification:
yourFollowRequestAccepted: "قُبل طلب المتابعة" yourFollowRequestAccepted: "قُبل طلب المتابعة"
youWereInvitedToGroup: "دُعيت إلى فريقٍ" youWereInvitedToGroup: "دُعيت إلى فريقٍ"
pollEnded: "ظهرت نتائج الاستطلاع" pollEnded: "ظهرت نتائج الاستطلاع"
unreadAntennaNote: "هوائي {name}"
_types: _types:
all: "الكل" all: "الكل"
follow: "متابِعون جدد" follow: "متابِعون جدد"

View File

@ -164,7 +164,6 @@ annotation: "মন্তব্য"
federation: "ফেডিভার্স" federation: "ফেডিভার্স"
instances: "ইন্সট্যান্স" instances: "ইন্সট্যান্স"
registeredAt: "যোগ দিয়েছেন" registeredAt: "যোগ দিয়েছেন"
latestRequestSentAt: "শেষ রিকুয়েস্ট পাঠানো হয়েছে"
latestRequestReceivedAt: "শেষ রিকুয়েস্ট গৃহীত হয়েছে" latestRequestReceivedAt: "শেষ রিকুয়েস্ট গৃহীত হয়েছে"
latestStatus: "সর্বশেষ অবস্থা" latestStatus: "সর্বশেষ অবস্থা"
storageUsage: "স্টোরেজের ব্যাবহার" storageUsage: "স্টোরেজের ব্যাবহার"
@ -852,6 +851,8 @@ colored: "রঙ্গিন"
label: "লেবেল" label: "লেবেল"
localOnly: "শুধুমাত্র লোকাল" localOnly: "শুধুমাত্র লোকাল"
account: "অ্যাকাউন্টগুলি" account: "অ্যাকাউন্টগুলি"
like: "পছন্দ করা"
show: "প্রদর্শন"
_emailUnavailable: _emailUnavailable:
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
@ -1320,6 +1321,12 @@ _timelines:
local: "স্থানীয়" local: "স্থানীয়"
social: "সামাজিক" social: "সামাজিক"
global: "গ্লোবাল" global: "গ্লোবাল"
_play:
viewSource: "উৎস দেখুন"
featured: "জনপ্রিয়"
title: "শিরোনাম"
script: "স্ক্রিপ্ট"
summary: "বর্ণনা"
_pages: _pages:
newPage: "নতুন পৃষ্ঠা বানান" newPage: "নতুন পৃষ্ঠা বানান"
editPage: "পৃষ্ঠাটি সম্পাদনা করুন" editPage: "পৃষ্ঠাটি সম্পাদনা করুন"

View File

@ -165,7 +165,6 @@ annotation: "Comentaris"
federation: "Federació" federation: "Federació"
instances: "Servidors" instances: "Servidors"
registeredAt: "Registrat a" registeredAt: "Registrat a"
latestRequestSentAt: "Darrera petició enviada"
latestRequestReceivedAt: "Última petició rebuda" latestRequestReceivedAt: "Última petició rebuda"
latestStatus: "Últim estat" latestStatus: "Últim estat"
storageUsage: "Emmagatzematge utilitzat" storageUsage: "Emmagatzematge utilitzat"

View File

@ -161,7 +161,6 @@ annotation: "Komentáře"
federation: "Federace" federation: "Federace"
instances: "Instance" instances: "Instance"
registeredAt: "Registrován" registeredAt: "Registrován"
latestRequestSentAt: "Poslední požadavek poslán"
latestRequestReceivedAt: "Poslední požadavek přijat" latestRequestReceivedAt: "Poslední požadavek přijat"
latestStatus: "Poslední status" latestStatus: "Poslední status"
storageUsage: "Využití úložiště" storageUsage: "Využití úložiště"
@ -611,6 +610,7 @@ speed: "Rychlost"
slow: "Pomalá" slow: "Pomalá"
fast: "Rychlá" fast: "Rychlá"
account: "Účty" account: "Účty"
show: "Zobrazit"
_ad: _ad:
back: "Zpět" back: "Zpět"
_gallery: _gallery:
@ -749,6 +749,9 @@ _charts:
_timelines: _timelines:
home: "Domů" home: "Domů"
global: "Globální" global: "Globální"
_play:
script: "Skript"
summary: "Popis"
_pages: _pages:
newPage: "Vytvořit novou stránku" newPage: "Vytvořit novou stránku"
editPage: "Upravit stránku" editPage: "Upravit stránku"

View File

@ -167,7 +167,6 @@ annotation: "Anmerkung"
federation: "Föderation" federation: "Föderation"
instances: "Instanzen" instances: "Instanzen"
registeredAt: "Registriert am" registeredAt: "Registriert am"
latestRequestSentAt: "Letzte Anfrage gesendet"
latestRequestReceivedAt: "Letzte Anfrage erhalten" latestRequestReceivedAt: "Letzte Anfrage erhalten"
latestStatus: "Neuster Status" latestStatus: "Neuster Status"
storageUsage: "Verbrauchter Speicherplatz" storageUsage: "Verbrauchter Speicherplatz"
@ -610,7 +609,7 @@ regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltun
instanceMute: "Instanzstummschaltungen" instanceMute: "Instanzstummschaltungen"
userSaysSomething: "{name} hat etwas gesagt" userSaysSomething: "{name} hat etwas gesagt"
makeActive: "Aktivieren" makeActive: "Aktivieren"
display: "Anzeigeart" display: "Anzeigen"
copy: "Kopieren" copy: "Kopieren"
metrics: "Metriken" metrics: "Metriken"
overview: "Übersicht" overview: "Übersicht"
@ -916,6 +915,11 @@ caption: "Beschreibung"
loggedInAsBot: "Momentan als Bot angemeldet" loggedInAsBot: "Momentan als Bot angemeldet"
tools: "Werkzeuge" tools: "Werkzeuge"
cannotLoad: "Kann nicht geladen werden" cannotLoad: "Kann nicht geladen werden"
numberOfProfileView: "Profilaufrufe"
like: "Gefällt mir"
unlike: "\"Gefällt mir\" entfernen"
numberOfLikes: "\"Gefällt mir\"-Anzahl"
show: "Anzeigen"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht." description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
sensitivity: "Erkennungssensitivität" sensitivity: "Erkennungssensitivität"
@ -1315,6 +1319,7 @@ _widgets:
jobQueue: "Job-Warteschlange" jobQueue: "Job-Warteschlange"
serverMetric: "Servermetriken" serverMetric: "Servermetriken"
aiscript: "AiScript-Konsole" aiscript: "AiScript-Konsole"
aiscriptApp: "AiScript-Anwendung"
aichan: "Ai" aichan: "Ai"
userList: "Benutzerliste" userList: "Benutzerliste"
_userList: _userList:
@ -1420,6 +1425,21 @@ _timelines:
local: "Lokal" local: "Lokal"
social: "Sozial" social: "Sozial"
global: "Global" global: "Global"
_play:
new: "Play erstellen"
edit: "Play bearbeiten"
created: "Play erfolgreich erstellt"
updated: "Play erfolgreich aktualisiert"
deleted: "Play erfolgreich gelöscht"
pageSetting: "Play-Einstellungen"
editThisPage: "Dieses Play bearbeiten"
viewSource: "Quelltext anzeigen"
my: "Meine Plays"
liked: "Mit \"Gefällt mir\" markierte Plays"
featured: "Beliebt"
title: "Titel"
script: "Skript"
summary: "Beschreibung"
_pages: _pages:
newPage: "Seite erstellen" newPage: "Seite erstellen"
editPage: "Seite bearbeiten" editPage: "Seite bearbeiten"

View File

@ -167,7 +167,6 @@ annotation: "Comments"
federation: "Federation" federation: "Federation"
instances: "Instances" instances: "Instances"
registeredAt: "Registered at" registeredAt: "Registered at"
latestRequestSentAt: "Last request sent"
latestRequestReceivedAt: "Last request received" latestRequestReceivedAt: "Last request received"
latestStatus: "Latest status" latestStatus: "Latest status"
storageUsage: "Storage usage" storageUsage: "Storage usage"
@ -916,6 +915,11 @@ caption: "Caption"
loggedInAsBot: "Currently logged in as bot" loggedInAsBot: "Currently logged in as bot"
tools: "Tools" tools: "Tools"
cannotLoad: "Unable to load" cannotLoad: "Unable to load"
numberOfProfileView: "Profile views"
like: "Like"
unlike: "Unlike"
numberOfLikes: "Likes"
show: "Show"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server." description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
sensitivity: "Detection sensitivity" sensitivity: "Detection sensitivity"
@ -1315,6 +1319,7 @@ _widgets:
jobQueue: "Job Queue" jobQueue: "Job Queue"
serverMetric: "Server metrics" serverMetric: "Server metrics"
aiscript: "AiScript console" aiscript: "AiScript console"
aiscriptApp: "AiScript App"
aichan: "Ai" aichan: "Ai"
userList: "User list" userList: "User list"
_userList: _userList:
@ -1420,6 +1425,21 @@ _timelines:
local: "Local" local: "Local"
social: "Social" social: "Social"
global: "Global" global: "Global"
_play:
new: "Create Play"
edit: "Edit Play"
created: "Play created"
updated: "Play edited"
deleted: "Play deleted"
pageSetting: "Play settings"
editThisPage: "Edit this Play"
viewSource: "View source"
my: "My Plays"
liked: "Liked Plays"
featured: "Popular"
title: "Title"
script: "Script"
summary: "Description"
_pages: _pages:
newPage: "Create a new Page" newPage: "Create a new Page"
editPage: "Edit this Page" editPage: "Edit this Page"

View File

@ -13,6 +13,7 @@ fetchingAsApObject: "Buscando en el fediverso"
ok: "OK" ok: "OK"
gotIt: "¡Lo tengo!" gotIt: "¡Lo tengo!"
cancel: "Cancelar" cancel: "Cancelar"
noThankYou: "No gracias"
enterUsername: "Introduce el nombre de usuario" enterUsername: "Introduce el nombre de usuario"
renotedBy: "Renotado por {user}" renotedBy: "Renotado por {user}"
noNotes: "No hay notas" noNotes: "No hay notas"
@ -48,6 +49,7 @@ deleteAndEdit: "Borrar y editar"
deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas." deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas."
addToList: "Agregar a lista" addToList: "Agregar a lista"
sendMessage: "Enviar un mensaje" sendMessage: "Enviar un mensaje"
copyRSS: "Copiar RSS"
copyUsername: "Copiar nombre de usuario" copyUsername: "Copiar nombre de usuario"
searchUser: "Buscar un usuario" searchUser: "Buscar un usuario"
reply: "Responder" reply: "Responder"
@ -165,7 +167,6 @@ annotation: "Anotación"
federation: "Federación" federation: "Federación"
instances: "Instancia" instances: "Instancia"
registeredAt: "Registrado en" registeredAt: "Registrado en"
latestRequestSentAt: "Ultimo pedido enviado"
latestRequestReceivedAt: "Ultimo pedido recibido" latestRequestReceivedAt: "Ultimo pedido recibido"
latestStatus: "Último status" latestStatus: "Último status"
storageUsage: "Almacenamiento usado" storageUsage: "Almacenamiento usado"
@ -455,6 +456,8 @@ language: "Idioma"
uiLanguage: "Idioma de visualización de la interfaz" uiLanguage: "Idioma de visualización de la interfaz"
groupInvited: "Invitado al grupo" groupInvited: "Invitado al grupo"
aboutX: "Acerca de {x}" aboutX: "Acerca de {x}"
emojiStyle: "Estilo de emoji"
native: "Nativo"
disableDrawer: "No mostrar los menús en cajones" disableDrawer: "No mostrar los menús en cajones"
youHaveNoGroups: "Sin grupos" youHaveNoGroups: "Sin grupos"
joinOrCreateGroup: "Obtenga una invitación para unirse al grupos o puede crear su propio grupo." joinOrCreateGroup: "Obtenga una invitación para unirse al grupos o puede crear su propio grupo."
@ -713,6 +716,7 @@ accentColor: "Acento"
textColor: "Texto" textColor: "Texto"
saveAs: "Guardar como…" saveAs: "Guardar como…"
advanced: "Avanzado" advanced: "Avanzado"
advancedSettings: "Configuración avanzada"
value: "Valores" value: "Valores"
createdAt: "Fecha de creación" createdAt: "Fecha de creación"
updatedAt: "Actualizado" updatedAt: "Actualizado"
@ -898,6 +902,22 @@ navbar: "Barra de navegación"
shuffle: "Aleatorio" shuffle: "Aleatorio"
account: "Cuentas" account: "Cuentas"
move: "Mover" move: "Mover"
pushNotification: "Alerta emergente"
subscribePushNotification: "Activar las notificaciones emergentes"
unsubscribePushNotification: "Desactivar las notificaciones emergentes"
pushNotificationAlreadySubscribed: "Notificaciones emergentes ya activadas"
pushNotificationNotSupported: "El navegador o la instancia no admiten notificaciones push"
sendPushNotificationReadMessage: "Eliminar las notificaciones push después de leer las notificaciones y los mensajes"
sendPushNotificationReadMessageCaption: "La notificación \"{emptyPushNotificationMessage}\" aparecerá momentáneamente. Esto puede aumentar el consumo de batería del dispositivo."
windowMaximize: "Maximizar"
windowRestore: "Regresar"
caption: "Pie de foto"
loggedInAsBot: "Inicio sesión como cuenta bot."
tools: "Utilidades"
cannotLoad: "No se puede cargar."
numberOfProfileView: "Número de vistas de perfil"
like: "¡Muy bien!"
show: "Apariencia"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduce el esfuerzo de la moderación el el servidor a través del reconocimiento automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar ligeramente la carga en el servidor." description: "Reduce el esfuerzo de la moderación el el servidor a través del reconocimiento automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar ligeramente la carga en el servidor."
sensitivity: "Sensibilidad de detección" sensitivity: "Sensibilidad de detección"
@ -1208,6 +1228,9 @@ _tutorial:
step7_1: "Así terminó la explicación del funcionamiento básico de Misskey. Eso fue todo." step7_1: "Así terminó la explicación del funcionamiento básico de Misskey. Eso fue todo."
step7_2: "Si quieres conocer más sobre Misskey, prueba con la sección {help}." step7_2: "Si quieres conocer más sobre Misskey, prueba con la sección {help}."
step7_3: "Así, disfruta de Misskey 🚀" step7_3: "Así, disfruta de Misskey 🚀"
step8_1: "Por último, ¿por qué no activar las notificaciones emergentes?"
step8_2: "Al recibir notificaciones emergentes, estarás al tanto de reacciones, seguimientos y menciones incluso cuando Misskey no esté abierto."
step8_3: "La configuración de las notificaciones puede modificarse posteriormente."
_2fa: _2fa:
alreadyRegistered: "Ya has completado la configuración." alreadyRegistered: "Ya has completado la configuración."
registerDevice: "Registrar dispositivo" registerDevice: "Registrar dispositivo"
@ -1295,6 +1318,7 @@ _widgets:
serverMetric: "Estadísticas del servidor" serverMetric: "Estadísticas del servidor"
aiscript: "Consola de AiScript" aiscript: "Consola de AiScript"
aichan: "indigo" aichan: "indigo"
userList: "Lista de usuarios"
_userList: _userList:
chooseList: "Seleccione una lista" chooseList: "Seleccione una lista"
_cw: _cw:
@ -1360,6 +1384,7 @@ _profile:
changeBanner: "Cambiar banner" changeBanner: "Cambiar banner"
_exportOrImport: _exportOrImport:
allNotes: "Todas las notas" allNotes: "Todas las notas"
favoritedNotes: "Notas favoritas"
followingList: "Siguiendo" followingList: "Siguiendo"
muteList: "Silenciados" muteList: "Silenciados"
blockingList: "Bloqueados" blockingList: "Bloqueados"
@ -1397,6 +1422,12 @@ _timelines:
local: "Local" local: "Local"
social: "Social" social: "Social"
global: "Global" global: "Global"
_play:
viewSource: "Ver la fuente"
featured: "Popular"
title: "Título"
script: "Script"
summary: "Descripción"
_pages: _pages:
newPage: "Crear página" newPage: "Crear página"
editPage: "Editar página" editPage: "Editar página"
@ -1464,6 +1495,7 @@ _notification:
yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada" yourFollowRequestAccepted: "Tu solicitud de seguimiento fue aceptada"
youWereInvitedToGroup: "Invitado al grupo" youWereInvitedToGroup: "Invitado al grupo"
pollEnded: "Estan disponibles los resultados de la encuesta" pollEnded: "Estan disponibles los resultados de la encuesta"
unreadAntennaNote: "Antena {name}"
emptyPushNotificationMessage: "Se han actualizado las notificaciones push" emptyPushNotificationMessage: "Se han actualizado las notificaciones push"
_types: _types:
all: "Todo" all: "Todo"

View File

@ -2,6 +2,7 @@
_lang_: "Français" _lang_: "Français"
headlineMisskey: "Réseau relié par des notes" 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 🚀" 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 🚀"
poweredByMisskeyDescription: "{nom} est l'un des services propulsés par la plateforme ouverte <b>Misskey</b> (appelée \"instance Misskey\")."
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Rechercher" search: "Rechercher"
notifications: "Notifications" notifications: "Notifications"
@ -12,6 +13,7 @@ fetchingAsApObject: "Récupération depuis le fédiverse …"
ok: "OK" ok: "OK"
gotIt: "Jai compris !" gotIt: "Jai compris !"
cancel: "Annuler" cancel: "Annuler"
noThankYou: "Pas maintenant"
enterUsername: "Entrer un nom dutilisateur·rice" enterUsername: "Entrer un nom dutilisateur·rice"
renotedBy: "Renoté par {user}" renotedBy: "Renoté par {user}"
noNotes: "Aucune note" noNotes: "Aucune note"
@ -47,6 +49,7 @@ deleteAndEdit: "Supprimer et réécrire"
deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler ? Vous perdrez toutes les réactions, renotes et réponses y afférentes." deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler ? Vous perdrez toutes les réactions, renotes et réponses y afférentes."
addToList: "Ajouter à une liste" addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message" sendMessage: "Envoyer un message"
copyRSS: "Copier le RSS"
copyUsername: "Copier le nom dutilisateur·rice" copyUsername: "Copier le nom dutilisateur·rice"
searchUser: "Chercher un·e utilisateur·rice" searchUser: "Chercher un·e utilisateur·rice"
reply: "Répondre" reply: "Répondre"
@ -143,6 +146,7 @@ flagAsBotDescription: "Si ce compte est géré de manière automatisée, choisis
flagAsCat: "Ce compte est un chat" flagAsCat: "Ce compte est un chat"
flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte." flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte."
flagShowTimelineReplies: "Afficher les réponses dans le fil" flagShowTimelineReplies: "Afficher les réponses dans le fil"
flagShowTimelineRepliesDescription: "Affiche les réponses des utilisateurs aux notes des autres utilisateurs dans la timeline si cette option est activée."
autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s que vous suivez" autoAcceptFollowed: "Accepter automatiquement les demandes dabonnement venant dutilisateur·rice·s que vous suivez"
addAccount: "Ajouter un compte" addAccount: "Ajouter un compte"
loginFailed: "Échec de la connexion" loginFailed: "Échec de la connexion"
@ -163,7 +167,6 @@ annotation: "Commentaires"
federation: "Fédération" federation: "Fédération"
instances: "Instance" instances: "Instance"
registeredAt: "Premier contact le" registeredAt: "Premier contact le"
latestRequestSentAt: "Dernière requête envoyée"
latestRequestReceivedAt: "Dernière requête reçue" latestRequestReceivedAt: "Dernière requête reçue"
latestStatus: "Dernier statut" latestStatus: "Dernier statut"
storageUsage: "Stockage utilisé" storageUsage: "Stockage utilisé"
@ -453,6 +456,8 @@ language: "Langue"
uiLanguage: "Langue daffichage de linterface" uiLanguage: "Langue daffichage de linterface"
groupInvited: "Invité au groupe" groupInvited: "Invité au groupe"
aboutX: "À propos de {x}" aboutX: "À propos de {x}"
emojiStyle: "Style des émojis"
native: "Natif"
disableDrawer: "Les menus ne s'affichent pas dans le tiroir" disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
youHaveNoGroups: "Vous navez aucun groupe" youHaveNoGroups: "Vous navez aucun groupe"
joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou créer votre propre nouveau groupe." joinOrCreateGroup: "Vous pouvez être invité·e à rejoindre des groupes existants ou créer votre propre nouveau groupe."
@ -600,6 +605,7 @@ smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
testEmail: "Tester la distribution de courriel" testEmail: "Tester la distribution de courriel"
wordMute: "Filtre de mots" wordMute: "Filtre de mots"
regexpError: "Erreur dexpression régulière" regexpError: "Erreur dexpression régulière"
regexpErrorDescription: "Une erreur s'est produite dans l'expression régulière sur la ligne {ligne} de votre mot muet {tab} :"
instanceMute: "Instance en sourdine" instanceMute: "Instance en sourdine"
userSaysSomething: "{name} a dit quelque chose" userSaysSomething: "{name} a dit quelque chose"
makeActive: "Activer" makeActive: "Activer"
@ -708,6 +714,7 @@ accentColor: "Accentuation"
textColor: "Texte" textColor: "Texte"
saveAs: "Enregistrer sous ..." saveAs: "Enregistrer sous ..."
advanced: "Avancé" advanced: "Avancé"
advancedSettings: "Paramètres avancés"
value: "Valeur" value: "Valeur"
createdAt: "Date de création" createdAt: "Date de création"
updatedAt: "Mis à jour le" updatedAt: "Mis à jour le"
@ -852,6 +859,7 @@ rateLimitExceeded: "Limite de taux dépassée"
cropImage: "Recadrer l'image" cropImage: "Recadrer l'image"
cropImageAsk: "Voulez-vous recadrer cette image ?" cropImageAsk: "Voulez-vous recadrer cette image ?"
file: "Fichiers" file: "Fichiers"
recentNHours: "Dernières {n} heures"
noEmailServerWarning: "Serveur de courrier non configuré." noEmailServerWarning: "Serveur de courrier non configuré."
thereIsUnresolvedAbuseReportWarning: "Il ny a aucun rapport non résolu." thereIsUnresolvedAbuseReportWarning: "Il ny a aucun rapport non résolu."
recommended: "Recommandé" recommended: "Recommandé"
@ -891,6 +899,19 @@ navbar: "Barre de navigation"
shuffle: "Lecture aléatoire" shuffle: "Lecture aléatoire"
account: "Comptes" account: "Comptes"
move: "Déplacer" move: "Déplacer"
pushNotification: "Notifications push"
subscribePushNotification: "Autoriser les notifications push"
unsubscribePushNotification: "Désactiver les notifications push"
pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
windowRestore: "Restaurer"
caption: "Libellé"
loggedInAsBot: "Connecté actuellement en tant que bot"
tools: "Outils"
cannotLoad: "Chargement impossible"
like: "J'aime"
show: "Affichage"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement." description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
sensitivity: "Sensibilité de la détection" sensitivity: "Sensibilité de la détection"
@ -1201,6 +1222,8 @@ _tutorial:
step7_1: "Félicitations ! Vous avez atteint la fin du tutoriel de base pour lutilisation de Misskey." step7_1: "Félicitations ! Vous avez atteint la fin du tutoriel de base pour lutilisation de Misskey."
step7_2: "Si vous désirez en savoir plus sur Misskey, jetez un œil sur la section {help}." step7_2: "Si vous désirez en savoir plus sur Misskey, jetez un œil sur la section {help}."
step7_3: "Bon courage et amusez-vous bien sur Misskey ! 🚀" step7_3: "Bon courage et amusez-vous bien sur Misskey ! 🚀"
step8_1: "Enfin, souhaitez-vous activer les notifications push ?"
step8_2: "En les activant, vous recevrez des notifications pour les mentions, les réactions, les suivis, etc., même lorsque Misskey n'est pas ouvert."
_2fa: _2fa:
alreadyRegistered: "Configuration déjà achevée." alreadyRegistered: "Configuration déjà achevée."
registerDevice: "Ajouter un nouvel appareil" registerDevice: "Ajouter un nouvel appareil"
@ -1287,6 +1310,7 @@ _widgets:
serverMetric: "Statistiques du serveur" serverMetric: "Statistiques du serveur"
aiscript: "Console AiScript" aiscript: "Console AiScript"
aichan: "Ai" aichan: "Ai"
userList: "Liste utilisateur"
_userList: _userList:
chooseList: "Sélectionner une liste" chooseList: "Sélectionner une liste"
_cw: _cw:
@ -1389,6 +1413,12 @@ _timelines:
local: "Local" local: "Local"
social: "Social" social: "Social"
global: "Global" global: "Global"
_play:
viewSource: "Afficher la source"
featured: "Populaire"
title: "Titre"
script: "Script"
summary: "Description"
_pages: _pages:
newPage: "Créer une page" newPage: "Créer une page"
editPage: "Modifier une page" editPage: "Modifier une page"
@ -1456,6 +1486,7 @@ _notification:
yourFollowRequestAccepted: "Votre demande dabonnement a été accepté" yourFollowRequestAccepted: "Votre demande dabonnement a été accepté"
youWereInvitedToGroup: "Invité·e au groupe" youWereInvitedToGroup: "Invité·e au groupe"
pollEnded: "Les résultats du sondage sont disponibles" pollEnded: "Les résultats du sondage sont disponibles"
unreadAntennaNote: "Antenne {name}"
emptyPushNotificationMessage: "Les notifications push ont été mises à jour" emptyPushNotificationMessage: "Les notifications push ont été mises à jour"
_types: _types:
all: "Toutes" all: "Toutes"
@ -1490,6 +1521,7 @@ _deck:
newProfile: "Nouveau profil" newProfile: "Nouveau profil"
deleteProfile: "Supprimer le profil" deleteProfile: "Supprimer le profil"
introduction: "Créez linterface parfaite qui vous sied en arrangeant librement les colonnes !" introduction: "Créez linterface parfaite qui vous sied en arrangeant librement les colonnes !"
introduction2: "Cliquez sur le + à droite de l'écran pour ajouter de nouvelles colonnes quand vous le souhaitez."
_columns: _columns:
main: "Principale" main: "Principale"
widgets: "Widgets" widgets: "Widgets"

View File

@ -164,7 +164,6 @@ annotation: "Keterangan konten"
federation: "Federasi" federation: "Federasi"
instances: "Instansi" instances: "Instansi"
registeredAt: "Terdaftar" registeredAt: "Terdaftar"
latestRequestSentAt: "Permintaan terakhir dikirim pada"
latestRequestReceivedAt: "Permintaan terakhir diterima pada" latestRequestReceivedAt: "Permintaan terakhir diterima pada"
latestStatus: "Status terakhir" latestStatus: "Status terakhir"
storageUsage: "Penggunaan penyimpanan" storageUsage: "Penggunaan penyimpanan"
@ -856,6 +855,10 @@ colored: "Diwarnai"
label: "Label" label: "Label"
localOnly: "Hanya lokal" localOnly: "Hanya lokal"
account: "Akun" account: "Akun"
like: "Suka"
unlike: "Tidak Suka"
numberOfLikes: "Jumlah yang disukai"
show: "Tampilkan"
_emailUnavailable: _emailUnavailable:
used: "Alamat surel ini telah digunakan" used: "Alamat surel ini telah digunakan"
format: "Format tidak valid." format: "Format tidak valid."
@ -1221,6 +1224,7 @@ _widgets:
jobQueue: "Antrian kerja" jobQueue: "Antrian kerja"
serverMetric: "Statistik peladen" serverMetric: "Statistik peladen"
aiscript: "Konsol AiScript" aiscript: "Konsol AiScript"
aiscriptApp: "Aplikasi AiScript"
aichan: "Ai" aichan: "Ai"
_userList: _userList:
chooseList: "Pilih daftar" chooseList: "Pilih daftar"
@ -1324,6 +1328,21 @@ _timelines:
local: "Lokal" local: "Lokal"
social: "Sosial" social: "Sosial"
global: "Global" global: "Global"
_play:
new: "Membuat Permainan"
edit: "Menyunting Permainan"
created: "Permainan sudah dibuat"
updated: "Permainan sudah diperbaharui"
deleted: "Hapus permainan"
pageSetting: "Pengaturan permainan"
editThisPage: "Sunting Permainan ini"
viewSource: "Lihat sumber"
my: "Permainan saya"
liked: "Permainan Disukai"
featured: "Populer"
title: "Judul"
script: "Script"
summary: "Deskripsi"
_pages: _pages:
newPage: "Buat halaman baru" newPage: "Buat halaman baru"
editPage: "Sunting halaman" editPage: "Sunting halaman"

View File

@ -1,7 +1,7 @@
--- ---
_lang_: "Italiano" _lang_: "Italiano"
headlineMisskey: "Rete collegata tramite note" headlineMisskey: "Rete collegata tramite note"
introMisskey: "Benvenut@! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \nScrivi \"note\" per condividere ciò che sta succedendo adesso o per dire a tutti qualcosa di te. 📡\nGrazie alla funzione \"reazioni\" puoi anche mandare reazioni rapide alle note delle altre persone del Fediverso. 👍\nEsplora un nuovo mondo! 🚀" introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n🚀 Esplora un nuovo mondo insieme a noi!"
poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>." poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Cerca" search: "Cerca"
@ -49,6 +49,7 @@ deleteAndEdit: "Elimina e modifica"
deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verrano eliminate anche tutte le reazioni, Rinote e risposte collegate." deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verrano eliminate anche tutte le reazioni, Rinote e risposte collegate."
addToList: "Aggiungi alla lista" addToList: "Aggiungi alla lista"
sendMessage: "Invia messaggio" sendMessage: "Invia messaggio"
copyRSS: "Copia RSS"
copyUsername: "Copia nome utente" copyUsername: "Copia nome utente"
searchUser: "Cerca utente" searchUser: "Cerca utente"
reply: "Rispondi" reply: "Rispondi"
@ -166,7 +167,6 @@ annotation: "Descrizione"
federation: "Federazione" federation: "Federazione"
instances: "Istanza" instances: "Istanza"
registeredAt: "Registrato presso" registeredAt: "Registrato presso"
latestRequestSentAt: "Ultima richiesta inviata"
latestRequestReceivedAt: "Ultima richiesta ricevuta" latestRequestReceivedAt: "Ultima richiesta ricevuta"
latestStatus: "Ultimo stato" latestStatus: "Ultimo stato"
storageUsage: "Capienza dei dischi" storageUsage: "Capienza dei dischi"
@ -456,6 +456,8 @@ language: "Lingua"
uiLanguage: "Lingua di visualizzazione dell'interfaccia" uiLanguage: "Lingua di visualizzazione dell'interfaccia"
groupInvited: "Invitat@ al gruppo" groupInvited: "Invitat@ al gruppo"
aboutX: "Informazioni su {x}" aboutX: "Informazioni su {x}"
emojiStyle: "Stile emoji"
native: "Nativo"
disableDrawer: "Non mostrare il menù sul drawer" disableDrawer: "Non mostrare il menù sul drawer"
youHaveNoGroups: "Nessun gruppo" youHaveNoGroups: "Nessun gruppo"
joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono." joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono."
@ -714,6 +716,7 @@ accentColor: "Colore principale"
textColor: "Testo" textColor: "Testo"
saveAs: "Salva con nome" saveAs: "Salva con nome"
advanced: "Avanzato" advanced: "Avanzato"
advancedSettings: "Impostazioni avanzate"
value: "Valore" value: "Valore"
createdAt: "Data di creazione" createdAt: "Data di creazione"
updatedAt: "Aggiornato il" updatedAt: "Aggiornato il"
@ -873,7 +876,7 @@ deleteAccount: "Eliminazione profilo"
document: "Documento" document: "Documento"
numberOfPageCache: "Numero di pagine cache" numberOfPageCache: "Numero di pagine cache"
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria." numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
logoutConfirm: "Sei sicuro di voler effettuare il logout?" logoutConfirm: "Vuoi davvero uscire da Misskey? "
lastActiveDate: "Data dell'ultimo utilizzo" lastActiveDate: "Data dell'ultimo utilizzo"
statusbar: "Barra di stato" statusbar: "Barra di stato"
pleaseSelect: "Scegli un'opzione" pleaseSelect: "Scegli un'opzione"
@ -910,6 +913,11 @@ windowMaximize: "Ingrandisci"
windowRestore: "Ripristina" windowRestore: "Ripristina"
caption: "Didascalia" caption: "Didascalia"
loggedInAsBot: "Connessione come Bot" loggedInAsBot: "Connessione come Bot"
tools: "Strumenti"
cannotLoad: "Caricamento impossibile"
numberOfProfileView: "Visualizzazioni profilo"
like: "Mi piace!"
show: "Visualizza"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente." description: "L'apprendimento automatico può essere utilizzato per individuare automaticamente i media sensibili da moderare. Il carico del server aumenta leggermente."
sensitivity: "Sensibilità di rilevamento" sensitivity: "Sensibilità di rilevamento"
@ -1061,7 +1069,7 @@ _mfm:
sparkleDescription: "Aggiungere effetti particellari scintillanti." sparkleDescription: "Aggiungere effetti particellari scintillanti."
rotate: "Ruota" rotate: "Ruota"
rotateDescription: "Ruota con un angolo specificato." rotateDescription: "Ruota con un angolo specificato."
plain: "aereo" plain: "Testo semplice"
plainDescription: "Disattiva tutta la sintassi interna." plainDescription: "Disattiva tutta la sintassi interna."
_instanceTicker: _instanceTicker:
none: "Nascondi" none: "Nascondi"
@ -1199,13 +1207,13 @@ _time:
day: "giorni" day: "giorni"
_tutorial: _tutorial:
title: "Come usare Misskey" title: "Come usare Misskey"
step1_1: "Benvenuto/a!" step1_1: "Eccoci!"
step1_2: "Questa pagina si chiama una \" Timeline \". Mostra in ordine cronologico le \" note \" delle persone che segui." step1_2: "Questa pagina si chiama una \" Timeline \". Mostra in ordine cronologico le \" note \" delle persone che segui."
step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun profilo e non hai pubblicato alcuna nota ancora." step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun profilo e non hai pubblicato alcuna nota ancora."
step2_1: "Prima di scrivere una nota o di seguire altri profili, imposta il tuo di profilo!" step2_1: "Prima di scrivere una «Nota» o di seguire altri profili, prepara il tuo profilo!"
step2_2: "Aggiungere qualche informazione su di te aumenterà le tue possibilità di essere seguit@ da altre persone. " step2_2: "Aggiungere qualche informazione su di te aumenterà le tue possibilità di essere seguit@ da altre persone. "
step3_1: "Hai finito di impostare il tuo profilo?" step3_1: "Hai finito di impostare il tuo profilo?"
step3_2: "Ora, puoi pubblicare una nota. Facciamo una prova! Premi il pulsante a forma di penna in cima allo schermo per aprire una finestra di dialogo. " step3_2: "Ora puoi pubblicare una «Nota». Proviamo subito! Premi il bottone con l'icona «penna» per iniziare a scrivere in una finestra di dialogo. "
step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo." step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo."
step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena cominciato a usare Misskey\"?" step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena cominciato a usare Misskey\"?"
step4_1: "Hai pubblicato qualcosa?" step4_1: "Hai pubblicato qualcosa?"
@ -1217,7 +1225,7 @@ _tutorial:
step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline." step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline."
step6_2: "Puoi anche rispondere alle note con un click, scegliendo le reazioni immediate." step6_2: "Puoi anche rispondere alle note con un click, scegliendo le reazioni immediate."
step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare." step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare."
step7_1: "Complimenti! Sei arrivat@ alla fine dell'esercitazione di base su come usare Misskey. " step7_1: "Congratulazioni! Hai completato l'esercitazione iniziale su come usare Misskey."
step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}." step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}."
step7_3: "Da ultimo, buon divertimento su Misskey! 🚀" step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
step8_1: "Per concludere, vuoi attivare le notifiche push?" step8_1: "Per concludere, vuoi attivare le notifiche push?"
@ -1309,7 +1317,8 @@ _widgets:
jobQueue: "Coda di lavoro" jobQueue: "Coda di lavoro"
serverMetric: "Statistiche server" serverMetric: "Statistiche server"
aiscript: "Console AiScript" aiscript: "Console AiScript"
aichan: "indaco (tintura)" aichan: "Mascotte Ai"
userList: "Elenco utenti"
_userList: _userList:
chooseList: "Seleziona una lista" chooseList: "Seleziona una lista"
_cw: _cw:
@ -1375,6 +1384,7 @@ _profile:
changeBanner: "Cambia intestazione" changeBanner: "Cambia intestazione"
_exportOrImport: _exportOrImport:
allNotes: "Tutte le note" allNotes: "Tutte le note"
favoritedNotes: "Note preferite"
followingList: "Follows" followingList: "Follows"
muteList: "Elenco profili silenziati" muteList: "Elenco profili silenziati"
blockingList: "Elenco profili bloccati" blockingList: "Elenco profili bloccati"
@ -1412,6 +1422,12 @@ _timelines:
local: "Locale" local: "Locale"
social: "Sociale" social: "Sociale"
global: "Federata" global: "Federata"
_play:
viewSource: "Visualizza sorgente"
featured: "Popolari"
title: "Titolo"
script: "Script"
summary: "Descrizione"
_pages: _pages:
newPage: "Crea pagina" newPage: "Crea pagina"
editPage: "Modifica pagina" editPage: "Modifica pagina"

View File

@ -916,6 +916,14 @@ loggedInAsBot: "Botアカウントでログイン中"
tools: "ツール" tools: "ツール"
cannotLoad: "読み込めません" cannotLoad: "読み込めません"
numberOfProfileView: "プロフィール表示回数" numberOfProfileView: "プロフィール表示回数"
like: "いいね!"
unlike: "いいねを解除"
numberOfLikes: "いいね数"
show: "表示"
neverShow: "今後表示しない"
remindMeLater: "また後で"
didYouLikeMisskey: "Misskeyを気に入っていただけましたか"
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
@ -1348,6 +1356,7 @@ _widgets:
jobQueue: "ジョブキュー" jobQueue: "ジョブキュー"
serverMetric: "サーバーメトリクス" serverMetric: "サーバーメトリクス"
aiscript: "AiScriptコンソール" aiscript: "AiScriptコンソール"
aiscriptApp: "AiScript App"
aichan: "藍" aichan: "藍"
userList: "ユーザーリスト" userList: "ユーザーリスト"
_userList: _userList:
@ -1463,6 +1472,22 @@ _timelines:
social: "ソーシャル" social: "ソーシャル"
global: "グローバル" global: "グローバル"
_play:
new: "Playの作成"
edit: "Playの編集"
created: "Playを作成しました"
updated: "Playを更新しました"
deleted: "Playを削除しました"
pageSetting: "Play設定"
editThisPage: "このPlayを編集"
viewSource: "ソースを表示"
my: "自分のPlay"
liked: "いいねしたPlay"
featured: "人気"
title: "タイトル"
script: "スクリプト"
summary: "説明"
_pages: _pages:
newPage: "ページの作成" newPage: "ページの作成"
editPage: "ページの編集" editPage: "ページの編集"

View File

@ -167,7 +167,6 @@ annotation: "注釈"
federation: "連合" federation: "連合"
instances: "インスタンス" instances: "インスタンス"
registeredAt: "初観測" registeredAt: "初観測"
latestRequestSentAt: "ちょっと前のリクエスト送信"
latestRequestReceivedAt: "ちょっと前のリクエスト受信" latestRequestReceivedAt: "ちょっと前のリクエスト受信"
latestStatus: "ちょっと前のステータス" latestStatus: "ちょっと前のステータス"
storageUsage: "ストレージ使うた量" storageUsage: "ストレージ使うた量"
@ -916,6 +915,8 @@ caption: "キャプション"
loggedInAsBot: "Botアカウントでログイン中やで" loggedInAsBot: "Botアカウントでログイン中やで"
tools: "ツール" tools: "ツール"
cannotLoad: "読み込めへんで" cannotLoad: "読み込めへんで"
like: "ええやん!"
show: "表示"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。" description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。"
sensitivity: "検出感度やで" sensitivity: "検出感度やで"
@ -1419,6 +1420,12 @@ _timelines:
local: "ローカル" local: "ローカル"
social: "ソーシャル" social: "ソーシャル"
global: "グローバル" global: "グローバル"
_play:
viewSource: "ソースを表示"
featured: "人気"
title: "タイトル"
script: "スクリプト"
summary: "説明"
_pages: _pages:
newPage: "ページを作る" newPage: "ページを作る"
editPage: "ページの編集" editPage: "ページの編集"

View File

@ -167,7 +167,6 @@ annotation: "내용에 대한 주석"
federation: "연합" federation: "연합"
instances: "인스턴스" instances: "인스턴스"
registeredAt: "등록 날짜" registeredAt: "등록 날짜"
latestRequestSentAt: "마지막으로 요청을 보낸 시간"
latestRequestReceivedAt: "마지막으로 요청을 받은 시간" latestRequestReceivedAt: "마지막으로 요청을 받은 시간"
latestStatus: "마지막 상태" latestStatus: "마지막 상태"
storageUsage: "스토리지 사용량" storageUsage: "스토리지 사용량"
@ -916,6 +915,11 @@ caption: "캡션"
loggedInAsBot: "봇 계정으로 로그인중" loggedInAsBot: "봇 계정으로 로그인중"
tools: "도구" tools: "도구"
cannotLoad: "불러오지 못했습니다" cannotLoad: "불러오지 못했습니다"
numberOfProfileView: "프로필 뷰 수"
like: "좋아요!"
unlike: "좋아요 취소"
numberOfLikes: "좋아요 수"
show: "표시"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다." description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
sensitivity: "탐지 민감도" sensitivity: "탐지 민감도"
@ -1315,6 +1319,7 @@ _widgets:
jobQueue: "작업 대기열" jobQueue: "작업 대기열"
serverMetric: "서버 통계" serverMetric: "서버 통계"
aiscript: "AiScript 콘솔" aiscript: "AiScript 콘솔"
aiscriptApp: "AiScript 앱"
aichan: "아이" aichan: "아이"
userList: "사용자 목록" userList: "사용자 목록"
_userList: _userList:
@ -1382,6 +1387,7 @@ _profile:
changeBanner: "배너 이미지 변경" changeBanner: "배너 이미지 변경"
_exportOrImport: _exportOrImport:
allNotes: "모든 노트" allNotes: "모든 노트"
favoritedNotes: "즐겨찾기한 노트"
followingList: "팔로잉" followingList: "팔로잉"
muteList: "뮤트" muteList: "뮤트"
blockingList: "차단" blockingList: "차단"
@ -1419,6 +1425,21 @@ _timelines:
local: "로컬" local: "로컬"
social: "소셜" social: "소셜"
global: "글로벌" global: "글로벌"
_play:
new: "Play 만들기"
edit: "Play 수정하기"
created: "Play를 생성했습니다"
updated: "Play를 갱신했습니다"
deleted: "Play를 삭제했습니다"
pageSetting: "Play 설정"
editThisPage: "이 Play를 수정"
viewSource: "소스 보기"
my: "나의 Play"
liked: "좋아요 한 Play"
featured: "인기"
title: "제목"
script: "스크립트"
summary: "설명"
_pages: _pages:
newPage: "페이지 만들기" newPage: "페이지 만들기"
editPage: "페이지 수정" editPage: "페이지 수정"

View File

@ -165,7 +165,6 @@ annotation: "Reacties"
federation: "Federatie" federation: "Federatie"
instances: "Server" instances: "Server"
registeredAt: "Geregistreerd op" registeredAt: "Geregistreerd op"
latestRequestSentAt: "Laatste aanvraag verstuurd"
latestRequestReceivedAt: "Laatste aanvraag ontvangen" latestRequestReceivedAt: "Laatste aanvraag ontvangen"
latestStatus: "Laatste status" latestStatus: "Laatste status"
storageUsage: "Gebruikte opslagruimte" storageUsage: "Gebruikte opslagruimte"

View File

@ -165,7 +165,6 @@ annotation: "Komentarze"
federation: "Federacja" federation: "Federacja"
instances: "Instancja" instances: "Instancja"
registeredAt: "Zarejestrowano" registeredAt: "Zarejestrowano"
latestRequestSentAt: "Ostatnie żądanie wysłano o"
latestRequestReceivedAt: "Ostatnie żądanie otrzymano o" latestRequestReceivedAt: "Ostatnie żądanie otrzymano o"
latestStatus: "Najnowszy status" latestStatus: "Najnowszy status"
storageUsage: "Użycie pamięci" storageUsage: "Użycie pamięci"
@ -867,6 +866,8 @@ pushNotificationNotSupported: "Przeglądarka lub instancja nie obsługuje powiad
sendPushNotificationReadMessage: "Usuń powiadomienia push po przeczytaniu powiadomień i wiadomości." sendPushNotificationReadMessage: "Usuń powiadomienia push po przeczytaniu powiadomień i wiadomości."
sendPushNotificationReadMessageCaption: "Chwilowo pojawi się powiadomienie \"{emptyPushNotificationMessage}\". Może wzrosnąć zużycie baterii urządzenia." sendPushNotificationReadMessageCaption: "Chwilowo pojawi się powiadomienie \"{emptyPushNotificationMessage}\". Może wzrosnąć zużycie baterii urządzenia."
loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot" loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot"
like: "Polub"
show: "Wyświetlanie"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Zmniejsza wysiłek związany z moderacją serwera dzięki automatycznemu rozpoznawaniu zawartości NSFW za pomocą uczenia maszynowego. To nieznacznie zwiększy obciążenie serwera." description: "Zmniejsza wysiłek związany z moderacją serwera dzięki automatycznemu rozpoznawaniu zawartości NSFW za pomocą uczenia maszynowego. To nieznacznie zwiększy obciążenie serwera."
setSensitiveFlagAutomatically: "Oznacz jako NSFW" setSensitiveFlagAutomatically: "Oznacz jako NSFW"
@ -1314,6 +1315,12 @@ _timelines:
local: "Lokalne" local: "Lokalne"
social: "Społeczność" social: "Społeczność"
global: "Globalna" global: "Globalna"
_play:
viewSource: "Zobacz źródło"
featured: "Wyróżnione"
title: "Tytuł"
script: "Skrypt"
summary: "Opis"
_pages: _pages:
newPage: "Utwórz stronę" newPage: "Utwórz stronę"
editPage: "Edytuj tę stronę" editPage: "Edytuj tę stronę"

View File

@ -164,7 +164,6 @@ annotation: "Anotação"
federation: "União" federation: "União"
instances: "Instância" instances: "Instância"
registeredAt: "Registrado em" registeredAt: "Registrado em"
latestRequestSentAt: "Enviar a solicitação mais recente"
latestRequestReceivedAt: "Recebeu a última solicitação" latestRequestReceivedAt: "Recebeu a última solicitação"
latestStatus: "Status mais recente" latestStatus: "Status mais recente"
storageUsage: "Uso de armazenamento" storageUsage: "Uso de armazenamento"

View File

@ -164,7 +164,6 @@ annotation: "Adnotări"
federation: "Federație" federation: "Federație"
instances: "Instanțe" instances: "Instanțe"
registeredAt: "Înregistrat în" registeredAt: "Înregistrat în"
latestRequestSentAt: "Ultima cerere trimisă"
latestRequestReceivedAt: "Ultima cerere primită" latestRequestReceivedAt: "Ultima cerere primită"
latestStatus: "Ultimul status" latestStatus: "Ultimul status"
storageUsage: "Utilizare stocare" storageUsage: "Utilizare stocare"
@ -648,6 +647,7 @@ middle: "Mediu"
sent: "Trimite" sent: "Trimite"
searchByGoogle: "Caută" searchByGoogle: "Caută"
file: "Fișiere" file: "Fișiere"
show: "Arată"
_email: _email:
_follow: _follow:
title: "te-a urmărit" title: "te-a urmărit"
@ -691,6 +691,9 @@ _charts:
federation: "Federație" federation: "Federație"
_timelines: _timelines:
home: "Acasă" home: "Acasă"
_play:
script: "Script"
summary: "Descriere"
_pages: _pages:
blocks: blocks:
image: "Imagini" image: "Imagini"

View File

@ -164,7 +164,6 @@ annotation: "Описание"
federation: "Федерация" federation: "Федерация"
instances: "Инстанс" instances: "Инстанс"
registeredAt: "Первое наблюдение" registeredAt: "Первое наблюдение"
latestRequestSentAt: "Последний отправленный запрос"
latestRequestReceivedAt: "Последний полученный запрос" latestRequestReceivedAt: "Последний полученный запрос"
latestStatus: "Последний статус" latestStatus: "Последний статус"
storageUsage: "Использовано" storageUsage: "Использовано"
@ -865,6 +864,8 @@ enableAutoSensitiveDescription: "Если доступно, используйт
account: "Учётные записи" account: "Учётные записи"
windowMaximize: "Развернуть" windowMaximize: "Развернуть"
windowRestore: "Восстановить" windowRestore: "Восстановить"
like: "Нравится!"
show: "Отображение"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно." description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
setSensitiveFlagAutomatically: "Установить флаг NSFW" setSensitiveFlagAutomatically: "Установить флаг NSFW"
@ -1333,6 +1334,12 @@ _timelines:
local: "Местная" local: "Местная"
social: "Социальная" social: "Социальная"
global: "Всеобщая" global: "Всеобщая"
_play:
viewSource: "Просмотр исходника"
featured: "Популярные"
title: "Заголовок"
script: "Скрипт"
summary: "Описание"
_pages: _pages:
newPage: "Создать страницу" newPage: "Создать страницу"
editPage: "Править страницу" editPage: "Править страницу"

View File

@ -167,7 +167,6 @@ annotation: "Komentáre"
federation: "Federácia" federation: "Federácia"
instances: "Inštancia" instances: "Inštancia"
registeredAt: "Registrácia" registeredAt: "Registrácia"
latestRequestSentAt: "Posledná odoslaná požiadavka"
latestRequestReceivedAt: "Posledná prijatá požiadavka" latestRequestReceivedAt: "Posledná prijatá požiadavka"
latestStatus: "Posledný status" latestStatus: "Posledný status"
storageUsage: "Využité úložisko" storageUsage: "Využité úložisko"
@ -912,6 +911,8 @@ windowRestore: "Obnoviť"
caption: "Nadpis" caption: "Nadpis"
tools: "Nástroje" tools: "Nástroje"
cannotLoad: "Nedá sa načítať." cannotLoad: "Nedá sa načítať."
like: "Páči sa mi"
show: "Zobraziť"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera." description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
sensitivity: "Citlivosť detekcie" sensitivity: "Citlivosť detekcie"
@ -1414,6 +1415,12 @@ _timelines:
local: "Lokálne" local: "Lokálne"
social: "Sociálne" social: "Sociálne"
global: "Globálne" global: "Globálne"
_play:
viewSource: "Ukázať zdroj"
featured: "Význačné"
title: "Nadpis"
script: "Skript"
summary: "Popis"
_pages: _pages:
newPage: "Vytvoriť novú stránku" newPage: "Vytvoriť novú stránku"
editPage: "Upraviť túto stránku" editPage: "Upraviť túto stránku"

View File

@ -2,6 +2,7 @@
_lang_: "Svenska" _lang_: "Svenska"
headlineMisskey: "Ett nätverk kopplat av noter" headlineMisskey: "Ett nätverk kopplat av noter"
introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀" introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀"
poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")."
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Sök" search: "Sök"
notifications: "Notifikationer" notifications: "Notifikationer"
@ -12,6 +13,7 @@ fetchingAsApObject: "Hämtar från Fediversum..."
ok: "OK" ok: "OK"
gotIt: "Uppfattat!" gotIt: "Uppfattat!"
cancel: "Avbryt" cancel: "Avbryt"
noThankYou: "Nej tack"
enterUsername: "Ange användarnamn" enterUsername: "Ange användarnamn"
renotedBy: "Omnoterad av {user}" renotedBy: "Omnoterad av {user}"
noNotes: "Inga noteringar" noNotes: "Inga noteringar"
@ -47,11 +49,13 @@ deleteAndEdit: "Radera och ändra"
deleteAndEditConfirm: "Är du säker att du vill radera denna not och ändra den? Du kommer förlora alla reaktioner, omnoteringar och svar till den." deleteAndEditConfirm: "Är du säker att du vill radera denna not och ändra den? Du kommer förlora alla reaktioner, omnoteringar och svar till den."
addToList: "Lägg till i lista" addToList: "Lägg till i lista"
sendMessage: "Skicka ett meddelande" sendMessage: "Skicka ett meddelande"
copyRSS: "Kopiera RSS"
copyUsername: "Kopiera användarnamn" copyUsername: "Kopiera användarnamn"
searchUser: "Sök användare" searchUser: "Sök användare"
reply: "Svara" reply: "Svara"
loadMore: "Ladda mer" loadMore: "Ladda mer"
showMore: "Visa mer" showMore: "Visa mer"
showLess: "Stäng"
youGotNewFollower: "följde dig" youGotNewFollower: "följde dig"
receiveFollowRequest: "Följarförfrågan mottagen" receiveFollowRequest: "Följarförfrågan mottagen"
followRequestAccepted: "Följarförfrågan accepterad" followRequestAccepted: "Följarförfrågan accepterad"
@ -163,7 +167,6 @@ annotation: "Kommentarer"
federation: "Federation" federation: "Federation"
instances: "Instanser" instances: "Instanser"
registeredAt: "Registrerad på" registeredAt: "Registrerad på"
latestRequestSentAt: "Senaste förfrågan skickad"
latestRequestReceivedAt: "Senaste begäran mottagen" latestRequestReceivedAt: "Senaste begäran mottagen"
latestStatus: "Senaste status" latestStatus: "Senaste status"
storageUsage: "Använt lagringsutrymme" storageUsage: "Använt lagringsutrymme"
@ -239,6 +242,17 @@ saved: "Sparad"
messaging: "Chatt" messaging: "Chatt"
upload: "Ladda upp" upload: "Ladda upp"
keepOriginalUploading: "Behåll originalbild" keepOriginalUploading: "Behåll originalbild"
keepOriginalUploadingDescription: "Sparar den originellt uppladdade bilden i sitt i befintliga skick. Om avstängd, kommer en webbversion bli genererad vid uppladdning."
fromDrive: "Från Drive"
fromUrl: "Från en länk"
uploadFromUrl: "Ladda upp från länk"
uploadFromUrlDescription: "Länken av filen du vill ladda upp"
uploadFromUrlRequested: "Uppladdning begärd"
uploadFromUrlMayTakeTime: "Det kan ta tid tills att uppladdningen blir klar."
explore: "Utforska"
messageRead: "Läs"
noMoreHistory: "Det finns ingen mer historik"
startMessaging: "Starta en chatt"
nsfw: "Känsligt innehåll" nsfw: "Känsligt innehåll"
pinnedNotes: "Fästad not" pinnedNotes: "Fästad not"
userList: "Listor" userList: "Listor"

View File

@ -167,7 +167,6 @@ annotation: "ความคิดเห็น"
federation: "เฟดิเวิร์ส" federation: "เฟดิเวิร์ส"
instances: "ตัวอย่าง" instances: "ตัวอย่าง"
registeredAt: "จดทะเบียนที่" registeredAt: "จดทะเบียนที่"
latestRequestSentAt: "ส่งคำขอล่าสุดไปแล้ว"
latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว" latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว"
latestStatus: "สถานะล่าสุด" latestStatus: "สถานะล่าสุด"
storageUsage: "พื้นที่จัดเก็บข้อมูลที่ใช้ไป" storageUsage: "พื้นที่จัดเก็บข้อมูลที่ใช้ไป"
@ -916,6 +915,9 @@ caption: "รายละเอียด"
loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้" loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้"
tools: "เครื่องมือ" tools: "เครื่องมือ"
cannotLoad: "ไม่สามารถโหลดได้" cannotLoad: "ไม่สามารถโหลดได้"
numberOfProfileView: "มุมมองโปรไฟล์"
like: "ชื่นชอบ"
show: "แสดงผล"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
sensitivity: "การตรวจจับความไว" sensitivity: "การตรวจจับความไว"
@ -1420,6 +1422,12 @@ _timelines:
local: "ในพื้นที่" local: "ในพื้นที่"
social: "โซเชี่ยล" social: "โซเชี่ยล"
global: "ทั่วโลก" global: "ทั่วโลก"
_play:
viewSource: "ดูต้นฉบับ"
featured: "เป็นที่นิยม"
title: "หัวข้อ"
script: "สคริปต์"
summary: "รายละเอียด"
_pages: _pages:
newPage: "สร้างหน้าเพจใหม่" newPage: "สร้างหน้าเพจใหม่"
editPage: "แก้ไขหน้าเพจ" editPage: "แก้ไขหน้าเพจ"

View File

@ -166,7 +166,6 @@ annotation: "Коментарі"
federation: "Федіверс" federation: "Федіверс"
instances: "Інстанс" instances: "Інстанс"
registeredAt: "Приєднався(лась)" registeredAt: "Приєднався(лась)"
latestRequestSentAt: "Останній запит надіслано"
latestRequestReceivedAt: "Останній запит прийнято" latestRequestReceivedAt: "Останній запит прийнято"
latestStatus: "Останній статус" latestStatus: "Останній статус"
storageUsage: "Використання простору" storageUsage: "Використання простору"
@ -893,6 +892,8 @@ unsubscribePushNotification: "Вимкнути push-сповіщення"
windowMaximize: "Розгорнути" windowMaximize: "Розгорнути"
windowRestore: "Відновити" windowRestore: "Відновити"
caption: "Підпис" caption: "Підпис"
like: "Вподобати"
show: "Відображення"
_sensitiveMediaDetection: _sensitiveMediaDetection:
sensitivity: "Чутливість детектування" sensitivity: "Чутливість детектування"
setSensitiveFlagAutomatically: "Позначити як NSFW" setSensitiveFlagAutomatically: "Позначити як NSFW"
@ -1349,6 +1350,12 @@ _timelines:
local: "Локальна" local: "Локальна"
social: "Соціальна" social: "Соціальна"
global: "Глобальна" global: "Глобальна"
_play:
viewSource: "Переглянути вихідний код"
featured: "Популярні"
title: "Заголовок"
script: "Скрипт"
summary: "Опис"
_pages: _pages:
newPage: "Створити сторінку" newPage: "Створити сторінку"
editPage: "Редагувати сторінку" editPage: "Редагувати сторінку"

View File

@ -164,7 +164,6 @@ annotation: "Bình luận"
federation: "Liên hợp" federation: "Liên hợp"
instances: "Máy chủ" instances: "Máy chủ"
registeredAt: "Đăng ký vào" registeredAt: "Đăng ký vào"
latestRequestSentAt: "Yêu cầu cuối gửi lúc"
latestRequestReceivedAt: "Yêu cầu cuối nhận lúc" latestRequestReceivedAt: "Yêu cầu cuối nhận lúc"
latestStatus: "Trạng thái cuối cùng" latestStatus: "Trạng thái cuối cùng"
storageUsage: "Dung lượng lưu trữ" storageUsage: "Dung lượng lưu trữ"
@ -895,6 +894,8 @@ navbar: "Thanh điều hướng"
shuffle: "Xáo trộn" shuffle: "Xáo trộn"
account: "Tài khoản của bạn" account: "Tài khoản của bạn"
move: "Di chuyển" move: "Di chuyển"
like: "Thích"
show: "Hiển thị"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ." description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ."
sensitivity: "Phát hiện nhạy cảm" sensitivity: "Phát hiện nhạy cảm"
@ -1394,6 +1395,12 @@ _timelines:
local: "Máy chủ này" local: "Máy chủ này"
social: "Xã hội" social: "Xã hội"
global: "Liên hợp" global: "Liên hợp"
_play:
viewSource: "Xem mã nguồn"
featured: "Nổi tiếng"
title: "Tựa đề"
script: "Kịch bản"
summary: "Mô tả"
_pages: _pages:
newPage: "Tạo Trang mới" newPage: "Tạo Trang mới"
editPage: "Sửa Trang này" editPage: "Sửa Trang này"

View File

@ -167,7 +167,6 @@ annotation: "注解"
federation: "联合" federation: "联合"
instances: "实例" instances: "实例"
registeredAt: "初次观测" registeredAt: "初次观测"
latestRequestSentAt: "上次发送的请求"
latestRequestReceivedAt: "上次收到的请求" latestRequestReceivedAt: "上次收到的请求"
latestStatus: "最后状态" latestStatus: "最后状态"
storageUsage: "已用存储" storageUsage: "已用存储"
@ -913,9 +912,14 @@ sendPushNotificationReadMessageCaption: "“{emptyPushNotificationMessage}”的
windowMaximize: "最大化" windowMaximize: "最大化"
windowRestore: "还原" windowRestore: "还原"
caption: "标题" caption: "标题"
loggedInAsBot: "已登录的Bot" loggedInAsBot: "以Bot账户登录"
tools: "工具" tools: "工具"
cannotLoad: "无法加载" cannotLoad: "无法加载"
numberOfProfileView: "个人资料展示次数"
like: "点赞!"
unlike: "取消赞"
numberOfLikes: "点赞数"
show: "显示"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
sensitivity: "检测敏感度" sensitivity: "检测敏感度"
@ -1049,8 +1053,8 @@ _mfm:
shakeDescription: "显示摇晃的动画效果。" shakeDescription: "显示摇晃的动画效果。"
twitch: "动画(颤抖)" twitch: "动画(颤抖)"
twitchDescription: "显示强烈颤抖的动画效果。" twitchDescription: "显示强烈颤抖的动画效果。"
spin: "动画(转)" spin: "动画(转)"
spinDescription: "显示转的动画效果。" spinDescription: "显示转的动画效果。"
x2: "大" x2: "大"
x2Description: "以大尺寸显示内容。" x2Description: "以大尺寸显示内容。"
x3: "非常大" x3: "非常大"
@ -1315,6 +1319,7 @@ _widgets:
jobQueue: "作业队列" jobQueue: "作业队列"
serverMetric: "服务器指标" serverMetric: "服务器指标"
aiscript: "AiScript控制台" aiscript: "AiScript控制台"
aiscriptApp: "AiScript App"
aichan: "小蓝" aichan: "小蓝"
userList: "用户列表" userList: "用户列表"
_userList: _userList:
@ -1382,6 +1387,7 @@ _profile:
changeBanner: "修改横幅" changeBanner: "修改横幅"
_exportOrImport: _exportOrImport:
allNotes: "所有帖子" allNotes: "所有帖子"
favoritedNotes: "收藏的帖子"
followingList: "关注中" followingList: "关注中"
muteList: "屏蔽" muteList: "屏蔽"
blockingList: "拉黑" blockingList: "拉黑"
@ -1419,6 +1425,21 @@ _timelines:
local: "本地" local: "本地"
social: "社交" social: "社交"
global: "全局" global: "全局"
_play:
new: "创建Play"
edit: "编辑Play"
created: "创建了一个Play"
updated: "更新了Play"
deleted: "删除了Play"
pageSetting: "Play设置"
editThisPage: "编辑此Play"
viewSource: "查看源代码"
my: "我的Play"
liked: "点赞的Play"
featured: "热门"
title: "标题"
script: "脚本"
summary: "描述"
_pages: _pages:
newPage: "创建页面" newPage: "创建页面"
editPage: "编辑页面" editPage: "编辑页面"

View File

@ -167,7 +167,6 @@ annotation: "註解"
federation: "站台聯邦" federation: "站台聯邦"
instances: "實例" instances: "實例"
registeredAt: "初次觀測" registeredAt: "初次觀測"
latestRequestSentAt: "上次發送的請求"
latestRequestReceivedAt: "上次收到的請求" latestRequestReceivedAt: "上次收到的請求"
latestStatus: "最後狀態" latestStatus: "最後狀態"
storageUsage: "已使用容量" storageUsage: "已使用容量"
@ -917,6 +916,9 @@ loggedInAsBot: "以機器人帳號登入中"
tools: "工具" tools: "工具"
cannotLoad: "無法載入" cannotLoad: "無法載入"
numberOfProfileView: "個人檔案檢視次數" numberOfProfileView: "個人檔案檢視次數"
like: "讚"
unlike: "收回讚"
show: "檢視"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
sensitivity: "檢測敏感度" sensitivity: "檢測敏感度"
@ -1421,6 +1423,12 @@ _timelines:
local: "本地" local: "本地"
social: "社群" social: "社群"
global: "公開" global: "公開"
_play:
viewSource: "檢視原始碼"
featured: "人氣"
title: "標題"
script: "腳本"
summary: "描述"
_pages: _pages:
newPage: "建立頁面" newPage: "建立頁面"
editPage: "編輯頁面" editPage: "編輯頁面"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.0.0-beta.20", "version": "13.0.0-beta.27",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -53,10 +53,10 @@
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.47.1", "@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.47.1", "@typescript-eslint/parser": "5.48.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.2.0", "cypress": "12.3.0",
"eslint": "^8.31.0", "eslint": "^8.31.0",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "4.9.4" "typescript": "4.9.4"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,29 @@
export class Flash1672822262496 {
name = 'Flash1672822262496'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "flash" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "title" character varying(256) NOT NULL, "summary" character varying(1024) NOT NULL, "userId" character varying(32) NOT NULL, "script" character varying(16384) NOT NULL, "permissions" character varying(256) array NOT NULL DEFAULT '{}', "likedCount" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_0c01a2c1c5f2266942dd1b3fdbc" PRIMARY KEY ("id")); COMMENT ON COLUMN "flash"."createdAt" IS 'The created date of the Flash.'; COMMENT ON COLUMN "flash"."updatedAt" IS 'The updated date of the Flash.'; COMMENT ON COLUMN "flash"."userId" IS 'The ID of author.'`);
await queryRunner.query(`CREATE INDEX "IDX_149d2e44785707548c82999b01" ON "flash" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_3aa8ea9a8f15214ad91638c0a7" ON "flash" ("updatedAt") `);
await queryRunner.query(`CREATE INDEX "IDX_9b88250fc2fd009b8f1b5623ed" ON "flash" ("userId") `);
await queryRunner.query(`CREATE TABLE "flash_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "flashId" character varying(32) NOT NULL, CONSTRAINT "PK_d110109ee310588d63d6183b233" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_60c4af1c19a7a75f1592f93b28" ON "flash_like" ("userId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_cfbfeeccb0cbedcd660b17eb07" ON "flash_like" ("userId", "flashId") `);
await queryRunner.query(`ALTER TABLE "flash" ADD CONSTRAINT "FK_9b88250fc2fd009b8f1b5623ed5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "flash_like" ADD CONSTRAINT "FK_60c4af1c19a7a75f1592f93b287" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "flash_like" ADD CONSTRAINT "FK_6c16fe0e93b7a1951eca624b76a" FOREIGN KEY ("flashId") REFERENCES "flash"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "flash_like" DROP CONSTRAINT "FK_6c16fe0e93b7a1951eca624b76a"`);
await queryRunner.query(`ALTER TABLE "flash_like" DROP CONSTRAINT "FK_60c4af1c19a7a75f1592f93b287"`);
await queryRunner.query(`ALTER TABLE "flash" DROP CONSTRAINT "FK_9b88250fc2fd009b8f1b5623ed5"`);
await queryRunner.query(`DROP INDEX "public"."IDX_cfbfeeccb0cbedcd660b17eb07"`);
await queryRunner.query(`DROP INDEX "public"."IDX_60c4af1c19a7a75f1592f93b28"`);
await queryRunner.query(`DROP TABLE "flash_like"`);
await queryRunner.query(`DROP INDEX "public"."IDX_9b88250fc2fd009b8f1b5623ed"`);
await queryRunner.query(`DROP INDEX "public"."IDX_3aa8ea9a8f15214ad91638c0a7"`);
await queryRunner.query(`DROP INDEX "public"."IDX_149d2e44785707548c82999b01"`);
await queryRunner.query(`DROP TABLE "flash"`);
}
}

View File

@ -21,9 +21,9 @@
"@tensorflow/tfjs-node": "4.1.0" "@tensorflow/tfjs-node": "4.1.0"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "^4.10.0", "@bull-board/api": "^4.10.1",
"@bull-board/fastify": "^4.10.0", "@bull-board/fastify": "^4.10.1",
"@bull-board/ui": "^4.10.0", "@bull-board/ui": "^4.10.1",
"@discordapp/twemoji": "14.0.2", "@discordapp/twemoji": "14.0.2",
"@fastify/accepts": "4.1.0", "@fastify/accepts": "4.1.0",
"@fastify/cookie": "^8.3.0", "@fastify/cookie": "^8.3.0",
@ -38,10 +38,10 @@
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2", "@sinonjs/fake-timers": "10.0.2",
"accepts": "^1.3.8", "accepts": "^1.3.8",
"ajv": "8.11.2", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1286.0", "aws-sdk": "2.1289.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.4", "blurhash": "2.0.4",
"bull": "4.10.2", "bull": "4.10.2",
@ -109,8 +109,8 @@
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "2.7.0",
"syslog-pro": "git+https://github.com/misskey-dev/SyslogPro#0.2.9-misskey.2", "syslog-pro": "git+https://github.com/misskey-dev/SyslogPro#0.2.9-misskey.2",
"systeminformation": "5.16.9", "systeminformation": "5.17.1",
"tinycolor2": "1.5.1", "tinycolor2": "1.5.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2", "tsconfig-paths": "4.1.2",
@ -128,7 +128,7 @@
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.117", "@redocly/openapi-core": "1.0.0-beta.117",
"@swc/core": "1.3.24", "@swc/core": "1.3.25",
"@swc/jest": "0.2.24", "@swc/jest": "0.2.24",
"@types/accepts": "1.3.5", "@types/accepts": "1.3.5",
"@types/archiver": "5.3.1", "@types/archiver": "5.3.1",
@ -172,8 +172,8 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.47.1", "@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.47.1", "@typescript-eslint/parser": "5.48.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.31.0", "eslint": "8.31.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",

View File

@ -95,6 +95,8 @@ import { UserEntityService } from './entities/UserEntityService.js';
import { UserGroupEntityService } from './entities/UserGroupEntityService.js'; import { UserGroupEntityService } from './entities/UserGroupEntityService.js';
import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js'; import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js';
import { UserListEntityService } from './entities/UserListEntityService.js'; import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js'; import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js'; import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js'; import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@ -216,6 +218,8 @@ const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting
const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService }; const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService };
const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService }; const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService };
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService }; const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService }; const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService }; const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@ -338,6 +342,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserGroupEntityService, UserGroupEntityService,
UserGroupInvitationEntityService, UserGroupInvitationEntityService,
UserListEntityService, UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
ApAudienceService, ApAudienceService,
ApDbResolverService, ApDbResolverService,
ApDeliverManagerService, ApDeliverManagerService,
@ -455,6 +461,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserGroupEntityService, $UserGroupEntityService,
$UserGroupInvitationEntityService, $UserGroupInvitationEntityService,
$UserListEntityService, $UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
$ApAudienceService, $ApAudienceService,
$ApDbResolverService, $ApDbResolverService,
$ApDeliverManagerService, $ApDeliverManagerService,
@ -572,6 +580,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserGroupEntityService, UserGroupEntityService,
UserGroupInvitationEntityService, UserGroupInvitationEntityService,
UserListEntityService, UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
ApAudienceService, ApAudienceService,
ApDbResolverService, ApDbResolverService,
ApDeliverManagerService, ApDeliverManagerService,
@ -688,6 +698,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserGroupEntityService, $UserGroupEntityService,
$UserGroupInvitationEntityService, $UserGroupInvitationEntityService,
$UserListEntityService, $UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
$ApAudienceService, $ApAudienceService,
$ApDbResolverService, $ApDbResolverService,
$ApDeliverManagerService, $ApDeliverManagerService,

View File

@ -47,26 +47,6 @@ function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pus
return body; return body;
} }
function truncateUnreadAntennaNote(notification: pushNotificationsTypes['unreadAntennaNote']): pushNotificationsTypes['unreadAntennaNote'] {
if (notification.note) {
return {
...notification,
note: {
...notification.note,
// textをgetNoteSummaryしたものに置き換える
text: getNoteSummary(('type' in notification && notification.type === 'renote') ? notification.note.renote as Packed<'Note'> : notification.note),
cw: undefined,
reply: undefined,
renote: undefined,
user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる アンテナの場合も不要なのでいらない
},
};
}
return notification;
}
@Injectable() @Injectable()
export class PushNotificationService { export class PushNotificationService {
constructor( constructor(

View File

@ -0,0 +1,55 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Flash } from '@/models/entities/Flash.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class FlashEntityService {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
private userEntityService: UserEntityService,
) {
}
@bindThis
public async pack(
src: Flash['id'] | Flash,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Flash'>> {
const meId = me ? me.id : null;
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
return await awaitAll({
id: flash.id,
createdAt: flash.createdAt.toISOString(),
updatedAt: flash.updatedAt.toISOString(),
userId: flash.userId,
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意
title: flash.title,
summary: flash.summary,
script: flash.script,
likedCount: flash.likedCount,
isLiked: meId ? await this.flashLikesRepository.findOneBy({ flashId: flash.id, userId: meId }).then(x => x != null) : undefined,
});
}
@bindThis
public packMany(
flashs: Flash[],
me?: { id: User['id'] } | null | undefined,
) {
return Promise.all(flashs.map(x => this.pack(x, me)));
}
}

View File

@ -0,0 +1,44 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { FlashLikesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { FlashLike } from '@/models/entities/FlashLike.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
import { FlashEntityService } from './FlashEntityService.js';
@Injectable()
export class FlashLikeEntityService {
constructor(
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
private flashEntityService: FlashEntityService,
) {
}
@bindThis
public async pack(
src: FlashLike['id'] | FlashLike,
me?: { id: User['id'] } | null | undefined,
) {
const like = typeof src === 'object' ? src : await this.flashLikesRepository.findOneByOrFail({ id: src });
return {
id: like.id,
flash: await this.flashEntityService.pack(like.flash ?? like.flashId, me),
};
}
@bindThis
public packMany(
likes: any[],
me: { id: User['id'] },
) {
return Promise.all(likes.map(x => this.pack(x, me)));
}
}

View File

@ -69,5 +69,7 @@ export const DI = {
adsRepository: Symbol('adsRepository'), adsRepository: Symbol('adsRepository'),
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'), passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'), retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
//#endregion //#endregion
}; };

View File

@ -3,6 +3,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
const dictionary = { const dictionary = {
'safe-file': FILE_TYPE_BROWSERSAFE, 'safe-file': FILE_TYPE_BROWSERSAFE,
'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'], 'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'],
'sharp-animation-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml'],
}; };
export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime); export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime);

View File

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation } from './index.js'; import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash } from './index.js';
import type { DataSource } from 'typeorm'; import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common'; import type { Provider } from '@nestjs/common';
@ -388,6 +388,18 @@ const $retentionAggregationsRepository: Provider = {
inject: [DI.db], inject: [DI.db],
}; };
const $flashsRepository: Provider = {
provide: DI.flashsRepository,
useFactory: (db: DataSource) => db.getRepository(Flash),
inject: [DI.db],
};
const $flashLikesRepository: Provider = {
provide: DI.flashLikesRepository,
useFactory: (db: DataSource) => db.getRepository(FlashLike),
inject: [DI.db],
};
@Module({ @Module({
imports: [ imports: [
], ],
@ -456,6 +468,8 @@ const $retentionAggregationsRepository: Provider = {
$adsRepository, $adsRepository,
$passwordResetRequestsRepository, $passwordResetRequestsRepository,
$retentionAggregationsRepository, $retentionAggregationsRepository,
$flashsRepository,
$flashLikesRepository,
], ],
exports: [ exports: [
$usersRepository, $usersRepository,
@ -522,6 +536,8 @@ const $retentionAggregationsRepository: Provider = {
$adsRepository, $adsRepository,
$passwordResetRequestsRepository, $passwordResetRequestsRepository,
$retentionAggregationsRepository, $retentionAggregationsRepository,
$flashsRepository,
$flashLikesRepository,
], ],
}) })
export class RepositoryModule {} export class RepositoryModule {}

View File

@ -0,0 +1,60 @@
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
import { id } from '../id.js';
import { User } from './User.js';
import { DriveFile } from './DriveFile.js';
@Entity()
export class Flash {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the Flash.',
})
public createdAt: Date;
@Index()
@Column('timestamp with time zone', {
comment: 'The updated date of the Flash.',
})
public updatedAt: Date;
@Column('varchar', {
length: 256,
})
public title: string;
@Column('varchar', {
length: 1024,
})
public summary: string;
@Index()
@Column({
...id(),
comment: 'The ID of author.',
})
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column('varchar', {
length: 16384,
})
public script: string;
@Column('varchar', {
length: 256, array: true, default: '{}',
})
public permissions: string[];
@Column('integer', {
default: 0,
})
public likedCount: number;
}

View File

@ -0,0 +1,33 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from '../id.js';
import { User } from './User.js';
import { Flash } from './Flash.js';
@Entity()
@Index(['userId', 'flashId'], { unique: true })
export class FlashLike {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone')
public createdAt: Date;
@Index()
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Column(id())
public flashId: Flash['id'];
@ManyToOne(type => Flash, {
onDelete: 'CASCADE',
})
@JoinColumn()
public flash: Flash | null;
}

View File

@ -62,6 +62,8 @@ import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js';
import { Webhook } from '@/models/entities/Webhook.js'; import { Webhook } from '@/models/entities/Webhook.js';
import { Channel } from '@/models/entities/Channel.js'; import { Channel } from '@/models/entities/Channel.js';
import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js';
import { Flash } from '@/models/entities/Flash.js';
import { FlashLike } from '@/models/entities/FlashLike.js';
import type { Repository } from 'typeorm'; import type { Repository } from 'typeorm';
export { export {
@ -129,6 +131,8 @@ export {
Webhook, Webhook,
Channel, Channel,
RetentionAggregation, RetentionAggregation,
Flash,
FlashLike,
}; };
export type AbuseUserReportsRepository = Repository<AbuseUserReport>; export type AbuseUserReportsRepository = Repository<AbuseUserReport>;
@ -195,3 +199,5 @@ export type UserSecurityKeysRepository = Repository<UserSecurityKey>;
export type WebhooksRepository = Repository<Webhook>; export type WebhooksRepository = Repository<Webhook>;
export type ChannelsRepository = Repository<Channel>; export type ChannelsRepository = Repository<Channel>;
export type RetentionAggregationsRepository = Repository<RetentionAggregation>; export type RetentionAggregationsRepository = Repository<RetentionAggregation>;
export type FlashsRepository = Repository<Flash>;
export type FlashLikesRepository = Repository<FlashLike>;

View File

@ -70,6 +70,8 @@ import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js';
import { Webhook } from '@/models/entities/Webhook.js'; import { Webhook } from '@/models/entities/Webhook.js';
import { Channel } from '@/models/entities/Channel.js'; import { Channel } from '@/models/entities/Channel.js';
import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js';
import { Flash } from '@/models/entities/Flash.js';
import { FlashLike } from '@/models/entities/FlashLike.js';
import { Config } from '@/config.js'; import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js'; import MisskeyLogger from '@/logger.js';
@ -184,6 +186,8 @@ export const entities = [
Webhook, Webhook,
UserIp, UserIp,
RetentionAggregation, RetentionAggregation,
Flash,
FlashLike,
...charts, ...charts,
]; ];

View File

@ -79,9 +79,17 @@ export class MediaProxyServerService {
const { mime, ext } = await this.fileInfoService.detectType(path); const { mime, ext } = await this.fileInfoService.detectType(path);
const isConvertibleImage = isMimeImage(mime, 'sharp-convertible-image'); const isConvertibleImage = isMimeImage(mime, 'sharp-convertible-image');
const isAnimationConvertibleImage = isMimeImage(mime, 'sharp-animation-convertible-image');
let image: IImage; let image: IImage;
if ('emoji' in request.query && isConvertibleImage) { if ('emoji' in request.query && isConvertibleImage) {
if (!isAnimationConvertibleImage && !('static' in request.query)) {
image = {
data: fs.readFileSync(path),
ext,
type: mime,
};
} else {
const data = await sharp(path, { animated: !('static' in request.query) }) const data = await sharp(path, { animated: !('static' in request.query) })
.resize({ .resize({
height: 128, height: 128,
@ -95,6 +103,7 @@ export class MediaProxyServerService {
ext: 'webp', ext: 'webp',
type: 'image/webp', type: 'image/webp',
}; };
}
} else if ('static' in request.query && isConvertibleImage) { } else if ('static' in request.query && isConvertibleImage) {
image = await this.imageProcessingService.convertToWebp(path, 498, 280); image = await this.imageProcessingService.convertToWebp(path, 498, 280);
} else if ('preview' in request.query && isConvertibleImage) { } else if ('preview' in request.query && isConvertibleImage) {

View File

@ -1,12 +1,11 @@
import cluster from 'node:cluster'; import cluster from 'node:cluster';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as http from 'node:http';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import Fastify from 'fastify'; import Fastify from 'fastify';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { envOption } from '@/env.js'; import { envOption } from '@/env.js';
@ -39,6 +38,9 @@ export class ServerService {
@Inject(DI.userProfilesRepository) @Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository, private userProfilesRepository: UserProfilesRepository,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private apiServerService: ApiServerService, private apiServerService: ApiServerService,
private streamingApiServerService: StreamingApiServerService, private streamingApiServerService: StreamingApiServerService,
@ -77,6 +79,43 @@ export class ServerService {
fastify.register(this.nodeinfoServerService.createServer); fastify.register(this.nodeinfoServerService.createServer);
fastify.register(this.wellKnownServerService.createServer); fastify.register(this.wellKnownServerService.createServer);
fastify.get<{ Params: { path: string }; Querystring: { static?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
const path = request.params.path;
if (!path.match(/^[a-zA-Z0-9\-_@\.]+?\.webp$/)) {
reply.code(404);
return;
}
reply.header('Cache-Control', 'public, max-age=86400');
const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');
const emoji = await this.emojisRepository.findOneBy({
// `@.` is the spec of ReactionService.decodeReaction
host: (host == null || host === '.') ? IsNull() : host,
name: name,
});
reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
if (emoji == null) {
return await reply.redirect('/static-assets/emoji-unknown.png');
}
const url = new URL('/proxy/emoji.webp', this.config.url);
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
url.searchParams.set('emoji', '1');
if ('static' in request.query) url.searchParams.set('static', '1');
return await reply.redirect(
301,
url.toString(),
);
});
fastify.get<{ Params: { acct: string } }>('/avatar/@:acct', async (request, reply) => { fastify.get<{ Params: { acct: string } }>('/avatar/@:acct', async (request, reply) => {
const { username, host } = Acct.parse(request.params.acct); const { username, host } = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOne({ const user = await this.usersRepository.findOne({

View File

@ -266,6 +266,15 @@ import * as ep___pages_like from './endpoints/pages/like.js';
import * as ep___pages_show from './endpoints/pages/show.js'; import * as ep___pages_show from './endpoints/pages/show.js';
import * as ep___pages_unlike from './endpoints/pages/unlike.js'; import * as ep___pages_unlike from './endpoints/pages/unlike.js';
import * as ep___pages_update from './endpoints/pages/update.js'; import * as ep___pages_update from './endpoints/pages/update.js';
import * as ep___flash_create from './endpoints/flash/create.js';
import * as ep___flash_delete from './endpoints/flash/delete.js';
import * as ep___flash_featured from './endpoints/flash/featured.js';
import * as ep___flash_like from './endpoints/flash/like.js';
import * as ep___flash_show from './endpoints/flash/show.js';
import * as ep___flash_unlike from './endpoints/flash/unlike.js';
import * as ep___flash_update from './endpoints/flash/update.js';
import * as ep___flash_my from './endpoints/flash/my.js';
import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
import * as ep___ping from './endpoints/ping.js'; import * as ep___ping from './endpoints/ping.js';
import * as ep___pinnedUsers from './endpoints/pinned-users.js'; import * as ep___pinnedUsers from './endpoints/pinned-users.js';
import * as ep___promo_read from './endpoints/promo/read.js'; import * as ep___promo_read from './endpoints/promo/read.js';
@ -587,6 +596,15 @@ const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_l
const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default }; const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default };
const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default }; const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default };
const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default }; const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default };
const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default };
const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default };
const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default };
const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default };
const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default };
const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default };
const $flash_update: Provider = { provide: 'ep:flash/update', useClass: ep___flash_update.default };
const $flash_my: Provider = { provide: 'ep:flash/my', useClass: ep___flash_my.default };
const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___flash_myLikes.default };
const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default }; const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default };
const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default }; const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default };
const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default }; const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default };
@ -912,6 +930,15 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$pages_show, $pages_show,
$pages_unlike, $pages_unlike,
$pages_update, $pages_update,
$flash_create,
$flash_delete,
$flash_featured,
$flash_like,
$flash_show,
$flash_unlike,
$flash_update,
$flash_my,
$flash_myLikes,
$ping, $ping,
$pinnedUsers, $pinnedUsers,
$promo_read, $promo_read,
@ -1231,6 +1258,15 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$pages_show, $pages_show,
$pages_unlike, $pages_unlike,
$pages_update, $pages_update,
$flash_create,
$flash_delete,
$flash_featured,
$flash_like,
$flash_show,
$flash_unlike,
$flash_update,
$flash_my,
$flash_myLikes,
$ping, $ping,
$pinnedUsers, $pinnedUsers,
$promo_read, $promo_read,

View File

@ -265,6 +265,15 @@ import * as ep___pages_like from './endpoints/pages/like.js';
import * as ep___pages_show from './endpoints/pages/show.js'; import * as ep___pages_show from './endpoints/pages/show.js';
import * as ep___pages_unlike from './endpoints/pages/unlike.js'; import * as ep___pages_unlike from './endpoints/pages/unlike.js';
import * as ep___pages_update from './endpoints/pages/update.js'; import * as ep___pages_update from './endpoints/pages/update.js';
import * as ep___flash_create from './endpoints/flash/create.js';
import * as ep___flash_delete from './endpoints/flash/delete.js';
import * as ep___flash_featured from './endpoints/flash/featured.js';
import * as ep___flash_like from './endpoints/flash/like.js';
import * as ep___flash_show from './endpoints/flash/show.js';
import * as ep___flash_unlike from './endpoints/flash/unlike.js';
import * as ep___flash_update from './endpoints/flash/update.js';
import * as ep___flash_my from './endpoints/flash/my.js';
import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
import * as ep___ping from './endpoints/ping.js'; import * as ep___ping from './endpoints/ping.js';
import * as ep___pinnedUsers from './endpoints/pinned-users.js'; import * as ep___pinnedUsers from './endpoints/pinned-users.js';
import * as ep___promo_read from './endpoints/promo/read.js'; import * as ep___promo_read from './endpoints/promo/read.js';
@ -584,6 +593,15 @@ const eps = [
['pages/show', ep___pages_show], ['pages/show', ep___pages_show],
['pages/unlike', ep___pages_unlike], ['pages/unlike', ep___pages_unlike],
['pages/update', ep___pages_update], ['pages/update', ep___pages_update],
['flash/create', ep___flash_create],
['flash/delete', ep___flash_delete],
['flash/featured', ep___flash_featured],
['flash/like', ep___flash_like],
['flash/show', ep___flash_show],
['flash/unlike', ep___flash_unlike],
['flash/update', ep___flash_update],
['flash/my', ep___flash_my],
['flash/my-likes', ep___flash_myLikes],
['ping', ep___ping], ['ping', ep___ping],
['pinned-users', ep___pinnedUsers], ['pinned-users', ep___pinnedUsers],
['promo/read', ep___promo_read], ['promo/read', ep___promo_read],

View File

@ -8,7 +8,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: false, requireCredential: true,
requireModerator: true, requireModerator: true,
res: { res: {

View File

@ -64,8 +64,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
case '-followers': query.orderBy('instance.followersCount', 'ASC'); break; case '-followers': query.orderBy('instance.followersCount', 'ASC'); break;
case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break; case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break;
case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break;
case '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC'); break; case '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC', 'NULLS LAST'); break;
case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC'); break; case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC', 'NULLS FIRST'); break;
default: query.orderBy('instance.id', 'DESC'); break; default: query.orderBy('instance.id', 'DESC'); break;
} }

View File

@ -0,0 +1,66 @@
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import type { DriveFilesRepository, FlashsRepository, PagesRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js';
import { Page } from '@/models/entities/Page.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { DI } from '@/di-symbols.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flash'],
requireCredential: true,
kind: 'write:flash',
limit: {
duration: ms('1hour'),
max: 10,
},
errors: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
title: { type: 'string' },
summary: { type: 'string' },
script: { type: 'string' },
permissions: { type: 'array', items: {
type: 'string',
} },
},
required: ['title', 'summary', 'script', 'permissions'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
private flashEntityService: FlashEntityService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.insert({
id: this.idService.genId(),
userId: me.id,
createdAt: new Date(),
updatedAt: new Date(),
title: ps.title,
summary: ps.summary,
script: ps.script,
permissions: ps.permissions,
}).then(x => this.flashsRepository.findOneByOrFail(x.identifiers[0]));
return await this.flashEntityService.pack(flash);
});
}
}

View File

@ -0,0 +1,56 @@
import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flashs'],
requireCredential: true,
kind: 'write:flash',
errors: {
noSuchFlash: {
message: 'No such flash.',
code: 'NO_SUCH_FLASH',
id: 'de1623ef-bbb3-4289-a71e-14cfa83d9740',
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '1036ad7b-9f92-4fff-89c3-0e50dc941704',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
flashId: { type: 'string', format: 'misskey:id' },
},
required: ['flashId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
if (flash.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
await this.flashsRepository.delete(flash.id);
});
}
}

View File

@ -0,0 +1,48 @@
import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['flash'],
requireCredential: false,
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Flash',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
private flashEntityService: FlashEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.flashsRepository.createQueryBuilder('flash')
.andWhere('flash.likedCount > 0')
.orderBy('flash.likedCount', 'DESC');
const flashs = await query.take(10).getMany();
return await this.flashEntityService.packMany(flashs, me);
});
}
}

View File

@ -0,0 +1,87 @@
import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flash'],
requireCredential: true,
kind: 'write:flash-likes',
errors: {
noSuchFlash: {
message: 'No such flash.',
code: 'NO_SUCH_FLASH',
id: 'c07c1491-9161-4c5c-9d75-01906f911f73',
},
yourFlash: {
message: 'You cannot like your flash.',
code: 'YOUR_FLASH',
id: '3fd8a0e7-5955-4ba9-85bb-bf3e0c30e13b',
},
alreadyLiked: {
message: 'The flash has already been liked.',
code: 'ALREADY_LIKED',
id: '010065cf-ad43-40df-8067-abff9f4686e3',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
flashId: { type: 'string', format: 'misskey:id' },
},
required: ['flashId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
if (flash.userId === me.id) {
throw new ApiError(meta.errors.yourFlash);
}
// if already liked
const exist = await this.flashLikesRepository.findOneBy({
flashId: flash.id,
userId: me.id,
});
if (exist != null) {
throw new ApiError(meta.errors.alreadyLiked);
}
// Create like
await this.flashLikesRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
flashId: flash.id,
userId: me.id,
});
this.flashsRepository.increment({ id: flash.id }, 'likedCount', 1);
});
}
}

View File

@ -0,0 +1,68 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FlashLikesRepository } from '@/models/index.js';
import { QueryService } from '@/core/QueryService.js';
import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'flash'],
requireCredential: true,
kind: 'read:flash-likes',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
flash: {
type: 'object',
optional: false, nullable: false,
ref: 'Flash',
},
},
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
private flashLikeEntityService: FlashLikeEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId)
.andWhere('like.userId = :meId', { meId: me.id })
.leftJoinAndSelect('like.flash', 'flash');
const likes = await query
.take(ps.limit)
.getMany();
return this.flashLikeEntityService.packMany(likes, me);
});
}
}

View File

@ -0,0 +1,57 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FlashsRepository } from '@/models/index.js';
import { QueryService } from '@/core/QueryService.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'flash'],
requireCredential: true,
kind: 'read:flash',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Flash',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
private flashEntityService: FlashEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.flashsRepository.createQueryBuilder('flash'), ps.sinceId, ps.untilId)
.andWhere('flash.userId = :meId', { meId: me.id });
const flashs = await query
.take(ps.limit)
.getMany();
return await this.flashEntityService.packMany(flashs);
});
}
}

View File

@ -0,0 +1,60 @@
import { IsNull } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository, FlashsRepository } from '@/models/index.js';
import type { Flash } from '@/models/entities/Flash.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flashs'],
requireCredential: false,
res: {
type: 'object',
optional: false, nullable: false,
ref: 'Flash',
},
errors: {
noSuchFlash: {
message: 'No such flash.',
code: 'NO_SUCH_FLASH',
id: 'f0d34a1a-d29a-401d-90ba-1982122b5630',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
flashId: { type: 'string', format: 'misskey:id' },
},
required: ['flashId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
private flashEntityService: FlashEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
return await this.flashEntityService.pack(flash, me);
});
}
}

View File

@ -0,0 +1,68 @@
import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository, FlashLikesRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flash'],
requireCredential: true,
kind: 'write:flash-likes',
errors: {
noSuchFlash: {
message: 'No such flash.',
code: 'NO_SUCH_FLASH',
id: 'afe8424a-a69e-432d-a5f2-2f0740c62410',
},
notLiked: {
message: 'You have not liked that flash.',
code: 'NOT_LIKED',
id: '755f25a7-9871-4f65-9f34-51eaad9ae0ac',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
flashId: { type: 'string', format: 'misskey:id' },
},
required: ['flashId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
@Inject(DI.flashLikesRepository)
private flashLikesRepository: FlashLikesRepository,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
const exist = await this.flashLikesRepository.findOneBy({
flashId: flash.id,
userId: me.id,
});
if (exist == null) {
throw new ApiError(meta.errors.notLiked);
}
// Delete like
await this.flashLikesRepository.delete(exist.id);
this.flashsRepository.decrement({ id: flash.id }, 'likedCount', 1);
});
}
}

View File

@ -0,0 +1,78 @@
import ms from 'ms';
import { Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import type { FlashsRepository, DriveFilesRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['flash'],
requireCredential: true,
kind: 'write:flash',
limit: {
duration: ms('1hour'),
max: 300,
},
errors: {
noSuchFlash: {
message: 'No such flash.',
code: 'NO_SUCH_FLASH',
id: '611e13d2-309e-419a-a5e4-e0422da39b02',
},
accessDenied: {
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '08e60c88-5948-478e-a132-02ec701d67b2',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
flashId: { type: 'string', format: 'misskey:id' },
title: { type: 'string' },
summary: { type: 'string' },
script: { type: 'string' },
permissions: { type: 'array', items: {
type: 'string',
} },
},
required: ['flashId', 'title', 'summary', 'script', 'permissions'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.flashsRepository)
private flashsRepository: FlashsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
const flash = await this.flashsRepository.findOneBy({ id: ps.flashId });
if (flash == null) {
throw new ApiError(meta.errors.noSuchFlash);
}
if (flash.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
await this.flashsRepository.update(flash.id, {
updatedAt: new Date(),
title: ps.title,
summary: ps.summary,
script: ps.script,
permissions: ps.permissions,
});
});
}
}

View File

@ -28,7 +28,7 @@ export const meta = {
alreadyLiked: { alreadyLiked: {
message: 'The page has already been liked.', message: 'The page has already been liked.',
code: 'ALREADY_LIKED', code: 'ALREADY_LIKED',
id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', id: 'd4c1edbe-7da2-4eae-8714-1acfd2d63941',
}, },
}, },
} as const; } as const;

View File

@ -111,7 +111,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
updatedAt: new Date(), updatedAt: new Date(),
title: ps.title, title: ps.title,
name: ps.name === undefined ? page.name : ps.name, name: ps.name === undefined ? page.name : ps.name,
summary: ps.name === undefined ? page.summary : ps.summary, summary: ps.summary === undefined ? page.summary : ps.summary,
content: ps.content, content: ps.content,
variables: ps.variables, variables: ps.variables,
script: ps.script, script: ps.script,

View File

@ -1,6 +1,5 @@
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { PathOrFileDescriptor, readFileSync } from 'node:fs';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { createBullBoard } from '@bull-board/api'; import { createBullBoard } from '@bull-board/api';
import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { BullAdapter } from '@bull-board/api/bullAdapter.js';
@ -26,9 +25,10 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, EmojisRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { ChannelsRepository, ClipsRepository, EmojisRepository, FlashsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import manifest from './manifest.json' assert { type: 'json' }; import manifest from './manifest.json' assert { type: 'json' };
import { FeedService } from './FeedService.js'; import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js'; import { UrlPreviewService } from './UrlPreviewService.js';
@ -70,9 +70,10 @@ export class ClientServerService {
@Inject(DI.pagesRepository) @Inject(DI.pagesRepository)
private pagesRepository: PagesRepository, private pagesRepository: PagesRepository,
@Inject(DI.emojisRepository) @Inject(DI.flashsRepository)
private emojisRepository: EmojisRepository, private flashsRepository: FlashsRepository,
private flashEntityService: FlashEntityService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService, private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService, private pageEntityService: PageEntityService,
@ -220,44 +221,6 @@ export class ClientServerService {
return reply.sendFile('/apple-touch-icon.png', staticAssets); return reply.sendFile('/apple-touch-icon.png', staticAssets);
}); });
fastify.get<{ Params: { path: string }; Querystring: { static?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
const path = request.params.path;
if (!path.match(/^[a-zA-Z0-9\-_@\.]+?\.webp$/)) {
reply.code(404);
return;
}
reply.header('Cache-Control', 'public, max-age=86400');
const name = path.split('@')[0].replace('.webp', '');
const host = path.split('@')[1]?.replace('.webp', '');
const emoji = await this.emojisRepository.findOneBy({
// `@.` is the spec of ReactionService.decodeReaction
host: (host == null || host === '.') ? IsNull() : host,
name: name,
});
if (emoji == null) {
reply.code(404);
return;
}
reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
const url = new URL('/proxy/emoji.webp', this.config.url);
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
url.searchParams.set('emoji', '1');
if ('static' in request.query) url.searchParams.set('static', '1');
return await reply.redirect(
301,
url.toString(),
);
});
fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => { fastify.get<{ Params: { path: string } }>('/fluent-emoji/:path(.*)', async (request, reply) => {
const path = request.params.path; const path = request.params.path;
@ -352,7 +315,7 @@ export class ClientServerService {
const name = meta.name || 'Misskey'; const name = meta.name || 'Misskey';
let content = ''; let content = '';
content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">'; content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
content += `<ShortName>${name} Search</ShortName>`; content += `<ShortName>${name}</ShortName>`;
content += `<Description>${name} Search</Description>`; content += `<Description>${name} Search</Description>`;
content += '<InputEncoding>UTF-8</InputEncoding>'; content += '<InputEncoding>UTF-8</InputEncoding>';
content += `<Image width="16" height="16" type="image/x-icon">${this.config.url}/favicon.ico</Image>`; content += `<Image width="16" height="16" type="image/x-icon">${this.config.url}/favicon.ico</Image>`;
@ -545,6 +508,30 @@ export class ClientServerService {
} }
}); });
// Flash
fastify.get<{ Params: { id: string; } }>('/play/:id', async (request, reply) => {
const flash = await this.flashsRepository.findOneBy({
id: request.params.id,
});
if (flash) {
const _flash = await this.flashEntityService.pack(flash);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('flash', {
flash: _flash,
profile,
avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: flash.userId })),
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
});
} else {
return await renderBase(reply);
}
});
// Clip // Clip
// TODO: 非publicなclipのハンドリング // TODO: 非publicなclipのハンドリング
fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => { fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => {

View File

@ -31,7 +31,7 @@ html
link(rel='icon' href= icon || '/favicon.ico') link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png')
link(rel='manifest' href='/manifest.json') link(rel='manifest' href='/manifest.json')
link(rel='search' type='application/opensearchdescription+xml' title=((title || "Misskey") + " Search") href=`${url}/opensearch.xml`) link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg')
link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')

View File

@ -0,0 +1,31 @@
extends ./base
block vars
- const user = flash.user;
- const title = flash.title;
- const url = `${config.url}/play/${flash.id}`;
block title
= `${title} | ${instanceName}`
block desc
meta(name='description' content= flash.summary)
block og
meta(property='og:type' content='article')
meta(property='og:title' content= title)
meta(property='og:description' content= flash.summary)
meta(property='og:url' content= url)
meta(property='og:image' content= avatarUrl)
block meta
if profile.noCrawle
meta(name='robots' content='noindex')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
meta(name='misskey:flash-id' content=flash.id)
// todo
if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -11,7 +11,7 @@
"@rollup/plugin-alias": "4.0.2", "@rollup/plugin-alias": "4.0.2",
"@rollup/plugin-json": "6.0.0", "@rollup/plugin-json": "6.0.0",
"@rollup/pluginutils": "5.0.2", "@rollup/pluginutils": "5.0.2",
"@syuilo/aiscript": "0.12.0", "@syuilo/aiscript": "0.12.2",
"@tabler/icons": "^1.118.0", "@tabler/icons": "^1.118.0",
"@vitejs/plugin-vue": "4.0.0", "@vitejs/plugin-vue": "4.0.0",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.45",
@ -20,7 +20,8 @@
"blurhash": "2.0.4", "blurhash": "2.0.4",
"broadcast-channel": "4.19.1", "broadcast-channel": "4.19.1",
"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"chart.js": "4.1.1", "canvas-confetti": "^1.6.0",
"chart.js": "4.1.2",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "^1.3.0", "chartjs-chart-matrix": "^1.3.0",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
@ -35,7 +36,7 @@
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
"json5": "2.2.3", "json5": "2.2.3",
"katex": "0.15.6", "katex": "0.16.4",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.23.0", "mfm-js": "0.23.0",
"misskey-js": "0.0.14", "misskey-js": "0.0.14",
@ -44,7 +45,7 @@
"punycode": "2.1.1", "punycode": "2.1.1",
"querystring": "0.2.1", "querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.9.0", "rollup": "3.9.1",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "^2.8.1", "sanitize-html": "^2.8.1",
"sass": "1.57.1", "sass": "1.57.1",
@ -55,14 +56,14 @@
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.148.0", "three": "0.148.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.5.1", "tinycolor2": "1.5.2",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2", "tsconfig-paths": "4.1.2",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "4.9.4", "typescript": "4.9.4",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.0.3", "vite": "4.0.4",
"vue": "3.2.45", "vue": "3.2.45",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "next" "vuedraggable": "next"
@ -72,7 +73,7 @@
"@types/glob": "8.0.0", "@types/glob": "8.0.0",
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@types/katex": "0.14.0", "@types/katex": "0.16.0",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.2",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/sanitize-html": "^2.8.0", "@types/sanitize-html": "^2.8.0",
@ -82,16 +83,16 @@
"@types/uuid": "9.0.0", "@types/uuid": "9.0.0",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.47.1", "@typescript-eslint/eslint-plugin": "5.48.0",
"@typescript-eslint/parser": "5.47.1", "@typescript-eslint/parser": "5.48.0",
"@vue/runtime-core": "3.2.45", "@vue/runtime-core": "3.2.45",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.2.0", "cypress": "12.3.0",
"eslint": "8.31.0", "eslint": "8.31.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.8.0", "eslint-plugin-vue": "9.8.0",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"vue-eslint-parser": "^9.1.0", "vue-eslint-parser": "^9.1.0",
"vue-tsc": "^1.0.19" "vue-tsc": "^1.0.22"
} }
} }

View File

@ -6,12 +6,13 @@ import { del, get, set } from '@/scripts/idb-proxy';
import { apiUrl } from '@/config'; import { apiUrl } from '@/config';
import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { waiting, api, popup, popupMenu, success, alert } from '@/os';
import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
import { miLocalStorage } from './local-storage';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
type Account = misskey.entities.MeDetailed; type Account = misskey.entities.MeDetailed;
const accountData = localStorage.getItem('account'); const accountData = miLocalStorage.getItem('account');
// TODO: 外部からはreadonlyに // TODO: 外部からはreadonlyに
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
@ -21,7 +22,7 @@ export const iAmAdmin = $i != null && $i.isAdmin;
export async function signout() { export async function signout() {
waiting(); waiting();
localStorage.removeItem('account'); miLocalStorage.removeItem('account');
await removeAccount($i.id); await removeAccount($i.id);
@ -119,7 +120,7 @@ export function updateAccount(accountData) {
for (const [key, value] of Object.entries(accountData)) { for (const [key, value] of Object.entries(accountData)) {
$i[key] = value; $i[key] = value;
} }
localStorage.setItem('account', JSON.stringify($i)); miLocalStorage.setItem('account', JSON.stringify($i));
} }
export function refreshAccount() { export function refreshAccount() {
@ -130,7 +131,7 @@ export async function login(token: Account['token'], redirect?: string) {
waiting(); waiting();
if (_DEV_) console.log('logging as token ', token); if (_DEV_) console.log('logging as token ', token);
const me = await fetchAccount(token); const me = await fetchAccount(token);
localStorage.setItem('account', JSON.stringify(me)); miLocalStorage.setItem('account', JSON.stringify(me));
document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う
await addAccount(me.id, token); await addAccount(me.id, token);

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="bcekxzvu _gap _panel"> <div class="bcekxzvu _margin _panel">
<div class="target"> <div class="target">
<MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`"> <MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`">
<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true" :disable-link="true"/> <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true" :disable-link="true"/>
@ -8,7 +8,7 @@
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/> <MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
</div> </div>
</MkA> </MkA>
<MkKeyValue class="_formBlock"> <MkKeyValue>
<template #key>{{ i18n.ts.registeredDate }}</template> <template #key>{{ i18n.ts.registeredDate }}</template>
<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template> <template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
</MkKeyValue> </MkKeyValue>
@ -37,7 +37,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/form/switch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import { acct, userPage } from '@/filters/user'; import { acct, userPage } from '@/filters/user';
import * as os from '@/os'; import * as os from '@/os';

View File

@ -1,5 +1,5 @@
<template> <template>
<XWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <MkWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
<template #header> <template #header>
<i class="ti ti-exclamation-circle" style="margin-right: 0.5em;"></i> <i class="ti ti-exclamation-circle" style="margin-right: 0.5em;"></i>
<I18n :src="i18n.ts.reportAbuseOf" tag="span"> <I18n :src="i18n.ts.reportAbuseOf" tag="span">
@ -8,25 +8,27 @@
</template> </template>
</I18n> </I18n>
</template> </template>
<div class="dpvffvvy _monolithic_"> <MkSpacer :margin-min="20" :margin-max="28">
<div class="_section"> <div class="dpvffvvy _gaps_m">
<div class="">
<MkTextarea v-model="comment"> <MkTextarea v-model="comment">
<template #label>{{ i18n.ts.details }}</template> <template #label>{{ i18n.ts.details }}</template>
<template #caption>{{ i18n.ts.fillAbuseReportDescription }}</template> <template #caption>{{ i18n.ts.fillAbuseReportDescription }}</template>
</MkTextarea> </MkTextarea>
</div> </div>
<div class="_section"> <div class="">
<MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton> <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.ts.send }}</MkButton>
</div> </div>
</div> </div>
</XWindow> </MkSpacer>
</MkWindow>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, shallowRef } from 'vue'; import { ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/form/textarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -40,7 +42,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const uiWindow = shallowRef<InstanceType<typeof XWindow>>(); const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
const comment = ref(props.initialComment || ''); const comment = ref(props.initialComment || '');
function send() { function send() {

View File

@ -0,0 +1,114 @@
<template>
<div>
<div v-if="c.type === 'root'" :class="$style.root">
<template v-for="child in c.children" :key="child">
<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
</template>
</div>
<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, color: c.color ?? null }" :text="c.text"/>
<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="c.onClick">{{ c.text }}</MkButton>
<div v-else-if="c.type === 'buttons'" class="_buttons">
<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>
</div>
<MkSwitch v-else-if="c.type === 'switch'" :model-value="valueForSwitch" @update:model-value="onSwitchUpdate">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
</MkSwitch>
<MkTextarea v-else-if="c.type === 'textarea'" :model-value="c.default" @update:model-value="c.onInput">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
</MkTextarea>
<MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onInput">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
</MkInput>
<MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :model-value="c.default" type="number" @update:model-value="c.onInput">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
</MkInput>
<MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onChange">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
</MkSelect>
<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="openPostForm">{{ c.text }}</MkButton>
<FormFolder v-else-if="c.type === 'folder'" :default-open="c.opened">
<template #label>{{ c.title }}</template>
<template v-for="child in c.children" :key="child">
<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
</template>
</FormFolder>
<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace', [$style.containerCenter]: c.align === 'center' }]" :style="{ backgroundColor: c.bgColor ?? null, color: c.fgColor ?? null, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }">
<template v-for="child in c.children" :key="child">
<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
</template>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, onUnmounted, Ref } from 'vue';
import * as os from '@/os';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
import { AsUiComponent } from '@/scripts/aiscript/ui';
import FormFolder from '@/components/form/folder.vue';
const props = withDefaults(defineProps<{
component: AsUiComponent;
components: Ref<AsUiComponent>[];
size: 'small' | 'medium' | 'large';
}>(), {
size: 'medium',
});
const c = props.component;
function g(id) {
return props.components.find(x => x.value.id === id).value;
}
let valueForSwitch = $ref(c.default ?? false);
function onSwitchUpdate(v) {
valueForSwitch = v;
if (c.onChange) c.onChange(v);
}
function openPostForm() {
os.post({
initialText: c.form.text,
instant: true,
});
}
</script>
<style lang="scss" module>
.root {
display: flex;
flex-direction: column;
gap: 12px;
}
.container {
display: flex;
flex-direction: column;
gap: 12px;
}
.containerCenter {
text-align: center;
}
.fontSerif {
font-family: serif;
}
.fontMonospace {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
</style>

View File

@ -46,6 +46,7 @@ import { defaultStore } from '@/store';
import { emojilist } from '@/scripts/emojilist'; import { emojilist } from '@/scripts/emojilist';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { miLocalStorage } from '@/local-storage';
type EmojiDef = { type EmojiDef = {
emoji: string; emoji: string;
@ -208,7 +209,7 @@ function exec() {
} }
} else if (props.type === 'hashtag') { } else if (props.type === 'hashtag') {
if (!props.q || props.q === '') { if (!props.q || props.q === '') {
hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]'); hashtags.value = JSON.parse(miLocalStorage.getItem('hashtags') || '[]');
fetching.value = false; fetching.value = false;
} else { } else {
const cacheKey = `autocomplete:hashtag:${props.q}`; const cacheKey = `autocomplete:hashtag:${props.q}`;

View File

@ -2,7 +2,7 @@
<button <button
v-if="!link" v-if="!link"
ref="el" class="bghgjjyj _button" ref="el" class="bghgjjyj _button"
:class="{ inline, primary, gradate, danger, rounded, full, small }" :class="{ inline, primary, gradate, danger, rounded, full, small, large, asLike }"
:type="type" :type="type"
@click="emit('click', $event)" @click="emit('click', $event)"
@mousedown="onMousedown" @mousedown="onMousedown"
@ -41,6 +41,8 @@ const props = defineProps<{
danger?: boolean; danger?: boolean;
full?: boolean; full?: boolean;
small?: boolean; small?: boolean;
large?: boolean;
asLike?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -131,6 +133,11 @@ function onMousedown(evt: MouseEvent): void {
padding: 6px 12px; padding: 6px 12px;
} }
&.large {
font-size: 100%;
padding: 8px 16px;
}
&.full { &.full {
width: 100%; width: 100%;
} }
@ -153,6 +160,37 @@ function onMousedown(evt: MouseEvent): void {
} }
} }
&.asLike {
background: rgba(255, 86, 125, 0.07);
color: #ff002f;
&:not(:disabled):hover {
background: rgba(255, 74, 116, 0.11);
}
&:not(:disabled):active {
background: rgba(224, 57, 96, 0.125);
}
> .ripples {
::v-deep(div) {
background: rgba(255, 60, 106, 0.15);
}
}
&.primary {
background: rgb(241 97 132);
&:not(:disabled):hover {
background: rgb(241 92 128);
}
&:not(:disabled):active {
background: rgb(241 92 128);
}
}
}
&.gradate { &.gradate {
font-weight: bold; font-weight: bold;
color: var(--fgOnAccent) !important; color: var(--fgOnAccent) !important;

View File

@ -16,7 +16,6 @@
*/ */
import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue'; import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import { enUS } from 'date-fns/locale';
import gradient from 'chartjs-plugin-gradient'; import gradient from 'chartjs-plugin-gradient';
import * as os from '@/os'; import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
@ -186,6 +185,10 @@ const render = () => {
time: { time: {
stepSize: 1, stepSize: 1,
unit: props.span === 'day' ? 'month' : 'day', unit: props.span === 'day' ? 'month' : 'day',
displayFormats: {
day: 'M/d',
month: 'Y/M',
},
}, },
grid: { grid: {
}, },
@ -194,11 +197,6 @@ const render = () => {
maxRotation: 0, maxRotation: 0,
autoSkipPadding: 16, autoSkipPadding: 16,
}, },
adapters: {
date: {
locale: enUS,
},
},
min: getDate(props.limit).getTime(), min: getDate(props.limit).getTime(),
}, },
y: { y: {

View File

@ -59,7 +59,7 @@ defineExpose({
&.disabled { &.disabled {
text-decoration: line-through; text-decoration: line-through;
opacity: 0.6; opacity: 0.5;
} }
> .box { > .box {
@ -72,4 +72,11 @@ defineExpose({
} }
} }
} }
@container (max-width: 500px) {
.root {
font-size: 90%;
gap: 6px;
}
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<XModalWindow <MkModalWindow
ref="dialogEl" ref="dialogEl"
:width="800" :width="800"
:height="500" :height="500"
@ -22,7 +22,7 @@
</div> </div>
</div> </div>
</template> </template>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -30,7 +30,7 @@ import { nextTick, onMounted } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os'; import * as os from '@/os';
import { $i } from '@/account'; import { $i } from '@/account';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
@ -50,7 +50,7 @@ const props = defineProps<{
}>(); }>();
const imgUrl = getProxiedImageUrl(props.file.url); const imgUrl = getProxiedImageUrl(props.file.url);
let dialogEl = $shallowRef<InstanceType<typeof XModalWindow>>(); let dialogEl = $shallowRef<InstanceType<typeof MkModalWindow>>();
let imgEl = $shallowRef<HTMLImageElement>(); let imgEl = $shallowRef<HTMLImageElement>();
let cropper: Cropper | null = null; let cropper: Cropper | null = null;
let loading = $ref(true); let loading = $ref(true);

View File

@ -42,8 +42,8 @@
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'; import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue'; import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue'; import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/form/select.vue'; import MkSelect from '@/components/MkSelect.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
type Input = { type Input = {

View File

@ -0,0 +1,109 @@
<template>
<div class="_panel _shadow" :class="$style.root">
<!-- TODO: インスタンス運営者が任意のテキストとリンクを設定できるようにする -->
<div :class="$style.icon">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pig-money" width="40" height="40" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M15 11v.01"></path>
<path d="M5.173 8.378a3 3 0 1 1 4.656 -1.377"></path>
<path d="M16 4v3.803a6.019 6.019 0 0 1 2.658 3.197h1.341a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-1.342c-.336 .95 -.907 1.8 -1.658 2.473v2.027a1.5 1.5 0 0 1 -3 0v-.583a6.04 6.04 0 0 1 -1 .083h-4a6.04 6.04 0 0 1 -1 -.083v.583a1.5 1.5 0 0 1 -3 0v-2l.001 -.027a6 6 0 0 1 3.999 -10.473h2.5l4.5 -3h.001z"></path>
</svg>
</div>
<div :class="$style.main">
<div :class="$style.title">{{ i18n.ts.didYouLikeMisskey }}</div>
<div :class="$style.text">
<I18n :src="i18n.ts.pleaseDonate" tag="span">
<template #host>
{{ $instance.name ?? host }}
</template>
</I18n>
<div style="margin-top: 0.2em;">
<MkLink target="_blank" url="https://misskey-hub.net/docs/donate.html">{{ i18n.ts.learnMore }}</MkLink>
</div>
</div>
<div class="_buttons">
<MkButton @click="close">{{ i18n.ts.remindMeLater }}</MkButton>
<MkButton @click="neverShow">{{ i18n.ts.neverShow }}</MkButton>
</div>
</div>
<button class="_button" :class="$style.close" @click="close"><i class="ti ti-x"></i></button>
</div>
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkLink from '@/components/MkLink.vue';
import { host } from '@/config';
import { i18n } from '@/i18n';
import * as os from '@/os';
import { miLocalStorage } from '@/local-storage';
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const zIndex = os.claimZIndex('low');
function close() {
miLocalStorage.setItem('latestDonationInfoShownAt', Date.now().toString());
emit('closed');
}
function neverShow() {
miLocalStorage.setItem('neverShowDonationInfo', 'true')
close();
}
</script>
<style lang="scss" module>
.root {
position: fixed;
z-index: v-bind(zIndex);
bottom: var(--margin);
left: 0;
right: 0;
margin: auto;
box-sizing: border-box;
width: calc(100% - (var(--margin) * 2));
max-width: 500px;
display: flex;
}
.icon {
text-align: center;
padding-top: 25px;
width: 100px;
color: var(--accent);
}
@media (max-width: 500px) {
.icon {
width: 80px;
}
}
@media (max-width: 450px) {
.icon {
width: 70px;
}
}
.main {
padding: 25px 25px 25px 0;
flex: 1;
}
.close {
position: absolute;
top: 8px;
right: 8px;
padding: 8px;
}
.title {
font-weight: bold;
}
.text {
margin: 0.7em 0 1em 0;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<XModalWindow <MkModalWindow
ref="dialog" ref="dialog"
:width="800" :width="800"
:height="500" :height="500"
@ -15,14 +15,14 @@
<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span>
</template> </template>
<XDrive :multiple="multiple" :select="type" @change-selection="onChangeSelection" @selected="ok()"/> <XDrive :multiple="multiple" :select="type" @change-selection="onChangeSelection" @selected="ok()"/>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef } from 'vue'; import { ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue'; import XDrive from '@/components/MkDrive.vue';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import number from '@/filters/number'; import number from '@/filters/number';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -38,7 +38,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialog = shallowRef<InstanceType<typeof XModalWindow>>(); const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const selected = ref<Misskey.entities.DriveFile[]>([]); const selected = ref<Misskey.entities.DriveFile[]>([]);

View File

@ -1,5 +1,5 @@
<template> <template>
<XWindow <MkWindow
ref="window" ref="window"
:initial-width="800" :initial-width="800"
:initial-height="500" :initial-height="500"
@ -10,14 +10,14 @@
{{ i18n.ts.drive }} {{ i18n.ts.drive }}
</template> </template>
<XDrive :initial-folder="initialFolder"/> <XDrive :initial-folder="initialFolder"/>
</XWindow> </MkWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue'; import XDrive from '@/components/MkDrive.vue';
import XWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
defineProps<{ defineProps<{

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()"> <input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
<div ref="emojis" class="emojis"> <div ref="emojis" class="emojis">
<section class="result"> <section class="result">
@ -94,6 +94,7 @@ const props = withDefaults(defineProps<{
asReactionPicker?: boolean; asReactionPicker?: boolean;
maxHeight?: number; maxHeight?: number;
asDrawer?: boolean; asDrawer?: boolean;
asWindow?: boolean;
}>(), { }>(), {
showPinned: true, showPinned: true,
}); });
@ -440,6 +441,28 @@ defineExpose({
} }
} }
&.asWindow {
width: 100% !important;
height: 100% !important;
> .emojis {
::v-deep(section) {
> .body {
display: grid;
grid-template-columns: var(--columns);
font-size: 30px;
> .item {
aspect-ratio: 1 / 1;
width: auto;
height: auto;
min-width: 0;
}
}
}
}
}
> .search { > .search {
width: 100%; width: 100%;
padding: 12px; padding: 12px;

View File

@ -1,13 +1,13 @@
<template> <template>
<MkWindow ref="window" <MkWindow ref="window"
:initial-width="null" :initial-width="300"
:initial-height="null" :initial-height="290"
:can-resize="false" :can-resize="true"
:mini="true" :mini="true"
:front="true" :front="true"
@closed="emit('closed')" @closed="emit('closed')"
> >
<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" as-window @chosen="chosen" :class="$style.picker"/>
</MkWindow> </MkWindow>
</template> </template>
@ -34,147 +34,8 @@ function chosen(emoji: any) {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.omfetrab { .picker {
$pad: 8px; height: 100%;
--eachSize: 40px;
display: flex;
flex-direction: column;
contain: content;
&.big {
--eachSize: 44px;
}
&.w1 {
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
}
&.w2 {
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
}
&.w3 {
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
}
&.h1 {
--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
}
&.h2 {
--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
}
&.h3 {
--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
}
> .search {
width: 100%;
padding: 12px;
box-sizing: border-box;
font-size: 1em;
outline: none;
border: none;
background: transparent;
color: var(--fg);
&:not(.filled) {
order: 1;
z-index: 2;
box-shadow: 0px -1px 0 0px var(--divider);
}
}
> .emojis {
height: var(--height);
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
> .index {
min-height: var(--height);
position: relative;
border-bottom: solid 0.5px var(--divider);
> .arrow {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 16px 0;
text-align: center;
opacity: 0.5;
pointer-events: none;
}
}
section {
> header {
position: sticky;
top: 0;
left: 0;
z-index: 1;
padding: 8px;
font-size: 12px;
}
> div {
padding: $pad;
> button {
position: relative;
padding: 0;
width: var(--eachSize);
height: var(--eachSize);
border-radius: 4px;
&:focus-visible {
outline: solid 2px var(--focus);
z-index: 1;
}
&:hover {
background: rgba(0, 0, 0, 0.05);
}
&:active {
background: var(--accent);
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
}
> * {
font-size: 24px;
height: 1.25em;
vertical-align: -.25em;
pointer-events: none;
}
}
}
&.result {
border-bottom: solid 0.5px var(--divider);
&:empty {
display: none;
}
}
&.unicode {
min-height: 384px;
}
&.custom {
min-height: 64px;
}
}
}
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<XModalWindow <MkModalWindow
ref="dialog" ref="dialog"
:width="400" :width="400"
:height="450" :height="450"
@ -16,14 +16,14 @@
<template #label>{{ i18n.ts.caption }}</template> <template #label>{{ i18n.ts.caption }}</template>
</MkTextarea> </MkTextarea>
</MkSpacer> </MkSpacer>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import MkTextarea from '@/components/form/textarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -37,7 +37,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialog = $shallowRef<InstanceType<typeof XModalWindow>>(); const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
let caption = $ref(props.default); let caption = $ref(props.default);

View File

@ -54,8 +54,6 @@ const props = defineProps<{
} }
.urempief { .urempief {
margin-top: var(--margin);
&.list { &.list {
> .file { > .file {
display: flex; display: flex;
@ -89,7 +87,6 @@ const props = defineProps<{
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
grid-gap: 12px; grid-gap: 12px;
margin: var(--margin) 0;
> .file { > .file {
position: relative; position: relative;

View File

@ -0,0 +1,112 @@
<template>
<MkA :to="`/play/${flash.id}`" class="vhpxefrk _panel" tabindex="-1">
<article>
<header>
<h1 :title="flash.title">{{ flash.title }}</h1>
</header>
<p v-if="flash.summary" :title="flash.summary">{{ flash.summary.length > 85 ? flash.summary.slice(0, 85) + '…' : flash.summary }}</p>
<footer>
<img class="icon" :src="flash.user.avatarUrl"/>
<p>{{ userName(flash.user) }}</p>
</footer>
</article>
</MkA>
</template>
<script lang="ts" setup>
import { } from 'vue';
import * as misskey from 'misskey-js';
import { userName } from '@/filters/user';
import * as os from '@/os';
const props = defineProps<{
//flash: misskey.entities.Flash;
flash: any;
}>();
</script>
<style lang="scss" scoped>
.vhpxefrk {
display: block;
&:hover {
text-decoration: none;
color: var(--accent);
}
> article {
padding: 16px;
> header {
margin-bottom: 8px;
> h1 {
margin: 0;
font-size: 1em;
color: var(--urlPreviewTitle);
}
}
> p {
margin: 0;
color: var(--urlPreviewText);
font-size: 0.8em;
}
> footer {
margin-top: 8px;
height: 16px;
> img {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 4px;
vertical-align: top;
}
> p {
display: inline-block;
margin: 0;
color: var(--urlPreviewInfo);
font-size: 0.8em;
line-height: 16px;
vertical-align: top;
}
}
}
@media (max-width: 700px) {
}
@media (max-width: 550px) {
font-size: 12px;
> article {
padding: 12px;
}
}
@media (max-width: 500px) {
font-size: 10px;
> article {
padding: 8px;
> header {
margin-bottom: 4px;
}
> footer {
margin-top: 4px;
> img {
width: 12px;
height: 12px;
}
}
}
}
}
</style>

View File

@ -25,8 +25,9 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { miLocalStorage } from '@/local-storage';
const localStoragePrefix = 'ui:folder:'; const miLocalStoragePrefix = 'ui:folder:' as const;
export default defineComponent({ export default defineComponent({
props: { props: {
@ -44,13 +45,13 @@ export default defineComponent({
data() { data() {
return { return {
bg: null, bg: null,
showBody: (this.persistKey && localStorage.getItem(localStoragePrefix + this.persistKey)) ? localStorage.getItem(localStoragePrefix + this.persistKey) === 't' : this.expanded, showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
}; };
}, },
watch: { watch: {
showBody() { showBody() {
if (this.persistKey) { if (this.persistKey) {
localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f'); miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f');
} }
}, },
}, },

View File

@ -1,5 +1,6 @@
<template> <template>
<XModalWindow ref="dialog" <MkModalWindow
ref="dialog"
:width="370" :width="370"
:height="400" :height="400"
@close="dialog.close()" @close="dialog.close()"
@ -8,18 +9,18 @@
<template #header>{{ i18n.ts.forgotPassword }}</template> <template #header>{{ i18n.ts.forgotPassword }}</template>
<form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit">
<div class="main _formRoot"> <div class="main _gaps_m">
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required> <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
<template #label>{{ i18n.ts.username }}</template> <template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template> <template #prefix>@</template>
</MkInput> </MkInput>
<MkInput v-model="email" class="_formBlock" type="email" :spellcheck="false" required> <MkInput v-model="email" type="email" :spellcheck="false" required>
<template #label>{{ i18n.ts.emailAddress }}</template> <template #label>{{ i18n.ts.emailAddress }}</template>
<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template> <template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
</MkInput> </MkInput>
<MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton> <MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton>
</div> </div>
<div class="sub"> <div class="sub">
<MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA> <MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA>
@ -28,14 +29,14 @@
<div v-else class="bafecedb"> <div v-else class="bafecedb">
{{ i18n.ts._forgotPassword.contactAdmin }} {{ i18n.ts._forgotPassword.contactAdmin }}
</div> </div>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue'; import MkInput from '@/components/MkInput.vue';
import * as os from '@/os'; import * as os from '@/os';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -45,7 +46,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
let dialog: InstanceType<typeof XModalWindow> = $ref(); let dialog: InstanceType<typeof MkModalWindow> = $ref();
let username = $ref(''); let username = $ref('');
let email = $ref(''); let email = $ref('');

View File

@ -1,5 +1,5 @@
<template> <template>
<XModalWindow <MkModalWindow
ref="dialog" ref="dialog"
:width="450" :width="450"
:can-close="false" :can-close="false"
@ -15,66 +15,66 @@
</template> </template>
<MkSpacer :margin-min="20" :margin-max="32"> <MkSpacer :margin-min="20" :margin-max="32">
<div class="xkpnjxcv _formRoot"> <div class="xkpnjxcv _gaps_m">
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)"> <template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
<FormInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1" class="_formBlock"> <MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormInput> </MkInput>
<FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text" class="_formBlock"> <MkInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormInput> </MkInput>
<FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]" class="_formBlock"> <MkTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormTextarea> </MkTextarea>
<FormSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]" class="_formBlock"> <MkSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]">
<span v-text="form[item].label || item"></span> <span v-text="form[item].label || item"></span>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormSwitch> </MkSwitch>
<FormSelect v-else-if="form[item].type === 'enum'" v-model="values[item]" class="_formBlock"> <MkSelect v-else-if="form[item].type === 'enum'" v-model="values[item]">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option> <option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option>
</FormSelect> </MkSelect>
<FormRadios v-else-if="form[item].type === 'radio'" v-model="values[item]" class="_formBlock"> <MkRadios v-else-if="form[item].type === 'radio'" v-model="values[item]">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option> <option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
</FormRadios> </MkRadios>
<FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter" class="_formBlock"> <MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormRange> </MkRange>
<MkButton v-else-if="form[item].type === 'button'" class="_formBlock" @click="form[item].action($event, values)"> <MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)">
<span v-text="form[item].content || item"></span> <span v-text="form[item].content || item"></span>
</MkButton> </MkButton>
</template> </template>
</div> </div>
</MkSpacer> </MkSpacer>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import FormInput from './form/input.vue'; import MkInput from './MkInput.vue';
import FormTextarea from './form/textarea.vue'; import MkTextarea from './MkTextarea.vue';
import FormSwitch from './form/switch.vue'; import MkSwitch from './MkSwitch.vue';
import FormSelect from './form/select.vue'; import MkSelect from './MkSelect.vue';
import FormRange from './form/range.vue'; import MkRange from './MkRange.vue';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';
import FormRadios from './form/radios.vue'; import MkRadios from './MkRadios.vue';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XModalWindow, MkModalWindow,
FormInput, MkInput,
FormTextarea, MkTextarea,
FormSwitch, MkSwitch,
FormSelect, MkSelect,
FormRange, MkRange,
MkButton, MkButton,
FormRadios, MkRadios,
}, },
props: { props: {

View File

@ -10,7 +10,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import { enUS } from 'date-fns/locale';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
import * as os from '@/os'; import * as os from '@/os';
@ -149,7 +148,9 @@ async function renderChart() {
round: 'week', round: 'week',
isoWeekday: 0, isoWeekday: 0,
displayFormats: { displayFormats: {
week: 'MMM dd', day: 'M/d',
month: 'Y/M',
week: 'M/d',
}, },
}, },
grid: { grid: {

View File

@ -78,8 +78,8 @@ const inputEl = shallowRef<HTMLElement>();
const prefixEl = shallowRef<HTMLElement>(); const prefixEl = shallowRef<HTMLElement>();
const suffixEl = shallowRef<HTMLElement>(); const suffixEl = shallowRef<HTMLElement>();
const height = const height =
props.small ? 35 : props.small ? 34 :
props.large ? 39 : props.large ? 40 :
37; 37;
const focus = () => inputEl.value.focus(); const focus = () => inputEl.value.focus();

View File

@ -78,7 +78,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import MkSelect from '@/components/form/select.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue'; import MkChart from '@/components/MkChart.vue';
import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { useChartTooltip } from '@/scripts/use-chart-tooltip';
import * as os from '@/os'; import * as os from '@/os';

View File

@ -50,7 +50,7 @@ const menu = defaultStore.state.menu;
const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
type: def.to ? 'link' : 'button', type: def.to ? 'link' : 'button',
text: i18n.ts[def.title], text: def.title,
icon: def.icon, icon: def.icon,
to: def.to, to: def.to,
action: def.action, action: def.action,

View File

@ -31,7 +31,7 @@
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> <span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
</button> </button>
<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch> <MkSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</MkSwitch>
</span> </span>
<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)"> <button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
<i v-if="item.icon" class="ti-fw" :class="item.icon"></i> <i v-if="item.icon" class="ti-fw" :class="item.icon"></i>
@ -58,7 +58,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus'; import { focusPrev, focusNext } from '@/scripts/focus';
import FormSwitch from '@/components/form/switch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu'; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -217,6 +217,7 @@ onBeforeUnmount(() => {
content: ""; content: "";
display: block; display: block;
position: absolute; position: absolute;
z-index: -1;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;

View File

@ -267,9 +267,8 @@ defineExpose({
} }
> .content { > .content {
transform-style: preserve-3d; transform: translateY(0px);
transform: perspective(50cm) translateZ(0px) translateY(0px) rotateX(0deg); transition: opacity 0.3s ease-in, transform 0.3s cubic-bezier(.5,-0.5,1,.5) !important;
transition: opacity 0.4s cubic-bezier(.5,-0.5,.75,1), transform 0.4s cubic-bezier(.5,-0.5,.75,1) !important;
} }
} }
.send-enter-from, .send-leave-to { .send-enter-from, .send-leave-to {
@ -280,8 +279,7 @@ defineExpose({
> .content { > .content {
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
transform-style: preserve-3d; transform: translateY(-300px);
transform: perspective(50cm) translateZ(-300px) translateY(-200px) rotateX(40deg);
} }
} }
@ -383,7 +381,6 @@ defineExpose({
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%); mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
overflow: auto; overflow: auto;
display: flex; display: flex;
container-type: inline-size;
@media (max-width: 500px) { @media (max-width: 500px) {
padding: 16px; padding: 16px;

View File

@ -1,6 +1,6 @@
<template> <template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div ref="rootEl" class="hrmcaedk _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> <div ref="rootEl" class="hrmcaedk" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header" @contextmenu="onContextmenu"> <div class="header" @contextmenu="onContextmenu">
<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button> <button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button>
<span v-else style="display: inline-block; width: 20px"></span> <span v-else style="display: inline-block; width: 20px"></span>

View File

@ -1,6 +1,6 @@
<template> <template>
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
<div ref="rootEl" class="ebkgoccj _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> <div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
<div ref="headerEl" class="header"> <div ref="headerEl" class="header">
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
<span class="title"> <span class="title">
@ -89,6 +89,7 @@ defineExpose({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
contain: content; contain: content;
container-type: inline-size;
border-radius: var(--radius); border-radius: var(--radius);
--root-margin: 24px; --root-margin: 24px;

View File

@ -4,7 +4,7 @@
v-show="!isDeleted" v-show="!isDeleted"
ref="el" ref="el"
v-hotkey="keymap" v-hotkey="keymap"
class="lxwezrsl _block" class="lxwezrsl"
:tabindex="!isDeleted ? '-1' : null" :tabindex="!isDeleted ? '-1' : null"
:class="{ renote: isRenote }" :class="{ renote: isRenote }"
> >

View File

@ -1,5 +1,5 @@
<template> <template>
<XModalWindow <MkModalWindow
ref="dialog" ref="dialog"
:width="400" :width="400"
:height="450" :height="450"
@ -12,33 +12,33 @@
<template #header>{{ i18n.ts.notificationSetting }}</template> <template #header>{{ i18n.ts.notificationSetting }}</template>
<MkSpacer :margin-min="20" :margin-max="28"> <MkSpacer :margin-min="20" :margin-max="28">
<div class="_formRoot"> <div class="_gaps_m">
<template v-if="showGlobalToggle"> <template v-if="showGlobalToggle">
<MkSwitch v-model="useGlobalSetting" class="_formBlock"> <MkSwitch v-model="useGlobalSetting">
{{ i18n.ts.useGlobalSetting }} {{ i18n.ts.useGlobalSetting }}
<template #caption>{{ i18n.ts.useGlobalSettingDesc }}</template> <template #caption>{{ i18n.ts.useGlobalSettingDesc }}</template>
</MkSwitch> </MkSwitch>
</template> </template>
<template v-if="!useGlobalSetting"> <template v-if="!useGlobalSetting">
<MkInfo class="_formBlock">{{ i18n.ts.notificationSettingDesc }}</MkInfo> <MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> <div class="_buttons">
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton> <MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton> <MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
</div> </div>
<MkSwitch v-for="ntype in notificationTypes" class="_formBlock" :key="ntype" v-model="typesMap[ntype]">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch> <MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype]">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
</template> </template>
</div> </div>
</MkSpacer> </MkSpacer>
</XModalWindow> </MkModalWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import { notificationTypes } from 'misskey-js'; import { notificationTypes } from 'misskey-js';
import MkSwitch from './form/switch.vue'; import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue'; import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';
import XModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
const emit = defineEmits<{ const emit = defineEmits<{
@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{
let includingTypes = $computed(() => props.includingTypes || []); let includingTypes = $computed(() => props.includingTypes || []);
const dialog = $shallowRef<InstanceType<typeof XModalWindow>>(); const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
let typesMap = $ref<Record<typeof notificationTypes[number], boolean>>({}); let typesMap = $ref<Record<typeof notificationTypes[number], boolean>>({});
let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle); let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);

View File

@ -1,5 +1,5 @@
<template> <template>
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj _block" tabindex="-1"> <MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<div v-if="page.eyeCatchingImage" class="thumbnail" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div> <div v-if="page.eyeCatchingImage" class="thumbnail" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<article> <article>
<header> <header>
@ -14,22 +14,15 @@
</MkA> </MkA>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import * as misskey from 'misskey-js';
import { userName } from '@/filters/user'; import { userName } from '@/filters/user';
import * as os from '@/os'; import * as os from '@/os';
export default defineComponent({ const props = defineProps<{
props: { page: misskey.entities.Page;
page: { }>();
type: Object,
required: true,
},
},
methods: {
userName,
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,5 +1,5 @@
<template> <template>
<XWindow <MkWindow
ref="windowEl" ref="windowEl"
:initial-width="500" :initial-width="500"
:initial-height="500" :initial-height="500"
@ -20,13 +20,13 @@
<div class="yrolvcoq" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;"> <div class="yrolvcoq" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;">
<RouterView :router="router"/> <RouterView :router="router"/>
</div> </div>
</XWindow> </MkWindow>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, inject, provide } from 'vue'; import { ComputedRef, inject, provide } from 'vue';
import RouterView from '@/components/global/RouterView.vue'; import RouterView from '@/components/global/RouterView.vue';
import XWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout'; import { popout as _popout } from '@/scripts/popout';
import copyToClipboard from '@/scripts/copy-to-clipboard'; import copyToClipboard from '@/scripts/copy-to-clipboard';
import { url } from '@/config'; import { url } from '@/config';
@ -47,7 +47,7 @@ defineEmits<{
const router = new Router(routes, props.initialPath); const router = new Router(routes, props.initialPath);
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let windowEl = $shallowRef<InstanceType<typeof XWindow>>(); let windowEl = $shallowRef<InstanceType<typeof MkWindow>>();
const history = $ref<{ path: string; key: any; }[]>([{ const history = $ref<{ path: string; key: any; }[]>([{
path: router.getCurrentPath(), path: router.getCurrentPath(),
key: router.getCurrentKey(), key: router.getCurrentKey(),
@ -84,6 +84,7 @@ provideMetadataReceiver((info) => {
}); });
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true); provide('shouldHeaderThin', true);
provide('forceSpacerMin', true);
const contextmenu = $computed(() => ([{ const contextmenu = $computed(() => ([{
icon: 'ti ti-player-eject', icon: 'ti ti-player-eject',
@ -136,5 +137,7 @@ defineExpose({
.yrolvcoq { .yrolvcoq {
min-height: 100%; min-height: 100%;
background: var(--bg); background: var(--bg);
--margin: var(--marginHalf);
} }
</style> </style>

View File

@ -14,14 +14,14 @@
</div> </div>
<div v-else ref="rootEl"> <div v-else ref="rootEl">
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> <div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _margin">
<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead"> <MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
{{ i18n.ts.loadMore }} {{ i18n.ts.loadMore }}
</MkButton> </MkButton>
<MkLoading v-else class="loading"/> <MkLoading v-else class="loading"/>
</div> </div>
<slot :items="items"></slot> <slot :items="items"></slot>
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> <div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _margin">
<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
{{ i18n.ts.loadMore }} {{ i18n.ts.loadMore }}
</MkButton> </MkButton>

View File

@ -1,14 +1,18 @@
<template> <template>
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
<span class="text" :class="{ up }">+1</span> <span class="text" :class="{ up }">
<XReactionIcon class="icon" :reaction="reaction"/>
</span>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
import XReactionIcon from '@/components/MkReactionIcon.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
reaction: string;
x: number; x: number;
y: number; y: number;
}>(), { }>(), {
@ -20,6 +24,7 @@ const emit = defineEmits<{
let up = $ref(false); let up = $ref(false);
const zIndex = os.claimZIndex('veryLow'); const zIndex = os.claimZIndex('veryLow');
const angle = (90 - (Math.random() * 180)) + 'deg';
onMounted(() => { onMounted(() => {
window.setTimeout(() => { window.setTimeout(() => {
@ -55,10 +60,11 @@ onMounted(() => {
font-weight: bold; font-weight: bold;
transform: translateY(-30px); transform: translateY(-30px);
transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5); transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5);
will-change: opacity, transform;
&.up { &.up {
opacity: 0; opacity: 0;
transform: translateY(-50px); transform: translateY(-50px) rotateZ(v-bind(angle));
} }
} }
} }

View File

@ -49,9 +49,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import MkInput from './form/input.vue'; import MkInput from './MkInput.vue';
import MkSelect from './form/select.vue'; import MkSelect from './MkSelect.vue';
import MkSwitch from './form/switch.vue'; import MkSwitch from './MkSwitch.vue';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';
import { formatDateTimeString } from '@/scripts/format-time-string'; import { formatDateTimeString } from '@/scripts/format-time-string';
import { addTime } from '@/scripts/time'; import { addTime } from '@/scripts/time';

View File

@ -1,5 +1,5 @@
<template> <template>
<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @closed="emit('closed')"> <MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')">
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq" :class="{ drawer: type === 'drawer' }" @close="modal.close()"/> <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq" :class="{ drawer: type === 'drawer' }" @close="modal.close()"/>
</MkModal> </MkModal>
</template> </template>
@ -20,6 +20,7 @@ defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
(ev: 'closing'): void;
}>(); }>();
let modal = $shallowRef<InstanceType<typeof MkModal>>(); let modal = $shallowRef<InstanceType<typeof MkModal>>();

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