Compare commits

..

67 Commits

Author SHA1 Message Date
syuilo
961badd01d New translations ja-jp.yml (German) 2025-03-23 03:19:14 +09:00
syuilo
1e4c49a9ab New translations ja-jp.yml (English) 2025-03-23 03:19:12 +09:00
syuilo
cc9d5bc2b5 New translations ja-jp.yml (Czech) 2025-03-21 02:23:37 +09:00
syuilo
144fabdead New translations ja-jp.yml (Korean) 2025-03-20 23:11:04 +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
github-actions[bot]
3b20279e20 Bump version to 2025.3.2-beta.4 2025-03-19 10:27:14 +00:00
Yuri Lee
21dc7aebe7 Fix: Don't delete remote emoji when import zip (#15674)
* Fix: Don't delete remote emoji when import zip

* Update packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts

Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>

---------

Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
2025-03-19 10:23:50 +00:00
syuilo
aff03708a7 New Crowdin updates (#15667)
* New translations ja-jp.yml (Chinese Traditional)

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

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Catalan)
2025-03-19 19:18:14 +09:00
syuilo
69ad3cf89b Update CHANGELOG.md 2025-03-19 19:17:33 +09:00
syuilo
7b323031b7 refactor(frontend): use useTemplateRef for DOM referencing 2025-03-19 18:46:03 +09:00
syuilo
81ac71f7e5 refactor(frontend): router refactoring 2025-03-19 18:06:22 +09:00
syuilo
2c76018b7f better import paths 2025-03-19 17:27:18 +09:00
syuilo
2dc2d2e4fe refactor 2025-03-19 16:04:01 +09:00
syuilo
409cd4fbd3 refactor(frontend): router refactoring 2025-03-19 15:54:30 +09:00
syuilo
7d4045e8b4 refactor(frontend): router refactoring 2025-03-19 15:24:56 +09:00
syuilo
bdc72e5817 clean up 2025-03-19 15:17:41 +09:00
syuilo
11378b17c5 🎨 2025-03-19 09:31:01 +09:00
syuilo
62bf0d53d3 🎨 2025-03-18 22:21:28 +09:00
syuilo
05391f59a5 enhance(frontend): improve StackingRouterView 2025-03-18 20:55:19 +09:00
syuilo
d609f41f61 🎨 2025-03-18 17:31:25 +09:00
syuilo
0a295e1bb0 🎨 2025-03-18 15:23:50 +09:00
syuilo
474155b677 follow up of 6c8f21b608 2025-03-17 13:27:47 +09:00
syuilo
6c8f21b608 fix(backend): 連合無しモードでも外部から照会可能だった問題を修正 2025-03-17 13:21:09 +09:00
ikasoba
7d5daa4b00 fix (#15671) 2025-03-17 03:40:10 +00:00
syuilo
1ce81f243e Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-16 19:04:16 +09:00
syuilo
a773f2976d refactor(frontend): signinRequired -> ensureSignin 2025-03-16 19:04:14 +09:00
github-actions[bot]
467c68fb98 Bump version to 2025.3.2-beta.3 2025-03-16 08:51:58 +00:00
syuilo
22b0ace8b4 enhance(frontend): 投稿フォームの設定メニューを改良 (改)
This reverts commit a814395127.
2025-03-16 17:48:16 +09:00
syuilo
a814395127 Revert "enhance(frontend): 投稿フォームの設定メニューを改良 (#14804)"
This reverts commit ce6b2448ce.
2025-03-16 17:21:20 +09:00
syuilo
81a0cbd294 chore(frontend): use toast to show message when call copyToClipboard 2025-03-16 15:04:38 +09:00
syuilo
32844e4775 🎨 2025-03-16 14:56:27 +09:00
github-actions[bot]
fbd9f47182 Bump version to 2025.3.2-beta.2 2025-03-16 05:18:29 +00:00
syuilo
1c9e25470a refactor 2025-03-16 14:15:09 +09:00
かっこかり
ce6b2448ce enhance(frontend): 投稿フォームの設定メニューを改良 (#14804)
* enhance(frontend): 投稿フォームの設定メニューを改良

* Update Changelog

* indent

* MkMenuのitemを切り出して共通化

* remove unused expose

* fix: ドロワーなどのOptionが当たらない問題を修正

* 他のpopupMenuの項目選択時と挙動をあわせる

* チュートリアルで詰む問題を修正

* Revert "MkMenuのitemを切り出して共通化"

This reverts commit ce3679798c.

* enhance: slotで共通化

* Update MkPostFormOtherMenu.vue

* remove duplicated locale key

* refactor: メニューの定義をMkPostForm側で行うように

* Update CHANGELOG.md

* [ci skip] Update MkPostFormOtherMenu.vue

* Update MkPostForm.vue

* Update CHANGELOG.md

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2025-03-16 05:05:58 +00:00
syuilo
7d44b47fdf New Crowdin updates (#15662)
* New translations ja-jp.yml (Catalan)

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

* New translations ja-jp.yml (German)

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

* New translations ja-jp.yml (Chinese Simplified)
2025-03-16 13:59:22 +09:00
syuilo
dca42fd6e6 enhance(frontend): improve ux for touch devices 2025-03-16 13:59:08 +09:00
syuilo
43153311c6 🎨 2025-03-16 13:43:47 +09:00
syuilo
2b23c7e7f5 chore(frontend): remove duplicated warn 2025-03-16 13:30:08 +09:00
syuilo
2d4e85b466 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2025-03-16 13:03:04 +09:00
syuilo
7bfada9792 enhance: remove bull-board support 2025-03-16 13:03:02 +09:00
lqvp
4fef9c670a fix(i18n): 通知タイプのcreateTokenが抜けていたのを修正 (#15663) 2025-03-16 02:27:41 +00:00
syuilo
9599261fc3 fix(frontend): fix settings-search-index of webhook 2025-03-16 11:02:54 +09:00
syuilo
c2940fd77c enhance(frontend): improve usability on touch device 2025-03-16 10:58:06 +09:00
314 changed files with 2440 additions and 2453 deletions

View File

@@ -24,9 +24,6 @@ updates:
aws-sdk: aws-sdk:
patterns: patterns:
- "@aws-sdk/*" - "@aws-sdk/*"
bull-board:
patterns:
- "@bull-board/*"
nestjs: nestjs:
patterns: patterns:
- "@nestjs/*" - "@nestjs/*"

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

@@ -1,23 +1,31 @@
## 2025.3.2 ## 2025.3.2
### General ### General
- - セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
- Misskeyネイティブでダッシュボードを実装予定です
### Client ### Client
- Feat: 設定の管理が強化されました - Feat: 設定の管理が強化されました
- 自動でバックアップされるように - 自動でバックアップされるように
- 任意の設定項目をデバイス間で同期できるように - 任意の設定項目をデバイス間で同期できるように
- Feat: 画面を重ねて表示するオプションを実装(実験的)
- Enhance: プラグインの管理が強化されました - Enhance: プラグインの管理が強化されました
- インストール/アンインストール/設定の変更時にリロード不要になりました - インストール/アンインストール/設定の変更時にリロード不要になりました
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
- Enhance: テーマ設定画面のデザインを改善 - Enhance: テーマ設定画面のデザインを改善
- Enhance: 投稿フォームの設定メニューを改良
- 投稿フォームをリセットできるように
- 文字数カウントを復活
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server ### Server
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正 - Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
- Fix: 連合無しモードでも外部から照会可能だった問題を修正
- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正
## 2025.3.1 ## 2025.3.1

View File

@@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
query?: Record<string, string>; query?: Record<string, string>;
loginRequired?: boolean; loginRequired?: boolean;
hash?: string; hash?: string;
globalCacheKey?: string;
children?: RouteDef[]; children?: RouteDef[];
} }
``` ```

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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
makeActive: "Activar" makeActive: "Activar"
display: "Veure" display: "Veure"
copy: "Copiar" copy: "Copiar"
copiedToClipboard: "Copiat al porta papers"
metrics: "Mètriques" metrics: "Mètriques"
overview: "Visió General" overview: "Visió General"
logs: "Registres" logs: "Registres"
@@ -1139,7 +1140,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d
thisChannelArchived: "Aquest Canal ha sigut arxivat." thisChannelArchived: "Aquest Canal ha sigut arxivat."
displayOfNote: "Mostrar notes" displayOfNote: "Mostrar notes"
initialAccountSetting: "Configuració del perfil" initialAccountSetting: "Configuració del perfil"
youFollowing: "Seguint" youFollowing: "Segueixes "
preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)" preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
options: "Opcions" options: "Opcions"
@@ -1190,7 +1191,7 @@ pastAnnouncements: "Informes passats"
youHaveUnreadAnnouncements: "Tens informes per llegir." youHaveUnreadAnnouncements: "Tens informes per llegir."
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
replies: "Respostes" replies: "Respostes"
renotes: "Impulsar" renotes: "Impulsos"
loadReplies: "Mostrar les respostes" loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació " loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada" pinnedList: "Llista fixada"
@@ -1326,7 +1327,19 @@ restore: "Restaurar "
syncBetweenDevices: "Sincronització entre dispositius" syncBetweenDevices: "Sincronització entre dispositius"
preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu"
preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?"
preferenceSyncConflictChoiceServer: "Valors de configuració del servidor"
preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu "
preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització "
paste: "Pegar"
emojiPalette: "Calaix d'emojis"
postForm: "Formulari de publicació" postForm: "Formulari de publicació"
textCount: "Nombre de caràcters "
information: "Informació"
_emojiPalette:
palettes: "Calaixos d'emojis"
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
paletteForMain: "Calaix d'emojis principal"
paletteForReaction: "Calaix d'emojis per reaccions"
_settings: _settings:
driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius." driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius."
pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se." pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se."
@@ -1344,6 +1357,9 @@ _settings:
preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències." preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències."
appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer." appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer."
soundsBanner: "Configuració dels sons que reproduirà el client." soundsBanner: "Configuració dels sons que reproduirà el client."
timelineAndNote: "Línia de temps i nota"
makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables"
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
_preferencesProfile: _preferencesProfile:
profileName: "Nom del perfil" profileName: "Nom del perfil"
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
@@ -2056,7 +2072,7 @@ _theme:
hashtag: "Etiqueta" hashtag: "Etiqueta"
mention: "Menció" mention: "Menció"
mentionMe: "Mencions (jo)" mentionMe: "Mencions (jo)"
renote: "Renotar" renote: "Impulsar"
modalBg: "Fons del modal" modalBg: "Fons del modal"
divider: "Divisor" divider: "Divisor"
scrollbarHandle: "Maneta de la barra de desplaçament" scrollbarHandle: "Maneta de la barra de desplaçament"
@@ -2498,7 +2514,7 @@ _notification:
follow: "Segueix-me" follow: "Segueix-me"
mention: "Menció" mention: "Menció"
reply: "Respostes" reply: "Respostes"
renote: "Impulsar" renote: "Impulsos"
quote: "Citar" quote: "Citar"
reaction: "Reaccions" reaction: "Reaccions"
pollEnded: "Enquesta terminada" pollEnded: "Enquesta terminada"
@@ -2508,12 +2524,13 @@ _notification:
achievementEarned: "Assoliment desbloquejat" achievementEarned: "Assoliment desbloquejat"
exportCompleted: "Exportació completada" exportCompleted: "Exportació completada"
login: "Iniciar sessió" login: "Iniciar sessió"
createToken: "Creació de tokens d'accés "
test: "Prova la notificació" test: "Prova la notificació"
app: "Notificacions d'aplicacions" app: "Notificacions d'aplicacions"
_actions: _actions:
followBack: "També et segueix" followBack: "També et segueix"
reply: "Respondre" reply: "Respondre"
renote: "Impulsos" renote: "Impulsar"
_deck: _deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal" alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes" columnAlign: "Alinea les columnes"
@@ -2657,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

@@ -9,6 +9,8 @@ reset: "Obnovit"
notifications: "Oznámení" notifications: "Oznámení"
username: "Uživatelské jméno" username: "Uživatelské jméno"
password: "Heslo" password: "Heslo"
initialPasswordForSetup: "Počáteční heslo pro nastavení"
initialPasswordIsIncorrect: "Počáteční heslo pro nastavení je nesprávné"
forgotPassword: "Zapomenuté heslo" forgotPassword: "Zapomenuté heslo"
fetchingAsApObject: "Načítám data z Fediversu..." fetchingAsApObject: "Načítám data z Fediversu..."
ok: "Potvrdit" ok: "Potvrdit"
@@ -169,6 +171,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 +198,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"
@@ -474,6 +480,8 @@ uiLanguage: "Jazyk uživatelského rozhraní"
aboutX: "O {x}" aboutX: "O {x}"
emojiStyle: "Styl emoji" emojiStyle: "Styl emoji"
native: "Výchozí" native: "Výchozí"
style: "Vzhled"
popup: "Vyskakovací okno"
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši" showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
noHistory: "Žádná historie" noHistory: "Žádná historie"
signinHistory: "Historie přihlášení" signinHistory: "Historie přihlášení"
@@ -1099,6 +1107,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

@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen" unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren" copyContent: "Inhalt kopieren"
copyLink: "Link kopieren" copyLink: "Link kopieren"
copyRemoteLink: "Renote-Link kopieren" copyRemoteLink: "Remote-Link kopieren"
copyLinkRenote: "Renote-Link kopieren" copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen" delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten" deleteAndEdit: "Löschen und Bearbeiten"
@@ -1144,7 +1144,7 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich." preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
options: "Optionen" options: "Optionen"
specifyUser: "Spezifischer Benutzer" specifyUser: "Spezifischer Benutzer"
lookupConfirm: "Zustimmen?" lookupConfirm: "Bist du sicher, dass du das nachschlagen möchtest?"
openTagPageConfirm: "Hashtag Seite wirklich öffnen?" openTagPageConfirm: "Hashtag Seite wirklich öffnen?"
specifyHost: "Host" specifyHost: "Host"
failedToPreviewUrl: "Vorschau nicht anzeigbar" failedToPreviewUrl: "Vorschau nicht anzeigbar"
@@ -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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate" makeActive: "Activate"
display: "Display" display: "Display"
copy: "Copy" copy: "Copy"
copiedToClipboard: "Copied to clipboard"
metrics: "Metrics" metrics: "Metrics"
overview: "Overview" overview: "Overview"
logs: "Logs" logs: "Logs"
@@ -1316,14 +1317,29 @@ 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"
noName: "No name" noName: "No name"
skip: "Skip" skip: "Skip"
restore: "Restore" restore: "Restore"
syncBetweenDevices: "Sync between devices"
preferenceSyncConflictTitle: "The configured value exists on the server."
preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?"
preferenceSyncConflictChoiceServer: "Configured value on server"
preferenceSyncConflictChoiceDevice: "Configured value on device"
preferenceSyncConflictChoiceCancel: "Cancel enabling sync"
paste: "Paste"
emojiPalette: "Emoji palette"
postForm: "Posting form" postForm: "Posting form"
textCount: "Character count"
information: "About"
_emojiPalette:
palettes: "Palette"
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
paletteForMain: "Main palette"
paletteForReaction: "Reaction palette"
_settings: _settings:
driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings." driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings."
pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually." pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually."
@@ -1341,6 +1357,9 @@ _settings:
preferencesBanner: "You can configure the overall behavior of the client according to your preferences." preferencesBanner: "You can configure the overall behavior of the client according to your preferences."
appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences." appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences."
soundsBanner: "You can configure the sound settings for playback in the client." soundsBanner: "You can configure the sound settings for playback in the client."
timelineAndNote: "Timeline and note"
makeEveryTextElementsSelectable: "Make all text elements selectable"
makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
_preferencesProfile: _preferencesProfile:
profileName: "Profile name" profileName: "Profile name"
profileNameDescription: "Set a name that identifies this device." profileNameDescription: "Set a name that identifies this device."
@@ -1373,7 +1392,7 @@ _abuseUserReport:
resolve: "Resolve" resolve: "Resolve"
accept: "Accept" accept: "Accept"
reject: "Reject" reject: "Reject"
resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative." resolveTutorial: "If the report's content is legitimate, select \"Accept\" to mark it as resolved.\nIf the report's content is illegitimate, select \"Reject\" to ignore it."
_delivery: _delivery:
status: "Delivery status" status: "Delivery status"
stop: "Suspended" stop: "Suspended"
@@ -2505,6 +2524,7 @@ _notification:
achievementEarned: "Achievement unlocked" achievementEarned: "Achievement unlocked"
exportCompleted: "The export has been completed" exportCompleted: "The export has been completed"
login: "Sign In" login: "Sign In"
createToken: "Create access token"
test: "Notification test" test: "Notification test"
app: "Notifications from linked apps" app: "Notifications from linked apps"
_actions: _actions:
@@ -2532,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "Use simple UI for navigated pages" useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
flexible: "Auto-adjust width" flexible: "Auto-adjust width"
enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices"
_columns: _columns:
main: "Main" main: "Main"
widgets: "Widgets" widgets: "Widgets"
@@ -2577,7 +2598,7 @@ _webhookSettings:
testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data." testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data."
_abuseReport: _abuseReport:
_notificationRecipient: _notificationRecipient:
createRecipient: "Add a recipient for reports" createRecipient: "Add recipient for reports"
modifyRecipient: "Edit a recipient for reports" modifyRecipient: "Edit a recipient for reports"
recipientType: "Notification type" recipientType: "Notification type"
_recipientType: _recipientType:
@@ -2653,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:
@@ -2809,7 +2828,7 @@ _customEmojisManager:
confirmImportEmojisTitle: "Import Emojis" confirmImportEmojisTitle: "Import Emojis"
confirmImportEmojisDescription: "Import {count} Emoji(s) received from the remote server. Please pay close attention to the license of the Emoji. Are you sure to continue?" confirmImportEmojisDescription: "Import {count} Emoji(s) received from the remote server. Please pay close attention to the license of the Emoji. Are you sure to continue?"
_local: _local:
tabTitleList: "List of registered Emojis" tabTitleList: "Registered emojis"
tabTitleRegister: "Emoji registration" tabTitleRegister: "Emoji registration"
_list: _list:
emojisNothing: "There are no registered Emojis." emojisNothing: "There are no registered Emojis."
@@ -2857,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:

32
locales/index.d.ts vendored
View File

@@ -2810,6 +2810,10 @@ export interface Locale extends ILocale {
* コピー * コピー
*/ */
"copy": string; "copy": string;
/**
* クリップボードにコピーされました
*/
"copiedToClipboard": string;
/** /**
* メトリクス * メトリクス
*/ */
@@ -5346,6 +5350,14 @@ export interface Locale extends ILocale {
* 投稿フォーム * 投稿フォーム
*/ */
"postForm": string; "postForm": string;
/**
* 文字数
*/
"textCount": string;
/**
* 情報
*/
"information": string;
"_emojiPalette": { "_emojiPalette": {
/** /**
* パレット * パレット
@@ -5433,6 +5445,14 @@ export interface Locale extends ILocale {
* タイムラインとノート * タイムラインとノート
*/ */
"timelineAndNote": string; "timelineAndNote": string;
/**
* 全てのテキスト要素を選択可能にする
*/
"makeEveryTextElementsSelectable": string;
/**
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
*/
"makeEveryTextElementsSelectable_description": string;
}; };
"_preferencesProfile": { "_preferencesProfile": {
/** /**
@@ -9777,6 +9797,10 @@ export interface Locale extends ILocale {
* ログイン * ログイン
*/ */
"login": string; "login": string;
/**
* アクセストークンの作成
*/
"createToken": string;
/** /**
* 通知のテスト * 通知のテスト
*/ */
@@ -10339,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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva" makeActive: "Attiva"
display: "Visualizza" display: "Visualizza"
copy: "Copia" copy: "Copia"
copiedToClipboard: "Copiato negli appunti"
metrics: "Statistiche" metrics: "Statistiche"
overview: "Anteprima" overview: "Anteprima"
logs: "Log" logs: "Log"
@@ -973,7 +974,7 @@ check: "Verifica"
driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo" driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato." driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore." requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema" isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal sistema."
typeToConfirm: "Digita {x} per continuare" typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo" deleteAccount: "Eliminazione profilo"
document: "Documentazione" document: "Documentazione"
@@ -1323,7 +1324,22 @@ untitled: "Senza titolo"
noName: "Senza nome" noName: "Senza nome"
skip: "Salta" skip: "Salta"
restore: "Ripristina" restore: "Ripristina"
syncBetweenDevices: "Sincronizzazione tra i dispositivi"
preferenceSyncConflictTitle: "Sul server esiste già il valore impostato"
preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?"
preferenceSyncConflictChoiceServer: "Valore del server"
preferenceSyncConflictChoiceDevice: "Valore del dispositivo"
preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione"
paste: "Incolla"
emojiPalette: "Tavolozza emoji"
postForm: "Finestra di pubblicazione" postForm: "Finestra di pubblicazione"
textCount: "Il numero di caratteri"
information: "Informazioni"
_emojiPalette:
palettes: "Tavolozza"
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
paletteForMain: "Tavolozza principale"
paletteForReaction: "Tavolozza per reazioni"
_settings: _settings:
driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file." driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file."
pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente." pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente."
@@ -1341,6 +1357,9 @@ _settings:
preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo." preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo."
appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze." appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze."
soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo." soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo."
timelineAndNote: "Note e Timeline"
makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile"
makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni."
_preferencesProfile: _preferencesProfile:
profileName: "Nome del profilo" profileName: "Nome del profilo"
profileNameDescription: "Impostare il nome che indentifica questo dispositivo." profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
@@ -2505,6 +2524,7 @@ _notification:
achievementEarned: "Risultato raggiunto" achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata" exportCompleted: "Esportazione completata"
login: "Accessi" login: "Accessi"
createToken: "Creare un token di accesso"
test: "Notifiche di test" test: "Notifiche di test"
app: "Notifiche da applicazioni" app: "Notifiche da applicazioni"
_actions: _actions:
@@ -2532,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice" useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima" usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
flexible: "Larghezza flessibile" flexible: "Larghezza flessibile"
enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi"
_columns: _columns:
main: "Principale" main: "Principale"
widgets: "Riquadri" widgets: "Riquadri"
@@ -2653,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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
makeActive: "アクティブにする" makeActive: "アクティブにする"
display: "表示" display: "表示"
copy: "コピー" copy: "コピー"
copiedToClipboard: "クリップボードにコピーされました"
metrics: "メトリクス" metrics: "メトリクス"
overview: "概要" overview: "概要"
logs: "ログ" logs: "ログ"
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
paste: "ペースト" paste: "ペースト"
emojiPalette: "絵文字パレット" emojiPalette: "絵文字パレット"
postForm: "投稿フォーム" postForm: "投稿フォーム"
textCount: "文字数"
information: "情報"
_emojiPalette: _emojiPalette:
palettes: "パレット" palettes: "パレット"
@@ -1357,6 +1360,8 @@ _settings:
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
soundsBanner: "クライアントで再生するサウンドの設定が行えます。" soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
timelineAndNote: "タイムラインとノート" timelineAndNote: "タイムラインとノート"
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
_preferencesProfile: _preferencesProfile:
profileName: "プロファイル名" profileName: "プロファイル名"
@@ -2584,6 +2589,7 @@ _notification:
achievementEarned: "実績の獲得" achievementEarned: "実績の獲得"
exportCompleted: "エクスポートが完了した" exportCompleted: "エクスポートが完了した"
login: "ログイン" login: "ログイン"
createToken: "アクセストークンの作成"
test: "通知のテスト" test: "通知のテスト"
app: "連携アプリからの通知" app: "連携アプリからの通知"
@@ -2744,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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}님이 \"{word}\"를 언급했습니다."
makeActive: "활성화" makeActive: "활성화"
display: "보기" display: "보기"
copy: "복사" copy: "복사"
copiedToClipboard: "클립보드에 복사되었습니다."
metrics: "통계" metrics: "통계"
overview: "요약" overview: "요약"
logs: "로그" logs: "로그"
@@ -1294,7 +1295,7 @@ thereAreNChanges: "{n}건 변경이 있습니다."
signinWithPasskey: "패스키로 로그인" signinWithPasskey: "패스키로 로그인"
unknownWebAuthnKey: "등록되지 않은 패스키입니다." unknownWebAuthnKey: "등록되지 않은 패스키입니다."
passkeyVerificationFailed: "패스키 검증을 실패했습니다." passkeyVerificationFailed: "패스키 검증을 실패했습니다."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다." passkeyVerificationSucceededButPasswordlessLoginDisabled: "입력된 패스키는 정상적이나, 비밀번호 없이 로그인 하는 기능이 비활성화 되어있습니다."
messageToFollower: "팔로워에게 보낼 메시지" messageToFollower: "팔로워에게 보낼 메시지"
target: "대상" target: "대상"
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>" testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
@@ -1325,20 +1326,40 @@ skip: "건너뛰기"
restore: "복원" restore: "복원"
syncBetweenDevices: "장치간 동기화" syncBetweenDevices: "장치간 동기화"
preferenceSyncConflictTitle: "서버에 설정값이 존재합니다." preferenceSyncConflictTitle: "서버에 설정값이 존재합니다."
preferenceSyncConflictText: "동기화를 활성화 한 항목의 설정 값은 서버에 저장되지만, 해당 항목은 이미 서버에 설정 값이 저장되어져 있습니다. 어느 쪽의 설정 값을 덮어씌울까요?"
preferenceSyncConflictChoiceServer: "서버 설정값" preferenceSyncConflictChoiceServer: "서버 설정값"
preferenceSyncConflictChoiceDevice: "장치 설정값" preferenceSyncConflictChoiceDevice: "장치 설정값"
preferenceSyncConflictChoiceCancel: "동기화 취소"
paste: "붙여넣기" paste: "붙여넣기"
emojiPalette: "이모지 팔레트" emojiPalette: "이모지 팔레트"
postForm: "글 입력란" postForm: "글 입력란"
textCount: "문자 수"
information: "정보"
_emojiPalette: _emojiPalette:
palettes: "팔레트" palettes: "팔레트"
enableSyncBetweenDevicesForPalettes: "팔레트의 디바이스 간 동기화를 활성화"
paletteForMain: "메인으로 사용할 팔레트" paletteForMain: "메인으로 사용할 팔레트"
paletteForReaction: "리액션으로 사용할 팔레트" paletteForReaction: "리액션으로 사용할 팔레트"
_settings: _settings:
driveBanner: "드라이브 관리, 사용량 확인, 파일 업로드에 관한 설정을 합니다."
pluginBanner: "플러그인을 사용하면 클라이언트 기능을 확장할 수 있습니다. 플러그인 설치와 개별적인 설정을 합니다."
notificationsBanner: "서버에서 받는 알림의 종류 및 범위, 푸시 알림 설정을 합니다."
api: "API" api: "API"
webhook: "Webhook" webhook: "Webhook"
serviceConnection: "서비스 연동" serviceConnection: "서비스 연동"
serviceConnectionBanner: "외부 앱, 서비스와 연결하기 위한 액세스 토큰과 웹 훅 관리 설정을 합니다."
accountData: "계정 데이터" accountData: "계정 데이터"
accountDataBanner: "계정 데이터의 아카이브를 추출하기/가져오기 하여 관리할 수 있습니다."
muteAndBlockBanner: "숨길 컨텐츠의 설정과, 특정 유저의 리액션을 제한하는 설정을 관리합니다."
accessibilityBanner: "좀 더 쾌적하게 사용할 수 있도록 클라이언트의 시각 및 움직임에 관한 개인화 설정을 합니다."
privacyBanner: "컨텐츠, 계정의 발견 범위, 팔로우 승인제 등의 계정의 프라이버시에 관한 설정을 합니다."
securityBanner: "비밀번호, 로그인 방법, OTP, 패스 키 등의 계정의 보안에 관련된 설정을 합니다."
preferencesBanner: "취향에 알맞는 클라이언트의 전체적인 동작을 설정합니다."
appearanceBanner: "취향에 알맞는 클라이언트의 디스플레이, 표시 방법에 관한 설정을 합니다."
soundsBanner: "클라이언트에서 재생할 소리에 대한 설정을 합니다."
timelineAndNote: "타임라인과 노트"
makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함"
makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 사용자의 접근성이 나빠질 수도 있습니다."
_preferencesProfile: _preferencesProfile:
profileName: "프로필 이름" profileName: "프로필 이름"
profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요." profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
@@ -1362,6 +1383,7 @@ _accountSettings:
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기" makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다." makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다." mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다."
mayNotEffectSomeSituations: "여기서 설정하는 제한은 모더레이션이나 리모트 서버에서 볼 때 등 일부 환경에서는 적용되지 않을 수도 있습니다."
notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트" notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트"
notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트" notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트"
_abuseUserReport: _abuseUserReport:
@@ -2502,6 +2524,7 @@ _notification:
achievementEarned: "도전 과제 획득" achievementEarned: "도전 과제 획득"
exportCompleted: "추출을 성공함" exportCompleted: "추출을 성공함"
login: "로그인" login: "로그인"
createToken: "액세스 토큰 만들기"
test: "알림 테스트" test: "알림 테스트"
app: "연동된 앱을 통한 알림" app: "연동된 앱을 통한 알림"
_actions: _actions:
@@ -2529,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기" useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다" usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다"
flexible: "폭 자동 조정" flexible: "폭 자동 조정"
enableSyncBetweenDevicesForProfiles: "프로파일 정보의 디바이스 간 동기화를 활성화"
_columns: _columns:
main: "메인" main: "메인"
widgets: "위젯" widgets: "위젯"
@@ -2650,10 +2674,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

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
makeActive: "启用" makeActive: "启用"
display: "显示" display: "显示"
copy: "复制" copy: "复制"
copiedToClipboard: "已复制到剪贴板"
metrics: "指标" metrics: "指标"
overview: "概览" overview: "概览"
logs: "日志" logs: "日志"
@@ -746,7 +747,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
public: "公开" public: "公开"
private: "私密" private: "私密"
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
manageAccessTokens: "管理 Access Tokens" manageAccessTokens: "管理访问令牌"
accountInfo: "账户信息" accountInfo: "账户信息"
notesCount: "帖子数量" notesCount: "帖子数量"
repliesCount: "回复数量" repliesCount: "回复数量"
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消同步"
paste: "粘贴" paste: "粘贴"
emojiPalette: "表情符号调色板" emojiPalette: "表情符号调色板"
postForm: "投稿窗口" postForm: "投稿窗口"
textCount: "字数"
information: "关于"
_emojiPalette: _emojiPalette:
palettes: "调色板" palettes: "调色板"
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步" enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
@@ -1339,13 +1342,24 @@ _emojiPalette:
paletteForReaction: "回应用调色板" paletteForReaction: "回应用调色板"
_settings: _settings:
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。" driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
api: "API" api: "API"
webhook: "Webhook" webhook: "Webhook"
serviceConnection: "连接服务"
serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
accountData: "账户数据"
accountDataBanner: "可在此导入或导出帐户数据的存档。"
muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。" privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。" securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
preferencesBanner: "可在此设置客户端的整体运作行为。" preferencesBanner: "可在此设置客户端的整体运作行为。"
appearanceBanner: "可在此设置客户端的外观及显示方式。" appearanceBanner: "可在此设置客户端的外观及显示方式。"
soundsBanner: "可在此设置客户端播放的声音。" soundsBanner: "可在此设置客户端播放的声音。"
timelineAndNote: "时间线和帖子"
makeEveryTextElementsSelectable: "使所有的文字均可选择"
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
_preferencesProfile: _preferencesProfile:
profileName: "配置名" profileName: "配置名"
profileNameDescription: "请指定用于识别此设备的名称" profileNameDescription: "请指定用于识别此设备的名称"
@@ -2510,6 +2524,7 @@ _notification:
achievementEarned: "取得的成就" achievementEarned: "取得的成就"
exportCompleted: "已完成导出" exportCompleted: "已完成导出"
login: "登录" login: "登录"
createToken: "创建访问令牌"
test: "测试通知" test: "测试通知"
app: "关联应用的通知" app: "关联应用的通知"
_actions: _actions:
@@ -2659,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "请在安装前确保来源可靠" checkVendorBeforeInstall: "请在安装前确保来源可靠"
_plugin: _plugin:
title: "要安装此插件吗?" title: "要安装此插件吗?"
metaTitle: "插件信息"
_theme: _theme:
title: "要安装此主题吗?" title: "要安装此主题吗?"
metaTitle: "主题信息"
_meta: _meta:
base: "基本配色方案" base: "基本配色方案"
_vendorInfo: _vendorInfo:

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話"
makeActive: "啟用" makeActive: "啟用"
display: "檢視" display: "檢視"
copy: "複製" copy: "複製"
copiedToClipboard: "已複製到剪貼簿"
metrics: "指標" metrics: "指標"
overview: "概覽" overview: "概覽"
logs: "日誌" logs: "日誌"
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消啟用同步"
paste: "貼上" paste: "貼上"
emojiPalette: "表情符號調色盤" emojiPalette: "表情符號調色盤"
postForm: "發文視窗" postForm: "發文視窗"
textCount: "字數"
information: "關於"
_emojiPalette: _emojiPalette:
palettes: "調色盤" palettes: "調色盤"
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化" enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
@@ -1355,6 +1358,8 @@ _settings:
appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。" appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。"
soundsBanner: "您可以調整用戶端播放的聲音設定。" soundsBanner: "您可以調整用戶端播放的聲音設定。"
timelineAndNote: "時間軸及貼文" timelineAndNote: "時間軸及貼文"
makeEveryTextElementsSelectable: "允許選取所有文字"
makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
_preferencesProfile: _preferencesProfile:
profileName: "設定檔案名稱" profileName: "設定檔案名稱"
profileNameDescription: "設定一個名稱來識別此裝置。" profileNameDescription: "設定一個名稱來識別此裝置。"
@@ -2519,6 +2524,7 @@ _notification:
achievementEarned: "獲得成就" achievementEarned: "獲得成就"
exportCompleted: "已完成匯出。" exportCompleted: "已完成匯出。"
login: "登入" login: "登入"
createToken: "建立存取權杖"
test: "通知測試" test: "通知測試"
app: "應用程式通知" app: "應用程式通知"
_actions: _actions:
@@ -2668,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.1", "version": "2025.3.2-beta.6",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -69,9 +69,6 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.749.0", "@aws-sdk/client-s3": "3.749.0",
"@aws-sdk/lib-storage": "3.749.0", "@aws-sdk/lib-storage": "3.749.0",
"@bull-board/api": "6.7.7",
"@bull-board/fastify": "6.7.7",
"@bull-board/ui": "6.7.7",
"@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",

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

@@ -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

@@ -6,6 +6,7 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ZipReader } from 'slacc'; import { ZipReader } from 'slacc';
import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js'; import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
@@ -86,6 +87,7 @@ export class ImportCustomEmojisProcessorService {
const emojiPath = outputPath + '/' + record.fileName; const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({ await this.emojisRepository.delete({
name: emojiInfo.name, name: emojiInfo.name,
host: IsNull(),
}); });
try { try {

View File

@@ -13,7 +13,7 @@ import accepts from 'accepts';
import vary from 'vary'; import vary from 'vary';
import secureJson from 'secure-json-parse'; import secureJson from 'secure-json-parse';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
import * as url from '@/misc/prelude/url.js'; import * as url from '@/misc/prelude/url.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -42,6 +42,9 @@ export class ActivityPubServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@@ -102,6 +105,11 @@ export class ActivityPubServerService {
@bindThis @bindThis
private inbox(request: FastifyRequest, reply: FastifyReply) { private inbox(request: FastifyRequest, reply: FastifyReply) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
let signature; let signature;
try { try {
@@ -173,6 +181,11 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const cursor = request.query.cursor; const cursor = request.query.cursor;
@@ -265,6 +278,11 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const cursor = request.query.cursor; const cursor = request.query.cursor;
@@ -354,6 +372,11 @@ export class ActivityPubServerService {
@bindThis @bindThis
private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -398,6 +421,11 @@ export class ActivityPubServerService {
}>, }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const sinceId = request.query.since_id; const sinceId = request.query.since_id;
@@ -482,6 +510,11 @@ export class ActivityPubServerService {
@bindThis @bindThis
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) { private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
if (user == null) { if (user == null) {
reply.code(404); reply.code(404);
return; return;
@@ -564,6 +597,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const note = await this.notesRepository.findOneBy({ const note = await this.notesRepository.findOneBy({
id: request.params.note, id: request.params.note,
visibility: In(['public', 'home']), visibility: In(['public', 'home']),
@@ -594,6 +632,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => { fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const note = await this.notesRepository.findOneBy({ const note = await this.notesRepository.findOneBy({
id: request.params.note, id: request.params.note,
userHost: IsNull(), userHost: IsNull(),
@@ -634,6 +677,11 @@ export class ActivityPubServerService {
// publickey // publickey
fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => { fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -661,6 +709,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -674,6 +727,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const acct = Acct.parse(request.params.acct); const acct = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -688,6 +746,11 @@ export class ActivityPubServerService {
// emoji // emoji
fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => { fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const emoji = await this.emojisRepository.findOneBy({ const emoji = await this.emojisRepository.findOneBy({
host: IsNull(), host: IsNull(),
name: request.params.emoji, name: request.params.emoji,
@@ -705,6 +768,11 @@ export class ActivityPubServerService {
// like // like
fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => { fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like }); const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like });
if (reaction == null) { if (reaction == null) {
@@ -726,6 +794,11 @@ export class ActivityPubServerService {
// follow // follow
fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => { fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
// This may be used before the follow is completed, so we do not // This may be used before the follow is completed, so we do not
// check if the following exists. // check if the following exists.
@@ -752,6 +825,11 @@ export class ActivityPubServerService {
// follow // follow
fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => { fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
// This may be used before the follow is completed, so we do not // This may be used before the follow is completed, so we do not
// check if the following exists and only check if the follow request exists. // check if the following exists and only check if the follow request exists.

View File

@@ -8,7 +8,7 @@ import { IsNull } from 'typeorm';
import vary from 'vary'; import vary from 'vary';
import fastifyAccepts from '@fastify/accepts'; import fastifyAccepts from '@fastify/accepts';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/_.js'; import type { MiMeta, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
@@ -26,6 +26,9 @@ export class WellKnownServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@@ -66,6 +69,11 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/host-meta', async (request, reply) => { fastify.get('/.well-known/host-meta', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
reply.header('Content-Type', xrd); reply.header('Content-Type', xrd);
return XRD({ element: 'Link', attributes: { return XRD({ element: 'Link', attributes: {
rel: 'lrdd', rel: 'lrdd',
@@ -75,6 +83,11 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/host-meta.json', async (request, reply) => { fastify.get('/.well-known/host-meta.json', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
reply.header('Content-Type', 'application/json'); reply.header('Content-Type', 'application/json');
return { return {
links: [{ links: [{
@@ -86,6 +99,11 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/nodeinfo', async (request, reply) => { fastify.get('/.well-known/nodeinfo', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
return { links: this.nodeinfoServerService.getLinks() }; return { links: this.nodeinfoServerService.getLinks() };
}); });
@@ -99,6 +117,11 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
*/ */
fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => { fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({ const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({
id, id,
host: IsNull(), host: IsNull(),

View File

@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import cors from '@fastify/cors'; import cors from '@fastify/cors';
import multipart from '@fastify/multipart'; import multipart from '@fastify/multipart';
import fastifyCookie from '@fastify/cookie';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@@ -57,8 +56,6 @@ export class ApiServerService {
}, },
}); });
fastify.register(fastifyCookie, {});
// Prevent cache // Prevent cache
fastify.addHook('onRequest', (request, reply, done) => { fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); reply.header('Cache-Control', 'private, max-age=0, must-revalidate');

View File

@@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
import ms from 'ms'; import ms from 'ms';
import sharp from 'sharp'; import sharp from 'sharp';
import pug from 'pug'; import pug from 'pug';
import { In, IsNull } from 'typeorm'; import { In, IsNull } from 'typeorm';
import fastifyStatic from '@fastify/static'; import fastifyStatic from '@fastify/static';
import fastifyView from '@fastify/view'; import fastifyView from '@fastify/view';
import fastifyCookie from '@fastify/cookie';
import fastifyProxy from '@fastify/http-proxy'; import fastifyProxy from '@fastify/http-proxy';
import vary from 'vary'; import vary from 'vary';
import htmlSafeJsonStringify from 'htmlescape'; import htmlSafeJsonStringify from 'htmlescape';
@@ -221,64 +217,6 @@ export class ClientServerService {
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.register(fastifyCookie, {});
//#region Bull Dashboard
const bullBoardPath = '/queue';
// Authenticate
fastify.addHook('onRequest', async (request, reply) => {
if (request.routeOptions.url == null) {
reply.code(404).send('Not found');
return;
}
// %71ueueとかでリクエストされたら困るため
const url = decodeURI(request.routeOptions.url);
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
if (!url.startsWith(bullBoardPath + '/static/')) {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
}
const token = request.cookies.token;
if (token == null) {
reply.code(401).send('Login required');
return;
}
const user = await this.usersRepository.findOneBy({ token });
if (user == null) {
reply.code(403).send('No such user');
return;
}
const isAdministrator = await this.roleService.isAdministrator(user);
if (!isAdministrator) {
reply.code(403).send('Access denied');
return;
}
}
});
const bullBoardServerAdapter = new BullBoardFastifyAdapter();
createBullBoard({
queues: [
this.systemQueue,
this.endedPollNotificationQueue,
this.deliverQueue,
this.inboxQueue,
this.dbQueue,
this.relationshipQueue,
this.objectStorageQueue,
this.userWebhookDeliverQueue,
this.systemWebhookDeliverQueue,
].map(q => new BullMQAdapter(q)),
serverAdapter: bullBoardServerAdapter,
});
bullBoardServerAdapter.setBasePath(bullBoardPath);
(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
//#endregion
fastify.register(fastifyView, { fastify.register(fastifyView, {
root: _dirname + '/views', root: _dirname + '/views',
engine: { engine: {

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
import * as assert from 'assert'; import * as assert from 'assert';
import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
import type { SimpleGetResponse } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
@@ -156,20 +156,20 @@ describe('Webリソース', () => {
describe(' has entry such ', () => { describe(' has entry such ', () => {
beforeEach(() => { beforeEach(() => {
post(alice, { text: "**a**" }) post(alice, { text: '**a**' });
}); });
test('MFMを含まない。', async () => { test('MFMを含まない。', async () => {
const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text()); const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text());
const _body: unknown = content.body; const _body: unknown = content.body;
// JSONフィードのときは改めて文字列化する // JSONフィードのときは改めて文字列化する
const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string; const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string;
if (body.includes("**a**")) { if (body.includes('**a**')) {
throw new Error("MFM shouldn't be included"); throw new Error('MFM shouldn\'t be included');
} }
}); });
}) });
}); });
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
@@ -180,24 +180,6 @@ describe('Webリソース', () => {
})); }));
}); });
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
test('はログインしないとGETできない。', async () => await notOk({
path,
status: 401,
}));
test('はadminでなければGETできない。', async () => await notOk({
path,
cookie: cookie(bob),
status: 403,
}));
test('はadminならGETできる。', async () => await ok({
path,
cookie: cookie(alice),
}));
});
describe.each([{ path: '/streaming' }])('$path', ({ path }) => { describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
test('はGETできない。', async () => await notOk({ test('はGETできない。', async () => await notOk({
path, path,

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

@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
createdAt: string; createdAt: string;
type: string; type: string;
body: any; body: any;
} };
const config = loadConfig(); const config = loadConfig();
export const port = config.port; export const port = config.port;
@@ -45,10 +45,6 @@ export const host = new URL(config.url).host;
export const WEBHOOK_HOST = 'http://localhost:15080'; export const WEBHOOK_HOST = 'http://localhost:15080';
export const WEBHOOK_PORT = 15080; export const WEBHOOK_PORT = 15080;
export const cookie = (me: UserToken): string => {
return `token=${me.token};`;
};
export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = { export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
endpoint: E, endpoint: E,
parameters: P, parameters: P,

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

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div> <div>
<div class="_fullinfo"> <div class="_fullinfo">
<img :src="notFoundImageUrl" class="_ghost"/> <img :src="notFoundImageUrl" draggable="false"/>
<div>{{ i18n.ts.notFoundDescription }}</div> <div>{{ i18n.ts.notFoundDescription }}</div>
</div> </div>
</div> </div>
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
const serverMetadata = inject(DI.serverMetadata)!; const serverMetadata = inject(DI.serverMetadata)!;
const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); const notFoundImageUrl = computed(() => serverMetadata.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
</script> </script>

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

@@ -64,12 +64,12 @@ initialize({
initLocalStorage(); initLocalStorage();
queueMicrotask(() => { queueMicrotask(() => {
Promise.all([ Promise.all([
import('../src/components'), import('../src/components/index.js'),
import('../src/directives'), import('../src/directives/index.js'),
import('../src/widgets'), import('../src/widgets/index.js'),
import('../src/theme'), import('../src/theme.js'),
import('../src/preferences'), import('../src/preferences.js'),
import('../src/os'), import('../src/os.js'),
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => { ]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
setup((app) => { setup((app) => {
moduleInitialized = true; moduleInitialized = true;

View File

@@ -50,9 +50,36 @@ 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 紛らわしい
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location'],
'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': 'history',
'message': 'Use `window.history`.',
},
],
'no-shadow': ['warn'], 'no-shadow': ['warn'],
'vue/attributes-order': ['error', { 'vue/attributes-order': ['error', {
alphabetical: false, alphabetical: false,

View File

@@ -12,7 +12,7 @@ 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 => location.pathname === i || location.pathname.startsWith(i + '/'))) {
subBoot(); subBoot();

View File

@@ -26,8 +26,6 @@ import { deckStore } from '@/ui/deck/deck-store.js';
import { analytics, initAnalytics } from '@/analytics.js'; import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js'; import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/main.js';
import { createMainRouter } from '@/router/definition.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
@@ -234,6 +232,10 @@ export async function common(createVue: () => App<Element>) {
}); });
} }
if (prefer.s.makeEveryTextElementsSelectable) {
document.documentElement.classList.add('forceSelectableAll');
}
//#region Fetch user //#region Fetch user
if ($i && $i.token) { if ($i && $i.token) {
if (_DEV_) { if (_DEV_) {
@@ -263,8 +265,6 @@ export async function common(createVue: () => App<Element>) {
const app = createVue(); const app = createVue();
setupRouter(app, createMainRouter);
if (_DEV_) { if (_DEV_) {
app.config.performance = true; app.config.performance = true;
} }
@@ -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 {

View File

@@ -24,7 +24,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js'; import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
import { initializeSw } from '@/utility/initialize-sw.js'; import { initializeSw } from '@/utility/initialize-sw.js';
import { emojiPicker } from '@/utility/emoji-picker.js'; import { emojiPicker } from '@/utility/emoji-picker.js';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router.js';
import { makeHotkey } from '@/utility/hotkey.js'; import { makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
@@ -201,6 +201,8 @@ export async function mainBoot() {
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
prefer.commit('sound.on.notification', store.s.sound_notification as any); prefer.commit('sound.on.notification', store.s.sound_notification as any);
prefer.commit('sound.on.reaction', store.s.sound_reaction as any); prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
window.setTimeout(() => { window.setTimeout(() => {
unisonReload(); unisonReload();

View File

@@ -88,9 +88,9 @@ import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js'; import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import RouterView from '@/components/global/RouterView.vue'; import RouterView from '@/components/global/RouterView.vue';
import { useRouterFactory } from '@/router/supplier';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { createRouter } from '@/router.js';
const props = defineProps<{ const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number]; report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@@ -100,10 +100,9 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void; (ev: 'resolved', reportId: string): void;
}>(); }>();
const routerFactory = useRouterFactory(); const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
targetRouter.init(); targetRouter.init();
const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`); const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
reporterRouter.init(); reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? ''); const moderationNote = ref(props.report.moderationNote ?? '');
@@ -135,7 +134,7 @@ function forward() {
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
os.popupMenu([{ os.popupMenu([{
icon: 'ti ti-id', icon: 'ti ti-hash',
text: 'Copy ID', text: 'Copy ID',
action: () => { action: () => {
copyToClipboard(props.report.id); copyToClipboard(props.report.id);

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, shallowRef } from 'vue'; import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@@ -47,7 +47,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const uiWindow = shallowRef<InstanceType<typeof MkWindow>>(); const uiWindow = useTemplateRef('uiWindow');
const comment = ref(props.initialComment ?? ''); const comment = ref(props.initialComment ?? '');
function send() { function send() {

View File

@@ -4,14 +4,14 @@ 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>
import { onMounted, onUnmounted, shallowRef } from 'vue'; import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import isChromatic from 'chromatic/isChromatic'; import isChromatic from 'chromatic/isChromatic';
const canvasEl = shallowRef<HTMLCanvasElement>(); const canvasEl = useTemplateRef('canvasEl');
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
scale?: number; scale?: number;

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'; import { onMounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@@ -37,8 +37,8 @@ const props = withDefaults(defineProps<{
}>(), { }>(), {
}); });
const rootEl = shallowRef<HTMLDivElement>(); const rootEl = useTemplateRef('rootEl');
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = useTemplateRef('modal');
async function ok() { async function ok() {
if (props.announcement.needConfirmationToRead) { if (props.announcement.needConfirmationToRead) {

View File

@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from 'vue'; import { useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import XAntennaEditor from '@/components/MkAntennaEditor.vue'; import XAntennaEditor from '@/components/MkAntennaEditor.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
(ev: 'closed'): void, (ev: 'closed'): void,
}>(); }>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialog = useTemplateRef('dialog');
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) { function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna); emit('created', newAntenna);

View File

@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts"> <script lang="ts">
import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import { emojilist, getEmojiName } from '@@/js/emojilist.js'; import { emojilist, getEmojiName } from '@@/js/emojilist.js';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
@@ -139,7 +139,7 @@ const emit = defineEmits<{
}>(); }>();
const suggests = ref<Element>(); const suggests = ref<Element>();
const rootEl = shallowRef<HTMLDivElement>(); const rootEl = useTemplateRef('rootEl');
const fetching = ref(true); const fetching = ref(true);
const users = ref<any[]>([]); const users = ref<any[]>([]);

View File

@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, shallowRef } from 'vue'; import { nextTick, onMounted, useTemplateRef } from 'vue';
const props = defineProps<{ const props = defineProps<{
type?: 'button' | 'submit' | 'reset'; type?: 'button' | 'submit' | 'reset';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
(ev: 'click', payload: MouseEvent): void; (ev: 'click', payload: MouseEvent): void;
}>(); }>();
const el = shallowRef<HTMLElement | null>(null); const el = useTemplateRef('el');
const ripples = shallowRef<HTMLElement | null>(null); const ripples = useTemplateRef('ripples');
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {

View File

@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue'; import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { store } from '@/store.js'; import { store } from '@/store.js';
// APIs provided by Captcha services // APIs provided by Captcha services
@@ -69,7 +69,7 @@ const emit = defineEmits<{
const available = ref(false); const available = ref(false);
const captchaEl = shallowRef<HTMLDivElement | undefined>(); const captchaEl = useTemplateRef('captchaEl');
const captchaWidgetId = ref<string | undefined>(undefined); const captchaWidgetId = ref<string | undefined>(undefined);
const testcaptchaInput = ref(''); const testcaptchaInput = ref('');
const testcaptchaPassed = ref(false); const testcaptchaPassed = ref(false);

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination :pagination="pagination"> <MkPagination :pagination="pagination">
<template #empty> <template #empty>
<div class="_fullinfo"> <div class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/> <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.notFound }}</div> <div>{{ i18n.ts.notFound }}</div>
</div> </div>
</template> </template>
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Paging } from '@/components/MkPagination.vue';
import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination from '@/components/MkPagination.vue';
import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js'; import { infoImageUrl } from '@/instance.js';

View File

@@ -45,12 +45,8 @@ export type ChartSrc =
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
/* eslint-disable id-denylist --
Chart.js has a `data` attribute in most chart definitions, which triggers the import { onMounted, ref, useTemplateRef, watch } from 'vue';
id-denylist violation when setting it. This is causing about 60+ lint issues.
As this is part of Chart.js's API it makes sense to disable the check here.
*/
import { onMounted, ref, shallowRef, watch } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { misskeyApiGet } from '@/utility/misskey-api.js'; import { misskeyApiGet } from '@/utility/misskey-api.js';
@@ -96,7 +92,7 @@ const props = withDefaults(defineProps<{
nowForChromatic: undefined, nowForChromatic: undefined,
}); });
const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>(); const legendEl = useTemplateRef('legendEl');
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x); const negate = arr => arr.map(x => -x);
@@ -134,7 +130,7 @@ let chartData: {
bytes?: boolean; bytes?: boolean;
} | null = null; } | null = null;
const chartEl = shallowRef<HTMLCanvasElement | null>(null); const chartEl = useTemplateRef('chartEl');
const fetching = ref(true); const fetching = ref(true);
const getDate = (ago: number) => { const getDate = (ago: number) => {
@@ -849,7 +845,7 @@ watch(() => [props.src, props.span], fetchAndRender);
onMounted(() => { onMounted(() => {
fetchAndRender(); fetchAndRender();
}); });
/* eslint-enable id-denylist */
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@@ -48,7 +48,6 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
function copy() { function copy() {
copyToClipboard(props.code); copyToClipboard(props.code);
os.success();
} }
</script> </script>

View File

@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue'; import { ref, watch, toRefs, useTemplateRef, nextTick } from 'vue';
import { debounce } from 'throttle-debounce'; import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@@ -61,7 +61,7 @@ const { modelValue } = toRefs(props);
const v = ref<string>(modelValue.value ?? ''); const v = ref<string>(modelValue.value ?? '');
const focused = ref(false); const focused = ref(false);
const changed = ref(false); const changed = ref(false);
const inputEl = shallowRef<HTMLTextAreaElement>(); const inputEl = useTemplateRef('inputEl');
const focus = () => inputEl.value?.focus(); const focus = () => inputEl.value?.focus();

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, toRefs } from 'vue'; import { ref, useTemplateRef, toRefs } from 'vue';
const props = defineProps<{ const props = defineProps<{
modelValue: string | null; modelValue: string | null;
@@ -39,7 +39,7 @@ const emit = defineEmits<{
const { modelValue } = toRefs(props); const { modelValue } = toRefs(props);
const v = ref(modelValue.value); const v = ref(modelValue.value);
const inputEl = shallowRef<HTMLElement>(); const inputEl = useTemplateRef('inputEl');
const onInput = () => { const onInput = () => {
emit('update:modelValue', v.value ?? ''); emit('update:modelValue', v.value ?? '');

View File

@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@@ -58,9 +58,9 @@ const props = withDefaults(defineProps<{
maxHeight: null, maxHeight: null,
}); });
const rootEl = shallowRef<HTMLElement>(); const rootEl = useTemplateRef('rootEl');
const contentEl = shallowRef<HTMLElement>(); const contentEl = useTemplateRef('contentEl');
const headerEl = shallowRef<HTMLElement>(); const headerEl = useTemplateRef('headerEl');
const showBody = ref(props.expanded); const showBody = ref(props.expanded);
const ignoreOmit = ref(false); const ignoreOmit = ref(false);
const omitted = ref(false); const omitted = ref(false);

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue'; import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import contains from '@/utility/contains.js'; import contains from '@/utility/contains.js';
@@ -34,7 +34,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const rootEl = shallowRef<HTMLDivElement>(); const rootEl = useTemplateRef('rootEl');
const zIndex = ref<number>(os.claimZIndex('high')); const zIndex = ref<number>(os.claimZIndex('high'));

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue'; import { onMounted, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
@@ -56,8 +56,8 @@ const props = defineProps<{
}>(); }>();
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true); const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialogEl = useTemplateRef('dialogEl');
const imgEl = shallowRef<HTMLImageElement>(); const imgEl = useTemplateRef('imgEl');
let cropper: Cropper | null = null; let cropper: Cropper | null = null;
const loading = ref(true); const loading = ref(true);

View File

@@ -57,14 +57,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue'; import { useTemplateRef } from 'vue';
import MkLink from '@/components/MkLink.vue'; import MkLink from '@/components/MkLink.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
const props = defineProps<{ const props = defineProps<{
emoji: Misskey.entities.EmojiDetailed, emoji: Misskey.entities.EmojiDetailed,
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@@ -73,7 +73,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialogEl = useTemplateRef('dialogEl');
function cancel() { function cancel() {
emit('cancel'); emit('cancel');

View File

@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i> <i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> <MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
</div> </div>
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header> <header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div> <div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div>
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> <template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption> <template #caption>
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue'; import { ref, useTemplateRef, computed } from 'vue';
import MkModal from '@/components/MkModal.vue'; import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
@@ -117,7 +117,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = useTemplateRef('modal');
const inputValue = ref<string | number | null>(props.input?.default ?? null); const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null); const selectedValue = ref(props.select?.default ?? null);

View File

@@ -47,7 +47,7 @@ import { i18n } from '@/i18n.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js'; import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
import { deviceKind } from '@/utility/device-kind.js'; import { deviceKind } from '@/utility/device-kind.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router.js';
const router = useRouter(); const router = useRouter();

View File

@@ -297,7 +297,7 @@ function onContextmenu(ev: MouseEvent) {
}]; }];
if (prefer.s.devMode) { if (prefer.s.devMode) {
menu = menu.concat([{ type: 'divider' }, { menu = menu.concat([{ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-hash',
text: i18n.ts.copyFolderId, text: i18n.ts.copyFolderId,
action: () => { action: () => {
copyToClipboard(props.folder.id); copyToClipboard(props.folder.id);

View File

@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
@@ -129,8 +129,8 @@ const emit = defineEmits<{
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void; (ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
}>(); }>();
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>(); const loadMoreFiles = useTemplateRef('loadMoreFiles');
const fileInput = shallowRef<HTMLInputElement>(); const fileInput = useTemplateRef('fileInput');
const folder = ref<Misskey.entities.DriveFolder | null>(null); const folder = ref<Misskey.entities.DriveFolder | null>(null);
const files = ref<Misskey.entities.DriveFile[]>([]); const files = ref<Misskey.entities.DriveFile[]>([]);

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef } from 'vue'; import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue'; import XDrive from '@/components/MkDrive.vue';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -43,7 +43,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialog = useTemplateRef('dialog');
const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]); const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);

View File

@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue'; import { useTemplateRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
import { url } from '@@/js/config.js'; import { url } from '@@/js/config.js';
import { embedRouteWithScrollbar } from '@@/js/embed-page.js'; import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js'; import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
@@ -121,7 +121,7 @@ const props = defineProps<{
}>(); }>();
//#region Modalの制御 //#region Modalの制御
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialogEl = useTemplateRef('dialogEl');
function cancel() { function cancel() {
emit('cancel'); emit('cancel');
@@ -194,14 +194,13 @@ function generate() {
function doCopy() { function doCopy() {
copyToClipboard(result.value); copyToClipboard(result.value);
os.success();
} }
//#endregion //#endregion
//#region プレビューのリサイズ //#region プレビューのリサイズ
const resizerRootEl = shallowRef<HTMLDivElement>(); const resizerRootEl = useTemplateRef('resizerRootEl');
const iframeLoading = ref(true); const iframeLoading = ref(true);
const iframeEl = shallowRef<HTMLIFrameElement>(); const iframeEl = useTemplateRef('iframeEl');
const iframeHeight = ref(0); const iframeHeight = ref(0);
const iframeScale = ref(1); const iframeScale = ref(1);
const iframeStyle = computed(() => { const iframeStyle = computed(() => {

View File

@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, computed, watch, onMounted } from 'vue'; import { ref, useTemplateRef, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { import {
emojilist, emojilist,
@@ -157,8 +157,8 @@ const emit = defineEmits<{
(ev: 'esc'): void; (ev: 'esc'): void;
}>(); }>();
const searchEl = shallowRef<HTMLInputElement>(); const searchEl = useTemplateRef('searchEl');
const emojisEl = shallowRef<HTMLDivElement>(); const emojisEl = useTemplateRef('emojisEl');
const { const {
emojiPickerScale, emojiPickerScale,

View File

@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue'; import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue'; import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = useTemplateRef('modal');
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>(); const picker = useTemplateRef('picker');
function chosen(emoji: string) { function chosen(emoji: string) {
emit('done', emoji); emit('done', emoji);

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

@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef, ref } from 'vue'; import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@@ -42,7 +42,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialog = useTemplateRef('dialog');
const caption = ref(props.default); const caption = ref(props.default);

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, shallowRef, watch } from 'vue'; import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js'; import { getBgColor } from '@/utility/get-bg-color.js';
@@ -46,7 +46,7 @@ const props = withDefaults(defineProps<{
persistKey: null, persistKey: null,
}); });
const rootEl = shallowRef<HTMLElement>(); const rootEl = useTemplateRef('rootEl');
const parentBg = ref<string | null>(null); const parentBg = ref<string | null>(null);
// eslint-disable-next-line vue/no-setup-props-reactivity-loss // eslint-disable-next-line vue/no-setup-props-reactivity-loss
const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded); const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref, shallowRef } from 'vue'; import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js'; import { getBgColor } from '@/utility/get-bg-color.js';
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
spacerMax: 22, spacerMax: 22,
}); });
const rootEl = shallowRef<HTMLElement>(); const rootEl = useTemplateRef('rootEl');
const bgSame = ref(false); const bgSame = ref(false);
const opened = ref(props.defaultOpen); const opened = ref(props.defaultOpen);
const openedAtLeastOnce = ref(props.defaultOpen); const openedAtLeastOnce = ref(props.defaultOpen);

View File

@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</div> </div>
<div v-else class="_fullinfo"> <div v-else class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/> <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div> <div>{{ i18n.ts.nothing }}</div>
</div> </div>
</MkSpacer> </MkSpacer>
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, shallowRef } from 'vue'; import { reactive, useTemplateRef } from 'vue';
import MkInput from './MkInput.vue'; import MkInput from './MkInput.vue';
import MkTextarea from './MkTextarea.vue'; import MkTextarea from './MkTextarea.vue';
import MkSwitch from './MkSwitch.vue'; import MkSwitch from './MkSwitch.vue';
@@ -99,7 +99,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialog = useTemplateRef('dialog');
const values = reactive({}); const values = reactive({});
for (const item in props.form) { for (const item in props.form) {

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue'; import { onMounted, nextTick, watch, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<{
label: '', label: '',
}); });
const rootEl = shallowRef<HTMLDivElement | null>(null); const rootEl = useTemplateRef('rootEl');
const chartEl = shallowRef<HTMLCanvasElement | null>(null); const chartEl = useTemplateRef('chartEl');
const now = new Date(); const now = new Date();
let chartInstance: Chart | null = null; let chartInstance: Chart | null = null;
const fetching = ref(true); const fetching = ref(true);

View File

@@ -11,18 +11,27 @@ SPDX-License-Identifier: AGPL-3.0-only
@touchmove.passive="touchMove" @touchmove.passive="touchMove"
@touchend.passive="touchEnd" @touchend.passive="touchEnd"
> >
<!-- 注意slot内の最上位要素に動的にkeyを設定すること --> <Transition
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません --> :class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
<slot></slot> :enterActiveClass="$style.swipeAnimation_enterActive"
:leaveActiveClass="$style.swipeAnimation_leaveActive"
:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
:style="`--swipe: ${pullDistance}px;`"
>
<!-- 注意slot内の最上位要素に動的にkeyを設定すること -->
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
<slot></slot>
</Transition>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, computed, nextTick, watch } from 'vue'; import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js'; import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
const rootEl = shallowRef<HTMLDivElement>(); const rootEl = useTemplateRef('rootEl');
const tabModel = defineModel<string>('tab'); const tabModel = defineModel<string>('tab');

View File

@@ -14,8 +14,34 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined" :enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined" :leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
> >
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/> <canvas
<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/> v-show="hide"
key="canvas"
ref="canvas"
:class="$style.canvas"
:width="canvasWidth"
:height="canvasHeight"
:title="title ?? undefined"
draggable="false"
tabindex="-1"
style="-webkit-user-drag: none;"
/>
<img
v-show="!hide"
key="img"
ref="img"
:height="imgHeight ?? undefined"
:width="imgWidth ?? undefined"
:class="$style.img"
:src="src ?? undefined"
:title="title ?? undefined"
:alt="alt ?? undefined"
loading="eager"
decoding="async"
draggable="false"
tabindex="-1"
style="-webkit-user-drag: none;"
/>
</TransitionGroup> </TransitionGroup>
</div> </div>
</template> </template>
@@ -43,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 = 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();
}); });
@@ -57,7 +81,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue'; import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { render } from 'buraha'; import { render } from 'buraha';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
@@ -94,9 +118,9 @@ const props = withDefaults(defineProps<{
}); });
const viewId = uuid(); const viewId = uuid();
const canvas = shallowRef<HTMLCanvasElement>(); const canvas = useTemplateRef('canvas');
const root = shallowRef<HTMLDivElement>(); const root = useTemplateRef('root');
const img = shallowRef<HTMLImageElement>(); const img = useTemplateRef('img');
const loaded = ref(false); const loaded = ref(false);
const canvasWidth = ref(64); const canvasWidth = ref(64);
const canvasHeight = ref(64); const canvasHeight = ref(64);

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="[$style.root, { [$style.warn]: warn }]"> <div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i> <i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
<i v-else class="ti ti-info-circle" :class="$style.i"></i> <i v-else class="ti ti-info-circle" :class="$style.i"></i>
<div><slot></slot></div> <div><slot></slot></div>

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div> <div class="_selectable">
<div :class="$style.label" @click="focus"><slot name="label"></slot></div> <div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]"> <div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> <div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
@@ -44,14 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
import type { InputHTMLAttributes } from 'vue';
import { debounce } from 'throttle-debounce'; import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import type { InputHTMLAttributes } from 'vue';
import type { SuggestionType } from '@/utility/autocomplete.js';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { Autocomplete } from '@/utility/autocomplete.js'; import { Autocomplete } from '@/utility/autocomplete.js';
import type { SuggestionType } from '@/utility/autocomplete.js';
const props = defineProps<{ const props = defineProps<{
modelValue: string | number | null; modelValue: string | number | null;
@@ -92,9 +92,9 @@ const focused = ref(false);
const changed = ref(false); const changed = ref(false);
const invalid = ref(false); const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null); const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = shallowRef<HTMLInputElement>(); const inputEl = useTemplateRef('inputEl');
const prefixEl = shallowRef<HTMLElement>(); const prefixEl = useTemplateRef('prefixEl');
const suffixEl = shallowRef<HTMLElement>(); const suffixEl = useTemplateRef('suffixEl');
const height = const height =
props.small ? 33 : props.small ? 33 :
props.large ? 39 : props.large ? 39 :

View File

@@ -84,8 +84,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, computed, shallowRef } from 'vue'; import { onMounted, ref, computed, useTemplateRef } from 'vue';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue'; import MkChart from '@/components/MkChart.vue';
import { useChartTooltip } from '@/use/use-chart-tooltip.js'; import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -95,7 +96,6 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkHeatmap from '@/components/MkHeatmap.vue'; import MkHeatmap from '@/components/MkHeatmap.vue';
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue'; import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
@@ -109,8 +109,8 @@ const chartLimit = 500;
const chartSpan = ref<'hour' | 'day'>('hour'); const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users'); const chartSrc = ref('active-users');
const heatmapSrc = ref<HeatmapSource>('active-users'); const heatmapSrc = ref<HeatmapSource>('active-users');
const subDoughnutEl = shallowRef<HTMLCanvasElement>(); const subDoughnutEl = useTemplateRef('subDoughnutEl');
const pubDoughnutEl = shallowRef<HTMLCanvasElement>(); const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
const { handler: externalTooltipHandler1 } = useChartTooltip({ const { handler: externalTooltipHandler1 } = useChartTooltip({
position: 'middle', position: 'middle',

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.items"> <div :class="$style.items">
<div> <div>
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div> <div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
<div>{{ invite.code }}</div> <div class="_selectableAtomic">{{ invite.code }}</div>
</div> </div>
<div v-if="moderator"> <div v-if="moderator">
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div> <div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
@@ -90,7 +90,6 @@ function deleteCode() {
function copyInviteCode() { function copyInviteCode() {
copyToClipboard(props.invite.code); copyToClipboard(props.invite.code);
os.success();
} }
</script> </script>

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.key"> <div :class="$style.key">
<slot name="key"></slot> <slot name="key"></slot>
</div> </div>
<div :class="$style.value"> <div :class="$style.value" class="_selectable">
<slot name="value"></slot> <slot name="value"></slot>
<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button> <button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button>
</div> </div>
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<{
const copy_ = () => { const copy_ = () => {
copyToClipboard(props.copy); copyToClipboard(props.copy);
os.success();
}; };
</script> </script>

View File

@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from 'vue'; import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue'; import MkModal from '@/components/MkModal.vue';
import { navbarItemDef } from '@/navbar.js'; import { navbarItemDef } from '@/navbar.js';
import { deviceKind } from '@/utility/device-kind.js'; import { deviceKind } from '@/utility/device-kind.js';
@@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
deviceKind === 'smartphone' ? 'drawer' : deviceKind === 'smartphone' ? 'drawer' :
'dialog'; 'dialog';
const modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = useTemplateRef('modal');
const menu = prefer.s.menu; const menu = prefer.s.menu;

View File

@@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue'; import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js'; import type { Keymap } from '@/utility/hotkey.js';
@@ -151,8 +151,8 @@ function hasFocus() {
return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement); return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
} }
const playerEl = shallowRef<HTMLDivElement>(); const playerEl = useTemplateRef('playerEl');
const audioEl = shallowRef<HTMLAudioElement>(); const audioEl = useTemplateRef('audioEl');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss // eslint-disable-next-line vue/no-setup-props-reactivity-loss
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore')); const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
@@ -242,7 +242,7 @@ function showMenu(ev: MouseEvent) {
if (prefer.s.devMode) { if (prefer.s.devMode) {
menu.push({ type: 'divider' }, { menu.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-hash',
text: i18n.ts.copyFileId, text: i18n.ts.copyFileId,
action: () => { action: () => {
copyToClipboard(props.audio.id); copyToClipboard(props.audio.id);

View File

@@ -168,7 +168,7 @@ function showMenu(ev: MouseEvent) {
if (prefer.s.devMode) { if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-hash',
text: i18n.ts.copyFileId, text: i18n.ts.copyFileId,
action: () => { action: () => {
copyToClipboard(props.image.id); copyToClipboard(props.image.id);

View File

@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, shallowRef } from 'vue'; import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe'; import PhotoSwipe from 'photoswipe';
@@ -46,7 +46,7 @@ const props = defineProps<{
raw?: boolean; raw?: boolean;
}>(); }>();
const gallery = shallowRef<HTMLDivElement>(); 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()); 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);

View File

@@ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue'; import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js'; import type { Keymap } from '@/utility/hotkey.js';
@@ -267,7 +267,7 @@ function showMenu(ev: MouseEvent) {
if (prefer.s.devMode) { if (prefer.s.devMode) {
menu.push({ type: 'divider' }, { menu.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-hash',
text: i18n.ts.copyFileId, text: i18n.ts.copyFileId,
action: () => { action: () => {
copyToClipboard(props.video.id); copyToClipboard(props.video.id);
@@ -299,8 +299,8 @@ async function toggleSensitive(file: Misskey.entities.DriveFile) {
} }
// MediaControl: Video State // MediaControl: Video State
const videoEl = shallowRef<HTMLVideoElement>(); const videoEl = useTemplateRef('videoEl');
const playerEl = shallowRef<HTMLDivElement>(); const playerEl = useTemplateRef('playerEl');
const isHoverring = ref(false); const isHoverring = ref(false);
const controlsShowing = computed(() => { const controlsShowing = computed(() => {
if (!oncePlayed.value) return true; if (!oncePlayed.value) return true;

View File

@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue'; import { nextTick, onMounted, onUnmounted, provide, useTemplateRef, watch } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
provide('isNestingMenu', true); provide('isNestingMenu', true);
const el = shallowRef<HTMLElement>(); const el = useTemplateRef('el');
const align = 'left'; const align = 'left';
const SCROLLBAR_THICKNESS = 16; const SCROLLBAR_THICKNESS = 16;

View File

@@ -35,6 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]"> <span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
<span><MkEllipsis/></span> <span><MkEllipsis/></span>
</span> </span>
<div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]">
<component :is="item.component" v-bind="item.props"/>
</div>
<MkA <MkA
v-else-if="item.type === 'link'" v-else-if="item.type === 'link'"
role="menuitem" role="menuitem"
@@ -176,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, shallowRef, 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';
@@ -209,11 +212,11 @@ const big = isTouchUsing;
const isNestingMenu = inject<boolean>('isNestingMenu', false); const isNestingMenu = inject<boolean>('isNestingMenu', false);
const itemsEl = shallowRef<HTMLElement>(); const itemsEl = useTemplateRef('itemsEl');
const items2 = ref<InnerMenuItem[]>(); const items2 = ref<InnerMenuItem[]>();
const child = shallowRef<InstanceType<typeof XChild>>(); const child = useTemplateRef('child');
const keymap = { const keymap = {
'up|k|shift+tab': { 'up|k|shift+tab': {
@@ -254,7 +257,7 @@ watch(() => props.items, () => {
}); });
const childMenu = ref<MenuItem[] | null>(); const childMenu = ref<MenuItem[] | null>();
const childTarget = shallowRef<HTMLElement | null>(); const childTarget = shallowRef<HTMLElement>();
function closeChild() { function closeChild() {
childMenu.value = null; childMenu.value = null;

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