Compare commits

...

42 Commits

Author SHA1 Message Date
renovate[bot]
da448c8034 chore(deps): update [tools] update dependencies 2025-03-21 10:52:33 +00:00
renovate[bot]
50724b6ab8 fix(deps): update [frontend] update dependencies (#15625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 19:41:26 +09:00
renovate[bot]
e61263cff0 fix(deps): update [backend] update dependencies (#15596)
* fix(deps): update [backend] update dependencies

* fix(backend/types): ensure to use nodejs-provided stream api

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
2025-03-21 19:38:11 +09:00
github-actions[bot]
d073fe6b02 Bump version to 2025.3.2-beta.8 2025-03-20 12:18:50 +00:00
syuilo
ce858a676b refactor(frontend): use PageWithHeader instead of MkStickyContainer+MkPageHeader combination 2025-03-20 21:04:37 +09:00
syuilo
733a391d86 follow up of 1fd87bd2e4
コミットし忘れ
2025-03-20 20:29:46 +09:00
syuilo
0e25a0fb81 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-20 20:28:29 +09:00
syuilo
1fd87bd2e4 fix(frontend): prevent transition glitch of MkHorizontalSwipe 2025-03-20 20:28:16 +09:00
github-actions[bot]
ebc54b1f82 Bump version to 2025.3.2-beta.7 2025-03-20 10:07:37 +00:00
syuilo
6015254e59 lint fixes 2025-03-20 19:00:09 +09:00
syuilo
c02f0b3b33 Update eslint.config.js 2025-03-20 18:59:10 +09:00
syuilo
abddd40c09 enhance(frontend): 通常のRouterViewにTransitionを追加 2025-03-20 18:55:32 +09:00
かっこかり
a865a949b5 fix(frontend): MkRoleSelectDialogでのpopupの使い方が誤っているのを修正 (#15683) 2025-03-20 16:36:37 +09:00
syuilo
0007723405 fix lint 2025-03-20 16:34:50 +09:00
syuilo
71188b3463 fix lint 2025-03-20 16:10:38 +09:00
syuilo
7f534a41a6 fix lint 2025-03-20 16:07:52 +09:00
syuilo
f25963e2c2 Update eslint.config.js 2025-03-20 16:06:32 +09:00
syuilo
dfab6b1b8d lint(frontend): force window prefix 2025-03-20 15:44:06 +09:00
syuilo
fac59d75e2 lint(frontend): relax id-denylist rule 2025-03-20 15:43:35 +09:00
syuilo
fd3e28812e clean up console 2025-03-20 15:15:46 +09:00
syuilo
6a90b7e04b add todo 2025-03-20 15:09:50 +09:00
syuilo
8d8414687a enhance(frontend): improve preference manager stability 2025-03-20 14:57:14 +09:00
syuilo
0c682dd027 🎨 2025-03-20 13:34:57 +09:00
syuilo
3399c786a8 refactor(frontend): refactor components 2025-03-20 13:33:01 +09:00
syuilo
64cf101fe7 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-20 13:16:23 +09:00
syuilo
8b6d90b7a4 🎨 2025-03-20 13:16:08 +09:00
github-actions[bot]
070749bdc8 Bump version to 2025.3.2-beta.6 2025-03-20 04:03:25 +00:00
syuilo
161706c5e2 New Crowdin updates (#15680)
* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Japanese, Kansai)

* New translations ja-jp.yml (Romanian)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Greek)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Dutch)

* New translations ja-jp.yml (Norwegian)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Swedish)

* New translations ja-jp.yml (Ukrainian)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Uzbek)

* New translations ja-jp.yml (Lao)

* New translations ja-jp.yml (Korean (Gyeongsang))
2025-03-20 13:01:20 +09:00
syuilo
596e517f77 fix(frontend): minimum uiが表示できない 2025-03-20 12:58:18 +09:00
syuilo
91670caca0 enhance(frontend): tweak install-extensions behaviour 2025-03-20 12:58:01 +09:00
syuilo
fccaadacf0 lint(frontend): improve id-denylist rule 2025-03-20 12:47:51 +09:00
syuilo
282caa0b7e 🎨 for install-extensions 2025-03-20 12:36:48 +09:00
syuilo
9529867630 fix(frontend): dev buildでpreferencesのタブ同期が不必要に行われるのを修正 2025-03-20 12:02:50 +09:00
github-actions[bot]
d06fadd095 Bump version to 2025.3.2-beta.5 2025-03-20 00:06:04 +00:00
zyoshoka
9dd13f364b fix(backend): mismatch in emojis param of test WebHook payload (#15675)
* fix(backend): mismatch in `emojis` param of test WebHook payload

* fix: test

* fix: type
2025-03-20 09:00:58 +09:00
syuilo
b067d4dcd6 follow up of 7b323031b7 2025-03-20 08:59:54 +09:00
syuilo
acac759d87 fix(frontend): モバイルレイアウト時にホームを押しても最上部へスクロールされない
Fix #15679
2025-03-20 08:35:45 +09:00
syuilo
b37622fa64 🎨 2025-03-19 20:54:07 +09:00
syuilo
c17a104de6 fix(frontend): router view stacking有効時にinstall-extensionsが動かない 2025-03-19 20:53:48 +09:00
syuilo
4ab9f66356 Update deep-equal.ts 2025-03-19 20:32:15 +09:00
zyoshoka
aed95a765d chore(storybook): fix storybook build (#15678) 2025-03-19 10:52:05 +00:00
Sayamame-beans
71841e365e Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように (#15677)
* enhance(frontend): include server hostname and port in 2fa recovery code filename

* chore(frontend): fix mistake(use `@` for indicate server hostname)

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-03-19 19:34:57 +09:00
216 changed files with 2737 additions and 2265 deletions

View File

@@ -50,8 +50,8 @@ jobs:
- run: pnpm i --frozen-lockfile - run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml - name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml run: git diff --exit-code pnpm-lock.yaml
- name: Build misskey-js - name: Build dependent packages
run: pnpm --filter misskey-js build run: pnpm -F misskey-js -F misskey-bubble-game -F misskey-reversi build
- name: Build storybook - name: Build storybook
run: pnpm --filter frontend build-storybook run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic - name: Publish to Chromatic

View File

@@ -18,12 +18,14 @@
- Enhance: 投稿フォームの設定メニューを改良 - Enhance: 投稿フォームの設定メニューを改良
- 投稿フォームをリセットできるように - 投稿フォームをリセットできるように
- 文字数カウントを復活 - 文字数カウントを復活
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server ### Server
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正 - Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
- Fix: 連合無しモードでも外部から照会可能だった問題を修正 - Fix: 連合無しモードでも外部から照会可能だった問題を修正
- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正
## 2025.3.1 ## 2025.3.1

View File

@@ -1013,6 +1013,7 @@ flip: "اقلب"
lastNDays: "آخر {n} أيام" lastNDays: "آخر {n} أيام"
surrender: "ألغِ" surrender: "ألغِ"
postForm: "أنشئ ملاحظة" postForm: "أنشئ ملاحظة"
information: "عن"
_delivery: _delivery:
stop: "مُعلّق" stop: "مُعلّق"
_initialAccountSetting: _initialAccountSetting:

View File

@@ -853,6 +853,7 @@ renotes: "রিনোট"
sourceCode: "সোর্স কোড" sourceCode: "সোর্স কোড"
flip: "উল্টান" flip: "উল্টান"
postForm: "নোট লিখুন" postForm: "নোট লিখুন"
information: "আপনার সম্পর্কে"
_delivery: _delivery:
stop: "স্থগিত করা হয়েছে" stop: "স্থগিত করা হয়েছে"
_type: _type:

View File

@@ -1334,6 +1334,7 @@ paste: "Pegar"
emojiPalette: "Calaix d'emojis" emojiPalette: "Calaix d'emojis"
postForm: "Formulari de publicació" postForm: "Formulari de publicació"
textCount: "Nombre de caràcters " textCount: "Nombre de caràcters "
information: "Informació"
_emojiPalette: _emojiPalette:
palettes: "Calaixos d'emojis" palettes: "Calaixos d'emojis"
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius" enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
@@ -2673,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho." checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho."
_plugin: _plugin:
title: "Vols instal·lar aquest afegit?" title: "Vols instal·lar aquest afegit?"
metaTitle: "Informació de l'afegit "
_theme: _theme:
title: "Vols instal·lar aquest tema?" title: "Vols instal·lar aquest tema?"
metaTitle: "Informació del tema"
_meta: _meta:
base: "Paleta de colors base" base: "Paleta de colors base"
_vendorInfo: _vendorInfo:

View File

@@ -169,6 +169,9 @@ addAccount: "Přidat účet"
reloadAccountsList: "Obnovit list účtů" reloadAccountsList: "Obnovit list účtů"
loginFailed: "Přihlášení se nezdařilo." loginFailed: "Přihlášení se nezdařilo."
showOnRemote: "Více na původním profilu" showOnRemote: "Více na původním profilu"
continueOnRemote: "Pokračujte na původní profil"
chooseServerOnMisskeyHub: "Vyberete si server z Misskey Hubu"
inputHostName: "Zadejte doménu"
general: "Obecně" general: "Obecně"
wallpaper: "Obrázek na pozadí" wallpaper: "Obrázek na pozadí"
setWallpaper: "Nastavení obrázku na pozadí" setWallpaper: "Nastavení obrázku na pozadí"
@@ -193,6 +196,7 @@ perHour: "za hodinu"
perDay: "za den" perDay: "za den"
stopActivityDelivery: "Přestat zasílat aktivitu" stopActivityDelivery: "Přestat zasílat aktivitu"
blockThisInstance: "Blokovat tuto instanci" blockThisInstance: "Blokovat tuto instanci"
silenceThisInstance: "Utišit tuto instanci"
operations: "Operace" operations: "Operace"
software: "Software" software: "Software"
version: "Verze" version: "Verze"
@@ -1099,6 +1103,7 @@ flip: "Otočit"
lastNDays: "Posledních {n} dnů" lastNDays: "Posledních {n} dnů"
surrender: "Zrušit" surrender: "Zrušit"
postForm: "Formulář pro odeslání" postForm: "Formulář pro odeslání"
information: "Informace"
_delivery: _delivery:
stop: "Suspendováno" stop: "Suspendováno"
_type: _type:

View File

@@ -1309,6 +1309,7 @@ availableRoles: "Verfügbare Rollen"
federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren." federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren."
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren." federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
postForm: "Notizfenster" postForm: "Notizfenster"
information: "Über"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2524,10 +2525,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers." checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers."
_plugin: _plugin:
title: "Möchtest du dieses Plugin installieren?" title: "Möchtest du dieses Plugin installieren?"
metaTitle: "Plugininformation"
_theme: _theme:
title: "Möchten du dieses Farbschema installieren?" title: "Möchten du dieses Farbschema installieren?"
metaTitle: "Farbschemainfo"
_meta: _meta:
base: "Farbschemavorlage" base: "Farbschemavorlage"
_vendorInfo: _vendorInfo:

View File

@@ -289,6 +289,7 @@ icon: "Εικονίδιο"
replies: "Απάντηση" replies: "Απάντηση"
renotes: "Κοινοποίηση σημειώματος" renotes: "Κοινοποίηση σημειώματος"
postForm: "Φόρμα δημοσίευσης" postForm: "Φόρμα δημοσίευσης"
information: "Πληροφορίες"
_email: _email:
_follow: _follow:
title: "Έχετε ένα νέο ακόλουθο" title: "Έχετε ένα νέο ακόλουθο"

View File

@@ -1317,7 +1317,7 @@ unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for t
preferences: "Preferences" preferences: "Preferences"
accessibility: "Accessibility" accessibility: "Accessibility"
preferencesProfile: "Preferences profile" preferencesProfile: "Preferences profile"
copyPreferenceId: "Copy the proference ID" copyPreferenceId: "Copy the preference ID"
resetToDefaultValue: "Revert to default" resetToDefaultValue: "Revert to default"
overrideByAccount: "Override by the account" overrideByAccount: "Override by the account"
untitled: "Untitled" untitled: "Untitled"
@@ -1334,6 +1334,7 @@ paste: "Paste"
emojiPalette: "Emoji palette" emojiPalette: "Emoji palette"
postForm: "Posting form" postForm: "Posting form"
textCount: "Character count" textCount: "Character count"
information: "About"
_emojiPalette: _emojiPalette:
palettes: "Palette" palettes: "Palette"
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices" enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
@@ -2673,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation." checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation."
_plugin: _plugin:
title: "Do you want to install this plugin?" title: "Do you want to install this plugin?"
metaTitle: "Plugin information"
_theme: _theme:
title: "Do you want to install this theme?" title: "Do you want to install this theme?"
metaTitle: "Theme information"
_meta: _meta:
base: "Base color scheme" base: "Base color scheme"
_vendorInfo: _vendorInfo:
@@ -2877,8 +2876,8 @@ _selfXssPrevention:
description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window." description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window."
description3: "For more information, please refer to this. {link}" description3: "For more information, please refer to this. {link}"
_followRequest: _followRequest:
recieved: "Received application" recieved: "Received request"
sent: "Sent application" sent: "Sent request"
_remoteLookupErrors: _remoteLookupErrors:
_federationNotAllowed: _federationNotAllowed:
title: "Unable to communicate with this server" title: "Unable to communicate with this server"

View File

@@ -1300,6 +1300,7 @@ target: "Para"
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador." federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores" federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
postForm: "Formulario" postForm: "Formulario"
information: "Información"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2521,10 +2522,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación." checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación."
_plugin: _plugin:
title: "¿Quieres instalar este plugin?" title: "¿Quieres instalar este plugin?"
metaTitle: "Información del plugin"
_theme: _theme:
title: "¿Quieres instalar este tema?" title: "¿Quieres instalar este tema?"
metaTitle: "Información del tema"
_meta: _meta:
base: "Esquema de color base" base: "Esquema de color base"
_vendorInfo: _vendorInfo:

View File

@@ -1278,6 +1278,7 @@ lockdown: "Verrouiller"
pleaseSelectAccount: "Sélectionner un compte" pleaseSelectAccount: "Sélectionner un compte"
availableRoles: "Rôles disponibles" availableRoles: "Rôles disponibles"
postForm: "Formulaire de publication" postForm: "Formulaire de publication"
information: "Informations"
_abuseUserReport: _abuseUserReport:
forward: "Transférer" forward: "Transférer"
forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme." forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme."
@@ -2295,10 +2296,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation." checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation."
_plugin: _plugin:
title: "Voulez-vous installer cette extension ?" title: "Voulez-vous installer cette extension ?"
metaTitle: "Informations sur l'extension"
_theme: _theme:
title: "Voulez-vous installer ce thème ?" title: "Voulez-vous installer ce thème ?"
metaTitle: "Informations sur le thème"
_meta: _meta:
base: "Palette de couleurs de base" base: "Palette de couleurs de base"
_vendorInfo: _vendorInfo:

View File

@@ -1262,6 +1262,7 @@ modified: "Diubah"
thereAreNChanges: "Ada {n} perubahan" thereAreNChanges: "Ada {n} perubahan"
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
postForm: "Buat catatan" postForm: "Buat catatan"
information: "Informasi"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_abuseUserReport: _abuseUserReport:
@@ -2492,10 +2493,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan." checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan."
_plugin: _plugin:
title: "Apakah kamu ingin memasang plugin ini?" title: "Apakah kamu ingin memasang plugin ini?"
metaTitle: "Informasi plugin"
_theme: _theme:
title: "Apakah kamu ingin memasang tema ini?" title: "Apakah kamu ingin memasang tema ini?"
metaTitle: "Informasi tema"
_meta: _meta:
base: "Skema warna dasar" base: "Skema warna dasar"
_vendorInfo: _vendorInfo:

12
locales/index.d.ts vendored
View File

@@ -5354,6 +5354,10 @@ export interface Locale extends ILocale {
* 文字数 * 文字数
*/ */
"textCount": string; "textCount": string;
/**
* 情報
*/
"information": string;
"_emojiPalette": { "_emojiPalette": {
/** /**
* パレット * パレット
@@ -10359,20 +10363,12 @@ export interface Locale extends ILocale {
* このプラグインをインストールしますか? * このプラグインをインストールしますか?
*/ */
"title": string; "title": string;
/**
* プラグイン情報
*/
"metaTitle": string;
}; };
"_theme": { "_theme": {
/** /**
* このテーマをインストールしますか? * このテーマをインストールしますか?
*/ */
"title": string; "title": string;
/**
* テーマ情報
*/
"metaTitle": string;
}; };
"_meta": { "_meta": {
/** /**

View File

@@ -1334,6 +1334,7 @@ paste: "Incolla"
emojiPalette: "Tavolozza emoji" emojiPalette: "Tavolozza emoji"
postForm: "Finestra di pubblicazione" postForm: "Finestra di pubblicazione"
textCount: "Il numero di caratteri" textCount: "Il numero di caratteri"
information: "Informazioni"
_emojiPalette: _emojiPalette:
palettes: "Tavolozza" palettes: "Tavolozza"
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi" enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
@@ -2673,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile." checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile."
_plugin: _plugin:
title: "Vuoi davvero installare questo componente aggiuntivo?" title: "Vuoi davvero installare questo componente aggiuntivo?"
metaTitle: "Informazioni sul componente aggiuntivo"
_theme: _theme:
title: "Vuoi davvero installare questa variazione grafica?" title: "Vuoi davvero installare questa variazione grafica?"
metaTitle: "Informazioni sulla variazione grafica"
_meta: _meta:
base: "Combinazione base di colori" base: "Combinazione base di colori"
_vendorInfo: _vendorInfo:

View File

@@ -1334,6 +1334,7 @@ paste: "ペースト"
emojiPalette: "絵文字パレット" emojiPalette: "絵文字パレット"
postForm: "投稿フォーム" postForm: "投稿フォーム"
textCount: "文字数" textCount: "文字数"
information: "情報"
_emojiPalette: _emojiPalette:
palettes: "パレット" palettes: "パレット"
@@ -2749,10 +2750,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。" checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。"
_plugin: _plugin:
title: "このプラグインをインストールしますか?" title: "このプラグインをインストールしますか?"
metaTitle: "プラグイン情報"
_theme: _theme:
title: "このテーマをインストールしますか?" title: "このテーマをインストールしますか?"
metaTitle: "テーマ情報"
_meta: _meta:
base: "基本のカラースキーム" base: "基本のカラースキーム"
_vendorInfo: _vendorInfo:

View File

@@ -1312,6 +1312,7 @@ federationDisabled: "このサーバーは連合が無効化されてるで。
confirmOnReact: "ツッコむときに確認とる" confirmOnReact: "ツッコむときに確認とる"
reactAreYouSure: "\" {emoji} \" でツッコむ?" reactAreYouSure: "\" {emoji} \" でツッコむ?"
postForm: "投稿フォーム" postForm: "投稿フォーム"
information: "情報"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2610,10 +2611,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。" checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。"
_plugin: _plugin:
title: "このプラグイン、インストールする?" title: "このプラグイン、インストールする?"
metaTitle: "プラグイン情報"
_theme: _theme:
title: "このテーマインストールする?" title: "このテーマインストールする?"
metaTitle: "テーマ情報"
_meta: _meta:
base: "" base: ""
_vendorInfo: _vendorInfo:

View File

@@ -655,6 +655,7 @@ replies: "답하기"
renotes: "리노트" renotes: "리노트"
attach: "옇기" attach: "옇기"
surrender: "아이예" surrender: "아이예"
information: "정보"
_delivery: _delivery:
stop: "고만 보내예" stop: "고만 보내예"
_type: _type:

View File

@@ -1330,6 +1330,7 @@ preferenceSyncConflictChoiceDevice: "장치 설정값"
paste: "붙여넣기" paste: "붙여넣기"
emojiPalette: "이모지 팔레트" emojiPalette: "이모지 팔레트"
postForm: "글 입력란" postForm: "글 입력란"
information: "정보"
_emojiPalette: _emojiPalette:
palettes: "팔레트" palettes: "팔레트"
paletteForMain: "메인으로 사용할 팔레트" paletteForMain: "메인으로 사용할 팔레트"
@@ -2650,10 +2651,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오." checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
_plugin: _plugin:
title: "이 플러그인을 설치하시겠습니까?" title: "이 플러그인을 설치하시겠습니까?"
metaTitle: "플러그인 정보"
_theme: _theme:
title: "이 테마를 설치하시겠습니까?" title: "이 테마를 설치하시겠습니까?"
metaTitle: "테마 정보"
_meta: _meta:
base: "기본 컬러 스키마" base: "기본 컬러 스키마"
_vendorInfo: _vendorInfo:

View File

@@ -394,6 +394,7 @@ searchByGoogle: "ຄົ້ນຫາ"
file: "ໄຟລ໌" file: "ໄຟລ໌"
replies: "ຕອບ​ກັບ" replies: "ຕອບ​ກັບ"
renotes: "Renote" renotes: "Renote"
information: "ກ່ຽວກັບ"
_delivery: _delivery:
stop: "ໂຈະ" stop: "ໂຈະ"
_type: _type:

View File

@@ -462,6 +462,7 @@ loggedInAsBot: "Momenteel als bot ingelogd"
icon: "Avatar" icon: "Avatar"
replies: "Antwoord" replies: "Antwoord"
renotes: "Herdelen" renotes: "Herdelen"
information: "Over"
_delivery: _delivery:
stop: "Opgeschort" stop: "Opgeschort"
_type: _type:

View File

@@ -463,6 +463,7 @@ icon: "Avatar"
replies: "Svar" replies: "Svar"
renotes: "Renote" renotes: "Renote"
surrender: "Avbryt" surrender: "Avbryt"
information: "Informasjon"
_delivery: _delivery:
stop: "Suspendert" stop: "Suspendert"
_initialAccountSetting: _initialAccountSetting:

View File

@@ -1045,6 +1045,7 @@ lastNDays: "W ciągu ostatnich {n} dni"
surrender: "Odrzuć" surrender: "Odrzuć"
gameRetry: "Spróbuj ponownie" gameRetry: "Spróbuj ponownie"
postForm: "Formularz tworzenia wpisu" postForm: "Formularz tworzenia wpisu"
information: "Informacje"
_delivery: _delivery:
stop: "Zawieszono" stop: "Zawieszono"
_type: _type:

View File

@@ -1302,6 +1302,7 @@ pleaseSelectAccount: "Selecione uma conta"
availableRoles: "Cargos disponíveis" availableRoles: "Cargos disponíveis"
acknowledgeNotesAndEnable: "Ative após compreender as precauções." acknowledgeNotesAndEnable: "Ative após compreender as precauções."
postForm: "Campo de postagem" postForm: "Campo de postagem"
information: "Informações"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2598,10 +2599,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação." checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação."
_plugin: _plugin:
title: "Deseja instalar esse plugin?" title: "Deseja instalar esse plugin?"
metaTitle: "Informações do plugin"
_theme: _theme:
title: "Deseja instalar esse tema?" title: "Deseja instalar esse tema?"
metaTitle: "Informações do tema"
_meta: _meta:
base: "Paleta de cores base" base: "Paleta de cores base"
_vendorInfo: _vendorInfo:

View File

@@ -646,6 +646,7 @@ show: "Arată"
icon: "Avatar" icon: "Avatar"
replies: "Răspunde" replies: "Răspunde"
renotes: "Re-notează" renotes: "Re-notează"
information: "Despre"
_delivery: _delivery:
stop: "Suspendat" stop: "Suspendat"
_type: _type:

View File

@@ -1182,6 +1182,7 @@ alwaysConfirmFollow: "Всегда подтверждать подписку"
inquiry: "Связаться" inquiry: "Связаться"
messageToFollower: "Сообщение подписчикам" messageToFollower: "Сообщение подписчикам"
postForm: "Форма отправки" postForm: "Форма отправки"
information: "Описание"
_settings: _settings:
webhook: "Вебхук" webhook: "Вебхук"
_delivery: _delivery:

View File

@@ -918,6 +918,7 @@ sourceCode: "Zdrojový kód"
flip: "Preklopiť" flip: "Preklopiť"
lastNDays: "Posledných {n} dní" lastNDays: "Posledných {n} dní"
postForm: "Napísať poznámku" postForm: "Napísať poznámku"
information: "Informácie"
_delivery: _delivery:
stop: "Zmrazené" stop: "Zmrazené"
_type: _type:

View File

@@ -562,6 +562,7 @@ inquiry: "Kontakt"
tryAgain: "Försök igen senare" tryAgain: "Försök igen senare"
signinWithPasskey: "Logga in med nyckel" signinWithPasskey: "Logga in med nyckel"
unknownWebAuthnKey: "Okänd nyckel" unknownWebAuthnKey: "Okänd nyckel"
information: "Om"
_delivery: _delivery:
stop: "Suspenderad" stop: "Suspenderad"
_type: _type:

View File

@@ -1293,6 +1293,7 @@ prohibitedWordsForNameOfUserDescription: "หากมีสตริงใด
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม" yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ" yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
postForm: "แบบฟอร์มการโพสต์" postForm: "แบบฟอร์มการโพสต์"
information: "เกี่ยวกับ"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_abuseUserReport: _abuseUserReport:
@@ -2572,10 +2573,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง" checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง"
_plugin: _plugin:
title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?" title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?"
metaTitle: "ข้อมูลส่วนเสริม"
_theme: _theme:
title: "ต้องการติดตั้งธีมนี้ใช่ไหม?" title: "ต้องการติดตั้งธีมนี้ใช่ไหม?"
metaTitle: "ข้อมูลธีม"
_meta: _meta:
base: "โทนสีพื้นฐาน" base: "โทนสีพื้นฐาน"
_vendorInfo: _vendorInfo:

View File

@@ -910,6 +910,7 @@ sourceCode: "Вихідний код"
flip: "Перевернути" flip: "Перевернути"
lastNDays: "Останні {n} днів" lastNDays: "Останні {n} днів"
postForm: "Створення нотатки" postForm: "Створення нотатки"
information: "Інформація"
_delivery: _delivery:
stop: "Призупинено" stop: "Призупинено"
_type: _type:

View File

@@ -841,6 +841,7 @@ icon: "Avatar"
replies: "Javob berish" replies: "Javob berish"
renotes: "Qayta qayd etish" renotes: "Qayta qayd etish"
flip: "Teskari" flip: "Teskari"
information: "Haqida"
_delivery: _delivery:
stop: "To'xtatilgan" stop: "To'xtatilgan"
_type: _type:

View File

@@ -1120,6 +1120,7 @@ cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải
lastNDays: "{n} ngày trước" lastNDays: "{n} ngày trước"
surrender: "Từ chối" surrender: "Từ chối"
postForm: "Mẫu đăng" postForm: "Mẫu đăng"
information: "Giới thiệu"
_delivery: _delivery:
stop: "Đã vô hiệu hóa" stop: "Đã vô hiệu hóa"
_type: _type:

View File

@@ -1334,6 +1334,7 @@ paste: "粘贴"
emojiPalette: "表情符号调色板" emojiPalette: "表情符号调色板"
postForm: "投稿窗口" postForm: "投稿窗口"
textCount: "字数" textCount: "字数"
information: "关于"
_emojiPalette: _emojiPalette:
palettes: "调色板" palettes: "调色板"
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步" enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
@@ -2673,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "请在安装前确保来源可靠" checkVendorBeforeInstall: "请在安装前确保来源可靠"
_plugin: _plugin:
title: "要安装此插件吗?" title: "要安装此插件吗?"
metaTitle: "插件信息"
_theme: _theme:
title: "要安装此主题吗?" title: "要安装此主题吗?"
metaTitle: "主题信息"
_meta: _meta:
base: "基本配色方案" base: "基本配色方案"
_vendorInfo: _vendorInfo:

View File

@@ -1334,6 +1334,7 @@ paste: "貼上"
emojiPalette: "表情符號調色盤" emojiPalette: "表情符號調色盤"
postForm: "發文視窗" postForm: "發文視窗"
textCount: "字數" textCount: "字數"
information: "關於"
_emojiPalette: _emojiPalette:
palettes: "調色盤" palettes: "調色盤"
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化" enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
@@ -2673,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。" checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。"
_plugin: _plugin:
title: "要安裝此外掛嘛?" title: "要安裝此外掛嘛?"
metaTitle: "外掛資訊"
_theme: _theme:
title: "要安裝此佈景主題嗎?" title: "要安裝此佈景主題嗎?"
metaTitle: "佈景主題資訊"
_meta: _meta:
base: "基本配色方案" base: "基本配色方案"
_vendorInfo: _vendorInfo:

View File

@@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.3.2-beta.4", "version": "2025.3.2-beta.8",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -37,17 +37,17 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.10.16", "@swc/core-darwin-arm64": "1.11.11",
"@swc/core-darwin-x64": "1.10.16", "@swc/core-darwin-x64": "1.11.11",
"@swc/core-freebsd-x64": "1.3.11", "@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.10.16", "@swc/core-linux-arm-gnueabihf": "1.11.11",
"@swc/core-linux-arm64-gnu": "1.10.16", "@swc/core-linux-arm64-gnu": "1.11.11",
"@swc/core-linux-arm64-musl": "1.10.16", "@swc/core-linux-arm64-musl": "1.11.11",
"@swc/core-linux-x64-gnu": "1.10.16", "@swc/core-linux-x64-gnu": "1.11.11",
"@swc/core-linux-x64-musl": "1.10.16", "@swc/core-linux-x64-musl": "1.11.11",
"@swc/core-win32-arm64-msvc": "1.10.16", "@swc/core-win32-arm64-msvc": "1.11.11",
"@swc/core-win32-ia32-msvc": "1.10.16", "@swc/core-win32-ia32-msvc": "1.11.11",
"@swc/core-win32-x64-msvc": "1.10.16", "@swc/core-win32-x64-msvc": "1.11.11",
"@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",
@@ -67,23 +67,23 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.749.0", "@aws-sdk/client-s3": "3.772.0",
"@aws-sdk/lib-storage": "3.749.0", "@aws-sdk/lib-storage": "3.772.0",
"@discordapp/twemoji": "15.1.0", "@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "5.0.2", "@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.2", "@fastify/cookie": "11.0.2",
"@fastify/cors": "10.0.2", "@fastify/cors": "10.1.0",
"@fastify/express": "4.0.2", "@fastify/express": "4.0.2",
"@fastify/http-proxy": "10.0.2", "@fastify/http-proxy": "10.0.2",
"@fastify/multipart": "9.0.3", "@fastify/multipart": "9.0.3",
"@fastify/static": "8.1.0", "@fastify/static": "8.1.1",
"@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.0", "@misskey-dev/summaly": "5.2.0",
"@napi-rs/canvas": "0.1.67", "@napi-rs/canvas": "0.1.68",
"@nestjs/common": "11.0.9", "@nestjs/common": "11.0.12",
"@nestjs/core": "11.0.9", "@nestjs/core": "11.0.12",
"@nestjs/testing": "11.0.9", "@nestjs/testing": "11.0.12",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "8.55.0", "@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.55.0", "@sentry/profiling-node": "8.55.0",
@@ -91,7 +91,7 @@
"@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.6.0", "@swc/cli": "0.6.0",
"@swc/core": "1.10.16", "@swc/core": "1.11.11",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.17.1", "ajv": "8.17.1",
@@ -100,7 +100,7 @@
"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.41.1", "bullmq": "5.44.1",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.4.1", "chalk": "5.4.1",
@@ -122,7 +122,7 @@
"hpagent": "1.2.0", "hpagent": "1.2.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",
"ioredis": "5.5.0", "ioredis": "5.6.0",
"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",
@@ -131,26 +131,26 @@
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.3", "jsonld": "8.3.3",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
"juice": "11.0.0", "juice": "11.0.1",
"meilisearch": "0.48.2", "meilisearch": "0.49.0",
"mfm-js": "0.24.0", "mfm-js": "0.24.0",
"microformats-parser": "2.0.2", "microformats-parser": "2.0.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nanoid": "5.1.0", "nanoid": "5.1.5",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.10.0", "nodemailer": "6.10.0",
"nsfwjs": "4.2.0", "nsfwjs": "4.2.0",
"oauth": "0.10.0", "oauth": "0.10.2",
"oauth2orize": "1.12.0", "oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.3.6", "otpauth": "9.3.6",
"parse5": "7.2.1", "parse5": "7.2.1",
"pg": "8.13.3", "pg": "8.14.1",
"pkce-challenge": "4.1.0", "pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@@ -163,8 +163,8 @@
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rename": "1.0.4", "rename": "1.0.4",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"rxjs": "7.8.1", "rxjs": "7.8.2",
"sanitize-html": "2.14.0", "sanitize-html": "2.15.0",
"secure-json-parse": "3.0.2", "secure-json-parse": "3.0.2",
"sharp": "0.33.5", "sharp": "0.33.5",
"slacc": "0.0.10", "slacc": "0.0.10",
@@ -173,14 +173,14 @@
"systeminformation": "5.25.11", "systeminformation": "5.25.11",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.3", "tmp": "0.2.3",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.20", "typeorm": "0.3.21",
"typescript": "5.7.3", "typescript": "5.8.2",
"ulid": "2.3.0", "ulid": "2.4.0",
"vary": "1.1.2", "vary": "1.1.2",
"web-push": "3.6.7", "web-push": "3.6.7",
"ws": "8.18.0", "ws": "8.18.1",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -204,7 +204,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": "22.13.4", "@types/node": "22.13.10",
"@types/nodemailer": "6.4.17", "@types/nodemailer": "6.4.17",
"@types/oauth": "0.9.6", "@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5", "@types/oauth2orize": "1.11.5",
@@ -223,9 +223,9 @@
"@types/tmp": "0.2.6", "@types/tmp": "0.2.6",
"@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.5.14", "@types/ws": "8.18.0",
"@typescript-eslint/eslint-plugin": "8.24.0", "@typescript-eslint/eslint-plugin": "8.27.0",
"@typescript-eslint/parser": "8.24.0", "@typescript-eslint/parser": "8.27.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.31.0", "eslint-plugin-import": "2.31.0",

View File

@@ -7,42 +7,16 @@ import { Injectable } from '@nestjs/common';
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js'; import { type AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
import { Packed } from '@/misc/json-schema.js'; import { type Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js'; import { type WebhookEventTypes } from '@/models/Webhook.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
const oneDayMillis = 24 * 60 * 60 * 1000; const oneDayMillis = 24 * 60 * 60 * 1000;
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseReportPayload {
const result: MiAbuseUserReport = {
id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user',
targetUser: null,
reporterId: 'dummy-reporter-user',
reporter: null,
assigneeId: null,
assignee: null,
resolved: false,
forwarded: false,
comment: 'This is a dummy report for testing purposes.',
targetUserHost: null,
reporterHost: null,
resolvedAs: null,
moderationNote: 'foo',
...override,
};
return {
...result,
targetUser: result.targetUser ? toPackedUserLite(result.targetUser) : null,
reporter: result.reporter ? toPackedUserLite(result.reporter) : null,
assignee: result.assignee ? toPackedUserLite(result.assignee) : null,
};
}
function generateDummyUser(override?: Partial<MiUser>): MiUser { function generateDummyUser(override?: Partial<MiUser>): MiUser {
return { return {
id: 'dummy-user-1', id: 'dummy-user-1',
@@ -134,124 +108,6 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
}; };
} }
function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
return {
id: note.id,
createdAt: new Date().toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
user: toPackedUserLite(note.user ?? generateDummyUser()),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
visibility: note.visibility,
mentions: note.mentions,
visibleUserIds: note.visibleUserIds,
fileIds: note.fileIds,
files: [],
tags: note.tags,
poll: null,
emojis: note.emojis,
channelId: note.channelId,
channel: note.channel,
localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
reactionEmojis: {},
reactions: {},
reactionCount: 0,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
reactionAndUserPairCache: note.reactionAndUserPairCache,
...(detail ? {
clippedCount: note.clippedCount,
reply: note.reply ? toPackedNote(note.reply, false) : null,
renote: note.renote ? toPackedNote(note.renote, true) : null,
myReaction: null,
} : {}),
...override,
};
}
function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
return {
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.map(it => ({
id: it.id,
angle: it.angle,
flipH: it.flipH,
url: 'https://example.com/dummy-image001.png',
offsetX: it.offsetX,
offsetY: it.offsetY,
})),
isBot: user.isBot,
isCat: user.isCat,
emojis: user.emojis,
onlineStatus: 'active',
badgeRoles: [],
...override,
};
}
function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
return {
...toPackedUserLite(user),
url: null,
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
isLocked: user.isLocked,
isSilenced: false,
isSuspended: user.isSuspended,
description: null,
location: null,
birthday: null,
lang: null,
fields: [],
verifiedLinks: [],
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: [],
pinnedNotes: [],
pinnedPageId: null,
pinnedPage: null,
publicReactions: true,
followersVisibility: 'public',
followingVisibility: 'public',
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
roles: [],
memo: null,
moderationNote: undefined,
isFollowing: false,
isFollowed: false,
hasPendingFollowRequestFromYou: false,
hasPendingFollowRequestToYou: false,
isBlocking: false,
isBlocked: false,
isMuted: false,
isRenoteMuted: false,
notify: 'none',
withReplies: true,
...override,
};
}
const dummyUser1 = generateDummyUser(); const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({ const dummyUser2 = generateDummyUser({
id: 'dummy-user-2', id: 'dummy-user-2',
@@ -284,6 +140,7 @@ export class WebhookTestService {
}; };
constructor( constructor(
private customEmojiService: CustomEmojiService,
private userWebhookService: UserWebhookService, private userWebhookService: UserWebhookService,
private systemWebhookService: SystemWebhookService, private systemWebhookService: SystemWebhookService,
private queueService: QueueService, private queueService: QueueService,
@@ -354,31 +211,31 @@ export class WebhookTestService {
switch (params.type) { switch (params.type) {
case 'note': { case 'note': {
send('note', { note: toPackedNote(dummyNote1) }); send('note', { note: await this.toPackedNote(dummyNote1) });
break; break;
} }
case 'reply': { case 'reply': {
send('reply', { note: toPackedNote(dummyReply1) }); send('reply', { note: await this.toPackedNote(dummyReply1) });
break; break;
} }
case 'renote': { case 'renote': {
send('renote', { note: toPackedNote(dummyRenote1) }); send('renote', { note: await this.toPackedNote(dummyRenote1) });
break; break;
} }
case 'mention': { case 'mention': {
send('mention', { note: toPackedNote(dummyMention1) }); send('mention', { note: await this.toPackedNote(dummyMention1) });
break; break;
} }
case 'follow': { case 'follow': {
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) }); send('follow', { user: await this.toPackedUserDetailedNotMe(dummyUser1) });
break; break;
} }
case 'followed': { case 'followed': {
send('followed', { user: toPackedUserLite(dummyUser2) }); send('followed', { user: await this.toPackedUserLite(dummyUser2) });
break; break;
} }
case 'unfollow': { case 'unfollow': {
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) }); send('unfollow', { user: await this.toPackedUserDetailedNotMe(dummyUser3) });
break; break;
} }
// まだ実装されていない (#9485) // まだ実装されていない (#9485)
@@ -427,7 +284,7 @@ export class WebhookTestService {
switch (params.type) { switch (params.type) {
case 'abuseReport': { case 'abuseReport': {
send('abuseReport', generateAbuseReport({ send('abuseReport', await this.generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@@ -436,7 +293,7 @@ export class WebhookTestService {
break; break;
} }
case 'abuseReportResolved': { case 'abuseReportResolved': {
send('abuseReportResolved', generateAbuseReport({ send('abuseReportResolved', await this.generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@@ -448,7 +305,7 @@ export class WebhookTestService {
break; break;
} }
case 'userCreated': { case 'userCreated': {
send('userCreated', toPackedUserLite(dummyUser1)); send('userCreated', await this.toPackedUserLite(dummyUser1));
break; break;
} }
case 'inactiveModeratorsWarning': { case 'inactiveModeratorsWarning': {
@@ -474,4 +331,153 @@ export class WebhookTestService {
} }
} }
} }
@bindThis
private async generateAbuseReport(override?: Partial<MiAbuseUserReport>): Promise<AbuseReportPayload> {
const result: MiAbuseUserReport = {
id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user',
targetUser: null,
reporterId: 'dummy-reporter-user',
reporter: null,
assigneeId: null,
assignee: null,
resolved: false,
forwarded: false,
comment: 'This is a dummy report for testing purposes.',
targetUserHost: null,
reporterHost: null,
resolvedAs: null,
moderationNote: 'foo',
...override,
};
return {
...result,
targetUser: result.targetUser ? await this.toPackedUserLite(result.targetUser) : null,
reporter: result.reporter ? await this.toPackedUserLite(result.reporter) : null,
assignee: result.assignee ? await this.toPackedUserLite(result.assignee) : null,
};
}
@bindThis
private async toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Promise<Packed<'Note'>> {
return {
id: note.id,
createdAt: new Date().toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
user: await this.toPackedUserLite(note.user ?? generateDummyUser()),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
visibility: note.visibility,
mentions: note.mentions,
visibleUserIds: note.visibleUserIds,
fileIds: note.fileIds,
files: [],
tags: note.tags,
poll: null,
emojis: await this.customEmojiService.populateEmojis(note.emojis, note.userHost),
channelId: note.channelId,
channel: note.channel,
localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
reactionEmojis: {},
reactions: {},
reactionCount: 0,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
reactionAndUserPairCache: note.reactionAndUserPairCache,
...(detail ? {
clippedCount: note.clippedCount,
reply: note.reply ? await this.toPackedNote(note.reply, false) : null,
renote: note.renote ? await this.toPackedNote(note.renote, true) : null,
myReaction: null,
} : {}),
...override,
};
}
@bindThis
private async toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Promise<Packed<'UserLite'>> {
return {
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.map(it => ({
id: it.id,
angle: it.angle,
flipH: it.flipH,
url: 'https://example.com/dummy-image001.png',
offsetX: it.offsetX,
offsetY: it.offsetY,
})),
isBot: user.isBot,
isCat: user.isCat,
emojis: await this.customEmojiService.populateEmojis(user.emojis, user.host),
onlineStatus: 'active',
badgeRoles: [],
...override,
};
}
@bindThis
private async toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Promise<Packed<'UserDetailedNotMe'>> {
return {
...await this.toPackedUserLite(user),
url: null,
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
isLocked: user.isLocked,
isSilenced: false,
isSuspended: user.isSuspended,
description: null,
location: null,
birthday: null,
lang: null,
fields: [],
verifiedLinks: [],
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: [],
pinnedNotes: [],
pinnedPageId: null,
pinnedPage: null,
publicReactions: true,
followersVisibility: 'public',
followingVisibility: 'public',
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
roles: [],
memo: null,
moderationNote: undefined,
isFollowing: false,
isFollowed: false,
hasPendingFollowRequestFromYou: false,
hasPendingFollowRequestToYou: false,
isBlocking: false,
isBlocked: false,
isMuted: false,
isRenoteMuted: false,
notify: 'none',
withReplies: true,
...override,
};
}
} }

View File

@@ -4,6 +4,7 @@
*/ */
import * as fs from 'node:fs/promises'; import * as fs from 'node:fs/promises';
import { WritableStream } from 'node:stream/web';
import type { PathLike } from 'node:fs'; import type { PathLike } from 'node:fs';
/** /**

View File

@@ -166,6 +166,7 @@ export interface Schema extends OfSchema {
readonly maximum?: number; readonly maximum?: number;
readonly minimum?: number; readonly minimum?: number;
readonly pattern?: string; readonly pattern?: string;
readonly additionalProperties?: Schema | boolean;
} }
type RequiredPropertyNames<s extends Obj> = { type RequiredPropertyNames<s extends Obj> = {
@@ -217,6 +218,13 @@ type ObjectSchemaTypeDef<p extends Schema> =
: :
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
p['additionalProperties'] extends true ? Record<string, any> :
p['additionalProperties'] extends Schema ?
p['additionalProperties'] extends infer AdditionalProperties ?
AdditionalProperties extends Schema ?
Record<string, SchemaType<AdditionalProperties>> :
never :
never :
any; any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>; type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;

View File

@@ -14,6 +14,7 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
describe('WebhookTestService', () => { describe('WebhookTestService', () => {
let app: TestingModule; let app: TestingModule;
@@ -56,6 +57,11 @@ describe('WebhookTestService', () => {
providers: [ providers: [
WebhookTestService, WebhookTestService,
IdService, IdService,
{
provide: CustomEmojiService, useFactory: () => ({
populateEmojis: jest.fn(),
}),
},
{ {
provide: QueueService, useFactory: () => ({ provide: QueueService, useFactory: () => ({
systemWebhookDeliver: jest.fn(), systemWebhookDeliver: jest.fn(),

View File

@@ -16,7 +16,7 @@
"@rollup/pluginutils": "5.1.4", "@rollup/pluginutils": "5.1.4",
"@tabler/icons-webfont": "3.31.0", "@tabler/icons-webfont": "3.31.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
@@ -25,16 +25,16 @@
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.34.9", "rollup": "4.36.0",
"sass": "1.85.1", "sass": "1.86.0",
"shiki": "3.1.0", "shiki": "3.2.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.11", "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.8.2", "typescript": "5.8.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"vite": "6.2.1", "vite": "6.2.2",
"vue": "3.5.13" "vue": "3.5.13"
}, },
"devDependencies": { "devDependencies": {
@@ -42,26 +42,26 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.13.9", "@types/node": "22.13.11",
"@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.0", "@types/ws": "8.18.0",
"@typescript-eslint/eslint-plugin": "8.26.0", "@typescript-eslint/eslint-plugin": "8.27.0",
"@typescript-eslint/parser": "8.26.0", "@typescript-eslint/parser": "8.27.0",
"@vitest/coverage-v8": "3.0.8", "@vitest/coverage-v8": "3.0.9",
"@vue/runtime-core": "3.5.13", "@vue/runtime-core": "3.5.13",
"acorn": "8.14.1", "acorn": "8.14.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "17.3.0", "happy-dom": "17.4.4",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.7.3", "msw": "2.7.3",
"nodemon": "3.1.9", "nodemon": "3.1.9",
"prettier": "3.5.3", "prettier": "3.5.3",
"start-server-and-test": "2.0.10", "start-server-and-test": "2.0.11",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "2.2.8", "vue-component-type-helpers": "2.2.8",
"vue-eslint-parser": "10.1.1", "vue-eslint-parser": "10.1.1",

View File

@@ -33,13 +33,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4), Math.min(navigator.hardwareConcurrency - 1, 4),
); );
resolve(workers); resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!');
} else { } else {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 64; canvas.width = 64;
canvas.height = 64; canvas.height = 64;
resolve(canvas); resolve(canvas);
if (_DEV_) console.log('WebGL2 in worker is not supported...');
} }
testWorker.terminate(); testWorker.terminate();
}); });

View File

@@ -12,6 +12,7 @@ const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site
export const host = address.host; export const host = address.host;
export const hostname = address.hostname; export const hostname = address.hostname;
export const url = address.origin; export const url = address.origin;
export const port = address.port;
export const apiUrl = location.origin + '/api'; export const apiUrl = location.origin + '/api';
export const wsOrigin = location.origin; export const wsOrigin = location.origin;
export const lang = localStorage.getItem('lang') ?? 'en-US'; export const lang = localStorage.getItem('lang') ?? 'en-US';

View File

@@ -21,10 +21,10 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.13.9", "@types/node": "22.13.11",
"@typescript-eslint/eslint-plugin": "8.26.0", "@typescript-eslint/eslint-plugin": "8.27.0",
"@typescript-eslint/parser": "8.26.0", "@typescript-eslint/parser": "8.27.0",
"esbuild": "0.25.0", "esbuild": "0.25.1",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"nodemon": "3.1.9", "nodemon": "3.1.9",
"typescript": "5.8.2", "typescript": "5.8.2",

View File

@@ -131,7 +131,7 @@ export function imageDataUrl(options?: {
alpha?: number, alpha?: number,
} }
}, seed?: string): string { }, seed?: string): string {
const canvas = document.createElement('canvas'); const canvas = window.document.createElement('canvas');
canvas.width = options?.size?.width ?? 100; canvas.width = options?.size?.width ?? 100;
canvas.height = options?.size?.height ?? 100; canvas.height = options?.size?.height ?? 100;

View File

@@ -23,9 +23,9 @@ let misskeyOS = null;
function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) { function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
unobserve(); unobserve();
const theme = themes[document.documentElement.dataset.misskeyTheme]; const theme = themes[window.document.documentElement.dataset.misskeyTheme];
if (theme) { if (theme) {
applyTheme(themes[document.documentElement.dataset.misskeyTheme]); applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]);
} else { } else {
applyTheme(themes['l-light']); applyTheme(themes['l-light']);
} }
@@ -42,7 +42,7 @@ function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
} }
} }
}); });
observer.observe(document.documentElement, { observer.observe(window.document.documentElement, {
attributes: true, attributes: true,
attributeFilter: ['data-misskey-theme'], attributeFilter: ['data-misskey-theme'],
}); });

View File

@@ -50,9 +50,46 @@ export default [
// defineExposeが誤検知されてしまう // defineExposeが誤検知されてしまう
'@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため // window ... グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
'id-denylist': ['error', 'window', 'e'], // close ... window.closeと衝突 or 紛らわしい
// open ... window.openと衝突 or 紛らわしい
// fetch ... window.fetchと衝突 or 紛らわしい
// location ... window.locationと衝突 or 紛らわしい
// document ... window.documentと衝突 or 紛らわしい
// history ... window.historyと衝突 or 紛らわしい
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
'no-restricted-globals': [
'error',
{
'name': 'open',
'message': 'Use `window.open`.',
},
{
'name': 'close',
'message': 'Use `window.close`.',
},
{
'name': 'fetch',
'message': 'Use `window.fetch`.',
},
{
'name': 'location',
'message': 'Use `window.location`.',
},
{
'name': 'document',
'message': 'Use `window.document`.',
},
{
'name': 'history',
'message': 'Use `window.history`.',
},
{
'name': 'name',
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
},
],
'no-shadow': ['warn'], 'no-shadow': ['warn'],
'vue/attributes-order': ['error', { 'vue/attributes-order': ['error', {
alphabetical: false, alphabetical: false,

View File

@@ -28,7 +28,7 @@
"@syuilo/aiscript": "0.19.0", "@syuilo/aiscript": "0.19.0",
"@tabler/icons-webfont": "3.31.0", "@tabler/icons-webfont": "3.31.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.16", "analytics": "0.8.16",
@@ -38,7 +38,7 @@
"canvas-confetti": "1.9.3", "canvas-confetti": "1.9.3",
"chart.js": "4.4.8", "chart.js": "4.4.8",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.1.1",
"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": "11.27.0", "chromatic": "11.27.0",
@@ -60,10 +60,10 @@
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"photoswipe": "5.4.4", "photoswipe": "5.4.4",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.34.9", "rollup": "4.36.0",
"sanitize-html": "2.14.0", "sanitize-html": "2.15.0",
"sass": "1.85.1", "sass": "1.86.0",
"shiki": "3.1.0", "shiki": "3.2.1",
"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.174.0", "three": "0.174.0",
@@ -74,54 +74,54 @@
"typescript": "5.8.2", "typescript": "5.8.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "6.2.1", "vite": "6.2.2",
"vue": "3.5.13", "vue": "3.5.13",
"vuedraggable": "next", "vuedraggable": "next",
"wanakana": "5.3.1" "wanakana": "5.3.1"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.0", "@misskey-dev/summaly": "5.2.0",
"@storybook/addon-actions": "8.6.4", "@storybook/addon-actions": "8.6.7",
"@storybook/addon-essentials": "8.6.4", "@storybook/addon-essentials": "8.6.7",
"@storybook/addon-interactions": "8.6.4", "@storybook/addon-interactions": "8.6.7",
"@storybook/addon-links": "8.6.4", "@storybook/addon-links": "8.6.7",
"@storybook/addon-mdx-gfm": "8.6.4", "@storybook/addon-mdx-gfm": "8.6.7",
"@storybook/addon-storysource": "8.6.4", "@storybook/addon-storysource": "8.6.7",
"@storybook/blocks": "8.6.4", "@storybook/blocks": "8.6.7",
"@storybook/components": "8.6.4", "@storybook/components": "8.6.7",
"@storybook/core-events": "8.6.4", "@storybook/core-events": "8.6.7",
"@storybook/manager-api": "8.6.4", "@storybook/manager-api": "8.6.7",
"@storybook/preview-api": "8.6.4", "@storybook/preview-api": "8.6.7",
"@storybook/react": "8.6.4", "@storybook/react": "8.6.7",
"@storybook/react-vite": "8.6.4", "@storybook/react-vite": "8.6.7",
"@storybook/test": "8.6.4", "@storybook/test": "8.6.7",
"@storybook/theming": "8.6.4", "@storybook/theming": "8.6.7",
"@storybook/types": "8.6.4", "@storybook/types": "8.6.7",
"@storybook/vue3": "8.6.4", "@storybook/vue3": "8.6.7",
"@storybook/vue3-vite": "8.6.4", "@storybook/vue3-vite": "8.6.7",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/matter-js": "0.19.8", "@types/matter-js": "0.19.8",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.13.9", "@types/node": "22.13.11",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.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.0", "@types/ws": "8.18.0",
"@typescript-eslint/eslint-plugin": "8.26.0", "@typescript-eslint/eslint-plugin": "8.27.0",
"@typescript-eslint/parser": "8.26.0", "@typescript-eslint/parser": "8.27.0",
"@vitest/coverage-v8": "3.0.8", "@vitest/coverage-v8": "3.0.9",
"@vue/runtime-core": "3.5.13", "@vue/runtime-core": "3.5.13",
"acorn": "8.14.1", "acorn": "8.14.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "14.1.0", "cypress": "14.2.0",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "17.3.0", "happy-dom": "17.4.4",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.7.3", "msw": "2.7.3",
@@ -131,12 +131,12 @@
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.0.10", "start-server-and-test": "2.0.11",
"storybook": "8.6.4", "storybook": "8.6.7",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-node": "3.0.8", "vite-node": "3.0.9",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "3.0.8", "vitest": "3.0.9",
"vitest-fetch-mock": "0.4.5", "vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "2.2.8", "vue-component-type-helpers": "2.2.8",
"vue-eslint-parser": "10.1.1", "vue-eslint-parser": "10.1.1",

View File

@@ -12,9 +12,9 @@ import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js'; import { mainBoot } from '@/boot/main-boot.js';
import { subBoot } from '@/boot/sub-boot.js'; import { subBoot } from '@/boot/sub-boot.js';
const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete']; const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions'];
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) {
subBoot(); subBoot();
} else { } else {
mainBoot(); mainBoot();

View File

@@ -191,7 +191,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string)
// 他のタブは再読み込みするだけ // 他のタブは再読み込みするだけ
reloadChannel.postMessage(null); reloadChannel.postMessage(null);
// このページはredirectで指定された先に移動 // このページはredirectで指定された先に移動
location.href = redirect; window.location.href = redirect;
return; return;
} }

View File

@@ -95,28 +95,28 @@ export async function common(createVue: () => App<Element>) {
//#endregion //#endregion
// タッチデバイスでCSSの:hoverを機能させる // タッチデバイスでCSSの:hoverを機能させる
document.addEventListener('touchend', () => {}, { passive: true }); window.document.addEventListener('touchend', () => {}, { passive: true });
// URLに#pswpを含む場合は取り除く // URLに#pswpを含む場合は取り除く
if (location.hash === '#pswp') { if (window.location.hash === '#pswp') {
history.replaceState(null, '', location.href.replace('#pswp', '')); window.history.replaceState(null, '', window.location.href.replace('#pswp', ''));
} }
// 一斉リロード // 一斉リロード
reloadChannel.addEventListener('message', path => { reloadChannel.addEventListener('message', path => {
if (path !== null) location.href = path; if (path !== null) window.location.href = path;
else location.reload(); else window.location.reload();
}); });
// If mobile, insert the viewport meta tag // If mobile, insert the viewport meta tag
if (['smartphone', 'tablet'].includes(deviceKind)) { if (['smartphone', 'tablet'].includes(deviceKind)) {
const viewport = document.getElementsByName('viewport').item(0); const viewport = window.document.getElementsByName('viewport').item(0);
viewport.setAttribute('content', viewport.setAttribute('content',
`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); `${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 = document.documentElement; const html = window.document.documentElement;
html.setAttribute('lang', lang); html.setAttribute('lang', lang);
//#endregion //#endregion
@@ -130,11 +130,11 @@ export async function common(createVue: () => App<Element>) {
}); });
//#region loginId //#region loginId
const params = new URLSearchParams(location.search); const params = new URLSearchParams(window.location.search);
const loginId = params.get('loginId'); const loginId = params.get('loginId');
if (loginId) { if (loginId) {
const target = getUrlWithoutLoginId(location.href); const target = getUrlWithoutLoginId(window.location.href);
if (!$i || $i.id !== loginId) { if (!$i || $i.id !== loginId) {
const account = await getAccountFromId(loginId); const account = await getAccountFromId(loginId);
@@ -143,7 +143,7 @@ export async function common(createVue: () => App<Element>) {
} }
} }
history.replaceState({ misskey: 'loginId' }, '', target); window.history.replaceState({ misskey: 'loginId' }, '', target);
} }
//#endregion //#endregion
@@ -155,7 +155,7 @@ export async function common(createVue: () => App<Element>) {
); );
}, { immediate: miLocalStorage.getItem('theme') == null }); }, { immediate: miLocalStorage.getItem('theme') == null });
document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
const darkTheme = prefer.model('darkTheme'); const darkTheme = prefer.model('darkTheme');
const lightTheme = prefer.model('lightTheme'); const lightTheme = prefer.model('lightTheme');
@@ -201,20 +201,20 @@ export async function common(createVue: () => App<Element>) {
}, { immediate: true }); }, { immediate: true });
watch(prefer.r.useBlurEffectForModal, v => { watch(prefer.r.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); window.document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true }); }, { immediate: true });
watch(prefer.r.useBlurEffect, v => { watch(prefer.r.useBlurEffect, v => {
if (v) { if (v) {
document.documentElement.style.removeProperty('--MI-blur'); window.document.documentElement.style.removeProperty('--MI-blur');
} else { } else {
document.documentElement.style.setProperty('--MI-blur', 'none'); window.document.documentElement.style.setProperty('--MI-blur', 'none');
} }
}, { immediate: true }); }, { immediate: true });
// Keep screen on // Keep screen on
const onVisibilityChange = () => document.addEventListener('visibilitychange', () => { const onVisibilityChange = () => window.document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') { if (window.document.visibilityState === 'visible') {
navigator.wakeLock.request('screen'); navigator.wakeLock.request('screen');
} }
}); });
@@ -224,7 +224,7 @@ export async function common(createVue: () => App<Element>) {
.catch(() => { .catch(() => {
// On WebKit-based browsers, user activation is required to send wake lock request // On WebKit-based browsers, user activation is required to send wake lock request
// https://webkit.org/blog/13862/the-user-activation-api/ // https://webkit.org/blog/13862/the-user-activation-api/
document.addEventListener( window.document.addEventListener(
'click', 'click',
() => navigator.wakeLock.request('screen').then(onVisibilityChange), () => navigator.wakeLock.request('screen').then(onVisibilityChange),
{ once: true }, { once: true },
@@ -233,7 +233,7 @@ export async function common(createVue: () => App<Element>) {
} }
if (prefer.s.makeEveryTextElementsSelectable) { if (prefer.s.makeEveryTextElementsSelectable) {
document.documentElement.classList.add('forceSelectableAll'); window.document.documentElement.classList.add('forceSelectableAll');
} }
//#region Fetch user //#region Fetch user
@@ -278,16 +278,16 @@ export async function common(createVue: () => App<Element>) {
const rootEl = ((): HTMLElement => { const rootEl = ((): HTMLElement => {
const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID);
if (currentRoot) { if (currentRoot) {
console.warn('multiple import detected'); console.warn('multiple import detected');
return currentRoot; return currentRoot;
} }
const root = document.createElement('div'); const root = window.document.createElement('div');
root.id = MISSKEY_MOUNT_DIV_ID; root.id = MISSKEY_MOUNT_DIV_ID;
document.body.appendChild(root); window.document.body.appendChild(root);
return root; return root;
})(); })();
@@ -300,24 +300,26 @@ export async function common(createVue: () => App<Element>) {
removeSplash(); removeSplash();
//#region Self-XSS 対策メッセージ //#region Self-XSS 対策メッセージ
console.log( if (!_DEV_) {
`%c${i18n.ts._selfXssPrevention.warning}`, console.log(
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;', `%c${i18n.ts._selfXssPrevention.warning}`,
); 'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
console.log( );
`%c${i18n.ts._selfXssPrevention.title}`, console.log(
'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;', `%c${i18n.ts._selfXssPrevention.title}`,
); 'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
console.log( );
`%c${i18n.ts._selfXssPrevention.description1}`, console.log(
'font-size: 16px; font-weight: 700;', `%c${i18n.ts._selfXssPrevention.description1}`,
); 'font-size: 16px; font-weight: 700;',
console.log( );
`%c${i18n.ts._selfXssPrevention.description2}`, console.log(
'font-size: 16px;', `%c${i18n.ts._selfXssPrevention.description2}`,
'font-size: 20px; font-weight: 700; color: #f00;', 'font-size: 16px;',
); 'font-size: 20px; font-weight: 700; color: #f00;',
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' })); );
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
}
//#endregion //#endregion
return { return {
@@ -328,7 +330,7 @@ export async function common(createVue: () => App<Element>) {
} }
function removeSplash() { function removeSplash() {
const splash = document.getElementById('splash'); const splash = window.document.getElementById('splash');
if (splash) { if (splash) {
splash.style.opacity = '0'; splash.style.opacity = '0';
splash.style.pointerEvents = 'none'; splash.style.pointerEvents = 'none';

View File

@@ -43,7 +43,7 @@ export async function mainBoot() {
if (!$i) uiStyle = 'visitor'; if (!$i) uiStyle = 'visitor';
if (searchParams.has('zen')) uiStyle = 'zen'; if (searchParams.has('zen')) uiStyle = 'zen';
if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && location.pathname !== '/') uiStyle = 'zen'; if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && window.location.pathname !== '/') uiStyle = 'zen';
if (searchParams.has('ui')) uiStyle = searchParams.get('ui'); if (searchParams.has('ui')) uiStyle = searchParams.get('ui');
@@ -216,7 +216,7 @@ export async function mainBoot() {
let reloadDialogShowing = false; let reloadDialogShowing = false;
stream.on('_disconnected_', async () => { stream.on('_disconnected_', async () => {
if (prefer.s.serverDisconnectedBehavior === 'reload') { if (prefer.s.serverDisconnectedBehavior === 'reload') {
location.reload(); window.location.reload();
} else if (prefer.s.serverDisconnectedBehavior === 'dialog') { } else if (prefer.s.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return; if (reloadDialogShowing) return;
reloadDialogShowing = true; reloadDialogShowing = true;
@@ -227,7 +227,7 @@ export async function mainBoot() {
}); });
reloadDialogShowing = false; reloadDialogShowing = false;
if (!canceled) { if (!canceled) {
location.reload(); window.location.reload();
} }
} }
}); });
@@ -398,7 +398,7 @@ export async function mainBoot() {
let lastVisibilityChangedAt = Date.now(); let lastVisibilityChangedAt = Date.now();
function claimPlainLucky() { function claimPlainLucky() {
if (document.visibilityState !== 'visible') { if (window.document.visibilityState !== 'visible') {
if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer); if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer);
return; return;
} }
@@ -413,7 +413,7 @@ export async function mainBoot() {
window.addEventListener('visibilitychange', () => { window.addEventListener('visibilitychange', () => {
const now = Date.now(); const now = Date.now();
if (document.visibilityState === 'visible') { if (window.document.visibilityState === 'visible') {
// タブを高速で切り替えたら取得処理が何度も走るのを防ぐ // タブを高速で切り替えたら取得処理が何度も走るのを防ぐ
if ((now - lastVisibilityChangedAt) < 1000 * 10) { if ((now - lastVisibilityChangedAt) < 1000 * 10) {
justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10); justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10);
@@ -458,7 +458,7 @@ export async function mainBoot() {
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !window.location.pathname.startsWith('/miauth')) {
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {
closed: () => dispose(), closed: () => dispose(),
@@ -554,7 +554,7 @@ export async function mainBoot() {
mainRouter.push('/search'); mainRouter.push('/search');
}, },
} as const satisfies Keymap; } as const satisfies Keymap;
document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
initializeSw(); initializeSw();
} }

View File

@@ -192,7 +192,7 @@ function tick() {
tick(); tick();
function calcColors() { function calcColors() {
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(window.document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark(); const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark();
const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas> <canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -359,7 +359,7 @@ onMounted(() => {
props.textarea.addEventListener('keydown', onKeydown); props.textarea.addEventListener('keydown', onKeydown);
document.body.addEventListener('mousedown', onMousedown); window.document.body.addEventListener('mousedown', onMousedown);
nextTick(() => { nextTick(() => {
exec(); exec();
@@ -375,7 +375,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
props.textarea.removeEventListener('keydown', onKeydown); props.textarea.removeEventListener('keydown', onKeydown);
document.body.removeEventListener('mousedown', onMousedown); window.document.body.removeEventListener('mousedown', onMousedown);
}); });
</script> </script>

View File

@@ -92,7 +92,7 @@ function onMousedown(evt: MouseEvent): void {
const target = evt.target! as HTMLElement; const target = evt.target! as HTMLElement;
const rect = target.getBoundingClientRect(); const rect = target.getBoundingClientRect();
const ripple = document.createElement('div'); const ripple = window.document.createElement('div');
ripple.classList.add(ripples.value!.dataset.childrenClass!); ripple.classList.add(ripples.value!.dataset.childrenClass!);
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';

View File

@@ -112,7 +112,7 @@ watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => {
if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') {
available.value = true; available.value = true;
} else if (src.value !== null) { } else if (src.value !== null) {
(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), { (window.document.getElementById(scriptId.value) ?? window.document.head.appendChild(Object.assign(window.document.createElement('script'), {
async: true, async: true,
id: scriptId.value, id: scriptId.value,
src: src.value, src: src.value,
@@ -149,7 +149,7 @@ async function requestRender() {
if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) { if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) {
// reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する. // reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する.
// 同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので // 同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので
const elem = document.createElement('div'); const elem = window.document.createElement('div');
captchaEl.value.appendChild(elem); captchaEl.value.appendChild(elem);
captchaWidgetId.value = captcha.value.render(elem, { captchaWidgetId.value = captcha.value.render(elem, {
@@ -174,7 +174,7 @@ async function requestRender() {
function clearWidget() { function clearWidget() {
if (props.provider === 'mcaptcha') { if (props.provider === 'mcaptcha') {
const container = document.getElementById('mcaptcha__widget-container'); const container = window.document.getElementById('mcaptcha__widget-container');
if (container) { if (container) {
container.innerHTML = ''; container.innerHTML = '';
} }

View File

@@ -68,11 +68,11 @@ onMounted(() => {
rootEl.value.style.left = `${left}px`; rootEl.value.style.left = `${left}px`;
} }
document.body.addEventListener('mousedown', onMousedown); window.document.body.addEventListener('mousedown', onMousedown);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.body.removeEventListener('mousedown', onMousedown); window.document.body.removeEventListener('mousedown', onMousedown);
}); });
function onMousedown(evt: Event) { function onMousedown(evt: Event) {

View File

@@ -3,14 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw'; import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { file } from '../../.storybook/fakes.js'; import { file } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkCropperDialog from './MkCropperDialog.vue'; import MkCropperDialog from './MkCropperDialog.vue';
import type { StoryObj } from '@storybook/vue3';
export const Default = { export const Default = {
render(args) { render(args) {
return { return {
@@ -55,7 +53,7 @@ export const Default = {
http.get('/proxy/image.webp', async ({ request }) => { http.get('/proxy/image.webp', async ({ request }) => {
const url = new URL(request.url).searchParams.get('url'); const url = new URL(request.url).searchParams.get('url');
if (url === 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true') { if (url === 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true') {
const image = await (await fetch('client-assets/fedi.jpg')).blob(); const image = await (await window.fetch('client-assets/fedi.jpg')).blob();
return new HttpResponse(image, { return new HttpResponse(image, {
headers: { headers: {
'Content-Type': 'image/jpeg', 'Content-Type': 'image/jpeg',

View File

@@ -122,7 +122,7 @@ onMounted(() => {
cropper = new Cropper(imgEl.value!, { cropper = new Cropper(imgEl.value!, {
}); });
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(window.document.documentElement);
const selection = cropper.getCropperSelection()!; const selection = cropper.getCropperSelection()!;
selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();

View File

@@ -180,7 +180,7 @@ function applyToPreview() {
nextTick(() => { nextTick(() => {
if (currentPreviewUrl === embedPreviewUrl.value) { if (currentPreviewUrl === embedPreviewUrl.value) {
// URLが変わらなくてもリロード // URLが変わらなくてもリロード
iframeEl.value?.contentWindow?.location.reload(); iframeEl.value?.contentWindow?.window.location.reload();
} }
}); });
} }

View File

@@ -11,54 +11,91 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- 拡張用 --> <!-- 拡張用 -->
<i v-else class="ti ti-download"></i> <i v-else class="ti ti-download"></i>
</div> </div>
<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].title }}</h2>
<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
<MkInfo v-if="isPlugin" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
<FormSection>
<template #label>{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].metaTitle }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.version }}</template>
<template #value>{{ extension.meta.version }}</template>
</MkKeyValue>
<MkKeyValue v-if="isPlugin">
<template #key>{{ i18n.ts.permission }}</template>
<template #value>
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
</ul>
<template v-else>{{ i18n.ts.none }}</template>
</template>
</MkKeyValue>
<MkKeyValue v-if="isTheme">
<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
</MkKeyValue>
<MkFolder>
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
<MkCode :code="extension.raw"/> <h2 v-if="isPlugin" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._plugin.title }}</h2>
</MkFolder> <h2 v-else-if="isTheme" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._theme.title }}</h2>
</div>
</FormSection> <MkInfo :warn="true">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</MkInfo>
<div v-if="isPlugin" class="_gaps_s">
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-info-circle"></i></template>
<template #label>{{ i18n.ts.metadata }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.version }}</template>
<template #value>{{ extension.meta.version }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.permission }}</template>
<template #value>
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
</ul>
<template v-else>{{ i18n.ts.none }}</template>
</template>
</MkKeyValue>
</div>
</MkFolder>
<MkFolder :withSpacer="false">
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
<MkCode :code="extension.raw"/>
</MkFolder>
</div>
<div v-else-if="isTheme" class="_gaps_s">
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-info-circle"></i></template>
<template #label>{{ i18n.ts.metadata }}</template>
<div class="_gaps_s">
<FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ extension.meta.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ extension.meta.author }}</template>
</MkKeyValue>
</FormSplit>
<MkKeyValue>
<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
</MkKeyValue>
</div>
</MkFolder>
<MkFolder :withSpacer="false">
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._theme.code }}</template>
<MkCode :code="extension.raw"/>
</MkFolder>
</div>
<slot name="additionalInfo"/> <slot name="additionalInfo"/>
<div class="_buttonsCenter"> <div class="_buttonsCenter">
<MkButton primary @click="emits('confirm')"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton> <MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
<MkButton gradate rounded large @click="emits('confirm')"><i class="ti ti-download"></i> {{ i18n.ts.install }}</MkButton>
</div> </div>
</div> </div>
</template> </template>
@@ -105,6 +142,7 @@ const props = defineProps<{
const emits = defineEmits<{ const emits = defineEmits<{
(ev: 'confirm'): void; (ev: 'confirm'): void;
(ev: 'cancel'): void;
}>(); }>();
</script> </script>
@@ -112,13 +150,13 @@ const emits = defineEmits<{
.extInstallerRoot { .extInstallerRoot {
border-radius: var(--MI-radius); border-radius: var(--MI-radius);
background: var(--MI_THEME-panel); background: var(--MI_THEME-panel);
padding: 1.5rem; padding: 20px;
} }
.extInstallerIconWrapper { .extInstallerIconWrapper {
width: 48px; width: 48px;
height: 48px; height: 48px;
font-size: 24px; font-size: 20px;
line-height: 48px; line-height: 48px;
text-align: center; text-align: center;
border-radius: 50%; border-radius: 50%;
@@ -135,10 +173,6 @@ const emits = defineEmits<{
margin: 0; margin: 0;
} }
.extInstallerNormDesc {
text-align: center;
}
.extInstallerKVList { .extInstallerKVList {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;

View File

@@ -116,7 +116,7 @@ function toggle() {
} }
onMounted(() => { onMounted(() => {
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(window.document.documentElement);
const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent'; const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent';
const myBg = computedStyle.getPropertyValue('--MI_THEME-panel'); const myBg = computedStyle.getPropertyValue('--MI_THEME-panel');
bgSame.value = parentBg === myBg; bgSame.value = parentBg === myBg;

View File

@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo" :leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
:style="`--swipe: ${pullDistance}px;`" :style="`--swipe: ${pullDistance}px;`"
> >
<!-- 注意slot内の最上位要素に動的にkeyを設定すること --> <div :key="tabModel">
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません --> <slot></slot>
<slot></slot> </div>
</Transition> </Transition>
</div> </div>
</template> </template>

View File

@@ -55,7 +55,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha
const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => { const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
// テスト環境で Web Worker インスタンスは作成できない // テスト環境で Web Worker インスタンスは作成できない
if (import.meta.env.MODE === 'test') { if (import.meta.env.MODE === 'test') {
const canvas = document.createElement('canvas'); const canvas = window.document.createElement('canvas');
canvas.width = 64; canvas.width = 64;
canvas.height = 64; canvas.height = 64;
resolve(canvas); resolve(canvas);
@@ -69,13 +69,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4), Math.min(navigator.hardwareConcurrency - 1, 4),
); );
resolve(workers); resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!');
} else { } else {
const canvas = document.createElement('canvas'); const canvas = window.document.createElement('canvas');
canvas.width = 64; canvas.width = 64;
canvas.height = 64; canvas.height = 64;
resolve(canvas); resolve(canvas);
if (_DEV_) console.log('WebGL2 in worker is not supported...');
} }
testWorker.terminate(); testWorker.terminate();
}); });

View File

@@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw'; import { HttpResponse, http } from 'msw';
import { federationInstance } from '../../.storybook/fakes.js'; import { federationInstance } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js'; import { commonHandlers } from '../../.storybook/mocks.js';
import { getChartResolver } from '../../.storybook/charts.js'; import { getChartResolver } from '../../.storybook/charts.js';
import MkInstanceCardMini from './MkInstanceCardMini.vue'; import MkInstanceCardMini from './MkInstanceCardMini.vue';
import type { StoryObj } from '@storybook/vue3';
export const Default = { export const Default = {
render(args) { render(args) {
@@ -48,7 +47,7 @@ export const Default = {
const url = new URL(urlStr); const url = new URL(urlStr);
if (url.href.startsWith('https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/')) { if (url.href.startsWith('https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/')) {
const image = await (await fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob(); const image = await (await window.fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob();
return new HttpResponse(image, { return new HttpResponse(image, {
headers: { headers: {
'Content-Type': 'image/jpeg', 'Content-Type': 'image/jpeg',

View File

@@ -126,7 +126,7 @@ function createDoughnut(chartEl, tooltip, data) {
labels: data.map(x => x.name), labels: data.map(x => x.name),
datasets: [{ datasets: [{
backgroundColor: data.map(x => x.color), backgroundColor: data.map(x => x.color),
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'), borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'),
borderWidth: 2, borderWidth: 2,
hoverOffset: 0, hoverOffset: 0,
data: data.map(x => x.value), data: data.map(x => x.value),

View File

@@ -148,7 +148,7 @@ const keymap = {
// PlayerElもしくはその子要素にフォーカスがあるかどうか // PlayerElもしくはその子要素にフォーカスがあるかどうか
function hasFocus() { function hasFocus() {
if (!playerEl.value) return false; if (!playerEl.value) return false;
return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement); return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement);
} }
const playerEl = useTemplateRef('playerEl'); const playerEl = useTemplateRef('playerEl');

View File

@@ -48,7 +48,7 @@ const props = defineProps<{
const gallery = useTemplateRef('gallery'); const gallery = useTemplateRef('gallery');
const pswpZIndex = os.claimZIndex('middle'); const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); window.document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
const count = computed(() => props.mediaList.filter(media => previewable(media)).length); const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
let lightbox: PhotoSwipeLightbox | null = null; let lightbox: PhotoSwipeLightbox | null = null;
@@ -166,7 +166,7 @@ onMounted(() => {
className: 'pswp__alt-text-container', className: 'pswp__alt-text-container',
appendTo: 'wrapper', appendTo: 'wrapper',
onInit: (el, pswp) => { onInit: (el, pswp) => {
const textBox = document.createElement('p'); const textBox = window.document.createElement('p');
textBox.className = 'pswp__alt-text _acrylic'; textBox.className = 'pswp__alt-text _acrylic';
el.appendChild(textBox); el.appendChild(textBox);
@@ -178,19 +178,19 @@ onMounted(() => {
}); });
lightbox.on('afterInit', () => { lightbox.on('afterInit', () => {
activeEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; activeEl = window.document.activeElement instanceof HTMLElement ? window.document.activeElement : null;
focusParent(activeEl, true, true); focusParent(activeEl, true, true);
lightbox?.pswp?.element?.focus({ lightbox?.pswp?.element?.focus({
preventScroll: true, preventScroll: true,
}); });
history.pushState(null, '', '#pswp'); window.history.pushState(null, '', '#pswp');
}); });
lightbox.on('destroy', () => { lightbox.on('destroy', () => {
focusParent(activeEl, true, false); focusParent(activeEl, true, false);
activeEl = null; activeEl = null;
if (window.location.hash === '#pswp') { if (window.location.hash === '#pswp') {
history.back(); window.history.back();
} }
}); });

View File

@@ -171,7 +171,7 @@ const keymap = {
// PlayerElもしくはその子要素にフォーカスがあるかどうか // PlayerElもしくはその子要素にフォーカスがあるかどうか
function hasFocus() { function hasFocus() {
if (!playerEl.value) return false; if (!playerEl.value) return false;
return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement); return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement);
} }
// eslint-disable-next-line vue/no-setup-props-reactivity-loss // eslint-disable-next-line vue/no-setup-props-reactivity-loss
@@ -216,7 +216,7 @@ function showMenu(ev: MouseEvent) {
'2.0x': 2, '2.0x': 2,
}, },
}, },
...(document.pictureInPictureEnabled ? [{ ...(window.document.pictureInPictureEnabled ? [{
text: i18n.ts._mediaControls.pip, text: i18n.ts._mediaControls.pip,
icon: 'ti ti-picture-in-picture', icon: 'ti ti-picture-in-picture',
action: togglePictureInPicture, action: togglePictureInPicture,
@@ -384,8 +384,8 @@ function toggleFullscreen() {
function togglePictureInPicture() { function togglePictureInPicture() {
if (videoEl.value) { if (videoEl.value) {
if (document.pictureInPictureElement) { if (window.document.pictureInPictureElement) {
document.exitPictureInPicture(); window.document.exitPictureInPicture();
} else { } else {
videoEl.value.requestPictureInPicture(); videoEl.value.requestPictureInPicture();
} }

View File

@@ -179,7 +179,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch } from 'vue'; import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef } from 'vue';
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js'; import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js'; import type { Keymap } from '@/utility/hotkey.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue'; import MkSwitchButton from '@/components/MkSwitch.button.vue';
@@ -257,7 +257,7 @@ watch(() => props.items, () => {
}); });
const childMenu = ref<MenuItem[] | null>(); const childMenu = ref<MenuItem[] | null>();
const childTarget = useTemplateRef('childTarget'); const childTarget = shallowRef<HTMLElement>();
function closeChild() { function closeChild() {
childMenu.value = null; childMenu.value = null;
@@ -358,10 +358,10 @@ function switchItem(item: MenuSwitch & { ref: any }) {
function focusUp() { function focusUp() {
if (disposed) return; if (disposed) return;
if (!itemsEl.value?.contains(document.activeElement)) return; if (!itemsEl.value?.contains(window.document.activeElement)) return;
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable); const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
const activeIndex = focusableElements.findIndex(el => el === document.activeElement); const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
const targetIndex = (activeIndex !== -1 && activeIndex !== 0) ? (activeIndex - 1) : (focusableElements.length - 1); const targetIndex = (activeIndex !== -1 && activeIndex !== 0) ? (activeIndex - 1) : (focusableElements.length - 1);
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value; const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
@@ -370,10 +370,10 @@ function focusUp() {
function focusDown() { function focusDown() {
if (disposed) return; if (disposed) return;
if (!itemsEl.value?.contains(document.activeElement)) return; if (!itemsEl.value?.contains(window.document.activeElement)) return;
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable); const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
const activeIndex = focusableElements.findIndex(el => el === document.activeElement); const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
const targetIndex = (activeIndex !== -1 && activeIndex !== (focusableElements.length - 1)) ? (activeIndex + 1) : 0; const targetIndex = (activeIndex !== -1 && activeIndex !== (focusableElements.length - 1)) ? (activeIndex + 1) : 0;
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value; const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
@@ -400,9 +400,9 @@ const onGlobalMousedown = (ev: MouseEvent) => {
const setupHandlers = () => { const setupHandlers = () => {
if (!isNestingMenu) { if (!isNestingMenu) {
document.addEventListener('focusin', onGlobalFocusin, { passive: true }); window.document.addEventListener('focusin', onGlobalFocusin, { passive: true });
} }
document.addEventListener('mousedown', onGlobalMousedown, { passive: true }); window.document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
}; };
let disposed = false; let disposed = false;
@@ -410,9 +410,9 @@ let disposed = false;
const disposeHandlers = () => { const disposeHandlers = () => {
disposed = true; disposed = true;
if (!isNestingMenu) { if (!isNestingMenu) {
document.removeEventListener('focusin', onGlobalFocusin); window.document.removeEventListener('focusin', onGlobalFocusin);
} }
document.removeEventListener('mousedown', onGlobalMousedown); window.document.removeEventListener('mousedown', onGlobalMousedown);
}; };
onMounted(() => { onMounted(() => {

View File

@@ -48,7 +48,7 @@ const polygonPoints = ref('');
const headX = ref<number | null>(null); const headX = ref<number | null>(null);
const headY = ref<number | null>(null); const headY = ref<number | null>(null);
const clock = ref<number | null>(null); const clock = ref<number | null>(null);
const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent'));
const color = accent.toRgbString(); const color = accent.toRgbString();
function draw(): void { function draw(): void {

View File

@@ -59,7 +59,7 @@ const pagination = computed(() => prefer.r.useGroupedNotifications.value ? {
function onNotification(notification) { function onNotification(notification) {
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false; const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
if (isMuted || document.visibilityState === 'visible') { if (isMuted || window.document.visibilityState === 'visible') {
useStream().send('readNotification'); useStream().send('readNotification');
} }

View File

@@ -142,7 +142,7 @@ const {
} = prefer.r; } = prefer.r;
const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value); const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body); const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : window.document.body);
const visibility = useDocumentVisibility(); const visibility = useDocumentVisibility();

View File

@@ -151,9 +151,9 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
closed: () => dispose(), closed: () => dispose(),
}); });
const style = document.createElement('style'); const style = window.document.createElement('style');
style.appendChild(document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }')); style.appendChild(window.document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }'));
document.head.appendChild(style); window.document.head.appendChild(style);
const thumbWidth = getThumbWidth(); const thumbWidth = getThumbWidth();
@@ -172,7 +172,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
let beforeValue = finalValue.value; let beforeValue = finalValue.value;
const onMouseup = () => { const onMouseup = () => {
document.head.removeChild(style); window.document.head.removeChild(style);
tooltipForDragShowing.value = false; tooltipForDragShowing.value = false;
window.removeEventListener('mousemove', onDrag); window.removeEventListener('mousemove', onDrag);
window.removeEventListener('touchmove', onDrag); window.removeEventListener('touchmove', onDrag);

View File

@@ -136,7 +136,7 @@ async function menu(ev) {
} }
function anime() { function anime() {
if (document.hidden || !prefer.s.animation || buttonEl.value == null) return; if (window.document.hidden || !prefer.s.animation || buttonEl.value == null) return;
const rect = buttonEl.value.getBoundingClientRect(); const rect = buttonEl.value.getBoundingClientRect();
const x = rect.left + 16; const x = rect.left + 16;

View File

@@ -44,7 +44,7 @@ onMounted(async () => {
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent'));
const color = accent.toHex(); const color = accent.toHex();
if (chartEl.value == null) return; if (chartEl.value == null) return;

View File

@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:width="400" :width="400"
:height="500" :height="500"
@close="onCloseModalWindow" @close="onCloseModalWindow"
@closed="console.log('MkRoleSelectDialog: closed') ; $emit('dispose')" @closed="emit('closed')"
> >
<template #header>{{ title }}</template> <template #header>{{ title }}</template>
<MkSpacer :marginMin="20" :marginMax="28"> <MkSpacer :marginMin="20" :marginMax="28">
@@ -58,7 +58,7 @@ import MkLoading from '@/components/global/MkLoading.vue';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'done', value: Misskey.entities.Role[]), (ev: 'done', value: Misskey.entities.Role[]),
(ev: 'close'), (ev: 'close'),
(ev: 'dispose'), (ev: 'closed'),
}>(); }>();
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@@ -267,7 +267,7 @@ async function onSubmit(): Promise<void> {
'testcaptcha-response': testcaptchaResponse.value, 'testcaptcha-response': testcaptchaResponse.value,
}; };
const res = await fetch(`${config.apiUrl}/signup`, { const res = await window.fetch(`${config.apiUrl}/signup`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@@ -20,7 +20,7 @@ import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas; const loaded = !!window.TagCanvas;
const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz'; const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(window.document.documentElement);
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join(''); const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const available = ref(false); const available = ref(false);
@@ -57,7 +57,7 @@ onMounted(() => {
if (loaded) { if (loaded) {
available.value = true; available.value = true;
} else { } else {
document.head.appendChild(Object.assign(document.createElement('script'), { window.document.head.appendChild(Object.assign(window.document.createElement('script'), {
async: true, async: true,
src: '/client-assets/tagcanvas.min.js', src: '/client-assets/tagcanvas.min.js',
})).addEventListener('load', () => available.value = true); })).addEventListener('load', () => available.value = true);

View File

@@ -61,7 +61,7 @@ async function renderChart() {
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(window.document.documentElement);
const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
const colorRead = accent; const colorRead = accent;

View File

@@ -240,7 +240,7 @@ function onHeaderMousedown(evt: MouseEvent | TouchEvent) {
const main = rootEl.value; const main = rootEl.value;
if (main == null) return; if (main == null) return;
if (!contains(main, document.activeElement)) main.focus(); if (!contains(main, window.document.activeElement)) main.focus();
const position = main.getBoundingClientRect(); const position = main.getBoundingClientRect();

View File

@@ -87,7 +87,7 @@ function openWindow() {
function nav(ev: MouseEvent) { function nav(ev: MouseEvent) {
if (behavior === 'browser') { if (behavior === 'browser') {
location.href = props.to; window.location.href = props.to;
return; return;
} }

View File

@@ -170,7 +170,7 @@ onMounted(() => {
if (props.rootEl) { if (props.rootEl) {
ro2 = new ResizeObserver((entries, observer) => { ro2 = new ResizeObserver((entries, observer) => {
if (document.body.contains(el.value as HTMLElement)) { if (window.document.body.contains(el.value as HTMLElement)) {
nextTick(() => renderTab()); nextTick(() => renderTab());
} }
}); });

View File

@@ -69,6 +69,8 @@ const emit = defineEmits<{
(ev: 'update:tab', key: string); (ev: 'update:tab', key: string);
}>(); }>();
const viewId = inject(DI.viewId);
const viewTransitionName = computed(() => `${viewId}---pageHeader`);
const injectedPageMetadata = inject(DI.pageMetadata); const injectedPageMetadata = inject(DI.pageMetadata);
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value); const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
@@ -106,7 +108,7 @@ function onTabClick(): void {
const calcBg = () => { const calcBg = () => {
const rawBg = 'var(--MI_THEME-bg)'; const rawBg = 'var(--MI_THEME-bg)';
const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
tinyBg.setAlpha(0.85); tinyBg.setAlpha(0.85);
bg.value = tinyBg.toRgbString(); bg.value = tinyBg.toRgbString();
}; };
@@ -120,7 +122,7 @@ onMounted(() => {
if (el.value && el.value.parentElement) { if (el.value && el.value.parentElement) {
narrow.value = el.value.parentElement.offsetWidth < 500; narrow.value = el.value.parentElement.offsetWidth < 500;
ro = new ResizeObserver((entries, observer) => { ro = new ResizeObserver((entries, observer) => {
if (el.value && el.value.parentElement && document.body.contains(el.value as HTMLElement)) { if (el.value && el.value.parentElement && window.document.body.contains(el.value as HTMLElement)) {
narrow.value = el.value.parentElement.offsetWidth < 500; narrow.value = el.value.parentElement.offsetWidth < 500;
} }
}); });
@@ -140,6 +142,7 @@ onUnmounted(() => {
backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px));
border-bottom: solid 0.5px var(--MI_THEME-divider); border-bottom: solid 0.5px var(--MI_THEME-divider);
width: 100%; width: 100%;
view-transition-name: v-bind(viewTransitionName);
} }
.upper, .upper,

View File

@@ -0,0 +1,21 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<MkAnimBg style="position: absolute;"/>
<div class="_pageScrollable" style="position: absolute; top: 0; width: 100%; height: 100%;">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import MkAnimBg from '@/components/MkAnimBg.vue';
</script>
<style lang="scss" module>
</style>

View File

@@ -0,0 +1,32 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer class="_pageScrollable">
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
<slot></slot>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import type { PageHeaderItem } from '@/types/page-header.js';
import type { Tab } from './MkPageHeader.tabs.vue';
const props = withDefaults(defineProps<{
tabs?: Tab[];
actions?: PageHeaderItem[] | null;
thin?: boolean;
hideTitle?: boolean;
displayMyAvatar?: boolean;
}>(), {
tabs: () => ([] as Tab[]),
});
const tab = defineModel<string>('tab');
</script>
<style lang="scss" module>
</style>

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div class="_pageContainer" style="height: 100%;"> <div ref="rootEl" class="_pageContainer" :class="$style.root">
<KeepAlive :max="prefer.s.numberOfPageCache"> <KeepAlive :max="prefer.s.numberOfPageCache">
<Suspense :timeout="0"> <Suspense :timeout="0">
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/> <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
@@ -18,11 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, provide, ref, shallowRef } from 'vue'; import { inject, nextTick, onMounted, provide, ref, shallowRef, useTemplateRef } from 'vue';
import type { Router } from '@/router.js'; import type { Router } from '@/router.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import MkLoadingPage from '@/pages/_loading_.vue'; import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js'; import { DI } from '@/di.js';
import { randomId } from '@/utility/random-id.js';
import { deepEqual } from '@/utility/deep-equal.js';
const props = defineProps<{ const props = defineProps<{
router?: Router; router?: Router;
@@ -34,18 +36,76 @@ if (router == null) {
throw new Error('no router provided'); throw new Error('no router provided');
} }
const viewId = randomId();
provide(DI.viewId, viewId);
const currentDepth = inject(DI.routerCurrentDepth, 0); const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1); provide(DI.routerCurrentDepth, currentDepth + 1);
const rootEl = useTemplateRef('rootEl');
onMounted(() => {
rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
});
// view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成
const viewTransitionStylesTag = window.document.createElement('style');
viewTransitionStylesTag.textContent = `
@keyframes ${viewId}-old {
to { transform: scale(0.95); opacity: 0; }
}
@keyframes ${viewId}-new {
from { transform: scale(0.95); opacity: 0; }
}
::view-transition-old(${viewId}) {
animation-duration: 0.2s;
animation-name: ${viewId}-old;
}
::view-transition-new(${viewId}) {
animation-duration: 0.2s;
animation-name: ${viewId}-new;
}
`;
window.document.head.appendChild(viewTransitionStylesTag);
const current = router.current!; const current = router.current!;
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage); const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props); const currentPageProps = ref(current.props);
let currentRoutePath = current.route.path;
const key = ref(router.getCurrentFullPath()); const key = ref(router.getCurrentFullPath());
router.useListener('change', ({ resolved }) => { router.useListener('change', ({ resolved }) => {
if (resolved == null || 'redirect' in resolved.route) return; if (resolved == null || 'redirect' in resolved.route) return;
currentPageComponent.value = resolved.route.component; if (resolved.route.path === currentRoutePath && deepEqual(resolved.props, currentPageProps.value)) return;
currentPageProps.value = resolved.props;
key.value = router.getCurrentFullPath(); function _() {
currentPageComponent.value = resolved.route.component;
currentPageProps.value = resolved.props;
key.value = router.getCurrentFullPath();
currentRoutePath = resolved.route.path;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (prefer.s.animation && window.document.startViewTransition) {
window.document.startViewTransition(() => new Promise((res) => {
_();
nextTick(() => {
res();
//setTimeout(res, 100);
});
}));
} else {
_();
}
}); });
</script> </script>
<style lang="scss" module>
.root {
height: 100%;
background-color: var(--MI_THEME-bg);
}
</style>

View File

@@ -42,7 +42,7 @@ const highlighted = ref(props.markerId === searchMarkerId.value);
function checkChildren() { function checkChildren() {
if (props.children?.includes(searchMarkerId.value)) { if (props.children?.includes(searchMarkerId.value)) {
const el = document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`); const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
highlighted.value = el == null; highlighted.value = el == null;
} }
} }

View File

@@ -25,6 +25,8 @@ import MkPageHeader from './global/MkPageHeader.vue';
import MkSpacer from './global/MkSpacer.vue'; import MkSpacer from './global/MkSpacer.vue';
import MkStickyContainer from './global/MkStickyContainer.vue'; import MkStickyContainer from './global/MkStickyContainer.vue';
import MkLazy from './global/MkLazy.vue'; import MkLazy from './global/MkLazy.vue';
import PageWithHeader from './global/PageWithHeader.vue';
import PageWithAnimBg from './global/PageWithAnimBg.vue';
import SearchMarker from './global/SearchMarker.vue'; import SearchMarker from './global/SearchMarker.vue';
import SearchLabel from './global/SearchLabel.vue'; import SearchLabel from './global/SearchLabel.vue';
import SearchKeyword from './global/SearchKeyword.vue'; import SearchKeyword from './global/SearchKeyword.vue';
@@ -60,6 +62,8 @@ export const components = {
MkSpacer: MkSpacer, MkSpacer: MkSpacer,
MkStickyContainer: MkStickyContainer, MkStickyContainer: MkStickyContainer,
MkLazy: MkLazy, MkLazy: MkLazy,
PageWithHeader: PageWithHeader,
PageWithAnimBg: PageWithAnimBg,
SearchMarker: SearchMarker, SearchMarker: SearchMarker,
SearchLabel: SearchLabel, SearchLabel: SearchLabel,
SearchKeyword: SearchKeyword, SearchKeyword: SearchKeyword,
@@ -89,6 +93,8 @@ declare module '@vue/runtime-core' {
MkSpacer: typeof MkSpacer; MkSpacer: typeof MkSpacer;
MkStickyContainer: typeof MkStickyContainer; MkStickyContainer: typeof MkStickyContainer;
MkLazy: typeof MkLazy; MkLazy: typeof MkLazy;
PageWithHeader: typeof PageWithHeader;
PageWithAnimBg: typeof PageWithAnimBg;
SearchMarker: typeof SearchMarker; SearchMarker: typeof SearchMarker;
SearchLabel: typeof SearchLabel; SearchLabel: typeof SearchLabel;
SearchKeyword: typeof SearchKeyword; SearchKeyword: typeof SearchKeyword;

View File

@@ -11,4 +11,5 @@ export const DI = {
router: Symbol() as InjectionKey<Router>, router: Symbol() as InjectionKey<Router>,
mock: Symbol() as InjectionKey<boolean>, mock: Symbol() as InjectionKey<boolean>,
pageMetadata: Symbol() as InjectionKey<Ref<Record<string, any>>>, pageMetadata: Symbol() as InjectionKey<Ref<Record<string, any>>>,
viewId: Symbol() as InjectionKey<string>,
}; };

View File

@@ -13,7 +13,7 @@ export default {
el._keyHandler = makeHotkey(binding.value); el._keyHandler = makeHotkey(binding.value);
if (el._hotkey_global) { if (el._hotkey_global) {
document.addEventListener('keydown', el._keyHandler, { passive: false }); window.document.addEventListener('keydown', el._keyHandler, { passive: false });
} else { } else {
el.addEventListener('keydown', el._keyHandler, { passive: false }); el.addEventListener('keydown', el._keyHandler, { passive: false });
} }
@@ -21,7 +21,7 @@ export default {
unmounted(el) { unmounted(el) {
if (el._hotkey_global) { if (el._hotkey_global) {
document.removeEventListener('keydown', el._keyHandler); window.document.removeEventListener('keydown', el._keyHandler);
} else { } else {
el.removeEventListener('keydown', el._keyHandler); el.removeEventListener('keydown', el._keyHandler);
} }

View File

@@ -10,7 +10,7 @@ export default {
mounted(src, binding, vn) { mounted(src, binding, vn) {
const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const parentBg = getBgColor(src.parentElement) ?? 'transparent';
const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'); const myBg = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel');
if (parentBg === myBg) { if (parentBg === myBg) {
src.style.backgroundColor = 'var(--MI_THEME-bg)'; src.style.backgroundColor = 'var(--MI_THEME-bg)';

View File

@@ -47,7 +47,7 @@ export default {
} }
self.show = () => { self.show = () => {
if (!document.body.contains(el)) return; if (!window.document.body.contains(el)) return;
if (self._close) return; if (self._close) return;
if (self.text == null) return; if (self.text == null) return;

View File

@@ -31,7 +31,7 @@ export class UserPreview {
} }
private show() { private show() {
if (!document.body.contains(this.el)) return; if (!window.document.body.contains(this.el)) return;
if (this.promise) return; if (this.promise) return;
const showing = ref(true); const showing = ref(true);
@@ -58,7 +58,7 @@ export class UserPreview {
}; };
this.checkTimer = window.setInterval(() => { this.checkTimer = window.setInterval(() => {
if (!document.body.contains(this.el)) { if (!window.document.body.contains(this.el)) {
window.clearTimeout(this.showTimer); window.clearTimeout(this.showTimer);
window.clearTimeout(this.hideTimer); window.clearTimeout(this.hideTimer);
this.close(); this.close();

View File

@@ -12,7 +12,7 @@ import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERR
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
//#region loader //#region loader
const providedMetaEl = document.getElementById('misskey_meta'); const providedMetaEl = window.document.getElementById('misskey_meta');
let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null; let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null;
let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0; let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;

View File

@@ -320,7 +320,7 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
} }
const res = this.navigate(fullPath); const res = this.navigate(fullPath);
if (res.route.path === '/:(*)') { if (res.route.path === '/:(*)') {
location.href = fullPath; window.location.href = fullPath;
} else { } else {
this.emit('push', { this.emit('push', {
beforeFullPath, beforeFullPath,

View File

@@ -167,7 +167,7 @@ export const navbarItemDef = reactive({
title: i18n.ts.reload, title: i18n.ts.reload,
icon: 'ti ti-refresh', icon: 'ti ti-refresh',
action: (ev) => { action: (ev) => {
location.reload(); window.location.reload();
}, },
}, },
profile: { profile: {

View File

@@ -21,10 +21,10 @@ import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
import MkPageWindow from '@/components/MkPageWindow.vue'; import MkPageWindow from '@/components/MkPageWindow.vue';
import MkToast from '@/components/MkToast.vue'; import MkToast from '@/components/MkToast.vue';
import MkDialog from '@/components/MkDialog.vue'; import MkDialog from '@/components/MkDialog.vue';
import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkPopupMenu from '@/components/MkPopupMenu.vue';
import MkContextMenu from '@/components/MkContextMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue';
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { pleaseLogin } from '@/utility/please-login.js'; import { pleaseLogin } from '@/utility/please-login.js';
import { showMovedDialog } from '@/utility/show-moved-dialog.js'; import { showMovedDialog } from '@/utility/show-moved-dialog.js';
@@ -181,7 +181,7 @@ type EmitsExtractor<T> = {
export function popup<T extends Component>( export function popup<T extends Component>(
component: T, component: T,
props: ComponentProps<T>, props: ComponentProps<T>,
events: ComponentEmit<T> = {} as ComponentEmit<T>, events: Partial<ComponentEmit<T>> = {},
): { dispose: () => void } { ): { dispose: () => void } {
markRaw(component); markRaw(component);
@@ -460,7 +460,7 @@ export function authenticateDialog(): Promise<{
canceled: false; result: { password: string; token: string | null; }; canceled: false; result: { password: string; token: string | null; };
}> { }> {
return new Promise(resolve => { return new Promise(resolve => {
const { dispose } = popup(MkPasswordDialog, {}, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkPasswordDialog.vue')), {}, {
done: result => { done: result => {
resolve(result ? { canceled: false, result } : { canceled: true, result: undefined }); resolve(result ? { canceled: false, result } : { canceled: true, result: undefined });
}, },
@@ -617,30 +617,26 @@ export async function selectDriveFolder(multiple: boolean): Promise<Misskey.enti
}); });
} }
export async function selectRole(params: { export async function selectRole(params: ComponentProps<typeof MkRoleSelectDialog_TypeReferenceOnly>): Promise<
initialRoleIds?: string[],
title?: string,
infoMessage?: string,
publicOnly?: boolean,
}): Promise<
{ canceled: true; result: undefined; } | { canceled: true; result: undefined; } |
{ canceled: false; result: Misskey.entities.Role[] } { canceled: false; result: Misskey.entities.Role[] }
> { > {
return new Promise((resolve) => { return new Promise((resolve) => {
popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
done: roles => { done: roles => {
resolve({ canceled: false, result: roles }); resolve({ canceled: false, result: roles });
}, },
close: () => { close: () => {
resolve({ canceled: true, result: undefined }); resolve({ canceled: true, result: undefined });
}, },
}, 'dispose'); closed: () => dispose(),
});
}); });
} }
export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog>): Promise<string> { export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog_TypeReferenceOnly>): Promise<string> {
return new Promise(resolve => { return new Promise(resolve => {
const { dispose } = popup(MkEmojiPickerDialog, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src, src,
...opts, ...opts,
}, { }, {
@@ -675,7 +671,11 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
width?: number; width?: number;
onClosing?: () => void; onClosing?: () => void;
}): Promise<void> { }): Promise<void> {
let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(document.activeElement); if (!(src instanceof HTMLElement)) {
src = null;
}
let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(window.document.activeElement);
return new Promise(resolve => nextTick(() => { return new Promise(resolve => nextTick(() => {
const { dispose } = popup(MkPopupMenu, { const { dispose } = popup(MkPopupMenu, {
items, items,
@@ -704,7 +704,7 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
return Promise.resolve(); return Promise.resolve();
} }
let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(document.activeElement); let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(window.document.activeElement);
ev.preventDefault(); ev.preventDefault();
return new Promise(resolve => nextTick(() => { return new Promise(resolve => nextTick(() => {
const { dispose } = popup(MkContextMenu, { const { dispose } = popup(MkContextMenu, {

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkStickyContainer> <PageWithHeader :actions="headerActions" :tabs="headerTabs">
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<div style="overflow: clip;"> <div style="overflow: clip;">
<MkSpacer :contentMax="600" :marginMin="20"> <MkSpacer :contentMax="600" :marginMin="20">
<div class="_gaps_m znqjceqz"> <div class="_gaps_m znqjceqz">
@@ -130,7 +129,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</MkSpacer> </MkSpacer>
</div> </div>
</MkStickyContainer> </PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkStickyContainer> <PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs"> <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20"> <MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
<XOverview/> <XOverview/>
@@ -20,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInstanceStats/> <MkInstanceStats/>
</MkSpacer> </MkSpacer>
</MkHorizontalSwipe> </MkHorizontalSwipe>
</MkStickyContainer> </PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

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