Compare commits
67 Commits
view-trans
...
l10n_devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
961badd01d | ||
|
|
1e4c49a9ab | ||
|
|
cc9d5bc2b5 | ||
|
|
144fabdead | ||
|
|
dfab6b1b8d | ||
|
|
fac59d75e2 | ||
|
|
fd3e28812e | ||
|
|
6a90b7e04b | ||
|
|
8d8414687a | ||
|
|
0c682dd027 | ||
|
|
3399c786a8 | ||
|
|
64cf101fe7 | ||
|
|
8b6d90b7a4 | ||
|
|
070749bdc8 | ||
|
|
161706c5e2 | ||
|
|
596e517f77 | ||
|
|
91670caca0 | ||
|
|
fccaadacf0 | ||
|
|
282caa0b7e | ||
|
|
9529867630 | ||
|
|
d06fadd095 | ||
|
|
9dd13f364b | ||
|
|
b067d4dcd6 | ||
|
|
acac759d87 | ||
|
|
b37622fa64 | ||
|
|
c17a104de6 | ||
|
|
4ab9f66356 | ||
|
|
aed95a765d | ||
|
|
71841e365e | ||
|
|
3b20279e20 | ||
|
|
21dc7aebe7 | ||
|
|
aff03708a7 | ||
|
|
69ad3cf89b | ||
|
|
7b323031b7 | ||
|
|
81ac71f7e5 | ||
|
|
2c76018b7f | ||
|
|
2dc2d2e4fe | ||
|
|
409cd4fbd3 | ||
|
|
7d4045e8b4 | ||
|
|
bdc72e5817 | ||
|
|
11378b17c5 | ||
|
|
62bf0d53d3 | ||
|
|
05391f59a5 | ||
|
|
d609f41f61 | ||
|
|
0a295e1bb0 | ||
|
|
474155b677 | ||
|
|
6c8f21b608 | ||
|
|
7d5daa4b00 | ||
|
|
1ce81f243e | ||
|
|
a773f2976d | ||
|
|
467c68fb98 | ||
|
|
22b0ace8b4 | ||
|
|
a814395127 | ||
|
|
81a0cbd294 | ||
|
|
32844e4775 | ||
|
|
fbd9f47182 | ||
|
|
1c9e25470a | ||
|
|
ce6b2448ce | ||
|
|
7d44b47fdf | ||
|
|
dca42fd6e6 | ||
|
|
43153311c6 | ||
|
|
2b23c7e7f5 | ||
|
|
2d4e85b466 | ||
|
|
7bfada9792 | ||
|
|
4fef9c670a | ||
|
|
9599261fc3 | ||
|
|
c2940fd77c |
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -24,9 +24,6 @@ updates:
|
||||
aws-sdk:
|
||||
patterns:
|
||||
- "@aws-sdk/*"
|
||||
bull-board:
|
||||
patterns:
|
||||
- "@bull-board/*"
|
||||
nestjs:
|
||||
patterns:
|
||||
- "@nestjs/*"
|
||||
|
||||
4
.github/workflows/storybook.yml
vendored
4
.github/workflows/storybook.yml
vendored
@@ -50,8 +50,8 @@ jobs:
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- name: Check pnpm-lock.yaml
|
||||
run: git diff --exit-code pnpm-lock.yaml
|
||||
- name: Build misskey-js
|
||||
run: pnpm --filter misskey-js build
|
||||
- name: Build dependent packages
|
||||
run: pnpm -F misskey-js -F misskey-bubble-game -F misskey-reversi build
|
||||
- name: Build storybook
|
||||
run: pnpm --filter frontend build-storybook
|
||||
- name: Publish to Chromatic
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,23 +1,31 @@
|
||||
## 2025.3.2
|
||||
|
||||
### General
|
||||
-
|
||||
- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||
- Misskeyネイティブでダッシュボードを実装予定です
|
||||
|
||||
### Client
|
||||
- Feat: 設定の管理が強化されました
|
||||
- 自動でバックアップされるように
|
||||
- 任意の設定項目をデバイス間で同期できるように
|
||||
- Feat: 画面を重ねて表示するオプションを実装(実験的)
|
||||
- Enhance: プラグインの管理が強化されました
|
||||
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
||||
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||
- Enhance: テーマ設定画面のデザインを改善
|
||||
- Enhance: 投稿フォームの設定メニューを改良
|
||||
- 投稿フォームをリセットできるように
|
||||
- 文字数カウントを復活
|
||||
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
|
||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||
|
||||
### Server
|
||||
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
|
||||
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
|
||||
- Fix: 連合無しモードでも外部から照会可能だった問題を修正
|
||||
- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正
|
||||
|
||||
## 2025.3.1
|
||||
|
||||
|
||||
@@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
||||
query?: Record<string, string>;
|
||||
loginRequired?: boolean;
|
||||
hash?: string;
|
||||
globalCacheKey?: string;
|
||||
children?: RouteDef[];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1013,6 +1013,7 @@ flip: "اقلب"
|
||||
lastNDays: "آخر {n} أيام"
|
||||
surrender: "ألغِ"
|
||||
postForm: "أنشئ ملاحظة"
|
||||
information: "عن"
|
||||
_delivery:
|
||||
stop: "مُعلّق"
|
||||
_initialAccountSetting:
|
||||
|
||||
@@ -853,6 +853,7 @@ renotes: "রিনোট"
|
||||
sourceCode: "সোর্স কোড"
|
||||
flip: "উল্টান"
|
||||
postForm: "নোট লিখুন"
|
||||
information: "আপনার সম্পর্কে"
|
||||
_delivery:
|
||||
stop: "স্থগিত করা হয়েছে"
|
||||
_type:
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
|
||||
makeActive: "Activar"
|
||||
display: "Veure"
|
||||
copy: "Copiar"
|
||||
copiedToClipboard: "Copiat al porta papers"
|
||||
metrics: "Mètriques"
|
||||
overview: "Visió General"
|
||||
logs: "Registres"
|
||||
@@ -1139,7 +1140,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d
|
||||
thisChannelArchived: "Aquest Canal ha sigut arxivat."
|
||||
displayOfNote: "Mostrar notes"
|
||||
initialAccountSetting: "Configuració del perfil"
|
||||
youFollowing: "Seguint"
|
||||
youFollowing: "Segueixes "
|
||||
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."
|
||||
options: "Opcions"
|
||||
@@ -1190,7 +1191,7 @@ pastAnnouncements: "Informes passats"
|
||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||
replies: "Respostes"
|
||||
renotes: "Impulsar"
|
||||
renotes: "Impulsos"
|
||||
loadReplies: "Mostrar les respostes"
|
||||
loadConversation: "Mostrar la conversació "
|
||||
pinnedList: "Llista fixada"
|
||||
@@ -1326,7 +1327,19 @@ restore: "Restaurar "
|
||||
syncBetweenDevices: "Sincronització entre dispositius"
|
||||
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?"
|
||||
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ó"
|
||||
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:
|
||||
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."
|
||||
@@ -1344,6 +1357,9 @@ _settings:
|
||||
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."
|
||||
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:
|
||||
profileName: "Nom del perfil"
|
||||
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
|
||||
@@ -2056,7 +2072,7 @@ _theme:
|
||||
hashtag: "Etiqueta"
|
||||
mention: "Menció"
|
||||
mentionMe: "Mencions (jo)"
|
||||
renote: "Renotar"
|
||||
renote: "Impulsar"
|
||||
modalBg: "Fons del modal"
|
||||
divider: "Divisor"
|
||||
scrollbarHandle: "Maneta de la barra de desplaçament"
|
||||
@@ -2498,7 +2514,7 @@ _notification:
|
||||
follow: "Segueix-me"
|
||||
mention: "Menció"
|
||||
reply: "Respostes"
|
||||
renote: "Impulsar"
|
||||
renote: "Impulsos"
|
||||
quote: "Citar"
|
||||
reaction: "Reaccions"
|
||||
pollEnded: "Enquesta terminada"
|
||||
@@ -2508,12 +2524,13 @@ _notification:
|
||||
achievementEarned: "Assoliment desbloquejat"
|
||||
exportCompleted: "Exportació completada"
|
||||
login: "Iniciar sessió"
|
||||
createToken: "Creació de tokens d'accés "
|
||||
test: "Prova la notificació"
|
||||
app: "Notificacions d'aplicacions"
|
||||
_actions:
|
||||
followBack: "També et segueix"
|
||||
reply: "Respondre"
|
||||
renote: "Impulsos"
|
||||
renote: "Impulsar"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Mostrar sempre la columna principal"
|
||||
columnAlign: "Alinea les columnes"
|
||||
@@ -2657,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho."
|
||||
_plugin:
|
||||
title: "Vols instal·lar aquest afegit?"
|
||||
metaTitle: "Informació de l'afegit "
|
||||
_theme:
|
||||
title: "Vols instal·lar aquest tema?"
|
||||
metaTitle: "Informació del tema"
|
||||
_meta:
|
||||
base: "Paleta de colors base"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -9,6 +9,8 @@ reset: "Obnovit"
|
||||
notifications: "Oznámení"
|
||||
username: "Uživatelské jméno"
|
||||
password: "Heslo"
|
||||
initialPasswordForSetup: "Počáteční heslo pro nastavení"
|
||||
initialPasswordIsIncorrect: "Počáteční heslo pro nastavení je nesprávné"
|
||||
forgotPassword: "Zapomenuté heslo"
|
||||
fetchingAsApObject: "Načítám data z Fediversu..."
|
||||
ok: "Potvrdit"
|
||||
@@ -169,6 +171,9 @@ addAccount: "Přidat účet"
|
||||
reloadAccountsList: "Obnovit list účtů"
|
||||
loginFailed: "Přihlášení se nezdařilo."
|
||||
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ě"
|
||||
wallpaper: "Obrázek na pozadí"
|
||||
setWallpaper: "Nastavení obrázku na pozadí"
|
||||
@@ -193,6 +198,7 @@ perHour: "za hodinu"
|
||||
perDay: "za den"
|
||||
stopActivityDelivery: "Přestat zasílat aktivitu"
|
||||
blockThisInstance: "Blokovat tuto instanci"
|
||||
silenceThisInstance: "Utišit tuto instanci"
|
||||
operations: "Operace"
|
||||
software: "Software"
|
||||
version: "Verze"
|
||||
@@ -474,6 +480,8 @@ uiLanguage: "Jazyk uživatelského rozhraní"
|
||||
aboutX: "O {x}"
|
||||
emojiStyle: "Styl emoji"
|
||||
native: "Výchozí"
|
||||
style: "Vzhled"
|
||||
popup: "Vyskakovací okno"
|
||||
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
|
||||
noHistory: "Žádná historie"
|
||||
signinHistory: "Historie přihlášení"
|
||||
@@ -1099,6 +1107,7 @@ flip: "Otočit"
|
||||
lastNDays: "Posledních {n} dnů"
|
||||
surrender: "Zrušit"
|
||||
postForm: "Formulář pro odeslání"
|
||||
information: "Informace"
|
||||
_delivery:
|
||||
stop: "Suspendováno"
|
||||
_type:
|
||||
|
||||
@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
|
||||
unpin: "Von deinem Profil lösen"
|
||||
copyContent: "Inhalt kopieren"
|
||||
copyLink: "Link kopieren"
|
||||
copyRemoteLink: "Renote-Link kopieren"
|
||||
copyRemoteLink: "Remote-Link kopieren"
|
||||
copyLinkRenote: "Renote-Link kopieren"
|
||||
delete: "Löschen"
|
||||
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."
|
||||
options: "Optionen"
|
||||
specifyUser: "Spezifischer Benutzer"
|
||||
lookupConfirm: "Zustimmen?"
|
||||
lookupConfirm: "Bist du sicher, dass du das nachschlagen möchtest?"
|
||||
openTagPageConfirm: "Hashtag Seite wirklich öffnen?"
|
||||
specifyHost: "Host"
|
||||
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."
|
||||
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
|
||||
postForm: "Notizfenster"
|
||||
information: "Über"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
@@ -2524,10 +2525,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers."
|
||||
_plugin:
|
||||
title: "Möchtest du dieses Plugin installieren?"
|
||||
metaTitle: "Plugininformation"
|
||||
_theme:
|
||||
title: "Möchten du dieses Farbschema installieren?"
|
||||
metaTitle: "Farbschemainfo"
|
||||
_meta:
|
||||
base: "Farbschemavorlage"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -289,6 +289,7 @@ icon: "Εικονίδιο"
|
||||
replies: "Απάντηση"
|
||||
renotes: "Κοινοποίηση σημειώματος"
|
||||
postForm: "Φόρμα δημοσίευσης"
|
||||
information: "Πληροφορίες"
|
||||
_email:
|
||||
_follow:
|
||||
title: "Έχετε ένα νέο ακόλουθο"
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
|
||||
makeActive: "Activate"
|
||||
display: "Display"
|
||||
copy: "Copy"
|
||||
copiedToClipboard: "Copied to clipboard"
|
||||
metrics: "Metrics"
|
||||
overview: "Overview"
|
||||
logs: "Logs"
|
||||
@@ -1316,14 +1317,29 @@ unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for t
|
||||
preferences: "Preferences"
|
||||
accessibility: "Accessibility"
|
||||
preferencesProfile: "Preferences profile"
|
||||
copyPreferenceId: "Copy the proference ID"
|
||||
copyPreferenceId: "Copy the preference ID"
|
||||
resetToDefaultValue: "Revert to default"
|
||||
overrideByAccount: "Override by the account"
|
||||
untitled: "Untitled"
|
||||
noName: "No name"
|
||||
skip: "Skip"
|
||||
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"
|
||||
textCount: "Character count"
|
||||
information: "About"
|
||||
_emojiPalette:
|
||||
palettes: "Palette"
|
||||
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
|
||||
paletteForMain: "Main palette"
|
||||
paletteForReaction: "Reaction palette"
|
||||
_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."
|
||||
@@ -1341,6 +1357,9 @@ _settings:
|
||||
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."
|
||||
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:
|
||||
profileName: "Profile name"
|
||||
profileNameDescription: "Set a name that identifies this device."
|
||||
@@ -1373,7 +1392,7 @@ _abuseUserReport:
|
||||
resolve: "Resolve"
|
||||
accept: "Accept"
|
||||
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:
|
||||
status: "Delivery status"
|
||||
stop: "Suspended"
|
||||
@@ -2505,6 +2524,7 @@ _notification:
|
||||
achievementEarned: "Achievement unlocked"
|
||||
exportCompleted: "The export has been completed"
|
||||
login: "Sign In"
|
||||
createToken: "Create access token"
|
||||
test: "Notification test"
|
||||
app: "Notifications from linked apps"
|
||||
_actions:
|
||||
@@ -2532,6 +2552,7 @@ _deck:
|
||||
useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
|
||||
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
|
||||
flexible: "Auto-adjust width"
|
||||
enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices"
|
||||
_columns:
|
||||
main: "Main"
|
||||
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."
|
||||
_abuseReport:
|
||||
_notificationRecipient:
|
||||
createRecipient: "Add a recipient for reports"
|
||||
createRecipient: "Add recipient for reports"
|
||||
modifyRecipient: "Edit a recipient for reports"
|
||||
recipientType: "Notification type"
|
||||
_recipientType:
|
||||
@@ -2653,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation."
|
||||
_plugin:
|
||||
title: "Do you want to install this plugin?"
|
||||
metaTitle: "Plugin information"
|
||||
_theme:
|
||||
title: "Do you want to install this theme?"
|
||||
metaTitle: "Theme information"
|
||||
_meta:
|
||||
base: "Base color scheme"
|
||||
_vendorInfo:
|
||||
@@ -2809,7 +2828,7 @@ _customEmojisManager:
|
||||
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?"
|
||||
_local:
|
||||
tabTitleList: "List of registered Emojis"
|
||||
tabTitleList: "Registered emojis"
|
||||
tabTitleRegister: "Emoji registration"
|
||||
_list:
|
||||
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."
|
||||
description3: "For more information, please refer to this. {link}"
|
||||
_followRequest:
|
||||
recieved: "Received application"
|
||||
sent: "Sent application"
|
||||
recieved: "Received request"
|
||||
sent: "Sent request"
|
||||
_remoteLookupErrors:
|
||||
_federationNotAllowed:
|
||||
title: "Unable to communicate with this server"
|
||||
|
||||
@@ -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."
|
||||
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
|
||||
postForm: "Formulario"
|
||||
information: "Información"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_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."
|
||||
_plugin:
|
||||
title: "¿Quieres instalar este plugin?"
|
||||
metaTitle: "Información del plugin"
|
||||
_theme:
|
||||
title: "¿Quieres instalar este tema?"
|
||||
metaTitle: "Información del tema"
|
||||
_meta:
|
||||
base: "Esquema de color base"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -1278,6 +1278,7 @@ lockdown: "Verrouiller"
|
||||
pleaseSelectAccount: "Sélectionner un compte"
|
||||
availableRoles: "Rôles disponibles"
|
||||
postForm: "Formulaire de publication"
|
||||
information: "Informations"
|
||||
_abuseUserReport:
|
||||
forward: "Transférer"
|
||||
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."
|
||||
_plugin:
|
||||
title: "Voulez-vous installer cette extension ?"
|
||||
metaTitle: "Informations sur l'extension"
|
||||
_theme:
|
||||
title: "Voulez-vous installer ce thème ?"
|
||||
metaTitle: "Informations sur le thème"
|
||||
_meta:
|
||||
base: "Palette de couleurs de base"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -1262,6 +1262,7 @@ modified: "Diubah"
|
||||
thereAreNChanges: "Ada {n} perubahan"
|
||||
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
||||
postForm: "Buat catatan"
|
||||
information: "Informasi"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_abuseUserReport:
|
||||
@@ -2492,10 +2493,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan."
|
||||
_plugin:
|
||||
title: "Apakah kamu ingin memasang plugin ini?"
|
||||
metaTitle: "Informasi plugin"
|
||||
_theme:
|
||||
title: "Apakah kamu ingin memasang tema ini?"
|
||||
metaTitle: "Informasi tema"
|
||||
_meta:
|
||||
base: "Skema warna dasar"
|
||||
_vendorInfo:
|
||||
|
||||
32
locales/index.d.ts
vendored
32
locales/index.d.ts
vendored
@@ -2810,6 +2810,10 @@ export interface Locale extends ILocale {
|
||||
* コピー
|
||||
*/
|
||||
"copy": string;
|
||||
/**
|
||||
* クリップボードにコピーされました
|
||||
*/
|
||||
"copiedToClipboard": string;
|
||||
/**
|
||||
* メトリクス
|
||||
*/
|
||||
@@ -5346,6 +5350,14 @@ export interface Locale extends ILocale {
|
||||
* 投稿フォーム
|
||||
*/
|
||||
"postForm": string;
|
||||
/**
|
||||
* 文字数
|
||||
*/
|
||||
"textCount": string;
|
||||
/**
|
||||
* 情報
|
||||
*/
|
||||
"information": string;
|
||||
"_emojiPalette": {
|
||||
/**
|
||||
* パレット
|
||||
@@ -5433,6 +5445,14 @@ export interface Locale extends ILocale {
|
||||
* タイムラインとノート
|
||||
*/
|
||||
"timelineAndNote": string;
|
||||
/**
|
||||
* 全てのテキスト要素を選択可能にする
|
||||
*/
|
||||
"makeEveryTextElementsSelectable": string;
|
||||
/**
|
||||
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
|
||||
*/
|
||||
"makeEveryTextElementsSelectable_description": string;
|
||||
};
|
||||
"_preferencesProfile": {
|
||||
/**
|
||||
@@ -9777,6 +9797,10 @@ export interface Locale extends ILocale {
|
||||
* ログイン
|
||||
*/
|
||||
"login": string;
|
||||
/**
|
||||
* アクセストークンの作成
|
||||
*/
|
||||
"createToken": string;
|
||||
/**
|
||||
* 通知のテスト
|
||||
*/
|
||||
@@ -10339,20 +10363,12 @@ export interface Locale extends ILocale {
|
||||
* このプラグインをインストールしますか?
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* プラグイン情報
|
||||
*/
|
||||
"metaTitle": string;
|
||||
};
|
||||
"_theme": {
|
||||
/**
|
||||
* このテーマをインストールしますか?
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* テーマ情報
|
||||
*/
|
||||
"metaTitle": string;
|
||||
};
|
||||
"_meta": {
|
||||
/**
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
|
||||
makeActive: "Attiva"
|
||||
display: "Visualizza"
|
||||
copy: "Copia"
|
||||
copiedToClipboard: "Copiato negli appunti"
|
||||
metrics: "Statistiche"
|
||||
overview: "Anteprima"
|
||||
logs: "Log"
|
||||
@@ -973,7 +974,7 @@ check: "Verifica"
|
||||
driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
|
||||
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
|
||||
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"
|
||||
deleteAccount: "Eliminazione profilo"
|
||||
document: "Documentazione"
|
||||
@@ -1323,7 +1324,22 @@ untitled: "Senza titolo"
|
||||
noName: "Senza nome"
|
||||
skip: "Salta"
|
||||
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"
|
||||
textCount: "Il numero di caratteri"
|
||||
information: "Informazioni"
|
||||
_emojiPalette:
|
||||
palettes: "Tavolozza"
|
||||
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
|
||||
paletteForMain: "Tavolozza principale"
|
||||
paletteForReaction: "Tavolozza per reazioni"
|
||||
_settings:
|
||||
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."
|
||||
@@ -1341,6 +1357,9 @@ _settings:
|
||||
preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo."
|
||||
appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze."
|
||||
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:
|
||||
profileName: "Nome del profilo"
|
||||
profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
|
||||
@@ -2505,6 +2524,7 @@ _notification:
|
||||
achievementEarned: "Risultato raggiunto"
|
||||
exportCompleted: "Esportazione completata"
|
||||
login: "Accessi"
|
||||
createToken: "Creare un token di accesso"
|
||||
test: "Notifiche di test"
|
||||
app: "Notifiche da applicazioni"
|
||||
_actions:
|
||||
@@ -2532,6 +2552,7 @@ _deck:
|
||||
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
|
||||
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
|
||||
flexible: "Larghezza flessibile"
|
||||
enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi"
|
||||
_columns:
|
||||
main: "Principale"
|
||||
widgets: "Riquadri"
|
||||
@@ -2653,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile."
|
||||
_plugin:
|
||||
title: "Vuoi davvero installare questo componente aggiuntivo?"
|
||||
metaTitle: "Informazioni sul componente aggiuntivo"
|
||||
_theme:
|
||||
title: "Vuoi davvero installare questa variazione grafica?"
|
||||
metaTitle: "Informazioni sulla variazione grafica"
|
||||
_meta:
|
||||
base: "Combinazione base di colori"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
|
||||
makeActive: "アクティブにする"
|
||||
display: "表示"
|
||||
copy: "コピー"
|
||||
copiedToClipboard: "クリップボードにコピーされました"
|
||||
metrics: "メトリクス"
|
||||
overview: "概要"
|
||||
logs: "ログ"
|
||||
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
|
||||
paste: "ペースト"
|
||||
emojiPalette: "絵文字パレット"
|
||||
postForm: "投稿フォーム"
|
||||
textCount: "文字数"
|
||||
information: "情報"
|
||||
|
||||
_emojiPalette:
|
||||
palettes: "パレット"
|
||||
@@ -1357,6 +1360,8 @@ _settings:
|
||||
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
||||
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
||||
timelineAndNote: "タイムラインとノート"
|
||||
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
|
||||
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
|
||||
|
||||
_preferencesProfile:
|
||||
profileName: "プロファイル名"
|
||||
@@ -2584,6 +2589,7 @@ _notification:
|
||||
achievementEarned: "実績の獲得"
|
||||
exportCompleted: "エクスポートが完了した"
|
||||
login: "ログイン"
|
||||
createToken: "アクセストークンの作成"
|
||||
test: "通知のテスト"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
@@ -2744,10 +2750,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。"
|
||||
_plugin:
|
||||
title: "このプラグインをインストールしますか?"
|
||||
metaTitle: "プラグイン情報"
|
||||
_theme:
|
||||
title: "このテーマをインストールしますか?"
|
||||
metaTitle: "テーマ情報"
|
||||
_meta:
|
||||
base: "基本のカラースキーム"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -1312,6 +1312,7 @@ federationDisabled: "このサーバーは連合が無効化されてるで。
|
||||
confirmOnReact: "ツッコむときに確認とる"
|
||||
reactAreYouSure: "\" {emoji} \" でツッコむ?"
|
||||
postForm: "投稿フォーム"
|
||||
information: "情報"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
@@ -2610,10 +2611,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。"
|
||||
_plugin:
|
||||
title: "このプラグイン、インストールする?"
|
||||
metaTitle: "プラグイン情報"
|
||||
_theme:
|
||||
title: "このテーマインストールする?"
|
||||
metaTitle: "テーマ情報"
|
||||
_meta:
|
||||
base: ""
|
||||
_vendorInfo:
|
||||
|
||||
@@ -655,6 +655,7 @@ replies: "답하기"
|
||||
renotes: "리노트"
|
||||
attach: "옇기"
|
||||
surrender: "아이예"
|
||||
information: "정보"
|
||||
_delivery:
|
||||
stop: "고만 보내예"
|
||||
_type:
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}님이 \"{word}\"를 언급했습니다."
|
||||
makeActive: "활성화"
|
||||
display: "보기"
|
||||
copy: "복사"
|
||||
copiedToClipboard: "클립보드에 복사되었습니다."
|
||||
metrics: "통계"
|
||||
overview: "요약"
|
||||
logs: "로그"
|
||||
@@ -1294,7 +1295,7 @@ thereAreNChanges: "{n}건 변경이 있습니다."
|
||||
signinWithPasskey: "패스키로 로그인"
|
||||
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
|
||||
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
|
||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "입력된 패스키는 정상적이나, 비밀번호 없이 로그인 하는 기능이 비활성화 되어있습니다."
|
||||
messageToFollower: "팔로워에게 보낼 메시지"
|
||||
target: "대상"
|
||||
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
||||
@@ -1325,20 +1326,40 @@ skip: "건너뛰기"
|
||||
restore: "복원"
|
||||
syncBetweenDevices: "장치간 동기화"
|
||||
preferenceSyncConflictTitle: "서버에 설정값이 존재합니다."
|
||||
preferenceSyncConflictText: "동기화를 활성화 한 항목의 설정 값은 서버에 저장되지만, 해당 항목은 이미 서버에 설정 값이 저장되어져 있습니다. 어느 쪽의 설정 값을 덮어씌울까요?"
|
||||
preferenceSyncConflictChoiceServer: "서버 설정값"
|
||||
preferenceSyncConflictChoiceDevice: "장치 설정값"
|
||||
preferenceSyncConflictChoiceCancel: "동기화 취소"
|
||||
paste: "붙여넣기"
|
||||
emojiPalette: "이모지 팔레트"
|
||||
postForm: "글 입력란"
|
||||
textCount: "문자 수"
|
||||
information: "정보"
|
||||
_emojiPalette:
|
||||
palettes: "팔레트"
|
||||
enableSyncBetweenDevicesForPalettes: "팔레트의 디바이스 간 동기화를 활성화"
|
||||
paletteForMain: "메인으로 사용할 팔레트"
|
||||
paletteForReaction: "리액션으로 사용할 팔레트"
|
||||
_settings:
|
||||
driveBanner: "드라이브 관리, 사용량 확인, 파일 업로드에 관한 설정을 합니다."
|
||||
pluginBanner: "플러그인을 사용하면 클라이언트 기능을 확장할 수 있습니다. 플러그인 설치와 개별적인 설정을 합니다."
|
||||
notificationsBanner: "서버에서 받는 알림의 종류 및 범위, 푸시 알림 설정을 합니다."
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnection: "서비스 연동"
|
||||
serviceConnectionBanner: "외부 앱, 서비스와 연결하기 위한 액세스 토큰과 웹 훅 관리 설정을 합니다."
|
||||
accountData: "계정 데이터"
|
||||
accountDataBanner: "계정 데이터의 아카이브를 추출하기/가져오기 하여 관리할 수 있습니다."
|
||||
muteAndBlockBanner: "숨길 컨텐츠의 설정과, 특정 유저의 리액션을 제한하는 설정을 관리합니다."
|
||||
accessibilityBanner: "좀 더 쾌적하게 사용할 수 있도록 클라이언트의 시각 및 움직임에 관한 개인화 설정을 합니다."
|
||||
privacyBanner: "컨텐츠, 계정의 발견 범위, 팔로우 승인제 등의 계정의 프라이버시에 관한 설정을 합니다."
|
||||
securityBanner: "비밀번호, 로그인 방법, OTP, 패스 키 등의 계정의 보안에 관련된 설정을 합니다."
|
||||
preferencesBanner: "취향에 알맞는 클라이언트의 전체적인 동작을 설정합니다."
|
||||
appearanceBanner: "취향에 알맞는 클라이언트의 디스플레이, 표시 방법에 관한 설정을 합니다."
|
||||
soundsBanner: "클라이언트에서 재생할 소리에 대한 설정을 합니다."
|
||||
timelineAndNote: "타임라인과 노트"
|
||||
makeEveryTextElementsSelectable: "모든 텍스트 요소를 선택할 수 있도록 함"
|
||||
makeEveryTextElementsSelectable_description: "활성화 시, 일부 동작에서 사용자의 접근성이 나빠질 수도 있습니다."
|
||||
_preferencesProfile:
|
||||
profileName: "프로필 이름"
|
||||
profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
|
||||
@@ -1362,6 +1383,7 @@ _accountSettings:
|
||||
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
|
||||
makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
|
||||
mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다."
|
||||
mayNotEffectSomeSituations: "여기서 설정하는 제한은 모더레이션이나 리모트 서버에서 볼 때 등 일부 환경에서는 적용되지 않을 수도 있습니다."
|
||||
notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트"
|
||||
notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트"
|
||||
_abuseUserReport:
|
||||
@@ -2502,6 +2524,7 @@ _notification:
|
||||
achievementEarned: "도전 과제 획득"
|
||||
exportCompleted: "추출을 성공함"
|
||||
login: "로그인"
|
||||
createToken: "액세스 토큰 만들기"
|
||||
test: "알림 테스트"
|
||||
app: "연동된 앱을 통한 알림"
|
||||
_actions:
|
||||
@@ -2529,6 +2552,7 @@ _deck:
|
||||
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
|
||||
usedAsMinWidthWhenFlexible: "'폭 자동 조정'이 활성화된 경우 최소 폭으로 사용됩니다"
|
||||
flexible: "폭 자동 조정"
|
||||
enableSyncBetweenDevicesForProfiles: "프로파일 정보의 디바이스 간 동기화를 활성화"
|
||||
_columns:
|
||||
main: "메인"
|
||||
widgets: "위젯"
|
||||
@@ -2650,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
|
||||
_plugin:
|
||||
title: "이 플러그인을 설치하시겠습니까?"
|
||||
metaTitle: "플러그인 정보"
|
||||
_theme:
|
||||
title: "이 테마를 설치하시겠습니까?"
|
||||
metaTitle: "테마 정보"
|
||||
_meta:
|
||||
base: "기본 컬러 스키마"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -394,6 +394,7 @@ searchByGoogle: "ຄົ້ນຫາ"
|
||||
file: "ໄຟລ໌"
|
||||
replies: "ຕອບກັບ"
|
||||
renotes: "Renote"
|
||||
information: "ກ່ຽວກັບ"
|
||||
_delivery:
|
||||
stop: "ໂຈະ"
|
||||
_type:
|
||||
|
||||
@@ -462,6 +462,7 @@ loggedInAsBot: "Momenteel als bot ingelogd"
|
||||
icon: "Avatar"
|
||||
replies: "Antwoord"
|
||||
renotes: "Herdelen"
|
||||
information: "Over"
|
||||
_delivery:
|
||||
stop: "Opgeschort"
|
||||
_type:
|
||||
|
||||
@@ -463,6 +463,7 @@ icon: "Avatar"
|
||||
replies: "Svar"
|
||||
renotes: "Renote"
|
||||
surrender: "Avbryt"
|
||||
information: "Informasjon"
|
||||
_delivery:
|
||||
stop: "Suspendert"
|
||||
_initialAccountSetting:
|
||||
|
||||
@@ -1045,6 +1045,7 @@ lastNDays: "W ciągu ostatnich {n} dni"
|
||||
surrender: "Odrzuć"
|
||||
gameRetry: "Spróbuj ponownie"
|
||||
postForm: "Formularz tworzenia wpisu"
|
||||
information: "Informacje"
|
||||
_delivery:
|
||||
stop: "Zawieszono"
|
||||
_type:
|
||||
|
||||
@@ -1302,6 +1302,7 @@ pleaseSelectAccount: "Selecione uma conta"
|
||||
availableRoles: "Cargos disponíveis"
|
||||
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
|
||||
postForm: "Campo de postagem"
|
||||
information: "Informações"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_accountSettings:
|
||||
@@ -2598,10 +2599,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação."
|
||||
_plugin:
|
||||
title: "Deseja instalar esse plugin?"
|
||||
metaTitle: "Informações do plugin"
|
||||
_theme:
|
||||
title: "Deseja instalar esse tema?"
|
||||
metaTitle: "Informações do tema"
|
||||
_meta:
|
||||
base: "Paleta de cores base"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -646,6 +646,7 @@ show: "Arată"
|
||||
icon: "Avatar"
|
||||
replies: "Răspunde"
|
||||
renotes: "Re-notează"
|
||||
information: "Despre"
|
||||
_delivery:
|
||||
stop: "Suspendat"
|
||||
_type:
|
||||
|
||||
@@ -1182,6 +1182,7 @@ alwaysConfirmFollow: "Всегда подтверждать подписку"
|
||||
inquiry: "Связаться"
|
||||
messageToFollower: "Сообщение подписчикам"
|
||||
postForm: "Форма отправки"
|
||||
information: "Описание"
|
||||
_settings:
|
||||
webhook: "Вебхук"
|
||||
_delivery:
|
||||
|
||||
@@ -918,6 +918,7 @@ sourceCode: "Zdrojový kód"
|
||||
flip: "Preklopiť"
|
||||
lastNDays: "Posledných {n} dní"
|
||||
postForm: "Napísať poznámku"
|
||||
information: "Informácie"
|
||||
_delivery:
|
||||
stop: "Zmrazené"
|
||||
_type:
|
||||
|
||||
@@ -562,6 +562,7 @@ inquiry: "Kontakt"
|
||||
tryAgain: "Försök igen senare"
|
||||
signinWithPasskey: "Logga in med nyckel"
|
||||
unknownWebAuthnKey: "Okänd nyckel"
|
||||
information: "Om"
|
||||
_delivery:
|
||||
stop: "Suspenderad"
|
||||
_type:
|
||||
|
||||
@@ -1293,6 +1293,7 @@ prohibitedWordsForNameOfUserDescription: "หากมีสตริงใด
|
||||
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
|
||||
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
|
||||
postForm: "แบบฟอร์มการโพสต์"
|
||||
information: "เกี่ยวกับ"
|
||||
_settings:
|
||||
webhook: "Webhook"
|
||||
_abuseUserReport:
|
||||
@@ -2572,10 +2573,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง"
|
||||
_plugin:
|
||||
title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?"
|
||||
metaTitle: "ข้อมูลส่วนเสริม"
|
||||
_theme:
|
||||
title: "ต้องการติดตั้งธีมนี้ใช่ไหม?"
|
||||
metaTitle: "ข้อมูลธีม"
|
||||
_meta:
|
||||
base: "โทนสีพื้นฐาน"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -910,6 +910,7 @@ sourceCode: "Вихідний код"
|
||||
flip: "Перевернути"
|
||||
lastNDays: "Останні {n} днів"
|
||||
postForm: "Створення нотатки"
|
||||
information: "Інформація"
|
||||
_delivery:
|
||||
stop: "Призупинено"
|
||||
_type:
|
||||
|
||||
@@ -841,6 +841,7 @@ icon: "Avatar"
|
||||
replies: "Javob berish"
|
||||
renotes: "Qayta qayd etish"
|
||||
flip: "Teskari"
|
||||
information: "Haqida"
|
||||
_delivery:
|
||||
stop: "To'xtatilgan"
|
||||
_type:
|
||||
|
||||
@@ -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"
|
||||
surrender: "Từ chối"
|
||||
postForm: "Mẫu đăng"
|
||||
information: "Giới thiệu"
|
||||
_delivery:
|
||||
stop: "Đã vô hiệu hóa"
|
||||
_type:
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
|
||||
makeActive: "启用"
|
||||
display: "显示"
|
||||
copy: "复制"
|
||||
copiedToClipboard: "已复制到剪贴板"
|
||||
metrics: "指标"
|
||||
overview: "概览"
|
||||
logs: "日志"
|
||||
@@ -746,7 +747,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
|
||||
public: "公开"
|
||||
private: "私密"
|
||||
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
|
||||
manageAccessTokens: "管理 Access Tokens"
|
||||
manageAccessTokens: "管理访问令牌"
|
||||
accountInfo: "账户信息"
|
||||
notesCount: "帖子数量"
|
||||
repliesCount: "回复数量"
|
||||
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消同步"
|
||||
paste: "粘贴"
|
||||
emojiPalette: "表情符号调色板"
|
||||
postForm: "投稿窗口"
|
||||
textCount: "字数"
|
||||
information: "关于"
|
||||
_emojiPalette:
|
||||
palettes: "调色板"
|
||||
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
|
||||
@@ -1339,13 +1342,24 @@ _emojiPalette:
|
||||
paletteForReaction: "回应用调色板"
|
||||
_settings:
|
||||
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
|
||||
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
|
||||
notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnection: "连接服务"
|
||||
serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
|
||||
accountData: "账户数据"
|
||||
accountDataBanner: "可在此导入或导出帐户数据的存档。"
|
||||
muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
|
||||
accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
|
||||
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
|
||||
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
|
||||
preferencesBanner: "可在此设置客户端的整体运作行为。"
|
||||
appearanceBanner: "可在此设置客户端的外观及显示方式。"
|
||||
soundsBanner: "可在此设置客户端播放的声音。"
|
||||
timelineAndNote: "时间线和帖子"
|
||||
makeEveryTextElementsSelectable: "使所有的文字均可选择"
|
||||
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
|
||||
_preferencesProfile:
|
||||
profileName: "配置名"
|
||||
profileNameDescription: "请指定用于识别此设备的名称"
|
||||
@@ -2510,6 +2524,7 @@ _notification:
|
||||
achievementEarned: "取得的成就"
|
||||
exportCompleted: "已完成导出"
|
||||
login: "登录"
|
||||
createToken: "创建访问令牌"
|
||||
test: "测试通知"
|
||||
app: "关联应用的通知"
|
||||
_actions:
|
||||
@@ -2659,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "请在安装前确保来源可靠"
|
||||
_plugin:
|
||||
title: "要安装此插件吗?"
|
||||
metaTitle: "插件信息"
|
||||
_theme:
|
||||
title: "要安装此主题吗?"
|
||||
metaTitle: "主题信息"
|
||||
_meta:
|
||||
base: "基本配色方案"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話"
|
||||
makeActive: "啟用"
|
||||
display: "檢視"
|
||||
copy: "複製"
|
||||
copiedToClipboard: "已複製到剪貼簿"
|
||||
metrics: "指標"
|
||||
overview: "概覽"
|
||||
logs: "日誌"
|
||||
@@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消啟用同步"
|
||||
paste: "貼上"
|
||||
emojiPalette: "表情符號調色盤"
|
||||
postForm: "發文視窗"
|
||||
textCount: "字數"
|
||||
information: "關於"
|
||||
_emojiPalette:
|
||||
palettes: "調色盤"
|
||||
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
|
||||
@@ -1355,6 +1358,8 @@ _settings:
|
||||
appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。"
|
||||
soundsBanner: "您可以調整用戶端播放的聲音設定。"
|
||||
timelineAndNote: "時間軸及貼文"
|
||||
makeEveryTextElementsSelectable: "允許選取所有文字"
|
||||
makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
|
||||
_preferencesProfile:
|
||||
profileName: "設定檔案名稱"
|
||||
profileNameDescription: "設定一個名稱來識別此裝置。"
|
||||
@@ -2519,6 +2524,7 @@ _notification:
|
||||
achievementEarned: "獲得成就"
|
||||
exportCompleted: "已完成匯出。"
|
||||
login: "登入"
|
||||
createToken: "建立存取權杖"
|
||||
test: "通知測試"
|
||||
app: "應用程式通知"
|
||||
_actions:
|
||||
@@ -2668,10 +2674,8 @@ _externalResourceInstaller:
|
||||
checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。"
|
||||
_plugin:
|
||||
title: "要安裝此外掛嘛?"
|
||||
metaTitle: "外掛資訊"
|
||||
_theme:
|
||||
title: "要安裝此佈景主題嗎?"
|
||||
metaTitle: "佈景主題資訊"
|
||||
_meta:
|
||||
base: "基本配色方案"
|
||||
_vendorInfo:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.3.2-beta.1",
|
||||
"version": "2025.3.2-beta.6",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -69,9 +69,6 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "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",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
|
||||
@@ -7,42 +7,16 @@ import { Injectable } from '@nestjs/common';
|
||||
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { type AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { type Packed } from '@/misc/json-schema.js';
|
||||
import { type WebhookEventTypes } from '@/models/Webhook.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||
|
||||
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 {
|
||||
return {
|
||||
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 dummyUser2 = generateDummyUser({
|
||||
id: 'dummy-user-2',
|
||||
@@ -284,6 +140,7 @@ export class WebhookTestService {
|
||||
};
|
||||
|
||||
constructor(
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private userWebhookService: UserWebhookService,
|
||||
private systemWebhookService: SystemWebhookService,
|
||||
private queueService: QueueService,
|
||||
@@ -354,31 +211,31 @@ export class WebhookTestService {
|
||||
|
||||
switch (params.type) {
|
||||
case 'note': {
|
||||
send('note', { note: toPackedNote(dummyNote1) });
|
||||
send('note', { note: await this.toPackedNote(dummyNote1) });
|
||||
break;
|
||||
}
|
||||
case 'reply': {
|
||||
send('reply', { note: toPackedNote(dummyReply1) });
|
||||
send('reply', { note: await this.toPackedNote(dummyReply1) });
|
||||
break;
|
||||
}
|
||||
case 'renote': {
|
||||
send('renote', { note: toPackedNote(dummyRenote1) });
|
||||
send('renote', { note: await this.toPackedNote(dummyRenote1) });
|
||||
break;
|
||||
}
|
||||
case 'mention': {
|
||||
send('mention', { note: toPackedNote(dummyMention1) });
|
||||
send('mention', { note: await this.toPackedNote(dummyMention1) });
|
||||
break;
|
||||
}
|
||||
case 'follow': {
|
||||
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
|
||||
send('follow', { user: await this.toPackedUserDetailedNotMe(dummyUser1) });
|
||||
break;
|
||||
}
|
||||
case 'followed': {
|
||||
send('followed', { user: toPackedUserLite(dummyUser2) });
|
||||
send('followed', { user: await this.toPackedUserLite(dummyUser2) });
|
||||
break;
|
||||
}
|
||||
case 'unfollow': {
|
||||
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
|
||||
send('unfollow', { user: await this.toPackedUserDetailedNotMe(dummyUser3) });
|
||||
break;
|
||||
}
|
||||
// まだ実装されていない (#9485)
|
||||
@@ -427,7 +284,7 @@ export class WebhookTestService {
|
||||
|
||||
switch (params.type) {
|
||||
case 'abuseReport': {
|
||||
send('abuseReport', generateAbuseReport({
|
||||
send('abuseReport', await this.generateAbuseReport({
|
||||
targetUserId: dummyUser1.id,
|
||||
targetUser: dummyUser1,
|
||||
reporterId: dummyUser2.id,
|
||||
@@ -436,7 +293,7 @@ export class WebhookTestService {
|
||||
break;
|
||||
}
|
||||
case 'abuseReportResolved': {
|
||||
send('abuseReportResolved', generateAbuseReport({
|
||||
send('abuseReportResolved', await this.generateAbuseReport({
|
||||
targetUserId: dummyUser1.id,
|
||||
targetUser: dummyUser1,
|
||||
reporterId: dummyUser2.id,
|
||||
@@ -448,7 +305,7 @@ export class WebhookTestService {
|
||||
break;
|
||||
}
|
||||
case 'userCreated': {
|
||||
send('userCreated', toPackedUserLite(dummyUser1));
|
||||
send('userCreated', await this.toPackedUserLite(dummyUser1));
|
||||
break;
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ export interface Schema extends OfSchema {
|
||||
readonly maximum?: number;
|
||||
readonly minimum?: number;
|
||||
readonly pattern?: string;
|
||||
readonly additionalProperties?: Schema | boolean;
|
||||
}
|
||||
|
||||
type RequiredPropertyNames<s extends Obj> = {
|
||||
@@ -217,6 +218,13 @@ type ObjectSchemaTypeDef<p extends Schema> =
|
||||
:
|
||||
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
|
||||
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;
|
||||
|
||||
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ZipReader } from 'slacc';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
@@ -86,6 +87,7 @@ export class ImportCustomEmojisProcessorService {
|
||||
const emojiPath = outputPath + '/' + record.fileName;
|
||||
await this.emojisRepository.delete({
|
||||
name: emojiInfo.name,
|
||||
host: IsNull(),
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -13,7 +13,7 @@ import accepts from 'accepts';
|
||||
import vary from 'vary';
|
||||
import secureJson from 'secure-json-parse';
|
||||
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 type { Config } from '@/config.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
@@ -42,6 +42,9 @@ export class ActivityPubServerService {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@@ -102,6 +105,11 @@ export class ActivityPubServerService {
|
||||
|
||||
@bindThis
|
||||
private inbox(request: FastifyRequest, reply: FastifyReply) {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
let signature;
|
||||
|
||||
try {
|
||||
@@ -173,6 +181,11 @@ export class ActivityPubServerService {
|
||||
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = request.params.user;
|
||||
|
||||
const cursor = request.query.cursor;
|
||||
@@ -265,6 +278,11 @@ export class ActivityPubServerService {
|
||||
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = request.params.user;
|
||||
|
||||
const cursor = request.query.cursor;
|
||||
@@ -354,6 +372,11 @@ export class ActivityPubServerService {
|
||||
|
||||
@bindThis
|
||||
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 user = await this.usersRepository.findOneBy({
|
||||
@@ -398,6 +421,11 @@ export class ActivityPubServerService {
|
||||
}>,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = request.params.user;
|
||||
|
||||
const sinceId = request.query.since_id;
|
||||
@@ -482,6 +510,11 @@ export class ActivityPubServerService {
|
||||
|
||||
@bindThis
|
||||
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
reply.code(404);
|
||||
return;
|
||||
@@ -564,6 +597,11 @@ export class ActivityPubServerService {
|
||||
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await this.notesRepository.findOneBy({
|
||||
id: request.params.note,
|
||||
visibility: In(['public', 'home']),
|
||||
@@ -594,6 +632,11 @@ export class ActivityPubServerService {
|
||||
fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => {
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await this.notesRepository.findOneBy({
|
||||
id: request.params.note,
|
||||
userHost: IsNull(),
|
||||
@@ -634,6 +677,11 @@ export class ActivityPubServerService {
|
||||
|
||||
// publickey
|
||||
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 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) => {
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = request.params.user;
|
||||
|
||||
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) => {
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const acct = Acct.parse(request.params.acct);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
@@ -688,6 +746,11 @@ export class ActivityPubServerService {
|
||||
|
||||
// emoji
|
||||
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({
|
||||
host: IsNull(),
|
||||
name: request.params.emoji,
|
||||
@@ -705,6 +768,11 @@ export class ActivityPubServerService {
|
||||
|
||||
// like
|
||||
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 });
|
||||
|
||||
if (reaction == null) {
|
||||
@@ -726,6 +794,11 @@ export class ActivityPubServerService {
|
||||
|
||||
// follow
|
||||
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
|
||||
// check if the following exists.
|
||||
|
||||
@@ -752,6 +825,11 @@ export class ActivityPubServerService {
|
||||
|
||||
// follow
|
||||
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
|
||||
// check if the following exists and only check if the follow request exists.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IsNull } from 'typeorm';
|
||||
import vary from 'vary';
|
||||
import fastifyAccepts from '@fastify/accepts';
|
||||
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 { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
@@ -26,6 +26,9 @@ export class WellKnownServerService {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@@ -66,6 +69,11 @@ export class WellKnownServerService {
|
||||
});
|
||||
|
||||
fastify.get('/.well-known/host-meta', async (request, reply) => {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
reply.header('Content-Type', xrd);
|
||||
return XRD({ element: 'Link', attributes: {
|
||||
rel: 'lrdd',
|
||||
@@ -75,6 +83,11 @@ export class WellKnownServerService {
|
||||
});
|
||||
|
||||
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');
|
||||
return {
|
||||
links: [{
|
||||
@@ -86,6 +99,11 @@ export class WellKnownServerService {
|
||||
});
|
||||
|
||||
fastify.get('/.well-known/nodeinfo', async (request, reply) => {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (this.meta.federation === 'none') {
|
||||
reply.code(403);
|
||||
return;
|
||||
}
|
||||
|
||||
const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({
|
||||
id,
|
||||
host: IsNull(),
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import cors from '@fastify/cors';
|
||||
import multipart from '@fastify/multipart';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -57,8 +56,6 @@ export class ApiServerService {
|
||||
},
|
||||
});
|
||||
|
||||
fastify.register(fastifyCookie, {});
|
||||
|
||||
// Prevent cache
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
|
||||
@@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
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 sharp from 'sharp';
|
||||
import pug from 'pug';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import fastifyView from '@fastify/view';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import fastifyProxy from '@fastify/http-proxy';
|
||||
import vary from 'vary';
|
||||
import htmlSafeJsonStringify from 'htmlescape';
|
||||
@@ -221,64 +217,6 @@ export class ClientServerService {
|
||||
|
||||
@bindThis
|
||||
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, {
|
||||
root: _dirname + '/views',
|
||||
engine: {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
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 * as misskey from 'misskey-js';
|
||||
|
||||
@@ -156,20 +156,20 @@ describe('Webリソース', () => {
|
||||
|
||||
describe(' has entry such ', () => {
|
||||
beforeEach(() => {
|
||||
post(alice, { text: "**a**" })
|
||||
post(alice, { text: '**a**' });
|
||||
});
|
||||
|
||||
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;
|
||||
// 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**")) {
|
||||
throw new Error("MFM shouldn't be included");
|
||||
if (body.includes('**a**')) {
|
||||
throw new Error('MFM shouldn\'t be included');
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
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 }) => {
|
||||
test('はGETできない。', async () => await notOk({
|
||||
path,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
|
||||
describe('WebhookTestService', () => {
|
||||
let app: TestingModule;
|
||||
@@ -56,6 +57,11 @@ describe('WebhookTestService', () => {
|
||||
providers: [
|
||||
WebhookTestService,
|
||||
IdService,
|
||||
{
|
||||
provide: CustomEmojiService, useFactory: () => ({
|
||||
populateEmojis: jest.fn(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
provide: QueueService, useFactory: () => ({
|
||||
systemWebhookDeliver: jest.fn(),
|
||||
|
||||
@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
|
||||
createdAt: string;
|
||||
type: string;
|
||||
body: any;
|
||||
}
|
||||
};
|
||||
|
||||
const config = loadConfig();
|
||||
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_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']> = {
|
||||
endpoint: E,
|
||||
parameters: P,
|
||||
|
||||
@@ -33,13 +33,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
|
||||
Math.min(navigator.hardwareConcurrency - 1, 4),
|
||||
);
|
||||
resolve(workers);
|
||||
if (_DEV_) console.log('WebGL2 in worker is supported!');
|
||||
} else {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
resolve(canvas);
|
||||
if (_DEV_) console.log('WebGL2 in worker is not supported...');
|
||||
}
|
||||
testWorker.terminate();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div>
|
||||
<div class="_fullinfo">
|
||||
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||
<img :src="notFoundImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
|
||||
|
||||
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>
|
||||
|
||||
@@ -12,6 +12,7 @@ const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site
|
||||
export const host = address.host;
|
||||
export const hostname = address.hostname;
|
||||
export const url = address.origin;
|
||||
export const port = address.port;
|
||||
export const apiUrl = location.origin + '/api';
|
||||
export const wsOrigin = location.origin;
|
||||
export const lang = localStorage.getItem('lang') ?? 'en-US';
|
||||
|
||||
@@ -64,12 +64,12 @@ initialize({
|
||||
initLocalStorage();
|
||||
queueMicrotask(() => {
|
||||
Promise.all([
|
||||
import('../src/components'),
|
||||
import('../src/directives'),
|
||||
import('../src/widgets'),
|
||||
import('../src/theme'),
|
||||
import('../src/preferences'),
|
||||
import('../src/os'),
|
||||
import('../src/components/index.js'),
|
||||
import('../src/directives/index.js'),
|
||||
import('../src/widgets/index.js'),
|
||||
import('../src/theme.js'),
|
||||
import('../src/preferences.js'),
|
||||
import('../src/os.js'),
|
||||
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
|
||||
setup((app) => {
|
||||
moduleInitialized = true;
|
||||
|
||||
@@ -50,9 +50,36 @@ export default [
|
||||
// defineExposeが誤検知されてしまう
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||
'id-denylist': ['error', 'window', 'e'],
|
||||
// window ... グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||
// e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||
// 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'],
|
||||
'vue/attributes-order': ['error', {
|
||||
alphabetical: false,
|
||||
|
||||
@@ -12,7 +12,7 @@ import '@/style.scss';
|
||||
import { mainBoot } from '@/boot/main-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 + '/'))) {
|
||||
subBoot();
|
||||
|
||||
@@ -26,8 +26,6 @@ import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { analytics, initAnalytics } from '@/analytics.js';
|
||||
import { miLocalStorage } from '@/local-storage.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 { $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
|
||||
if ($i && $i.token) {
|
||||
if (_DEV_) {
|
||||
@@ -263,8 +265,6 @@ export async function common(createVue: () => App<Element>) {
|
||||
|
||||
const app = createVue();
|
||||
|
||||
setupRouter(app, createMainRouter);
|
||||
|
||||
if (_DEV_) {
|
||||
app.config.performance = true;
|
||||
}
|
||||
@@ -300,6 +300,7 @@ export async function common(createVue: () => App<Element>) {
|
||||
removeSplash();
|
||||
|
||||
//#region Self-XSS 対策メッセージ
|
||||
if (!_DEV_) {
|
||||
console.log(
|
||||
`%c${i18n.ts._selfXssPrevention.warning}`,
|
||||
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
|
||||
@@ -318,6 +319,7 @@ export async function common(createVue: () => App<Element>) {
|
||||
'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/' }));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { miLocalStorage } from '@/local-storage.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
|
||||
import { initializeSw } from '@/utility/initialize-sw.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 { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.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.notification', store.s.sound_notification 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(() => {
|
||||
unisonReload();
|
||||
|
||||
@@ -88,9 +88,9 @@ import { i18n } from '@/i18n.js';
|
||||
import { dateString } from '@/filters/date.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import RouterView from '@/components/global/RouterView.vue';
|
||||
import { useRouterFactory } from '@/router/supplier';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { createRouter } from '@/router.js';
|
||||
|
||||
const props = defineProps<{
|
||||
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
|
||||
@@ -100,10 +100,9 @@ const emit = defineEmits<{
|
||||
(ev: 'resolved', reportId: string): void;
|
||||
}>();
|
||||
|
||||
const routerFactory = useRouterFactory();
|
||||
const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
|
||||
const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
|
||||
targetRouter.init();
|
||||
const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
|
||||
const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
|
||||
reporterRouter.init();
|
||||
|
||||
const moderationNote = ref(props.report.moderationNote ?? '');
|
||||
@@ -135,7 +134,7 @@ function forward() {
|
||||
|
||||
function showMenu(ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: 'Copy ID',
|
||||
action: () => {
|
||||
copyToClipboard(props.report.id);
|
||||
|
||||
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkWindow from '@/components/MkWindow.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
@@ -47,7 +47,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
|
||||
const uiWindow = useTemplateRef('uiWindow');
|
||||
const comment = ref(props.initialComment ?? '');
|
||||
|
||||
function send() {
|
||||
|
||||
@@ -4,14 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, shallowRef } from 'vue';
|
||||
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
||||
import isChromatic from 'chromatic/isChromatic';
|
||||
|
||||
const canvasEl = shallowRef<HTMLCanvasElement>();
|
||||
const canvasEl = useTemplateRef('canvasEl');
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
scale?: number;
|
||||
|
||||
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import { onMounted, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -37,8 +37,8 @@ const props = withDefaults(defineProps<{
|
||||
}>(), {
|
||||
});
|
||||
|
||||
const rootEl = shallowRef<HTMLDivElement>();
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
async function ok() {
|
||||
if (props.announcement.needConfirmationToRead) {
|
||||
|
||||
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef } from 'vue';
|
||||
import { useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import XAntennaEditor from '@/components/MkAntennaEditor.vue';
|
||||
@@ -40,7 +40,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void,
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
|
||||
emit('created', newAntenna);
|
||||
|
||||
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 { emojilist, getEmojiName } from '@@/js/emojilist.js';
|
||||
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
|
||||
@@ -139,7 +139,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const suggests = ref<Element>();
|
||||
const rootEl = shallowRef<HTMLDivElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
const fetching = ref(true);
|
||||
const users = ref<any[]>([]);
|
||||
|
||||
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, shallowRef } from 'vue';
|
||||
import { nextTick, onMounted, useTemplateRef } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
@@ -64,8 +64,8 @@ const emit = defineEmits<{
|
||||
(ev: 'click', payload: MouseEvent): void;
|
||||
}>();
|
||||
|
||||
const el = shallowRef<HTMLElement | null>(null);
|
||||
const ripples = shallowRef<HTMLElement | null>(null);
|
||||
const el = useTemplateRef('el');
|
||||
const ripples = useTemplateRef('ripples');
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autofocus) {
|
||||
|
||||
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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';
|
||||
|
||||
// APIs provided by Captcha services
|
||||
@@ -69,7 +69,7 @@ const emit = defineEmits<{
|
||||
|
||||
const available = ref(false);
|
||||
|
||||
const captchaEl = shallowRef<HTMLDivElement | undefined>();
|
||||
const captchaEl = useTemplateRef('captchaEl');
|
||||
const captchaWidgetId = ref<string | undefined>(undefined);
|
||||
const testcaptchaInput = ref('');
|
||||
const testcaptchaPassed = ref(false);
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.notFound }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
|
||||
@@ -45,12 +45,8 @@ export type ChartSrc =
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/* eslint-disable id-denylist --
|
||||
Chart.js has a `data` attribute in most chart definitions, which triggers the
|
||||
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 { onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||
@@ -96,7 +92,7 @@ const props = withDefaults(defineProps<{
|
||||
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 negate = arr => arr.map(x => -x);
|
||||
@@ -134,7 +130,7 @@ let chartData: {
|
||||
bytes?: boolean;
|
||||
} | null = null;
|
||||
|
||||
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
|
||||
const chartEl = useTemplateRef('chartEl');
|
||||
const fetching = ref(true);
|
||||
|
||||
const getDate = (ago: number) => {
|
||||
@@ -849,7 +845,7 @@ watch(() => [props.src, props.span], fetchAndRender);
|
||||
onMounted(() => {
|
||||
fetchAndRender();
|
||||
});
|
||||
/* eslint-enable id-denylist */
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
@@ -48,7 +48,6 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
|
||||
|
||||
function copy() {
|
||||
copyToClipboard(props.code);
|
||||
os.success();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -61,7 +61,7 @@ const { modelValue } = toRefs(props);
|
||||
const v = ref<string>(modelValue.value ?? '');
|
||||
const focused = ref(false);
|
||||
const changed = ref(false);
|
||||
const inputEl = shallowRef<HTMLTextAreaElement>();
|
||||
const inputEl = useTemplateRef('inputEl');
|
||||
|
||||
const focus = () => inputEl.value?.focus();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, toRefs } from 'vue';
|
||||
import { ref, useTemplateRef, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | null;
|
||||
@@ -39,7 +39,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { modelValue } = toRefs(props);
|
||||
const v = ref(modelValue.value);
|
||||
const inputEl = shallowRef<HTMLElement>();
|
||||
const inputEl = useTemplateRef('inputEl');
|
||||
|
||||
const onInput = () => {
|
||||
emit('update:modelValue', v.value ?? '');
|
||||
|
||||
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 { i18n } from '@/i18n.js';
|
||||
|
||||
@@ -58,9 +58,9 @@ const props = withDefaults(defineProps<{
|
||||
maxHeight: null,
|
||||
});
|
||||
|
||||
const rootEl = shallowRef<HTMLElement>();
|
||||
const contentEl = shallowRef<HTMLElement>();
|
||||
const headerEl = shallowRef<HTMLElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const contentEl = useTemplateRef('contentEl');
|
||||
const headerEl = useTemplateRef('headerEl');
|
||||
const showBody = ref(props.expanded);
|
||||
const ignoreOmit = ref(false);
|
||||
const omitted = ref(false);
|
||||
|
||||
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
|
||||
import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
|
||||
import MkMenu from './MkMenu.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import contains from '@/utility/contains.js';
|
||||
@@ -34,7 +34,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const rootEl = shallowRef<HTMLDivElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
const zIndex = ref<number>(os.claimZIndex('high'));
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, shallowRef, ref } from 'vue';
|
||||
import { onMounted, useTemplateRef, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import Cropper from 'cropperjs';
|
||||
import tinycolor from 'tinycolor2';
|
||||
@@ -56,8 +56,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
|
||||
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const imgEl = shallowRef<HTMLImageElement>();
|
||||
const dialogEl = useTemplateRef('dialogEl');
|
||||
const imgEl = useTemplateRef('imgEl');
|
||||
let cropper: Cropper | null = null;
|
||||
const loading = ref(true);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { shallowRef } from 'vue';
|
||||
import { useTemplateRef } from 'vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
@@ -73,7 +73,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialogEl = useTemplateRef('dialogEl');
|
||||
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
|
||||
@@ -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>
|
||||
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||
</div>
|
||||
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
|
||||
<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
|
||||
<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">
|
||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||
<template #caption>
|
||||
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, computed } from 'vue';
|
||||
import { ref, useTemplateRef, computed } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
@@ -117,7 +117,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
const inputValue = ref<string | number | null>(props.input?.default ?? null);
|
||||
const selectedValue = ref(props.select?.default ?? null);
|
||||
|
||||
@@ -47,7 +47,7 @@ import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
}];
|
||||
if (prefer.s.devMode) {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFolderId,
|
||||
action: () => {
|
||||
copyToClipboard(props.folder.id);
|
||||
|
||||
@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 MkButton from './MkButton.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
@@ -129,8 +129,8 @@ const emit = defineEmits<{
|
||||
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
||||
}>();
|
||||
|
||||
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
|
||||
const fileInput = shallowRef<HTMLInputElement>();
|
||||
const loadMoreFiles = useTemplateRef('loadMoreFiles');
|
||||
const fileInput = useTemplateRef('fileInput');
|
||||
|
||||
const folder = ref<Misskey.entities.DriveFolder | null>(null);
|
||||
const files = ref<Misskey.entities.DriveFile[]>([]);
|
||||
|
||||
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XDrive from '@/components/MkDrive.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
@@ -43,7 +43,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 { embedRouteWithScrollbar } from '@@/js/embed-page.js';
|
||||
import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
|
||||
@@ -121,7 +121,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
//#region Modalの制御
|
||||
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialogEl = useTemplateRef('dialogEl');
|
||||
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
@@ -194,14 +194,13 @@ function generate() {
|
||||
|
||||
function doCopy() {
|
||||
copyToClipboard(result.value);
|
||||
os.success();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region プレビューのリサイズ
|
||||
const resizerRootEl = shallowRef<HTMLDivElement>();
|
||||
const resizerRootEl = useTemplateRef('resizerRootEl');
|
||||
const iframeLoading = ref(true);
|
||||
const iframeEl = shallowRef<HTMLIFrameElement>();
|
||||
const iframeEl = useTemplateRef('iframeEl');
|
||||
const iframeHeight = ref(0);
|
||||
const iframeScale = ref(1);
|
||||
const iframeStyle = computed(() => {
|
||||
|
||||
@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
emojilist,
|
||||
@@ -157,8 +157,8 @@ const emit = defineEmits<{
|
||||
(ev: 'esc'): void;
|
||||
}>();
|
||||
|
||||
const searchEl = shallowRef<HTMLInputElement>();
|
||||
const emojisEl = shallowRef<HTMLDivElement>();
|
||||
const searchEl = useTemplateRef('searchEl');
|
||||
const emojisEl = useTemplateRef('emojisEl');
|
||||
|
||||
const {
|
||||
emojiPickerScale,
|
||||
|
||||
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { shallowRef } from 'vue';
|
||||
import { useTemplateRef } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
@@ -64,8 +64,8 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
|
||||
const modal = useTemplateRef('modal');
|
||||
const picker = useTemplateRef('picker');
|
||||
|
||||
function chosen(emoji: string) {
|
||||
emit('done', emoji);
|
||||
|
||||
@@ -11,11 +11,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<!-- 拡張用? -->
|
||||
<i v-else class="ti ti-download"></i>
|
||||
</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>
|
||||
|
||||
<h2 v-if="isPlugin" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._plugin.title }}</h2>
|
||||
<h2 v-else-if="isTheme" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._theme.title }}</h2>
|
||||
|
||||
<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>
|
||||
@@ -27,15 +33,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #value>{{ extension.meta.author }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
<MkKeyValue v-if="isPlugin">
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.description }}</template>
|
||||
<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="isPlugin">
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.version }}</template>
|
||||
<template #value>{{ extension.meta.version }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="isPlugin">
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.permission }}</template>
|
||||
<template #value>
|
||||
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
|
||||
@@ -44,21 +50,52 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<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>
|
||||
</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>
|
||||
</FormSection>
|
||||
<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"/>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
@@ -105,6 +142,7 @@ const props = defineProps<{
|
||||
|
||||
const emits = defineEmits<{
|
||||
(ev: 'confirm'): void;
|
||||
(ev: 'cancel'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -112,13 +150,13 @@ const emits = defineEmits<{
|
||||
.extInstallerRoot {
|
||||
border-radius: var(--MI-radius);
|
||||
background: var(--MI_THEME-panel);
|
||||
padding: 1.5rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.extInstallerIconWrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
line-height: 48px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
@@ -135,10 +173,6 @@ const emits = defineEmits<{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.extInstallerNormDesc {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.extInstallerKVList {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef, ref } from 'vue';
|
||||
import { useTemplateRef, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
@@ -42,7 +42,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
const caption = ref(props.default);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 { prefer } from '@/preferences.js';
|
||||
import { getBgColor } from '@/utility/get-bg-color.js';
|
||||
@@ -46,7 +46,7 @@ const props = withDefaults(defineProps<{
|
||||
persistKey: null,
|
||||
});
|
||||
|
||||
const rootEl = shallowRef<HTMLElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const parentBg = ref<string | null>(null);
|
||||
// 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);
|
||||
|
||||
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, shallowRef } from 'vue';
|
||||
import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getBgColor } from '@/utility/get-bg-color.js';
|
||||
|
||||
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
|
||||
spacerMax: 22,
|
||||
});
|
||||
|
||||
const rootEl = shallowRef<HTMLElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const bgSame = ref(false);
|
||||
const opened = ref(props.defaultOpen);
|
||||
const openedAtLeastOnce = ref(props.defaultOpen);
|
||||
|
||||
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, shallowRef } from 'vue';
|
||||
import { reactive, useTemplateRef } from 'vue';
|
||||
import MkInput from './MkInput.vue';
|
||||
import MkTextarea from './MkTextarea.vue';
|
||||
import MkSwitch from './MkSwitch.vue';
|
||||
@@ -99,7 +99,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const values = reactive({});
|
||||
|
||||
for (const item in props.form) {
|
||||
|
||||
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<{
|
||||
label: '',
|
||||
});
|
||||
|
||||
const rootEl = shallowRef<HTMLDivElement | null>(null);
|
||||
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const chartEl = useTemplateRef('chartEl');
|
||||
const now = new Date();
|
||||
let chartInstance: Chart | null = null;
|
||||
const fetching = ref(true);
|
||||
|
||||
@@ -10,19 +10,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@touchstart.passive="touchStart"
|
||||
@touchmove.passive="touchMove"
|
||||
@touchend.passive="touchEnd"
|
||||
>
|
||||
<Transition
|
||||
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
||||
: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>
|
||||
</template>
|
||||
<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 { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const rootEl = shallowRef<HTMLDivElement>();
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
const tabModel = defineModel<string>('tab');
|
||||
|
||||
|
||||
@@ -14,8 +14,34 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:enterToClass="prefer.s.animation && props.transition?.enterToClass || 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"/>
|
||||
<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"/>
|
||||
<canvas
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
@@ -43,13 +69,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
|
||||
Math.min(navigator.hardwareConcurrency - 1, 4),
|
||||
);
|
||||
resolve(workers);
|
||||
if (_DEV_) console.log('WebGL2 in worker is supported!');
|
||||
} else {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
resolve(canvas);
|
||||
if (_DEV_) console.log('WebGL2 in worker is not supported...');
|
||||
}
|
||||
testWorker.terminate();
|
||||
});
|
||||
@@ -57,7 +81,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
|
||||
</script>
|
||||
|
||||
<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 { render } from 'buraha';
|
||||
import { prefer } from '@/preferences.js';
|
||||
@@ -94,9 +118,9 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const viewId = uuid();
|
||||
const canvas = shallowRef<HTMLCanvasElement>();
|
||||
const root = shallowRef<HTMLDivElement>();
|
||||
const img = shallowRef<HTMLImageElement>();
|
||||
const canvas = useTemplateRef('canvas');
|
||||
const root = useTemplateRef('root');
|
||||
const img = useTemplateRef('img');
|
||||
const loaded = ref(false);
|
||||
const canvasWidth = ref(64);
|
||||
const canvasHeight = ref(64);
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<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-else class="ti ti-info-circle" :class="$style.i"></i>
|
||||
<div><slot></slot></div>
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="_selectable">
|
||||
<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 ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
|
||||
@@ -44,14 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
|
||||
import type { InputHTMLAttributes } from 'vue';
|
||||
import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
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 { Autocomplete } from '@/utility/autocomplete.js';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | null;
|
||||
@@ -92,9 +92,9 @@ const focused = ref(false);
|
||||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const filled = computed(() => v.value !== '' && v.value != null);
|
||||
const inputEl = shallowRef<HTMLInputElement>();
|
||||
const prefixEl = shallowRef<HTMLElement>();
|
||||
const suffixEl = shallowRef<HTMLElement>();
|
||||
const inputEl = useTemplateRef('inputEl');
|
||||
const prefixEl = useTemplateRef('prefixEl');
|
||||
const suffixEl = useTemplateRef('suffixEl');
|
||||
const height =
|
||||
props.small ? 33 :
|
||||
props.large ? 39 :
|
||||
|
||||
@@ -84,8 +84,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, shallowRef } from 'vue';
|
||||
import { onMounted, ref, computed, useTemplateRef } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkChart from '@/components/MkChart.vue';
|
||||
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 { i18n } from '@/i18n.js';
|
||||
import MkHeatmap from '@/components/MkHeatmap.vue';
|
||||
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
|
||||
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
|
||||
@@ -109,8 +109,8 @@ const chartLimit = 500;
|
||||
const chartSpan = ref<'hour' | 'day'>('hour');
|
||||
const chartSrc = ref('active-users');
|
||||
const heatmapSrc = ref<HeatmapSource>('active-users');
|
||||
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||
const subDoughnutEl = useTemplateRef('subDoughnutEl');
|
||||
const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
|
||||
|
||||
const { handler: externalTooltipHandler1 } = useChartTooltip({
|
||||
position: 'middle',
|
||||
|
||||
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.items">
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
|
||||
<div>{{ invite.code }}</div>
|
||||
<div class="_selectableAtomic">{{ invite.code }}</div>
|
||||
</div>
|
||||
<div v-if="moderator">
|
||||
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
|
||||
@@ -90,7 +90,6 @@ function deleteCode() {
|
||||
|
||||
function copyInviteCode() {
|
||||
copyToClipboard(props.invite.code);
|
||||
os.success();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.key">
|
||||
<slot name="key"></slot>
|
||||
</div>
|
||||
<div :class="$style.value">
|
||||
<div :class="$style.value" class="_selectable">
|
||||
<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>
|
||||
</div>
|
||||
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
const copy_ = () => {
|
||||
copyToClipboard(props.copy);
|
||||
os.success();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef } from 'vue';
|
||||
import { useTemplateRef } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
@@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
|
||||
deviceKind === 'smartphone' ? 'drawer' :
|
||||
'dialog';
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
const modal = useTemplateRef('modal');
|
||||
|
||||
const menu = prefer.s.menu;
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 type { MenuItem } from '@/types/menu.js';
|
||||
import type { Keymap } from '@/utility/hotkey.js';
|
||||
@@ -151,8 +151,8 @@ function hasFocus() {
|
||||
return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
|
||||
}
|
||||
|
||||
const playerEl = shallowRef<HTMLDivElement>();
|
||||
const audioEl = shallowRef<HTMLAudioElement>();
|
||||
const playerEl = useTemplateRef('playerEl');
|
||||
const audioEl = useTemplateRef('audioEl');
|
||||
|
||||
// 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'));
|
||||
@@ -242,7 +242,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menu.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.audio.id);
|
||||
|
||||
@@ -168,7 +168,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.image.id);
|
||||
|
||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||
import PhotoSwipe from 'photoswipe';
|
||||
@@ -46,7 +46,7 @@ const props = defineProps<{
|
||||
raw?: boolean;
|
||||
}>();
|
||||
|
||||
const gallery = shallowRef<HTMLDivElement>();
|
||||
const gallery = useTemplateRef('gallery');
|
||||
const pswpZIndex = os.claimZIndex('middle');
|
||||
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
|
||||
const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
|
||||
|
||||
@@ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 type { MenuItem } from '@/types/menu.js';
|
||||
import type { Keymap } from '@/utility/hotkey.js';
|
||||
@@ -267,7 +267,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menu.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.video.id);
|
||||
@@ -299,8 +299,8 @@ async function toggleSensitive(file: Misskey.entities.DriveFile) {
|
||||
}
|
||||
|
||||
// MediaControl: Video State
|
||||
const videoEl = shallowRef<HTMLVideoElement>();
|
||||
const playerEl = shallowRef<HTMLDivElement>();
|
||||
const videoEl = useTemplateRef('videoEl');
|
||||
const playerEl = useTemplateRef('playerEl');
|
||||
const isHoverring = ref(false);
|
||||
const controlsShowing = computed(() => {
|
||||
if (!oncePlayed.value) return true;
|
||||
|
||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 type { MenuItem } from '@/types/menu.js';
|
||||
|
||||
@@ -28,7 +28,7 @@ const emit = defineEmits<{
|
||||
|
||||
provide('isNestingMenu', true);
|
||||
|
||||
const el = shallowRef<HTMLElement>();
|
||||
const el = useTemplateRef('el');
|
||||
const align = 'left';
|
||||
|
||||
const SCROLLBAR_THICKNESS = 16;
|
||||
|
||||
@@ -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><MkEllipsis/></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
|
||||
v-else-if="item.type === 'link'"
|
||||
role="menuitem"
|
||||
@@ -176,7 +179,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<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 { Keymap } from '@/utility/hotkey.js';
|
||||
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
||||
@@ -209,11 +212,11 @@ const big = isTouchUsing;
|
||||
|
||||
const isNestingMenu = inject<boolean>('isNestingMenu', false);
|
||||
|
||||
const itemsEl = shallowRef<HTMLElement>();
|
||||
const itemsEl = useTemplateRef('itemsEl');
|
||||
|
||||
const items2 = ref<InnerMenuItem[]>();
|
||||
|
||||
const child = shallowRef<InstanceType<typeof XChild>>();
|
||||
const child = useTemplateRef('child');
|
||||
|
||||
const keymap = {
|
||||
'up|k|shift+tab': {
|
||||
@@ -254,7 +257,7 @@ watch(() => props.items, () => {
|
||||
});
|
||||
|
||||
const childMenu = ref<MenuItem[] | null>();
|
||||
const childTarget = shallowRef<HTMLElement | null>();
|
||||
const childTarget = shallowRef<HTMLElement>();
|
||||
|
||||
function closeChild() {
|
||||
childMenu.value = null;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user