Merge remote-tracking branch 'upstream/develop' into suspended-users-note

This commit is contained in:
anatawa12 2025-04-10 19:33:32 +09:00
commit 3bdb23108a
No known key found for this signature in database
GPG Key ID: 9CA909848B8E4EA6
45 changed files with 609 additions and 692 deletions

View File

@ -18,8 +18,8 @@ jobs:
# Neither Dependabot nor Renovate will change the actual behavior for components. # Neither Dependabot nor Renovate will change the actual behavior for components.
if: >- if: >-
github.repository == 'misskey-dev/misskey' && github.repository == 'misskey-dev/misskey' &&
startsWith(github.ref, 'refs/heads/dependabot/') != true && startsWith(github.head_ref, 'refs/heads/dependabot/') != true &&
startsWith(github.ref, 'refs/heads/renovate/') != true startsWith(github.head_ref, 'refs/heads/renovate/') != true
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

View File

@ -1,7 +1,19 @@
## Unreleased
### General
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
### Client
-
### Server
-
## 2025.4.0 ## 2025.4.0
### General ### General
- Feat: チャットがリニューアルして復活しました(beta) - Feat: チャット(ダイレクトメッセージ)がリニューアルして復活しました
- 既存のDM機能よりも便利で効率的な実装になっています - 既存のDM機能よりも便利で効率的な実装になっています
- チャットを受け付ける相手を制限可能です - チャットを受け付ける相手を制限可能です
- 誰でも / フォローユーザーのみ / フォロワーのみ / 相互のみ / 受け付けない から選択できます - 誰でも / フォローユーザーのみ / フォロワーのみ / 相互のみ / 受け付けない から選択できます
@ -11,7 +23,8 @@
- 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です - 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です
- 参加中のルームをミュートして通知が来ないように設定可能です - 参加中のルームをミュートして通知が来ないように設定可能です
- メッセージにはリアクションも可能です - メッセージにはリアクションも可能です
- Feat: アカウントのマイグレーション時に古いアカウントからロールをコピーできるようになりました。 - 現在、リモートユーザーがチャットを受け付ける設定になっているかどうかを取得する術がないため、ローカルユーザー間でのみ利用可能です
- Feat: アカウントの移行時に古いアカウントからあたらしいアカウントにロールをコピーできるようになりました。
- 管理者がロールの設定でマイグレーション時にコピーするかを指定できるようになります。 - 管理者がロールの設定でマイグレーション時にコピーするかを指定できるようになります。
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 - Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
- Misskeyネイティブでダッシュボードを実装予定です - Misskeyネイティブでダッシュボードを実装予定です
@ -20,7 +33,6 @@
- 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。 - 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。
- Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように - Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように
- Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177` - Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177`
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
- Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 - Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正
### Client ### Client
@ -61,11 +73,13 @@
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように - Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
- Enhance: 全体的なブラッシュアップ - Enhance: 全体的なブラッシュアップ
- Enhance 全体的なパフォーマンス向上 - Enhance 全体的なパフォーマンス向上
- Enhance: ファイルのアップロードでデフォルトで圧縮するかどうかのオプションが廃止され、アップロード時に圧縮するかどうかを選択するようになりました
- 画像データの貼り付け、ドロップ時は圧縮されるようになりました
- Fix: 読み込み直後にスクロールしようとすると途中で止まる場合があるのを修正 - Fix: 読み込み直後にスクロールしようとすると途中で止まる場合があるのを修正
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正
- Fix: iPadOSでdeck uiをマウスカーソルによってスクロールできない問題を修正
- NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました - NOTE: 構造上クラシックUIを新しいデザインシステムに移行することが困難なため、クラシックUIが削除されました
- デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます - デッキUIでカラムを中央寄せにし、メインカラムの左右にウィジェットカラムを配置し、ナビゲーションバーを上部に表示することである程度クラシックUIを再現できます
- Fix: iPadOSでdeck uiをマウスカーソルによってスクロールできない問題を修正
### Server ### Server
- Enhance 全体的なパフォーマンス向上 - Enhance 全体的なパフォーマンス向上

View File

@ -424,7 +424,7 @@ antennaExcludeBots: "Exclou els bots"
antennaKeywordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR." antennaKeywordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR."
notifyAntenna: "Notifica'm les publicacions noves" notifyAntenna: "Notifica'm les publicacions noves"
withFileAntenna: "Només les publicacions amb fitxers" withFileAntenna: "Només les publicacions amb fitxers"
hideNotesInSensitiveChannel: "Amaga les notes a canals sensibles " excludeNotesInSensitiveChannel: "Excloure notes a canals sensibles"
enableServiceworker: "Activar les notificacions al navegador" enableServiceworker: "Activar les notificacions al navegador"
antennaUsersDescription: "Llistar un nom d'usuari per línia" antennaUsersDescription: "Llistar un nom d'usuari per línia"
caseSensitive: "Sensible a majúscules i minúscules " caseSensitive: "Sensible a majúscules i minúscules "
@ -537,7 +537,7 @@ mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única
limitTo: "Limita a {x}" limitTo: "Limita a {x}"
noFollowRequests: "No tens sol·licituds de seguiment" noFollowRequests: "No tens sol·licituds de seguiment"
openImageInNewTab: "Obre imatges a una nova pestanya" openImageInNewTab: "Obre imatges a una nova pestanya"
dashboard: "Taulell de control" dashboard: "Tauler de control"
local: "Local" local: "Local"
remote: "Remot" remote: "Remot"
total: "Total" total: "Total"
@ -652,7 +652,7 @@ manage: "Administració"
plugins: "Extensions" plugins: "Extensions"
preferencesBackups: "Configuracions de les Còpies de seguretat" preferencesBackups: "Configuracions de les Còpies de seguretat"
deck: "Escriptori" deck: "Escriptori"
undeck: "Tanca l'escriptori" undeck: "Tanca el tauler"
useBlurEffectForModal: "Utilitzar l'efecte de difuminació a modals" useBlurEffectForModal: "Utilitzar l'efecte de difuminació a modals"
useFullReactionPicker: "Utilitza el cercador de reaccions d'escala sencera" useFullReactionPicker: "Utilitza el cercador de reaccions d'escala sencera"
width: "Amplada" width: "Amplada"
@ -915,7 +915,7 @@ off: "Desactivar"
emailRequiredForSignup: "Demanar correu electrònic per registrar-se " emailRequiredForSignup: "Demanar correu electrònic per registrar-se "
unread: "Sense llegir" unread: "Sense llegir"
filter: "Filtrar" filter: "Filtrar"
controlPanel: "Taulell de control" controlPanel: "Tauler de control"
manageAccounts: "Gestionar comptes" manageAccounts: "Gestionar comptes"
makeReactionsPublic: "Reaccions públiques " makeReactionsPublic: "Reaccions públiques "
makeReactionsPublicDescription: "Això fa que totes les teves reaccions siguin visibles públicament " makeReactionsPublicDescription: "Això fa que totes les teves reaccions siguin visibles públicament "
@ -1342,6 +1342,8 @@ bottom: "A baix "
top: "A dalt " top: "A dalt "
embed: "Incrustar" embed: "Incrustar"
settingsMigrating: "Estem fent la migració de la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard i manualment anant a Preferències → Altres configuracions → Migrar configuració antiga)" settingsMigrating: "Estem fent la migració de la teva configuració. Si us plau espera un moment... (També pots fer la migració més tard i manualment anant a Preferències → Altres configuracions → Migrar configuració antiga)"
readonly: "Només lectura"
goToDeck: "Tornar al tauler"
_chat: _chat:
noMessagesYet: "Encara no tens missatges " noMessagesYet: "Encara no tens missatges "
newMessage: "Missatge nou" newMessage: "Missatge nou"
@ -1371,6 +1373,7 @@ _chat:
muteThisRoom: "Silenciar aquesta sala" muteThisRoom: "Silenciar aquesta sala"
deleteRoom: "Esborrar la sala" deleteRoom: "Esborrar la sala"
chatNotAvailableForThisAccountOrServer: "El xat no està disponible per aquest servidor o aquest compte." chatNotAvailableForThisAccountOrServer: "El xat no està disponible per aquest servidor o aquest compte."
chatIsReadOnlyForThisAccountOrServer: "El xat és només de lectura en aquest servidor o compte. No es poden escriure nous missatges ni crear o unir-se a sales de xat."
chatNotAvailableInOtherAccount: "La funció de xat es troba desactivada al compte de l'altre usuari." chatNotAvailableInOtherAccount: "La funció de xat es troba desactivada al compte de l'altre usuari."
cannotChatWithTheUser: "No pots xatejar amb aquest usuari" cannotChatWithTheUser: "No pots xatejar amb aquest usuari"
cannotChatWithTheUser_description: "El xat està desactivat o l'altra part encara no l'ha obert." cannotChatWithTheUser_description: "El xat està desactivat o l'altra part encara no l'ha obert."
@ -1931,7 +1934,7 @@ _role:
canImportFollowing: "Autoritza la importació de seguidors" canImportFollowing: "Autoritza la importació de seguidors"
canImportMuting: "Autoritza la importació de silenciats" canImportMuting: "Autoritza la importació de silenciats"
canImportUserLists: "Autoritza la importació de llistes d'usuaris " canImportUserLists: "Autoritza la importació de llistes d'usuaris "
canChat: "Pot xatejar" chatAvailability: "Es permet xatejar"
_condition: _condition:
roleAssignedTo: "Assignat a rols manuals" roleAssignedTo: "Assignat a rols manuals"
isLocal: "Usuari local" isLocal: "Usuari local"
@ -2123,7 +2126,7 @@ _theme:
fg: "Text" fg: "Text"
focus: "Enfocament" focus: "Enfocament"
indicator: "Indicador" indicator: "Indicador"
panel: "Taulell " panel: "Tauler"
shadow: "Ombra" shadow: "Ombra"
header: "Capçalera" header: "Capçalera"
navBg: "Fons de la barra lateral" navBg: "Fons de la barra lateral"
@ -2601,7 +2604,7 @@ _deck:
columnGap: "Espai entre columnes" columnGap: "Espai entre columnes"
deckMenuPosition: "Posició del menú del tauler" deckMenuPosition: "Posició del menú del tauler"
navbarPosition: "Posició de la barra de navegació " navbarPosition: "Posició de la barra de navegació "
addColumn: "Afig una columna" addColumn: "Afegeix una columna"
newNoteNotificationSettings: "Configuració de notificacions per a notes noves" newNoteNotificationSettings: "Configuració de notificacions per a notes noves"
configureColumn: "Configuració de columnes" configureColumn: "Configuració de columnes"
swapLeft: "Mou a lesquerra" swapLeft: "Mou a lesquerra"
@ -2796,7 +2799,7 @@ _hemisphere:
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Opcions del joc" gameSettings: "Opcions del joc"
chooseBoard: "Escull un taulell" chooseBoard: "Escull un tauler"
blackOrWhite: "Negres/Blanques" blackOrWhite: "Negres/Blanques"
blackIs: "{name} juga amb negres " blackIs: "{name} juga amb negres "
rules: "Regles" rules: "Regles"

View File

@ -424,7 +424,7 @@ antennaExcludeBots: "Bot-Accounts ausschließen"
antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen" antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen"
notifyAntenna: "Über neue Notizen benachrichtigen" notifyAntenna: "Über neue Notizen benachrichtigen"
withFileAntenna: "Nur Notizen mit Dateien" withFileAntenna: "Nur Notizen mit Dateien"
hideNotesInSensitiveChannel: "Verstecke Notizen von sensitive Kanäle" excludeNotesInSensitiveChannel: "Schließe Notizen von sensitive Kanäle aus"
enableServiceworker: "Push-Benachrichtigungen im Browser aktivieren" enableServiceworker: "Push-Benachrichtigungen im Browser aktivieren"
antennaUsersDescription: "Benutzernamen getrennt durch Zeilenumbrüche angeben" antennaUsersDescription: "Benutzernamen getrennt durch Zeilenumbrüche angeben"
caseSensitive: "Groß-/Kleinschreibung unterscheiden" caseSensitive: "Groß-/Kleinschreibung unterscheiden"
@ -1341,6 +1341,9 @@ right: "Rechts"
bottom: "Unten" bottom: "Unten"
top: "Oben" top: "Oben"
embed: "Einbetten" embed: "Einbetten"
settingsMigrating: "Ihre Einstellungen werden gerade migriert, Bitte warten Sie einen Moment... (Sie können die Einstellungen später auch manuell migrieren, indem Sie zu Einstellungen → Sonstiges → Alte Einstellungen migrieren gehen)"
readonly: "Nur Lesezugriff"
goToDeck: "Zurück zum Deck"
_chat: _chat:
noMessagesYet: "Noch keine Nachrichten" noMessagesYet: "Noch keine Nachrichten"
newMessage: "Neue Nachricht" newMessage: "Neue Nachricht"
@ -1370,6 +1373,7 @@ _chat:
muteThisRoom: "Raum stummschalten" muteThisRoom: "Raum stummschalten"
deleteRoom: "Raum löschen" deleteRoom: "Raum löschen"
chatNotAvailableForThisAccountOrServer: "Der Chat ist auf diesem Server oder für dieses Konto nicht aktiviert." chatNotAvailableForThisAccountOrServer: "Der Chat ist auf diesem Server oder für dieses Konto nicht aktiviert."
chatIsReadOnlyForThisAccountOrServer: "Der Chat ist auf dieser Instanz oder diesem Konto nur zum Lesen freigegeben. Es ist nicht möglich, neue Nachrichten zu schreiben oder Chaträume zu erstellen oder zu betreten."
chatNotAvailableInOtherAccount: "Die Chatfunktion wurde vom anderen Benutzer deaktiviert." chatNotAvailableInOtherAccount: "Die Chatfunktion wurde vom anderen Benutzer deaktiviert."
cannotChatWithTheUser: "Starten eines Chats mit diesem Benutzer nicht möglich" cannotChatWithTheUser: "Starten eines Chats mit diesem Benutzer nicht möglich"
cannotChatWithTheUser_description: "Der Chat ist entweder nicht verfügbar oder die andere Seite hat den Chat nicht aktiviert." cannotChatWithTheUser_description: "Der Chat ist entweder nicht verfügbar oder die andere Seite hat den Chat nicht aktiviert."
@ -1930,7 +1934,7 @@ _role:
canImportFollowing: "Importieren von Gefolgten zulassen" canImportFollowing: "Importieren von Gefolgten zulassen"
canImportMuting: "Importieren von Stummgeschalteten zulassen" canImportMuting: "Importieren von Stummgeschalteten zulassen"
canImportUserLists: "Importieren von Listen erlauben" canImportUserLists: "Importieren von Listen erlauben"
canChat: "Chatten erlauben" chatAvailability: "Chatten erlauben"
_condition: _condition:
roleAssignedTo: "Manuellen Rollen zugewiesen" roleAssignedTo: "Manuellen Rollen zugewiesen"
isLocal: "Lokaler Benutzer" isLocal: "Lokaler Benutzer"

View File

@ -424,7 +424,7 @@ antennaExcludeBots: "Exclude bot accounts"
antennaKeywordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition." antennaKeywordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
notifyAntenna: "Notify about new notes" notifyAntenna: "Notify about new notes"
withFileAntenna: "Only notes with files" withFileAntenna: "Only notes with files"
hideNotesInSensitiveChannel: "Hide notes from sensitive channels" excludeNotesInSensitiveChannel: "Exclude notes from sensitive channels"
enableServiceworker: "Enable Push-Notifications for your Browser" enableServiceworker: "Enable Push-Notifications for your Browser"
antennaUsersDescription: "List one username per line" antennaUsersDescription: "List one username per line"
caseSensitive: "Case sensitive" caseSensitive: "Case sensitive"
@ -1341,6 +1341,9 @@ right: "Right"
bottom: "Bottom" bottom: "Bottom"
top: "Top" top: "Top"
embed: "Embed" embed: "Embed"
settingsMigrating: "Settings are being migrated, please wait a moment... (You can also migrate manually later by going to Settings→Others→Migrate old settings)"
readonly: "Read only"
goToDeck: "Return to Deck"
_chat: _chat:
noMessagesYet: "No messages yet" noMessagesYet: "No messages yet"
newMessage: "New message" newMessage: "New message"
@ -1370,6 +1373,7 @@ _chat:
muteThisRoom: "Mute room" muteThisRoom: "Mute room"
deleteRoom: "Delete room" deleteRoom: "Delete room"
chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account." chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account."
chatIsReadOnlyForThisAccountOrServer: "Chat is read-only on this instance or this account. You cannot write new messages or create/join chat rooms."
chatNotAvailableInOtherAccount: "The chat function is disabled for the other user." chatNotAvailableInOtherAccount: "The chat function is disabled for the other user."
cannotChatWithTheUser: "Cannot start a chat with this user" cannotChatWithTheUser: "Cannot start a chat with this user"
cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat." cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat."
@ -1930,7 +1934,7 @@ _role:
canImportFollowing: "Allow importing following" canImportFollowing: "Allow importing following"
canImportMuting: "Allow importing muting" canImportMuting: "Allow importing muting"
canImportUserLists: "Allow importing lists" canImportUserLists: "Allow importing lists"
canChat: "Allow Chat" chatAvailability: "Allow Chat"
_condition: _condition:
roleAssignedTo: "Assigned to manual roles" roleAssignedTo: "Assigned to manual roles"
isLocal: "Local user" isLocal: "Local user"

8
locales/index.d.ts vendored
View File

@ -1715,9 +1715,9 @@ export interface Locale extends ILocale {
*/ */
"withFileAntenna": string; "withFileAntenna": string;
/** /**
* *
*/ */
"hideNotesInSensitiveChannel": string; "excludeNotesInSensitiveChannel": string;
/** /**
* *
*/ */
@ -5390,6 +5390,10 @@ export interface Locale extends ILocale {
* *
*/ */
"readonly": string; "readonly": string;
/**
*
*/
"goToDeck": string;
"_chat": { "_chat": {
/** /**
* *

View File

@ -424,7 +424,6 @@ antennaExcludeBots: "Escludere i Bot"
antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)." antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
notifyAntenna: "Invia notifiche delle nuove note" notifyAntenna: "Invia notifiche delle nuove note"
withFileAntenna: "Solo note con file in allegato" withFileAntenna: "Solo note con file in allegato"
hideNotesInSensitiveChannel: "Nascondere le Note dai canali espliciti"
enableServiceworker: "Abilita ServiceWorker" enableServiceworker: "Abilita ServiceWorker"
antennaUsersDescription: "Elenca un nome utente per riga" antennaUsersDescription: "Elenca un nome utente per riga"
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole" caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
@ -1930,7 +1929,6 @@ _role:
canImportFollowing: "Può importare Following" canImportFollowing: "Può importare Following"
canImportMuting: "Può importare Silenziati" canImportMuting: "Può importare Silenziati"
canImportUserLists: "Può importare liste di Profili" canImportUserLists: "Può importare liste di Profili"
canChat: "Chat consentita"
_condition: _condition:
roleAssignedTo: "Assegnato a ruoli manualmente" roleAssignedTo: "Assegnato a ruoli manualmente"
isLocal: "Profilo locale" isLocal: "Profilo locale"

View File

@ -424,7 +424,7 @@ antennaExcludeBots: "Botアカウントを除外"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する" notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ" withFileAntenna: "ファイルが添付されたノートのみ"
hideNotesInSensitiveChannel: "センシティブなチャンネルのノートを非表示" excludeNotesInSensitiveChannel: "センシティブなチャンネルのノートを除外"
enableServiceworker: "ブラウザへのプッシュ通知を有効にする" enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
antennaUsersDescription: "ユーザー名を改行で区切って指定します" antennaUsersDescription: "ユーザー名を改行で区切って指定します"
caseSensitive: "大文字小文字を区別する" caseSensitive: "大文字小文字を区別する"
@ -1343,6 +1343,7 @@ top: "上"
embed: "埋め込み" embed: "埋め込み"
settingsMigrating: "設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます)" settingsMigrating: "設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます)"
readonly: "読み取り専用" readonly: "読み取り専用"
goToDeck: "デッキへ戻る"
_chat: _chat:
noMessagesYet: "まだメッセージはありません" noMessagesYet: "まだメッセージはありません"

View File

@ -64,8 +64,8 @@ copyNoteId: "노트 ID 복사"
copyFileId: "파일 ID 복사" copyFileId: "파일 ID 복사"
copyFolderId: "폴더 ID 복사" copyFolderId: "폴더 ID 복사"
copyProfileUrl: "프로필 URL 복사" copyProfileUrl: "프로필 URL 복사"
searchUser: "사용자 검색" searchUser: "유저 검색"
searchThisUsersNotes: "사용자의 노트 검색" searchThisUsersNotes: "유저의 노트를 검색"
reply: "답글" reply: "답글"
loadMore: "더 보기" loadMore: "더 보기"
showMore: "더 보기" showMore: "더 보기"
@ -267,7 +267,7 @@ publishing: "배포 중"
notResponding: "응답 없음" notResponding: "응답 없음"
instanceFollowing: "서버의 팔로잉" instanceFollowing: "서버의 팔로잉"
instanceFollowers: "서버의 팔로워" instanceFollowers: "서버의 팔로워"
instanceUsers: "서버의 사용자" instanceUsers: "서버의 유저"
changePassword: "비밀번호 변경" changePassword: "비밀번호 변경"
security: "보안" security: "보안"
retypedNotMatch: "입력이 일치하지 않습니다." retypedNotMatch: "입력이 일치하지 않습니다."
@ -385,12 +385,12 @@ disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리
registration: "등록" registration: "등록"
invite: "초대" invite: "초대"
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량" driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
driveCapacityPerRemoteAccount: "원격 사용자별 드라이브 용량" driveCapacityPerRemoteAccount: "리모트 유저별 드라이브 용량"
inMb: "메가바이트 단위" inMb: "메가바이트 단위"
bannerUrl: "배너 이미지 URL" bannerUrl: "배너 이미지 URL"
backgroundImageUrl: "배경 이미지 URL" backgroundImageUrl: "배경 이미지 URL"
basicInfo: "기본 정보" basicInfo: "기본 정보"
pinnedUsers: "고정한 사용자" pinnedUsers: "고정한 유저"
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다." pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
pinnedPages: "고정한 페이지" pinnedPages: "고정한 페이지"
pinnedPagesDescription: "서버의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다." pinnedPagesDescription: "서버의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
@ -418,13 +418,13 @@ antennas: "안테나"
manageAntennas: "안테나 관리" manageAntennas: "안테나 관리"
name: "이름" name: "이름"
antennaSource: "받을 소스" antennaSource: "받을 소스"
antennaKeywords: "받을 검색어" antennaKeywords: "받을 키워드"
antennaExcludeKeywords: "제외할 검색어" antennaExcludeKeywords: "제외할 키워드"
antennaExcludeBots: "봇 계정 제외" antennaExcludeBots: "봇 계정 제외"
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다" antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
notifyAntenna: "새로운 노트를 알림" notifyAntenna: "새로운 노트를 알림"
withFileAntenna: "파일이 첨부된 노트만" withFileAntenna: "파일이 첨부된 노트만"
hideNotesInSensitiveChannel: "민감한 채널의 노트 숨기기" excludeNotesInSensitiveChannel: "민감한 채널의 노트 제외"
enableServiceworker: "ServiceWorker 사용" enableServiceworker: "ServiceWorker 사용"
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다" antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
caseSensitive: "대소문자를 구분" caseSensitive: "대소문자를 구분"
@ -436,11 +436,11 @@ silence: "사일런스"
silenceConfirm: "이 계정을 사일런스로 설정하시겠습니까?" silenceConfirm: "이 계정을 사일런스로 설정하시겠습니까?"
unsilence: "사일런스 해제" unsilence: "사일런스 해제"
unsilenceConfirm: "이 계정의 사일런스를 해제하시겠습니까?" unsilenceConfirm: "이 계정의 사일런스를 해제하시겠습니까?"
popularUsers: "인기 사용자" popularUsers: "인기 유저"
recentlyUpdatedUsers: "최근에 활동한 사용자" recentlyUpdatedUsers: "최근에 활동한 유저"
recentlyRegisteredUsers: "최근에 가입한 사용자" recentlyRegisteredUsers: "최근에 가입한 유저"
recentlyDiscoveredUsers: "최근에 발견한 사용자" recentlyDiscoveredUsers: "최근에 발견한 유저"
exploreUsersCount: "{count}명의 사용자가 있습니다" exploreUsersCount: "{count}명의 유저가 있습니다"
exploreFediverse: "연합우주를 탐색" exploreFediverse: "연합우주를 탐색"
popularTags: "인기 태그" popularTags: "인기 태그"
userList: "리스트" userList: "리스트"
@ -508,7 +508,7 @@ strongPassword: "강한 비밀번호"
passwordMatched: "일치합니다" passwordMatched: "일치합니다"
passwordNotMatched: "일치하지 않습니다" passwordNotMatched: "일치하지 않습니다"
signinWith: "{x}로 로그인" signinWith: "{x}로 로그인"
signinFailed: "로그인할 수 없습니다. 사용자 이름과 비밀번호를 확인해 주십시오." signinFailed: "로그인할 수 없습니다. 유저 이름과 비밀번호를 확인해 주십시오."
or: "혹은" or: "혹은"
language: "언어" language: "언어"
uiLanguage: "UI 표시 언어" uiLanguage: "UI 표시 언어"
@ -520,7 +520,7 @@ style: "스타일"
drawer: "서랍" drawer: "서랍"
popup: "팝업" popup: "팝업"
showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기" showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기"
showReactionsCount: "노트의 반응 수를 표시하기" showReactionsCount: "노트의 리액션 수를 표시하기"
noHistory: "기록이 없습니다" noHistory: "기록이 없습니다"
signinHistory: "로그인 기록" signinHistory: "로그인 기록"
enableAdvancedMfm: "고급 MFM을 활성화" enableAdvancedMfm: "고급 MFM을 활성화"
@ -607,7 +607,7 @@ uiInspectorDescription: "메모리에 있는 UI 컴포넌트의 인스턴트 목
output: "출력" output: "출력"
script: "스크립트" script: "스크립트"
disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음" disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음"
updateRemoteUser: "원격 사용자 정보 갱신" updateRemoteUser: "리모트 유저 정보 갱신"
unsetUserAvatar: "아바타 제거" unsetUserAvatar: "아바타 제거"
unsetUserAvatarConfirm: "아바타를 제거할까요?" unsetUserAvatarConfirm: "아바타를 제거할까요?"
unsetUserBanner: "배너 제거" unsetUserBanner: "배너 제거"
@ -616,7 +616,7 @@ deleteAllFiles: "모든 파일 삭제"
deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?" deleteAllFilesConfirm: "모든 파일을 삭제하시겠습니까?"
removeAllFollowing: "모든 팔로잉 해제" removeAllFollowing: "모든 팔로잉 해제"
removeAllFollowingDescription: "{host} 서버의 모든 팔로잉을 해제합니다. 해당 서버가 더 이상 존재하지 않는 경우 등에 실행해 주세요." removeAllFollowingDescription: "{host} 서버의 모든 팔로잉을 해제합니다. 해당 서버가 더 이상 존재하지 않는 경우 등에 실행해 주세요."
userSuspended: "이 사용자는 정지되었습니다." userSuspended: "이 유저는 정지되었습니다."
userSilenced: "이 계정은 사일런스된 상태입니다." userSilenced: "이 계정은 사일런스된 상태입니다."
yourAccountSuspendedTitle: "계정이 정지되었습니다" yourAccountSuspendedTitle: "계정이 정지되었습니다"
yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오." yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
@ -677,7 +677,7 @@ emailAddress: "메일 주소"
smtpConfig: "SMTP 서버 설정" smtpConfig: "SMTP 서버 설정"
smtpHost: "호스트" smtpHost: "호스트"
smtpPort: "포트" smtpPort: "포트"
smtpUser: "사용자 이름" smtpUser: "유저 이름"
smtpPass: "비밀번호" smtpPass: "비밀번호"
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다." emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다."
smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용" smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
@ -754,8 +754,8 @@ repliedCount: "받은 답글 수"
renotedCount: "받은 리노트 수" renotedCount: "받은 리노트 수"
followingCount: "팔로우 수" followingCount: "팔로우 수"
followersCount: "팔로워 수" followersCount: "팔로워 수"
sentReactionsCount: "반응 수" sentReactionsCount: "리액션 수"
receivedReactionsCount: "받은 반응 수" receivedReactionsCount: "받은 리액션 수"
pollVotesCount: "투표 수" pollVotesCount: "투표 수"
pollVotedCount: "받은 투표 수" pollVotedCount: "받은 투표 수"
yes: "예" yes: "예"
@ -763,7 +763,7 @@ no: "아니오"
driveFilesCount: "드라이브에 있는 파일 수" driveFilesCount: "드라이브에 있는 파일 수"
driveUsage: "드라이브 사용량" driveUsage: "드라이브 사용량"
noCrawle: "검색엔진의 인덱싱 거부" noCrawle: "검색엔진의 인덱싱 거부"
noCrawleDescription: "검색엔진에 사용자 페이지, 노트, 페이지 등의 콘텐츠를 인덱싱되지 않게 합니다." noCrawleDescription: "검색엔진에 유저 페이지, 노트, 페이지 등의 콘텐츠를 인덱싱되지 않게 합니다."
lockedAccountInfo: "팔로우를 승인으로 승인받더라도 노트의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 노트를 볼 수 있습니다." lockedAccountInfo: "팔로우를 승인으로 승인받더라도 노트의 공개 범위를 '팔로워'로 하지 않는 한 누구나 당신의 노트를 볼 수 있습니다."
alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정" alwaysMarkSensitive: "미디어를 항상 열람 주의로 설정"
loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시" loadRawImages: "첨부한 이미지의 썸네일을 원본화질로 표시"
@ -795,7 +795,7 @@ needReloadToApply: "변경 사항은 새로고침하면 적용됩니다."
showTitlebar: "타이틀 바를 표시하기" showTitlebar: "타이틀 바를 표시하기"
clearCache: "캐시 비우기" clearCache: "캐시 비우기"
onlineUsersCount: "{n}명이 접속 중" onlineUsersCount: "{n}명이 접속 중"
nUsers: "{n} 사용자" nUsers: "{n} 유저"
nNotes: "{n} 노트" nNotes: "{n} 노트"
sendErrorReports: "오류 보고서 보내기" sendErrorReports: "오류 보고서 보내기"
sendErrorReportsDescription: "이 설정을 활성화하면, 문제가 발생했을 때 오류에 대한 상세 정보를 Misskey에 보내어 더 나은 소프트웨어를 만드는 데에 도움을 줄 수 있습니다." sendErrorReportsDescription: "이 설정을 활성화하면, 문제가 발생했을 때 오류에 대한 상세 정보를 Misskey에 보내어 더 나은 소프트웨어를 만드는 데에 도움을 줄 수 있습니다."
@ -841,7 +841,7 @@ addDescription: "설명 추가"
userPagePinTip: "각 노트의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 노트를 표시해 둘 수 있어요." userPagePinTip: "각 노트의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 노트를 표시해 둘 수 있어요."
notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어요" notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어요"
info: "정보" info: "정보"
userInfo: "사용자 정보" userInfo: "유저 정보"
unknown: "알 수 없음" unknown: "알 수 없음"
onlineStatus: "온라인 상태" onlineStatus: "온라인 상태"
hideOnlineStatus: "온라인 상태 숨기기" hideOnlineStatus: "온라인 상태 숨기기"
@ -857,7 +857,7 @@ switchAccount: "계정 바꾸기"
enabled: "활성화" enabled: "활성화"
disabled: "비활성화" disabled: "비활성화"
quickAction: "빠른 동작" quickAction: "빠른 동작"
user: "사용자" user: "유저"
administration: "관리" administration: "관리"
accounts: "계정" accounts: "계정"
switch: "전환" switch: "전환"
@ -868,8 +868,8 @@ configure: "설정하기"
postToGallery: "갤러리에 업로드" postToGallery: "갤러리에 업로드"
postToHashtag: "이 해시태그에 게시" postToHashtag: "이 해시태그에 게시"
gallery: "갤러리" gallery: "갤러리"
recentPosts: "최근 포스트" recentPosts: "최근 게시물"
popularPosts: "인기 포스트" popularPosts: "인기 게시물"
shareWithNote: "노트로 공유" shareWithNote: "노트로 공유"
ads: "광고" ads: "광고"
expiration: "기한" expiration: "기한"
@ -898,7 +898,7 @@ whatIsNew: "패치 정보 보기"
translate: "번역" translate: "번역"
translatedFrom: "{x}에서 번역" translatedFrom: "{x}에서 번역"
accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다" accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다." usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 유저명은 나중에 변경할 수 없습니다."
aiChanMode: "아이 모드" aiChanMode: "아이 모드"
devMode: "개발자 모드" devMode: "개발자 모드"
keepCw: "CW 유지하기" keepCw: "CW 유지하기"
@ -1032,7 +1032,7 @@ correspondingSourceIsAvailable: "소스 코드는 {anchor}에서 받아보실
roles: "역할" roles: "역할"
role: "역할" role: "역할"
noRole: "역할이 없습니다" noRole: "역할이 없습니다"
normalUser: "일반 사용자" normalUser: "일반 유저"
undefined: "정의되지 않음" undefined: "정의되지 않음"
assign: "할당" assign: "할당"
unassign: "할당 취소" unassign: "할당 취소"
@ -1056,7 +1056,7 @@ thisPostMayBeAnnoyingHome: "홈에 게시"
thisPostMayBeAnnoyingCancel: "그만두기" thisPostMayBeAnnoyingCancel: "그만두기"
thisPostMayBeAnnoyingIgnore: "이대로 게시" thisPostMayBeAnnoyingIgnore: "이대로 게시"
collapseRenotes: "이미 본 리노트를 간략화하기" collapseRenotes: "이미 본 리노트를 간략화하기"
collapseRenotesDescription: "반응이나 리노트를 한 노트를 접어서 표시합니다." collapseRenotesDescription: "리액션이나 리노트를 한 노트를 접어서 표시합니다."
internalServerError: "내부 서버 오류" internalServerError: "내부 서버 오류"
internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했습니다." internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했습니다."
copyErrorInfo: "오류 정보 복사" copyErrorInfo: "오류 정보 복사"
@ -1080,8 +1080,8 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
sensitiveWords: "민감한 단어" sensitiveWords: "민감한 단어"
sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다." sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다." sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
prohibitedWords: "금지 워드" prohibitedWords: "금지 단어"
prohibitedWordsDescription: "설정된 워드가 포함되는 노트를 작성하려고 하면, 에러가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다." prohibitedWordsDescription: "설정된 단어가 포함되는 노트를 작성하려고 하면, 오류가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다." prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
hiddenTags: "숨긴 해시태그" hiddenTags: "숨긴 해시태그"
hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다." hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
@ -1106,7 +1106,7 @@ audio: "소리"
audioFiles: "소리" audioFiles: "소리"
dataSaver: "데이터 절약 모드" dataSaver: "데이터 절약 모드"
accountMigration: "계정 이동" accountMigration: "계정 이동"
accountMoved: "이 사용자는 다음 계정으로 이사했습니다:" accountMoved: "이 유저는 다음 계정으로 이사했습니다:"
accountMovedShort: "이사한 계정입니다" accountMovedShort: "이사한 계정입니다"
operationForbidden: "사용할 수 없습니다" operationForbidden: "사용할 수 없습니다"
forceShowAds: "광고를 항상 표시" forceShowAds: "광고를 항상 표시"
@ -1127,8 +1127,8 @@ serverRules: "서버 규칙"
pleaseConfirmBelowBeforeSignup: "이 서버에 가입하기 전에 아래 사항을 확인하여 주십시오." pleaseConfirmBelowBeforeSignup: "이 서버에 가입하기 전에 아래 사항을 확인하여 주십시오."
pleaseAgreeAllToContinue: "계속하시려면 모든 항목에 동의하십시오." pleaseAgreeAllToContinue: "계속하시려면 모든 항목에 동의하십시오."
continue: "계속" continue: "계속"
preservedUsernames: "예약한 사용자 이름" preservedUsernames: "예약한 유저명"
preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다." preservedUsernamesDescription: "예약할 유저명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 유저명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다."
createNoteFromTheFile: "이 파일로 노트를 작성" createNoteFromTheFile: "이 파일로 노트를 작성"
archive: "아카이브" archive: "아카이브"
archived: "아카이브 됨" archived: "아카이브 됨"
@ -1142,7 +1142,7 @@ youFollowing: "팔로잉"
preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부" preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부"
preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다." preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다."
options: "옵션" options: "옵션"
specifyUser: "사용자 지정" specifyUser: "유저 지정"
lookupConfirm: "조회 할까요?" lookupConfirm: "조회 할까요?"
openTagPageConfirm: "해시태그의 페이지를 열까요?" openTagPageConfirm: "해시태그의 페이지를 열까요?"
specifyHost: "호스트 지정" specifyHost: "호스트 지정"
@ -1297,8 +1297,8 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "입력된 패스키
messageToFollower: "팔로워에게 보낼 메시지" messageToFollower: "팔로워에게 보낼 메시지"
target: "대상" target: "대상"
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>" testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)" prohibitedWordsForNameOfUser: "금지 단어 (유저명)"
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다." prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 유저명에 있는 경우, 일반 유저는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 유저는 제한 대상에서 제외됩니다."
yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다." yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요." yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
thisContentsAreMarkedAsSigninRequiredByAuthor: "게시자에 의해 로그인해야 볼 수 있도록 설정되어 있습니다." thisContentsAreMarkedAsSigninRequiredByAuthor: "게시자에 의해 로그인해야 볼 수 있도록 설정되어 있습니다."
@ -1341,15 +1341,18 @@ right: "오른쪽"
bottom: "아래" bottom: "아래"
top: "위" top: "위"
embed: "임베드" embed: "임베드"
settingsMigrating: "설정을 이전하는 중입니다. 잠시 기다려주십시오... (나중에 '환경설정 → 기타 → 기존 설정 정보를 이전'에서 수동으로 이전할 수도 있습니다)"
readonly: "읽기 전용"
goToDeck: "덱으로 돌아가기"
_chat: _chat:
noMessagesYet: "아직 메시지가 없습니다" noMessagesYet: "아직 메시지가 없습니다"
newMessage: "새로운 메시지" newMessage: "새로운 메시지"
individualChat: "개인 대화" individualChat: "개인 대화"
individualChat_description: "특정 사용자와 일대일 채팅을 할 수 있습니다." individualChat_description: "특정 유저와 일대일 채팅을 할 수 있습니다."
roomChat: "룸 채팅" roomChat: "룸 채팅"
roomChat_description: "여러 명이 함께 채팅할 수 있습니다.\n또한, 개인 채팅을 허용하지 않은 사용자와도 상대방이 수락하면 채팅을 할 수 있습니다." roomChat_description: "여러 명이 함께 채팅할 수 있습니다.\n또한, 개인 채팅을 허용하지 않은 유저와도 상대방이 수락하면 채팅을 할 수 있습니다."
createRoom: "룸을 생성" createRoom: "룸을 생성"
inviteUserToChat: "사용자를 초대하여 채팅을 시작하세요" inviteUserToChat: "유저를 초대하여 채팅을 시작하세요"
yourRooms: "생성한 룸" yourRooms: "생성한 룸"
joiningRooms: "참가 중인 룸" joiningRooms: "참가 중인 룸"
invitations: "초대" invitations: "초대"
@ -1357,7 +1360,7 @@ _chat:
history: "이력" history: "이력"
noHistory: "기록이 없습니다" noHistory: "기록이 없습니다"
noRooms: "룸이 없습니다" noRooms: "룸이 없습니다"
inviteUser: "사용자를 초대" inviteUser: "유저를 초대"
sentInvitations: "초대를 보내기" sentInvitations: "초대를 보내기"
join: "참여" join: "참여"
ignore: "무시" ignore: "무시"
@ -1370,21 +1373,22 @@ _chat:
muteThisRoom: "이 룸을 뮤트" muteThisRoom: "이 룸을 뮤트"
deleteRoom: "룸을 삭제" deleteRoom: "룸을 삭제"
chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다." chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다."
chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅방을 만들거나 참가할 수 없습니다."
chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다." chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다."
cannotChatWithTheUser: "이 사용자와 채팅을 시작할 수 없습니다" cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다"
cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다." cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다."
chatWithThisUser: "채팅하기" chatWithThisUser: "채팅하기"
thisUserAllowsChatOnlyFromFollowers: "이 사용자는 팔로워만 채팅을 할 수 있습니다." thisUserAllowsChatOnlyFromFollowers: "이 유저는 팔로워만 채팅을 할 수 있습니다."
thisUserAllowsChatOnlyFromFollowing: "이 사용자는 이 사용자가 팔로우하는 사용자만 채팅을 허용합니다." thisUserAllowsChatOnlyFromFollowing: "이 유저는 이 유저가 팔로우하는 유저만 채팅을 허용합니다."
thisUserAllowsChatOnlyFromMutualFollowing: "이 사용자는 상호 팔로우하는 사용자만 채팅을 허용합니다." thisUserAllowsChatOnlyFromMutualFollowing: "이 유저는 상호 팔로우하는 유저만 채팅을 허용합니다."
thisUserNotAllowedChatAnyone: "이 사용자는 다른 사람의 채팅을 받지 않습니다." thisUserNotAllowedChatAnyone: "이 유저는 다른 사람의 채팅을 받지 않습니다."
chatAllowedUsers: "채팅을 허용한 상대" chatAllowedUsers: "채팅을 허용한 상대"
chatAllowedUsers_note: "내가 채팅 메시지를 보낸 상대와는 이 설정과 상관없이 채팅이 가능합니다." chatAllowedUsers_note: "내가 채팅 메시지를 보낸 상대와는 이 설정과 상관없이 채팅이 가능합니다."
_chatAllowedUsers: _chatAllowedUsers:
everyone: "누구나" everyone: "누구나"
followers: "자신의 팔로워만" followers: "자신의 팔로워만"
following: "자신이 팔로우한 사용자만" following: "자신이 팔로우한 유저만"
mutual: "상호 팔로우한 사용자만" mutual: "상호 팔로우한 유저만"
none: "아무도 허락하지 않기" none: "아무도 허락하지 않기"
_emojiPalette: _emojiPalette:
palettes: "팔레트" palettes: "팔레트"
@ -1410,7 +1414,7 @@ _settings:
soundsBanner: "클라이언트에서 재생할 소리에 대한 설정을 합니다." soundsBanner: "클라이언트에서 재생할 소리에 대한 설정을 합니다."
timelineAndNote: "타임라인과 노트" timelineAndNote: "타임라인과 노트"
makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함" makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함"
makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 사용자의 접근성이 나빠질 수도 있습니다." makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 유저의 접근성이 나빠질 수도 있습니다."
useStickyIcons: "아이콘이 스크롤을 따라가도록 하기" useStickyIcons: "아이콘이 스크롤을 따라가도록 하기"
showNavbarSubButtons: "내비게이션 바에 보조 버튼 표시" showNavbarSubButtons: "내비게이션 바에 보조 버튼 표시"
ifOn: "켜져 있을 때" ifOn: "켜져 있을 때"
@ -1436,12 +1440,12 @@ _accountSettings:
requireSigninToViewContents: "콘텐츠 열람을 위해 로그인을 필수로 설정하기" requireSigninToViewContents: "콘텐츠 열람을 위해 로그인을 필수로 설정하기"
requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다." requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다."
requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페이지에 삽입, 노트 인용을 지원하지 않는 서버에서 볼 수 없게 됩니다." requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페이지에 삽입, 노트 인용을 지원하지 않는 서버에서 볼 수 없게 됩니다."
requireSigninToViewContentsDescription3: "원격 서버에 연합된 콘텐츠에는 이러한 제한이 적용되지 않을 수 있습니다." requireSigninToViewContentsDescription3: "리모트 서버에 연합된 콘텐츠에는 이러한 제한이 적용되지 않을 수 있습니다."
makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 볼 수 있도록 설정하기" makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 볼 수 있도록 설정하기"
makeNotesFollowersOnlyBeforeDescription: "이 기능이 활성화되어 있는 동안, 설정된 날짜 및 시간보다 과거 또는 설정된 시간이 지난 노트는 팔로워만 볼 수 있게 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." makeNotesFollowersOnlyBeforeDescription: "이 기능이 활성화되어 있는 동안, 설정된 날짜 및 시간보다 과거 또는 설정된 시간이 지난 노트는 팔로워만 볼 수 있게 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기" makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다." mayNotEffectForFederatedNotes: "리모트 서버에 연합된 노트에는 효과가 없을 수도 있습니다."
mayNotEffectSomeSituations: "여기서 설정하는 제한은 모더레이션이나 리모트 서버에서 볼 때 등 일부 환경에서는 적용되지 않을 수도 있습니다." mayNotEffectSomeSituations: "여기서 설정하는 제한은 모더레이션이나 리모트 서버에서 볼 때 등 일부 환경에서는 적용되지 않을 수도 있습니다."
notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트" notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트"
notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트" notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트"
@ -1482,11 +1486,11 @@ _announcement:
needConfirmationToRead: "읽음으로 표시하기 전에 확인하기" needConfirmationToRead: "읽음으로 표시하기 전에 확인하기"
needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다. '모두 읽음'의 대상에서도 제외됩니다." needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다. '모두 읽음'의 대상에서도 제외됩니다."
end: "공지에서 내리기" end: "공지에서 내리기"
tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 사용자 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다." tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 유저 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다."
readConfirmTitle: "읽음으로 표시합니까?" readConfirmTitle: "읽음으로 표시합니까?"
readConfirmText: "〈{title}〉의 내용을 읽음으로 표시합니다." readConfirmText: "〈{title}〉의 내용을 읽음으로 표시합니다."
shouldNotBeUsedToPresentPermanentInfo: "신규 유저의 이용 경험에 악영향을 끼칠 수 있으므로, 일시적인 알림 수단으로만 사용하고 고정된 정보에는 사용을 지양하는 것을 추천합니다." shouldNotBeUsedToPresentPermanentInfo: "신규 유저의 이용 경험에 악영향을 끼칠 수 있으므로, 일시적인 알림 수단으로만 사용하고 고정된 정보에는 사용을 지양하는 것을 추천합니다."
dialogAnnouncementUxWarn: "다이얼로그 형태의 알림이 동시에 2개 이상 존재하는 경우, 사용자 경험에 악영향을 끼칠 수 있으므로 신중히 결정하십시오." dialogAnnouncementUxWarn: "다이얼로그 형태의 알림이 동시에 2개 이상 존재하는 경우, 유저 경험에 악영향을 끼칠 수 있으므로 신중히 결정하십시오."
silence: "조용히 알림" silence: "조용히 알림"
silenceDescription: "활성화하면 공지사항에 대한 알림이 가지 않게 되며, 확인 버튼을 누를 필요가 없게 됩니다." silenceDescription: "활성화하면 공지사항에 대한 알림이 가지 않게 되며, 확인 버튼을 누를 필요가 없게 됩니다."
_initialAccountSetting: _initialAccountSetting:
@ -1612,53 +1616,53 @@ _achievements:
_types: _types:
_notes1: _notes1:
title: "미스키 계정 만들었어요" title: "미스키 계정 만들었어요"
description: "첫 노트를 작성했습니다" description: "첫 노트를 게시했다"
flavor: "Misskey에 어서 오세요!" flavor: "Misskey에 어서 오세요!"
_notes10: _notes10:
title: "몇 가지 노트" title: "몇 가지 노트"
description: "10개의 노트를 작성했습니다" description: "10개의 노트를 게시했다"
_notes100: _notes100:
title: "많은 노트" title: "많은 노트"
description: "100개의 노트를 작성했습니다" description: "100개의 노트를 게시했다"
_notes500: _notes500:
title: "노트 범벅" title: "노트 범벅"
description: "500개의 노트를 작성했습니다" description: "500개의 노트를 게시했다"
_notes1000: _notes1000:
title: "노트가 산더미" title: "노트가 산더미"
description: "1,000개의 노트를 작성했습니다" description: "1,000개의 노트를 게시했다"
_notes5000: _notes5000:
title: "솟아나는 노트" title: "솟아나는 노트"
description: "5,000개의 노트를 작성했습니다" description: "5,000개의 노트를 게시했다"
_notes10000: _notes10000:
title: "슈퍼 노트" title: "슈퍼 노트"
description: "10,000개의 노트를 작성했습니다" description: "10,000개의 노트를 게시했다"
_notes20000: _notes20000:
title: "노트가 필요해요" title: "노트가 필요해요"
description: "20,000개의 노트를 작성했습니다" description: "20,000개의 노트를 게시했다"
_notes30000: _notes30000:
title: "노트노트노트" title: "노트노트노트"
description: "30,000개의 노트를 작성했습니다" description: "30,000개의 노트를 게시했다"
_notes40000: _notes40000:
title: "노트 공장" title: "노트 공장"
description: "40,000개의 노트를 작성했습니다" description: "40,000개의 노트를 게시했다"
_notes50000: _notes50000:
title: "노트 행성" title: "노트 행성"
description: "50,000개의 노트를 작성했습니다" description: "50,000개의 노트를 게시했다"
_notes60000: _notes60000:
title: "노트 퀘이사" title: "노트 퀘이사"
description: "60,000개의 노트를 작성했습니다" description: "60,000개의 노트를 게시했다"
_notes70000: _notes70000:
title: "노트 블랙홀" title: "노트 블랙홀"
description: "70,000개의 노트를 작성했습니다" description: "70,000개의 노트를 게시했다"
_notes80000: _notes80000:
title: "노트 은하" title: "노트 은하"
description: "80,000개의 노트를 작성했습니다" description: "80,000개의 노트를 게시했다"
_notes90000: _notes90000:
title: "노트 우주" title: "노트 우주"
description: "90,000개의 노트를 작성했습니다" description: "90,000개의 노트를 게시했다"
_notes100000: _notes100000:
title: "ALL YOUR NOTE ARE BELONG TO US" title: "ALL YOUR NOTE ARE BELONG TO US"
description: "100,000개의 노트를 작성했습니다" description: "100,000개의 노트를 게시했다"
flavor: "이렇게나 쓸 게 있어요?" flavor: "이렇게나 쓸 게 있어요?"
_login3: _login3:
title: "초보자 I" title: "초보자 I"
@ -1683,181 +1687,181 @@ _achievements:
flavor: "그 유저, 미스키스트이다" flavor: "그 유저, 미스키스트이다"
_login200: _login200:
title: "단골 I" title: "단골 I"
description: "총 200일간 로그인했습니다" description: "총 로그인한 날이 200일"
_login300: _login300:
title: "단골 II" title: "단골 II"
description: "총 300일간 로그인했습니다" description: "총 로그인한 날이 300일"
_login400: _login400:
title: "단골 III" title: "단골 III"
description: "총 400일간 로그인했습니다" description: "총 로그인한 날이 400일"
_login500: _login500:
title: "베테랑 I" title: "베테랑 I"
description: "총 500일간 로그인했습니다" description: "총 로그인한 날이 500일"
flavor: "제군, 나는 노트가 좋다" flavor: "제군, 나는 노트가 좋다"
_login600: _login600:
title: "베테랑 II" title: "베테랑 II"
description: "총 600일간 로그인했습니다" description: "총 로그인한 날이 600일"
_login700: _login700:
title: "베테랑 III" title: "베테랑 III"
description: "총 700일간 로그인했습니다" description: "총 로그인한 날이 700일"
_login800: _login800:
title: "노트 마스터 I" title: "노트 마스터 I"
description: "총 800일간 로그인했습니다" description: "총 로그인한 날이 800일"
_login900: _login900:
title: "노트 마스터 II" title: "노트 마스터 II"
description: "총 900일간 로그인했습니다" description: "총 로그인한 날이 900일"
_login1000: _login1000:
title: "노트 마스터 III" title: "노트 마스터 III"
description: "총 1,000일간 로그인했습니다" description: "총 로그인한 날이 1,000일"
flavor: "Misskey를 사용해 주셔서 감사합니다!" flavor: "Misskey를 사용해 주셔서 감사합니다!"
_noteClipped1: _noteClipped1:
title: "클립할 수밖에 없었어" title: "클립할 수밖에 없었어"
description: "처음으로 노트를 클립했습니다" description: "처음으로 노트를 클립했다"
_noteFavorited1: _noteFavorited1:
title: "별을 바라보는 자" title: "별을 바라보는 자"
description: "처음으로 노트를 즐겨찾기했습니다" description: "처음으로 노트를 즐겨찾기했다"
_myNoteFavorited1: _myNoteFavorited1:
title: "별을 원하는 자" title: "별을 원하는 자"
description: "다른 사람이 당신의 노트를 즐겨찾기했습니다" description: "다른 사람이 당신의 노트를 즐겨찾기했다"
_profileFilled: _profileFilled:
title: "준비 완료" title: "준비 완료"
description: "프로필 설정을 완료했습니다" description: "프로필 설정을 완료했다"
_markedAsCat: _markedAsCat:
title: "나는 고양이다냥!" title: "나는 고양이다냥!"
description: "계정을 고양이로 설정했습니다냥" description: "계정을 고양이로 설정했다냥"
flavor: "냐냐냐냐냐냐아아아아앙!" flavor: "냐냐냐냐냐냐아아아아앙!"
_following1: _following1:
title: "첫 팔로우" title: "첫 팔로우"
description: "사용자를 처음으로 팔로우했습니다" description: "유저를 처음으로 팔로우했다"
_following10: _following10:
title: "팔로우, 팔로우" title: "팔로우, 팔로우"
description: "10명의 사용자를 팔로우했습니다" description: "10명의 유저를 팔로우했다"
_following50: _following50:
title: "친구 잔뜩" title: "친구 잔뜩"
description: "50명의 사용자를 팔로우했습니다" description: "50명의 유저를 팔로우했다"
_following100: _following100:
title: "주소록 한 권으론 부족해" title: "주소록 한 권으론 부족해"
description: "100명의 사용자를 팔로우했습니다" description: "100명의 유저를 팔로우했다"
_following300: _following300:
title: "친구가 넘쳐나" title: "친구가 넘쳐나"
description: "300명의 사용자를 팔로우했습니다" description: "300명의 유저를 팔로우했다"
_followers1: _followers1:
title: "첫 팔로워" title: "첫 팔로워"
description: "사용자가 처음으로 팔로잉했습니다" description: "유저가 처음으로 팔로잉했다"
_followers10: _followers10:
title: "팔로우 미!" title: "팔로우 미!"
description: "10명의 사용자가 팔로우했습니다" description: "10명의 유저가 팔로우했다"
_followers50: _followers50:
title: "이곳저곳" title: "이곳저곳"
description: "50명의 사용자가 팔로우했습니다" description: "50명의 유저가 팔로우했다"
_followers100: _followers100:
title: "인기왕" title: "인기왕"
description: "100명의 사용자가 팔로우했습니다" description: "100명의 유저가 팔로우했다"
_followers300: _followers300:
title: "줄 좀 서봐요" title: "줄 좀 서봐요"
description: "100명의 사용자가 팔로우했습니다" description: "100명의 유저가 팔로우했다"
_followers500: _followers500:
title: "기지국" title: "기지국"
description: "500명의 사용자가 팔로우했습니다" description: "500명의 유저가 팔로우했다"
_followers1000: _followers1000:
title: "유명인사" title: "유명인사"
description: "1,000명의 사용자가 팔로우했습니다" description: "1,000명의 유저가 팔로우했다"
_collectAchievements30: _collectAchievements30:
title: "도전 과제 콜렉터" title: "도전 과제 콜렉터"
description: "30개의 도전과제를 획득했습니다" description: "30개의 도전과제를 획득했다"
_viewAchievements3min: _viewAchievements3min:
title: "저 도전과제 좋아해요" title: "저 도전과제 좋아해요"
description: "도전 과제 목록을 3분 이상 쳐다봤습니다" description: "도전 과제 목록을 3분 이상 쳐다봤다"
_iLoveMisskey: _iLoveMisskey:
title: "I Love Misskey" title: "I Love Misskey"
description: "\"I ❤ #Misskey\"를 포스트했습니다" description: "\"I ❤ #Misskey\"를 게시했다"
flavor: "Misskey를 이용해 주셔서 감사합니다! ― 개발 팀" flavor: "Misskey를 이용해 주셔서 감사합니다! ― 개발 팀"
_foundTreasure: _foundTreasure:
title: "보물찾기" title: "보물찾기"
description: "숨겨진 보물을 발견했습니다" description: "숨겨진 보물을 발견했다"
_client30min: _client30min:
title: "잠시 쉬어요" title: "잠시 쉬어요"
description: "클라이언트를 시작하고 30분이 경과하였습니다" description: "클라이언트를 시작하고 30분이 경과다"
_client60min: _client60min:
title: "No \"Miss\" in Misskey" title: "No \"Miss\" in Misskey"
description: "클라이언트를 시작하고 60분이 경과하였습니다" description: "클라이언트를 시작하고 60분이 경과다"
_noteDeletedWithin1min: _noteDeletedWithin1min:
title: "있었는데요 없었습니다" title: "있었는데요 없었습니다"
description: "노트를 포스트한 후 1분 이내에 삭제했습니다" description: "노트를 게시한 후 1분 이내에 삭제했다"
_postedAtLateNight: _postedAtLateNight:
title: "올빼미" title: "올빼미"
description: "한밤중에 노트를 포스트했습니다" description: "한밤중에 노트를 게시했다"
flavor: "잠 좀 자세요. 걱정돼요." flavor: "잠 좀 자세요. 걱정돼요."
_postedAt0min0sec: _postedAt0min0sec:
title: "정각" title: "정각"
description: "0분 0초 정각에 노트를 작성했습니다" description: "0분 0초 정각에 노트를 게시했다"
flavor: "째깍 째깍 째깍 땡!" flavor: "째깍 째깍 째깍 땡!"
_selfQuote: _selfQuote:
title: "혼잣말" title: "혼잣말"
description: "자기 노트를 인용했습니다" description: "자기 노트를 인용했다"
_htl20npm: _htl20npm:
title: "타임라인 폭주 중" title: "타임라인 폭주 중"
description: "1분 사이에 홈 타임라인에 노트가 20개 넘게 생성되었습니다" description: "1분 사이에 홈 타임라인에 노트가 20개 넘게 생성되었다"
_viewInstanceChart: _viewInstanceChart:
title: "애널리스트" title: "애널리스트"
description: "서버의 차트를 열었습니다" description: "서버의 차트를 열었다"
_outputHelloWorldOnScratchpad: _outputHelloWorldOnScratchpad:
title: "Hello, world!" title: "Hello, world!"
description: "스크래치패드에서 hello world를 출력했습니다" description: "스크래치패드에서 hello world를 출력했다"
_open3windows: _open3windows:
title: "멀티 윈도우" title: "멀티 윈도우"
description: "3개 이상의 창을 열었습니다" description: "3개 이상의 창을 열었다"
_driveFolderCircularReference: _driveFolderCircularReference:
title: "순환 참조" title: "순환 참조"
description: "드라이브 폴더에 스스로를 넣게 했습니다" description: "드라이브 폴더에 스스로를 넣게 했다"
_reactWithoutRead: _reactWithoutRead:
title: "읽고 답하긴 하시는 건가요?" title: "읽고 답하긴 하시는 건가요?"
description: "100자가 넘는 노트를 작성한 지 3초 안에 반응했어요" description: "100자가 넘는 노트를 작성한 지 3초 안에 리액션했다"
_clickedClickHere: _clickedClickHere:
title: "여기를 누르세요" title: "여길 눌러보세요"
description: "여기를 눌렀습니다" description: "여기를 눌렀다"
_justPlainLucky: _justPlainLucky:
title: "그냥 운이 좋았어" title: "그냥 운이 좋았어"
description: "매 10초마다 0.01%의 확률로 달성됩니다" description: "매 10초마다 0.01%의 확률로 달성다"
_setNameToSyuilo: _setNameToSyuilo:
title: "신 콤플렉스" title: "신 콤플렉스"
description: "이름을 syuilo로 설정했습니다" description: "이름을 syuilo로 설정했다"
_passedSinceAccountCreated1: _passedSinceAccountCreated1:
title: "1주년" title: "1주년"
description: "계정을 생성하고 1년이 지났습니다" description: "계정을 생성하고 1년이 지났다"
_passedSinceAccountCreated2: _passedSinceAccountCreated2:
title: "2주년" title: "2주년"
description: "계정을 생성하고 2년이 지났습니다" description: "계정을 생성하고 2년이 지났다"
_passedSinceAccountCreated3: _passedSinceAccountCreated3:
title: "3주년" title: "3주년"
description: "계정을 생성하고 3년이 지났습니다" description: "계정을 생성하고 3년이 지났다"
_loggedInOnBirthday: _loggedInOnBirthday:
title: "생일 축하합니다!" title: "생일 축하합니다!"
description: "생일에 로그인했습니다" description: "생일에 로그인했다"
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "새해 복 많이 받으세요" title: "새해 복 많이 받으세요"
description: "새해 첫 날에 로그인했습니다" description: "새해 첫 날에 로그인했다"
flavor: "올해에도 저희 서버에 관심을 가져 주셔서 감사합니다" flavor: "올해에도 저희 서버에 관심을 가져 주셔서 감사합니다"
_cookieClicked: _cookieClicked:
title: "쿠키를 클릭하는 게임" title: "쿠키를 클릭하는 게임"
description: "쿠키를 클릭했습니다" description: "쿠키를 클릭했다"
flavor: "소프트웨어 착각하지 않으셨나요?" flavor: "소프트웨어 착각하지 않았어?"
_brainDiver: _brainDiver:
title: "Brain Diver" title: "Brain Diver"
description: "Brain Diver로의 링크를 첨부했습니다" description: "Brain Diver로의 링크를 첨부했다"
flavor: "Misskey-Misskey La-Tu-Ma" flavor: "Misskey-Misskey La-Tu-Ma"
_smashTestNotificationButton: _smashTestNotificationButton:
title: "테스트 과잉" title: "테스트 과잉"
description: "매우 짧은 시간 안에 알림 테스트를 여러 번 수행했습니다" description: "매우 짧은 시간 안에 알림 테스트를 여러 번 수행했다"
_tutorialCompleted: _tutorialCompleted:
title: "Misskey 입문자 과정 수료증" title: "Misskey 입문자 과정 수료증"
description: "튜토리얼을 완료했습니다" description: "튜토리얼을 완료했다"
_bubbleGameExplodingHead: _bubbleGameExplodingHead:
title: "🤯" title: "🤯"
description: "버블 게임에서 가장 큰 물건을 내놓았다" description: "버블 게임에서 가장 큰 물건을 내놓았다"
_bubbleGameDoubleExplodingHead: _bubbleGameDoubleExplodingHead:
title: "더블 🤯" title: "더블 🤯"
description: "버블게임에서 가장 큰 물건 2개를 동시에 내놓았다." description: "버블게임에서 가장 큰 물건 2개를 동시에 내놓았다"
flavor: "이 정도만 도시락통에 🤯 🤯 조금만 더" flavor: "이 정도만 도시락통에 🤯 🤯 조금만 더"
_role: _role:
new: "새 역할 생성" new: "새 역할 생성"
@ -1867,7 +1871,7 @@ _role:
permission: "역할 권한" permission: "역할 권한"
descriptionOfPermission: "<b>조정자</b>는 기본적인 조정 작업을 진행할 수 있습니다.\n<b>관리자</b>는 서버의 모든 설정을 변경할 수 있습니다." descriptionOfPermission: "<b>조정자</b>는 기본적인 조정 작업을 진행할 수 있습니다.\n<b>관리자</b>는 서버의 모든 설정을 변경할 수 있습니다."
assignTarget: "할당 대상" assignTarget: "할당 대상"
descriptionOfAssignTarget: "<b>수동</b>을 선택하면 누가 이 역할에 포함되는지를 수동으로 관리할 수 있습니다.\n<b>조건부</b>를 선택하면 조건을 설정해 일치하는 사용자를 자동으로 포함되게 할 수 있습니다." descriptionOfAssignTarget: "<b>수동</b>을 선택하면 누가 이 역할에 포함되는지를 수동으로 관리할 수 있습니다.\n<b>조건부</b>를 선택하면 조건을 설정해 일치하는 유저를 자동으로 포함되게 할 수 있습니다."
manual: "수동" manual: "수동"
manualRoles: "수동 역할" manualRoles: "수동 역할"
conditional: "조건부" conditional: "조건부"
@ -1875,7 +1879,7 @@ _role:
condition: "조건" condition: "조건"
isConditionalRole: "조건부 역할입니다." isConditionalRole: "조건부 역할입니다."
isPublic: "역할 공개" isPublic: "역할 공개"
descriptionOfIsPublic: "역할에 할당된 사용자를 누구나 볼 수 있습니다. 또한 사용자 프로필에 이 역할이 표시됩니다." descriptionOfIsPublic: "역할에 할당된 유저를 누구나 볼 수 있습니다. 또한 유저 프로필에 이 역할이 표시됩니다."
options: "옵션" options: "옵션"
policies: "정책" policies: "정책"
baseRole: "기본 역할" baseRole: "기본 역할"
@ -1888,10 +1892,10 @@ _role:
descriptionOfIsExplorable: "활성화하면 역할 타임라인을 공개합니다. 비활성화 시 타임라인이 공개되지 않습니다." descriptionOfIsExplorable: "활성화하면 역할 타임라인을 공개합니다. 비활성화 시 타임라인이 공개되지 않습니다."
displayOrder: "표시 순서" displayOrder: "표시 순서"
descriptionOfDisplayOrder: "값이 클 수록 UI에서 먼저 표시됩니다." descriptionOfDisplayOrder: "값이 클 수록 UI에서 먼저 표시됩니다."
preserveAssignmentOnMoveAccount: "마이그레이션 대상 계정에도 할당 상태 전달" preserveAssignmentOnMoveAccount: "이전 대상 계정에도 할당 상태 전달"
preserveAssignmentOnMoveAccount_description: "켜면 이 역할이 부여된 계정이 마이그레이션될 때 마이그레이션 대상 계정에도 이 역할이 승계됩니다." preserveAssignmentOnMoveAccount_description: "켜면 이 역할이 부여된 계정이 이전될 때 마이그레이션 대상 계정에도 이 역할이 승계됩니다."
canEditMembersByModerator: "모더레이터의 역할 수정 허용" canEditMembersByModerator: "모더레이터의 역할 수정 허용"
descriptionOfCanEditMembersByModerator: "이 옵션을 켜면 모더레이터도 이 역할에 사용자를 할당하거나 삭제할 수 있습니다. 꺼져 있으면 관리자만 할당이 가능합니다." descriptionOfCanEditMembersByModerator: "이 옵션을 켜면 모더레이터도 이 역할에 유저를 할당하거나 삭제할 수 있습니다. 꺼져 있으면 관리자만 할당이 가능합니다."
priority: "우선순위" priority: "우선순위"
_priority: _priority:
low: "낮음" low: "낮음"
@ -1917,8 +1921,8 @@ _role:
webhookMax: "만들 수 있는 Webhook 수" webhookMax: "만들 수 있는 Webhook 수"
clipMax: "만들 수 있는 클립 수" clipMax: "만들 수 있는 클립 수"
noteEachClipsMax: "클립에 넣을 수 있는 노트 수" noteEachClipsMax: "클립에 넣을 수 있는 노트 수"
userListMax: "만들 수 있는 사용자 리스트 수" userListMax: "만들 수 있는 유저 리스트 수"
userEachUserListsMax: "사용자 리스트에 넣을 수 있는 사용자 수" userEachUserListsMax: "유저 리스트에 넣을 수 있는 유저 수"
rateLimitFactor: "요청 빈도 제한" rateLimitFactor: "요청 빈도 제한"
descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다." descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다."
canHideAds: "광고 숨기기" canHideAds: "광고 숨기기"
@ -1930,24 +1934,24 @@ _role:
canImportFollowing: "팔로우 가져오기 허용" canImportFollowing: "팔로우 가져오기 허용"
canImportMuting: "뮤트 목록 가져오기 허용" canImportMuting: "뮤트 목록 가져오기 허용"
canImportUserLists: "리스트 목록 가져오기 허용" canImportUserLists: "리스트 목록 가져오기 허용"
canChat: "채팅을 허락" chatAvailability: "채팅을 허락"
_condition: _condition:
roleAssignedTo: "수동 역할에 이미 할당됨" roleAssignedTo: "수동 역할에 이미 할당됨"
isLocal: "로컬 사용자" isLocal: "로컬 유저"
isRemote: "원격 사용자" isRemote: "리모트 유저"
isCat: "고양이 사용자" isCat: "고양이 유저"
isBot: "봇 사용자" isBot: "봇 유저"
isSuspended: "정지된 사용자" isSuspended: "정지된 유저"
isLocked: "잠금 계정 사용자" isLocked: "잠금 계정 유저"
isExplorable: "‘계정을 쉽게 발견하도록 하기’를 활성화한 사용자" isExplorable: "‘계정을 쉽게 발견하도록 하기’를 활성화한 유저"
createdLessThan: "가입한 지 다음 일수 이내인 유저" createdLessThan: "가입한 지 다음 일수 이내인 유저"
createdMoreThan: "가입한 지 다음 일수 이상인 유저" createdMoreThan: "가입한 지 다음 일수 이상인 유저"
followersLessThanOrEq: "팔로워 수가 다음 이하인 유저" followersLessThanOrEq: "팔로워 수가 다음 이하인 유저"
followersMoreThanOrEq: "팔로워 수가 다음보다 많은 사용자" followersMoreThanOrEq: "팔로워 수가 다음보다 많은 유저"
followingLessThanOrEq: "팔로잉 수가 다음 이하인 유저" followingLessThanOrEq: "팔로잉 수가 다음 이하인 유저"
followingMoreThanOrEq: "팔로잉 수가 다음보다 많은 사용자" followingMoreThanOrEq: "팔로잉 수가 다음보다 많은 유저"
notesLessThanOrEq: "노트 수가 다음 이하인 유저" notesLessThanOrEq: "노트 수가 다음 이하인 유저"
notesMoreThanOrEq: "노트 수가 다음보다 많은 사용자" notesMoreThanOrEq: "노트 수가 다음보다 많은 유저"
and: "다음을 모두 만족" and: "다음을 모두 만족"
or: "다음을 하나라도 만족" or: "다음을 하나라도 만족"
not: "다음을 만족하지 않음" not: "다음을 만족하지 않음"
@ -1989,7 +1993,7 @@ _ad:
adsSettings: "광고 표시 설정" adsSettings: "광고 표시 설정"
notesPerOneAd: "실시간으로 갱신되는 타임라인에서 광고를 노출시키는 간격 (노트 당)" notesPerOneAd: "실시간으로 갱신되는 타임라인에서 광고를 노출시키는 간격 (노트 당)"
setZeroToDisable: "0으로 지정하면 실시간 타임라인에서의 광고를 비활성화합니다" setZeroToDisable: "0으로 지정하면 실시간 타임라인에서의 광고를 비활성화합니다"
adsTooClose: "광고의 표시 간격이 매우 작아, 사용자 경험에 부정적인 영향을 미칠 수 있습니다." adsTooClose: "광고의 표시 간격이 매우 작아, 유저 경험에 부정적인 영향을 미칠 수 있습니다."
_forgotPassword: _forgotPassword:
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다." enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오." ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."
@ -2244,7 +2248,7 @@ _permissions:
"write:pages": "페이지를 수정합니다" "write:pages": "페이지를 수정합니다"
"read:page-likes": "페이지의 좋아요를 확인합니다" "read:page-likes": "페이지의 좋아요를 확인합니다"
"write:page-likes": "페이지에 좋아요를 추가하거나 취소합니다" "write:page-likes": "페이지에 좋아요를 추가하거나 취소합니다"
"read:user-groups": "사용자 그룹 보기" "read:user-groups": "유저 그룹 보기"
"write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다" "write:user-groups": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
"read:channels": "채널을 보기" "read:channels": "채널을 보기"
"write:channels": "채널을 추가하거나 삭제합니다" "write:channels": "채널을 추가하거나 삭제합니다"
@ -2256,23 +2260,23 @@ _permissions:
"write:flash": "Play를 조작합니다" "write:flash": "Play를 조작합니다"
"read:flash-likes": "Play의 좋아요를 봅니다" "read:flash-likes": "Play의 좋아요를 봅니다"
"write:flash-likes": "Play의 좋아요를 조작합니다" "write:flash-likes": "Play의 좋아요를 조작합니다"
"read:admin:abuse-user-reports": "사용자 신고 보기" "read:admin:abuse-user-reports": "유저 신고 보기"
"write:admin:delete-account": "사용자 계정 삭제하기" "write:admin:delete-account": "유저 계정 삭제하기"
"write:admin:delete-all-files-of-a-user": "모든 사용자 파일 삭제하기" "write:admin:delete-all-files-of-a-user": "모든 유저 파일 삭제하기"
"read:admin:index-stats": "데이터베이스 색인 정보 보기" "read:admin:index-stats": "데이터베이스 색인 정보 보기"
"read:admin:table-stats": "데이터베이스 테이블 정보 보기" "read:admin:table-stats": "데이터베이스 테이블 정보 보기"
"read:admin:user-ips": "사용자 IP 주소 보기" "read:admin:user-ips": "유저 IP 주소 보기"
"read:admin:meta": "인스턴스 메타데이터 보기" "read:admin:meta": "인스턴스 메타데이터 보기"
"write:admin:reset-password": "사용자 비밀번호 재설정하기" "write:admin:reset-password": "유저 비밀번호 재설정하기"
"write:admin:resolve-abuse-user-report": "사용자 신고 처리하기" "write:admin:resolve-abuse-user-report": "유저 신고 처리하기"
"write:admin:send-email": "이메일 보내기" "write:admin:send-email": "이메일 보내기"
"read:admin:server-info": "서버 정보 보기" "read:admin:server-info": "서버 정보 보기"
"read:admin:show-moderation-log": "조정 기록 보기" "read:admin:show-moderation-log": "조정 기록 보기"
"read:admin:show-user": "사용자 개인정보 보기" "read:admin:show-user": "유저 개인정보 보기"
"write:admin:suspend-user": "사용자 정지하기" "write:admin:suspend-user": "유저 정지하기"
"write:admin:unset-user-avatar": "사용자 아바타 삭제하기" "write:admin:unset-user-avatar": "유저 아바타 삭제하기"
"write:admin:unset-user-banner": "사용자 배너 삭제하기" "write:admin:unset-user-banner": "유저 배너 삭제하기"
"write:admin:unsuspend-user": "사용자 정지 해제하기" "write:admin:unsuspend-user": "유저 정지 해제하기"
"write:admin:meta": "인스턴스 메타데이터 수정하기" "write:admin:meta": "인스턴스 메타데이터 수정하기"
"write:admin:user-note": "조정 기록 수정하기" "write:admin:user-note": "조정 기록 수정하기"
"write:admin:roles": "역할 수정하기" "write:admin:roles": "역할 수정하기"
@ -2286,15 +2290,15 @@ _permissions:
"write:admin:avatar-decorations": "아바타 꾸미기 수정하기" "write:admin:avatar-decorations": "아바타 꾸미기 수정하기"
"read:admin:avatar-decorations": "아바타 꾸미기 보기" "read:admin:avatar-decorations": "아바타 꾸미기 보기"
"write:admin:federation": "연합 정보 수정하기" "write:admin:federation": "연합 정보 수정하기"
"write:admin:account": "사용자 계정 수정하기" "write:admin:account": "유저 계정 수정하기"
"read:admin:account": "사용자 정보 보기" "read:admin:account": "유저 정보 보기"
"write:admin:emoji": "이모지 수정하기" "write:admin:emoji": "이모지 수정하기"
"read:admin:emoji": "이모지 보기" "read:admin:emoji": "이모지 보기"
"write:admin:queue": "작업 대기열 수정하기" "write:admin:queue": "작업 대기열 수정하기"
"read:admin:queue": "작업 대기열 정보 보기" "read:admin:queue": "작업 대기열 정보 보기"
"write:admin:promo": "홍보 기록 수정하기" "write:admin:promo": "홍보 기록 수정하기"
"write:admin:drive": "사용자 드라이브 수정하기" "write:admin:drive": "유저 드라이브 수정하기"
"read:admin:drive": "사용자 드라이브 정보 보기" "read:admin:drive": "유저 드라이브 정보 보기"
"read:admin:stream": "관리자용 Websocket API 사용하기" "read:admin:stream": "관리자용 Websocket API 사용하기"
"write:admin:ad": "광고 수정하기" "write:admin:ad": "광고 수정하기"
"read:admin:ad": "광고 보기" "read:admin:ad": "광고 보기"
@ -2316,7 +2320,7 @@ _auth:
callback: "앱으로 돌아갑니다" callback: "앱으로 돌아갑니다"
accepted: "접근 권한이 부여되었습니다." accepted: "접근 권한이 부여되었습니다."
denied: "접근이 거부되었습니다" denied: "접근이 거부되었습니다"
scopeUser: "다음 사용자로 활동하고 있습니다." scopeUser: "다음 유저로 활동하고 있습니다."
pleaseLogin: "어플리케이션의 접근을 허가하려면 로그인하십시오." pleaseLogin: "어플리케이션의 접근을 허가하려면 로그인하십시오."
byClickingYouWillBeRedirectedToThisUrl: "접근을 허용하면 자동으로 다음 URL로 이동합니다." byClickingYouWillBeRedirectedToThisUrl: "접근을 허용하면 자동으로 다음 URL로 이동합니다."
_antennaSources: _antennaSources:
@ -2353,7 +2357,7 @@ _widgets:
postForm: "글 입력란" postForm: "글 입력란"
slideshow: "슬라이드 쇼" slideshow: "슬라이드 쇼"
button: "버튼" button: "버튼"
onlineUsers: "온라인 사용자" onlineUsers: "온라인 유저"
jobQueue: "작업 대기열" jobQueue: "작업 대기열"
serverMetric: "서버 통계" serverMetric: "서버 통계"
aiscript: "AiScript 콘솔" aiscript: "AiScript 콘솔"
@ -2363,7 +2367,7 @@ _widgets:
_userList: _userList:
chooseList: "리스트 선택" chooseList: "리스트 선택"
clicker: "클리커" clicker: "클리커"
birthdayFollowings: "오늘이 생일인 사용자" birthdayFollowings: "오늘이 생일인 유저"
_cw: _cw:
hide: "숨기기" hide: "숨기기"
show: "더 보기" show: "더 보기"
@ -2415,7 +2419,7 @@ _postForm:
f: "작성해주시길 기다리고 있어요..." f: "작성해주시길 기다리고 있어요..."
_profile: _profile:
name: "이름" name: "이름"
username: "사용자 이름" username: "유저명"
description: "자기소개" description: "자기소개"
youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다." youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다."
metadata: "추가 정보" metadata: "추가 정보"
@ -2446,7 +2450,7 @@ _charts:
apRequest: "요청" apRequest: "요청"
usersIncDec: "유저 수 증감" usersIncDec: "유저 수 증감"
usersTotal: "유저 수 합계" usersTotal: "유저 수 합계"
activeUsers: "활동 사용자 수" activeUsers: "활동 유저 수"
notesIncDec: "노트 수 증감" notesIncDec: "노트 수 증감"
localNotesIncDec: "로컬 노트 수 증감" localNotesIncDec: "로컬 노트 수 증감"
remoteNotesIncDec: "리모트 노트 수 증감" remoteNotesIncDec: "리모트 노트 수 증감"
@ -2457,8 +2461,8 @@ _charts:
storageUsageTotal: "스토리지 사용량 합계" storageUsageTotal: "스토리지 사용량 합계"
_instanceCharts: _instanceCharts:
requests: "요청" requests: "요청"
users: "사용자 수 차이" users: "유저 수 차이"
usersTotal: "누적 사용자 수" usersTotal: "누적 유저 수"
notes: "노트 수 증감" notes: "노트 수 증감"
notesTotal: "누적 노트 수" notesTotal: "누적 노트 수"
ff: "팔로잉/팔로워 증감" ff: "팔로잉/팔로워 증감"
@ -2561,7 +2565,7 @@ _notification:
checkNotificationBehavior: "알림 표시를 체크하기" checkNotificationBehavior: "알림 표시를 체크하기"
sendTestNotification: "테스트 알림 보내기" sendTestNotification: "테스트 알림 보내기"
notificationWillBeDisplayedLikeThis: "알림이 이렇게 표시됩니다" notificationWillBeDisplayedLikeThis: "알림이 이렇게 표시됩니다"
reactedBySomeUsers: "{n}명이 반응했습니다" reactedBySomeUsers: "{n}명이 리액션했습니다"
likedBySomeUsers: "{n}명이 좋아요를 했습니다" likedBySomeUsers: "{n}명이 좋아요를 했습니다"
renotedBySomeUsers: "{n}명이 리노트했습니다" renotedBySomeUsers: "{n}명이 리노트했습니다"
followedBySomeUsers: "{n}명에게 팔로우됨" followedBySomeUsers: "{n}명에게 팔로우됨"
@ -2572,18 +2576,18 @@ _notification:
createTokenDescription: "만약 기억이 나지 않는다면 '{text}'를 통해 액세스 토큰을 삭제해 주세요." createTokenDescription: "만약 기억이 나지 않는다면 '{text}'를 통해 액세스 토큰을 삭제해 주세요."
_types: _types:
all: "전부" all: "전부"
note: "사용자의 새 글" note: "유저의 새 글"
follow: "팔로잉" follow: "팔로잉"
mention: "멘션" mention: "멘션"
reply: "답글" reply: "답글"
renote: "리노트" renote: "리노트"
quote: "인용" quote: "인용"
reaction: "반응" reaction: "리액션"
pollEnded: "투표가 종료됨" pollEnded: "투표가 종료됨"
receiveFollowRequest: "팔로우 요청을 받았을 때" receiveFollowRequest: "팔로우 요청을 받았을 때"
followRequestAccepted: "팔로우 요청이 승인되었을 때" followRequestAccepted: "팔로우 요청이 승인되었을 때"
roleAssigned: "역할이 부여 됨" roleAssigned: "역할이 부여됨"
chatRoomInvitationReceived: "채팅 룸에 초대받았습니다" chatRoomInvitationReceived: "채팅 룸에 초대받"
achievementEarned: "도전 과제 획득" achievementEarned: "도전 과제 획득"
exportCompleted: "추출을 성공함" exportCompleted: "추출을 성공함"
login: "로그인" login: "로그인"
@ -2671,10 +2675,10 @@ _abuseReport:
mail: "이메일" mail: "이메일"
webhook: "Webhook" webhook: "Webhook"
_captions: _captions:
mail: "모더레이터 권한을 가진 사용자의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)" mail: "모더레이터 권한을 가진 유저의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)"
webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)" webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)"
keywords: "키워드" keywords: "키워드"
notifiedUser: "알릴 사용자" notifiedUser: "알릴 유저"
notifiedWebhook: "사용할 Webhook" notifiedWebhook: "사용할 Webhook"
deleteConfirm: "수신자를 삭제하시겠습니까?" deleteConfirm: "수신자를 삭제하시겠습니까?"
_moderationLogTypes: _moderationLogTypes:
@ -2693,11 +2697,11 @@ _moderationLogTypes:
deleteDriveFile: "파일 삭제" deleteDriveFile: "파일 삭제"
deleteNote: "노트 삭제" deleteNote: "노트 삭제"
createGlobalAnnouncement: "전역 공지사항 생성" createGlobalAnnouncement: "전역 공지사항 생성"
createUserAnnouncement: "사용자 공지사항 만들기" createUserAnnouncement: "유저에게 공지사항 만들기"
updateGlobalAnnouncement: "모든 공지사항 수정" updateGlobalAnnouncement: "모든 공지사항 수정"
updateUserAnnouncement: "사용자 공지사항 수정" updateUserAnnouncement: "유저의 공지사항 수정"
deleteGlobalAnnouncement: "모든 공지사항 삭제" deleteGlobalAnnouncement: "모든 공지사항 삭제"
deleteUserAnnouncement: "사용자 공지사항 삭제" deleteUserAnnouncement: "유저의 공지사항 삭제"
resetPassword: "비밀번호 재설정" resetPassword: "비밀번호 재설정"
suspendRemoteInstance: "리모트 서버를 정지" suspendRemoteInstance: "리모트 서버를 정지"
unsuspendRemoteInstance: "리모트 서버의 정지를 해제" unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
@ -2725,7 +2729,7 @@ _moderationLogTypes:
deleteAccount: "계정을 삭제" deleteAccount: "계정을 삭제"
deletePage: "페이지를 삭제" deletePage: "페이지를 삭제"
deleteFlash: "Play를 삭제" deleteFlash: "Play를 삭제"
deleteGalleryPost: "갤러리 포스트를 삭제" deleteGalleryPost: "갤러리 게시물을 삭제"
deleteChatRoom: "채팅 룸 삭제" deleteChatRoom: "채팅 룸 삭제"
updateProxyAccountDescription: "프록시 계정의 설명 업데이트" updateProxyAccountDescription: "프록시 계정의 설명 업데이트"
_fileViewer: _fileViewer:
@ -2939,7 +2943,7 @@ _embedCodeGen:
_selfXssPrevention: _selfXssPrevention:
warning: "경고" warning: "경고"
title: "“이 화면에 뭔가를 붙여넣어라\"는 것은 모두 사기입니다." title: "“이 화면에 뭔가를 붙여넣어라\"는 것은 모두 사기입니다."
description1: "여기에 무언가를 붙여넣으면 악의적인 사용자에게 계정을 탈취당하거나 개인정보를 도용당할 수 있습니다." description1: "여기에 무언가를 붙여넣으면 악의적인 유저에게 계정을 탈취당하거나 개인정보를 도용당할 수 있습니다."
description2: "붙여 넣으려는 항목이 무엇인지 정확히 이해하지 못하는 경우, %c지금 바로 작업을 중단하고 이 창을 닫으십시오." description2: "붙여 넣으려는 항목이 무엇인지 정확히 이해하지 못하는 경우, %c지금 바로 작업을 중단하고 이 창을 닫으십시오."
description3: "자세한 내용은 여기를 확인해 주세요. {link}" description3: "자세한 내용은 여기를 확인해 주세요. {link}"
_followRequest: _followRequest:
@ -2972,8 +2976,8 @@ _captcha:
title: "CAPTCHA 검증을 실패했습니다." title: "CAPTCHA 검증을 실패했습니다."
text: "설정이 올바른지 다시 한 번 확인해보세요." text: "설정이 올바른지 다시 한 번 확인해보세요."
_unknown: _unknown:
title: "CAPTCHA 에러" title: "CAPTCHA 오류"
text: "알 수 없는 에러가 발생했습니다." text: "알 수 없는 오류가 발생했습니다."
_bootErrors: _bootErrors:
title: "로딩이 실패함" title: "로딩이 실패함"
serverError: "잠시 기다렸다가 다시 로드해도 여전히 문제가 해결되지 않으면 아래 Error ID와 함께 서버 관리자에게 연락해 주세요." serverError: "잠시 기다렸다가 다시 로드해도 여전히 문제가 해결되지 않으면 아래 Error ID와 함께 서버 관리자에게 연락해 주세요."
@ -2990,7 +2994,7 @@ _search:
searchScopeAll: "전체" searchScopeAll: "전체"
searchScopeLocal: "로컬" searchScopeLocal: "로컬"
searchScopeServer: "서버 지정" searchScopeServer: "서버 지정"
searchScopeUser: "사용자 지정" searchScopeUser: "유저 지정"
pleaseEnterServerHost: "서버의 호스트를 입력해 주세요." pleaseEnterServerHost: "서버의 호스트를 입력해 주세요."
pleaseSelectUser: "유저를 선택해주세요" pleaseSelectUser: "유저를 선택해주세요"
serverHostPlaceholder: "예: misskey.example.com" serverHostPlaceholder: "예: misskey.example.com"

View File

@ -424,7 +424,7 @@ antennaExcludeBots: "排除机器人账户"
antennaKeywordsDescription: "AND 条件用空格分隔OR 条件用换行符分隔。" antennaKeywordsDescription: "AND 条件用空格分隔OR 条件用换行符分隔。"
notifyAntenna: "开启通知" notifyAntenna: "开启通知"
withFileAntenna: "仅带有附件的帖子" withFileAntenna: "仅带有附件的帖子"
hideNotesInSensitiveChannel: "隐藏敏感频道内的帖子" excludeNotesInSensitiveChannel: "排除敏感频道内的帖子"
enableServiceworker: "启用 ServiceWorker" enableServiceworker: "启用 ServiceWorker"
antennaUsersDescription: "指定用户名,一行一个" antennaUsersDescription: "指定用户名,一行一个"
caseSensitive: "区分大小写" caseSensitive: "区分大小写"
@ -1341,6 +1341,9 @@ right: "右"
bottom: "下" bottom: "下"
top: "上" top: "上"
embed: "嵌入" embed: "嵌入"
settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置 → 其它 → 迁移旧设置来手动迁移)"
readonly: "只读"
goToDeck: "返回至 Deck"
_chat: _chat:
noMessagesYet: "还没有消息" noMessagesYet: "还没有消息"
newMessage: "新消息" newMessage: "新消息"
@ -1370,6 +1373,7 @@ _chat:
muteThisRoom: "静音此房间" muteThisRoom: "静音此房间"
deleteRoom: "删除房间" deleteRoom: "删除房间"
chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。" chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。"
chatIsReadOnlyForThisAccountOrServer: "此服务器或者账户内的聊天为只读。无法发布新信息或创建及加入群聊。"
chatNotAvailableInOtherAccount: "对方账户目前处于无法使用聊天的状态。" chatNotAvailableInOtherAccount: "对方账户目前处于无法使用聊天的状态。"
cannotChatWithTheUser: "无法与此用户聊天" cannotChatWithTheUser: "无法与此用户聊天"
cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。" cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。"
@ -1929,7 +1933,7 @@ _role:
canImportFollowing: "允许导入关注列表" canImportFollowing: "允许导入关注列表"
canImportMuting: "允许导入隐藏列表" canImportMuting: "允许导入隐藏列表"
canImportUserLists: "允许导入用户列表" canImportUserLists: "允许导入用户列表"
canChat: "允许聊天" chatAvailability: "允许聊天"
_condition: _condition:
roleAssignedTo: "已分配给手动角色" roleAssignedTo: "已分配给手动角色"
isLocal: "是本地用户" isLocal: "是本地用户"

View File

@ -424,7 +424,6 @@ antennaExcludeBots: "排除機器人帳戶"
antennaKeywordsDescription: "空格代表「以及」AND換行代表「或者」OR" antennaKeywordsDescription: "空格代表「以及」AND換行代表「或者」OR"
notifyAntenna: "通知有新貼文" notifyAntenna: "通知有新貼文"
withFileAntenna: "僅帶有附件的貼文" withFileAntenna: "僅帶有附件的貼文"
hideNotesInSensitiveChannel: "隱藏敏感頻道的貼文"
enableServiceworker: "啟用瀏覽器的推播通知" enableServiceworker: "啟用瀏覽器的推播通知"
antennaUsersDescription: "填寫使用者名稱,以換行分隔" antennaUsersDescription: "填寫使用者名稱,以換行分隔"
caseSensitive: "區分大小寫" caseSensitive: "區分大小寫"
@ -1342,6 +1341,7 @@ bottom: "下"
top: "上" top: "上"
embed: "嵌入" embed: "嵌入"
settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「設定 → 其他 → 舊設定資訊移轉」中手動進行移轉)" settingsMigrating: "正在移轉設定。請稍候……(之後也可以到「設定 → 其他 → 舊設定資訊移轉」中手動進行移轉)"
readonly: "唯讀"
_chat: _chat:
noMessagesYet: "尚無訊息" noMessagesYet: "尚無訊息"
newMessage: "新訊息" newMessage: "新訊息"
@ -1371,6 +1371,7 @@ _chat:
muteThisRoom: "此聊天室已靜音" muteThisRoom: "此聊天室已靜音"
deleteRoom: "刪除聊天室" deleteRoom: "刪除聊天室"
chatNotAvailableForThisAccountOrServer: "這個伺服器或這個帳號的聊天功能尚未啟用。" chatNotAvailableForThisAccountOrServer: "這個伺服器或這個帳號的聊天功能尚未啟用。"
chatIsReadOnlyForThisAccountOrServer: "在此伺服器或此帳戶上的聊天是唯讀的。您無法發布新訊息、建立或加入聊天室。"
chatNotAvailableInOtherAccount: "對方的帳號無法使用聊天功能。" chatNotAvailableInOtherAccount: "對方的帳號無法使用聊天功能。"
cannotChatWithTheUser: "無法與此使用者聊天" cannotChatWithTheUser: "無法與此使用者聊天"
cannotChatWithTheUser_description: "聊天功能目前無法使用,或對方尚未開放聊天功能。" cannotChatWithTheUser_description: "聊天功能目前無法使用,或對方尚未開放聊天功能。"
@ -1436,7 +1437,7 @@ _preferencesBackup:
_accountSettings: _accountSettings:
requireSigninToViewContents: "須登入以顯示內容" requireSigninToViewContents: "須登入以顯示內容"
requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。" requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"
requireSigninToViewContentsDescription2: "來自不支援 URL 預覽 (OGP)、 網頁嵌入和引用貼文的伺服器,也將停止顯示。" requireSigninToViewContentsDescription2: "針對您貼文的 URL 預覽 (OGP) 與網頁嵌入功能將會無法使用。而不支援引用貼文的伺服器,也將停止顯示。"
requireSigninToViewContentsDescription3: "這些限制可能不適用於被聯邦發送至遠端伺服器的內容。" requireSigninToViewContentsDescription3: "這些限制可能不適用於被聯邦發送至遠端伺服器的內容。"
makeNotesFollowersOnlyBefore: "讓過去的貼文僅對追隨者顯示" makeNotesFollowersOnlyBefore: "讓過去的貼文僅對追隨者顯示"
makeNotesFollowersOnlyBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對追隨者顯示。 如果您再次停用它,貼文的公開狀態也會恢復原狀。" makeNotesFollowersOnlyBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對追隨者顯示。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
@ -1931,7 +1932,7 @@ _role:
canImportFollowing: "允許匯入追隨名單" canImportFollowing: "允許匯入追隨名單"
canImportMuting: "允許匯入靜音名單" canImportMuting: "允許匯入靜音名單"
canImportUserLists: "允許匯入清單" canImportUserLists: "允許匯入清單"
canChat: "允許聊天" chatAvailability: "允許聊天"
_condition: _condition:
roleAssignedTo: "手動指派角色完成" roleAssignedTo: "手動指派角色完成"
isLocal: "本地使用者" isLocal: "本地使用者"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.4.0-rc.3", "version": "2025.4.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class ExcludeNotesInSensitiveChannel1744075766000 {
name = 'ExcludeNotesInSensitiveChannel1744075766000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" RENAME COLUMN "hideNotesInSensitiveChannel" TO "excludeNotesInSensitiveChannel"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" RENAME COLUMN "excludeNotesInSensitiveChannel" TO "hideNotesInSensitiveChannel"`);
}
}

View File

@ -114,7 +114,7 @@ export class AntennaService implements OnApplicationShutdown {
if (note.visibility === 'specified') return false; if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false; if (note.visibility === 'followers') return false;
if (antenna.hideNotesInSensitiveChannel && note.channel?.isSensitive) return false; if (antenna.excludeNotesInSensitiveChannel && note.channel?.isSensitive) return false;
if (antenna.excludeBots && noteUser.isBot) return false; if (antenna.excludeBots && noteUser.isBot) return false;

View File

@ -41,7 +41,7 @@ export class AntennaEntityService {
excludeBots: antenna.excludeBots, excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies, withReplies: antenna.withReplies,
withFile: antenna.withFile, withFile: antenna.withFile,
hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel, excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel,
isActive: antenna.isActive, isActive: antenna.isActive,
hasUnreadNote: false, // TODO hasUnreadNote: false, // TODO
notify: false, // 後方互換性のため notify: false, // 後方互換性のため

View File

@ -104,5 +104,5 @@ export class MiAntenna {
@Column('boolean', { @Column('boolean', {
default: false, default: false,
}) })
public hideNotesInSensitiveChannel: boolean; public excludeNotesInSensitiveChannel: boolean;
} }

View File

@ -100,7 +100,7 @@ export const packedAntennaSchema = {
optional: false, nullable: false, optional: false, nullable: false,
default: false, default: false,
}, },
hideNotesInSensitiveChannel: { excludeNotesInSensitiveChannel: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
default: false, default: false,

View File

@ -73,7 +73,7 @@ export const paramDef = {
excludeBots: { type: 'boolean' }, excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' },
withFile: { type: 'boolean' }, withFile: { type: 'boolean' },
hideNotesInSensitiveChannel: { type: 'boolean' }, excludeNotesInSensitiveChannel: { type: 'boolean' },
}, },
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
} as const; } as const;
@ -134,7 +134,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeBots: ps.excludeBots, excludeBots: ps.excludeBots,
withReplies: ps.withReplies, withReplies: ps.withReplies,
withFile: ps.withFile, withFile: ps.withFile,
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, excludeNotesInSensitiveChannel: ps.excludeNotesInSensitiveChannel,
}); });
this.globalEventService.publishInternalEvent('antennaCreated', antenna); this.globalEventService.publishInternalEvent('antennaCreated', antenna);

View File

@ -72,7 +72,7 @@ export const paramDef = {
excludeBots: { type: 'boolean' }, excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' },
withFile: { type: 'boolean' }, withFile: { type: 'boolean' },
hideNotesInSensitiveChannel: { type: 'boolean' }, excludeNotesInSensitiveChannel: { type: 'boolean' },
}, },
required: ['antennaId'], required: ['antennaId'],
} as const; } as const;
@ -130,7 +130,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeBots: ps.excludeBots, excludeBots: ps.excludeBots,
withReplies: ps.withReplies, withReplies: ps.withReplies,
withFile: ps.withFile, withFile: ps.withFile,
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, excludeNotesInSensitiveChannel: ps.excludeNotesInSensitiveChannel,
isActive: true, isActive: true,
lastUsedAt: new Date(), lastUsedAt: new Date(),
}); });

View File

@ -146,7 +146,7 @@ describe('アンテナ', () => {
caseSensitive: false, caseSensitive: false,
createdAt: new Date(response.createdAt).toISOString(), createdAt: new Date(response.createdAt).toISOString(),
excludeKeywords: [['']], excludeKeywords: [['']],
hideNotesInSensitiveChannel: false, excludeNotesInSensitiveChannel: false,
hasUnreadNote: false, hasUnreadNote: false,
isActive: true, isActive: true,
keywords: [['keyword']], keywords: [['keyword']],
@ -218,8 +218,8 @@ describe('アンテナ', () => {
{ parameters: () => ({ withReplies: true }) }, { parameters: () => ({ withReplies: true }) },
{ parameters: () => ({ withFile: false }) }, { parameters: () => ({ withFile: false }) },
{ parameters: () => ({ withFile: true }) }, { parameters: () => ({ withFile: true }) },
{ parameters: () => ({ hideNotesInSensitiveChannel: false }) }, { parameters: () => ({ excludeNotesInSensitiveChannel: false }) },
{ parameters: () => ({ hideNotesInSensitiveChannel: true }) }, { parameters: () => ({ excludeNotesInSensitiveChannel: true }) },
]; ];
test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
const response = await successfulApiCall({ const response = await successfulApiCall({
@ -633,7 +633,7 @@ describe('アンテナ', () => {
const keyword = 'キーワード'; const keyword = 'キーワード';
const antenna = await successfulApiCall({ const antenna = await successfulApiCall({
endpoint: 'antennas/create', endpoint: 'antennas/create',
parameters: { ...defaultParam, keywords: [[keyword]], hideNotesInSensitiveChannel: true }, parameters: { ...defaultParam, keywords: [[keyword]], excludeNotesInSensitiveChannel: true },
user: alice, user: alice,
}); });
const nonSensitiveChannel = await successfulApiCall({ const nonSensitiveChannel = await successfulApiCall({

View File

@ -173,21 +173,21 @@ function customStringify(obj: unknown): string {
/** /**
* *
*/ */
function extractElementText(node: ElementNode): string | null { function extractElementText(node: ElementNode, id: string): string | null {
return extractElementTextChecked(node, node.tag); return extractElementTextChecked(node, node.tag, id);
} }
function extractElementTextChecked(node: ElementNode, processingNodeName: string): string | null { function extractElementTextChecked(node: ElementNode, processingNodeName: string, id: string): string | null {
const result: string[] = []; const result: string[] = [];
for (const child of node.children) { for (const child of node.children) {
const text = extractElementText2Inner(child, processingNodeName); const text = extractElementText2Inner(child, processingNodeName, id);
if (text == null) return null; if (text == null) return null;
result.push(text); result.push(text);
} }
return result.join(''); return result.join('');
} }
function extractElementText2Inner(node: TemplateChildNode, processingNodeName: string): string | null { function extractElementText2Inner(node: TemplateChildNode, processingNodeName: string, id: string): string | null {
if (node.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION"); if (node.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION");
switch (node.type) { switch (node.type) {
@ -196,16 +196,16 @@ function extractElementText2Inner(node: TemplateChildNode, processingNodeName: s
if (expr.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error(`Unexpected COMPOUND_EXPRESSION`); if (expr.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error(`Unexpected COMPOUND_EXPRESSION`);
const exprResult = evalExpression(expr.content); const exprResult = evalExpression(expr.content);
if (typeof exprResult !== 'string') { if (typeof exprResult !== 'string') {
logger.error(`Result of interpolation node is not string at line ${node.loc.start.line}`); logger.error(`Result of interpolation node is not string at line ${id}:${node.loc.start.line}`);
return null; return null;
} }
return exprResult; return exprResult;
} }
case NodeTypes.ELEMENT: case NodeTypes.ELEMENT:
if (node.tagType === ElementTypes.ELEMENT) { if (node.tagType === ElementTypes.ELEMENT) {
return extractElementTextChecked(node, processingNodeName); return extractElementTextChecked(node, processingNodeName, id);
} else { } else {
logger.error(`Unexpected ${node.tag} extracting text of ${processingNodeName} ${node.loc.start.line}`); logger.error(`Unexpected ${node.tag} extracting text of ${processingNodeName} ${id}:${node.loc.start.line}`);
return null; return null;
} }
case NodeTypes.TEXT: case NodeTypes.TEXT:
@ -217,7 +217,7 @@ function extractElementText2Inner(node: TemplateChildNode, processingNodeName: s
case NodeTypes.IF_BRANCH: case NodeTypes.IF_BRANCH:
case NodeTypes.FOR: case NodeTypes.FOR:
case NodeTypes.TEXT_CALL: case NodeTypes.TEXT_CALL:
logger.error(`Unexpected controlflow element extracting text of ${processingNodeName} ${node.loc.start.line}`); logger.error(`Unexpected controlflow element extracting text of ${processingNodeName} ${id}:${node.loc.start.line}`);
return null; return null;
} }
} }
@ -229,7 +229,7 @@ function extractElementText2Inner(node: TemplateChildNode, processingNodeName: s
/** /**
* SearchLabel/SearchKeyword/SearchIconを探して抽出する関数 * SearchLabel/SearchKeyword/SearchIconを探して抽出する関数
*/ */
function extractSugarTags(nodes: TemplateChildNode[]): { label: string | null, keywords: string[], icon: string | null } { function extractSugarTags(nodes: TemplateChildNode[], id: string): { label: string | null, keywords: string[], icon: string | null } {
let label: string | null | undefined = undefined; let label: string | null | undefined = undefined;
let icon: string | null | undefined = undefined; let icon: string | null | undefined = undefined;
const keywords: string[] = []; const keywords: string[] = [];
@ -242,35 +242,35 @@ function extractSugarTags(nodes: TemplateChildNode[]): { label: string | null, k
return false; // SearchMarkerはスキップ return false; // SearchMarkerはスキップ
case 'SearchLabel': case 'SearchLabel':
if (label !== undefined) { if (label !== undefined) {
logger.warn(`Duplicate SearchLabel found, ignoring the second one at ${node.loc.start.line}`); logger.warn(`Duplicate SearchLabel found, ignoring the second one at ${id}:${node.loc.start.line}`);
break; // 2つ目のSearchLabelは無視 break; // 2つ目のSearchLabelは無視
} }
label = extractElementText(node); label = extractElementText(node, id);
return; return;
case 'SearchKeyword': case 'SearchKeyword':
const content = extractElementText(node); const content = extractElementText(node, id);
if (content) { if (content) {
keywords.push(content); keywords.push(content);
} }
return; return;
case 'SearchIcon': case 'SearchIcon':
if (icon !== undefined) { if (icon !== undefined) {
logger.warn(`Duplicate SearchIcon found, ignoring the second one at ${node.loc.start.line}`); logger.warn(`Duplicate SearchIcon found, ignoring the second one at ${id}:${node.loc.start.line}`);
break; // 2つ目のSearchIconは無視 break; // 2つ目のSearchIconは無視
} }
if (node.children.length !== 1) { if (node.children.length !== 1) {
logger.error(`SearchIcon must have exactly one child at ${node.loc.start.line}`); logger.error(`SearchIcon must have exactly one child at ${id}:${node.loc.start.line}`);
return; return;
} }
const iconNode = node.children[0]; const iconNode = node.children[0];
if (iconNode.type !== NodeTypes.ELEMENT) { if (iconNode.type !== NodeTypes.ELEMENT) {
logger.error(`SearchIcon must have a child element at ${node.loc.start.line}`); logger.error(`SearchIcon must have a child element at ${id}:${node.loc.start.line}`);
return; return;
} }
icon = getStringProp(findAttribute(iconNode.props, 'class')); icon = getStringProp(findAttribute(iconNode.props, 'class'), id);
return; return;
} }
@ -282,7 +282,7 @@ function extractSugarTags(nodes: TemplateChildNode[]): { label: string | null, k
return { label: label ?? null, keywords, icon: icon ?? null }; return { label: label ?? null, keywords, icon: icon ?? null };
} }
function getStringProp(attr: AttributeNode | DirectiveNode | null): string | null { function getStringProp(attr: AttributeNode | DirectiveNode | null, id: string): string | null {
switch (attr?.type) { switch (attr?.type) {
case null: case null:
case undefined: case undefined:
@ -294,27 +294,27 @@ function getStringProp(attr: AttributeNode | DirectiveNode | null): string | nul
if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION'); if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION');
const value = evalExpression(attr.exp.content ?? ''); const value = evalExpression(attr.exp.content ?? '');
if (typeof value !== 'string') { if (typeof value !== 'string') {
logger.error(`Expected string value, got ${typeof value} at line ${attr.loc.start.line}`); logger.error(`Expected string value, got ${typeof value} at ${id}:${attr.loc.start.line}`);
return null; return null;
} }
return value; return value;
} }
} }
function getStringArrayProp(attr: AttributeNode | DirectiveNode | null): string[] | null { function getStringArrayProp(attr: AttributeNode | DirectiveNode | null, id: string): string[] | null {
switch (attr?.type) { switch (attr?.type) {
case null: case null:
case undefined: case undefined:
return null; return null;
case NodeTypes.ATTRIBUTE: case NodeTypes.ATTRIBUTE:
logger.error(`Expected directive, got attribute at line ${attr.loc.start.line}`); logger.error(`Expected directive, got attribute at ${id}:${attr.loc.start.line}`);
return null; return null;
case NodeTypes.DIRECTIVE: case NodeTypes.DIRECTIVE:
if (attr.exp == null) return null; if (attr.exp == null) return null;
if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION'); if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION');
const value = evalExpression(attr.exp.content ?? ''); const value = evalExpression(attr.exp.content ?? '');
if (!Array.isArray(value) || !value.every(x => typeof x === 'string')) { if (!Array.isArray(value) || !value.every(x => typeof x === 'string')) {
logger.error(`Expected string array value, got ${typeof value} at line ${attr.loc.start.line}`); logger.error(`Expected string array value, got ${typeof value} at ${id}:${attr.loc.start.line}`);
return null; return null;
} }
return value; return value;
@ -354,11 +354,11 @@ function extractUsageInfoFromTemplateAst(
}; };
// バインドプロパティを取得 // バインドプロパティを取得
const path = getStringProp(findAttribute(node.props, 'path')) const path = getStringProp(findAttribute(node.props, 'path'), id)
const icon = getStringProp(findAttribute(node.props, 'icon')) const icon = getStringProp(findAttribute(node.props, 'icon'), id)
const label = getStringProp(findAttribute(node.props, 'label')) const label = getStringProp(findAttribute(node.props, 'label'), id)
const inlining = getStringArrayProp(findAttribute(node.props, 'inlining')) const inlining = getStringArrayProp(findAttribute(node.props, 'inlining'), id)
const keywords = getStringArrayProp(findAttribute(node.props, 'keywords')) const keywords = getStringArrayProp(findAttribute(node.props, 'keywords'), id)
if (path) markerInfo.path = path; if (path) markerInfo.path = path;
if (icon) markerInfo.icon = icon; if (icon) markerInfo.icon = icon;
@ -373,7 +373,7 @@ function extractUsageInfoFromTemplateAst(
// SearchLabelとSearchKeywordを抽出 (AST全体を探索) // SearchLabelとSearchKeywordを抽出 (AST全体を探索)
{ {
const extracted = extractSugarTags(node.children); const extracted = extractSugarTags(node.children, id);
if (extracted.label && markerInfo.label) logger.warn(`Duplicate label found for ${markerId} at ${id}:${node.loc.start.line}`); if (extracted.label && markerInfo.label) logger.warn(`Duplicate label found for ${markerId} at ${id}:${node.loc.start.line}`);
if (extracted.icon && markerInfo.icon) logger.warn(`Duplicate icon found for ${markerId} at ${id}:${node.loc.start.line}`); if (extracted.icon && markerInfo.icon) logger.warn(`Duplicate icon found for ${markerId} at ${id}:${node.loc.start.line}`);
markerInfo.label = extracted.label ?? markerInfo.label ?? ''; markerInfo.label = extracted.label ?? markerInfo.label ?? '';
@ -585,7 +585,7 @@ export class MarkerIdAssigner {
} }
// AST で :children 属性が検出された場合、それを更新 // AST で :children 属性が検出された場合、それを更新
const childrenValue = getStringArrayProp(childrenProp); const childrenValue = getStringArrayProp(childrenProp, id);
if (childrenValue == null) continue; if (childrenValue == null) continue;
const newValue: string[] = [...childrenValue]; const newValue: string[] = [...childrenValue];
@ -740,7 +740,7 @@ export function pluginCreateSearchIndexVirtualModule(options: Options, asigner:
this.addWatchFile(searchIndexFilePath); this.addWatchFile(searchIndexFilePath);
const code = await asigner.getOrLoad(searchIndexFilePath); const code = await asigner.getOrLoad(searchIndexFilePath);
return generateJavaScriptCode(collectFileMarkers(id, code)); return generateJavaScriptCode(collectFileMarkers(searchIndexFilePath, code));
} }
return null; return null;
}, },

View File

@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch> <MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch> <MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch> <MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
<MkSwitch v-model="hideNotesInSensitiveChannel">{{ i18n.ts.hideNotesInSensitiveChannel }}</MkSwitch> <MkSwitch v-model="excludeNotesInSensitiveChannel">{{ i18n.ts.excludeNotesInSensitiveChannel }}</MkSwitch>
</div> </div>
<div :class="$style.actions"> <div :class="$style.actions">
<div class="_buttons"> <div class="_buttons">
@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref } from 'vue'; import { watch, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { DeepPartial } from '@/utility/merge.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -63,7 +64,6 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { deepMerge } from '@/utility/merge.js'; import { deepMerge } from '@/utility/merge.js';
import type { DeepPartial } from '@/utility/merge.js';
type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & { type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & {
id?: string; id?: string;
@ -87,7 +87,7 @@ const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, {
caseSensitive: false, caseSensitive: false,
localOnly: false, localOnly: false,
withFile: false, withFile: false,
hideNotesInSensitiveChannel: false, excludeNotesInSensitiveChannel: false,
isActive: true, isActive: true,
hasUnreadNote: false, hasUnreadNote: false,
notify: false, notify: false,
@ -110,7 +110,7 @@ const localOnly = ref<boolean>(initialAntenna.localOnly);
const excludeBots = ref<boolean>(initialAntenna.excludeBots); const excludeBots = ref<boolean>(initialAntenna.excludeBots);
const withReplies = ref<boolean>(initialAntenna.withReplies); const withReplies = ref<boolean>(initialAntenna.withReplies);
const withFile = ref<boolean>(initialAntenna.withFile); const withFile = ref<boolean>(initialAntenna.withFile);
const hideNotesInSensitiveChannel = ref<boolean>(initialAntenna.hideNotesInSensitiveChannel); const excludeNotesInSensitiveChannel = ref<boolean>(initialAntenna.excludeNotesInSensitiveChannel);
const userLists = ref<Misskey.entities.UserList[] | null>(null); const userLists = ref<Misskey.entities.UserList[] | null>(null);
watch(() => src.value, async () => { watch(() => src.value, async () => {
@ -127,7 +127,7 @@ async function saveAntenna() {
excludeBots: excludeBots.value, excludeBots: excludeBots.value,
withReplies: withReplies.value, withReplies: withReplies.value,
withFile: withFile.value, withFile: withFile.value,
hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value, excludeNotesInSensitiveChannel: excludeNotesInSensitiveChannel.value,
caseSensitive: caseSensitive.value, caseSensitive: caseSensitive.value,
localOnly: localOnly.value, localOnly: localOnly.value,
users: users.value.trim().split('\n').map(x => x.trim()), users: users.value.trim().split('\n').map(x => x.trim()),

View File

@ -117,7 +117,7 @@ windowRouter.addListener('change', ctx => {
windowRouter.init(); windowRouter.init();
provide(DI.router, windowRouter); provide(DI.router, windowRouter);
provide('inAppSearchMarkerId', searchMarkerId); provide(DI.inAppSearchMarkerId, searchMarkerId);
provideMetadataReceiver((metadataGetter) => { provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter(); const info = metadataGetter();
pageMetadata.value = info; pageMetadata.value = info;
@ -125,7 +125,7 @@ provideMetadataReceiver((metadataGetter) => {
provideReactiveMetadata(pageMetadata); provideReactiveMetadata(pageMetadata);
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true); provide('shouldHeaderThin', true);
provide('forceSpacerMin', true); provide(DI.forceSpacerMin, true);
const contextmenu = computed(() => ([{ const contextmenu = computed(() => ([{
icon: 'ti ti-player-eject', icon: 'ti ti-player-eject',

View File

@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue'; import { inject } from 'vue';
import { deviceKind } from '@/utility/device-kind.js'; import { deviceKind } from '@/utility/device-kind.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
contentMax?: number | null; contentMax?: number | null;
@ -25,7 +26,7 @@ const props = withDefaults(defineProps<{
marginMax: 24, marginMax: 24,
}); });
const forceSpacerMin = inject('forceSpacerMin', false) || deviceKind === 'smartphone'; const forceSpacerMin = inject(DI.forceSpacerMin, false) || deviceKind === 'smartphone';
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]"> <div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]">
<slot></slot> <slot :isParentOfTarget="isParentOfTarget"></slot>
</div> </div>
</template> </template>
@ -21,7 +21,7 @@ import {
useTemplateRef, useTemplateRef,
inject, inject,
} from 'vue'; } from 'vue';
import type { Ref } from 'vue'; import { DI } from '@/di.js';
const props = defineProps<{ const props = defineProps<{
markerId?: string; markerId?: string;
@ -36,12 +36,13 @@ const rootEl = useTemplateRef('root');
const rootElMutationObserver = new MutationObserver(() => { const rootElMutationObserver = new MutationObserver(() => {
checkChildren(); checkChildren();
}); });
const injectedSearchMarkerId = inject<Ref<string | null> | null>('inAppSearchMarkerId', null); const injectedSearchMarkerId = inject(DI.inAppSearchMarkerId, null);
const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1)); const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1));
const highlighted = ref(props.markerId === searchMarkerId.value); const highlighted = ref(props.markerId === searchMarkerId.value);
const isParentOfTarget = computed(() => props.children?.includes(searchMarkerId.value));
function checkChildren() { function checkChildren() {
if (props.children?.includes(searchMarkerId.value)) { if (isParentOfTarget.value) {
const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`); const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
highlighted.value = el == null; highlighted.value = el == null;
} }
@ -105,8 +106,8 @@ onBeforeUnmount(dispose);
@keyframes blink { @keyframes blink {
0%, 100% { 0%, 100% {
background: color(from var(--MI_THEME-accent) srgb r g b / 0.05); background: color(from var(--MI_THEME-accent) srgb r g b / 0.1);
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.7); border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.75);
} }
50% { 50% {
background: transparent; background: transparent;

View File

@ -16,4 +16,6 @@ export const DI = {
currentStickyBottom: Symbol() as InjectionKey<Ref<number>>, currentStickyBottom: Symbol() as InjectionKey<Ref<number>>,
mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>, mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>,
inModal: Symbol() as InjectionKey<boolean>, inModal: Symbol() as InjectionKey<boolean>,
inAppSearchMarkerId: Symbol() as InjectionKey<Ref<string | null>>,
forceSpacerMin: Symbol() as InjectionKey<boolean>,
}; };

View File

@ -266,18 +266,20 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
throw new Error('no route found for: ' + fullPath); throw new Error('no route found for: ' + fullPath);
} }
if ('redirect' in res.route) { for (let current: PathResolvedResult | undefined = res; current; current = current.child) {
let redirectPath: string; if ('redirect' in current.route) {
if (typeof res.route.redirect === 'function') { let redirectPath: string;
redirectPath = res.route.redirect(res.props); if (typeof current.route.redirect === 'function') {
} else { redirectPath = current.route.redirect(current.props);
redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : ''); } else {
redirectPath = current.route.redirect + (current._parsedRoute.queryString ? '?' + current._parsedRoute.queryString : '') + (current._parsedRoute.hash ? '#' + current._parsedRoute.hash : '');
}
if (_DEV_) console.log('Redirecting to: ', redirectPath);
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
return this.navigate(redirectPath, emitChange, true);
} }
if (_DEV_) console.log('Redirecting to: ', redirectPath);
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
return this.navigate(redirectPath, emitChange, true);
} }
if (res.route.loginRequired && !this.isLoggedIn) { if (res.route.loginRequired && !this.isLoggedIn) {

View File

@ -341,7 +341,7 @@ defineExpose({
box-sizing: border-box; box-sizing: border-box;
border-right: solid 0.5px var(--MI_THEME-divider); border-right: solid 0.5px var(--MI_THEME-divider);
overflow: auto; overflow: auto;
height: 100dvh; height: 100cqh;
} }
> .main { > .main {

View File

@ -103,7 +103,6 @@ function removeItem(index: number) {
async function save() { async function save() {
prefer.commit('menu', items.value.map(x => x.type)); prefer.commit('menu', items.value.map(x => x.type));
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
} }
function reset() { function reset() {

View File

@ -4,64 +4,66 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div class="_gaps_m"> <SearchMarker path="/settings/notifications" :label="i18n.ts.notifications" :keywords="['notifications']" icon="ti ti-bell">
<MkFeatureBanner icon="/client-assets/bell_3d.png" color="#ffff00"> <div class="_gaps_m">
<SearchKeyword>{{ i18n.ts._settings.notificationsBanner }}</SearchKeyword> <MkFeatureBanner icon="/client-assets/bell_3d.png" color="#ffff00">
</MkFeatureBanner> <SearchKeyword>{{ i18n.ts._settings.notificationsBanner }}</SearchKeyword>
</MkFeatureBanner>
<FormSection first> <FormSection first>
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template> <template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
<div class="_gaps_s"> <div class="_gaps_s">
<MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type"> <MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type">
<template #label>{{ i18n.ts._notification._types[type] }}</template> <template #label>{{ i18n.ts._notification._types[type] }}</template>
<template #suffix> <template #suffix>
{{ {{
$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none : $i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
$i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following : $i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following :
$i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers : $i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers :
$i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow : $i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow :
$i.notificationRecieveConfig[type]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower : $i.notificationRecieveConfig[type]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower :
$i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList : $i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList :
i18n.ts.all i18n.ts.all
}} }}
</template> </template>
<XNotificationConfig <XNotificationConfig
:userLists="userLists" :userLists="userLists"
:value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }"
:configurableTypes="onlyOnOrOffNotificationTypes.includes(type) ? ['all', 'never'] : undefined" :configurableTypes="onlyOnOrOffNotificationTypes.includes(type) ? ['all', 'never'] : undefined"
@update="(res) => updateReceiveConfig(type, res)" @update="(res) => updateReceiveConfig(type, res)"
/> />
</MkFolder> </MkFolder>
</div> </div>
</FormSection> </FormSection>
<FormSection> <FormSection>
<div class="_gaps_m"> <div class="_gaps_m">
<FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> <FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
</div> </div>
</FormSection> </FormSection>
<FormSection> <FormSection>
<div class="_gaps_m"> <div class="_gaps_m">
<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink> <FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
<FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink> <FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink>
</div> </div>
</FormSection> </FormSection>
<FormSection> <FormSection>
<template #label>{{ i18n.ts.pushNotification }}</template> <template #label>{{ i18n.ts.pushNotification }}</template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkPushNotificationAllowButton ref="allowButton"/> <MkPushNotificationAllowButton ref="allowButton"/>
<MkSwitch :disabled="!pushRegistrationInServer" :modelValue="sendReadMessage" @update:modelValue="onChangeSendReadMessage"> <MkSwitch :disabled="!pushRegistrationInServer" :modelValue="sendReadMessage" @update:modelValue="onChangeSendReadMessage">
<template #label>{{ i18n.ts.sendPushNotificationReadMessage }}</template> <template #label>{{ i18n.ts.sendPushNotificationReadMessage }}</template>
<template #caption> <template #caption>
<I18n :src="i18n.ts.sendPushNotificationReadMessageCaption"> <I18n :src="i18n.ts.sendPushNotificationReadMessageCaption">
<template #emptyPushNotificationMessage>{{ i18n.ts._notification.emptyPushNotificationMessage }}</template> <template #emptyPushNotificationMessage>{{ i18n.ts._notification.emptyPushNotificationMessage }}</template>
</I18n> </I18n>
</template> </template>
</MkSwitch> </MkSwitch>
</div> </div>
</FormSection> </FormSection>
</div> </div>
</SearchMarker>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -33,16 +33,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #value><MkTime :time="$i.createdAt" mode="detail"/></template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
</MkKeyValue> </MkKeyValue>
<MkFolder> <SearchMarker :keywords="['role', 'policy']">
<template #icon><i class="ti ti-badges"></i></template> <MkFolder>
<template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template> <template #icon><i class="ti ti-badges"></i></template>
<template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template>
<div class="_gaps_s"> <div class="_gaps_s">
<div v-for="policy in Object.keys($i.policies)" :key="policy"> <div v-for="policy in Object.keys($i.policies)" :key="policy">
{{ policy }} ... {{ $i.policies[policy] }} {{ policy }} ... {{ $i.policies[policy] }}
</div>
</div> </div>
</div> </MkFolder>
</MkFolder> </SearchMarker>
</div> </div>
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>

View File

@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFeatureBanner> </MkFeatureBanner>
<div class="_gaps_s"> <div class="_gaps_s">
<SearchMarker :keywords="['general']"> <SearchMarker v-slot="slotProps" :keywords="['general']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-settings"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-settings"></i></SearchIcon></template>
@ -108,8 +108,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['timeline', 'note']"> <SearchMarker v-slot="slotProps" :keywords="['timeline', 'note']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts._settings.timelineAndNote }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts._settings.timelineAndNote }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-notes"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-notes"></i></SearchIcon></template>
@ -276,8 +276,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['post', 'form']"> <SearchMarker v-slot="slotProps" :keywords="['post', 'form']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.postForm }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.postForm }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-edit"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-edit"></i></SearchIcon></template>
@ -338,8 +338,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['notification']"> <SearchMarker v-slot="slotProps" :keywords="['notification']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-bell"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-bell"></i></SearchIcon></template>
@ -380,8 +380,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</SearchMarker> </SearchMarker>
<template v-if="$i.policies.chatAvailability !== 'unavailable'"> <template v-if="$i.policies.chatAvailability !== 'unavailable'">
<SearchMarker :keywords="['chat', 'messaging']"> <SearchMarker v-slot="slotProps" :keywords="['chat', 'messaging']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.chat }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.chat }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-messages"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-messages"></i></SearchIcon></template>
@ -420,8 +420,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</SearchMarker> </SearchMarker>
</template> </template>
<SearchMarker :keywords="['accessibility']"> <SearchMarker v-slot="slotProps" :keywords="['accessibility']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.accessibility }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.accessibility }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-accessible"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-accessible"></i></SearchIcon></template>
@ -530,8 +530,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['performance']"> <SearchMarker v-slot="slotProps" :keywords="['performance']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.performance }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.performance }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-battery-vertical-eco"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-battery-vertical-eco"></i></SearchIcon></template>
@ -540,7 +540,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPreferenceContainer k="useBlurEffect"> <MkPreferenceContainer k="useBlurEffect">
<MkSwitch v-model="useBlurEffect"> <MkSwitch v-model="useBlurEffect">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template> <template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
</MkSwitch> </MkSwitch>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
@ -549,7 +549,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPreferenceContainer k="useBlurEffectForModal"> <MkPreferenceContainer k="useBlurEffectForModal">
<MkSwitch v-model="useBlurEffectForModal"> <MkSwitch v-model="useBlurEffectForModal">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template> <template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
</MkSwitch> </MkSwitch>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
@ -558,7 +558,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPreferenceContainer k="useStickyIcons"> <MkPreferenceContainer k="useStickyIcons">
<MkSwitch v-model="useStickyIcons"> <MkSwitch v-model="useStickyIcons">
<template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template> <template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
</MkSwitch> </MkSwitch>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
@ -566,8 +566,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['datasaver']"> <SearchMarker v-slot="slotProps" :keywords="['datasaver']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-antenna-bars-3"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-antenna-bars-3"></i></SearchIcon></template>
@ -600,8 +600,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
<SearchMarker :keywords="['other']"> <SearchMarker v-slot="slotProps" :keywords="['other']">
<MkFolder> <MkFolder :defaultOpen="slotProps.isParentOfTarget">
<template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template>
<template #icon><SearchIcon><i class="ti ti-settings-cog"></i></SearchIcon></template> <template #icon><SearchIcon><i class="ti ti-settings-cog"></i></SearchIcon></template>

View File

@ -211,15 +211,12 @@ import FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkThemePreview from '@/components/MkThemePreview.vue'; import MkThemePreview from '@/components/MkThemePreview.vue';
import { getBuiltinThemesRef, getThemesRef } from '@/theme.js'; import { getBuiltinThemesRef, getThemesRef } from '@/theme.js';
import { selectFile } from '@/utility/select-file.js';
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js'; import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
import { store } from '@/store.js'; import { store } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { uniqueBy } from '@/utility/array.js'; import { uniqueBy } from '@/utility/array.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { miLocalStorage } from '@/local-storage.js';
import { reloadAsk } from '@/utility/reload-ask.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
const installedThemes = getThemesRef(); const installedThemes = getThemesRef();

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div ref="rootEl" class="_pageScrollable"> <div ref="rootEl" class="_pageScrollable">
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"/></template> <template #header><MkPageHeader v-model:tab="src" :displayMyAvatar="true" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"/></template>
<MkSpacer :contentMax="800"> <MkSpacer :contentMax="800">
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> <MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }} {{ i18n.ts._timelineDescription[src] }}

View File

@ -19,8 +19,10 @@ export async function signout() {
localStorage.clear(); localStorage.clear();
defaultMemoryStorage.clear(); defaultMemoryStorage.clear();
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise<void>((res, rej) => {
indexedDB.deleteDatabase(name); const delidb = indexedDB.deleteDatabase(name);
delidb.onsuccess = () => res();
delidb.onerror = e => rej(e);
})); }));
await Promise.all(idbPromises); await Promise.all(idbPromises);

View File

@ -4,6 +4,59 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuDrawerBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<component <component
:is="popup.component" :is="popup.component"
v-for="popup in popups" v-for="popup in popups"
@ -59,9 +112,14 @@ import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue')); const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
const XUpload = defineAsyncComponent(() => import('./upload.vue')); const XUpload = defineAsyncComponent(() => import('./upload.vue'));
const XWidgets = defineAsyncComponent(() => import('./widgets.vue'));
const drawerMenuShowing = defineModel<boolean>('drawerMenuShowing');
const widgetsShowing = defineModel<boolean>('widgetsShowing');
const dev = _DEV_; const dev = _DEV_;
@ -100,6 +158,50 @@ if ($i) {
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawerBg_enterFrom,
.transition_menuDrawerBg_leaveTo {
opacity: 0;
}
.transition_menuDrawer_enterActive,
.transition_menuDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawer_enterFrom,
.transition_menuDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.transition_widgetsDrawerBg_enterActive,
.transition_widgetsDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawerBg_enterFrom,
.transition_widgetsDrawerBg_leaveTo {
opacity: 0;
}
.transition_widgetsDrawer_enterActive,
.transition_widgetsDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawer_enterFrom,
.transition_widgetsDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.transition_notification_move, .transition_notification_move,
.transition_notification_enterActive, .transition_notification_enterActive,
.transition_notification_leaveActive { .transition_notification_leaveActive {
@ -114,6 +216,54 @@ if ($i) {
transform: translateX(-250px); transform: translateX(-250px);
} }
.menuDrawerBg {
z-index: 1001;
}
.menuDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
height: 100dvh;
width: 240px;
box-sizing: border-box;
contain: strict;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-navBg);
}
.widgetsDrawerBg {
z-index: 1001;
}
.widgetsDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 310px;
height: 100dvh;
padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-bg);
}
.widgetsCloseButton {
padding: 8px;
display: block;
margin: 0 auto;
}
@media (min-width: 370px) {
.widgetsCloseButton {
display: none;
}
}
.notifications { .notifications {
position: fixed; position: fixed;
z-index: 3900000; z-index: 3900000;

View File

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA :class="$style.item" :activeClass="$style.active" to="/" exact> <MkA :class="$style.item" :activeClass="$style.active" to="/" exact>
<i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span> <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
</MkA> </MkA>
<template v-for="item in menu"> <template v-for="item in prefer.r.menu.value">
<div v-if="item === '-'" :class="$style.divider"></div> <div v-if="item === '-'" :class="$style.divider"></div>
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, toRef } from 'vue'; import { computed, defineAsyncComponent } from 'vue';
import { openInstanceMenu } from './common.js'; import { openInstanceMenu } from './common.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js'; import { navbarItemDef } from '@/navbar.js';
@ -59,10 +59,9 @@ import { instance } from '@/instance.js';
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
const menu = toRef(prefer.s, 'menu');
const otherMenuItemIndicated = computed(() => { const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) { for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue; if (prefer.r.menu.value.includes(def)) continue;
if (navbarItemDef[def].indicated) return true; if (navbarItemDef[def].indicated) return true;
} }
return false; return false;

View File

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact> <MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
<i :class="$style.itemIcon" class="ti ti-home ti-fw" style="viewTransitionName: navbar-homeIcon;"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span> <i :class="$style.itemIcon" class="ti ti-home ti-fw" style="viewTransitionName: navbar-homeIcon;"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
</MkA> </MkA>
<template v-for="item in menu"> <template v-for="item in prefer.r.menu.value">
<div v-if="item === '-'" :class="$style.divider"></div> <div v-if="item === '-'" :class="$style.divider"></div>
<component <component
:is="navbarItemDef[item].to ? 'MkA' : 'button'" :is="navbarItemDef[item].to ? 'MkA' : 'button'"
@ -120,10 +120,9 @@ const iconOnly = computed(() => {
return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'); return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon');
}); });
const menu = computed(() => prefer.s.menu);
const otherMenuItemIndicated = computed(() => { const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) { for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue; if (prefer.r.menu.value.includes(def)) continue;
if (navbarItemDef[def].indicated) return true; if (navbarItemDef[def].indicated) return true;
} }
return false; return false;

View File

@ -71,60 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/> <XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div> </div>
<Transition <XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menu">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<XCommon/>
</div> </div>
</template> </template>
@ -134,7 +81,6 @@ import { v4 as uuid } from 'uuid';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import XSidebar from '@/ui/_common_/navbar.vue'; import XSidebar from '@/ui/_common_/navbar.vue';
import XNavbarH from '@/ui/_common_/navbar-h.vue'; import XNavbarH from '@/ui/_common_/navbar-h.vue';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue'; import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
@ -156,7 +102,6 @@ import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumn
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
const XWidgets = defineAsyncComponent(() => import('./_common_/widgets.vue'));
const columnComponents = { const columnComponents = {
main: XMainColumn, main: XMainColumn,
@ -269,50 +214,6 @@ if (prefer.s['deck.wallpaper'] != null) {
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawerBg_enterFrom,
.transition_menuDrawerBg_leaveTo {
opacity: 0;
}
.transition_menuDrawer_enterActive,
.transition_menuDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawer_enterFrom,
.transition_menuDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.transition_widgetsDrawerBg_enterActive,
.transition_widgetsDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawerBg_enterFrom,
.transition_widgetsDrawerBg_leaveTo {
opacity: 0;
}
.transition_widgetsDrawer_enterActive,
.transition_widgetsDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawer_enterFrom,
.transition_widgetsDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.root { .root {
$nav-hide-threshold: 650px; // TODO: $nav-hide-threshold: 650px; // TODO:
@ -344,6 +245,9 @@ if (prefer.s['deck.wallpaper'] != null) {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
//
min-height: 0;
} }
.columns { .columns {
@ -441,52 +345,4 @@ if (prefer.s['deck.wallpaper'] != null) {
.bottomMenuRight { .bottomMenuRight {
margin-left: auto; margin-left: auto;
} }
.menuBg {
z-index: 1001;
}
.menu {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
height: 100dvh;
width: 240px;
box-sizing: border-box;
contain: strict;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-navBg);
}
.widgetsDrawerBg {
z-index: 1001;
}
.widgetsDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 310px;
height: 100dvh;
padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-bg);
}
.widgetsCloseButton {
padding: 8px;
display: block;
margin: 0 auto;
}
@media (min-width: 370px) {
.widgetsCloseButton {
display: none;
}
}
</style> </style>

View File

@ -49,10 +49,11 @@ import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownCo
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
provide('shouldHeaderThin', true); provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
provide('forceSpacerMin', true); provide(DI.forceSpacerMin, true);
const withWallpaper = prefer.s['deck.wallpaper'] != null; const withWallpaper = prefer.s['deck.wallpaper'] != null;

View File

@ -22,60 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XWidgets/> <XWidgets/>
</div> </div>
<Transition <XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuDrawerBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<XCommon/>
</div> </div>
</template> </template>
@ -85,7 +32,6 @@ import { instanceName } from '@@/js/config.js';
import { isLink } from '@@/js/is-link.js'; import { isLink } from '@@/js/is-link.js';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import type { PageMetadata } from '@/page.js'; import type { PageMetadata } from '@/page.js';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue'; import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue'; import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -178,50 +124,6 @@ const onContextmenu = (ev) => {
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$widgets-hide-threshold: 1090px; $widgets-hide-threshold: 1090px;
.transition_menuDrawerBg_enterActive,
.transition_menuDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawerBg_enterFrom,
.transition_menuDrawerBg_leaveTo {
opacity: 0;
}
.transition_menuDrawer_enterActive,
.transition_menuDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_menuDrawer_enterFrom,
.transition_menuDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.transition_widgetsDrawerBg_enterActive,
.transition_widgetsDrawerBg_leaveActive {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawerBg_enterFrom,
.transition_widgetsDrawerBg_leaveTo {
opacity: 0;
}
.transition_widgetsDrawer_enterActive,
.transition_widgetsDrawer_leaveActive {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.transition_widgetsDrawer_enterFrom,
.transition_widgetsDrawer_leaveTo {
opacity: 0;
transform: translateX(-240px);
}
.root { .root {
height: 100dvh; height: 100dvh;
overflow: clip; overflow: clip;
@ -248,24 +150,6 @@ $widgets-hide-threshold: 1090px;
min-height: 0; min-height: 0;
} }
.menuDrawerBg {
z-index: 1001;
}
.menuDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
height: 100dvh;
width: 240px;
box-sizing: border-box;
contain: strict;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-navBg);
}
.statusbars { .statusbars {
position: sticky; position: sticky;
top: 0; top: 0;
@ -285,34 +169,4 @@ $widgets-hide-threshold: 1090px;
display: none; display: none;
} }
} }
.widgetsDrawerBg {
z-index: 1001;
}
.widgetsDrawer {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 310px;
height: 100dvh;
padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--MI_THEME-bg);
}
.widgetsCloseButton {
padding: 8px;
display: block;
margin: 0 auto;
}
@media (min-width: 370px) {
.widgetsCloseButton {
display: none;
}
}
</style> </style>

View File

@ -6,16 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.root"> <div :class="$style.root">
<div :class="$style.contents"> <div :class="$style.contents">
<div style="flex: 1; min-height: 0;">
<RouterView/>
</div>
<!-- <!--
デッキUIが設定されている場合はデッキUIに戻れるようにする (ただし?zenが明示された場合は表示しない) デッキUIが設定されている場合はデッキUIに戻れるようにする (ただし?zenが明示された場合は表示しない)
See https://github.com/misskey-dev/misskey/issues/10905 See https://github.com/misskey-dev/misskey/issues/10905
--> -->
<div v-if="showBottom" :class="$style.bottom"> <button v-if="showDeckNav" class="_buttonPrimary" :class="$style.deckNav" @click="goToDeck">{{ i18n.ts.goToDeck }}</button>
<button v-tooltip="i18n.ts.goToMisskey" :class="['_button', '_shadow', $style.button]" @click="goToMisskey"><i class="ti ti-home"></i></button>
<div style="flex: 1; min-height: 0;">
<RouterView/>
</div> </div>
</div> </div>
@ -37,7 +35,7 @@ const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const pageMetadata = ref<null | PageMetadata>(null); const pageMetadata = ref<null | PageMetadata>(null);
const showBottom = !(new URLSearchParams(window.location.search)).has('zen') && ui === 'deck'; const showDeckNav = !(new URLSearchParams(window.location.search)).has('zen') && ui === 'deck';
provide(DI.router, mainRouter); provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => { provideMetadataReceiver((metadataGetter) => {
@ -53,7 +51,7 @@ provideMetadataReceiver((metadataGetter) => {
}); });
provideReactiveMetadata(pageMetadata); provideReactiveMetadata(pageMetadata);
function goToMisskey() { function goToDeck() {
window.location.href = '/'; window.location.href = '/';
} }
</script> </script>
@ -68,10 +66,8 @@ function goToMisskey() {
height: 100dvh; height: 100dvh;
} }
.bottom { .deckNav {
height: calc(60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px)); padding: 4px;
width: 100%;
margin-top: auto;
} }
.button { .button {

View File

@ -65,10 +65,11 @@ const hyphens = [
]; ];
const hyphensCodePoints = hyphens.map(code => `\\u{${code.toString(16).padStart(4, '0')}}`); const hyphensCodePoints = hyphens.map(code => `\\u{${code.toString(16).padStart(4, '0')}}`);
const hyphensRegex = new RegExp(`[${hyphensCodePoints.join('')}]`, 'ug');
/** ハイフンを統一(ローマ字半角入力時に`ー`と`-`が判定できない問題の調整) */ /** ハイフンを統一(ローマ字半角入力時に`ー`と`-`が判定できない問題の調整) */
export function normalizeHyphens(str: string) { export function normalizeHyphens(str: string) {
return str.replace(new RegExp(`[${hyphensCodePoints.join('')}]`, 'ug'), '\u002d'); return str.replace(hyphensRegex, '\u002d');
} }
/** /**

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.4.0-rc.3", "version": "2025.4.0",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

View File

@ -4899,7 +4899,7 @@ export type components = {
/** @default false */ /** @default false */
notify: boolean; notify: boolean;
/** @default false */ /** @default false */
hideNotesInSensitiveChannel: boolean; excludeNotesInSensitiveChannel: boolean;
}; };
Clip: { Clip: {
/** /**
@ -11340,7 +11340,7 @@ export type operations = {
excludeBots?: boolean; excludeBots?: boolean;
withReplies: boolean; withReplies: boolean;
withFile: boolean; withFile: boolean;
hideNotesInSensitiveChannel?: boolean; excludeNotesInSensitiveChannel?: boolean;
}; };
}; };
}; };
@ -11622,7 +11622,7 @@ export type operations = {
excludeBots?: boolean; excludeBots?: boolean;
withReplies?: boolean; withReplies?: boolean;
withFile?: boolean; withFile?: boolean;
hideNotesInSensitiveChannel?: boolean; excludeNotesInSensitiveChannel?: boolean;
}; };
}; };
}; };