commit
994fc062cf
|
|
@ -1 +1 @@
|
||||||
FROM mcr.microsoft.com/devcontainers/javascript-node:0-18
|
FROM mcr.microsoft.com/devcontainers/javascript-node:4.0.3-24-trixie
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ services:
|
||||||
|
|
||||||
db:
|
db:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: postgres:15-alpine
|
image: postgres:18-alpine
|
||||||
networks:
|
networks:
|
||||||
- internal_network
|
- internal_network
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ body:
|
||||||
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
||||||
* Misskey: 2025.x.x
|
* Misskey: 2025.x.x
|
||||||
* Node: 20.x.x
|
* Node: 20.x.x
|
||||||
* PostgreSQL: 15.x.x
|
* PostgreSQL: 18.x.x
|
||||||
* Redis: 7.x.x
|
* Redis: 7.x.x
|
||||||
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
|
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
|
||||||
value: |
|
value: |
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: postgres:18
|
||||||
ports:
|
ports:
|
||||||
- 54312:5432
|
- 54312:5432
|
||||||
env:
|
env:
|
||||||
|
|
@ -117,7 +117,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: postgres:18
|
||||||
ports:
|
ports:
|
||||||
- 54312:5432
|
- 54312:5432
|
||||||
env:
|
env:
|
||||||
|
|
@ -165,7 +165,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: postgres:18
|
||||||
ports:
|
ports:
|
||||||
- 54312:5432
|
- 54312:5432
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: postgres:18
|
||||||
ports:
|
ports:
|
||||||
- 54312:5432
|
- 54312:5432
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
30
CHANGELOG.md
30
CHANGELOG.md
|
|
@ -1,3 +1,33 @@
|
||||||
|
## 2025.11.1
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
- Enhance: リアクションの受け入れ設定にキャプションを追加 #15921
|
||||||
|
- Fix: ページの内容がはみ出ることがある問題を修正
|
||||||
|
- Fix: ナビゲーションバーを下に表示しているときに、項目数が多いと表示が崩れる問題を修正
|
||||||
|
- Fix: ヘッダーメニューのチャンネルの新規作成の項目でチャンネル作成ページに飛べない問題を修正 #16816
|
||||||
|
- Fix: ラジオボタンに空白の選択肢が表示される問題を修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1105)
|
||||||
|
- Fix: 一部のシチュエーションで投稿フォームのツアーが正しく表示されない問題を修正
|
||||||
|
- Fix: 投稿フォームのリセットボタンで注釈がリセットされない問題を修正
|
||||||
|
- Fix: PlayのAiScriptバージョン判定(v0.x系・v1.x系の判定)が正しく動作しない問題を修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1129)
|
||||||
|
- Fix: フォロー申請をキャンセルする際の確認ダイアログの文言が不正確な問題を修正
|
||||||
|
- Fix: 初回読み込み時にエラーになることがある問題を修正
|
||||||
|
- Fix: お気に入りクリップの一覧表示が正しく動作しない問題を修正
|
||||||
|
- Fix: AiScript Misskey 拡張APIにおいて、各種関数の引数で明示的に `null` が指定されている場合のハンドリングを修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Enhance: メモリ使用量を削減しました
|
||||||
|
- Enhance: 依存関係の更新
|
||||||
|
- Fix: ワードミュートの文字数計算を修正
|
||||||
|
- Fix: チャンネルのリアルタイム更新時に、ロックダウン設定にて非ログイン時にノートを表示しない設定にしている場合でもノートが表示されてしまう問題を修正
|
||||||
|
- Fix: DeepL APIのAPIキー指定方式変更に対応
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1096)
|
||||||
|
- 内部実装の変更にて対応可能な更新です。Misskey側の設定方法に変更はありません。
|
||||||
|
- Fix: DBレプリケーションを利用する環境でクエリーが失敗する問題を修正
|
||||||
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1123)
|
||||||
|
|
||||||
## 2025.11.0
|
## 2025.11.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ spec:
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
- name: postgres
|
- name: postgres
|
||||||
image: postgres:15-alpine
|
image: postgres:18-alpine
|
||||||
env:
|
env:
|
||||||
- name: POSTGRES_USER
|
- name: POSTGRES_USER
|
||||||
value: "example-misskey-user"
|
value: "example-misskey-user"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ services:
|
||||||
|
|
||||||
db:
|
db:
|
||||||
restart: always
|
restart: always
|
||||||
image: postgres:15-alpine
|
image: postgres:18-alpine
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ services:
|
||||||
|
|
||||||
db:
|
db:
|
||||||
restart: always
|
restart: always
|
||||||
image: postgres:15-alpine
|
image: postgres:18-alpine
|
||||||
networks:
|
networks:
|
||||||
- internal_network
|
- internal_network
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "Fitxers"
|
||||||
download: "Descarregar"
|
download: "Descarregar"
|
||||||
driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades."
|
driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades."
|
||||||
unfollowConfirm: "Segur que vols deixar de seguir a {name}?"
|
unfollowConfirm: "Segur que vols deixar de seguir a {name}?"
|
||||||
|
cancelFollowRequestConfirm: "Vols cancel·lar la teva sol·licitud de seguiment a {name}?"
|
||||||
|
rejectFollowRequestConfirm: "Vols rebutjar la sol·licitud de seguiment de {name}?"
|
||||||
exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada."
|
exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada."
|
||||||
importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona."
|
importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona."
|
||||||
lists: "Llistes"
|
lists: "Llistes"
|
||||||
|
|
|
||||||
|
|
@ -1556,6 +1556,8 @@ _preferencesProfile:
|
||||||
profileNameDescription: "Set a name that identifies this device."
|
profileNameDescription: "Set a name that identifies this device."
|
||||||
profileNameDescription2: "Example: \"Main PC\", \"Smartphone\""
|
profileNameDescription2: "Example: \"Main PC\", \"Smartphone\""
|
||||||
manageProfiles: "Manage Profiles"
|
manageProfiles: "Manage Profiles"
|
||||||
|
shareSameProfileBetweenDevicesIsNotRecommended: "We do not recommend sharing the same profile across multiple devices."
|
||||||
|
useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "If there are settings you wish to synchronize across multiple devices, enable the “Synchronize across multiple devices” option individually for each device."
|
||||||
_preferencesBackup:
|
_preferencesBackup:
|
||||||
autoBackup: "Auto backup"
|
autoBackup: "Auto backup"
|
||||||
restoreFromBackup: "Restore from backup"
|
restoreFromBackup: "Restore from backup"
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "Archivos"
|
||||||
download: "Descargar"
|
download: "Descargar"
|
||||||
driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas"
|
driveFileDeleteConfirm: "¿Desea borrar el archivo \"{name}\"? Las notas que tengan este archivo como adjunto serán eliminadas"
|
||||||
unfollowConfirm: "¿Desea dejar de seguir a {name}?"
|
unfollowConfirm: "¿Desea dejar de seguir a {name}?"
|
||||||
|
cancelFollowRequestConfirm: "¿Desea cancelar su solicitud de seguimiento a {name}?"
|
||||||
|
rejectFollowRequestConfirm: "¿Desea rechazar la solicitud de seguimiento de {name}?"
|
||||||
exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive"
|
exportRequested: "Has solicitado la exportación. Puede llevar un tiempo. Cuando termine la exportación, se añadirá al drive"
|
||||||
importRequested: "Has solicitado la importación. Puede llevar un tiempo."
|
importRequested: "Has solicitado la importación. Puede llevar un tiempo."
|
||||||
lists: "Listas"
|
lists: "Listas"
|
||||||
|
|
@ -1354,7 +1356,7 @@ textCount: "caracteres"
|
||||||
information: "Información"
|
information: "Información"
|
||||||
chat: "Chat"
|
chat: "Chat"
|
||||||
directMessage: "Chatear"
|
directMessage: "Chatear"
|
||||||
directMessage_short: "Mensaje"
|
directMessage_short: "Mensajes"
|
||||||
migrateOldSettings: "Migrar la configuración anterior"
|
migrateOldSettings: "Migrar la configuración anterior"
|
||||||
migrateOldSettings_description: "Esto debería hacerse automáticamente, pero si por alguna razón la migración no ha tenido éxito, puede activar usted mismo el proceso de migración manualmente. Se sobrescribirá la información de configuración actual."
|
migrateOldSettings_description: "Esto debería hacerse automáticamente, pero si por alguna razón la migración no ha tenido éxito, puede activar usted mismo el proceso de migración manualmente. Se sobrescribirá la información de configuración actual."
|
||||||
compress: "Compresión de la imagen"
|
compress: "Compresión de la imagen"
|
||||||
|
|
@ -1457,7 +1459,7 @@ _order:
|
||||||
newest: "Los más recientes primero"
|
newest: "Los más recientes primero"
|
||||||
oldest: "Los más antiguos primero"
|
oldest: "Los más antiguos primero"
|
||||||
_chat:
|
_chat:
|
||||||
messages: "Mensaje"
|
messages: "Mensajes"
|
||||||
noMessagesYet: "Aún no hay mensajes"
|
noMessagesYet: "Aún no hay mensajes"
|
||||||
newMessage: "Mensajes nuevos"
|
newMessage: "Mensajes nuevos"
|
||||||
individualChat: "Chat individual"
|
individualChat: "Chat individual"
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,14 @@ export interface Locale extends ILocale {
|
||||||
* {name}のフォローを解除しますか?
|
* {name}のフォローを解除しますか?
|
||||||
*/
|
*/
|
||||||
"unfollowConfirm": ParameterizedString<"name">;
|
"unfollowConfirm": ParameterizedString<"name">;
|
||||||
|
/**
|
||||||
|
* {name}へのフォロー申請をキャンセルしますか?
|
||||||
|
*/
|
||||||
|
"cancelFollowRequestConfirm": ParameterizedString<"name">;
|
||||||
|
/**
|
||||||
|
* {name}からのフォロー申請を拒否しますか?
|
||||||
|
*/
|
||||||
|
"rejectFollowRequestConfirm": ParameterizedString<"name">;
|
||||||
/**
|
/**
|
||||||
* エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。
|
* エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -692,15 +692,15 @@ smtpSecure: "Usare SSL/TLS implicito per le connessioni SMTP"
|
||||||
smtpSecureInfo: "Disabilitare quando è attivo STARTTLS."
|
smtpSecureInfo: "Disabilitare quando è attivo STARTTLS."
|
||||||
testEmail: "Verifica il funzionamento"
|
testEmail: "Verifica il funzionamento"
|
||||||
wordMute: "Parole silenziate"
|
wordMute: "Parole silenziate"
|
||||||
wordMuteDescription: "Contrae le Note con la parola o la frase specificata. Permette di espandere le Note, cliccandole."
|
wordMuteDescription: "Comprimi le Note che hanno la parola o la regola specificata. Cliccale per espanderle e leggerne comunque il contenuto."
|
||||||
hardWordMute: "Filtro per parole"
|
hardWordMute: "Filtro per parole"
|
||||||
showMutedWord: "Elenca le parole silenziate"
|
showMutedWord: "Elenca le parole silenziate"
|
||||||
hardWordMuteDescription: "Ignora le Note con la parola o la frase specificata. A differenza delle parole silenziate, la Nota non verrà federata."
|
hardWordMuteDescription: "Ignora le Note con la parola o la regola specificata. A differenza delle \"Parole Silenziate\", queste Note non ti verranno proprio recapitate."
|
||||||
regexpError: "errore regex"
|
regexpError: "errore regex"
|
||||||
regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:"
|
regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:"
|
||||||
instanceMute: "Silenziare l'istanza"
|
instanceMute: "Silenziare l'istanza"
|
||||||
userSaysSomething: "{name} ha detto qualcosa"
|
userSaysSomething: "{name} ha detto qualcosa"
|
||||||
userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
|
userSaysSomethingAbout: "{name} ha anNotato qualcosa su \"{word}\""
|
||||||
makeActive: "Attiva"
|
makeActive: "Attiva"
|
||||||
display: "Visualizza"
|
display: "Visualizza"
|
||||||
copy: "Copia"
|
copy: "Copia"
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "ファイル"
|
||||||
download: "ダウンロード"
|
download: "ダウンロード"
|
||||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
||||||
unfollowConfirm: "{name}のフォローを解除しますか?"
|
unfollowConfirm: "{name}のフォローを解除しますか?"
|
||||||
|
cancelFollowRequestConfirm: "{name}へのフォロー申請をキャンセルしますか?"
|
||||||
|
rejectFollowRequestConfirm: "{name}からのフォロー申請を拒否しますか?"
|
||||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||||
lists: "リスト"
|
lists: "リスト"
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "파일"
|
||||||
download: "다운로드"
|
download: "다운로드"
|
||||||
driveFileDeleteConfirm: "‘{name}’ 파일을 삭제하시겠습니까? 이 파일을 사용하는 일부 콘텐츠도 삭제됩니다."
|
driveFileDeleteConfirm: "‘{name}’ 파일을 삭제하시겠습니까? 이 파일을 사용하는 일부 콘텐츠도 삭제됩니다."
|
||||||
unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
|
unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
|
||||||
|
cancelFollowRequestConfirm: "{name}(으)로의 팔로우 신청을 취소하시겠습니까?"
|
||||||
|
rejectFollowRequestConfirm: "{name}(으)로부터의 팔로우 신청을 거부하시겠습니까?"
|
||||||
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
|
||||||
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
|
||||||
lists: "리스트"
|
lists: "리스트"
|
||||||
|
|
@ -1556,6 +1558,8 @@ _preferencesProfile:
|
||||||
profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
|
profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
|
||||||
profileNameDescription2: "예: '메인PC', '스마트폰' 등"
|
profileNameDescription2: "예: '메인PC', '스마트폰' 등"
|
||||||
manageProfiles: "프로파일 관리"
|
manageProfiles: "프로파일 관리"
|
||||||
|
shareSameProfileBetweenDevicesIsNotRecommended: "여러 장치에서 동일한 프로필을 공유하는 것은 권장하지 않습니다."
|
||||||
|
useSyncBetweenDevicesOptionIfYouWantToSyncSetting: "여러 장치에서 동기화하고 싶은 설정 항목이 있는 경우에는 개별로 '여러 장치에서 동기화' 옵션을 활성화해 주십시오."
|
||||||
_preferencesBackup:
|
_preferencesBackup:
|
||||||
autoBackup: "자동 백업"
|
autoBackup: "자동 백업"
|
||||||
restoreFromBackup: "백업으로 복구"
|
restoreFromBackup: "백업으로 복구"
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "文件"
|
||||||
download: "下载"
|
download: "下载"
|
||||||
driveFileDeleteConfirm: "要删除「{name}」文件吗?附加此文件的帖子也会被删除。"
|
driveFileDeleteConfirm: "要删除「{name}」文件吗?附加此文件的帖子也会被删除。"
|
||||||
unfollowConfirm: "要取消对 {name} 的关注吗?"
|
unfollowConfirm: "要取消对 {name} 的关注吗?"
|
||||||
|
cancelFollowRequestConfirm: "要取消申请关注{name}吗?"
|
||||||
|
rejectFollowRequestConfirm: "要拒绝{name}的关注申请吗?"
|
||||||
exportRequested: "导出请求已提交,这可能需要花一些时间,导出的文件将保存到网盘中。"
|
exportRequested: "导出请求已提交,这可能需要花一些时间,导出的文件将保存到网盘中。"
|
||||||
importRequested: "导入请求已提交,这可能需要花一点时间。"
|
importRequested: "导入请求已提交,这可能需要花一点时间。"
|
||||||
lists: "列表"
|
lists: "列表"
|
||||||
|
|
@ -474,7 +476,7 @@ passwordLessLogin: "无密码登录"
|
||||||
passwordLessLoginDescription: "不使用密码,仅使用安全密钥或 Passkey 登录"
|
passwordLessLoginDescription: "不使用密码,仅使用安全密钥或 Passkey 登录"
|
||||||
resetPassword: "重置密码"
|
resetPassword: "重置密码"
|
||||||
newPasswordIs: "新的密码是「{password}」"
|
newPasswordIs: "新的密码是「{password}」"
|
||||||
reduceUiAnimation: "减少UI动画"
|
reduceUiAnimation: "减少 UI 动画"
|
||||||
share: "分享"
|
share: "分享"
|
||||||
notFound: "未找到"
|
notFound: "未找到"
|
||||||
notFoundDescription: "没有与指定 URL 对应的页面。"
|
notFoundDescription: "没有与指定 URL 对应的页面。"
|
||||||
|
|
@ -795,7 +797,7 @@ makeExplorable: "使账号可见。"
|
||||||
makeExplorableDescription: "关闭时,账号不会显示在\"发现\"中。"
|
makeExplorableDescription: "关闭时,账号不会显示在\"发现\"中。"
|
||||||
duplicate: "复制"
|
duplicate: "复制"
|
||||||
left: "左"
|
left: "左"
|
||||||
center: "中央"
|
center: "居中"
|
||||||
wide: "宽"
|
wide: "宽"
|
||||||
narrow: "窄"
|
narrow: "窄"
|
||||||
reloadToApplySetting: "页面刷新后设置才会生效。是否现在刷新页面?"
|
reloadToApplySetting: "页面刷新后设置才会生效。是否现在刷新页面?"
|
||||||
|
|
@ -817,7 +819,7 @@ advanced: "高级"
|
||||||
advancedSettings: "高级设置"
|
advancedSettings: "高级设置"
|
||||||
value: "值"
|
value: "值"
|
||||||
createdAt: "创建日期"
|
createdAt: "创建日期"
|
||||||
updatedAt: "更新时间"
|
updatedAt: "更新日期"
|
||||||
saveConfirm: "确定保存?"
|
saveConfirm: "确定保存?"
|
||||||
deleteConfirm: "确定删除?"
|
deleteConfirm: "确定删除?"
|
||||||
invalidValue: "无效值。"
|
invalidValue: "无效值。"
|
||||||
|
|
@ -1023,6 +1025,9 @@ pushNotificationAlreadySubscribed: "推送通知消息已启用"
|
||||||
pushNotificationNotSupported: "浏览器或服务器不支持推送通知消息"
|
pushNotificationNotSupported: "浏览器或服务器不支持推送通知消息"
|
||||||
sendPushNotificationReadMessage: "删除已读推送通知消息"
|
sendPushNotificationReadMessage: "删除已读推送通知消息"
|
||||||
sendPushNotificationReadMessageCaption: "您终端设备的电池消耗可能会增加。"
|
sendPushNotificationReadMessageCaption: "您终端设备的电池消耗可能会增加。"
|
||||||
|
pleaseAllowPushNotification: "请在浏览器中启用推送通知"
|
||||||
|
browserPushNotificationDisabled: "未能获取发送通知的权限"
|
||||||
|
browserPushNotificationDisabledDescription: "{serverName}无权限发送通知。请在浏览器设置中允许通知后重新尝试。"
|
||||||
windowMaximize: "最大化"
|
windowMaximize: "最大化"
|
||||||
windowMinimize: "最小化"
|
windowMinimize: "最小化"
|
||||||
windowRestore: "还原"
|
windowRestore: "还原"
|
||||||
|
|
@ -1207,7 +1212,7 @@ renotes: "转发"
|
||||||
loadReplies: "查看回复"
|
loadReplies: "查看回复"
|
||||||
loadConversation: "查看对话"
|
loadConversation: "查看对话"
|
||||||
pinnedList: "已置顶的列表"
|
pinnedList: "已置顶的列表"
|
||||||
keepScreenOn: "保持设备屏幕开启"
|
keepScreenOn: "保持屏幕常亮"
|
||||||
verifiedLink: "已验证的链接"
|
verifiedLink: "已验证的链接"
|
||||||
notifyNotes: "打开发帖通知"
|
notifyNotes: "打开发帖通知"
|
||||||
unnotifyNotes: "关闭发帖通知"
|
unnotifyNotes: "关闭发帖通知"
|
||||||
|
|
@ -1332,7 +1337,7 @@ accessibility: "辅助功能"
|
||||||
preferencesProfile: "设置的配置"
|
preferencesProfile: "设置的配置"
|
||||||
copyPreferenceId: "复制设置 ID"
|
copyPreferenceId: "复制设置 ID"
|
||||||
resetToDefaultValue: "重置为默认值"
|
resetToDefaultValue: "重置为默认值"
|
||||||
overrideByAccount: "用账户覆盖"
|
overrideByAccount: "覆盖账号"
|
||||||
untitled: "未命名"
|
untitled: "未命名"
|
||||||
noName: "没有名字"
|
noName: "没有名字"
|
||||||
skip: "跳过"
|
skip: "跳过"
|
||||||
|
|
@ -1398,13 +1403,14 @@ widgets: "小工具"
|
||||||
deviceInfo: "设备信息"
|
deviceInfo: "设备信息"
|
||||||
deviceInfoDescription: "咨询技术问题时,将以下信息一并发送有助于解决问题。"
|
deviceInfoDescription: "咨询技术问题时,将以下信息一并发送有助于解决问题。"
|
||||||
youAreAdmin: "你是管理员"
|
youAreAdmin: "你是管理员"
|
||||||
|
frame: "边框"
|
||||||
presets: "预设值"
|
presets: "预设值"
|
||||||
zeroPadding: "填充 0"
|
zeroPadding: "填充 0"
|
||||||
_imageEditing:
|
_imageEditing:
|
||||||
_vars:
|
_vars:
|
||||||
caption: "文件标题"
|
caption: "文件标题"
|
||||||
filename: "文件名称"
|
filename: "文件名称"
|
||||||
filename_without_ext: "无扩展文件名"
|
filename_without_ext: "不带扩展名的文件名"
|
||||||
year: "拍摄年"
|
year: "拍摄年"
|
||||||
month: "拍摄月"
|
month: "拍摄月"
|
||||||
day: "拍摄日"
|
day: "拍摄日"
|
||||||
|
|
@ -1414,26 +1420,31 @@ _imageEditing:
|
||||||
camera_model: "相机名称"
|
camera_model: "相机名称"
|
||||||
camera_lens_model: "镜头型号"
|
camera_lens_model: "镜头型号"
|
||||||
camera_mm: "焦距"
|
camera_mm: "焦距"
|
||||||
|
camera_mm_35: "焦距(35mm等效)"
|
||||||
camera_f: "光圈"
|
camera_f: "光圈"
|
||||||
camera_s: "快门速度"
|
camera_s: "快门速度"
|
||||||
camera_iso: "ISO"
|
camera_iso: "ISO"
|
||||||
gps_lat: "纬度"
|
gps_lat: "纬度"
|
||||||
gps_long: "经度"
|
gps_long: "经度"
|
||||||
_imageFrameEditor:
|
_imageFrameEditor:
|
||||||
|
title: "编辑边框"
|
||||||
|
tip: "您可以通过添加包含边框和元数据的标签来装饰图片。"
|
||||||
header: "顶栏"
|
header: "顶栏"
|
||||||
footer: "底部"
|
footer: "页脚"
|
||||||
|
borderThickness: "边框宽度"
|
||||||
|
labelThickness: "标签宽度"
|
||||||
labelScale: "标签比例"
|
labelScale: "标签比例"
|
||||||
centered: "居中"
|
centered: "居中"
|
||||||
captionMain: "标题(大)"
|
captionMain: "标题(大)"
|
||||||
captionSub: "标题(小)"
|
captionSub: "标题(小)"
|
||||||
availableVariables: "可用变量"
|
availableVariables: "可修改的变量"
|
||||||
withQrCode: "二维码"
|
withQrCode: "二维码"
|
||||||
backgroundColor: "背景色"
|
backgroundColor: "背景颜色"
|
||||||
textColor: "文字色"
|
textColor: "文本颜色"
|
||||||
font: "字体"
|
font: "字体"
|
||||||
fontSerif: "衬线字体"
|
fontSerif: "衬线字体"
|
||||||
fontSansSerif: "无衬线字体"
|
fontSansSerif: "无衬线字体"
|
||||||
quitWithoutSaveConfirm: "不保存就退出吗?"
|
quitWithoutSaveConfirm: "放弃未保存的更改?"
|
||||||
failedToLoadImage: "图片加载失败"
|
failedToLoadImage: "图片加载失败"
|
||||||
_compression:
|
_compression:
|
||||||
_quality:
|
_quality:
|
||||||
|
|
@ -1452,36 +1463,36 @@ _chat:
|
||||||
noMessagesYet: "还没有消息"
|
noMessagesYet: "还没有消息"
|
||||||
newMessage: "新消息"
|
newMessage: "新消息"
|
||||||
individualChat: "私聊"
|
individualChat: "私聊"
|
||||||
individualChat_description: "可以与特定用户进行一对一聊天。"
|
individualChat_description: "与特定的用户单独聊天。"
|
||||||
roomChat: "群聊"
|
roomChat: "群聊"
|
||||||
roomChat_description: "支持多人同时进行消息交流。\n即使部分用户未开放私信权限,只要接受了邀请,仍可进行聊天。"
|
roomChat_description: "支持多人同时聊天。\n即使对方不允许私聊,只要接受邀请也能加入。"
|
||||||
createRoom: "创建群组"
|
createRoom: "创建群聊"
|
||||||
inviteUserToChat: "邀请用户来开始聊天"
|
inviteUserToChat: "邀请用户来聊天吧"
|
||||||
yourRooms: "创建的群组"
|
yourRooms: "已创建的群聊"
|
||||||
joiningRooms: "已加入的群组"
|
joiningRooms: "已加入的群聊"
|
||||||
invitations: "邀请"
|
invitations: "邀请"
|
||||||
noInvitations: "没有邀请"
|
noInvitations: "没有邀请"
|
||||||
history: "历史"
|
history: "历史"
|
||||||
noHistory: "没有历史记录"
|
noHistory: "没有历史记录"
|
||||||
noRooms: "没有群组"
|
noRooms: "没有群聊"
|
||||||
inviteUser: "邀请用户"
|
inviteUser: "邀请用户"
|
||||||
sentInvitations: "已发送的邀请"
|
sentInvitations: "已发送的邀请"
|
||||||
join: "加入"
|
join: "加入"
|
||||||
ignore: "忽略"
|
ignore: "忽略"
|
||||||
leave: "退出房间"
|
leave: "退出群聊"
|
||||||
members: "成员"
|
members: "成员"
|
||||||
searchMessages: "搜索消息"
|
searchMessages: "搜索消息"
|
||||||
home: "首页"
|
home: "首页"
|
||||||
send: "发送"
|
send: "发送"
|
||||||
newline: "换行"
|
newline: "换行"
|
||||||
muteThisRoom: "屏蔽该群组"
|
muteThisRoom: "屏蔽该群聊"
|
||||||
deleteRoom: "删除群组"
|
deleteRoom: "删除群聊"
|
||||||
chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。"
|
chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。"
|
||||||
chatIsReadOnlyForThisAccountOrServer: "此服务器或者账户内的聊天为只读。无法发布新信息或创建及加入群聊。"
|
chatIsReadOnlyForThisAccountOrServer: "此服务器或者账户内的聊天为只读。无法发布新信息或创建及加入群聊。"
|
||||||
chatNotAvailableInOtherAccount: "对方的账户当前无法使用私信。"
|
chatNotAvailableInOtherAccount: "对方的账户当前无法使用私信。"
|
||||||
cannotChatWithTheUser: "无法私信该用户"
|
cannotChatWithTheUser: "无法私信该用户"
|
||||||
cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。"
|
cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。"
|
||||||
youAreNotAMemberOfThisRoomButInvited: "您还未加入此房间,但已收到邀请。如要加入,请接受邀请。"
|
youAreNotAMemberOfThisRoomButInvited: "您尚未加入此群组,但已收到加入邀请。请接受邀请加入。"
|
||||||
doYouAcceptInvitation: "要接受邀请吗?"
|
doYouAcceptInvitation: "要接受邀请吗?"
|
||||||
chatWithThisUser: "私信"
|
chatWithThisUser: "私信"
|
||||||
thisUserAllowsChatOnlyFromFollowers: "此用户仅接受关注者发起的聊天。"
|
thisUserAllowsChatOnlyFromFollowers: "此用户仅接受关注者发起的聊天。"
|
||||||
|
|
@ -1558,6 +1569,7 @@ _preferencesBackup:
|
||||||
youNeedToNameYourProfileToEnableAutoBackup: "需指定配置名以开启自动备份。"
|
youNeedToNameYourProfileToEnableAutoBackup: "需指定配置名以开启自动备份。"
|
||||||
autoPreferencesBackupIsNotEnabledForThisDevice: "此设备未开启自动备份"
|
autoPreferencesBackupIsNotEnabledForThisDevice: "此设备未开启自动备份"
|
||||||
backupFound: "已找到备份"
|
backupFound: "已找到备份"
|
||||||
|
forceBackup: "强制备份设置"
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "需要登录才能显示内容"
|
requireSigninToViewContents: "需要登录才能显示内容"
|
||||||
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
|
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
|
||||||
|
|
@ -2219,7 +2231,7 @@ _instanceTicker:
|
||||||
_serverDisconnectedBehavior:
|
_serverDisconnectedBehavior:
|
||||||
reload: "自动重载"
|
reload: "自动重载"
|
||||||
dialog: "对话框警告"
|
dialog: "对话框警告"
|
||||||
quiet: "安静警告"
|
quiet: "静默警告"
|
||||||
_channel:
|
_channel:
|
||||||
create: "创建频道"
|
create: "创建频道"
|
||||||
edit: "编辑频道"
|
edit: "编辑频道"
|
||||||
|
|
@ -2538,7 +2550,7 @@ _poll:
|
||||||
noOnlyOneChoice: "需要至少两个选项"
|
noOnlyOneChoice: "需要至少两个选项"
|
||||||
choiceN: "选项{n}"
|
choiceN: "选项{n}"
|
||||||
noMore: "无法再添加更多了"
|
noMore: "无法再添加更多了"
|
||||||
canMultipleVote: "允许选择多个选项"
|
canMultipleVote: "允许多选"
|
||||||
expiration: "截止时间"
|
expiration: "截止时间"
|
||||||
infinite: "永久"
|
infinite: "永久"
|
||||||
at: "指定日期"
|
at: "指定日期"
|
||||||
|
|
@ -2547,13 +2559,13 @@ _poll:
|
||||||
deadlineTime: "时间"
|
deadlineTime: "时间"
|
||||||
duration: "期限"
|
duration: "期限"
|
||||||
votesCount: "{n}票"
|
votesCount: "{n}票"
|
||||||
totalVotes: "总票数 {n}"
|
totalVotes: "总计{n}票"
|
||||||
vote: "投票"
|
vote: "投票"
|
||||||
showResult: "显示结果"
|
showResult: "查看结果"
|
||||||
voted: "已投票"
|
voted: "已投票"
|
||||||
closed: "已截止"
|
closed: "已截止"
|
||||||
remainingDays: "{d}天{h}小时后截止"
|
remainingDays: "{d}天{h}小时后截止"
|
||||||
remainingHours: "{h} 小时 {m} 分后截止"
|
remainingHours: "{h}小时{m}分后截止"
|
||||||
remainingMinutes: "{m}分{s}秒后截止"
|
remainingMinutes: "{m}分{s}秒后截止"
|
||||||
remainingSeconds: "{s}秒后截止"
|
remainingSeconds: "{s}秒后截止"
|
||||||
_visibility:
|
_visibility:
|
||||||
|
|
@ -2737,7 +2749,7 @@ _notification:
|
||||||
newNote: "新的帖子"
|
newNote: "新的帖子"
|
||||||
unreadAntennaNote: "天线 {name}"
|
unreadAntennaNote: "天线 {name}"
|
||||||
roleAssigned: "授予的角色"
|
roleAssigned: "授予的角色"
|
||||||
chatRoomInvitationReceived: "受邀加入聊天室"
|
chatRoomInvitationReceived: "您已被邀请加入群聊"
|
||||||
emptyPushNotificationMessage: "推送通知已更新"
|
emptyPushNotificationMessage: "推送通知已更新"
|
||||||
achievementEarned: "获得成就"
|
achievementEarned: "获得成就"
|
||||||
testNotification: "测试通知"
|
testNotification: "测试通知"
|
||||||
|
|
@ -2768,7 +2780,7 @@ _notification:
|
||||||
receiveFollowRequest: "收到关注请求"
|
receiveFollowRequest: "收到关注请求"
|
||||||
followRequestAccepted: "关注请求已通过"
|
followRequestAccepted: "关注请求已通过"
|
||||||
roleAssigned: "授予的角色"
|
roleAssigned: "授予的角色"
|
||||||
chatRoomInvitationReceived: "受邀加入聊天室"
|
chatRoomInvitationReceived: "您已被邀请加入群聊"
|
||||||
achievementEarned: "取得的成就"
|
achievementEarned: "取得的成就"
|
||||||
exportCompleted: "已完成导出"
|
exportCompleted: "已完成导出"
|
||||||
login: "登录"
|
login: "登录"
|
||||||
|
|
@ -2864,6 +2876,8 @@ _abuseReport:
|
||||||
notifiedWebhook: "使用的 webhook"
|
notifiedWebhook: "使用的 webhook"
|
||||||
deleteConfirm: "要删除通知吗?"
|
deleteConfirm: "要删除通知吗?"
|
||||||
_moderationLogTypes:
|
_moderationLogTypes:
|
||||||
|
clearQueue: "清除队列"
|
||||||
|
promoteQueue: "重新执行队列中的任务"
|
||||||
createRole: "创建角色"
|
createRole: "创建角色"
|
||||||
deleteRole: "删除角色"
|
deleteRole: "删除角色"
|
||||||
updateRole: "更新角色"
|
updateRole: "更新角色"
|
||||||
|
|
@ -2908,11 +2922,11 @@ _moderationLogTypes:
|
||||||
createAbuseReportNotificationRecipient: "新建了举报通知"
|
createAbuseReportNotificationRecipient: "新建了举报通知"
|
||||||
updateAbuseReportNotificationRecipient: "更新了举报通知"
|
updateAbuseReportNotificationRecipient: "更新了举报通知"
|
||||||
deleteAbuseReportNotificationRecipient: "删除了举报通知"
|
deleteAbuseReportNotificationRecipient: "删除了举报通知"
|
||||||
deleteAccount: "删除了账户"
|
deleteAccount: "删除帐户"
|
||||||
deletePage: "删除了页面"
|
deletePage: "删除页面"
|
||||||
deleteFlash: "删除了 Play"
|
deleteFlash: "删除 Play"
|
||||||
deleteGalleryPost: "删除图集内容"
|
deleteGalleryPost: "删除图集内容"
|
||||||
deleteChatRoom: "删除聊天室"
|
deleteChatRoom: "删除群聊"
|
||||||
updateProxyAccountDescription: "更新代理账户的简介"
|
updateProxyAccountDescription: "更新代理账户的简介"
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "文件信息"
|
title: "文件信息"
|
||||||
|
|
@ -3185,7 +3199,7 @@ _search:
|
||||||
serverHostPlaceholder: "如:misskey.example.com"
|
serverHostPlaceholder: "如:misskey.example.com"
|
||||||
_serverSetupWizard:
|
_serverSetupWizard:
|
||||||
installCompleted: "Misskey 安装完成!"
|
installCompleted: "Misskey 安装完成!"
|
||||||
firstCreateAccount: "首先来创建管理员账号吧。"
|
firstCreateAccount: "首先,创建一个管理员帐户。"
|
||||||
accountCreated: "管理员账号已创建!"
|
accountCreated: "管理员账号已创建!"
|
||||||
serverSetting: "服务器设置"
|
serverSetting: "服务器设置"
|
||||||
youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "用此向导来轻松地以最佳方式配置服务器。"
|
youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "用此向导来轻松地以最佳方式配置服务器。"
|
||||||
|
|
@ -3195,7 +3209,7 @@ _serverSetupWizard:
|
||||||
single: "单用户服务器"
|
single: "单用户服务器"
|
||||||
single_description: "仅供自己使用的单人服务器"
|
single_description: "仅供自己使用的单人服务器"
|
||||||
single_youCanCreateMultipleAccounts: "使用单用户服务器模式使用时,也可以根据需要创建多个账号。"
|
single_youCanCreateMultipleAccounts: "使用单用户服务器模式使用时,也可以根据需要创建多个账号。"
|
||||||
group: "小圈子服务器"
|
group: "群组服务器"
|
||||||
group_description: "邀请其他可信用户一起使用的多人服务器"
|
group_description: "邀请其他可信用户一起使用的多人服务器"
|
||||||
open: "开放服务器"
|
open: "开放服务器"
|
||||||
open_description: "以容纳不限定数量的用户的模式运行"
|
open_description: "以容纳不限定数量的用户的模式运行"
|
||||||
|
|
@ -3232,7 +3246,7 @@ _uploader:
|
||||||
compressedToX: "压缩 {x}"
|
compressedToX: "压缩 {x}"
|
||||||
savedXPercent: "节省了 {x}% 的空间"
|
savedXPercent: "节省了 {x}% 的空间"
|
||||||
abortConfirm: "还有未上传的文件,要中止吗?"
|
abortConfirm: "还有未上传的文件,要中止吗?"
|
||||||
doneConfirm: "还有未上传的文件,要完成吗?"
|
doneConfirm: "部分文件尚未上传,是否继续?"
|
||||||
maxFileSizeIsX: "可上传最大 {x} 的文件。"
|
maxFileSizeIsX: "可上传最大 {x} 的文件。"
|
||||||
allowedTypes: "可上传的文件类型"
|
allowedTypes: "可上传的文件类型"
|
||||||
tip: "文件还没有被上传。可在此对话框中进行上传前确认、重命名、压缩、裁剪等操作。准备完成后,点击「上传」即可开始上传。"
|
tip: "文件还没有被上传。可在此对话框中进行上传前确认、重命名、压缩、裁剪等操作。准备完成后,点击「上传」即可开始上传。"
|
||||||
|
|
@ -3252,7 +3266,7 @@ watermark: "水印"
|
||||||
defaultPreset: "默认预设"
|
defaultPreset: "默认预设"
|
||||||
_watermarkEditor:
|
_watermarkEditor:
|
||||||
tip: "可在图像内增加包含作者等信息的水印。"
|
tip: "可在图像内增加包含作者等信息的水印。"
|
||||||
quitWithoutSaveConfirm: "不保存就退出吗?"
|
quitWithoutSaveConfirm: "放弃未保存的更改?"
|
||||||
driveFileTypeWarn: "不支持此文件"
|
driveFileTypeWarn: "不支持此文件"
|
||||||
driveFileTypeWarnDescription: "请选择图像文件"
|
driveFileTypeWarnDescription: "请选择图像文件"
|
||||||
title: "编辑水印"
|
title: "编辑水印"
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ files: "檔案"
|
||||||
download: "下載"
|
download: "下載"
|
||||||
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。"
|
driveFileDeleteConfirm: "確定要刪除檔案「{name}」嗎?使用此檔案的貼文也會跟著被刪除。"
|
||||||
unfollowConfirm: "確定要取消追隨{name}嗎?"
|
unfollowConfirm: "確定要取消追隨{name}嗎?"
|
||||||
|
cancelFollowRequestConfirm: "要取消向 {name} 送出的追隨申請嗎?"
|
||||||
|
rejectFollowRequestConfirm: "要拒絕來自 {name} 的追隨申請嗎?"
|
||||||
exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。"
|
exportRequested: "已請求匯出。這可能會花一點時間。匯出的檔案將會被放到雲端硬碟裡。"
|
||||||
importRequested: "已請求匯入。這可能會花一點時間。"
|
importRequested: "已請求匯入。這可能會花一點時間。"
|
||||||
lists: "清單"
|
lists: "清單"
|
||||||
|
|
@ -172,7 +174,7 @@ emojiUrl: "表情符號 URL"
|
||||||
addEmoji: "新增表情符號"
|
addEmoji: "新增表情符號"
|
||||||
settingGuide: "推薦設定"
|
settingGuide: "推薦設定"
|
||||||
cacheRemoteFiles: "快取遠端檔案"
|
cacheRemoteFiles: "快取遠端檔案"
|
||||||
cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私。"
|
cacheRemoteFilesDescription: "啟用這個設定後,遠端檔案會被快取到這台伺服器的儲存空間中。這樣能加快圖片的顯示速度,但會多占用伺服器的儲存容量。遠端使用者能保留多少快取,取決於其角色所設定的硬碟容量上限。若超過這個上限,系統會從最舊的檔案開始刪除快取並改成連結。若停用這個設定,遠端檔案一開始就只會以連結的形式保留。"
|
||||||
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。"
|
youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。"
|
||||||
cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
|
cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
|
||||||
cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
|
cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
|
||||||
|
|
|
||||||
29
package.json
29
package.json
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.11.0",
|
"version": "2025.11.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.20.0",
|
"packageManager": "pnpm@10.22.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend-shared",
|
"packages/frontend-shared",
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
|
"start:inspect": "cd packages/backend && node --inspect ./built/boot/entry.js",
|
||||||
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"cli": "cd packages/backend && pnpm cli",
|
"cli": "cd packages/backend && pnpm cli",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
|
|
@ -54,30 +55,30 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano": "7.1.2",
|
"cssnano": "7.1.2",
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"glob": "11.0.3",
|
"glob": "13.0.0",
|
||||||
"ignore-walk": "8.0.0",
|
"ignore-walk": "8.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tar": "7.5.2",
|
"tar": "7.5.2",
|
||||||
"terser": "5.44.0",
|
"terser": "5.44.1",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.39.0",
|
"@eslint/js": "9.39.1",
|
||||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
"@misskey-dev/eslint-plugin": "2.2.0",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"cypress": "15.5.0",
|
"cypress": "15.6.0",
|
||||||
"eslint": "9.39.0",
|
"eslint": "9.39.1",
|
||||||
"globals": "16.5.0",
|
"globals": "16.5.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"pnpm": "10.20.0",
|
"pnpm": "10.22.0",
|
||||||
"start-server-and-test": "2.1.2"
|
"start-server-and-test": "2.1.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./built/boot/entry.js",
|
"start": "node ./built/boot/entry.js",
|
||||||
|
"start:inspect": "node --inspect ./built/boot/entry.js",
|
||||||
"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
||||||
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||||
|
|
@ -39,17 +40,17 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@swc/core-darwin-arm64": "1.14.0",
|
"@swc/core-darwin-arm64": "1.15.2",
|
||||||
"@swc/core-darwin-x64": "1.14.0",
|
"@swc/core-darwin-x64": "1.15.2",
|
||||||
"@swc/core-freebsd-x64": "1.3.11",
|
"@swc/core-freebsd-x64": "1.3.11",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.14.0",
|
"@swc/core-linux-arm-gnueabihf": "1.15.2",
|
||||||
"@swc/core-linux-arm64-gnu": "1.14.0",
|
"@swc/core-linux-arm64-gnu": "1.15.2",
|
||||||
"@swc/core-linux-arm64-musl": "1.14.0",
|
"@swc/core-linux-arm64-musl": "1.15.2",
|
||||||
"@swc/core-linux-x64-gnu": "1.14.0",
|
"@swc/core-linux-x64-gnu": "1.15.2",
|
||||||
"@swc/core-linux-x64-musl": "1.14.0",
|
"@swc/core-linux-x64-musl": "1.15.2",
|
||||||
"@swc/core-win32-arm64-msvc": "1.14.0",
|
"@swc/core-win32-arm64-msvc": "1.15.2",
|
||||||
"@swc/core-win32-ia32-msvc": "1.14.0",
|
"@swc/core-win32-ia32-msvc": "1.15.2",
|
||||||
"@swc/core-win32-x64-msvc": "1.14.0",
|
"@swc/core-win32-x64-msvc": "1.15.2",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.9",
|
"bufferutil": "4.0.9",
|
||||||
|
|
@ -69,8 +70,8 @@
|
||||||
"utf-8-validate": "6.0.5"
|
"utf-8-validate": "6.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.922.0",
|
"@aws-sdk/client-s3": "3.936.0",
|
||||||
"@aws-sdk/lib-storage": "3.922.0",
|
"@aws-sdk/lib-storage": "3.936.0",
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@fastify/accepts": "5.0.3",
|
"@fastify/accepts": "5.0.3",
|
||||||
"@fastify/cookie": "11.0.2",
|
"@fastify/cookie": "11.0.2",
|
||||||
|
|
@ -82,18 +83,18 @@
|
||||||
"@fastify/view": "10.0.2",
|
"@fastify/view": "10.0.2",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
"@napi-rs/canvas": "0.1.81",
|
"@napi-rs/canvas": "0.1.82",
|
||||||
"@nestjs/common": "11.1.8",
|
"@nestjs/common": "11.1.9",
|
||||||
"@nestjs/core": "11.1.8",
|
"@nestjs/core": "11.1.9",
|
||||||
"@nestjs/testing": "11.1.8",
|
"@nestjs/testing": "11.1.9",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sentry/node": "10.22.0",
|
"@sentry/node": "10.26.0",
|
||||||
"@sentry/profiling-node": "10.22.0",
|
"@sentry/profiling-node": "10.26.0",
|
||||||
"@simplewebauthn/server": "12.0.0",
|
"@simplewebauthn/server": "12.0.0",
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "2.5.0",
|
"@smithy/node-http-handler": "2.5.0",
|
||||||
"@swc/cli": "0.7.8",
|
"@swc/cli": "0.7.9",
|
||||||
"@swc/core": "1.14.0",
|
"@swc/core": "1.15.2",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@types/redis-info": "3.0.3",
|
"@types/redis-info": "3.0.3",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
|
|
@ -103,24 +104,23 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.63.0",
|
"bullmq": "5.63.2",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.6.2",
|
"chalk": "5.6.2",
|
||||||
"chalk-template": "1.1.2",
|
"chalk-template": "1.1.2",
|
||||||
"chokidar": "4.0.3",
|
"chokidar": "4.0.3",
|
||||||
"cli-highlight": "2.1.11",
|
|
||||||
"color-convert": "2.0.1",
|
"color-convert": "2.0.1",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "5.6.1",
|
"fastify": "5.6.2",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "21.0.0",
|
"file-type": "21.1.1",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.4",
|
"form-data": "4.0.5",
|
||||||
"got": "14.6.1",
|
"got": "14.6.4",
|
||||||
"happy-dom": "20.0.10",
|
"happy-dom": "20.0.10",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
"ip-cidr": "4.0.2",
|
"ip-cidr": "4.0.2",
|
||||||
"ipaddr.js": "2.2.0",
|
"ipaddr.js": "2.2.0",
|
||||||
"is-svg": "5.1.0",
|
"is-svg": "5.1.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.1",
|
||||||
"jsdom": "26.1.0",
|
"jsdom": "26.1.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.3",
|
"jsonld": "8.3.3",
|
||||||
|
|
@ -161,7 +161,7 @@
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.22.1",
|
"re2": "1.22.3",
|
||||||
"redis-info": "3.1.0",
|
"redis-info": "3.1.0",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.2.2",
|
||||||
|
|
@ -170,8 +170,8 @@
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"secure-json-parse": "3.0.2",
|
"secure-json-parse": "3.0.2",
|
||||||
"sharp": "0.33.5",
|
|
||||||
"semver": "7.7.3",
|
"semver": "7.7.3",
|
||||||
|
"sharp": "0.33.5",
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
|
|
@ -191,7 +191,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@nestjs/platform-express": "10.4.20",
|
"@nestjs/platform-express": "10.4.20",
|
||||||
"@sentry/vue": "10.22.0",
|
"@sentry/vue": "10.26.0",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
"@swc/jest": "0.2.39",
|
"@swc/jest": "0.2.39",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
|
@ -210,7 +210,7 @@
|
||||||
"@types/jsrsasign": "10.5.15",
|
"@types/jsrsasign": "10.5.15",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@types/nodemailer": "6.4.21",
|
"@types/nodemailer": "6.4.21",
|
||||||
"@types/oauth": "0.9.6",
|
"@types/oauth": "0.9.6",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
|
|
@ -231,8 +231,8 @@
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
|
|
@ -240,7 +240,7 @@
|
||||||
"fkill": "9.0.0",
|
"fkill": "9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"pid-port": "1.0.2",
|
"pid-port": "1.0.2",
|
||||||
"simple-oauth2": "5.1.0",
|
"simple-oauth2": "5.1.0",
|
||||||
"supertest": "7.1.4"
|
"supertest": "7.1.4"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ function greet() {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
console.log(' Misskey is an open-source decentralized microblogging platform.');
|
console.log(' Misskey is an open-source decentralized microblogging platform.');
|
||||||
console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
|
console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please consider donating to support dev. https://misskey-hub.net/docs/donate/'));
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
|
console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import * as fs from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as nsfw from 'nsfwjs';
|
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
import { Mutex } from 'async-mutex';
|
import { Mutex } from 'async-mutex';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { NSFWJS, PredictionType } from 'nsfwjs';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
|
@ -21,7 +21,7 @@ let isSupportedCpu: undefined | boolean = undefined;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AiService {
|
export class AiService {
|
||||||
private model: nsfw.NSFWJS;
|
private model: NSFWJS;
|
||||||
private modelLoadMutex: Mutex = new Mutex();
|
private modelLoadMutex: Mutex = new Mutex();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -29,7 +29,7 @@ export class AiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async detectSensitive(source: string | Buffer): Promise<nsfw.PredictionType[] | null> {
|
public async detectSensitive(source: string | Buffer): Promise<PredictionType[] | null> {
|
||||||
try {
|
try {
|
||||||
if (isSupportedCpu === undefined) {
|
if (isSupportedCpu === undefined) {
|
||||||
isSupportedCpu = await this.computeIsSupportedCpu();
|
isSupportedCpu = await this.computeIsSupportedCpu();
|
||||||
|
|
@ -44,6 +44,7 @@ export class AiService {
|
||||||
tf.env().global.fetch = fetch;
|
tf.env().global.fetch = fetch;
|
||||||
|
|
||||||
if (this.model == null) {
|
if (this.model == null) {
|
||||||
|
const nsfw = await import('nsfwjs');
|
||||||
await this.modelLoadMutex.runExclusive(async () => {
|
await this.modelLoadMutex.runExclusive(async () => {
|
||||||
if (this.model == null) {
|
if (this.model == null) {
|
||||||
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,9 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
InsertQueryBuilder,
|
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
QueryRunner,
|
|
||||||
Repository,
|
Repository,
|
||||||
SelectQueryBuilder,
|
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
|
|
||||||
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
|
||||||
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
|
||||||
import {
|
|
||||||
RawSqlResultsToEntityTransformer,
|
|
||||||
} from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
|
||||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||||
|
|
@ -96,66 +87,12 @@ import { MiWebhook } from '@/models/Webhook.js';
|
||||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||||
|
|
||||||
export interface MiRepository<T extends ObjectLiteral> {
|
export interface MiRepository<T extends ObjectLiteral> {
|
||||||
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
|
|
||||||
|
|
||||||
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
||||||
|
|
||||||
insertOneImpl(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>, queryRunner?: QueryRunner): Promise<T>;
|
|
||||||
|
|
||||||
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const miRepository = {
|
export const miRepository = {
|
||||||
createTableColumnNames() {
|
|
||||||
return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName);
|
|
||||||
},
|
|
||||||
async insertOne(entity, findOptions?) {
|
async insertOne(entity, findOptions?) {
|
||||||
const opt = this.manager.connection.options as PostgresConnectionOptions;
|
return await this.insert(entity).then(x => this.findOneOrFail({ where: x.identifiers[0], ...findOptions }));
|
||||||
if (opt.replication) {
|
|
||||||
const queryRunner = this.manager.connection.createQueryRunner('master');
|
|
||||||
try {
|
|
||||||
return this.insertOneImpl(entity, findOptions, queryRunner);
|
|
||||||
} finally {
|
|
||||||
await queryRunner.release();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.insertOneImpl(entity, findOptions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async insertOneImpl(entity, findOptions?, queryRunner?) {
|
|
||||||
// ---- insert + returningの結果を共通テーブル式(CTE)に保持するクエリを生成 ----
|
|
||||||
|
|
||||||
const queryBuilder = this.createQueryBuilder().insert().values(entity);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
|
||||||
const name = mainAlias.name;
|
|
||||||
mainAlias.name = 't';
|
|
||||||
const columnNames = this.createTableColumnNames();
|
|
||||||
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
|
||||||
|
|
||||||
// ---- 共通テーブル式(CTE)から結果を取得 ----
|
|
||||||
const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
|
||||||
this.selectAliasColumnNames(queryBuilder, builder);
|
|
||||||
if (findOptions) {
|
|
||||||
builder.setFindOptions(findOptions);
|
|
||||||
}
|
|
||||||
const raw = await builder.execute();
|
|
||||||
mainAlias.name = name;
|
|
||||||
const relationId = await new RelationIdLoader(builder.connection, this.queryRunner, builder.expressionMap.relationIdAttributes).load(raw);
|
|
||||||
const relationCount = await new RelationCountLoader(builder.connection, this.queryRunner, builder.expressionMap.relationCountAttributes).load(raw);
|
|
||||||
const result = new RawSqlResultsToEntityTransformer(builder.expressionMap, builder.connection.driver, relationId, relationCount, this.queryRunner).transform(raw, mainAlias);
|
|
||||||
return result[0];
|
|
||||||
},
|
|
||||||
selectAliasColumnNames(queryBuilder, builder) {
|
|
||||||
let selectOrAddSelect = (selection: string, selectionAliasName?: string) => {
|
|
||||||
selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName);
|
|
||||||
return builder.select(selection, selectionAliasName);
|
|
||||||
};
|
|
||||||
for (const columnName of this.createTableColumnNames()) {
|
|
||||||
selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
} satisfies MiRepository<ObjectLiteral>;
|
} satisfies MiRepository<ObjectLiteral>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
// https://github.com/typeorm/typeorm/issues/2400
|
// https://github.com/typeorm/typeorm/issues/2400
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import { DataSource, Logger, type QueryRunner } from 'typeorm';
|
import { DataSource, Logger, type QueryRunner } from 'typeorm';
|
||||||
import * as highlight from 'cli-highlight';
|
|
||||||
import { entities as charts } from '@/core/chart/entities.js';
|
import { entities as charts } from '@/core/chart/entities.js';
|
||||||
import { Config } from '@/config.js';
|
import { Config } from '@/config.js';
|
||||||
import MisskeyLogger from '@/logger.js';
|
import MisskeyLogger from '@/logger.js';
|
||||||
|
|
@ -25,7 +24,7 @@ import { MiAuthSession } from '@/models/AuthSession.js';
|
||||||
import { MiBlocking } from '@/models/Blocking.js';
|
import { MiBlocking } from '@/models/Blocking.js';
|
||||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||||
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
||||||
import { MiChannelMuting } from "@/models/ChannelMuting.js";
|
import { MiChannelMuting } from '@/models/ChannelMuting.js';
|
||||||
import { MiClip } from '@/models/Clip.js';
|
import { MiClip } from '@/models/Clip.js';
|
||||||
import { MiClipNote } from '@/models/ClipNote.js';
|
import { MiClipNote } from '@/models/ClipNote.js';
|
||||||
import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
||||||
|
|
@ -101,12 +100,6 @@ export type LoggerProps = {
|
||||||
printReplicationMode?: boolean,
|
printReplicationMode?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function highlightSql(sql: string) {
|
|
||||||
return highlight.highlight(sql, {
|
|
||||||
language: 'sql', ignoreIllegals: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function truncateSql(sql: string) {
|
function truncateSql(sql: string) {
|
||||||
return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql;
|
return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql;
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +125,7 @@ class MyCustomLogger implements Logger {
|
||||||
modded = truncateSql(modded);
|
modded = truncateSql(modded);
|
||||||
}
|
}
|
||||||
|
|
||||||
return highlightSql(modded);
|
return modded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
||||||
|
|
@ -295,8 +295,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope;
|
if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope;
|
||||||
|
|
||||||
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
||||||
// TODO: ちゃんと数える
|
const count = (arr: (string[] | string)[]) => {
|
||||||
const length = JSON.stringify(mutedWords).length;
|
let length = 0;
|
||||||
|
for (const item of arr) {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
length += item.length;
|
||||||
|
} else if (Array.isArray(item)) {
|
||||||
|
for (const subItem of item) {
|
||||||
|
length += subItem.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
};
|
||||||
|
const length = count(mutedWords);
|
||||||
if (length > limit) {
|
if (length > limit) {
|
||||||
throw new ApiError(meta.errors.tooManyMutedWords);
|
throw new ApiError(meta.errors.tooManyMutedWords);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,18 @@ export const meta = {
|
||||||
code: 'CANNOT_RENOTE_TO_EXTERNAL',
|
code: 'CANNOT_RENOTE_TO_EXTERNAL',
|
||||||
id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7',
|
id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scheduledAtRequired: {
|
||||||
|
message: 'scheduledAt is required when isActuallyScheduled is true.',
|
||||||
|
code: 'SCHEDULED_AT_REQUIRED',
|
||||||
|
id: '15e28a55-e74c-4d65-89b7-8880cdaaa87d',
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduledAtMustBeInFuture: {
|
||||||
|
message: 'scheduledAt must be in the future.',
|
||||||
|
code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE',
|
||||||
|
id: 'e4bed6c9-017e-4934-aed0-01c22cc60ec1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
|
|
@ -252,6 +264,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
||||||
case 'c3275f19-4558-4c59-83e1-4f684b5fab66':
|
case 'c3275f19-4558-4c59-83e1-4f684b5fab66':
|
||||||
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
||||||
|
case '94a89a43-3591-400a-9c17-dd166e71fdfa':
|
||||||
|
throw new ApiError(meta.errors.scheduledAtRequired);
|
||||||
|
case 'b34d0c1b-996f-4e34-a428-c636d98df457':
|
||||||
|
throw new ApiError(meta.errors.scheduledAtMustBeInFuture);
|
||||||
default:
|
default:
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,18 @@ export const meta = {
|
||||||
code: 'TOO_MANY_SCHEDULED_NOTES',
|
code: 'TOO_MANY_SCHEDULED_NOTES',
|
||||||
id: '02f5df79-08ae-4a33-8524-f1503c8f6212',
|
id: '02f5df79-08ae-4a33-8524-f1503c8f6212',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scheduledAtRequired: {
|
||||||
|
message: 'scheduledAt is required when isActuallyScheduled is true.',
|
||||||
|
code: 'SCHEDULED_AT_REQUIRED',
|
||||||
|
id: 'fe9737d5-cc41-498c-af9d-149207307530',
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduledAtMustBeInFuture: {
|
||||||
|
message: 'scheduledAt must be in the future.',
|
||||||
|
code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE',
|
||||||
|
id: 'ed1a6673-d0d1-4364-aaae-9bf3f139cbc5',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
|
|
@ -295,6 +307,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.containsTooManyMentions);
|
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||||
case 'bacdf856-5c51-4159-b88a-804fa5103be5':
|
case 'bacdf856-5c51-4159-b88a-804fa5103be5':
|
||||||
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
throw new ApiError(meta.errors.tooManyScheduledNotes);
|
||||||
|
case '94a89a43-3591-400a-9c17-dd166e71fdfa':
|
||||||
|
throw new ApiError(meta.errors.scheduledAtRequired);
|
||||||
|
case 'b34d0c1b-996f-4e34-a428-c636d98df457':
|
||||||
|
throw new ApiError(meta.errors.scheduledAtMustBeInFuture);
|
||||||
default:
|
default:
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('auth_key', this.serverSettings.deeplAuthKey);
|
|
||||||
params.append('text', note.text);
|
params.append('text', note.text);
|
||||||
params.append('target_lang', targetLang);
|
params.append('target_lang', targetLang);
|
||||||
|
|
||||||
|
|
@ -104,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const res = await this.httpRequestService.send(endpoint, {
|
const res = await this.httpRequestService.send(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Authorization': `DeepL-Auth-Key ${this.serverSettings.deeplAuthKey}`,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
Accept: 'application/json, */*',
|
Accept: 'application/json, */*',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ class ChannelChannel extends Channel {
|
||||||
private async onNote(note: Packed<'Note'>) {
|
private async onNote(note: Packed<'Note'>) {
|
||||||
if (note.channelId !== this.channelId) return;
|
if (note.channelId !== this.channelId) return;
|
||||||
|
|
||||||
|
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||||
|
if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return;
|
||||||
|
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||||
|
|
||||||
if (this.isNoteMutedOrBlocked(note)) return;
|
if (this.isNoteMutedOrBlocked(note)) return;
|
||||||
|
|
||||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntitySer
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
|
@ -918,7 +918,7 @@ export class ClientServerService {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.setErrorHandler(async (error, request, reply) => {
|
fastify.setErrorHandler<FastifyError>(async (error, request, reply) => {
|
||||||
const errId = randomUUID();
|
const errId = randomUUID();
|
||||||
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, {
|
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, {
|
||||||
path: request.routeOptions.url,
|
path: request.routeOptions.url,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
|
|
||||||
//#region Script
|
//#region Script
|
||||||
async function importAppScript() {
|
async function importAppScript() {
|
||||||
await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts')
|
await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/boot.ts')
|
||||||
.catch(async e => {
|
.catch(async e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
renderError('APP_IMPORT');
|
renderError('APP_IMPORT');
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ html
|
||||||
meta(name='theme-color-orig' content= themeColor || '#86b300')
|
meta(name='theme-color-orig' content= themeColor || '#86b300')
|
||||||
meta(property='og:site_name' content= instanceName || 'Misskey')
|
meta(property='og:site_name' content= instanceName || 'Misskey')
|
||||||
meta(property='instance_url' content= instanceUrl)
|
meta(property='instance_url' content= instanceUrl)
|
||||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
meta(name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover')
|
||||||
meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
|
meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
|
||||||
link(rel='icon' href= icon || '/favicon.ico')
|
link(rel='icon' href= icon || '/favicon.ico')
|
||||||
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
|
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ services:
|
||||||
retries: 20
|
retries: 20
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:18-alpine
|
||||||
env_file:
|
env_file:
|
||||||
- ./.config/docker.env
|
- ./.config/docker.env
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ services:
|
||||||
- "127.0.0.1:56312:6379"
|
- "127.0.0.1:56312:6379"
|
||||||
|
|
||||||
dbtest:
|
dbtest:
|
||||||
image: postgres:15
|
image: postgres:18
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:54312:5432"
|
- "127.0.0.1:54312:5432"
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"rollup": "4.52.5",
|
"rollup": "4.53.3",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"magic-string": "0.30.21",
|
"magic-string": "0.30.21",
|
||||||
"vite": "7.1.11"
|
"vite": "7.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
"@rollup/plugin-replace": "6.0.3",
|
"@rollup/plugin-replace": "6.0.3",
|
||||||
"@rollup/pluginutils": "5.3.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@vitejs/plugin-vue": "6.0.2",
|
||||||
"@vue/compiler-sfc": "3.5.22",
|
"@vue/compiler-sfc": "3.5.24",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
|
|
@ -26,16 +26,16 @@
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.52.5",
|
"rollup": "4.53.3",
|
||||||
"sass": "1.93.3",
|
"sass": "1.94.1",
|
||||||
"shiki": "3.14.0",
|
"shiki": "3.15.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"uuid": "13.0.0",
|
"uuid": "13.0.0",
|
||||||
"vite": "7.1.11",
|
"vite": "7.2.2",
|
||||||
"vue": "3.5.22"
|
"vue": "3.5.24"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
|
|
@ -43,14 +43,14 @@
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/micromatch": "4.0.10",
|
"@types/micromatch": "4.0.10",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"@vue/runtime-core": "3.5.22",
|
"@vue/runtime-core": "3.5.24",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
|
|
@ -59,14 +59,14 @@
|
||||||
"happy-dom": "20.0.10",
|
"happy-dom": "20.0.10",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.11.6",
|
"msw": "2.12.2",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"start-server-and-test": "2.1.2",
|
"start-server-and-test": "2.1.2",
|
||||||
"tsx": "4.20.6",
|
"tsx": "4.20.6",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "3.1.2",
|
"vue-component-type-helpers": "3.1.4",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "3.1.2"
|
"vue-tsc": "3.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"eslint-plugin-vue": "10.5.1",
|
"eslint-plugin-vue": "10.5.1",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vue-eslint-parser": "10.2.0"
|
"vue-eslint-parser": "10.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -35,6 +35,6 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"vue": "3.5.22"
|
"vue": "3.5.24"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.3",
|
"@rollup/plugin-replace": "6.0.3",
|
||||||
"@rollup/pluginutils": "5.3.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
"@sentry/vue": "10.22.0",
|
"@sentry/vue": "10.26.0",
|
||||||
"@syuilo/aiscript": "1.1.2",
|
"@syuilo/aiscript": "1.2.0",
|
||||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@vitejs/plugin-vue": "6.0.2",
|
||||||
"@vue/compiler-sfc": "3.5.22",
|
"@vue/compiler-sfc": "3.5.24",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||||
"analytics": "0.8.19",
|
"analytics": "0.8.19",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"chartjs-chart-matrix": "3.0.0",
|
"chartjs-chart-matrix": "3.0.0",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "13.3.3",
|
"chromatic": "13.3.4",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.1.0",
|
"cropperjs": "2.1.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"magic-string": "0.30.21",
|
"magic-string": "0.30.21",
|
||||||
"matter-js": "0.20.0",
|
"matter-js": "0.20.0",
|
||||||
"mediabunny": "1.24.2",
|
"mediabunny": "1.25.0",
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"misskey-bubble-game": "workspace:*",
|
"misskey-bubble-game": "workspace:*",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
|
|
@ -67,21 +67,21 @@
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"qr-code-styling": "1.9.2",
|
"qr-code-styling": "1.9.2",
|
||||||
"qr-scanner": "1.4.2",
|
"qr-scanner": "1.4.2",
|
||||||
"rollup": "4.52.5",
|
"rollup": "4.53.3",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"sass": "1.93.3",
|
"sass": "1.94.1",
|
||||||
"shiki": "3.14.0",
|
"shiki": "3.15.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.181.0",
|
"three": "0.181.2",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "7.1.11",
|
"vite": "7.2.2",
|
||||||
"vue": "3.5.22",
|
"vue": "3.5.24",
|
||||||
"vuedraggable": "next",
|
"vuedraggable": "next",
|
||||||
"wanakana": "5.3.1"
|
"wanakana": "5.3.1"
|
||||||
},
|
},
|
||||||
|
|
@ -110,21 +110,21 @@
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/matter-js": "0.20.2",
|
"@types/matter-js": "0.20.2",
|
||||||
"@types/micromatch": "4.0.10",
|
"@types/micromatch": "4.0.10",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"@vue/compiler-core": "3.5.22",
|
"@vue/compiler-core": "3.5.24",
|
||||||
"@vue/runtime-core": "3.5.22",
|
"@vue/runtime-core": "3.5.24",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"cypress": "15.5.0",
|
"cypress": "15.6.0",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-vue": "10.5.1",
|
"eslint-plugin-vue": "10.5.1",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
|
|
@ -132,9 +132,9 @@
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"minimatch": "10.1.1",
|
"minimatch": "10.1.1",
|
||||||
"msw": "2.11.6",
|
"msw": "2.12.2",
|
||||||
"msw-storybook-addon": "2.0.6",
|
"msw-storybook-addon": "2.0.6",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
|
|
@ -147,8 +147,8 @@
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.2.4",
|
"vitest": "3.2.4",
|
||||||
"vitest-fetch-mock": "0.4.5",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
"vue-component-type-helpers": "3.1.2",
|
"vue-component-type-helpers": "3.1.4",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
"vue-tsc": "3.1.2"
|
"vue-tsc": "3.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,29 +40,77 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string })
|
||||||
CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value),
|
CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value),
|
||||||
LOCALE: values.STR(lang),
|
LOCALE: values.STR(lang),
|
||||||
SERVER_URL: values.STR(url),
|
SERVER_URL: values.STR(url),
|
||||||
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
|
'Mk:dialog': values.FN_NATIVE(async ([_title, _text, _type]) => {
|
||||||
utils.assertString(title);
|
let title: string | undefined = undefined;
|
||||||
utils.assertString(text);
|
let text: string | undefined = undefined;
|
||||||
if (type != null) {
|
let type: typeof DIALOG_TYPES[number] = 'info';
|
||||||
assertStringAndIsIn(type, DIALOG_TYPES);
|
|
||||||
|
if (_title != null) {
|
||||||
|
if (utils.isString(_title)) {
|
||||||
|
title = _title.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_text != null) {
|
||||||
|
if (utils.isString(_text)) {
|
||||||
|
text = _text.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type != null) {
|
||||||
|
if (utils.isString(_type)) {
|
||||||
|
assertStringAndIsIn(_type, DIALOG_TYPES);
|
||||||
|
type = _type.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await os.alert({
|
await os.alert({
|
||||||
type: type ? type.value : 'info',
|
type,
|
||||||
title: title.value,
|
title,
|
||||||
text: text.value,
|
text,
|
||||||
});
|
});
|
||||||
return values.NULL;
|
return values.NULL;
|
||||||
}),
|
}),
|
||||||
'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => {
|
'Mk:confirm': values.FN_NATIVE(async ([_title, _text, _type]) => {
|
||||||
utils.assertString(title);
|
let title: string | undefined = undefined;
|
||||||
utils.assertString(text);
|
let text: string | undefined = undefined;
|
||||||
if (type != null) {
|
let type: typeof DIALOG_TYPES[number] = 'question';
|
||||||
assertStringAndIsIn(type, DIALOG_TYPES);
|
|
||||||
|
if (_title != null) {
|
||||||
|
if (utils.isString(_title)) {
|
||||||
|
title = _title.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_text != null) {
|
||||||
|
if (utils.isString(_text)) {
|
||||||
|
text = _text.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type != null) {
|
||||||
|
if (utils.isString(_type)) {
|
||||||
|
assertStringAndIsIn(_type, DIALOG_TYPES);
|
||||||
|
type = _type.value;
|
||||||
|
} else {
|
||||||
|
utils.assertNull(_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: type ? type.value : 'question',
|
type,
|
||||||
title: title.value,
|
title,
|
||||||
text: text.value,
|
text,
|
||||||
});
|
});
|
||||||
return confirm.canceled ? values.FALSE : values.TRUE;
|
return confirm.canceled ? values.FALSE : values.TRUE;
|
||||||
}),
|
}),
|
||||||
|
|
@ -76,15 +124,23 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string })
|
||||||
if (ep.value.includes('://') || ep.value.includes('..')) {
|
if (ep.value.includes('://') || ep.value.includes('..')) {
|
||||||
throw new errors.AiScriptRuntimeError('invalid endpoint');
|
throw new errors.AiScriptRuntimeError('invalid endpoint');
|
||||||
}
|
}
|
||||||
if (token) {
|
|
||||||
|
let actualToken: string | null = null;
|
||||||
|
if (token != null && !utils.isNull(token)) {
|
||||||
utils.assertString(token);
|
utils.assertString(token);
|
||||||
// バグがあればundefinedもあり得るため念のため
|
// バグがあればundefinedもあり得るため念のため
|
||||||
if (typeof token.value !== 'string') throw new Error('invalid token');
|
if (typeof token.value !== 'string') throw new errors.AiScriptRuntimeError('invalid token');
|
||||||
|
actualToken = token.value;
|
||||||
}
|
}
|
||||||
const actualToken: string | null = token?.value ?? opts.token ?? null;
|
|
||||||
|
if (actualToken == null) {
|
||||||
|
actualToken = opts.token ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
if (param == null) {
|
if (param == null) {
|
||||||
throw new errors.AiScriptRuntimeError('expected param');
|
throw new errors.AiScriptRuntimeError('expected param');
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.assertObject(param);
|
utils.assertObject(param);
|
||||||
return misskeyApi(ep.value as keyof Misskey.Endpoints, utils.valToJs(param) as object, actualToken).then(res => {
|
return misskeyApi(ep.value as keyof Misskey.Endpoints, utils.valToJs(param) as object, actualToken).then(res => {
|
||||||
return utils.jsToVal(res);
|
return utils.jsToVal(res);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, watch, version as vueVersion } from 'vue';
|
import { watch, version as vueVersion } from 'vue';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import { version, lang, apiUrl, isSafeMode } from '@@/js/config.js';
|
import { version, lang, apiUrl, isSafeMode } from '@@/js/config.js';
|
||||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||||
|
|
@ -15,11 +15,11 @@ import directives from '@/directives/index.js';
|
||||||
import components from '@/components/index.js';
|
import components from '@/components/index.js';
|
||||||
import { applyTheme } from '@/theme.js';
|
import { applyTheme } from '@/theme.js';
|
||||||
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
|
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
|
||||||
import { updateI18n, i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { refreshCurrentAccount, login } from '@/accounts.js';
|
import { refreshCurrentAccount, login } from '@/accounts.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { deviceKind, updateDeviceKind } from '@/utility/device-kind.js';
|
import { updateDeviceKind } from '@/utility/device-kind.js';
|
||||||
import { reloadChannel } from '@/utility/unison-reload.js';
|
import { reloadChannel } from '@/utility/unison-reload.js';
|
||||||
import { getUrlWithoutLoginId } from '@/utility/login-id.js';
|
import { getUrlWithoutLoginId } from '@/utility/login-id.js';
|
||||||
import { getAccountFromId } from '@/utility/get-account-from-id.js';
|
import { getAccountFromId } from '@/utility/get-account-from-id.js';
|
||||||
|
|
@ -109,13 +109,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||||
else window.location.reload();
|
else window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
// If mobile, insert the viewport meta tag
|
|
||||||
if (['smartphone', 'tablet'].includes(deviceKind)) {
|
|
||||||
const viewport = window.document.getElementsByName('viewport').item(0);
|
|
||||||
viewport.setAttribute('content',
|
|
||||||
`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region Set lang attr
|
//#region Set lang attr
|
||||||
const html = window.document.documentElement;
|
const html = window.document.documentElement;
|
||||||
html.setAttribute('lang', lang);
|
html.setAttribute('lang', lang);
|
||||||
|
|
@ -160,8 +153,15 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
if (!isSafeMode) {
|
||||||
|
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||||
|
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||||
|
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
|
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
|
||||||
// NOTE: この処理は必ずダークモード判定処理より後に来ること(初回のテーマ適用のため)
|
// NOTE: この処理は必ずダークモード判定処理より後に来ること(初回のテーマ適用のため)
|
||||||
|
// NOTE: この処理は必ずサーバーテーマ適用処理より後に来ること(二重applyTheme発火を防ぐため)
|
||||||
// see: https://github.com/misskey-dev/misskey/issues/16562
|
// see: https://github.com/misskey-dev/misskey/issues/16562
|
||||||
watch(store.r.darkMode, (darkMode) => {
|
watch(store.r.darkMode, (darkMode) => {
|
||||||
const theme = (() => {
|
const theme = (() => {
|
||||||
|
|
@ -178,26 +178,17 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||||
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||||
|
|
||||||
if (!isSafeMode) {
|
if (!isSafeMode) {
|
||||||
const darkTheme = prefer.model('darkTheme');
|
watch(prefer.r.darkTheme, (theme) => {
|
||||||
const lightTheme = prefer.model('lightTheme');
|
|
||||||
|
|
||||||
watch(darkTheme, (theme) => {
|
|
||||||
if (store.s.darkMode) {
|
if (store.s.darkMode) {
|
||||||
applyTheme(theme ?? defaultDarkTheme);
|
applyTheme(theme ?? defaultDarkTheme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(lightTheme, (theme) => {
|
watch(prefer.r.lightTheme, (theme) => {
|
||||||
if (!store.s.darkMode) {
|
if (!store.s.darkMode) {
|
||||||
applyTheme(theme ?? defaultLightTheme);
|
applyTheme(theme ?? defaultLightTheme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchInstanceMetaPromise.then(() => {
|
|
||||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
|
||||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
|
||||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(prefer.r.overridedDeviceKind, (kind) => {
|
watch(prefer.r.overridedDeviceKind, (kind) => {
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,21 @@ async function onClick() {
|
||||||
await misskeyApi('following/delete', {
|
await misskeyApi('following/delete', {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
|
} else if (hasPendingFollowRequestFromYou.value) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.tsx.cancelFollowRequestConfirm({ name: props.user.name || props.user.username }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
wait.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await misskeyApi('following/requests/cancel', {
|
||||||
|
userId: props.user.id,
|
||||||
|
});
|
||||||
|
hasPendingFollowRequestFromYou.value = false;
|
||||||
} else {
|
} else {
|
||||||
if (prefer.s.alwaysConfirmFollow) {
|
if (prefer.s.alwaysConfirmFollow) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
|
@ -115,41 +130,34 @@ async function onClick() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPendingFollowRequestFromYou.value) {
|
await misskeyApi('following/create', {
|
||||||
await misskeyApi('following/requests/cancel', {
|
userId: props.user.id,
|
||||||
userId: props.user.id,
|
withReplies: prefer.s.defaultFollowWithReplies,
|
||||||
});
|
});
|
||||||
hasPendingFollowRequestFromYou.value = false;
|
emit('update:user', {
|
||||||
} else {
|
...props.user,
|
||||||
await misskeyApi('following/create', {
|
withReplies: prefer.s.defaultFollowWithReplies,
|
||||||
userId: props.user.id,
|
});
|
||||||
withReplies: prefer.s.defaultFollowWithReplies,
|
hasPendingFollowRequestFromYou.value = true;
|
||||||
});
|
|
||||||
emit('update:user', {
|
|
||||||
...props.user,
|
|
||||||
withReplies: prefer.s.defaultFollowWithReplies,
|
|
||||||
});
|
|
||||||
hasPendingFollowRequestFromYou.value = true;
|
|
||||||
|
|
||||||
if ($i == null) {
|
if ($i == null) {
|
||||||
wait.value = false;
|
wait.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
claimAchievement('following1');
|
claimAchievement('following1');
|
||||||
|
|
||||||
if ($i.followingCount >= 10) {
|
if ($i.followingCount >= 10) {
|
||||||
claimAchievement('following10');
|
claimAchievement('following10');
|
||||||
}
|
}
|
||||||
if ($i.followingCount >= 50) {
|
if ($i.followingCount >= 50) {
|
||||||
claimAchievement('following50');
|
claimAchievement('following50');
|
||||||
}
|
}
|
||||||
if ($i.followingCount >= 100) {
|
if ($i.followingCount >= 100) {
|
||||||
claimAchievement('following100');
|
claimAchievement('following100');
|
||||||
}
|
}
|
||||||
if ($i.followingCount >= 300) {
|
if ($i.followingCount >= 300) {
|
||||||
claimAchievement('following300');
|
claimAchievement('following300');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -608,11 +608,30 @@ async function toggleReactionAcceptance() {
|
||||||
//#region その他の設定メニューpopup
|
//#region その他の設定メニューpopup
|
||||||
function showOtherSettings() {
|
function showOtherSettings() {
|
||||||
let reactionAcceptanceIcon = 'ti ti-icons';
|
let reactionAcceptanceIcon = 'ti ti-icons';
|
||||||
|
let reactionAcceptanceCaption = '';
|
||||||
|
|
||||||
if (reactionAcceptance.value === 'likeOnly') {
|
switch (reactionAcceptance.value) {
|
||||||
reactionAcceptanceIcon = 'ti ti-heart _love';
|
case 'likeOnly':
|
||||||
} else if (reactionAcceptance.value === 'likeOnlyForRemote') {
|
reactionAcceptanceIcon = 'ti ti-heart _love';
|
||||||
reactionAcceptanceIcon = 'ti ti-heart-plus';
|
reactionAcceptanceCaption = i18n.ts.likeOnly;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'likeOnlyForRemote':
|
||||||
|
reactionAcceptanceIcon = 'ti ti-heart-plus';
|
||||||
|
reactionAcceptanceCaption = i18n.ts.likeOnlyForRemote;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'nonSensitiveOnly':
|
||||||
|
reactionAcceptanceCaption = i18n.ts.nonSensitiveOnly;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'nonSensitiveOnlyForLocalLikeOnlyForRemote':
|
||||||
|
reactionAcceptanceCaption = i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
reactionAcceptanceCaption = i18n.ts.all;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [{
|
const menuItems = [{
|
||||||
|
|
@ -624,6 +643,7 @@ function showOtherSettings() {
|
||||||
}, { type: 'divider' }, {
|
}, { type: 'divider' }, {
|
||||||
icon: reactionAcceptanceIcon,
|
icon: reactionAcceptanceIcon,
|
||||||
text: i18n.ts.reactionAcceptance,
|
text: i18n.ts.reactionAcceptance,
|
||||||
|
caption: reactionAcceptanceCaption,
|
||||||
action: () => {
|
action: () => {
|
||||||
toggleReactionAcceptance();
|
toggleReactionAcceptance();
|
||||||
},
|
},
|
||||||
|
|
@ -692,6 +712,7 @@ function removeVisibleUser(user) {
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
text.value = '';
|
text.value = '';
|
||||||
|
cw.value = null;
|
||||||
files.value = [];
|
files.value = [];
|
||||||
poll.value = null;
|
poll.value = null;
|
||||||
quoteId.value = null;
|
quoteId.value = null;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, h, ref, watch } from 'vue';
|
import { Comment, defineComponent, h, ref, watch } from 'vue';
|
||||||
import MkRadio from './MkRadio.vue';
|
import MkRadio from './MkRadio.vue';
|
||||||
import type { VNode } from 'vue';
|
import type { VNode } from 'vue';
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ export default defineComponent({
|
||||||
if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[];
|
if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[];
|
||||||
|
|
||||||
// vnodeのうちv-if=falseなものを除外する(trueになるものはoptionなど他typeになる)
|
// vnodeのうちv-if=falseなものを除外する(trueになるものはoptionなど他typeになる)
|
||||||
options = options.filter(vnode => !(typeof vnode.type === 'symbol' && vnode.type.description === 'v-cmt' && vnode.children === 'v-if'));
|
options = options.filter(vnode => vnode.type !== Comment);
|
||||||
|
|
||||||
return () => h('div', {
|
return () => h('div', {
|
||||||
class: [
|
class: [
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,11 @@ onUnmounted(() => {
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
.root {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg {
|
.bg {
|
||||||
|
|
|
||||||
|
|
@ -5,31 +5,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg v-if="type === 'info'" :class="[$style.icon, $style.info]" viewBox="0 0 160 160">
|
<svg v-if="type === 'info'" :class="[$style.icon, $style.info]" viewBox="0 0 160 160">
|
||||||
<path d="M80,108L80,72" style="--l:37;" :class="[$style.line, $style.animLine]"/>
|
<path d="M80,108L80,72" pathLength="1" :class="[$style.line, $style.animLine]"/>
|
||||||
<path d="M80,52L80,52" :class="[$style.line, $style.animFade]"/>
|
<path d="M80,52L80,52" :class="[$style.line, $style.animFade]"/>
|
||||||
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
|
<circle cx="80" cy="80" r="56" pathLength="1" :class="[$style.line, $style.animCircle]"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="type === 'question'" :class="[$style.icon, $style.question]" viewBox="0 0 160 160">
|
<svg v-else-if="type === 'question'" :class="[$style.icon, $style.question]" viewBox="0 0 160 160">
|
||||||
<path d="M80,92L79.991,84C88.799,83.98 96,76.962 96,68C96,59.038 88.953,52 79.991,52C71.03,52 64,59.038 64,68" style="--l:85;" :class="[$style.line, $style.animLine]"/>
|
<path d="M80,92L79.991,84C88.799,83.98 96,76.962 96,68C96,59.038 88.953,52 79.991,52C71.03,52 64,59.038 64,68" pathLength="1" :class="[$style.line, $style.animLine]"/>
|
||||||
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
|
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
|
||||||
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
|
<circle cx="80" cy="80" r="56" pathLength="1" :class="[$style.line, $style.animCircle]"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="type === 'success'" :class="[$style.icon, $style.success]" viewBox="0 0 160 160">
|
<svg v-else-if="type === 'success'" :class="[$style.icon, $style.success]" viewBox="0 0 160 160">
|
||||||
<path d="M62,80L74,92L98,68" style="--l:50;" :class="[$style.line, $style.animLine]"/>
|
<path d="M62,80L74,92L98,68" pathLength="1" :class="[$style.line, $style.animLine]"/>
|
||||||
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
|
<circle cx="80" cy="80" r="56" pathLength="1" :class="[$style.line, $style.animCircle]"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="type === 'warn'" :class="[$style.icon, $style.warn]" viewBox="0 0 160 160">
|
<svg v-else-if="type === 'warn'" :class="[$style.icon, $style.warn]" viewBox="0 0 160 160">
|
||||||
<path d="M80,64L80,88" style="--l:27;" :class="[$style.line, $style.animLine]"/>
|
<path d="M80,64L80,88" pathLength="1" :class="[$style.line, $style.animLine]"/>
|
||||||
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
|
<path d="M80,108L80,108" :class="[$style.line, $style.animFade]"/>
|
||||||
<path d="M92,28L144,116C148.709,124.65 144.083,135.82 136,136L24,136C15.917,135.82 11.291,124.65 16,116L68,28C73.498,19.945 86.771,19.945 92,28Z" style="--l:395;" :class="[$style.line, $style.animLine]"/>
|
<path d="M92,28L144,116C148.709,124.65 144.083,135.82 136,136L24,136C15.917,135.82 11.291,124.65 16,116L68,28C73.498,19.945 86.771,19.945 92,28Z" pathLength="1" :class="[$style.line, $style.animLine]"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="type === 'error'" :class="[$style.icon, $style.error]" viewBox="0 0 160 160">
|
<svg v-else-if="type === 'error'" :class="[$style.icon, $style.error]" viewBox="0 0 160 160">
|
||||||
<path d="M63,63L96,96" style="--l:47;--duration:0.3s;" :class="[$style.line, $style.animLine]"/>
|
<path d="M63,63L96,96" pathLength="1" style="--duration:0.3s;" :class="[$style.line, $style.animLine]"/>
|
||||||
<path d="M96,63L63,96" style="--l:47;--duration:0.3s;--delay:0.2s;" :class="[$style.line, $style.animLine]"/>
|
<path d="M96,63L63,96" pathLength="1" style="--duration:0.3s;--delay:0.2s;" :class="[$style.line, $style.animLine]"/>
|
||||||
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircle]"/>
|
<circle cx="80" cy="80" r="56" pathLength="1" :class="[$style.line, $style.animCircle]"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="type === 'waiting'" :class="[$style.icon, $style.waiting]" viewBox="0 0 160 160">
|
<svg v-else-if="type === 'waiting'" :class="[$style.icon, $style.waiting]" viewBox="0 0 160 160">
|
||||||
<circle cx="80" cy="80" r="56" style="--l:350;" :class="[$style.line, $style.animCircleWaiting]"/>
|
<circle cx="80" cy="80" r="56" pathLength="1" :class="[$style.line, $style.animCircleWaiting]"/>
|
||||||
<circle cx="80" cy="80" r="56" style="opacity: 0.25;" :class="[$style.line]"/>
|
<circle cx="80" cy="80" r="56" style="opacity: 0.25;" :class="[$style.line]"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -80,15 +80,15 @@ const props = defineProps<{
|
||||||
}
|
}
|
||||||
|
|
||||||
.animLine {
|
.animLine {
|
||||||
stroke-dasharray: var(--l);
|
stroke-dasharray: 1;
|
||||||
stroke-dashoffset: var(--l);
|
stroke-dashoffset: 1;
|
||||||
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
|
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
|
||||||
animation-delay: var(--delay, 0s);
|
animation-delay: var(--delay, 0s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.animCircle {
|
.animCircle {
|
||||||
stroke-dasharray: var(--l);
|
stroke-dasharray: 1;
|
||||||
stroke-dashoffset: var(--l);
|
stroke-dashoffset: 1;
|
||||||
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
|
animation: line var(--duration, 0.5s) cubic-bezier(0,0,.25,1) 1 forwards;
|
||||||
animation-delay: var(--delay, 0s);
|
animation-delay: var(--delay, 0s);
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
|
|
@ -96,8 +96,8 @@ const props = defineProps<{
|
||||||
}
|
}
|
||||||
|
|
||||||
.animCircleWaiting {
|
.animCircleWaiting {
|
||||||
stroke-dasharray: var(--l);
|
stroke-dasharray: 1;
|
||||||
stroke-dashoffset: calc(var(--l) / 1.5);
|
stroke-dashoffset: calc(1 / 1.5);
|
||||||
animation: waiting 0.75s linear infinite;
|
animation: waiting 0.75s linear infinite;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
@keyframes line {
|
@keyframes line {
|
||||||
0% {
|
0% {
|
||||||
stroke-dashoffset: var(--l);
|
stroke-dashoffset: 1;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
|
|
@ -447,7 +447,6 @@ const headerTabs = computed(() => room.value ? [{
|
||||||
|
|
||||||
const headerActions = computed<PageHeaderItem[]>(() => [{
|
const headerActions = computed<PageHeaderItem[]>(() => [{
|
||||||
icon: 'ti ti-dots',
|
icon: 'ti ti-dots',
|
||||||
text: '',
|
|
||||||
handler: showMenu,
|
handler: showMenu,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onDeactivated, onUnmounted, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
|
import { computed, onDeactivated, onUnmounted, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { utils } from '@syuilo/aiscript';
|
||||||
|
import { compareVersions } from 'compare-versions';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
|
import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
|
||||||
|
|
@ -190,11 +192,21 @@ function start() {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIsLegacy(version: string | null): boolean {
|
||||||
|
if (version == null) return false;
|
||||||
|
try {
|
||||||
|
return compareVersions(version, '1.0.0') < 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
if (aiscript.value) aiscript.value.abort();
|
if (aiscript.value) aiscript.value.abort();
|
||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
|
|
||||||
const isLegacy = !flash.value.script.replaceAll(' ', '').startsWith('///@1.0.0');
|
const version = utils.getLangVersion(flash.value.script);
|
||||||
|
const isLegacy = version != null && getIsLegacy(version);
|
||||||
|
|
||||||
const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
|
const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,28 @@ function accept(user: Misskey.entities.UserLite) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(user: Misskey.entities.UserLite) {
|
async function reject(user: Misskey.entities.UserLite) {
|
||||||
os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.tsx.rejectFollowRequestConfirm({ name: user.name || user.username }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
|
||||||
paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel(user: Misskey.entities.UserLite) {
|
async function cancel(user: Misskey.entities.UserLite) {
|
||||||
os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.tsx.cancelFollowRequestConfirm({ name: user.name || user.username }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
await os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
|
||||||
paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ const paginator = markRaw(new Paginator('clips/list', {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const favoritesPaginator = markRaw(new Paginator('clips/my-favorites', {
|
const favoritesPaginator = markRaw(new Paginator('clips/my-favorites', {
|
||||||
|
// ページネーションに対応していない
|
||||||
|
noPaging: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,7 @@ definePage(() => ({
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageContent {
|
.pageContent {
|
||||||
|
contain: content;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
text: i18n.ts.createNew,
|
text: i18n.ts.createNew,
|
||||||
to: '/channels',
|
to: '/channels/new',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
os.popupMenu(items.filter(i => i != null), ev.currentTarget ?? ev.target);
|
os.popupMenu(items.filter(i => i != null), ev.currentTarget ?? ev.target);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { BroadcastChannel } from 'broadcast-channel';
|
||||||
import type { StorageProvider } from '@/preferences/manager.js';
|
import type { StorageProvider } from '@/preferences/manager.js';
|
||||||
import { cloudBackup } from '@/preferences/utility.js';
|
import { cloudBackup } from '@/preferences/utility.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
|
@ -12,6 +13,7 @@ import { $i } from '@/i.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { TAB_ID } from '@/tab-id.js';
|
import { TAB_ID } from '@/tab-id.js';
|
||||||
|
|
||||||
|
// クラウド同期用グループ名
|
||||||
const syncGroup = 'default';
|
const syncGroup = 'default';
|
||||||
|
|
||||||
const io: StorageProvider = {
|
const io: StorageProvider = {
|
||||||
|
|
@ -26,7 +28,6 @@ const io: StorageProvider = {
|
||||||
|
|
||||||
save: (ctx) => {
|
save: (ctx) => {
|
||||||
miLocalStorage.setItem('preferences', JSON.stringify(ctx.profile));
|
miLocalStorage.setItem('preferences', JSON.stringify(ctx.profile));
|
||||||
miLocalStorage.setItem('latestPreferencesUpdate', `${TAB_ID}/${Date.now()}`);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cloudGet: async (ctx) => {
|
cloudGet: async (ctx) => {
|
||||||
|
|
@ -99,33 +100,47 @@ const io: StorageProvider = {
|
||||||
|
|
||||||
export const prefer = new PreferencesManager(io, $i);
|
export const prefer = new PreferencesManager(io, $i);
|
||||||
|
|
||||||
let latestSyncedAt = Date.now();
|
//#region タブ間同期
|
||||||
|
let latestPreferencesUpdate: {
|
||||||
|
tabId: string;
|
||||||
|
timestamp: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
function syncBetweenTabs() {
|
const preferencesChannel = new BroadcastChannel<{
|
||||||
const latest = miLocalStorage.getItem('latestPreferencesUpdate');
|
type: 'preferencesUpdate';
|
||||||
if (latest == null) return;
|
tabId: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>('preferences');
|
||||||
|
|
||||||
const latestTab = latest.split('/')[0];
|
prefer.on('committed', () => {
|
||||||
const latestAt = parseInt(latest.split('/')[1]);
|
latestPreferencesUpdate = {
|
||||||
|
tabId: TAB_ID,
|
||||||
if (latestTab === TAB_ID) return;
|
timestamp: Date.now(),
|
||||||
if (latestAt <= latestSyncedAt) return;
|
};
|
||||||
|
preferencesChannel.postMessage({
|
||||||
prefer.reloadProfile();
|
type: 'preferencesUpdate',
|
||||||
|
tabId: TAB_ID,
|
||||||
latestSyncedAt = Date.now();
|
timestamp: latestPreferencesUpdate.timestamp,
|
||||||
|
});
|
||||||
if (_DEV_) console.log('prefer:synced');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setInterval(syncBetweenTabs, 5000);
|
|
||||||
|
|
||||||
window.document.addEventListener('visibilitychange', () => {
|
|
||||||
if (window.document.visibilityState === 'visible') {
|
|
||||||
syncBetweenTabs();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
preferencesChannel.addEventListener('message', (msg) => {
|
||||||
|
if (msg.type === 'preferencesUpdate') {
|
||||||
|
if (msg.tabId === TAB_ID) return;
|
||||||
|
if (latestPreferencesUpdate != null) {
|
||||||
|
if (msg.timestamp <= latestPreferencesUpdate.timestamp) return;
|
||||||
|
}
|
||||||
|
prefer.reloadProfile();
|
||||||
|
if (_DEV_) console.log('prefer:received update from other tab');
|
||||||
|
latestPreferencesUpdate = {
|
||||||
|
tabId: msg.tabId,
|
||||||
|
timestamp: msg.timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 定期クラウドバックアップ
|
||||||
let latestBackupAt = 0;
|
let latestBackupAt = 0;
|
||||||
|
|
||||||
window.setInterval(() => {
|
window.setInterval(() => {
|
||||||
|
|
@ -138,6 +153,7 @@ window.setInterval(() => {
|
||||||
latestBackupAt = Date.now();
|
latestBackupAt = Date.now();
|
||||||
});
|
});
|
||||||
}, 1000 * 60 * 3);
|
}, 1000 * 60 * 3);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
(window as any).prefer = prefer;
|
(window as any).prefer = prefer;
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ export const PREF_DEF = definePreferences({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
disableShowingAnimatedImages: {
|
disableShowingAnimatedImages: {
|
||||||
default: prefersReducedMotion,
|
default: false,
|
||||||
},
|
},
|
||||||
emojiStyle: {
|
emojiStyle: {
|
||||||
default: 'twemoji', // twemoji / fluentEmoji / native
|
default: 'twemoji', // twemoji / fluentEmoji / native
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
import { computed, onUnmounted, ref, watch } from 'vue';
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { host, version } from '@@/js/config.js';
|
import { host, version } from '@@/js/config.js';
|
||||||
import { PREF_DEF } from './def.js';
|
import { PREF_DEF } from './def.js';
|
||||||
import type { Ref, WritableComputedRef } from 'vue';
|
import type { Ref, WritableComputedRef } from 'vue';
|
||||||
|
|
@ -100,6 +101,14 @@ type PreferencesDefinitionRecord<Default, T = Default extends (...args: any) =>
|
||||||
|
|
||||||
export type PreferencesDefinition = Record<string, PreferencesDefinitionRecord<any>>;
|
export type PreferencesDefinition = Record<string, PreferencesDefinitionRecord<any>>;
|
||||||
|
|
||||||
|
type PreferencesManagerEvents = {
|
||||||
|
'committed': <K extends keyof PREF>(ctx: {
|
||||||
|
key: K;
|
||||||
|
value: ValueOf<K>;
|
||||||
|
oldValue: ValueOf<K>;
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export function definePreferences<T extends Record<string, unknown>>(x: {
|
export function definePreferences<T extends Record<string, unknown>>(x: {
|
||||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>
|
[K in keyof T]: PreferencesDefinitionRecord<T[K]>
|
||||||
}): {
|
}): {
|
||||||
|
|
@ -180,7 +189,7 @@ function normalizePreferences(preferences: PossiblyNonNormalizedPreferencesProfi
|
||||||
// TODO: PreferencesManagerForGuest のような非ログイン専用のクラスを分離すればthis.currentAccountのnullチェックやaccountがnullであるスコープのレコード挿入などが不要になり綺麗になるかもしれない
|
// TODO: PreferencesManagerForGuest のような非ログイン専用のクラスを分離すればthis.currentAccountのnullチェックやaccountがnullであるスコープのレコード挿入などが不要になり綺麗になるかもしれない
|
||||||
// と思ったけど操作アカウントが存在しない場合も考慮する現在の設計の方が汎用的かつ堅牢かもしれない
|
// と思ったけど操作アカウントが存在しない場合も考慮する現在の設計の方が汎用的かつ堅牢かもしれない
|
||||||
// NOTE: accountDependentな設定は初期状態であってもアカウントごとのスコープでレコードを作成しておかないと、サーバー同期する際に正しく動作しなくなる
|
// NOTE: accountDependentな設定は初期状態であってもアカウントごとのスコープでレコードを作成しておかないと、サーバー同期する際に正しく動作しなくなる
|
||||||
export class PreferencesManager {
|
export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
|
||||||
private io: StorageProvider;
|
private io: StorageProvider;
|
||||||
private currentAccount: { id: string } | null;
|
private currentAccount: { id: string } | null;
|
||||||
public profile: PreferencesProfile;
|
public profile: PreferencesProfile;
|
||||||
|
|
@ -201,6 +210,8 @@ export class PreferencesManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(io: StorageProvider, currentAccount: { id: string } | null) {
|
constructor(io: StorageProvider, currentAccount: { id: string } | null) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.io = io;
|
this.io = io;
|
||||||
this.currentAccount = currentAccount;
|
this.currentAccount = currentAccount;
|
||||||
|
|
||||||
|
|
@ -246,6 +257,12 @@ export class PreferencesManager {
|
||||||
|
|
||||||
this.rewriteRawState(key, v);
|
this.rewriteRawState(key, v);
|
||||||
|
|
||||||
|
this.emit('committed', {
|
||||||
|
key,
|
||||||
|
value: v,
|
||||||
|
oldValue: this.s[key],
|
||||||
|
});
|
||||||
|
|
||||||
const record = this.getMatchedRecordOf(key);
|
const record = this.getMatchedRecordOf(key);
|
||||||
|
|
||||||
if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
|
if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,5 @@
|
||||||
|
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
|
||||||
// HMR有効時にバグか知らんけど複数回実行されるのでその対策
|
export const TAB_ID = genId();
|
||||||
export const TAB_ID = window.sessionStorage.getItem('TAB_ID') ?? genId();
|
|
||||||
window.sessionStorage.setItem('TAB_ID', TAB_ID);
|
|
||||||
if (_DEV_) console.log('TAB_ID', TAB_ID);
|
if (_DEV_) console.log('TAB_ID', TAB_ID);
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,8 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
// 様々な理由により startViewTransition は失敗することがある
|
// 様々な理由により startViewTransition は失敗することがある
|
||||||
// ref. https://github.com/misskey-dev/misskey/issues/16562
|
// ref. https://github.com/misskey-dev/misskey/issues/16562
|
||||||
|
|
||||||
|
// FIXME: viewTransitonエラーはtry~catch貫通してそうな気配がする
|
||||||
|
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
window.document.documentElement.classList.remove('_themeChanging_');
|
window.document.documentElement.classList.remove('_themeChanging_');
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type PageHeaderItem = {
|
export type PageHeaderItem = {
|
||||||
text: string;
|
text?: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
handler: (ev: MouseEvent) => void;
|
handler: (ev: MouseEvent) => void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,40 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="azykntjl">
|
<div :class="[$style.root, acrylic ? $style.acrylic : null]">
|
||||||
<div class="body">
|
<div :class="$style.body">
|
||||||
<div class="left">
|
<div :class="$style.left">
|
||||||
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
<button v-click-anime :class="[$style.item, $style.instance]" class="_button" @click="openInstanceMenu">
|
||||||
<img :src="instance.iconUrl ?? '/favicon.ico'" draggable="false"/>
|
<img :class="$style.instanceIcon" :src="instance.iconUrl ?? '/favicon.ico'" draggable="false"/>
|
||||||
</button>
|
</button>
|
||||||
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
|
<MkA v-click-anime v-tooltip="i18n.ts.timeline" :class="$style.item" activeClass="active" to="/" exact>
|
||||||
<i class="ti ti-home ti-fw"></i>
|
<i :class="$style.itemIcon" class="ti ti-home ti-fw"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<template v-for="item in menu">
|
<template v-for="item in menu">
|
||||||
<div v-if="item === '-'" class="divider"></div>
|
<div v-if="item === '-'" :class="$style.divider"></div>
|
||||||
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
|
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="_button" :class="$style.item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
|
||||||
<i class="ti-fw" :class="navbarItemDef[item].icon"></i>
|
<i :class="[$style.itemIcon, navbarItemDef[item].icon]" class="ti-fw"></i>
|
||||||
<span v-if="navbarItemDef[item].indicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
|
<span v-if="navbarItemDef[item].indicated" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
<div class="divider"></div>
|
<div :class="$style.divider"></div>
|
||||||
<MkA v-if="$i && ($i.isAdmin || $i.isModerator)" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
|
<MkA v-if="$i && ($i.isAdmin || $i.isModerator)" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
|
||||||
<i class="ti ti-dashboard ti-fw"></i>
|
<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button v-click-anime class="item _button" @click="more">
|
<button v-click-anime :class="$style.item" class="_button" @click="more">
|
||||||
<i class="ti ti-dots ti-fw"></i>
|
<i :class="$style.itemIcon" class="ti ti-dots ti-fw"></i>
|
||||||
<span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
|
<span v-if="otherNavItemIndicated" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div :class="$style.right">
|
||||||
<MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
|
<MkA v-click-anime v-tooltip="i18n.ts.settings" :class="$style.item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
|
||||||
<i class="ti ti-settings ti-fw"></i>
|
<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button v-if="$i" v-click-anime class="item _button account" @click="openAccountMenu">
|
<button v-if="$i" v-click-anime :class="[$style.item, $style.account]" class="_button" @click="openAccountMenu">
|
||||||
<MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/>
|
<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" :user="$i"/>
|
||||||
</button>
|
</button>
|
||||||
<div class="post" @click="os.post()">
|
<div :class="$style.post" @click="os.post()">
|
||||||
<MkButton class="button" gradate full rounded>
|
<MkButton :class="$style.postButton" gradate rounded>
|
||||||
<i class="ti ti-pencil ti-fw"></i>
|
<i class="ti ti-pencil ti-fw"></i>
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,6 +61,10 @@ import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||||
|
|
||||||
const WINDOW_THRESHOLD = 1400;
|
const WINDOW_THRESHOLD = 1400;
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
acrylic?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
|
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
|
||||||
const menu = ref(prefer.s.menu);
|
const menu = ref(prefer.s.menu);
|
||||||
// const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
// const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
||||||
|
|
@ -100,121 +104,140 @@ onMounted(() => {
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.azykntjl {
|
.root {
|
||||||
$height: 60px;
|
--height: 60px;
|
||||||
$avatar-size: 32px;
|
|
||||||
$avatar-margin: 8px;
|
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $height;
|
height: var(--height);
|
||||||
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
contain: strict;
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
background: var(--MI_THEME-navBg);
|
||||||
backdrop-filter: var(--MI-blur, blur(15px));
|
|
||||||
|
|
||||||
> .body {
|
&.acrylic {
|
||||||
max-width: 1380px;
|
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
||||||
margin: 0 auto;
|
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
display: flex;
|
backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
|
|
||||||
> .right,
|
|
||||||
> .left {
|
|
||||||
|
|
||||||
> .item {
|
|
||||||
position: relative;
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 12px;
|
|
||||||
line-height: $height;
|
|
||||||
|
|
||||||
> i,
|
|
||||||
> .avatar {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> i {
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .avatar {
|
|
||||||
width: $avatar-size;
|
|
||||||
height: $avatar-size;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
color: var(--MI_THEME-navIndicator);
|
|
||||||
font-size: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17)));
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--MI_THEME-navActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .divider {
|
|
||||||
display: inline-block;
|
|
||||||
height: 16px;
|
|
||||||
margin: 0 10px;
|
|
||||||
border-right: solid 0.5px var(--MI_THEME-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .instance {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 56px;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: bottom;
|
|
||||||
|
|
||||||
> img {
|
|
||||||
display: inline-block;
|
|
||||||
width: 24px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .post {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .account {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
vertical-align: top;
|
|
||||||
margin-right: 8px;
|
|
||||||
|
|
||||||
> .acct {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .right {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
max-width: 1380px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-y: clip;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.9em;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 12px;
|
||||||
|
line-height: var(--height);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17)));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: var(--MI_THEME-navActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemIcon {
|
||||||
|
margin-right: 0;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin-right: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.acct {
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
color: var(--MI_THEME-navIndicator);
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0 10px;
|
||||||
|
border-right: solid 0.5px var(--MI_THEME-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instance {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 56px;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: bottom;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instanceIcon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
contain: content;
|
||||||
|
background: var(--MI_THEME-navBg);
|
||||||
|
}
|
||||||
|
.acrylic .right {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postButton {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/>
|
<XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/>
|
||||||
|
|
||||||
<div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : '' }">
|
<div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : '' }">
|
||||||
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/>
|
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'" :acrylic="withWallpaper"/>
|
||||||
|
|
||||||
<XReloadSuggestion v-if="shouldSuggestReload"/>
|
<XReloadSuggestion v-if="shouldSuggestReload"/>
|
||||||
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
|
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
|
||||||
|
|
@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'bottom'"/>
|
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'bottom'" :acrylic="withWallpaper"/>
|
||||||
|
|
||||||
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
|
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, ref, shallowRef, watch } from 'vue';
|
import { computed, ref, shallowRef, watch, defineAsyncComponent } from 'vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
type TourStep = {
|
type TourStep = {
|
||||||
|
|
@ -26,7 +26,7 @@ export function startTour(steps: TourStep[]) {
|
||||||
anchorElementRef.value = step.element;
|
anchorElementRef.value = step.element;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dispose } = os.popup(await import('@/components/MkSpot.vue').then(x => x.default), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSpot.vue')), {
|
||||||
title: titleRef,
|
title: titleRef,
|
||||||
description: descriptionRef,
|
description: descriptionRef,
|
||||||
anchorElement: anchorElementRef,
|
anchorElement: anchorElementRef,
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@types/wawoff2": "1.0.2",
|
"@types/wawoff2": "1.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2"
|
"@typescript-eslint/parser": "8.47.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tabler/icons-webfont": "3.35.0",
|
"@tabler/icons-webfont": "3.35.0",
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,14 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/matter-js": "0.20.2",
|
"@types/matter-js": "0.20.2",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"glob": "11.0.3",
|
"glob": "11.1.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,16 @@
|
||||||
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@readme/openapi-parser": "5.2.0",
|
"@readme/openapi-parser": "5.2.1",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "7.10.1",
|
"openapi-typescript": "7.10.1",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
"tsx": "4.20.6",
|
"tsx": "4.20.6",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"eslint": "9.39.0"
|
"eslint": "9.39.1"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"built"
|
"built"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.11.0",
|
"version": "2025.11.1",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
@ -37,19 +37,19 @@
|
||||||
"directory": "packages/misskey-js"
|
"directory": "packages/misskey-js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.53.3",
|
"@microsoft/api-extractor": "7.55.0",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "4.0.10",
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"glob": "11.0.3",
|
"glob": "13.0.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"tsd": "0.33.0",
|
"tsd": "0.33.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vitest": "3.2.4",
|
"vitest": "4.0.10",
|
||||||
"vitest-websocket-mock": "0.5.0"
|
"vitest-websocket-mock": "0.5.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.10.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"glob": "11.0.3",
|
"glob": "11.1.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "0.25.11",
|
"esbuild": "0.27.0",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "8.46.2",
|
"@typescript-eslint/parser": "8.47.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.11",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
|
|
|
||||||
8662
pnpm-lock.yaml
8662
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -32,3 +32,4 @@ onlyBuiltDependencies:
|
||||||
ignorePatchFailures: false
|
ignorePatchFailures: false
|
||||||
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
|
minimumReleaseAge: 10080 # delay 7days to mitigate supply-chain attack
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
|
- '@syuilo/aiscript'
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,18 @@
|
||||||
'.devcontainer/**',
|
'.devcontainer/**',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// devcontainer (Dockerfile用の設定)
|
||||||
|
groupName: '[devcontainer] Update dependencies',
|
||||||
|
matchFileNames: ['.devcontainer/Dockerfile'],
|
||||||
|
matchDepNames: ['mcr.microsoft.com/devcontainers/javascript-node'],
|
||||||
|
allowedVersions: '/-[0-9]+-trixie$/',
|
||||||
|
|
||||||
|
// major/minor/patch: devcontainer の semver
|
||||||
|
// build: Node メジャー
|
||||||
|
// dist: Debian codename (e.g., bullseye, bookworm)(比較には使わない)
|
||||||
|
versioning: 'regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)-(?<build>\\d+)-(?<dist>.+)$',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
customManagers: [
|
customManagers: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,14 +11,14 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdast": "4.0.4",
|
"@types/mdast": "4.0.4",
|
||||||
"@types/node": "24.9.1",
|
"@types/node": "24.9.1",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "4.0.10",
|
||||||
"mdast-util-to-string": "4.0.0",
|
"mdast-util-to-string": "4.0.0",
|
||||||
"remark": "15.0.1",
|
"remark": "15.0.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "6.4.1",
|
"vite": "7.2.2",
|
||||||
"vite-node": "3.2.4",
|
"vite-node": "5.2.0",
|
||||||
"vitest": "3.2.4"
|
"vitest": "4.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue