Merge pull request #17232 from misskey-dev/develop

Release: 2026.3.2
This commit is contained in:
misskey-release-bot[bot] 2026-03-31 12:14:43 +00:00 committed by GitHub
commit 41048638a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 2839 additions and 2583 deletions

View file

@ -19,10 +19,10 @@ jobs:
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Setup Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -14,7 +14,7 @@ jobs:
- name: Checkout head
uses: actions/checkout@v6.0.2
- name: Setup Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'

View file

@ -29,7 +29,7 @@ jobs:
- name: setup node
id: setup-node
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: pnpm

View file

@ -30,9 +30,9 @@ jobs:
ref: ${{ matrix.ref }}
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -45,9 +45,9 @@ jobs:
ref: ${{ matrix.ref }}
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -41,8 +41,8 @@ jobs:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6.2.0
uses: pnpm/action-setup@v4.4.0
- uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -74,8 +74,8 @@ jobs:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6.2.0
uses: pnpm/action-setup@v4.4.0
- uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -105,8 +105,8 @@ jobs:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6.2.0
uses: pnpm/action-setup@v4.4.0
- uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -21,8 +21,8 @@ jobs:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6.2.0
uses: pnpm/action-setup@v4.4.0
- uses: actions/setup-node@v6.3.0
with:
node-version-file: ".node-version"
cache: "pnpm"

View file

@ -20,9 +20,9 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -37,9 +37,9 @@ jobs:
if: github.event_name == 'pull_request_target'
run: git checkout "$(git rev-list --parents -n1 HEAD | cut -d" " -f3)"
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -49,7 +49,7 @@ jobs:
ports:
- 56312:6379
meilisearch:
image: getmeili/meilisearch:v1.36.0
image: getmeili/meilisearch:v1.38.2
ports:
- 57712:7700
env:
@ -61,7 +61,7 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
@ -93,7 +93,7 @@ jobs:
fi
done
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
@ -140,9 +140,9 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
@ -184,12 +184,12 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'

View file

@ -36,7 +36,7 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
@ -68,7 +68,7 @@ jobs:
fi
done
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'

View file

@ -32,9 +32,9 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -86,9 +86,9 @@ jobs:
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -25,10 +25,10 @@ jobs:
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Setup Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -20,9 +20,9 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -21,9 +21,9 @@ jobs:
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4.4.0
- name: Use Node.js
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6.3.0
with:
node-version-file: '.node-version'
cache: 'pnpm'

View file

@ -1,3 +1,21 @@
## 2026.3.2
### General
- 依存関係の更新
### Client
- Enhance: アプリ内ウィンドウの初期サイズを画面サイズに応じて自動で調整するように
- Fix: 絵文字パレットが空の状態でMisskeyについてのページが閲覧できない問題を修正
- Fix: ウィンドウのタイトルをクリックしても最前面に出ないことがある問題を修正
### Server
- Fix: 自分の行ったフォロワー限定投稿または指名投稿に自分自身でリアクションなどを行った場合のイベントが流れない問題を修正
- Fix: 署名付きGETリクエストにおいてAcceptヘッダを署名の対象から除外Acceptヘッダを正規化するCDNやリバースプロキシを使用している際に挙動がおかしくなる問題を修正
- Fix: WebSocket接続におけるートの非表示ロジックを修正
- Fix: チャンネルミュートを有効にしている際に、一部のタイムラインやノート一覧が空になる問題を修正
- Fix: 初期読込時に必要なフロントエンドのアセットがすべて読み込まれていない問題を修正
## 2026.3.1
### General

View file

@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.20
# syntax = docker/dockerfile:1.21
ARG NODE_VERSION=22.21.1-bookworm
ARG NODE_VERSION=22.22.0-bookworm
# build assets & compile TypeScript

View file

@ -264,7 +264,7 @@ noJobs: "No hi ha feines"
federating: "Federant"
blocked: "Bloquejat"
suspended: "Anul·lar subscripció "
all: "tot"
all: "Tot"
subscribing: "Subscrit a"
publishing: "S'està publicant"
notResponding: "Sense resposta"
@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "Llindar"
centerX: "Centre de X"
centerY: "Centre de Y"
zoomLinesSmoothing: "Suavitzat"
zoomLinesSmoothingDescription: "Els paràmetres de suavitzat i amplada de línia en augmentar no es poden fer servir junts."
zoomLinesThreshold: "Amplada de línia a l'augmentar "
density: "Densitat"
zoomLinesOutlineThickness: "Amplada de les vores exteriors"
zoomLinesMaskSize: "Diàmetre del centre"
zoomLinesBlack: "Obscurir"
circle: "Cercle"
drafts: "Esborrany "
_drafts:

View file

@ -130,6 +130,7 @@ reactions: "Reakce"
reactionSettingDescription2: "Přetažením změníte pořadí, kliknutím smažete, zmáčkněte \"+\" k přidání"
rememberNoteVisibility: "Zapamatovat nastavení zobrazení poznámky"
attachCancel: "Odstranit přílohu"
deleteFile: "Smazat soubor"
markAsSensitive: "Označit jako NSFW"
unmarkAsSensitive: "Odznačit jako NSFW"
enterFileName: "Zadejte název souboru"
@ -205,6 +206,7 @@ blockThisInstance: "Blokovat tuto instanci"
silenceThisInstance: "Utišit tuto instanci"
operations: "Operace"
software: "Software"
softwareName: "Software"
version: "Verze"
metadata: "Metadata"
withNFiles: "{n} soubor(ů)"
@ -231,6 +233,7 @@ noteDeleteConfirm: "Jste si jistí že chcete smazat tuhle poznámku?"
pinLimitExceeded: "Nemůžete připnout další poznámky."
done: "Hotovo"
processing: "Zpracovávám"
preprocessing: "Připravuji..."
preview: "Náhled"
default: "Výchozí"
defaultValueIs: "Základní hodnota: {value}"
@ -265,6 +268,7 @@ removed: "Smazáno"
removeAreYouSure: "Jste si jistí že chcete smazat \"{x}\"?"
deleteAreYouSure: "Jste si jistí že chcete smazat \"{x}\"?"
resetAreYouSure: "Opravdu resetovat?"
areYouSure: "Jste si jistí?"
saved: "Uloženo"
upload: "Nahrát soubory"
keepOriginalUploading: "Ponechat originální obrázek"
@ -275,9 +279,12 @@ uploadFromUrl: "Nahrát z URL adresy"
uploadFromUrlDescription: "URL adresa souboru, který chcete nahrát"
uploadFromUrlRequested: "Upload zažádán"
uploadFromUrlMayTakeTime: "Může trvat nějakou dobu, dokud nebude dokončeno nahrávání."
uploadNFiles: "Uploadovat {n} souborů"
explore: "Objevovat"
messageRead: "Přečtené"
readAllChatMessages: "Označit všechny zprávy za přečtené"
noMoreHistory: "To je vše"
startChat: "Začít chat"
nUsersRead: "přečteno {n} uživateli"
agreeTo: "Souhlasím s {0}"
agree: "Souhlasím"
@ -308,12 +315,15 @@ selectFile: "Vybrat soubor"
selectFiles: "Vybrat soubory"
selectFolder: "Vyberte složku"
selectFolders: "Vyberte složky"
fileNotSelected: "Nebyl vybrán žádný soubor"
renameFile: "Přejmenovat soubor"
folderName: "Název složky"
createFolder: "Vytvořit složku"
renameFolder: "Přejmenovat složku"
deleteFolder: "Odstranit složku"
folder: "Složka "
addFile: "Přidat soubor"
showFile: "Procházet soubory"
emptyDrive: "Váš disk je prázdný"
emptyFolder: "Tato složka je prázdná"
unableToDelete: "Nelze smazat"
@ -424,6 +434,7 @@ totp: "Ověřovací aplikace"
totpDescription: "Použít ověřovací aplikaci pro použití jednorázových hesel"
moderator: "Moderátor"
moderation: "Moderování"
moderationNote: "Poznámka moderátora"
nUsersMentioned: "{n} uživatelů zmínilo"
securityKeyAndPasskey: "Bezpečnostní klíče a tokeny"
securityKey: "Bezpečnostní klíč"
@ -479,7 +490,9 @@ uiLanguage: "Jazyk uživatelského rozhraní"
aboutX: "O {x}"
emojiStyle: "Styl emoji"
native: "Výchozí"
menuStyle: "Styl nabídky"
style: "Vzhled"
drawer: "Boční menu"
popup: "Vyskakovací okno"
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
noHistory: "Žádná historie"
@ -535,6 +548,7 @@ deleteAll: "Smazat vše"
showFixedPostForm: "Zobrazit formulář pro nové příspěvky nad časovou osou"
showFixedPostFormInChannel: "Zobrazit vkládací formulář na vrcholu časové osy (Kanály)"
newNoteRecived: "Jsou k dispozici nové poznámky"
newNote: "Nová poznámka"
sounds: "Zvuky"
sound: "Zvuky"
listen: "Poslouchat"
@ -614,6 +628,7 @@ medium: "Střední"
small: "Malé"
generateAccessToken: "Vygenerovat přístupový token"
permission: "Oprávnění"
adminPermission: "Administrátorská práva"
enableAll: "Povolit vše"
disableAll: "Vypnout vše"
tokenRequested: "Povolit přístup k účtu"
@ -889,6 +904,9 @@ oneHour: "1 hodina"
oneDay: "1 den"
oneWeek: "1 týden"
oneMonth: "1 měsíc"
threeMonths: "3 měsíce"
oneYear: "1 rok"
threeDays: "3 dny"
reflectMayTakeTime: "Může trvat nějakou dobu, než se projeví změny."
failedToFetchAccountInformation: "Nepodařily se načíst informace o účtě"
rateLimitExceeded: "Překročení rychlostního limitu"
@ -1026,6 +1044,8 @@ showClipButtonInNoteFooter: "Přidat \"Připnout\" do akčního menu poznámky"
noteIdOrUrl: "ID nebo URL poznámky"
video: "Video"
videos: "Videa"
audio: "Zvuk"
audioFiles: "Zvuk"
dataSaver: "Spořič dat"
accountMigration: "Migrace účtu"
accountMoved: "Tenhle uživatel se přesunul na nový účet:"
@ -1053,6 +1073,8 @@ preservedUsernames: "Rezervované uživatelské jména"
preservedUsernamesDescription: "Seznam uživatelských jmén na rezervaci oddělené mezerama. Tyhle jména se potom nebudou moc použít při normálním procesu vytvoření účtu ale můžou být použiti manuálně administratorém. Existujících účtů se to nedotkne."
createNoteFromTheFile: "Vytvořit poznámku z tohodle souboru"
archive: "Archiv"
archived: "Archivované"
unarchive: "Obnovit"
channelArchiveConfirmTitle: "Opravdu chcete archivovat {name}?"
channelArchiveConfirmDescription: "Archivovaný kanál se objeví v seznamu kanálů nebo ve výsledcích hledání. Nové poznámky se nedají vložit do seznamu."
thisChannelArchived: "Tenhle kanál je archivovaný"
@ -1099,6 +1121,7 @@ doYouAgree: "Souhlasíte?"
beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace."
iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním."
icon: "Avatar"
forYou: "Pro vás"
replies: "Odpovědět"
renotes: "Přeposlat"
sourceCode: "Zdrojový kód"

View file

@ -3401,11 +3401,7 @@ _imageEffector:
threshold: "Schwellenwert"
centerX: "Zentrum X"
centerY: "Zentrum Y"
zoomLinesSmoothing: "Glättung"
zoomLinesSmoothingDescription: "Die Einstellungen für die Glättung und für die Breite der Konzentrationslinien können nicht gleichzeitig verwendet werden."
zoomLinesThreshold: "Breite der Konzentrationslinien"
zoomLinesMaskSize: "Mitteldurchmesser"
zoomLinesBlack: "Schwarz machen"
circle: "Kreisförmig"
drafts: "Entwurf"
_drafts:

View file

@ -3401,11 +3401,7 @@ _imageEffector:
threshold: "Threshold"
centerX: "Center X"
centerY: "Center Y"
zoomLinesSmoothing: "Smoothing"
zoomLinesSmoothingDescription: "Smoothing and zoom line width cannot be used together."
zoomLinesThreshold: "Zoom line width"
zoomLinesMaskSize: "Center diameter"
zoomLinesBlack: "Make black"
circle: "Circular"
drafts: "Drafts"
_drafts:

View file

@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "Umbral"
centerX: "Centrar X"
centerY: "Centrar Y"
zoomLinesSmoothing: "Suavizado"
zoomLinesSmoothingDescription: "El suavizado y el ancho de línea de zoom no se pueden utilizar juntos."
zoomLinesThreshold: "Ancho de línea del zoom"
density: "Densidad"
zoomLinesOutlineThickness: "Grosor del borde"
zoomLinesMaskSize: "Diámetro del centro"
zoomLinesBlack: "Cambiar color de las líneas de impacto a negro."
circle: "Círculo"
drafts: "Borrador"
_drafts:

View file

@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "Soglia"
centerX: "Centro orizzontale"
centerY: "Centro verticale"
zoomLinesSmoothing: "Levigatura"
zoomLinesSmoothingDescription: "Non si possono usare insieme la levigatura e la larghezza della linea centrale."
zoomLinesThreshold: "Limite delle linee zoom"
density: "Densità"
zoomLinesOutlineThickness: "Spessore del bordo"
zoomLinesMaskSize: "Ampiezza del diametro"
zoomLinesBlack: "Bande nere"
circle: "Circolare"
drafts: "Bozze"
_drafts:

View file

@ -3512,11 +3512,9 @@ _imageEffector:
threshold: "しきい値"
centerX: "中心X"
centerY: "中心Y"
zoomLinesSmoothing: "スムージング"
zoomLinesSmoothingDescription: "スムージングと集中線の幅の設定は併用できません。"
zoomLinesThreshold: "集中線の幅"
density: "密度"
zoomLinesOutlineThickness: "線の影の太さ"
zoomLinesMaskSize: "中心径"
zoomLinesBlack: "黒色にする"
circle: "円形"
drafts: "下書き"

View file

@ -1466,17 +1466,17 @@ _chat:
newMessage: "새로운 메시지"
individualChat: "개인 대화"
individualChat_description: "특정 유저와 일대일 채팅을 할 수 있습니다."
roomChat: " 채팅"
roomChat: "그룹 채팅"
roomChat_description: "여러 명이 함께 채팅할 수 있습니다.\n또한, 개인 채팅을 허용하지 않은 유저와도 상대방이 수락하면 채팅을 할 수 있습니다."
createRoom: "룸을 생성"
createRoom: "방 만들기"
inviteUserToChat: "유저를 초대하여 채팅을 시작하세요"
yourRooms: "생성한 룸"
joiningRooms: "참가 중인 "
yourRooms: "만들어진 방"
joiningRooms: "참가 중인 "
invitations: "초대"
noInvitations: "초대장이 없습니다"
history: "이력"
noHistory: "기록이 없습니다"
noRooms: "이 없습니다"
noRooms: "이 없습니다"
inviteUser: "유저를 초대"
sentInvitations: "초대를 보내기"
join: "참여"
@ -1487,14 +1487,14 @@ _chat:
home: "홈"
send: "전송"
newline: "줄바꿈"
muteThisRoom: "이 룸을 뮤트"
deleteRoom: "룸을 삭제"
muteThisRoom: "이 방을 뮤트하기"
deleteRoom: "방을 삭제하기"
chatNotAvailableForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅이 활성화되어 있지 않습니다."
chatIsReadOnlyForThisAccountOrServer: "이 서버 또는 이 계정에서 채팅은 읽기 전용입니다. 새로 쓰거나 채팅 룸을 만들거나 참가할 수 없습니다."
chatNotAvailableInOtherAccount: "상대방 계정에서 채팅 기능을 사용할 수 없는 상태입니다."
cannotChatWithTheUser: "이 유저와 채팅을 시작할 수 없습니다"
cannotChatWithTheUser_description: "채팅을 사용할 수 없는 상태이거나 상대방이 채팅을 열지 않은 상태입니다."
youAreNotAMemberOfThisRoomButInvited: "당신은 이 룸의 참가자가 아닙니다만 초대 신청을 받으셨습니다. 참가하려면 초대를 수락해주십시오."
youAreNotAMemberOfThisRoomButInvited: "이 방의 참가자가 아니지만 초대를 받았습니다. 참가하려면 초대를 수락하세요."
doYouAcceptInvitation: "초대를 수락하시겠습니까?"
chatWithThisUser: "채팅하기"
thisUserAllowsChatOnlyFromFollowers: "이 유저는 팔로워만 채팅을 할 수 있습니다."
@ -2544,7 +2544,7 @@ _widgets:
_userList:
chooseList: "리스트 선택"
clicker: "클리커"
birthdayFollowings: "오늘이 생일인 유저"
birthdayFollowings: "곧 생일인 사용자"
chat: "채팅하기"
_widgetOptions:
showHeader: "해더를 표시"
@ -2792,7 +2792,7 @@ _notification:
newNote: "새 게시물"
unreadAntennaNote: "안테나 {name}"
roleAssigned: "역할이 부여 되었습니다."
chatRoomInvitationReceived: "채팅 룸에 초대받았습니다"
chatRoomInvitationReceived: "채팅방에 초대되었습니다"
emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
achievementEarned: "도전 과제를 달성했습니다"
testNotification: "알림 테스트"
@ -2823,7 +2823,7 @@ _notification:
receiveFollowRequest: "팔로우 요청을 받았을 때"
followRequestAccepted: "팔로우 요청이 승인되었을 때"
roleAssigned: "역할이 부여됨"
chatRoomInvitationReceived: "채팅 룸에 초대받음"
chatRoomInvitationReceived: "채팅방에 초대됨"
achievementEarned: "도전 과제 획득"
exportCompleted: "추출을 성공함"
login: "로그인"
@ -2977,7 +2977,7 @@ _moderationLogTypes:
deletePage: "페이지를 삭제"
deleteFlash: "Play를 삭제"
deleteGalleryPost: "갤러리 게시물을 삭제"
deleteChatRoom: "채팅 룸 삭제"
deleteChatRoom: "채팅방 삭제하기"
updateProxyAccountDescription: "프록시 계정의 설명 업데이트"
_fileViewer:
title: "파일 상세"
@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "한계 값"
centerX: "X축 중심"
centerY: "Y축 중심"
zoomLinesSmoothing: "다듬기"
zoomLinesSmoothingDescription: "다듬기와 집중선 폭 설정은 같이 쓸 수 없습니다."
zoomLinesThreshold: "집중선 폭"
density: "밀도"
zoomLinesOutlineThickness: "선 그림자의 굵기"
zoomLinesMaskSize: "중앙 값"
zoomLinesBlack: "검은색으로 하기"
circle: "원형"
drafts: "초안"
_drafts:

View file

@ -3289,11 +3289,7 @@ _imageEffector:
threshold: "Limiar"
centerX: "Centralizar X"
centerY: "Centralizar Y"
zoomLinesSmoothing: "Suavização"
zoomLinesSmoothingDescription: "Suavização e largura das linhas de zoom não podem ser utilizados simultaneamente."
zoomLinesThreshold: "Largura das linhas de zoom"
zoomLinesMaskSize: "Diâmetro do centro"
zoomLinesBlack: "Linhas pretas"
circle: "Circular"
drafts: "Rascunhos"
_drafts:

View file

@ -3401,11 +3401,7 @@ _imageEffector:
threshold: "เทรชโฮลด์"
centerX: "กลาง X"
centerY: "กลาง Y"
zoomLinesSmoothing: "ทำให้สมูธ"
zoomLinesSmoothingDescription: "ตั้งให้สมูธไม่สามารถใช้ร่วมกับตั้งความกว้างเส้นรวมศูนย์ได้"
zoomLinesThreshold: "ความกว้างเส้นรวมศูนย์"
zoomLinesMaskSize: "ขนาดพื้นที่ตรงกลาง"
zoomLinesBlack: "ทำให้ดำ"
circle: "ทรงกลม"
drafts: "ร่าง"
_drafts:

View file

@ -83,6 +83,8 @@ files: "Dosyalar"
download: "İndir"
driveFileDeleteConfirm: "“{name}” dosyasını silmek istediğinden emin misin? Bu dosyaya ekli tüm notlar da silinecek."
unfollowConfirm: "{name} kullanıcısını cidden takipden çıkmak istiyor musun?"
cancelFollowRequestConfirm: "{name} adlı kişiye gönderdiğiniz takip isteğini iptal etmek ister misiniz?"
rejectFollowRequestConfirm: "{name} adlı kullanıcının takip isteğini reddetmek istiyor musunuz?"
exportRequested: "Dışa aktarma işlemi talep ettin. Bu işlem biraz zaman alabilir. İşlem tamamlandığında Drive'ına eklenecek."
importRequested: "İçe aktarma talebinde bulundun. Bu işlem biraz zaman alabilir."
lists: "Listeler"
@ -253,6 +255,7 @@ noteDeleteConfirm: "Bu notu silmek istediğinden emin misin?"
pinLimitExceeded: "Artık daha fazla not sabitleyemezsin"
done: "Tamam"
processing: "İşleniyor..."
preprocessing: "Hazırlık aşamasında"
preview: "Önizleme"
default: "Varsayılan"
defaultValueIs: "Varsayılan: {value}"
@ -301,6 +304,7 @@ uploadFromUrlMayTakeTime: "Yükleme işleminin tamamlanması biraz zaman alabili
uploadNFiles: "{n} dosya yükle"
explore: "Keşfet"
messageRead: "Oku"
readAllChatMessages: "Tüm mesajları okundu olarak işaretle"
noMoreHistory: "Daha fazla geçmiş bilgisi yok."
startChat: "Sohbete başla"
nUsersRead: "{n} tarafından okundu"
@ -333,6 +337,7 @@ fileName: "Dosya adı"
selectFile: "Dosya seçin"
selectFiles: "Dosyaları seçin"
selectFolder: "Klasör seçin"
unselectFolder: "Klasör seçimini kaldır"
selectFolders: "Klasörleri seçin"
fileNotSelected: "Hiç dosya seçilmedi"
renameFile: "Dosyayı yeniden adlandır"
@ -345,6 +350,7 @@ addFile: "Bir dosya ekle"
showFile: "Dosyaları göster"
emptyDrive: "Drive boş"
emptyFolder: "Bu klasör boş"
dropHereToUpload: "Yüklemek için dosyalarınızı buraya sürükleyin."
unableToDelete: "Silinemiyor"
inputNewFileName: "Yeni bir dosya adı girin"
inputNewDescription: "Yeni alternatif metin girin"
@ -537,6 +543,7 @@ regenerate: "Yeniden oluştur"
fontSize: "Yazı tipi boyutu"
mediaListWithOneImageAppearance: "Tek bir resim içeren medya listelerinin yüksekliği"
limitTo: "{x} ile sınırlandır"
showMediaListByGridInWideArea: "Ekran genişliği geniş olduğunda, medya listesi yatay olarak görüntülenecektir."
noFollowRequests: "Bekleyen takip istekleri yok."
openImageInNewTab: "Görüntüleri yeni sekmede aç"
dashboard: "Gösterge paneli"
@ -772,6 +779,7 @@ lockedAccountInfo: "Notunuzun görünürlüğünü “Yalnızca takipçiler” o
alwaysMarkSensitive: "Varsayılan olarak hassas olarak işaretle"
loadRawImages: "Küçük resimleri göstermek yerine orijinal resimleri yükle"
disableShowingAnimatedImages: "Animasyonlu görüntüleri oynatmayın"
disableShowingAnimatedImages_caption: "Bu ayara rağmen animasyonlu görüntüler oynatılmıyorsa, bunun nedeni tarayıcınızın veya işletim sisteminizin erişilebilirlik ayarları veya güç tasarrufu ayarlarından kaynaklanan parazit olabilir."
highlightSensitiveMedia: "Hassas medyayı vurgulayın"
verificationEmailSent: "Doğrulama e-postası gönderildi. Doğrulamayı tamamlamak için e-postadaki bağlantıyı takip edin."
notSet: "Ayarlı değil"
@ -1018,6 +1026,9 @@ pushNotificationAlreadySubscribed: "Push bildirimleri zaten açık"
pushNotificationNotSupported: "Push bildirimleri sunucu veya tarayıcı tarafından desteklenmiyor"
sendPushNotificationReadMessage: "Okunduktan sonra push bildirimlerini silin"
sendPushNotificationReadMessageCaption: "Bu, cihazınızın güç tüketimini artırabilir."
pleaseAllowPushNotification: "Lütfen tarayıcı ayarlarınızdan bildirimlere izin verin."
browserPushNotificationDisabled: "Bildirim gönderme izni alınamadı."
browserPushNotificationDisabledDescription: "{serverName} sunucusundan bildirim gönderme izniniz yok. Lütfen tarayıcı ayarlarınızdan bildirimlere izin verin ve tekrar deneyin."
windowMaximize: "Maksimize et"
windowMinimize: "Minimize et"
windowRestore: "Geri yükle"
@ -1168,6 +1179,7 @@ installed: "Yüklendi"
branding: "Markalaşma"
enableServerMachineStats: "Sunucu donanım istatistiklerini yayınla"
enableIdenticonGeneration: "Kullanıcı identicon oluşturmayı etkinleştir"
showRoleBadgesOfRemoteUsers: "Uzaktan kullanıcılara verilen rol rozetlerini görüntüle"
turnOffToImprovePerformance: "Devre dışı bırakma, daha yüksek performansa yol açabilir."
createInviteCode: "Davet Kodu oluştur"
createWithOptions: "Seçeneklerle oluştur"
@ -1316,6 +1328,7 @@ acknowledgeNotesAndEnable: "Önlemleri anladıktan sonra açın."
federationSpecified: "Bu sunucu, beyaz liste federasyonunda çalıştırılmaktadır. Yönetici tarafından belirlenen sunucular dışında diğer sunucularla etkileşim kurmak yasaktır."
federationDisabled: "Bu sunucuda federasyon devre dışıdır. Diğer sunuculardaki kullanıcılarla etkileşim kuramazsınız."
draft: "Taslaklar"
draftsAndScheduledNotes: "Taslaklar ve planlanmış gönderiler"
confirmOnReact: "Tepki verirken onaylayın"
reactAreYouSure: "“{emoji}” tepkisini eklemek ister misin?"
markAsSensitiveConfirm: "Bu medyayı hassas olarak ayarlamak ister misin?"
@ -1344,6 +1357,7 @@ textCount: "Karakter sayısı"
information: "Hakkında"
chat: "Sohbet"
directMessage: "Kullanıcıyla sohbet et"
directMessage_short: "Mesaj"
migrateOldSettings: "Eski istemci ayarlarını taşıma"
migrateOldSettings_description: "Bu işlem otomatik olarak yapılmalıdır, ancak herhangi bir nedenle geçiş başarısız olursa, geçiş işlemini manuel olarak kendin başlatabilirsin. Mevcut yapılandırma bilgileri üzerine yazılacaktır."
compress: "Sıkıştır"
@ -1371,6 +1385,8 @@ redisplayAllTips: "Tüm “İpucu & Püf Nokta” tekrar göster"
hideAllTips: "Tüm “İpucu & Püf Nokta” gizle"
defaultImageCompressionLevel: "Varsayılan görüntü sıkıştırma düzeyi"
defaultImageCompressionLevel_description: "Düşük seviye görüntü kalitesini korur ancak dosya boyutunu artırır.<br>Yüksek seviye dosya boyutunu azaltır ancak görüntü kalitesini düşürür."
defaultCompressionLevel: "Varsayılan sıkıştırma seviyesi"
defaultCompressionLevel_description: "Ayarı düşürmek kaliteyi koruyacak ancak dosya boyutunu artıracaktır. <br> Ayarı yükseltmek dosya boyutunu küçültecek ancak kaliteyi düşürecektir."
inMinutes: "Dakika(lar)"
inDays: "Gün(ler)"
safeModeEnabled: "Güvenli mod etkinleştirildi"
@ -1378,21 +1394,74 @@ pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm
customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor."
themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır."
thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!"
createUserSpecifiedNote: "Kullanıcı tarafından belirtilen notlar oluşturun"
schedulePost: "Bir gönderi planla"
scheduleToPostOnX: "{x} için bir gönderi planla"
scheduledToPostOnX: "{x} için bir gönderi planlandı."
schedule: "rezervasyon"
scheduled: "rezervasyon"
widgets: "Widget'lar"
deviceInfo: "Cihaz Bilgileri"
deviceInfoDescription: "Teknik bir sorunuz olduğunda, aşağıdaki bilgileri eklemeniz sorunun çözülmesine yardımcı olabilir."
youAreAdmin: "Siz yöneticisiniz."
frame: "Çerçeve"
presets: "Ön ayar"
zeroPadding: "Sıfır doldurma"
nothingToConfigure: "Ayarlar seçeneği bulunmamaktadır."
_imageEditing:
_vars:
caption: "Dosya başlığı"
filename: "Dosya adı"
filename_without_ext: "Uzantısız dosya adları"
year: "Çekim yılı"
month: "Çekim ayı"
day: "Çekim tarihi"
hour: "Fotoğrafın çekildiği zaman (saat)"
minute: "Çekim süresi (dakika)"
second: "Çekim süresi (saniye)"
camera_model: "Kamera Adı"
camera_lens_model: "Lens adı"
camera_mm: "Odak uzaklığı"
camera_mm_35: "Genişlik (35mm)"
camera_f: "açıklık"
camera_s: "Enstantane hızı"
camera_iso: "ISO hassasiyeti"
gps_lat: "Enlem"
gps_long: "Boylam"
_imageFrameEditor:
title: "Düzenleme kareleri"
tip: "Görselleri, meta verileri içeren çerçeveler ve etiketler ekleyerek süsleyebilirsiniz."
header: "Başlık"
footer: "Alt bilgi"
borderThickness: "jantın genişliği"
labelThickness: "Etiket genişliği"
labelScale: "Etiket ölçeği"
centered: "Merkezlenmiş"
captionMain: "Altyazı (büyük)"
captionSub: "Altyazı (küçük)"
availableVariables: "Mevcut değişkenler"
withQrCode: "2 boyutlu kod"
backgroundColor: "Arka Plan Rengi "
textColor: "Metin Rengi "
font: "Yazı tipi"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
quitWithoutSaveConfirm: "Kaydedilmemiş değişiklikleri silmek ister misin?"
failedToLoadImage: "Görüntü yükleme başarısız oldu "
_compression:
_quality:
high: "Yüksek Kalite "
medium: "Orta Kalite"
low: "Düşük Kalite "
_size:
large: "Büyük Boyut"
medium: "Orta Boyut"
small: "Küçük Boyut"
_order:
newest: "Önce yeni"
oldest: "Önce eski"
_chat:
messages: "Mesaj"
noMessagesYet: "Henüz mesaj yok"
newMessage: "Yeni mesaj"
individualChat: "Özel Sohbet"
@ -1481,6 +1550,11 @@ _settings:
showUrlPreview: "URL önizlemesi"
showAvailableReactionsFirstInNote: "Mevcut tepkileri en üstte göster."
showPageTabBarBottom: "Sayfa sekme çubuğunu aşağıda göster"
emojiPaletteBanner: "Emoji seçiciye kalıcı olarak bir palet olarak görüntülenecek ön ayarları kaydedebilir veya seçicinin nasıl görüntüleneceğini özelleştirebilirsiniz."
enableAnimatedImages: "Hareketli görüntüleri etkinleştirin"
settingsPersistence_title: "Ayarların kalıcılığı"
settingsPersistence_description1: "Ayarların kalıcı olarak saklanmasını etkinleştirmek, yapılandırma bilgilerinin kaybolmasını önler."
settingsPersistence_description2: "Ortamınıza bağlı olarak bu özelliği etkinleştirmek mümkün olmayabilir."
_chat:
showSenderName: "Gönderenin adını göster"
sendOnEnter: "Enter tuşuna basarak gönderin"
@ -1489,6 +1563,8 @@ _preferencesProfile:
profileNameDescription: "Bu cihazı tanımlayan bir ad belirle."
profileNameDescription2: "Örnek: “Ana bilgisayar”, “Akıllı telefon”"
manageProfiles: "Profilleri Yönet"
shareSameProfileBetweenDevicesIsNotRecommended: "Aynı profili birden fazla cihazda kullanmak önerilmez."
useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "Birden fazla cihazda senkronize etmek istediğiniz ayarlarınız varsa, lütfen her bir ayar için \"Birden fazla cihazda senkronize et\" seçeneğini etkinleştirin."
_preferencesBackup:
autoBackup: "Otomatik yedekleme"
restoreFromBackup: "Yedeklemeden geri yükle"
@ -1498,6 +1574,7 @@ _preferencesBackup:
youNeedToNameYourProfileToEnableAutoBackup: "Otomatik yedeklemeyi etkinleştirmek için bir profil adı ayarlanmalıdır."
autoPreferencesBackupIsNotEnabledForThisDevice: "Bu cihazda ayarların otomatik yedeklemesi etkinleştirilmemiş."
backupFound: "Ayarların yedeği bulundu"
forceBackup: "Ayarların zorunlu yedeklenmesi"
_accountSettings:
requireSigninToViewContents: "İçeriği görüntülemek için oturum açmanız gerekir."
requireSigninToViewContentsDescription1: "Oluşturduğun tüm notları ve diğer içeriği görüntülemek için oturum açman gerekir. Bu, tarayıcıların bilgilerini toplamasına engel olacaktır."
@ -2003,6 +2080,7 @@ _role:
canManageAvatarDecorations: "Avatar süslerini yönet"
driveCapacity: "Drive kapasitesi"
maxFileSize: "Yükleyebileceğin maksimum dosya boyutu"
maxFileSize_caption: "Önceki aşamada ters proxy veya CDN gibi başka yapılandırma ayarları da olabilir."
alwaysMarkNsfw: "Dosyaları her zaman NSFW olarak işaretle"
canUpdateBioMedia: "Bir simge veya banner görüntüsünü düzenleyebilir"
pinMax: "Sabitlenmiş notların maksimum sayısı"
@ -2030,6 +2108,7 @@ _role:
uploadableFileTypes_caption: "İzin verilen MIME/dosya türlerini belirtir. Birden fazla MIME türü, yeni bir satırla ayırarak belirtilebilir ve joker karakterler yıldız işareti (*) ile belirtilebilir. (örneğin, image/*)"
uploadableFileTypes_caption2: "Bazı dosya türleri algılanamayabilir. Bu tür dosyalara izin vermek için, spesifikasyona {x} ekle."
noteDraftLimit: "Sunucu notlarının olası taslak sayısı"
scheduledNoteLimit: "Aynı anda oluşturulabilecek planlanmış gönderi sayısı"
watermarkAvailable: "Filigran işlevinin kullanılabilirliği"
_condition:
roleAssignedTo: "Manuel rollere atanmış"
@ -2420,6 +2499,7 @@ _auth:
scopeUser: "Aşağıdaki kullanıcı olarak çalıştırın"
pleaseLogin: "Uygulamaları yetkilendirmek için lütfen giriş yapın."
byClickingYouWillBeRedirectedToThisUrl: "Erişim izni verildiğinde, otomatik olarak aşağıdaki URL'ye yönlendirileceksin."
alreadyAuthorized: "Bu uygulamaya zaten erişim izinleri verilmiş durumda."
_antennaSources:
all: "Tüm notlar"
homeTimeline: "Takip edilen kullanıcıların notları"
@ -2468,11 +2548,40 @@ _widgets:
chat: "Sohbet"
_widgetOptions:
showHeader: "Başlığı göster"
transparent: "Arka planı şeffaf yapın"
height: "Yükseklik"
_button:
colored: "Renkli"
_clock:
size: "Boyut"
thickness: "İğne kalınlığı"
thicknessThin: "İnce"
thicknessMedium: "Normal"
thicknessThick: "Kalın"
graduations: "Kadran ölçeği"
graduationDots: "Nokta"
graduationArabic: "Arap rakamları"
fadeGraduations: "ölçeği soluklaştır"
sAnimation: "İkinci el animasyon"
sAnimationElastic: "Gerçek"
sAnimationEaseOut: "Düz"
twentyFour: "24 saat ekran"
labelTime: "Zaman"
labelTz: "Zaman Dilimi"
labelTimeAndTz: "Zaman ve Saat Dilimi"
timezone: "Zaman Dilimi "
showMs: "Milisaniye cinsinden göster"
showLabel: "Etiketi Göster"
_jobQueue:
sound: "Sesleri Çal"
_rss:
url: "RSS beslemesi URL'si"
refreshIntervalSec: "Güncelleme aralığı (saniye)"
maxEntries: "Görüntülenecek maksimum öğe sayısı"
_rssTicker:
shuffle: "Görüntüleme sırasını karıştır"
duration: "Kaydırma yazısı hızı (saniye)"
reverse: "Geriye doğru kaydır"
_birthdayFollowings:
period: "Süre"
_cw:
@ -2519,9 +2628,20 @@ _postForm:
replyPlaceholder: "Bu notu yanıtla..."
quotePlaceholder: "Bu notu alıntı yap..."
channelPlaceholder: "Bir kanala gönder..."
showHowToUse: "Form açıklamasını göster"
_howToUse:
content_title: "Metin"
content_description: "Yayınlamak istediğiniz içeriği girin."
toolbar_title: "Araç Çubuğu"
toolbar_description: "Dosya ve anket ekleyebilir, açıklamalar ve etiketler ekleyebilir, emoji ve bahsetme mesajları ekleyebilirsiniz."
account_title: "Hesap Menüsü"
account_description: "Paylaşım yaptığınız hesabı değiştirebilir ve hesabınıza kaydedilmiş taslak ve planlanmış paylaşımların listesini görüntüleyebilirsiniz."
visibility_title: "Görünürlük"
visibility_description: "Notlarınıza kimlerin erişebileceğinin kapsamını belirleyebilirsiniz."
menu_title: "Menü"
menu_description: "Taslak olarak kaydetme, gönderi planlama ve tepki ayarlama gibi diğer işlemleri de gerçekleştirebilirsiniz."
submit_title: "Gönder düğmesi"
submit_description: "Bir not paylaşacağım. Ctrl + Enter / Cmd + Enter tuşlarını kullanarak da paylaşım yapabilirsiniz."
_placeholders:
a: "Ne yapıyorsun?"
b: "Çevrende neler oluyor?"
@ -2667,6 +2787,8 @@ _notification:
youReceivedFollowRequest: "Bir takip isteği aldınız."
yourFollowRequestAccepted: "Takip isteğin kabul edildi."
pollEnded: "Anket sonuçlarııklandı."
scheduledNotePosted: "Rezervasyon defteri yayınlandı."
scheduledNotePostFailed: "Rezervasyon defterine gönderilemedi"
newNote: "Yeni not"
unreadAntennaNote: "{name} anteni"
roleAssigned: "Verilen rol"
@ -2696,6 +2818,8 @@ _notification:
quote: "Alıntılar"
reaction: "Tepki"
pollEnded: "Anketler sona eriyor"
scheduledNotePosted: "Planlanan gönderi başarılı"
scheduledNotePostFailed: "Planlanan gönderi başarısız oldu"
receiveFollowRequest: "Takip istekleri alındı"
followRequestAccepted: "Kabul edilen takip istekleri"
roleAssigned: "Verilen rol"
@ -2735,6 +2859,14 @@ _deck:
usedAsMinWidthWhenFlexible: "“Otomatik genişlik ayarı” seçeneği etkinleştirildiğinde, bunun için minimum genişlik kullanılacak."
flexible: "Otomatik genişlik ayarı"
enableSyncBetweenDevicesForProfiles: "Cihazlar arasında profil bilgilerinin senkronizasyonunu etkinleştir"
showHowToUse: "Kullanıcı arayüzü açıklamasını görüntüle"
_howToUse:
addColumn_title: "Sütun ekle"
addColumn_description: "Sütun türlerini seçip ekleyebilirsiniz."
settings_title: "Arayüz Yapılandırması"
settings_description: "Sekme kullanıcı arayüzünü ayrıntılı olarak yapılandırabilirsiniz."
switchProfile_title: "Profili Değiştir"
switchProfile_description: "Kullanıcı arayüzü düzenlerini profil olarak kaydedebilir ve istediğiniz zaman bunlar arasında geçiş yapabilirsiniz."
_columns:
main: "Ana"
widgets: "Widget'lar"
@ -2795,6 +2927,8 @@ _abuseReport:
notifiedWebhook: "Kullanılacak webhook"
deleteConfirm: "Bildirim alıcısını silmek istediğinden emin misin?"
_moderationLogTypes:
clearQueue: "Kuyruğu temizle"
promoteQueue: "Sıraya alınmış işi yeniden deneyin."
createRole: "Rol oluşturuldu"
deleteRole: "Rol silindi"
updateRole: "Rol güncellendi"
@ -3189,10 +3323,13 @@ _watermarkEditor:
title: "Filigranı Düzenle"
cover: "Her şeyi örtün"
repeat: "her yere yayılmış"
preserveBoundingRect: "Döndürme sırasında dışarı çıkmayacak şekilde ayarlayın."
opacity: "Opaklık"
scale: "Boyut"
text: "Metin"
qr: "2 boyutlu kod"
position: "Pozisyon"
margin: "Kenar"
type: "Tür"
image: "Görseller"
advanced: "Gelişmiş"
@ -3207,16 +3344,21 @@ _watermarkEditor:
polkadotSubDotOpacity: "İkincil noktanın opaklığı"
polkadotSubDotRadius: "İkincil noktanın boyutu"
polkadotSubDotDivisions: "Alt nokta sayısı."
leaveBlankToAccountUrl: "Boş bırakılması durumunda hesap URL'si görüntülenecektir."
failedToLoadImage: "Görüntü yükleme başarısız oldu "
_imageEffector:
title: "Effektler"
addEffect: "Efektler Ekle"
discardChangesConfirm: "Cidden çıkmak istiyor musun? Kaydedilmemiş değişikliklerin var."
failedToLoadImage: "Görüntü yükleme başarısız oldu "
_fxs:
chromaticAberration: "Renk Sapması"
glitch: "Bozulma"
mirror: "Ayna"
invert: "Renkleri Ters Çevir"
grayscale: "Gri tonlama"
blur: "Bulanıklık"
pixelate: "Mozaik"
colorAdjust: "Renk Düzeltme"
colorClamp: "Renk Sıkıştırma"
colorClampAdvanced: "Renk Sıkıştırma (Gelişmiş)"
@ -3228,10 +3370,13 @@ _imageEffector:
checker: "Denetleyici"
blockNoise: "Gürültüyü Engelle"
tearing: "Yırtılma"
fill: "Doldur"
_fxProps:
angle: "Açı"
scale: "Boyut"
size: "Boyut"
radius: "Yarıçap"
samples: "Örnek sayısı"
offset: "Pozisyon"
color: "Renk"
opacity: "Opaklık"
@ -3256,11 +3401,10 @@ _imageEffector:
threshold: "Eşik"
centerX: "Merkez X"
centerY: "Merkez Y"
zoomLinesSmoothing: "Düzeltme"
zoomLinesSmoothingDescription: "Düzeltme ve yakınlaştırma çizgi genişliği birlikte kullanılamaz."
zoomLinesThreshold: "Zoom çizgi genişliği"
density: "Yoğunluk"
zoomLinesOutlineThickness: "çizgi gölge kalınlığı"
zoomLinesMaskSize: "Merkez çapı"
zoomLinesBlack: "Siyah yap"
circle: "Dairesel"
drafts: "Taslaklar"
_drafts:
select: "Taslak Seç"
@ -3276,6 +3420,22 @@ _drafts:
restoreFromDraft: "Taslaktan geri yükle"
restore: "Geri yükle"
listDrafts: "Taslaklar Listesi"
schedule: "Planlanmış Gönderi"
listScheduledNotes: "Planlanmış gönderilerin listesi"
cancelSchedule: "Rezervasyonu iptal et"
qr: "2 boyutlu kod"
_qr:
showTabTitle: "Ekran"
readTabTitle: "Okumak"
shareTitle: "{name}{acct}"
shareText: "Beni Fediverse'te takip edin!"
chooseCamera: "Kamera Seç"
cannotToggleFlash: "Işık seçeneği mevcut değil."
turnOnFlash: "Işığıın"
turnOffFlash: "Işığı kapatın"
startQr: "Özgeçmiş Kodu Okuyucu"
stopQr: "Kod okuyucuyu durdurun"
noQrCodeFound: "QR kodu bulunamadı"
scanFile: "Cihazdaki görüntüyü tarayın"
raw: "Metin"
mfm: "MFM"

View file

@ -1,5 +1,5 @@
---
_lang_: "Tiếng Việt "
_lang_: "Tiếng Việt"
headlineMisskey: "Mạng xã hội liên hợp"
introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀"
poweredByMisskeyDescription: "{name} là một trong những chủ máy của <b>Misskey</b> là nền tảng mã nguồn mở"
@ -576,6 +576,7 @@ showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin"
showFixedPostFormInChannel: "Hiển thị mẫu bài đăng ở phía trên bản tin"
withRepliesByDefaultForNewlyFollowed: "Mặc định hiển thị trả lời từ những người dùng mới theo dõi trong dòng thời gian"
newNoteRecived: "Đã nhận tút mới"
newNote: "Ghi chú mới"
sounds: "Âm thanh"
sound: "Âm thanh"
notificationSoundSettings: "Cài đặt âm thanh thông báo"
@ -848,7 +849,7 @@ hideOnlineStatus: "Ẩn trạng thái online"
hideOnlineStatusDescription: "Ẩn trạng thái online của bạn làm giảm sự tiện lợi của một số tính năng như tìm kiếm."
online: "Online"
active: "Hoạt động"
offline: "Offline"
offline: "Ngoại tuyến"
notRecommended: "Không đề xuất"
botProtection: "Bảo vệ Bot"
instanceBlocking: "Máy chủ đã chặn"
@ -1220,6 +1221,7 @@ information: "Giới thiệu"
chat: "Trò chuyện"
migrateOldSettings: "Di chuyển cài đặt cũ"
migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn."
driveAboutTip: "Trong Drive, danh sách các tệp bạn đã tải lên trước đây sẽ được hiển thị.<br>\nBạn có thể sử dụng lại chúng khi đính kèm vào ghi chú, hoặc tải lên trước các tệp để đăng sau.<br>\n<b>Lưu ý rằng nếu bạn xóa một tệp, tệp đó cũng sẽ biến mất khỏi tất cả những nơi đã sử dụng tệp đó (ghi chú, trang, ảnh đại diện, biểu ngữ, v.v.).</b><br>\nBạn cũng có thể tạo các thư mục để sắp xếp chúng."
inMinutes: "phút"
inDays: "ngày"
widgets: "Tiện ích"

View file

@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "阈值"
centerX: "中心 X "
centerY: "中心 Y"
zoomLinesSmoothing: "平滑"
zoomLinesSmoothingDescription: "平滑和集中线宽度设置不能同时使用。"
zoomLinesThreshold: "集中线宽度"
density: "密度"
zoomLinesOutlineThickness: "线条阴影粗细"
zoomLinesMaskSize: "中心直径"
zoomLinesBlack: "变成黑色"
circle: "圆形"
drafts: "草稿"
_drafts:

View file

@ -3401,11 +3401,9 @@ _imageEffector:
threshold: "閾值"
centerX: "X中心座標"
centerY: "Y中心座標"
zoomLinesSmoothing: "平滑化"
zoomLinesSmoothingDescription: "平滑化與集中線寬度設定不能同時使用。"
zoomLinesThreshold: "集中線的寬度"
density: "密度"
zoomLinesOutlineThickness: "線條陰影的粗細"
zoomLinesMaskSize: "中心直徑"
zoomLinesBlack: "變成黑色"
circle: "圓形"
drafts: "草稿\n"
_drafts:

View file

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2026.3.1",
"version": "2026.3.2",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@10.30.3",
"packageManager": "pnpm@10.32.1",
"workspaces": [
"packages/misskey-js",
"packages/i18n",
@ -53,29 +53,29 @@
"cleanall": "pnpm clean-all"
},
"dependencies": {
"cssnano": "7.1.2",
"esbuild": "0.27.3",
"cssnano": "7.1.3",
"esbuild": "0.27.4",
"execa": "9.6.1",
"ignore-walk": "8.0.0",
"js-yaml": "4.1.1",
"postcss": "8.5.6",
"tar": "7.5.10",
"postcss": "8.5.8",
"tar": "7.5.11",
"terser": "5.46.0"
},
"devDependencies": {
"@eslint/js": "9.39.3",
"@eslint/js": "9.39.4",
"@misskey-dev/eslint-plugin": "2.1.0",
"@types/js-yaml": "4.0.9",
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@typescript/native-preview": "7.0.0-dev.20260116.1",
"cross-env": "10.1.0",
"cypress": "15.11.0",
"eslint": "9.39.3",
"eslint": "9.39.4",
"globals": "17.4.0",
"ncp": "2.0.0",
"pnpm": "10.30.3",
"pnpm": "10.32.1",
"start-server-and-test": "2.1.5",
"typescript": "5.9.3"
},

View file

@ -71,8 +71,8 @@
"utf-8-validate": "6.0.6"
},
"dependencies": {
"@aws-sdk/client-s3": "3.1000.0",
"@aws-sdk/lib-storage": "3.1000.0",
"@aws-sdk/client-s3": "3.1008.0",
"@aws-sdk/lib-storage": "3.1008.0",
"@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.4",
"@fastify/cors": "11.2.0",
@ -83,16 +83,16 @@
"@kitajs/html": "4.2.13",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.5",
"@napi-rs/canvas": "0.1.95",
"@nestjs/common": "11.1.14",
"@nestjs/core": "11.1.14",
"@nestjs/testing": "11.1.14",
"@napi-rs/canvas": "0.1.96",
"@nestjs/common": "11.1.16",
"@nestjs/core": "11.1.16",
"@nestjs/testing": "11.1.16",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "10.40.0",
"@sentry/profiling-node": "10.40.0",
"@simplewebauthn/server": "13.2.3",
"@sinonjs/fake-timers": "15.1.0",
"@smithy/node-http-handler": "4.4.12",
"@sentry/node": "10.43.0",
"@sentry/profiling-node": "10.43.0",
"@simplewebauthn/server": "13.3.0",
"@sinonjs/fake-timers": "15.1.1",
"@smithy/node-http-handler": "4.4.16",
"@swc/cli": "0.8.0",
"@swc/core": "1.15.18",
"@twemoji/parser": "16.0.0",
@ -103,7 +103,7 @@
"bcryptjs": "3.0.3",
"blurhash": "2.0.5",
"body-parser": "2.2.2",
"bullmq": "5.70.1",
"bullmq": "5.71.0",
"cacheable-lookup": "7.0.0",
"chalk": "5.6.2",
"chalk-template": "1.1.2",
@ -112,10 +112,10 @@
"content-disposition": "1.0.1",
"date-fns": "4.1.0",
"deep-email-validator": "0.1.21",
"fastify": "5.8.1",
"fastify": "5.8.2",
"fastify-raw-body": "5.0.0",
"feed": "5.2.0",
"file-type": "21.3.0",
"file-type": "21.3.2",
"fluent-ffmpeg": "2.1.3",
"form-data": "4.0.5",
"got": "14.6.6",
@ -138,14 +138,14 @@
"nanoid": "5.1.6",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"node-html-parser": "7.0.2",
"nodemailer": "8.0.1",
"node-html-parser": "7.1.0",
"nodemailer": "8.0.2",
"nsfwjs": "4.2.0",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
"otpauth": "9.5.0",
"pg": "8.19.0",
"pg": "8.20.0",
"pkce-challenge": "6.0.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@ -164,7 +164,7 @@
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"systeminformation": "5.31.1",
"systeminformation": "5.31.4",
"tinycolor2": "1.6.0",
"tmp": "0.2.5",
"tsc-alias": "1.8.16",
@ -178,8 +178,8 @@
"devDependencies": {
"@jest/globals": "29.7.0",
"@kitajs/ts-html-plugin": "4.1.4",
"@nestjs/platform-express": "11.1.14",
"@sentry/vue": "10.40.0",
"@nestjs/platform-express": "11.1.16",
"@sentry/vue": "10.43.0",
"@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.39",
"@types/accepts": "1.3.7",
@ -193,7 +193,7 @@
"@types/jsonld": "1.5.15",
"@types/mime-types": "3.0.1",
"@types/ms": "2.1.0",
"@types/node": "24.11.0",
"@types/node": "24.12.0",
"@types/nodemailer": "7.0.11",
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
@ -202,7 +202,7 @@
"@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6",
"@types/rename": "1.0.7",
"@types/sanitize-html": "2.16.0",
"@types/sanitize-html": "2.16.1",
"@types/semver": "7.7.1",
"@types/simple-oauth2": "5.0.8",
"@types/sinonjs__fake-timers": "15.0.1",
@ -212,10 +212,10 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.4",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"aws-sdk-client-mock": "4.1.0",
"cbor": "10.0.11",
"cbor": "10.0.12",
"cross-env": "10.1.0",
"esbuild-plugin-swc": "1.0.1",
"eslint-plugin-import": "2.32.0",

View file

@ -10,7 +10,6 @@ import { type FastifyServerOptions } from 'fastify';
import type * as Sentry from '@sentry/node';
import type * as SentryVue from '@sentry/vue';
import type { RedisOptions } from 'ioredis';
import type { ManifestChunk } from 'vite';
type RedisOptionsSource = Partial<RedisOptions> & {
host: string;
@ -189,9 +188,7 @@ export type Config = {
authUrl: string;
driveUrl: string;
userAgent: string;
frontendEntry: ManifestChunk;
frontendManifestExists: boolean;
frontendEmbedEntry: ManifestChunk;
frontendEmbedManifestExists: boolean;
mediaProxy: string;
externalMediaProxyEnabled: boolean;
@ -250,12 +247,6 @@ export function loadConfig(): Config {
const frontendManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'));
const frontendEmbedManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'));
const frontendManifest = frontendManifestExists ?
JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'), 'utf-8'))
: { 'src/_boot_.ts': { file: null } };
const frontendEmbedManifest = frontendEmbedManifestExists ?
JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'), 'utf-8'))
: { 'src/boot.ts': { file: null } };
const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source;
@ -337,9 +328,7 @@ export function loadConfig(): Config {
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null,
userAgent: `Misskey/${version} (${config.url})`,
frontendEntry: frontendManifest['src/_boot_.ts'],
frontendManifestExists: frontendManifestExists,
frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
frontendEmbedManifestExists: frontendEmbedManifestExists,
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,

View file

@ -81,7 +81,7 @@ export class ApRequestCreator {
}, args.additionalHeaders),
};
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host']);
return {
request,

View file

@ -15,6 +15,7 @@ import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointServ
import { MiLocalUser } from '@/models/User.js';
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
import { ApiError } from '../../error.js';
import { Brackets } from 'typeorm';
export const meta = {
tags: ['notes', 'channels'],
@ -132,7 +133,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.then(x => x.map(x => x.id).filter(x => x !== ps.channelId));
if (mutingChannelIds.length > 0) {
query.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
query.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
query.andWhere(new Brackets(qb => {
qb.orWhere('note.renoteChannelId IS NULL');
qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
}));
}
}
//#endregion

View file

@ -177,7 +177,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.andWhere('note.channelId IS NULL')
.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
if (mutingChannelIds.length > 0) {
qb.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
qb.andWhere(new Brackets(qb2 => {
qb2.orWhere('note.renoteChannelId IS NULL');
qb2.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
}));
}
}));
} else if (followingChannelIds.length > 0) {

View file

@ -185,7 +185,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.withChannelNotes) {
query.andWhere(new Brackets(qb => {
if (mutingChannelIds.length > 0) {
qb.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds: mutingChannelIds });
qb.andWhere(new Brackets(qb2 => {
qb2.orWhere('note.channelId IS NULL');
qb2.orWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
}));
}
if (!isSelf) {

View file

@ -4,23 +4,19 @@
*/
import * as WebSocket from 'ws';
import type { MiUser } from '@/models/User.js';
import type { MiAccessToken } from '@/models/AccessToken.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
import { MiFollowing, MiUserProfile } from '@/models/_.js';
import type { GlobalEvents, StreamEventEmitter } from '@/core/GlobalEventService.js';
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import { isJsonObject } from '@/misc/json-value.js';
import type { EventEmitter } from 'events';
import type Channel from './channel.js';
import type { ChannelConstructor } from './channel.js';
import type { ChannelRequest } from './channel.js';
import { ContextIdFactory, ModuleRef, REQUEST } from '@nestjs/core';
import { Inject, Injectable, Scope } from '@nestjs/common';
import { isJsonObject } from '@/misc/json-value.js';
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
import type { GlobalEvents, StreamEventEmitter } from '@/core/GlobalEventService.js';
import { MiFollowing, MiUserProfile } from '@/models/_.js';
import { CacheService } from '@/core/CacheService.js';
import { bindThis } from '@/decorators.js';
import { NotificationService } from '@/core/NotificationService.js';
import type { MiAccessToken } from '@/models/AccessToken.js';
import type { MiUser } from '@/models/User.js';
import { MainChannel } from '@/server/api/stream/channels/main.js';
import { HomeTimelineChannel } from '@/server/api/stream/channels/home-timeline.js';
import { LocalTimelineChannel } from '@/server/api/stream/channels/local-timeline.js';
@ -39,13 +35,17 @@ import { ChatUserChannel } from '@/server/api/stream/channels/chat-user.js';
import { ChatRoomChannel } from '@/server/api/stream/channels/chat-room.js';
import { ReversiChannel } from '@/server/api/stream/channels/reversi.js';
import { ReversiGameChannel } from '@/server/api/stream/channels/reversi-game.js';
import type { ChannelRequest } from './channel.js';
import type { ChannelConstructor } from './channel.js';
import type Channel from './channel.js';
import type { EventEmitter } from 'events';
const MAX_CHANNELS_PER_CONNECTION = 32;
/**
* Main stream connection
*/
// eslint-disable-next-line import/no-default-export
@Injectable({ scope: Scope.TRANSIENT })
export default class Connection {
public user?: MiUser;
@ -206,12 +206,17 @@ export default class Connection {
@bindThis
private async onNoteStreamMessage(data: GlobalEvents['note']['payload']) {
if (data.body.visibility === 'specified' && !data.body.visibleUserIds.includes(this.user!.id)) {
return;
}
// 自分自身ではないかつ
if (data.body.userId !== this.user?.id) {
// 公開範囲が指名で自分が含まれてない
if (data.body.visibility === 'specified' && (this.user == null || !data.body.visibleUserIds.includes(this.user.id))) {
return;
}
if (data.body.visibility === 'followers' && !Object.hasOwn(this.following, data.body.userId)) {
return;
// 公開範囲がフォロワーで自分がフォロワーでない
if (data.body.visibility === 'followers' && !Object.hasOwn(this.following, data.body.userId)) {
return;
}
}
this.sendMessageToWs('noteUpdated', {

View file

@ -6,16 +6,11 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { deepClone } from '@/misc/clone.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { Packed } from '@/misc/json-schema.js';
import type { MiUser } from '@/models/User.js';
type HiddenLayer = 'note' | 'renote' | 'renoteRenote';
type LockdownCheckResult =
| { shouldSkip: true }
| { shouldSkip: false; hiddenLayers: Set<HiddenLayer> };
/** Streamにおいて、ートを隠すhideNoteを適用するためのService */
@Injectable()
export class NoteStreamingHidingService {
@ -23,110 +18,52 @@ export class NoteStreamingHidingService {
private noteEntityService: NoteEntityService,
) {}
/**
*
*
* @param note -
* @param meId - IDnull
* @returns shouldSkip: true false hiddenLayers
*/
@bindThis
public async shouldHide(
note: Packed<'Note'>,
meId: MiUser['id'] | null,
): Promise<LockdownCheckResult> {
const hiddenLayers = new Set<HiddenLayer>();
private collectRenoteChain(note: Packed<'Note'>): Packed<'Note'>[] {
const renoteChain: Packed<'Note'>[] = [];
// 1階層目: note自体
const shouldHideThisNote = await this.noteEntityService.shouldHideNote(note, meId);
if (shouldHideThisNote) {
if (isRenotePacked(note) && isQuotePacked(note)) {
// 引用リノートの場合、内容を隠して流す
hiddenLayers.add('note');
} else if (isRenotePacked(note)) {
// 純粋リノートの場合、流さない
return { shouldSkip: true };
} else {
// 通常ノートの場合、内容を隠して流す
hiddenLayers.add('note');
}
for (let current: Packed<'Note'> | null | undefined = note; current != null; current = current.renote) {
renoteChain.push(current);
}
// 2階層目: note.renote
if (isRenotePacked(note) && note.renote) {
const shouldHideRenote = await this.noteEntityService.shouldHideNote(note.renote, meId);
if (shouldHideRenote) {
if (isQuotePacked(note)) {
// noteが引用リートの場合、renote部分だけ隠す
hiddenLayers.add('renote');
} else {
// noteが純粋リートの場合、流さない
return { shouldSkip: true };
}
}
}
// 3階層目: note.renote.renote
if (isRenotePacked(note) && note.renote &&
isRenotePacked(note.renote) && note.renote.renote) {
const shouldHideRenoteRenote = await this.noteEntityService.shouldHideNote(note.renote.renote, meId);
if (shouldHideRenoteRenote) {
if (isQuotePacked(note.renote)) {
// note.renoteが引用リートの場合、renote.renote部分だけ隠す
hiddenLayers.add('renoteRenote');
} else {
// note.renoteが純粋リートの場合、note.renoteの意味がなくなるので流さない
return { shouldSkip: true };
}
}
}
return { shouldSkip: false, hiddenLayers };
return renoteChain;
}
/**
* hiddenLayersに基づいてートの内容を隠す
*
*
* `note`
*
* `null`
*
*
* @param note -
* @param hiddenLayers -
* @param meId - ID `null`
* @returns `null`
*/
@bindThis
public applyHiding(
note: Packed<'Note'>,
hiddenLayers: Set<HiddenLayer>,
): void {
if (hiddenLayers.has('note')) {
this.noteEntityService.hideNote(note);
}
if (hiddenLayers.has('renote') && note.renote) {
this.noteEntityService.hideNote(note.renote);
}
if (hiddenLayers.has('renoteRenote') && note.renote && note.renote.renote) {
this.noteEntityService.hideNote(note.renote.renote);
}
}
public async filter(note: Packed<'Note'>, meId: MiUser['id'] | null): Promise<Packed<'Note'> | null> {
const renoteChain = this.collectRenoteChain(note);
const shouldHide = await Promise.all(renoteChain.map(n => this.noteEntityService.shouldHideNote(n, meId)));
/**
*
*
* `note`
*
* @param note -
* @param meId - IDnull
* @returns shouldSkip: true
*/
@bindThis
public async processHiding(
note: Packed<'Note'>,
meId: MiUser['id'] | null,
): Promise<{ shouldSkip: boolean }> {
const result = await this.shouldHide(note, meId);
if (result.shouldSkip) {
return { shouldSkip: true };
if (!shouldHide.some(h => h)) {
// 隠す必要がない場合は元のノートをそのまま返す
return note;
}
this.applyHiding(note, result.hiddenLayers);
return { shouldSkip: false };
if (renoteChain.some(n => isRenotePacked(n) && !isQuotePacked(n))) {
// 純粋リノートの場合は配信をスキップする
return null;
}
const clonedNote = deepClone(note);
let currentCloned = clonedNote;
for (let i = 0; i < renoteChain.length; i++) {
if (shouldHide[i]) {
this.noteEntityService.hideNote(currentCloned);
}
currentCloned = currentCloned.renote!;
}
return clonedNote;
}
}

View file

@ -62,13 +62,14 @@ export class AntennaChannel extends Channel {
@bindThis
private async onEvent(data: GlobalEvents['antenna']['payload']) {
if (data.type === 'note') {
const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
let note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
if (!this.isNoteVisibleForMe(note)) return;
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -53,8 +53,10 @@ export class ChannelChannel extends Channel {
if (!this.isNoteVisibleForMe(note)) return;
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -62,8 +62,10 @@ export class GlobalTimelineChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -59,8 +59,10 @@ export class HashtagChannel extends Channel {
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -82,8 +82,10 @@ export class HomeTimelineChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -101,8 +101,10 @@ export class HybridTimelineChannel extends Channel {
}
}
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -72,8 +72,10 @@ export class LocalTimelineChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -44,7 +44,7 @@ export class RoleTimelineChannel extends Channel {
@bindThis
private async onEvent(data: GlobalEvents['roleTimeline']['payload']) {
if (data.type === 'note') {
const note = data.body;
let note = data.body;
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
return;
@ -56,8 +56,9 @@ export class RoleTimelineChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -117,8 +117,10 @@ export class UserListChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
if (shouldSkip) return;
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
if (!filtered) return;
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
note = filtered;
if (this.user) {
if (isRenotePacked(note) && !isQuotePacked(note)) {

View file

@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { dirname } from 'node:path';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { promises as fsp } from 'node:fs';
import { promises as fsp, existsSync } from 'node:fs';
import { languages } from 'i18n/const';
import { Injectable, Inject } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
@ -13,21 +13,34 @@ import { bindThis } from '@/decorators.js';
import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
import type { FastifyReply } from 'fastify';
import type { Manifest } from 'vite';
import type { Config } from '@/config.js';
import type { MiMeta } from '@/models/Meta.js';
import type { CommonData } from './views/_.js';
import type { CommonData, ViteFiles } from './views/_.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const frontendVitePublic = `${_dirname}/../../../../frontend/public/`;
const frontendEmbedVitePublic = `${_dirname}/../../../../frontend-embed/public/`;
let rootDir = _dirname;
// 見つかるまで上に遡る
while (!existsSync(resolve(rootDir, 'packages'))) {
const parentDir = dirname(rootDir);
if (parentDir === rootDir) {
throw new Error('Cannot find root directory');
}
rootDir = parentDir;
}
const frontendViteBuilt = resolve(rootDir, 'built/_frontend_vite_');
const frontendEmbedViteBuilt = resolve(rootDir, 'built/_frontend_embed_vite_');
@Injectable()
export class HtmlTemplateService {
private frontendBootloadersFetched = false;
private frontendAssetsFetched = false;
public frontendViteFiles: ViteFiles | null = null;
public frontendBootloaderJs: string | null = null;
public frontendBootloaderCss: string | null = null;
public frontendEmbedViteFiles: ViteFiles | null = null;
public frontendEmbedBootloaderJs: string | null = null;
public frontendEmbedBootloaderCss: string | null = null;
@ -42,18 +55,92 @@ export class HtmlTemplateService {
) {
}
// 初期ロードで読み込むべきファイルのパスを収集する。
// See https://ja.vite.dev/guide/backend-integration
@bindThis
private async prepareFrontendBootloaders() {
if (this.frontendBootloadersFetched) return;
this.frontendBootloadersFetched = true;
private collectViteAssetFiles(manifest: Manifest): ViteFiles {
const entryFile = Object.values(manifest).find((chunk) => chunk.isEntry);
if (!entryFile) return {
entryJs: null,
css: [],
modulePreloads: [],
};
const [bootJs, bootCss, embedBootJs, embedBootCss] = await Promise.all([
fsp.readFile(`${frontendVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
fsp.readFile(`${frontendVitePublic}loader/style.css`, 'utf-8').catch(() => null),
fsp.readFile(`${frontendEmbedVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
fsp.readFile(`${frontendEmbedVitePublic}loader/style.css`, 'utf-8').catch(() => null),
const seenChunkIds = new Set<string>();
const cssFiles = new Set<string>();
const modulePreloads = new Set<string>();
if (entryFile.css) {
entryFile.css.forEach((css) => cssFiles.add(css));
}
if (entryFile.imports != null && Array.isArray(entryFile.imports)) {
function collectImports(imports: string[], recursive = false) {
for (const importId of imports) {
if (seenChunkIds.has(importId)) continue;
seenChunkIds.add(importId);
const importedChunk = manifest[importId];
if (!importedChunk) return;
if (importedChunk.css) {
importedChunk.css.forEach((css) => cssFiles.add(css));
}
if (importedChunk.imports != null && Array.isArray(importedChunk.imports)) {
collectImports(importedChunk.imports, true);
}
if (!recursive) {
modulePreloads.add(importedChunk.file);
}
}
}
collectImports(entryFile.imports);
}
return {
entryJs: entryFile.file,
css: Array.from(cssFiles),
modulePreloads: Array.from(modulePreloads),
};
}
@bindThis
private async prepareFrontendAssets() {
if (this.frontendAssetsFetched) return;
this.frontendAssetsFetched = true;
const [
bootJs,
bootCss,
embedBootJs,
embedBootCss,
] = await Promise.all([
fsp.readFile(resolve(frontendViteBuilt, 'loader/boot.js'), 'utf-8').catch(() => null),
fsp.readFile(resolve(frontendViteBuilt, 'loader/style.css'), 'utf-8').catch(() => null),
fsp.readFile(resolve(frontendEmbedViteBuilt, 'loader/boot.js'), 'utf-8').catch(() => null),
fsp.readFile(resolve(frontendEmbedViteBuilt, 'loader/style.css'), 'utf-8').catch(() => null),
]);
let feViteManifest: Manifest | null = null;
let embedFeViteManifest: Manifest | null = null;
if (this.config.frontendManifestExists) {
const manifestContent = await fsp.readFile(resolve(frontendViteBuilt, 'manifest.json'), 'utf-8').catch(() => null);
feViteManifest = manifestContent ? JSON.parse(manifestContent) : null;
}
if (this.config.frontendEmbedManifestExists) {
const manifestContent = await fsp.readFile(resolve(frontendEmbedViteBuilt, 'manifest.json'), 'utf-8').catch(() => null);
embedFeViteManifest = manifestContent ? JSON.parse(manifestContent) : null;
}
if (feViteManifest != null) {
this.frontendViteFiles = this.collectViteAssetFiles(feViteManifest);
}
if (bootJs != null) {
this.frontendBootloaderJs = bootJs;
}
@ -62,6 +149,10 @@ export class HtmlTemplateService {
this.frontendBootloaderCss = bootCss;
}
if (embedFeViteManifest != null) {
this.frontendEmbedViteFiles = this.collectViteAssetFiles(embedFeViteManifest);
}
if (embedBootJs != null) {
this.frontendEmbedBootloaderJs = embedBootJs;
}
@ -73,7 +164,7 @@ export class HtmlTemplateService {
@bindThis
public async getCommonData(): Promise<CommonData> {
await this.prepareFrontendBootloaders();
await this.prepareFrontendAssets();
return {
version: this.config.version,
@ -90,8 +181,10 @@ export class HtmlTemplateService {
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(this.meta)),
now: Date.now(),
federationEnabled: this.meta.federation !== 'none',
frontendViteFiles: this.frontendViteFiles,
frontendBootloaderJs: this.frontendBootloaderJs,
frontendBootloaderCss: this.frontendBootloaderCss,
frontendEmbedViteFiles: this.frontendEmbedViteFiles,
frontendEmbedBootloaderJs: this.frontendEmbedBootloaderJs,
frontendEmbedBootloaderCss: this.frontendEmbedBootloaderCss,
};

View file

@ -24,6 +24,12 @@ export type MinimumCommonData = {
config: Config;
};
export type ViteFiles = {
entryJs: string | null;
css: string[];
modulePreloads: string[];
};
export type CommonData = MinimumCommonData & {
langs: string[];
instanceName: string;
@ -36,8 +42,10 @@ export type CommonData = MinimumCommonData & {
instanceUrl: string;
now: number;
federationEnabled: boolean;
frontendViteFiles: ViteFiles | null;
frontendBootloaderJs: string | null;
frontendBootloaderCss: string | null;
frontendEmbedViteFiles: ViteFiles | null;
frontendEmbedBootloaderJs: string | null;
frontendEmbedBootloaderCss: string | null;
metaJson?: string;

View file

@ -46,11 +46,11 @@ export function BaseEmbed(props: PropsWithChildren<CommonProps<{
<link rel="icon" href={props.icon ?? '/favicon.ico'} />
<link rel="apple-touch-icon" href={props.appleTouchIcon ?? '/apple-touch-icon.png'} />
{!props.config.frontendEmbedManifestExists ? <script type="module" src="/embed_vite/@vite/client"></script> : null}
{props.frontendEmbedViteFiles == null ? <script type="module" src="/embed_vite/@vite/client"></script> : null}
{props.config.frontendEmbedEntry.css != null ? props.config.frontendEmbedEntry.css.map((href) => (
{(props.frontendEmbedViteFiles?.css ?? []).map((href) => (
<link rel="stylesheet" href={`/embed_vite/${href}`} />
)) : null}
))}
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
@ -62,7 +62,7 @@ export function BaseEmbed(props: PropsWithChildren<CommonProps<{
<script>
const VERSION = '{props.version}';
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEmbedEntry.file)};
const CLIENT_ENTRY = {JSON.stringify(props.frontendEmbedViteFiles?.entryJs ?? null)};
const LANGS = {JSON.stringify(props.langs)};
</script>

View file

@ -53,11 +53,11 @@ export function Layout(props: PropsWithChildren<CommonProps<{
{props.infoImageUrl != null ? <link rel="prefetch" as="image" href={props.infoImageUrl} /> : null}
{props.notFoundImageUrl != null ? <link rel="prefetch" as="image" href={props.notFoundImageUrl} /> : null}
{!props.config.frontendManifestExists ? <script type="module" src="/vite/@vite/client"></script> : null}
{props.frontendViteFiles == null ? <script type="module" src="/vite/@vite/client"></script> : null}
{props.config.frontendEntry.css != null ? props.config.frontendEntry.css.map((href) => (
{(props.frontendViteFiles?.css ?? []).map((href) => (
<link rel="stylesheet" href={`/vite/${href}`} />
)) : null}
))}
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
@ -80,7 +80,7 @@ export function Layout(props: PropsWithChildren<CommonProps<{
<script>
const VERSION = '{props.version}';
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEntry.file)};
const CLIENT_ENTRY = {JSON.stringify(props.frontendViteFiles?.entryJs ?? null)};
const LANGS = {JSON.stringify(props.langs)};
</script>

View file

@ -1,6 +1,6 @@
services:
nginx:
image: nginx:1.27
image: nginx:1.29
volumes:
- type: bind
source: ./certificates/rootCA.crt

View file

@ -11,9 +11,9 @@
},
"devDependencies": {
"@types/estree": "1.0.8",
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"rollup": "4.59.0"
},
"dependencies": {

View file

@ -15,7 +15,7 @@
"@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0",
"@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.4",
"@vitejs/plugin-vue": "6.0.5",
"buraha": "0.0.1",
"estree-walker": "3.0.3",
"frontend-shared": "workspace:*",
@ -26,12 +26,12 @@
"misskey-js": "workspace:*",
"punycode.js": "2.3.1",
"rollup": "4.59.0",
"sass": "1.97.3",
"sass": "1.98.0",
"shiki": "3.23.0",
"tinycolor2": "1.6.0",
"uuid": "13.0.0",
"vite": "7.3.1",
"vue": "3.5.29"
"vue": "3.5.30"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
@ -39,19 +39,19 @@
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8",
"@types/micromatch": "4.0.10",
"@types/node": "24.11.0",
"@types/node": "24.12.0",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/coverage-v8": "4.0.18",
"@vue/runtime-core": "3.5.29",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"@vue/runtime-core": "3.5.30",
"acorn": "8.16.0",
"cross-env": "10.1.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"happy-dom": "20.7.0",
"happy-dom": "20.8.4",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
"msw": "2.12.10",

View file

@ -79,3 +79,9 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
ruby: [],
unixtime: [],
};
/**
* UIの演出などでも使用されるため空にしてはいけない
* preferenceのdefinition側の値を空にすること
*/
export const DEFAULT_EMOJIS = ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'];

View file

@ -21,10 +21,10 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"esbuild": "0.27.3",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"esbuild": "0.27.4",
"eslint-plugin-vue": "10.8.0",
"nodemon": "3.1.14",
"vue-eslint-parser": "10.4.0"
@ -35,6 +35,6 @@
"dependencies": {
"i18n": "workspace:*",
"misskey-js": "workspace:*",
"vue": "3.5.29"
"vue": "3.5.30"
}
}

View file

@ -24,11 +24,11 @@
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.3",
"@rollup/pluginutils": "5.3.0",
"@sentry/vue": "10.40.0",
"@sentry/vue": "10.43.0",
"@syuilo/aiscript": "1.2.1",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.4",
"@vitejs/plugin-vue": "6.0.5",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.16",
"analytics": "0.8.19",
"broadcast-channel": "7.3.0",
@ -45,7 +45,7 @@
"date-fns": "4.1.0",
"eventemitter3": "5.0.4",
"execa": "9.6.1",
"exifreader": "4.36.2",
"exifreader": "4.37.0",
"frontend-shared": "workspace:*",
"i18n": "workspace:*",
"icons-subsetter": "workspace:*",
@ -55,7 +55,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"matter-js": "0.20.0",
"mediabunny": "1.35.1",
"mediabunny": "1.39.2",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@ -66,7 +66,7 @@
"qr-scanner": "1.4.2",
"rollup": "4.59.0",
"sanitize-html": "2.17.1",
"sass": "1.97.3",
"sass": "1.98.0",
"shiki": "3.23.0",
"textarea-caret": "3.1.0",
"three": "0.183.2",
@ -74,28 +74,28 @@
"tinycolor2": "1.6.0",
"v-code-diff": "1.13.1",
"vite": "7.3.1",
"vue": "3.5.29",
"vue": "3.5.30",
"wanakana": "5.3.1"
},
"devDependencies": {
"@misskey-dev/summaly": "5.2.5",
"@storybook/addon-essentials": "8.6.17",
"@storybook/addon-interactions": "8.6.17",
"@storybook/addon-links": "10.2.13",
"@storybook/addon-mdx-gfm": "8.6.17",
"@storybook/addon-storysource": "8.6.17",
"@storybook/blocks": "8.6.17",
"@storybook/components": "8.6.17",
"@storybook/core-events": "8.6.17",
"@storybook/manager-api": "8.6.17",
"@storybook/preview-api": "8.6.17",
"@storybook/react": "10.2.13",
"@storybook/react-vite": "10.2.13",
"@storybook/test": "8.6.17",
"@storybook/theming": "8.6.17",
"@storybook/types": "8.6.17",
"@storybook/vue3": "10.2.13",
"@storybook/vue3-vite": "10.2.13",
"@storybook/addon-essentials": "8.6.18",
"@storybook/addon-interactions": "8.6.18",
"@storybook/addon-links": "10.2.17",
"@storybook/addon-mdx-gfm": "8.6.18",
"@storybook/addon-storysource": "8.6.18",
"@storybook/blocks": "8.6.18",
"@storybook/components": "8.6.18",
"@storybook/core-events": "8.6.18",
"@storybook/manager-api": "8.6.18",
"@storybook/preview-api": "8.6.18",
"@storybook/react": "10.2.17",
"@storybook/react-vite": "10.2.17",
"@storybook/test": "8.6.18",
"@storybook/theming": "8.6.18",
"@storybook/types": "8.6.18",
"@storybook/vue3": "10.2.17",
"@storybook/vue3-vite": "10.2.17",
"@tabler/icons-webfont": "3.35.0",
"@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0",
@ -103,17 +103,17 @@
"@types/insert-text-at-cursor": "0.3.2",
"@types/matter-js": "0.20.2",
"@types/micromatch": "4.0.10",
"@types/node": "24.11.0",
"@types/node": "24.12.0",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0",
"@types/sanitize-html": "2.16.1",
"@types/seedrandom": "3.0.8",
"@types/textarea-caret": "3.0.4",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/coverage-v8": "4.0.18",
"@vue/compiler-core": "3.5.29",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"@vue/compiler-core": "3.5.30",
"acorn": "8.16.0",
"astring": "1.9.0",
"cross-env": "10.1.0",
@ -121,7 +121,7 @@
"eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.8.0",
"estree-walker": "3.0.3",
"happy-dom": "20.7.0",
"happy-dom": "20.8.4",
"intersection-observer": "0.12.2",
"magic-string": "0.30.21",
"micromatch": "4.0.8",
@ -134,12 +134,12 @@
"react-dom": "19.2.4",
"seedrandom": "3.0.5",
"start-server-and-test": "2.1.5",
"storybook": "10.2.13",
"storybook": "10.2.17",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.21.0",
"vite-plugin-glsl": "1.5.5",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "4.0.18",
"vitest": "4.1.0",
"vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.2.5",
"vue-eslint-parser": "10.4.0",

View file

@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkWindow
ref="window"
:initialWidth="800"
:initialHeight="500"
:canResize="true"
@closed="emit('closed')"
>

View file

@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkWindow
ref="windowEl"
:initialWidth="500"
:initialHeight="500"
:canResize="true"
:closeButton="true"
:buttonsLeft="buttonsLeft"

View file

@ -106,7 +106,9 @@ async function toggleReaction() {
reaction: props.reaction,
}).then(() => {
const emoji = customEmojisMap.get(emojiName.value);
if (emoji == null) return;
if (emoji == null && getUnicodeEmojiOrNull(props.reaction) == null) {
return;
}
noteEvents.emit(`reacted:${props.noteId}`, {
userId: me.id,
reaction: props.reaction,
@ -138,7 +140,9 @@ async function toggleReaction() {
reaction: props.reaction,
}).then(() => {
const emoji = customEmojisMap.get(emojiName.value);
if (emoji == null) return;
if (emoji == null && getUnicodeEmojiOrNull(props.reaction) == null) {
return;
}
noteEvents.emit(`reacted:${props.noteId}`, {
userId: me.id,

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@afterLeave="emit('closed')"
>
<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
<div :class="$style.body" class="_shadow" @pointerdown="onBodyPointerDown" @keydown="onKeydown">
<div :class="[$style.header, { [$style.mini]: mini }]" @contextmenu.prevent.stop="onContextmenu">
<span :class="$style.headerLeft">
<template v-if="!minimized">
@ -106,8 +106,8 @@ function capturePointer(evt: PointerEvent) {
}
const props = withDefaults(defineProps<{
initialWidth: number;
initialHeight: number | null;
initialWidth?: number | null;
initialHeight?: number | null;
canResize?: boolean;
closeButton?: boolean;
mini?: boolean;
@ -116,7 +116,7 @@ const props = withDefaults(defineProps<{
buttonsLeft?: WindowButton[];
buttonsRight?: WindowButton[];
}>(), {
initialWidth: 400,
initialWidth: null,
initialHeight: null,
canResize: false,
closeButton: true,
@ -131,6 +131,12 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const INITIAL_WINDOW_WIDTH_RATIO = 0.5;
const INITIAL_WINDOW_HEIGHT_RATIO = 0.75;
const INITIAL_WINDOW_WIDTH_MIN = 400; // applyTransormWidth
const INITIAL_WINDOW_WIDTH_MAX = 1000; //
const INITIAL_WINDOW_HEIGHT_MIN = 500; // applyTransormHeight
provide('inWindow', true);
const rootEl = useTemplateRef('rootEl');
@ -216,7 +222,7 @@ function unMinimize() {
if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px';
}
function onBodyMousedown() {
function onBodyPointerDown() {
top();
}
@ -484,8 +490,14 @@ function onBrowserResize() {
}
onMounted(() => {
applyTransformWidth(props.initialWidth);
if (props.initialHeight) applyTransformHeight(props.initialHeight);
let initialWidth = props.initialWidth;
let initialHeight = props.initialHeight;
if (initialWidth == null) initialWidth = Math.min(Math.max(Math.round(window.innerWidth * INITIAL_WINDOW_WIDTH_RATIO), INITIAL_WINDOW_WIDTH_MIN), INITIAL_WINDOW_WIDTH_MAX);
if (initialHeight == null) initialHeight = Math.max(Math.round(window.innerHeight * INITIAL_WINDOW_HEIGHT_RATIO), INITIAL_WINDOW_HEIGHT_MIN);
applyTransformWidth(initialWidth);
applyTransformHeight(initialHeight);
if (rootEl.value) {
applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));

View file

@ -150,6 +150,7 @@ import { definePage } from '@/page.js';
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
import { $i } from '@/i.js';
import { prefer } from '@/preferences.js';
import { DEFAULT_EMOJIS } from '@@/js/const.js';
const patronsWithIcon = [{
name: 'カイヤン',
@ -429,7 +430,12 @@ const containerEl = useTemplateRef('containerEl');
function iconLoaded() {
if (containerEl.value == null) return;
const emojis = prefer.s.emojiPalettes[0].emojis;
const emojis = prefer.s.emojiPalettes[0]?.emojis ?? [];
if (emojis.length < DEFAULT_EMOJIS.length) {
emojis.push(...DEFAULT_EMOJIS.slice(0, DEFAULT_EMOJIS.length - emojis.length));
}
const containerWidth = containerEl.value.offsetWidth;
for (let i = 0; i < 32; i++) {
easterEggEmojis.value.push({

View file

@ -5,6 +5,7 @@
import * as Misskey from 'misskey-js';
import { hemisphere } from '@@/js/intl-const.js';
import { DEFAULT_EMOJIS } from '@@/js/const.js';
import { prefersReducedMotion } from '@@/js/config.js';
import { definePreferences } from './manager.js';
import type { Theme } from '@/theme.js';
@ -103,7 +104,7 @@ export const PREF_DEF = definePreferences({
default: () => [{
id: genId(),
name: '',
emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
emojis: DEFAULT_EMOJIS,
}] as {
id: string;
name: string;

View file

@ -10,6 +10,10 @@ const float PI = 3.141592653589793;
const float TWO_PI = 6.283185307179586;
const float HALF_PI = 1.5707963267948966;
const float goldenAngle = 2.39996323;
const int sampleCount = 256;
const float sampleCountF = float(sampleCount);
in vec2 in_uv;
uniform sampler2D in_texture;
uniform vec2 in_resolution;
@ -18,7 +22,6 @@ uniform vec2 u_scale;
uniform bool u_ellipse;
uniform float u_angle;
uniform float u_radius;
uniform int u_samples;
out vec4 out_color;
float rand(vec2 value) {
@ -51,17 +54,7 @@ void main() {
vec4 result = vec4(0.0);
float totalSamples = 0.0;
// Make blur radius resolution-independent by using a percentage of image size
float referenceSize = min(in_resolution.x, in_resolution.y);
float normalizedRadius = u_radius / 100.0;
float radiusPx = normalizedRadius * referenceSize;
vec2 texelSize = 1.0 / in_resolution;
int sampleCount = max(u_samples, 1);
float sampleCountF = float(sampleCount);
float jitter = rand(in_uv * in_resolution);
float goldenAngle = 2.39996323;
float jitter = rand(in_uv);
// Sample in a circular pattern to avoid axis-aligned artifacts
for (int i = 0; i < sampleCount; i++) {
@ -69,15 +62,11 @@ void main() {
float radius = sqrt((fi + 0.5) / sampleCountF);
float theta = (fi + jitter) * goldenAngle;
vec2 direction = vec2(cos(theta), sin(theta));
vec2 offset = direction * (radiusPx * radius) * texelSize;
vec2 sampleUV = in_uv + offset;
if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 && sampleUV.y >= 0.0 && sampleUV.y <= 1.0) {
float weight = exp(-radius * radius * 4.0);
result += texture(in_texture, sampleUV) * weight;
totalSamples += weight;
}
vec2 offset = direction * (u_radius * radius);
float weight = exp(-radius * radius * 4.0);
result += texture(in_texture, in_uv + offset) * weight;
totalSamples += weight;
}
out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv);
out_color = result / totalSamples;
}

View file

@ -24,7 +24,6 @@ export const fn = defineImageCompositorFunction<{
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
gl.uniform1f(u.angle, params.angle / 2);
gl.uniform1f(u.radius, params.radius);
gl.uniform1i(u.samples, 256);
},
});
@ -84,10 +83,10 @@ export const uiDefinition = {
radius: {
label: i18n.ts._imageEffector._fxProps.strength,
type: 'number',
default: 10.0,
default: 0.15,
min: 0.0,
max: 20.0,
step: 0.5,
max: 0.3,
step: 0.01,
},
},
} satisfies ImageEffectorUiDefinition<typeof fn>;

View file

@ -14,12 +14,15 @@ uniform sampler2D in_texture;
uniform vec2 in_resolution;
uniform vec2 u_pos;
uniform float u_frequency;
uniform bool u_thresholdEnabled;
uniform float u_threshold;
uniform float u_outlineThickness;
uniform float u_maskSize;
uniform bool u_black;
out vec4 out_color;
float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) {
return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
}
void main() {
vec4 in_color = texture(in_texture, in_uv);
vec2 centeredUv = (in_uv - vec2(0.5, 0.5));
@ -33,16 +36,19 @@ void main() {
float noiseY = (noiseUV.y + seed) * u_frequency;
float noise = (1.0 + snoise(vec3(noiseX, noiseY, time))) / 2.0;
float t = noise;
if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0;
if (noise < u_threshold) {
out_color = in_color;
} else {
float n = remap(noise, u_threshold, 1.0, 0.0, 1.0);
// TODO: マスクの形自体も揺らぎを与える
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
out_color = vec4(
mix(in_color.r, u_black ? 0.0 : 1.0, t * mask),
mix(in_color.g, u_black ? 0.0 : 1.0, t * mask),
mix(in_color.b, u_black ? 0.0 : 1.0, t * mask),
in_color.a
);
// TODO: マスクの形自体も揺らぎを与える
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
out_color = vec4(
mix(in_color.r, n < u_outlineThickness ? 0.0 : 1.0, mask),
mix(in_color.g, n < u_outlineThickness ? 0.0 : 1.0, mask),
mix(in_color.b, n < u_outlineThickness ? 0.0 : 1.0, mask),
in_color.a
);
}
}

View file

@ -12,20 +12,17 @@ export const fn = defineImageCompositorFunction<{
x: number;
y: number;
frequency: number;
smoothing: boolean;
threshold: number;
density: number;
outlineThickness: number;
maskSize: number;
black: boolean;
}>({
shader,
main: ({ gl, u, params }) => {
gl.uniform2f(u.pos, params.x / 2, params.y / 2);
gl.uniform1f(u.frequency, params.frequency * params.frequency);
// thresholdの調整が有効な間はsmoothingが利用できない
gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1);
gl.uniform1f(u.threshold, params.threshold);
gl.uniform1f(u.threshold, 1.0 - params.density);
gl.uniform1f(u.outlineThickness, params.outlineThickness);
gl.uniform1f(u.maskSize, params.maskSize);
gl.uniform1i(u.black, params.black ? 1 : 0);
},
});
@ -56,20 +53,22 @@ export const uiDefinition = {
max: 15.0,
step: 0.1,
},
smoothing: {
label: i18n.ts._imageEffector._fxProps.zoomLinesSmoothing,
caption: i18n.ts._imageEffector._fxProps.zoomLinesSmoothingDescription,
type: 'boolean',
default: false,
},
threshold: {
label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold,
density: {
label: i18n.ts._imageEffector._fxProps.density,
type: 'number',
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
outlineThickness: {
label: i18n.ts._imageEffector._fxProps.zoomLinesOutlineThickness,
type: 'number',
default: 0.25,
min: 0.0,
max: 1.0,
step: 0.01,
},
maskSize: {
label: i18n.ts._imageEffector._fxProps.zoomLinesMaskSize,
type: 'number',
@ -78,10 +77,5 @@ export const uiDefinition = {
max: 1.0,
step: 0.01,
},
black: {
label: i18n.ts._imageEffector._fxProps.zoomLinesBlack,
type: 'boolean',
default: false,
},
},
} satisfies ImageEffectorUiDefinition<typeof fn>;

View file

@ -29,11 +29,11 @@
],
"devDependencies": {
"@types/js-yaml": "4.0.9",
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"chokidar": "5.0.0",
"esbuild": "0.27.3",
"esbuild": "0.27.4",
"execa": "9.6.1",
"nodemon": "3.1.14",
"tsx": "4.21.0"

View file

@ -13109,25 +13109,17 @@ export interface Locale extends ILocale {
*/
"centerY": string;
/**
*
*
*/
"zoomLinesSmoothing": string;
"density": string;
/**
*
*
*/
"zoomLinesSmoothingDescription": string;
/**
*
*/
"zoomLinesThreshold": string;
"zoomLinesOutlineThickness": string;
/**
*
*/
"zoomLinesMaskSize": string;
/**
*
*/
"zoomLinesBlack": string;
/**
*
*/

View file

@ -11,10 +11,10 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.11.0",
"@types/node": "24.12.0",
"@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1"
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0"
},
"dependencies": {
"@tabler/icons-webfont": "3.35.0",

View file

@ -25,11 +25,11 @@
},
"devDependencies": {
"@types/matter-js": "0.20.2",
"@types/node": "24.11.0",
"@types/node": "24.12.0",
"@types/seedrandom": "3.0.8",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"esbuild": "0.27.3",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"esbuild": "0.27.4",
"execa": "9.6.1",
"nodemon": "3.1.14"
},

View file

@ -8,14 +8,14 @@
},
"devDependencies": {
"@readme/openapi-parser": "5.5.0",
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"openapi-types": "12.1.3",
"openapi-typescript": "7.13.0",
"ts-case-convert": "2.1.0",
"tsx": "4.21.0",
"eslint": "9.39.3"
"eslint": "9.39.4"
},
"files": [
"built"

View file

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2026.3.1",
"version": "2026.3.2",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
@ -37,17 +37,17 @@
"directory": "packages/misskey-js"
},
"devDependencies": {
"@microsoft/api-extractor": "7.57.6",
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"@vitest/coverage-v8": "4.0.18",
"esbuild": "0.27.3",
"@microsoft/api-extractor": "7.57.7",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"@vitest/coverage-v8": "4.1.0",
"esbuild": "0.27.4",
"execa": "9.6.1",
"ncp": "2.0.0",
"nodemon": "3.1.14",
"tsd": "0.33.0",
"vitest": "4.0.18",
"vitest": "4.1.0",
"vitest-websocket-mock": "0.5.0"
},
"files": [

View file

@ -24,10 +24,10 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
"@types/node": "24.11.0",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@typescript-eslint/parser": "8.56.1",
"esbuild": "0.27.3",
"@types/node": "24.12.0",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"esbuild": "0.27.4",
"execa": "9.6.1",
"nodemon": "3.1.14"
},

View file

@ -10,12 +10,12 @@
},
"dependencies": {
"i18n": "workspace:*",
"esbuild": "0.27.3",
"esbuild": "0.27.4",
"idb-keyval": "6.2.2",
"misskey-js": "workspace:*"
},
"devDependencies": {
"@typescript-eslint/parser": "8.56.1",
"@typescript-eslint/parser": "8.57.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
"eslint-plugin-import": "2.32.0",
"nodemon": "3.1.14"

3978
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -9,8 +9,8 @@
"version": "1.0.0",
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.11.0",
"@vitest/coverage-v8": "4.0.18",
"@types/node": "24.12.0",
"@vitest/coverage-v8": "4.1.0",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
@ -18,7 +18,7 @@
"unified": "11.0.5",
"vite": "7.3.1",
"vite-node": "5.3.0",
"vitest": "4.0.18"
"vitest": "4.1.0"
}
},
"node_modules/@babel/helper-string-parser": {
@ -42,13 +42,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -58,9 +58,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -917,9 +917,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "24.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz",
"integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==",
"version": "24.12.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz",
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -933,29 +933,29 @@
"dev": true
},
"node_modules/@vitest/coverage-v8": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
"integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz",
"integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.0.18",
"ast-v8-to-istanbul": "^0.3.10",
"@vitest/utils": "4.1.0",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.1",
"magicast": "^0.5.2",
"obug": "^2.1.1",
"std-env": "^3.10.0",
"std-env": "^4.0.0-rc.1",
"tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.0.18",
"vitest": "4.0.18"
"@vitest/browser": "4.1.0",
"vitest": "4.1.0"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@ -964,17 +964,17 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz",
"integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"chai": "^6.2.1",
"@vitest/spy": "4.1.0",
"@vitest/utils": "4.1.0",
"chai": "^6.2.2",
"tinyrainbow": "^3.0.3"
},
"funding": {
@ -982,13 +982,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
"integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.0.18",
"@vitest/spy": "4.1.0",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@ -997,7 +997,7 @@
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^6.0.0 || ^7.0.0-0"
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
@ -1009,9 +1009,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz",
"integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1022,13 +1022,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz",
"integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.18",
"@vitest/utils": "4.1.0",
"pathe": "^2.0.3"
},
"funding": {
@ -1036,13 +1036,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz",
"integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"@vitest/pretty-format": "4.1.0",
"@vitest/utils": "4.1.0",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@ -1051,9 +1052,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz",
"integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==",
"dev": true,
"license": "MIT",
"funding": {
@ -1061,13 +1062,14 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz",
"integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"@vitest/pretty-format": "4.1.0",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.0.3"
},
"funding": {
@ -1085,15 +1087,15 @@
}
},
"node_modules/ast-v8-to-istanbul": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz",
"integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
"integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^9.0.1"
"js-tokens": "^10.0.0"
}
},
"node_modules/bail": {
@ -1135,6 +1137,13 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -1189,9 +1198,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
"dev": true,
"license": "MIT"
},
@ -1361,9 +1370,9 @@
}
},
"node_modules/js-tokens": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
@ -1400,14 +1409,14 @@
}
},
"node_modules/magicast": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz",
"integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==",
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
"integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
},
@ -2160,9 +2169,9 @@
"license": "MIT"
},
"node_modules/std-env": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
"integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
"dev": true,
"license": "MIT"
},
@ -2186,9 +2195,9 @@
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
"integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
"dev": true,
"license": "MIT",
"engines": {
@ -2213,9 +2222,9 @@
}
},
"node_modules/tinyrainbow": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
"integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
"dev": true,
"license": "MIT",
"engines": {
@ -2455,39 +2464,32 @@
"url": "https://opencollective.com/antfu"
}
},
"node_modules/vite-node/node_modules/es-module-lexer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
"dev": true,
"license": "MIT"
},
"node_modules/vitest": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz",
"integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
"@vitest/pretty-format": "4.0.18",
"@vitest/runner": "4.0.18",
"@vitest/snapshot": "4.0.18",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"@vitest/expect": "4.1.0",
"@vitest/mocker": "4.1.0",
"@vitest/pretty-format": "4.1.0",
"@vitest/runner": "4.1.0",
"@vitest/snapshot": "4.1.0",
"@vitest/spy": "4.1.0",
"@vitest/utils": "4.1.0",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
"obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
"std-env": "^4.0.0-rc.1",
"tinybench": "^2.9.0",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tinyrainbow": "^3.0.3",
"vite": "^6.0.0 || ^7.0.0",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0",
"why-is-node-running": "^2.3.0"
},
"bin": {
@ -2503,12 +2505,13 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.18",
"@vitest/browser-preview": "4.0.18",
"@vitest/browser-webdriverio": "4.0.18",
"@vitest/ui": "4.0.18",
"@vitest/browser-playwright": "4.1.0",
"@vitest/browser-preview": "4.1.0",
"@vitest/browser-webdriverio": "4.1.0",
"@vitest/ui": "4.1.0",
"happy-dom": "*",
"jsdom": "*"
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
@ -2537,6 +2540,9 @@
},
"jsdom": {
"optional": true
},
"vite": {
"optional": false
}
}
},

View file

@ -10,8 +10,8 @@
},
"devDependencies": {
"@types/mdast": "4.0.4",
"@types/node": "24.11.0",
"@vitest/coverage-v8": "4.0.18",
"@types/node": "24.12.0",
"@vitest/coverage-v8": "4.1.0",
"mdast-util-to-string": "4.0.0",
"remark": "15.0.1",
"remark-parse": "11.0.0",
@ -19,6 +19,6 @@
"unified": "11.0.5",
"vite": "7.3.1",
"vite-node": "5.3.0",
"vitest": "4.0.18"
"vitest": "4.1.0"
}
}