mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-08-01 04:45:51 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c85bfd56f | |||
| 9d37f696b4 | |||
| 2734ff6af7 | |||
| 456504cf82 | |||
| 81cea6aed5 | |||
| 2d1b7c957a | |||
| 5b6aa1496a | |||
| 259dd34b26 | |||
| cf81406fae | |||
| 42f230f223 | |||
| 2e07e50bb4 | |||
| d203e1a446 | |||
| 4988719a2e | |||
| f0380f2d1c | |||
| 130d065d0c | |||
| 7b41fddf54 | |||
| aafd8b6bf7 | |||
| 7a82c1a912 | |||
| 3c97c12e7f | |||
| 5b5a1f08e1 | |||
| c4ee95a40a | |||
| 8ea7fe0ba1 | |||
| 187b6477da | |||
| 09896fdc12 | |||
| d4cda989a2 | |||
| f93043e170 | |||
| 1c2e57d60c | |||
| 67c853104c | |||
| 986d783940 | |||
| 61cfccff37 | |||
| b161fe7adc | |||
| c3b1c8a8ff | |||
| bc2b512be6 | |||
| 7fe9574897 | |||
| 8549f71656 | |||
| ae4487fa69 | |||
| 0a75d6fcf1 | |||
| d3bb24d851 | |||
| 9aea7363ce |
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Request release review
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
reply:
|
||||
if: github.event.comment.body == '/request-release-review'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Reply
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const body = `To dev team (@misskey-dev/dev):
|
||||
|
||||
リリースが提案されています :rocket:
|
||||
|
||||
GOの場合はapprove、NO GOの場合はその旨コメントをお願いいたします。
|
||||
|
||||
判断にあたって考慮すべき観点は、
|
||||
|
||||
- やり残したことはないか?
|
||||
- CHANGELOGは過不足ないか?
|
||||
- バージョンに問題はないか?(月跨いでいるのに更新忘れているなど)
|
||||
- 再考すべき仕様・実装はないか?
|
||||
- ベータ版を検証したサーバーから不具合の報告等は上がってないか?
|
||||
- (セキュリティの修正や重要なバグ修正などのため)リリースを急いだ方が良いか?そうではないか?
|
||||
- Actionsが落ちていないか?
|
||||
|
||||
などが挙げられます。
|
||||
|
||||
ご協力ありがとうございます :sparkles:
|
||||
`
|
||||
|
||||
const issue_number = context.payload.issue ? context.payload.issue.number : (context.payload.pull_request && context.payload.pull_request.number)
|
||||
if (!issue_number) {
|
||||
console.log('No issue or PR number found in payload; skipping')
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number,
|
||||
body,
|
||||
})
|
||||
}
|
||||
@@ -37,7 +37,7 @@ 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.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Get current date
|
||||
id: current-date
|
||||
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Get current date
|
||||
id: current-date
|
||||
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Get current date
|
||||
id: current-date
|
||||
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
#- uses: browser-actions/setup-firefox@latest
|
||||
# if: ${{ matrix.browser == 'firefox' }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
||||
+7
-1
@@ -7,18 +7,24 @@
|
||||
|
||||
### Client
|
||||
- Enhance: デッキのメインカラムのヘッダをクリックしてページ上部/下部にスクロールできるように
|
||||
- Enhance: 下書き/予約投稿一覧は投稿フォームのアカウントメニュー内に移動し、下書き保存は「...」メニュー内に移動されました
|
||||
- Fix: カスタム絵文字画面(beta)のaliasesで使用される区切り文字が一致していないのを修正 #15614
|
||||
- Fix: バナー画像の幅が表示領域と一致していない問題を修正
|
||||
- Fix: 一部のブラウザでバナー画像が上下中央に表示されない問題を修正
|
||||
- Fix: ナビゲーションバーの設定で削除した項目をその場で再追加できない問題を修正
|
||||
- Fix: ロールポリシーによりダイレクトメッセージが無効化されている際のデッキのダイレクトメッセージカラムの挙動を改善
|
||||
- Fix: 画像のマスクでタッチ操作が不安定な問題を修正
|
||||
- Fix: ウォーターマークの各種挙動修正
|
||||
- ウォーターマークを回転させると歪む問題を修正
|
||||
- ウォーターマークを敷き詰めると上下左右反転した画像/文字が表示される問題を修正
|
||||
- ウォーターマークを回転させた際に画面からはみ出た部分を考慮できるように
|
||||
- Fix: 投票が終了した後に投票結果が正しく表示されない問題を修正
|
||||
- Fix: ダークモードの同期が機能しない場合がある問題を修正
|
||||
- Fix: iOSで動画の圧縮を行うと音声トラックが失われる問題を修正
|
||||
|
||||
### Server
|
||||
-
|
||||
- Enhance: 管理者/モデレーターはファイルのアップロード制限をバイパスするように
|
||||
- Enhance: セキュリティの向上
|
||||
|
||||
## 2025.10.0
|
||||
|
||||
|
||||
@@ -3201,6 +3201,7 @@ _watermarkEditor:
|
||||
title: "Editar la marca d'aigua "
|
||||
cover: "Cobrir-ho tot"
|
||||
repeat: "Repetir"
|
||||
preserveBoundingRect: "Ajusta'l per evitar que sobresortir en fer la rotació "
|
||||
opacity: "Opacitat"
|
||||
scale: "Mida"
|
||||
text: "Text"
|
||||
|
||||
@@ -775,6 +775,7 @@ lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", yo
|
||||
alwaysMarkSensitive: "Mark as sensitive by default"
|
||||
loadRawImages: "Load original images instead of showing thumbnails"
|
||||
disableShowingAnimatedImages: "Don't play animated images"
|
||||
disableShowingAnimatedImages_caption: "If animated images do not play even if this setting is disabled, it may be due to browser or OS accessibility settings, power-saving settings, or similar factors."
|
||||
highlightSensitiveMedia: "Highlight sensitive media"
|
||||
verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
|
||||
notSet: "Not set"
|
||||
@@ -1171,6 +1172,7 @@ installed: "Installed"
|
||||
branding: "Branding"
|
||||
enableServerMachineStats: "Publish server hardware stats"
|
||||
enableIdenticonGeneration: "Enable user identicon generation"
|
||||
showRoleBadgesOfRemoteUsers: "Display the role badges assigned to remote users"
|
||||
turnOffToImprovePerformance: "Turning this off can increase performance."
|
||||
createInviteCode: "Generate invite"
|
||||
createWithOptions: "Generate with options"
|
||||
@@ -3199,6 +3201,7 @@ _watermarkEditor:
|
||||
title: "Edit Watermark"
|
||||
cover: "Cover everything"
|
||||
repeat: "spread all over"
|
||||
preserveBoundingRect: "Adjust to prevent overflow when rotating"
|
||||
opacity: "Opacity"
|
||||
scale: "Size"
|
||||
text: "Text"
|
||||
|
||||
+2
-1
@@ -1172,7 +1172,7 @@ installed: "Instalado"
|
||||
branding: "Marca"
|
||||
enableServerMachineStats: "Publicar estadísticas de hardware del servidor"
|
||||
enableIdenticonGeneration: "Activar generación de identicon por usuario"
|
||||
showRoleBadgesOfRemoteUsers: "Mostrar insignias de rol de usuarios remotos"
|
||||
showRoleBadgesOfRemoteUsers: "Mostrar la insignia de rol asignada a los usuarios remotos."
|
||||
turnOffToImprovePerformance: "Desactivar esto puede aumentar el rendimiento."
|
||||
createInviteCode: "Generar invitación"
|
||||
createWithOptions: "Generar con opciones"
|
||||
@@ -3201,6 +3201,7 @@ _watermarkEditor:
|
||||
title: "Editar la marca de agua"
|
||||
cover: "Cubrir todo"
|
||||
repeat: "Repetir"
|
||||
preserveBoundingRect: "Ajuste para evitar que se desborde al rotar."
|
||||
opacity: "Opacidad"
|
||||
scale: "Tamaño"
|
||||
text: "Texto"
|
||||
|
||||
@@ -298,6 +298,7 @@ uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesa
|
||||
explore: "Jelajahi"
|
||||
messageRead: "Telah dibaca"
|
||||
noMoreHistory: "Tidak ada sejarah lagi"
|
||||
startChat: "Kirim pesan"
|
||||
nUsersRead: "Dibaca oleh {n}"
|
||||
agreeTo: "Saya setuju kepada {0}"
|
||||
agree: "Setuju"
|
||||
@@ -510,6 +511,7 @@ emojiStyle: "Gaya emoji"
|
||||
native: "Native"
|
||||
menuStyle: "Gaya menu"
|
||||
style: "Gaya"
|
||||
popup: "Pemunculan"
|
||||
showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
|
||||
showReactionsCount: "Lihat jumlah reaksi dalam catatan"
|
||||
noHistory: "Tidak ada riwayat"
|
||||
@@ -566,6 +568,7 @@ showFixedPostForm: "Tampilkan form posting di atas lini masa"
|
||||
showFixedPostFormInChannel: "Tampilkan form posting di atas lini masa (Kanal)"
|
||||
withRepliesByDefaultForNewlyFollowed: "Termasuk balasan dari pengguna baru yang diikuti pada lini masa secara bawaan"
|
||||
newNoteRecived: "Kamu mendapat catatan baru"
|
||||
newNote: "Catatan baru"
|
||||
sounds: "Bunyi"
|
||||
sound: "Bunyi"
|
||||
listen: "Dengarkan"
|
||||
@@ -1028,6 +1031,7 @@ permissionDeniedError: "Operasi ditolak"
|
||||
permissionDeniedErrorDescription: "Akun ini tidak memiliki izin untuk melakukan aksi ini."
|
||||
preset: "Prasetel"
|
||||
selectFromPresets: "Pilih dari prasetel"
|
||||
custom: "Penyesuaian"
|
||||
achievements: "Pencapaian"
|
||||
gotInvalidResponseError: "Respon peladen tidak valid"
|
||||
gotInvalidResponseErrorDescription: "Peladen tidak dapat dijangkau atau sedang dalam perawatan. Mohon coba lagi nanti."
|
||||
@@ -1110,6 +1114,7 @@ preservedUsernamesDescription: "Daftar nama pengguna yang dicadangkan dipisah de
|
||||
createNoteFromTheFile: "Buat catatan dari berkas ini"
|
||||
archive: "Arsipkan"
|
||||
archived: "Diarsipkan"
|
||||
unarchive: "Batalkan pengarsipan"
|
||||
channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?"
|
||||
channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi."
|
||||
thisChannelArchived: "Kanal ini telah diarsipkan."
|
||||
@@ -1251,6 +1256,7 @@ noDescription: "Tidak ada deskripsi"
|
||||
alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
|
||||
inquiry: "Hubungi kami"
|
||||
tryAgain: "Silahkan coba lagi."
|
||||
sensitiveMediaRevealConfirm: "Media sensitif. Apakah ingin melihat?"
|
||||
createdLists: "Senarai yang dibuat"
|
||||
createdAntennas: "Antena yang dibuat"
|
||||
fromX: "Dari {x}"
|
||||
@@ -1258,21 +1264,43 @@ noteOfThisUser: "Catatan oleh pengguna ini"
|
||||
clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan."
|
||||
performance: "Kinerja"
|
||||
modified: "Diubah"
|
||||
discard: "Buang"
|
||||
thereAreNChanges: "Ada {n} perubahan"
|
||||
signinWithPasskey: "Masuk dengan kunci sandi"
|
||||
unknownWebAuthnKey: "Kunci sandi tidak terdaftar."
|
||||
passkeyVerificationFailed: "Verifikasi kunci sandi gagal."
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "Verifikasi kunci sandi berhasil, namun pemasukan tanpa sandi dinonaktifkan."
|
||||
messageToFollower: "Pesan kepada pengikut"
|
||||
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
||||
lockdown: "Kuncitara"
|
||||
noName: "Tidak ada nama"
|
||||
skip: "Lewati"
|
||||
paste: "Tempel"
|
||||
emojiPalette: "Palet emoji"
|
||||
postForm: "Buat catatan"
|
||||
information: "Informasi"
|
||||
chat: "Obrolan"
|
||||
directMessage: "Obrolan pengguna"
|
||||
right: "Kanan"
|
||||
bottom: "Bawah"
|
||||
top: "Atas"
|
||||
advice: "Saran"
|
||||
inMinutes: "menit"
|
||||
inDays: "hari"
|
||||
widgets: "Widget"
|
||||
_chat:
|
||||
invitations: "Undang"
|
||||
history: "Riwayat obrolan"
|
||||
noHistory: "Tidak ada riwayat"
|
||||
members: "Anggota"
|
||||
home: "Beranda"
|
||||
send: "Kirim"
|
||||
chatWithThisUser: "Obrolan pengguna"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
contentsUpdateFrequency: "Frekuensi pembaruan konten"
|
||||
_preferencesProfile:
|
||||
profileName: "Nama profil"
|
||||
_abuseUserReport:
|
||||
accept: "Setuju"
|
||||
reject: "Tolak"
|
||||
@@ -1966,6 +1994,7 @@ _sfx:
|
||||
noteMy: "Catatan (Saya)"
|
||||
notification: "Notifikasi"
|
||||
reaction: "Ketika memilih reaksi"
|
||||
chatMessage: "Obrolan pengguna"
|
||||
_soundSettings:
|
||||
driveFile: "Menggunakan berkas audio dalam Drive"
|
||||
driveFileWarn: "Pilih berkas audio dari Drive"
|
||||
@@ -2168,6 +2197,7 @@ _widgets:
|
||||
chooseList: "Pilih daftar"
|
||||
clicker: "Pengeklik"
|
||||
birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini"
|
||||
chat: "Obrolan pengguna"
|
||||
_cw:
|
||||
hide: "Sembunyikan"
|
||||
show: "Lihat konten"
|
||||
@@ -2416,6 +2446,7 @@ _deck:
|
||||
mentions: "Sebutan"
|
||||
direct: "Langsung"
|
||||
roleTimeline: "Lini masa peran"
|
||||
chat: "Obrolan pengguna"
|
||||
_dialog:
|
||||
charactersExceeded: "Kamu telah melebihi batas karakter maksimum! Saat ini pada {current} dari {max}."
|
||||
charactersBelow: "Kamu berada di bawah batas minimum karakter! Saat ini pada {current} dari {min}."
|
||||
|
||||
@@ -775,6 +775,7 @@ lockedAccountInfo: "A meno che non imposti la visibilità delle tue note su \"So
|
||||
alwaysMarkSensitive: "Segnare automaticamente come espliciti gli allegati"
|
||||
loadRawImages: "Visualizza le intere immagini allegate invece delle miniature."
|
||||
disableShowingAnimatedImages: "Disabilitare le immagini animate"
|
||||
disableShowingAnimatedImages_caption: "L'attivazione delle animazioni immagini potrebbe interferire sull'accessibilità e sul risparmio energetico nel dispositivo."
|
||||
highlightSensitiveMedia: "Evidenzia i media espliciti"
|
||||
verificationEmailSent: "Una mail di verifica è stata inviata. Si prega di accedere al collegamento per compiere la verifica."
|
||||
notSet: "Non impostato"
|
||||
@@ -1171,6 +1172,7 @@ installed: "Installazione avvenuta"
|
||||
branding: "Branding"
|
||||
enableServerMachineStats: "Pubblicare le informazioni sul server"
|
||||
enableIdenticonGeneration: "Generazione automatica delle Identicon"
|
||||
showRoleBadgesOfRemoteUsers: "Visualizza i badge per i ruoli concessi ai profili remoti"
|
||||
turnOffToImprovePerformance: "Disattiva, per migliorare le prestazioni"
|
||||
createInviteCode: "Genera codice di invito"
|
||||
createWithOptions: "Genera con opzioni"
|
||||
|
||||
@@ -3201,6 +3201,7 @@ _watermarkEditor:
|
||||
title: "워터마크 편집"
|
||||
cover: "전체에 붙이기"
|
||||
repeat: "전면에 깔기"
|
||||
preserveBoundingRect: "회전 시 빠져나오지 않도록 조정"
|
||||
opacity: "불투명도"
|
||||
scale: "크기"
|
||||
text: "텍스트"
|
||||
|
||||
+24
-23
@@ -106,8 +106,8 @@ privacy: "隐私"
|
||||
makeFollowManuallyApprove: "关注请求需要批准"
|
||||
defaultNoteVisibility: "默认可见性"
|
||||
follow: "关注"
|
||||
followRequest: "关注申请"
|
||||
followRequests: "关注申请"
|
||||
followRequest: "申请关注"
|
||||
followRequests: "关注请求"
|
||||
unfollow: "取消关注"
|
||||
followRequestPending: "关注请求待批准"
|
||||
enterEmoji: "输入表情符号"
|
||||
@@ -540,7 +540,7 @@ regenerate: "重新生成"
|
||||
fontSize: "字体大小"
|
||||
mediaListWithOneImageAppearance: "仅一张图片的媒体列表高度"
|
||||
limitTo: "上限为 {x}"
|
||||
noFollowRequests: "没有关注申请"
|
||||
noFollowRequests: "没有关注请求"
|
||||
openImageInNewTab: "在新标签页中打开图片"
|
||||
dashboard: "管理面板"
|
||||
local: "本地"
|
||||
@@ -873,12 +873,12 @@ noMaintainerInformationWarning: "尚未设置管理员信息。"
|
||||
noInquiryUrlWarning: "尚未设置联络地址。"
|
||||
noBotProtectionWarning: "尚未设置 Bot 防御。"
|
||||
configure: "设置"
|
||||
postToGallery: "创建新相册"
|
||||
postToGallery: "创建新图集"
|
||||
postToHashtag: "投稿到这个标签"
|
||||
gallery: "相册"
|
||||
gallery: "图集"
|
||||
recentPosts: "最新发布"
|
||||
popularPosts: "热门投稿"
|
||||
shareWithNote: "分享到贴文"
|
||||
shareWithNote: "分享到帖文"
|
||||
ads: "广告"
|
||||
expiration: "截止时间"
|
||||
startingperiod: "开始时间"
|
||||
@@ -1031,7 +1031,7 @@ tools: "工具"
|
||||
cannotLoad: "无法加载"
|
||||
numberOfProfileView: "个人资料展示次数"
|
||||
like: "点赞!"
|
||||
unlike: "取消赞"
|
||||
unlike: "取消喜欢"
|
||||
numberOfLikes: "点赞数"
|
||||
show: "显示"
|
||||
neverShow: "不再显示"
|
||||
@@ -1067,7 +1067,7 @@ thisPostMayBeAnnoyingHome: "发到首页"
|
||||
thisPostMayBeAnnoyingCancel: "取消"
|
||||
thisPostMayBeAnnoyingIgnore: "就这样发布"
|
||||
collapseRenotes: "省略显示已经看过的转发内容"
|
||||
collapseRenotesDescription: "将回应过或转贴过的贴子折叠表示。"
|
||||
collapseRenotesDescription: "折叠显示回应或转发过的帖文。"
|
||||
internalServerError: "内部服务器错误"
|
||||
internalServerErrorDescription: "内部服务器发生了预期外的错误"
|
||||
copyErrorInfo: "复制错误信息"
|
||||
@@ -1108,7 +1108,7 @@ retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
|
||||
enableChartsForRemoteUser: "生成远程用户的图表"
|
||||
enableChartsForFederatedInstances: "生成远程服务器的图表"
|
||||
enableStatsForFederatedInstances: "获取远程服务器的信息"
|
||||
showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
|
||||
showClipButtonInNoteFooter: "在帖文下方显示便签按钮"
|
||||
reactionsDisplaySize: "回应显示大小"
|
||||
limitWidthOfReaction: "限制回应的最大宽度,并将其缩小显示"
|
||||
noteIdOrUrl: "帖子 ID 或 URL"
|
||||
@@ -1146,7 +1146,7 @@ archive: "归档"
|
||||
archived: "已归档"
|
||||
unarchive: "取消归档"
|
||||
channelArchiveConfirmTitle: "要将 {name} 归档吗?"
|
||||
channelArchiveConfirmDescription: "归档后,在频道列表与搜索结果中不会显示,也无法发布新的贴文。"
|
||||
channelArchiveConfirmDescription: "归档后,不会在频道列表与搜索结果中显示,也无法发布新的帖文。"
|
||||
thisChannelArchived: "该频道已被归档。"
|
||||
displayOfNote: "显示帖子"
|
||||
initialAccountSetting: "初始设定"
|
||||
@@ -1803,7 +1803,7 @@ _achievements:
|
||||
_login500:
|
||||
title: "老熟人Ⅰ"
|
||||
description: "累计登录 500 天"
|
||||
flavor: "诸君,我喜欢贴文"
|
||||
flavor: "诸君,我喜欢帖文"
|
||||
_login600:
|
||||
title: "老熟人Ⅱ"
|
||||
description: "累计登录 600 天"
|
||||
@@ -1822,7 +1822,7 @@ _achievements:
|
||||
flavor: "感谢您使用 Misskey!"
|
||||
_noteClipped1:
|
||||
title: "忍不住要收藏到便签"
|
||||
description: "第一次将贴文贴进便签"
|
||||
description: "第一次将帖子加入便签"
|
||||
_noteFavorited1:
|
||||
title: "观星者"
|
||||
description: "第一次将帖子加入收藏"
|
||||
@@ -2028,7 +2028,7 @@ _role:
|
||||
wordMuteMax: "屏蔽词的字数限制"
|
||||
webhookMax: "Webhook 创建数量限制"
|
||||
clipMax: "便签创建数量限制"
|
||||
noteEachClipsMax: "单个便签内的贴文数量限制"
|
||||
noteEachClipsMax: "便签内贴文的最大数量"
|
||||
userListMax: "用户列表创建数量限制"
|
||||
userEachUserListsMax: "单个用户列表内用户数量限制"
|
||||
rateLimitFactor: "速率限制"
|
||||
@@ -2114,8 +2114,8 @@ _forgotPassword:
|
||||
ifNoEmail: "如果您没有设置电子邮件地址,请联系管理员。"
|
||||
contactAdmin: "该服务器不支持发送电子邮件。如果您想重设密码,请联系管理员。"
|
||||
_gallery:
|
||||
my: "我的相册"
|
||||
liked: "喜欢的相册"
|
||||
my: "我的图集"
|
||||
liked: "喜欢的图集"
|
||||
like: "喜欢!"
|
||||
unlike: "取消喜欢"
|
||||
_email:
|
||||
@@ -2368,10 +2368,10 @@ _permissions:
|
||||
"write:user-groups": "编辑用户组"
|
||||
"read:channels": "查看频道"
|
||||
"write:channels": "管理频道"
|
||||
"read:gallery": "浏览相册"
|
||||
"write:gallery": "编辑相册"
|
||||
"read:gallery-likes": "浏览喜欢的相册"
|
||||
"write:gallery-likes": "管理喜欢的相册"
|
||||
"read:gallery": "浏览图集"
|
||||
"write:gallery": "编辑图集"
|
||||
"read:gallery-likes": "浏览喜欢的图集"
|
||||
"write:gallery-likes": "管理喜欢的图集"
|
||||
"read:flash": "查看 Play"
|
||||
"write:flash": "编辑 Play"
|
||||
"read:flash-likes": "查看 Play 的点赞"
|
||||
@@ -2778,7 +2778,7 @@ _webhookSettings:
|
||||
_events:
|
||||
follow: "关注时"
|
||||
followed: "被关注时"
|
||||
note: "发布贴文时"
|
||||
note: "发布帖文时"
|
||||
reply: "收到回复时"
|
||||
renote: "被转发时"
|
||||
reaction: "被回应时"
|
||||
@@ -2854,7 +2854,7 @@ _moderationLogTypes:
|
||||
deleteAccount: "删除了账户"
|
||||
deletePage: "删除了页面"
|
||||
deleteFlash: "删除了 Play"
|
||||
deleteGalleryPost: "删除相册内容"
|
||||
deleteGalleryPost: "删除图集内容"
|
||||
deleteChatRoom: "删除聊天室"
|
||||
updateProxyAccountDescription: "更新代理账户的简介"
|
||||
_fileViewer:
|
||||
@@ -3074,8 +3074,8 @@ _selfXssPrevention:
|
||||
description2: "如果不能完全理解将要粘贴的内容,%c 请立即停止操作并关闭这个窗口。"
|
||||
description3: "详情请看这里。{link}"
|
||||
_followRequest:
|
||||
recieved: "已收到申请"
|
||||
sent: "已发送申请"
|
||||
recieved: "收到的请求"
|
||||
sent: "已发送的请求"
|
||||
_remoteLookupErrors:
|
||||
_federationNotAllowed:
|
||||
title: "无法与此服务器通信"
|
||||
@@ -3201,6 +3201,7 @@ _watermarkEditor:
|
||||
title: "编辑水印"
|
||||
cover: "覆盖全体"
|
||||
repeat: "平铺"
|
||||
preserveBoundingRect: "调整为旋转时不超出范围"
|
||||
opacity: "不透明度"
|
||||
scale: "大小"
|
||||
text: "文本"
|
||||
|
||||
@@ -3200,6 +3200,7 @@ _watermarkEditor:
|
||||
title: "編輯浮水印"
|
||||
cover: "覆蓋整體"
|
||||
repeat: "佈局"
|
||||
preserveBoundingRect: "調整使其在旋轉時不會突出"
|
||||
opacity: "透明度"
|
||||
scale: "大小"
|
||||
text: "文字"
|
||||
|
||||
+8
-8
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.10.1-alpha.4",
|
||||
"version": "2025.10.1-beta.4",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"packageManager": "pnpm@10.18.2",
|
||||
"workspaces": [
|
||||
"packages/frontend-shared",
|
||||
"packages/frontend",
|
||||
@@ -68,15 +68,15 @@
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.3.0",
|
||||
"eslint": "9.36.0",
|
||||
"cypress": "15.4.0",
|
||||
"eslint": "9.37.0",
|
||||
"globals": "16.4.0",
|
||||
"ncp": "2.0.0",
|
||||
"pnpm": "10.17.1",
|
||||
"pnpm": "10.18.2",
|
||||
"start-server-and-test": "2.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.901.0",
|
||||
"@aws-sdk/lib-storage": "3.901.0",
|
||||
"@aws-sdk/client-s3": "3.908.0",
|
||||
"@aws-sdk/lib-storage": "3.908.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.3",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
@@ -103,7 +103,7 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.3",
|
||||
"bullmq": "5.59.0",
|
||||
"bullmq": "5.61.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.6.2",
|
||||
@@ -120,12 +120,12 @@
|
||||
"file-type": "19.6.0",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.4",
|
||||
"got": "14.4.9",
|
||||
"got": "14.5.0",
|
||||
"happy-dom": "20.0.7",
|
||||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
"ioredis": "5.8.0",
|
||||
"ioredis": "5.8.1",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.2.0",
|
||||
"is-svg": "5.1.0",
|
||||
@@ -145,7 +145,7 @@
|
||||
"nanoid": "5.1.6",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.10.1",
|
||||
"nodemailer": "7.0.9",
|
||||
"nsfwjs": "4.2.0",
|
||||
"oauth": "0.10.2",
|
||||
"oauth2orize": "1.12.0",
|
||||
@@ -171,11 +171,11 @@
|
||||
"sanitize-html": "2.17.0",
|
||||
"secure-json-parse": "3.0.2",
|
||||
"sharp": "0.33.5",
|
||||
"semver": "7.7.2",
|
||||
"semver": "7.7.3",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.27.10",
|
||||
"systeminformation": "5.27.11",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
@@ -210,7 +210,7 @@
|
||||
"@types/jsrsasign": "10.5.15",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/nodemailer": "6.4.20",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
@@ -231,8 +231,8 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
|
||||
@@ -517,40 +517,43 @@ export class DriveService {
|
||||
this.registerLogger.debug(`ADD DRIVE FILE: user ${user?.id ?? 'not set'}, name ${detectedName}, tmp ${path}`);
|
||||
|
||||
//#region Check drive usage and mime type
|
||||
if (user && !isLink) {
|
||||
if (user != null && !isLink) {
|
||||
const isLocalUser = this.userEntityService.isLocalUser(user);
|
||||
const policies = await this.roleService.getUserPolicies(user.id);
|
||||
const isModerator = isLocalUser ? await this.roleService.isModerator(user) : false;
|
||||
if (!isModerator) {
|
||||
const policies = await this.roleService.getUserPolicies(user.id);
|
||||
|
||||
const allowedMimeTypes = policies.uploadableFileTypes;
|
||||
const isAllowed = allowedMimeTypes.some((mimeType) => {
|
||||
if (mimeType === '*' || mimeType === '*/*') return true;
|
||||
if (mimeType.endsWith('/*')) return info.type.mime.startsWith(mimeType.slice(0, -1));
|
||||
return info.type.mime === mimeType;
|
||||
});
|
||||
if (!isAllowed) {
|
||||
throw new IdentifiableError('bd71c601-f9b0-4808-9137-a330647ced9b', `Unallowed file type: ${info.type.mime}`);
|
||||
}
|
||||
|
||||
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
||||
const maxFileSize = 1024 * 1024 * policies.maxFileSizeMb;
|
||||
|
||||
if (maxFileSize < info.size) {
|
||||
if (isLocalUser) {
|
||||
throw new IdentifiableError('f9e4e5f3-4df4-40b5-b400-f236945f7073', 'Max file size exceeded.');
|
||||
const allowedMimeTypes = policies.uploadableFileTypes;
|
||||
const isAllowed = allowedMimeTypes.some((mimeType) => {
|
||||
if (mimeType === '*' || mimeType === '*/*') return true;
|
||||
if (mimeType.endsWith('/*')) return info.type.mime.startsWith(mimeType.slice(0, -1));
|
||||
return info.type.mime === mimeType;
|
||||
});
|
||||
if (!isAllowed) {
|
||||
throw new IdentifiableError('bd71c601-f9b0-4808-9137-a330647ced9b', `Unallowed file type: ${info.type.mime}`);
|
||||
}
|
||||
}
|
||||
|
||||
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
||||
const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
|
||||
const maxFileSize = 1024 * 1024 * policies.maxFileSizeMb;
|
||||
|
||||
this.registerLogger.debug('drive capacity override applied');
|
||||
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||
|
||||
// If usage limit exceeded
|
||||
if (driveCapacity < usage + info.size) {
|
||||
if (isLocalUser) {
|
||||
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
|
||||
if (maxFileSize < info.size) {
|
||||
if (isLocalUser) {
|
||||
throw new IdentifiableError('f9e4e5f3-4df4-40b5-b400-f236945f7073', 'Max file size exceeded.');
|
||||
}
|
||||
}
|
||||
|
||||
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
|
||||
|
||||
this.registerLogger.debug('drive capacity override applied');
|
||||
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||
|
||||
// If usage limit exceeded
|
||||
if (driveCapacity < usage + info.size) {
|
||||
if (isLocalUser) {
|
||||
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
|
||||
}
|
||||
await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size);
|
||||
}
|
||||
await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"rollup": "4.52.3",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"rollup": "4.52.4",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": "3.0.3",
|
||||
"magic-string": "0.30.19",
|
||||
"vite": "7.1.7"
|
||||
"vite": "7.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-js": "workspace:*",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.52.3",
|
||||
"rollup": "4.52.4",
|
||||
"sass": "1.93.2",
|
||||
"shiki": "3.13.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
@@ -34,7 +34,7 @@
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.3",
|
||||
"uuid": "13.0.0",
|
||||
"vite": "7.1.7",
|
||||
"vite": "7.1.9",
|
||||
"vue": "3.5.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -43,12 +43,12 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/runtime-core": "3.5.22",
|
||||
"acorn": "8.15.0",
|
||||
@@ -59,14 +59,14 @@
|
||||
"happy-dom": "20.0.7",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.11.3",
|
||||
"msw": "2.11.5",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"start-server-and-test": "2.1.2",
|
||||
"tsx": "4.20.6",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.1.0",
|
||||
"vue-component-type-helpers": "3.1.1",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.1.0"
|
||||
"vue-tsc": "3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,18 @@ for (let i = 0; i < emojilist.length; i++) {
|
||||
|
||||
export const emojiCharByCategory = _charGroupByCategory;
|
||||
|
||||
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
|
||||
export function getUnicodeEmojiOrNull(char: string): UnicodeEmojiDef | null {
|
||||
// Colorize it because emojilist.json assumes that
|
||||
return unicodeEmojisMap.get(colorizeEmoji(char))
|
||||
// カラースタイル絵文字がjsonに無い場合はテキストスタイル絵文字にフォールバックする
|
||||
?? unicodeEmojisMap.get(char)
|
||||
// それでも見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
|
||||
?? char;
|
||||
// それでも見つからない場合はnullを返す
|
||||
?? null;
|
||||
}
|
||||
|
||||
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
|
||||
// 絵文字が見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
|
||||
return getUnicodeEmojiOrNull(char) ?? char;
|
||||
}
|
||||
|
||||
export function isSupportedEmoji(char: string): boolean {
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"esbuild": "0.25.10",
|
||||
"eslint-plugin-vue": "10.5.0",
|
||||
"nodemon": "3.1.10",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.17.0",
|
||||
"@sentry/vue": "10.19.0",
|
||||
"@syuilo/aiscript": "1.1.2",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"broadcast-channel": "7.1.0",
|
||||
"buraha": "0.0.1",
|
||||
"canvas-confetti": "1.9.3",
|
||||
"chart.js": "4.5.0",
|
||||
"chart.js": "4.5.1",
|
||||
"chartjs-adapter-date-fns": "3.0.0",
|
||||
"chartjs-chart-matrix": "3.0.0",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
@@ -57,7 +57,7 @@
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.19",
|
||||
"matter-js": "0.20.0",
|
||||
"mediabunny": "1.21.1",
|
||||
"mediabunny": "1.23.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -66,7 +66,7 @@
|
||||
"punycode.js": "2.3.1",
|
||||
"qr-code-styling": "1.9.2",
|
||||
"qr-scanner": "1.4.2",
|
||||
"rollup": "4.52.3",
|
||||
"rollup": "4.52.4",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.93.2",
|
||||
"shiki": "3.13.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.3",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.1.7",
|
||||
"vite": "7.1.9",
|
||||
"vue": "3.5.22",
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
@@ -109,21 +109,21 @@
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/compiler-core": "3.5.22",
|
||||
"@vue/runtime-core": "3.5.22",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.3.0",
|
||||
"cypress": "15.4.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.5.0",
|
||||
"fast-glob": "3.3.3",
|
||||
@@ -131,8 +131,8 @@
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.0.3",
|
||||
"msw": "2.11.3",
|
||||
"msw-storybook-addon": "2.0.5",
|
||||
"msw": "2.11.5",
|
||||
"msw-storybook-addon": "2.0.6",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.2.0",
|
||||
@@ -146,8 +146,8 @@
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.1.0",
|
||||
"vue-component-type-helpers": "3.1.1",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.1.0"
|
||||
"vue-tsc": "3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +211,13 @@ export async function switchAccount(host: string, id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function openAccountMenu(opts: {
|
||||
export async function getAccountMenu(opts: {
|
||||
includeCurrentAccount?: boolean;
|
||||
withExtraOperation: boolean;
|
||||
active?: Misskey.entities.User['id'];
|
||||
onChoose?: (account: Misskey.entities.MeDetailed) => void;
|
||||
}, ev: MouseEvent) {
|
||||
if (!$i) return;
|
||||
}) {
|
||||
if ($i == null) throw new Error('No current account');
|
||||
const me = $i;
|
||||
|
||||
const callback = opts.onChoose;
|
||||
@@ -338,9 +338,7 @@ export async function openAccountMenu(opts: {
|
||||
menuItems.push(...accountItems);
|
||||
}
|
||||
|
||||
popupMenu(menuItems, ev.currentTarget ?? ev.target, {
|
||||
align: 'left',
|
||||
});
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> {
|
||||
|
||||
@@ -69,9 +69,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
if (lastVersion !== version) {
|
||||
miLocalStorage.setItem('lastVersion', version);
|
||||
|
||||
// テーマリビルドするため
|
||||
miLocalStorage.removeItem('theme');
|
||||
|
||||
try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
||||
if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
|
||||
isClientUpdated = true;
|
||||
@@ -176,7 +173,7 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
})();
|
||||
|
||||
applyTheme(theme);
|
||||
}, { immediate: isSafeMode || miLocalStorage.getItem('theme') == null });
|
||||
}, { immediate: true });
|
||||
|
||||
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||
|
||||
@@ -195,14 +192,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!isSafeMode) {
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
vec3 mod289(vec3 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec2 mod289(vec2 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec3 permute(vec3 x) {
|
||||
return mod289(((x*34.0)+1.0)*x);
|
||||
}
|
||||
|
||||
float snoise(vec2 v) {
|
||||
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
|
||||
|
||||
vec2 i = floor(v + dot(v, C.yy));
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
|
||||
vec2 i1;
|
||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
|
||||
i = mod289(i);
|
||||
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
|
||||
|
||||
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
||||
m = m*m;
|
||||
m = m*m;
|
||||
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
|
||||
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
|
||||
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
|
||||
in vec2 in_uv;
|
||||
uniform float u_time;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_spread;
|
||||
uniform float u_speed;
|
||||
uniform float u_warp;
|
||||
uniform float u_focus;
|
||||
uniform float u_itensity;
|
||||
out vec4 out_color;
|
||||
|
||||
float circle(in vec2 _pos, in vec2 _origin, in float _radius) {
|
||||
float SPREAD = 0.7 * u_spread;
|
||||
float SPEED = 0.00055 * u_speed;
|
||||
float WARP = 1.5 * u_warp;
|
||||
float FOCUS = 1.15 * u_focus;
|
||||
|
||||
vec2 dist = _pos - _origin;
|
||||
|
||||
float distortion = snoise(vec2(
|
||||
_pos.x * 1.587 * WARP + u_time * SPEED * 0.5,
|
||||
_pos.y * 1.192 * WARP + u_time * SPEED * 0.3
|
||||
)) * 0.5 + 0.5;
|
||||
|
||||
float feather = 0.01 + SPREAD * pow(distortion, FOCUS);
|
||||
|
||||
return 1.0 - smoothstep(
|
||||
_radius - (_radius * feather),
|
||||
_radius + (_radius * feather),
|
||||
dot( dist, dist ) * 4.0
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 green = vec3(1.0) - vec3(153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0);
|
||||
vec3 purple = vec3(1.0) - vec3(195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0);
|
||||
vec3 orange = vec3(1.0) - vec3(255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0);
|
||||
|
||||
float ratio = u_resolution.x / u_resolution.y;
|
||||
|
||||
vec2 uv = vec2(in_uv.x, in_uv.y / ratio) * 0.5 + 0.5;
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
|
||||
float greenMix = snoise(in_uv * 1.31 + u_time * 0.8 * 0.00017) * 0.5 + 0.5;
|
||||
float purpleMix = snoise(in_uv * 1.26 + u_time * 0.8 * -0.0001) * 0.5 + 0.5;
|
||||
float orangeMix = snoise(in_uv * 1.34 + u_time * 0.8 * 0.00015) * 0.5 + 0.5;
|
||||
|
||||
float alphaOne = 0.35 + 0.65 * pow(snoise(vec2(u_time * 0.00012, uv.x)) * 0.5 + 0.5, 1.2);
|
||||
float alphaTwo = 0.35 + 0.65 * pow(snoise(vec2((u_time + 1561.0) * 0.00014, uv.x )) * 0.5 + 0.5, 1.2);
|
||||
float alphaThree = 0.35 + 0.65 * pow(snoise(vec2((u_time + 3917.0) * 0.00013, uv.x )) * 0.5 + 0.5, 1.2);
|
||||
|
||||
color += vec3(circle(uv, vec2(0.22 + sin(u_time * 0.000201) * 0.06, 0.80 + cos(u_time * 0.000151) * 0.06), 0.15)) * alphaOne * (purple * purpleMix + orange * orangeMix);
|
||||
color += vec3(circle(uv, vec2(0.90 + cos(u_time * 0.000166) * 0.06, 0.42 + sin(u_time * 0.000138) * 0.06), 0.18)) * alphaTwo * (green * greenMix + purple * purpleMix);
|
||||
color += vec3(circle(uv, vec2(0.19 + sin(u_time * 0.000112) * 0.06, 0.25 + sin(u_time * 0.000192) * 0.06), 0.09)) * alphaThree * (orange * orangeMix);
|
||||
|
||||
color *= u_itensity + 1.0 * pow(snoise(vec2(in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009)) * 0.5 + 0.5, 2.0);
|
||||
|
||||
vec3 inverted = vec3(1.0) - color;
|
||||
out_color = vec4(color, max(max(color.x, color.y), color.z));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 300 es
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
in vec2 position;
|
||||
uniform vec2 u_scale;
|
||||
out vec2 in_uv;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
in_uv = position / u_scale;
|
||||
}
|
||||
@@ -10,6 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
||||
import isChromatic from 'chromatic/isChromatic';
|
||||
import vertexShaderSource from './MkAnimBg.vertex.glsl';
|
||||
import fragmentShaderSource from './MkAnimBg.fragment.glsl';
|
||||
import { initShaderProgram } from '@/utility/webgl.js';
|
||||
|
||||
const canvasEl = useTemplateRef('canvasEl');
|
||||
@@ -42,126 +44,7 @@ onMounted(() => {
|
||||
const positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
const shaderProgram = initShaderProgram(gl, `#version 300 es
|
||||
in vec2 position;
|
||||
uniform vec2 u_scale;
|
||||
out vec2 in_uv;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
in_uv = position / u_scale;
|
||||
}
|
||||
`, `#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
vec3 mod289(vec3 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec2 mod289(vec2 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec3 permute(vec3 x) {
|
||||
return mod289(((x*34.0)+1.0)*x);
|
||||
}
|
||||
|
||||
float snoise(vec2 v) {
|
||||
const vec4 C = vec4(0.211324865405187,
|
||||
0.366025403784439,
|
||||
-0.577350269189626,
|
||||
0.024390243902439);
|
||||
|
||||
vec2 i = floor(v + dot(v, C.yy) );
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
|
||||
vec2 i1;
|
||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
|
||||
i = mod289(i);
|
||||
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
||||
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
||||
|
||||
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
||||
m = m*m ;
|
||||
m = m*m ;
|
||||
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
|
||||
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
||||
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
|
||||
in vec2 in_uv;
|
||||
uniform float u_time;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_spread;
|
||||
uniform float u_speed;
|
||||
uniform float u_warp;
|
||||
uniform float u_focus;
|
||||
uniform float u_itensity;
|
||||
out vec4 out_color;
|
||||
|
||||
float circle( in vec2 _pos, in vec2 _origin, in float _radius ) {
|
||||
float SPREAD = 0.7 * u_spread;
|
||||
float SPEED = 0.00055 * u_speed;
|
||||
float WARP = 1.5 * u_warp;
|
||||
float FOCUS = 1.15 * u_focus;
|
||||
|
||||
vec2 dist = _pos - _origin;
|
||||
|
||||
float distortion = snoise( vec2(
|
||||
_pos.x * 1.587 * WARP + u_time * SPEED * 0.5,
|
||||
_pos.y * 1.192 * WARP + u_time * SPEED * 0.3
|
||||
) ) * 0.5 + 0.5;
|
||||
|
||||
float feather = 0.01 + SPREAD * pow( distortion, FOCUS );
|
||||
|
||||
return 1.0 - smoothstep(
|
||||
_radius - ( _radius * feather ),
|
||||
_radius + ( _radius * feather ),
|
||||
dot( dist, dist ) * 4.0
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 green = vec3( 1.0 ) - vec3( 153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0 );
|
||||
vec3 purple = vec3( 1.0 ) - vec3( 195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0 );
|
||||
vec3 orange = vec3( 1.0 ) - vec3( 255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0 );
|
||||
|
||||
float ratio = u_resolution.x / u_resolution.y;
|
||||
|
||||
vec2 uv = vec2( in_uv.x, in_uv.y / ratio ) * 0.5 + 0.5;
|
||||
|
||||
vec3 color = vec3( 0.0 );
|
||||
|
||||
float greenMix = snoise( in_uv * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5;
|
||||
float purpleMix = snoise( in_uv * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5;
|
||||
float orangeMix = snoise( in_uv * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5;
|
||||
|
||||
float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
||||
float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
||||
float alphaThree = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 3917.0 ) * 0.00013, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
||||
|
||||
color += vec3( circle( uv, vec2( 0.22 + sin( u_time * 0.000201 ) * 0.06, 0.80 + cos( u_time * 0.000151 ) * 0.06 ), 0.15 ) ) * alphaOne * ( purple * purpleMix + orange * orangeMix );
|
||||
color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix );
|
||||
color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix );
|
||||
|
||||
color *= u_itensity + 1.0 * pow( snoise( vec2( in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 );
|
||||
|
||||
vec3 inverted = vec3( 1.0 ) - color;
|
||||
out_color = vec4(color, max(max(color.x, color.y), color.z));
|
||||
}
|
||||
`);
|
||||
const shaderProgram = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource);
|
||||
if (shaderProgram == null) return;
|
||||
|
||||
gl.useProgram(shaderProgram);
|
||||
|
||||
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.root">
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.preview">
|
||||
<canvas ref="canvasEl" :class="$style.previewCanvas" @pointerdown="onImagePointerdown"></canvas>
|
||||
<canvas ref="canvasEl" :class="$style.previewCanvas" @pointerdown.prevent.stop="onImagePointerdown"></canvas>
|
||||
<div :class="$style.previewContainer">
|
||||
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
|
||||
<div class="_acrylic" :class="$style.editControls">
|
||||
@@ -257,8 +257,12 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
xOffset /= 2;
|
||||
yOffset /= 2;
|
||||
|
||||
let startX = ev.offsetX - xOffset;
|
||||
let startY = ev.offsetY - yOffset;
|
||||
const rect = canvasEl.value.getBoundingClientRect();
|
||||
const pointerOffsetX = ev.clientX - rect.left;
|
||||
const pointerOffsetY = ev.clientY - rect.top;
|
||||
|
||||
let startX = pointerOffsetX - xOffset;
|
||||
let startY = pointerOffsetY - yOffset;
|
||||
|
||||
if (AW / AH < BW / BH) { // 横長
|
||||
startX = startX / (Math.max(AW, AH) / Math.max(BH / BW, 1));
|
||||
@@ -311,9 +315,11 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
_move(ev.offsetX, ev.offsetY);
|
||||
_move(ev.clientX, ev.clientY);
|
||||
|
||||
function _move(pointerX: number, pointerY: number) {
|
||||
function _move(pointerClientX: number, pointerClientY: number) {
|
||||
const pointerX = pointerClientX - rect.left;
|
||||
const pointerY = pointerClientY - rect.top;
|
||||
let x = pointerX - xOffset;
|
||||
let y = pointerY - yOffset;
|
||||
|
||||
@@ -340,7 +346,7 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
}
|
||||
|
||||
function move(ev: PointerEvent) {
|
||||
_move(ev.offsetX, ev.offsetY);
|
||||
_move(ev.clientX, ev.clientY);
|
||||
}
|
||||
|
||||
function up() {
|
||||
@@ -448,6 +454,7 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
margin: 20px;
|
||||
box-sizing: border-box;
|
||||
object-fit: contain;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
||||
@@ -57,15 +57,8 @@ const remaining = computed(() => {
|
||||
return Math.floor(Math.max(expiresAtTime.value - now.value, 0) / 1000);
|
||||
});
|
||||
|
||||
const remainingWatchStop = watch(remaining, (to) => {
|
||||
if (to <= 0) {
|
||||
showResult.value = true;
|
||||
remainingWatchStop();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const total = computed(() => sum(props.choices.map(x => x.votes)));
|
||||
const closed = computed(() => remaining.value === 0);
|
||||
const closed = computed(() => remaining.value <= 0);
|
||||
const isVoted = computed(() => !props.multiple && props.choices.some(c => c.isVoted));
|
||||
const timer = computed(() => i18n.tsx._poll[
|
||||
remaining.value >= 86400 ? 'remainingDays' :
|
||||
@@ -78,7 +71,16 @@ const timer = computed(() => i18n.tsx._poll[
|
||||
d: Math.floor(remaining.value / 86400),
|
||||
}));
|
||||
|
||||
const showResult = ref(props.readOnly || isVoted.value);
|
||||
const showResult = ref(props.readOnly || isVoted.value || closed.value);
|
||||
|
||||
if (!closed.value) {
|
||||
const closedWatchStop = watch(closed, (isNowClosed) => {
|
||||
if (isNowClosed) {
|
||||
showResult.value = true;
|
||||
closedWatchStop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||
type: 'lookup',
|
||||
|
||||
@@ -17,7 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<button v-click-anime v-tooltip="i18n.ts.switchAccount" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/>
|
||||
</button>
|
||||
<button v-if="$i.policies.noteDraftLimit > 0" v-tooltip="(postAccount != null && postAccount.id !== $i.id) ? null : i18n.ts.draftsAndScheduledNotes" class="_button" :class="$style.draftButton" :disabled="postAccount != null && postAccount.id !== $i.id" @click="showDraftMenu"><i class="ti ti-list"></i></button>
|
||||
</div>
|
||||
<div :class="$style.headerRight">
|
||||
<template v-if="!(targetChannel != null && fixed)">
|
||||
@@ -141,7 +140,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { ensureSignin, notesCount, incNotesCount } from '@/i.js';
|
||||
import { getAccounts, openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { getAccounts, getAccountMenu } from '@/accounts.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
@@ -620,6 +619,19 @@ function showOtherSettings() {
|
||||
action: () => {
|
||||
toggleReactionAcceptance();
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.saveToDraft,
|
||||
icon: 'ti ti-cloud-upload',
|
||||
action: async () => {
|
||||
if (!canSaveAsServerDraft.value) {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts._drafts.cannotCreateDraft,
|
||||
});
|
||||
}
|
||||
saveServerDraft();
|
||||
},
|
||||
}, ...($i.policies.scheduledNoteLimit > 0 ? [{
|
||||
icon: 'ti ti-calendar-time',
|
||||
text: i18n.ts.schedulePost + '...',
|
||||
@@ -1159,34 +1171,9 @@ function showActions(ev: MouseEvent) {
|
||||
|
||||
const postAccount = ref<Misskey.entities.UserDetailed | null>(null);
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
async function openAccountMenu(ev: MouseEvent) {
|
||||
if (props.mock) return;
|
||||
|
||||
openAccountMenu_({
|
||||
withExtraOperation: false,
|
||||
includeCurrentAccount: true,
|
||||
active: postAccount.value != null ? postAccount.value.id : $i.id,
|
||||
onChoose: (account) => {
|
||||
if (account.id === $i.id) {
|
||||
postAccount.value = null;
|
||||
} else {
|
||||
postAccount.value = account;
|
||||
}
|
||||
},
|
||||
}, ev);
|
||||
}
|
||||
|
||||
function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) {
|
||||
const menu = uploader.getMenu(item);
|
||||
os.popupMenu(menu, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) {
|
||||
const menu = uploader.getMenu(item);
|
||||
os.contextMenu(menu, ev);
|
||||
}
|
||||
|
||||
function showDraftMenu(ev: MouseEvent) {
|
||||
function showDraftsDialog(scheduled: boolean) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), {
|
||||
scheduled,
|
||||
@@ -1244,34 +1231,44 @@ function showDraftMenu(ev: MouseEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
os.popupMenu([{
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.saveToDraft,
|
||||
icon: 'ti ti-cloud-upload',
|
||||
action: async () => {
|
||||
if (!canSaveAsServerDraft.value) {
|
||||
return os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts._drafts.cannotCreateDraft,
|
||||
});
|
||||
const items = await getAccountMenu({
|
||||
withExtraOperation: false,
|
||||
includeCurrentAccount: true,
|
||||
active: postAccount.value != null ? postAccount.value.id : $i.id,
|
||||
onChoose: (account) => {
|
||||
if (account.id === $i.id) {
|
||||
postAccount.value = null;
|
||||
} else {
|
||||
postAccount.value = account;
|
||||
}
|
||||
saveServerDraft();
|
||||
},
|
||||
}, {
|
||||
});
|
||||
|
||||
os.popupMenu([{
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.listDrafts,
|
||||
icon: 'ti ti-cloud-download',
|
||||
action: () => {
|
||||
showDraftsDialog(false);
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
}, {
|
||||
type: 'button',
|
||||
text: i18n.ts._drafts.listScheduledNotes,
|
||||
icon: 'ti ti-clock-down',
|
||||
action: () => {
|
||||
showDraftsDialog(true);
|
||||
},
|
||||
}], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||
}, { type: 'divider' }, ...items], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||
}
|
||||
|
||||
function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) {
|
||||
const menu = uploader.getMenu(item);
|
||||
os.popupMenu(menu, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) {
|
||||
const menu = uploader.getMenu(item);
|
||||
os.contextMenu(menu, ev);
|
||||
}
|
||||
|
||||
async function schedule() {
|
||||
@@ -1422,20 +1419,6 @@ defineExpose({
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.draftButton {
|
||||
padding: 8px;
|
||||
font-size: 90%;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.headerRight {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { getUnicodeEmoji } from '@@/js/emojilist.js';
|
||||
import { getUnicodeEmojiOrNull } from '@@/js/emojilist.js';
|
||||
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
||||
import type { MenuItem } from '@/types/menu';
|
||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||
@@ -60,11 +60,11 @@ const buttonEl = useTemplateRef('buttonEl');
|
||||
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
||||
|
||||
const canToggle = computed(() => {
|
||||
const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction);
|
||||
const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmojiOrNull(props.reaction);
|
||||
|
||||
// TODO
|
||||
//return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji);
|
||||
return !props.reaction.match(/@\w/) && $i && emoji;
|
||||
return props.reaction.match(/@\w/) == null && $i != null && emoji != null;
|
||||
});
|
||||
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
||||
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
||||
|
||||
@@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Awaitable } from '@/types/misc.js';
|
||||
|
||||
export type SuperMenuDef = {
|
||||
title?: string;
|
||||
items: ({
|
||||
@@ -80,7 +82,7 @@ export type SuperMenuDef = {
|
||||
text: string;
|
||||
danger?: boolean;
|
||||
active?: boolean;
|
||||
action: (ev: MouseEvent) => void | Promise<void>;
|
||||
action: (ev: MouseEvent) => Awaitable<void>;
|
||||
} | {
|
||||
type?: 'link';
|
||||
to: string;
|
||||
|
||||
@@ -62,9 +62,10 @@ import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'v
|
||||
import { scrollToTop } from '@@/js/scroll.js';
|
||||
import XTabs from './MkPageHeader.tabs.vue';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { getAccountMenu } from '@/accounts.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { DI } from '@/di.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const props = withDefaults(defineProps<PageHeaderProps>(), {
|
||||
tabs: () => ([] as Tab[]),
|
||||
@@ -99,10 +100,12 @@ const top = () => {
|
||||
}
|
||||
};
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
async function openAccountMenu(ev: MouseEvent) {
|
||||
const menuItems = await getAccountMenu({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
});
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function onTabClick(): void {
|
||||
|
||||
@@ -634,7 +634,9 @@ export function useUploader(options: {
|
||||
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
|
||||
},
|
||||
audio: {
|
||||
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
|
||||
// Explicitly keep audio (don't discard) and copy it if possible
|
||||
// without re-encoding to avoid WebCodecs limitations on iOS Safari
|
||||
discard: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { throttle } from 'throttle-debounce';
|
||||
import type { Directive } from 'vue';
|
||||
import type { Awaitable } from '@/types/misc.js';
|
||||
|
||||
interface HTMLElementWithObserver extends HTMLElement {
|
||||
_observer_?: IntersectionObserver;
|
||||
@@ -31,4 +32,4 @@ export const appearDirective = {
|
||||
unmounted(src) {
|
||||
if (src._observer_) src._observer_.disconnect();
|
||||
},
|
||||
} as Directive<HTMLElementWithObserver, () => void>;
|
||||
} as Directive<HTMLElementWithObserver, (() => Awaitable<void>) | null | undefined>;
|
||||
|
||||
@@ -15,7 +15,7 @@ const start = isTouchUsing ? 'touchstart' : 'mouseenter';
|
||||
const end = isTouchUsing ? 'touchend' : 'mouseleave';
|
||||
|
||||
type TooltipDirectiveState = {
|
||||
text: string;
|
||||
text: string | null | undefined;
|
||||
_close: null | (() => void);
|
||||
showTimer: number | null;
|
||||
hideTimer: number | null;
|
||||
@@ -53,6 +53,7 @@ export const tooltipDirective = {
|
||||
|
||||
if (binding.arg === 'dialog') {
|
||||
el.addEventListener('click', (ev) => {
|
||||
if (binding.value == null) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
alert({
|
||||
@@ -128,4 +129,4 @@ export const tooltipDirective = {
|
||||
if (self.checkTimer) window.clearTimeout(self.checkTimer);
|
||||
self.close();
|
||||
},
|
||||
} as Directive<TooltipDirectiveElement, string, TooltipDirectiveModifiers, TooltipDirectiveArg>;
|
||||
} as Directive<TooltipDirectiveElement, string | null | undefined, TooltipDirectiveModifiers, TooltipDirectiveArg>;
|
||||
|
||||
@@ -131,4 +131,4 @@ export const userPreviewDirective = {
|
||||
if (self == null) return;
|
||||
self.preview.detach();
|
||||
},
|
||||
} as Directive<UserPreviewDirectiveElement, string | Misskey.entities.UserDetailed>;
|
||||
} as Directive<UserPreviewDirectiveElement, string | Misskey.entities.UserDetailed | null | undefined>;
|
||||
|
||||
@@ -25,6 +25,7 @@ export type Keys = (
|
||||
'bootloaderLocales' |
|
||||
'theme' |
|
||||
'themeId' |
|
||||
'themeCachedVersion' |
|
||||
'customCss' |
|
||||
'chatMessageDrafts' |
|
||||
'scratchpad' |
|
||||
|
||||
@@ -295,6 +295,9 @@ const patronsWithIcon = [{
|
||||
}, {
|
||||
name: 'しゃどかの',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/5bec3c6b402942619e03f7a2ae76d69e.jpg',
|
||||
}, {
|
||||
name: '大賀愛一郎',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/c701a797d1df4125970f25d3052250ac.jpg',
|
||||
}];
|
||||
|
||||
const patrons = [
|
||||
|
||||
@@ -10,6 +10,7 @@ import tinycolor from 'tinycolor2';
|
||||
import lightTheme from '@@/themes/_light.json5';
|
||||
import darkTheme from '@@/themes/_dark.json5';
|
||||
import JSON5 from 'json5';
|
||||
import { version } from '@@/js/config.js';
|
||||
import type { Ref } from 'vue';
|
||||
import type { BundledTheme } from 'shiki/themes';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
@@ -123,6 +124,7 @@ function applyThemeInternal(theme: Theme, persist: boolean) {
|
||||
if (persist) {
|
||||
miLocalStorage.setItem('theme', JSON.stringify(props));
|
||||
miLocalStorage.setItem('themeId', theme.id);
|
||||
miLocalStorage.setItem('themeCachedVersion', version);
|
||||
miLocalStorage.setItem('colorScheme', colorScheme);
|
||||
}
|
||||
|
||||
@@ -131,7 +133,7 @@ function applyThemeInternal(theme: Theme, persist: boolean) {
|
||||
}
|
||||
|
||||
let timeout: number | null = null;
|
||||
let currentTheme: Theme | null = null;
|
||||
let currentThemeId = miLocalStorage.getItem('themeId');
|
||||
|
||||
export function applyTheme(theme: Theme, persist = true) {
|
||||
if (timeout) {
|
||||
@@ -139,9 +141,8 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||
timeout = null;
|
||||
}
|
||||
|
||||
if (deepEqual(currentTheme, theme)) return;
|
||||
// リアクティビティ解除
|
||||
currentTheme = deepClone(theme);
|
||||
if (theme.id === currentThemeId && miLocalStorage.getItem('themeCachedVersion') === version) return;
|
||||
currentThemeId = theme.id;
|
||||
|
||||
if (window.document.startViewTransition != null) {
|
||||
window.document.documentElement.classList.add('_themeChanging_');
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
||||
|
||||
export type WithNonNullable<T, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> };
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export type Awaitable <T> = T | Promise<T>;
|
||||
@@ -55,7 +55,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { getAccountMenu } from '@/accounts.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||
|
||||
@@ -84,10 +84,12 @@ async function more(ev: MouseEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
async function openAccountMenu(ev: MouseEvent) {
|
||||
const menuItems = await getAccountMenu({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
});
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -109,7 +109,7 @@ import { instance } from '@/instance.js';
|
||||
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { getAccountMenu } from '@/accounts.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -170,10 +170,12 @@ function toggleRealtimeMode(ev: MouseEvent) {
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
async function openAccountMenu(ev: MouseEvent) {
|
||||
const menuItems = await getAccountMenu({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
});
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function more(ev: MouseEvent) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
in vec4 v_color;
|
||||
in float v_rotation;
|
||||
uniform sampler2D u_texture;
|
||||
out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
vec2 rotated = vec2(
|
||||
cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
|
||||
cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
|
||||
);
|
||||
|
||||
vec4 snowflake = texture(u_texture, rotated);
|
||||
|
||||
out_color = vec4(snowflake.rgb * v_color.xyz, snowflake.a * v_color.a);
|
||||
}
|
||||
@@ -3,59 +3,12 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import vertexSource from './snowfall-effect.vertex.glsl';
|
||||
import fragmentSource from './snowfall-effect.fragment.glsl';
|
||||
|
||||
export class SnowfallEffect {
|
||||
private VERTEX_SOURCE = `#version 300 es
|
||||
in vec4 a_position;
|
||||
in vec4 a_color;
|
||||
in vec3 a_rotation;
|
||||
in vec3 a_speed;
|
||||
in float a_size;
|
||||
out vec4 v_color;
|
||||
out float v_rotation;
|
||||
uniform float u_time;
|
||||
uniform mat4 u_projection;
|
||||
uniform vec3 u_worldSize;
|
||||
uniform float u_gravity;
|
||||
uniform float u_wind;
|
||||
uniform float u_spin_factor;
|
||||
uniform float u_turbulence;
|
||||
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y;
|
||||
|
||||
vec3 pos = a_position.xyz;
|
||||
|
||||
pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
|
||||
pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
|
||||
|
||||
pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||
pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||
|
||||
gl_Position = u_projection * vec4(pos.xyz, a_position.w);
|
||||
gl_PointSize = (a_size / gl_Position.w) * 100.0;
|
||||
}
|
||||
`;
|
||||
|
||||
private FRAGMENT_SOURCE = `#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
in vec4 v_color;
|
||||
in float v_rotation;
|
||||
uniform sampler2D u_texture;
|
||||
out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
vec2 rotated = vec2(
|
||||
cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
|
||||
cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
|
||||
);
|
||||
|
||||
vec4 snowflake = texture(u_texture, rotated);
|
||||
|
||||
out_color = vec4(snowflake.rgb * v_color.xyz, snowflake.a * v_color.a);
|
||||
}
|
||||
`;
|
||||
private VERTEX_SOURCE = vertexSource;
|
||||
private FRAGMENT_SOURCE = fragmentSource;
|
||||
|
||||
private gl: WebGLRenderingContext;
|
||||
private program: WebGLProgram;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#version 300 es
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
in vec4 a_position;
|
||||
in vec4 a_color;
|
||||
in vec3 a_rotation;
|
||||
in vec3 a_speed;
|
||||
in float a_size;
|
||||
out vec4 v_color;
|
||||
out float v_rotation;
|
||||
uniform float u_time;
|
||||
uniform mat4 u_projection;
|
||||
uniform vec3 u_worldSize;
|
||||
uniform float u_gravity;
|
||||
uniform float u_wind;
|
||||
uniform float u_spin_factor;
|
||||
uniform float u_turbulence;
|
||||
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y;
|
||||
|
||||
vec3 pos = a_position.xyz;
|
||||
|
||||
pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
|
||||
pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
|
||||
|
||||
pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||
pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||
|
||||
gl_Position = u_projection * vec4(pos.xyz, a_position.w);
|
||||
gl_PointSize = (a_size / gl_Position.w) * 100.0;
|
||||
}
|
||||
@@ -11,10 +11,10 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"esbuild": "0.25.10",
|
||||
"execa": "9.6.0",
|
||||
"glob": "11.0.3",
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "5.0.1",
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@readme/openapi-parser": "5.0.2",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"openapi-types": "12.1.3",
|
||||
"openapi-typescript": "7.9.1",
|
||||
"ts-case-convert": "2.1.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.10.1-alpha.4",
|
||||
"version": "2025.10.1-beta.4",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
@@ -35,10 +35,10 @@
|
||||
"directory": "packages/misskey-js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.52.15",
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@microsoft/api-extractor": "7.53.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"esbuild": "0.25.10",
|
||||
"execa": "9.6.0",
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.18.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"esbuild": "0.25.10",
|
||||
"execa": "9.6.0",
|
||||
"glob": "11.0.3",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"misskey-js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"nodemon": "3.1.10",
|
||||
|
||||
Generated
+1008
-1017
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
@@ -940,9 +940,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.18.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz",
|
||||
"integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==",
|
||||
"version": "22.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz",
|
||||
"integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "22.18.8",
|
||||
"@types/node": "22.18.10",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
|
||||
Reference in New Issue
Block a user