Compare commits

..

12 Commits

Author SHA1 Message Date
syuilo
76dc7affe0 wip 2025-03-14 20:29:39 +09:00
syuilo
1c9d9923f4 Merge branch 'develop' into view-transition 2025-03-14 19:56:13 +09:00
syuilo
c8db2043b5 wip 2025-03-14 19:55:45 +09:00
syuilo
f40c5f27dd wip 2025-03-14 19:53:06 +09:00
syuilo
386494dd6c Merge branch 'develop' into view-transition 2025-03-14 19:37:24 +09:00
syuilo
f5c946b44d Merge branch 'develop' into view-transition 2025-03-14 14:48:35 +09:00
syuilo
5fe23d3f69 Update RouterView.vue 2025-03-11 17:29:00 +09:00
syuilo
7d86efd087 Update notifications.vue 2025-03-11 17:26:16 +09:00
syuilo
361f810da8 Merge branch 'develop' into view-transition 2025-03-11 17:14:18 +09:00
syuilo
be16622de2 wip 2025-03-10 17:01:52 +09:00
syuilo
f930cd7842 Merge branch 'develop' into view-transition 2025-03-10 16:47:30 +09:00
syuilo
f1014bc7f7 wip 2025-03-10 15:14:21 +09:00
272 changed files with 2098 additions and 1938 deletions

View File

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

View File

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

View File

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

View File

@@ -698,7 +698,6 @@ 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"
@@ -1140,7 +1139,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: "Segueixes "
youFollowing: "Seguint"
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"
@@ -1191,7 +1190,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: "Impulsos"
renotes: "Impulsar"
loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada"
@@ -1327,18 +1326,7 @@ 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 "
_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."
@@ -1356,9 +1344,6 @@ _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."
@@ -2071,7 +2056,7 @@ _theme:
hashtag: "Etiqueta"
mention: "Menció"
mentionMe: "Mencions (jo)"
renote: "Impulsar"
renote: "Renotar"
modalBg: "Fons del modal"
divider: "Divisor"
scrollbarHandle: "Maneta de la barra de desplaçament"
@@ -2513,7 +2498,7 @@ _notification:
follow: "Segueix-me"
mention: "Menció"
reply: "Respostes"
renote: "Impulsos"
renote: "Impulsar"
quote: "Citar"
reaction: "Reaccions"
pollEnded: "Enquesta terminada"
@@ -2523,13 +2508,12 @@ _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: "Impulsar"
renote: "Impulsos"
_deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes"

View File

@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
copyRemoteLink: "Remote-Link kopieren"
copyRemoteLink: "Renote-Link kopieren"
copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate"
display: "Display"
copy: "Copy"
copiedToClipboard: "Copied to clipboard"
metrics: "Metrics"
overview: "Overview"
logs: "Logs"
@@ -1324,21 +1323,7 @@ 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"
_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."
@@ -1356,9 +1341,6 @@ _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."
@@ -2523,7 +2505,6 @@ _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:
@@ -2551,7 +2532,6 @@ _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"

20
locales/index.d.ts vendored
View File

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

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva"
display: "Visualizza"
copy: "Copia"
copiedToClipboard: "Copiato negli appunti"
metrics: "Statistiche"
overview: "Anteprima"
logs: "Log"
@@ -974,7 +973,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: "Si tratta di un profilo creato e gestito automaticamente dal sistema."
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo"
document: "Documentazione"
@@ -1324,21 +1323,7 @@ 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"
_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."
@@ -1356,9 +1341,6 @@ _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."
@@ -2523,7 +2505,6 @@ _notification:
achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata"
login: "Accessi"
createToken: "Creare un token di accesso"
test: "Notifiche di test"
app: "Notifiche da applicazioni"
_actions:
@@ -2551,7 +2532,6 @@ _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"

View File

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

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
makeActive: "启用"
display: "显示"
copy: "复制"
copiedToClipboard: "已复制到剪贴板"
metrics: "指标"
overview: "概览"
logs: "日志"
@@ -747,7 +746,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
public: "公开"
private: "私密"
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
manageAccessTokens: "管理访问令牌"
manageAccessTokens: "管理 Access Tokens"
accountInfo: "账户信息"
notesCount: "帖子数量"
repliesCount: "回复数量"
@@ -1333,7 +1332,6 @@ preferenceSyncConflictChoiceCancel: "取消同步"
paste: "粘贴"
emojiPalette: "表情符号调色板"
postForm: "投稿窗口"
textCount: "字数"
_emojiPalette:
palettes: "调色板"
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
@@ -1341,24 +1339,13 @@ _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: "请指定用于识别此设备的名称"
@@ -2523,7 +2510,6 @@ _notification:
achievementEarned: "取得的成就"
exportCompleted: "已完成导出"
login: "登录"
createToken: "创建访问令牌"
test: "测试通知"
app: "关联应用的通知"
_actions:

View File

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

View File

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

View File

@@ -69,6 +69,9 @@
"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",

View File

@@ -6,7 +6,6 @@
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';
@@ -87,7 +86,6 @@ export class ImportCustomEmojisProcessorService {
const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({
name: emojiInfo.name,
host: IsNull(),
});
try {

View File

@@ -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, MiMeta } from '@/models/_.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } 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,9 +42,6 @@ export class ActivityPubServerService {
@Inject(DI.config)
private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -105,11 +102,6 @@ export class ActivityPubServerService {
@bindThis
private inbox(request: FastifyRequest, reply: FastifyReply) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
let signature;
try {
@@ -181,11 +173,6 @@ 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;
@@ -278,11 +265,6 @@ 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;
@@ -372,11 +354,6 @@ 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({
@@ -421,11 +398,6 @@ 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;
@@ -510,11 +482,6 @@ 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;
@@ -597,11 +564,6 @@ 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']),
@@ -632,11 +594,6 @@ 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(),
@@ -677,11 +634,6 @@ 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({
@@ -709,11 +661,6 @@ 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({
@@ -727,11 +674,6 @@ 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({
@@ -746,11 +688,6 @@ 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,
@@ -768,11 +705,6 @@ 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) {
@@ -794,11 +726,6 @@ 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.
@@ -825,11 +752,6 @@ 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.

View File

@@ -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 { MiMeta, UsersRepository } from '@/models/_.js';
import type { 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,9 +26,6 @@ export class WellKnownServerService {
@Inject(DI.config)
private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -69,11 +66,6 @@ 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',
@@ -83,11 +75,6 @@ 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: [{
@@ -99,11 +86,6 @@ 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() };
});
@@ -117,11 +99,6 @@ 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(),

View File

@@ -6,6 +6,7 @@
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';
@@ -56,6 +57,8 @@ export class ApiServerService {
},
});
fastify.register(fastifyCookie, {});
// Prevent cache
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');

View File

@@ -7,12 +7,16 @@ 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';
@@ -217,6 +221,64 @@ 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: {

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
import { channel, clip, cookie, 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,6 +180,24 @@ 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,

View File

@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
createdAt: string;
type: string;
body: any;
};
}
const config = loadConfig();
export const port = config.port;
@@ -45,6 +45,10 @@ 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,

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<div class="_fullinfo">
<img :src="notFoundImageUrl" draggable="false"/>
<img :src="notFoundImageUrl" class="_ghost"/>
<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>

View File

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

View File

@@ -26,6 +26,8 @@ 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';
@@ -232,10 +234,6 @@ 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_) {
@@ -265,6 +263,8 @@ export async function common(createVue: () => App<Element>) {
const app = createVue();
setupRouter(app, createMainRouter);
if (_DEV_) {
app.config.performance = true;
}

View File

@@ -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.js';
import { mainRouter } from '@/router/main.js';
import { makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js';
@@ -201,8 +201,6 @@ 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();

View File

@@ -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,9 +100,10 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
const routerFactory = useRouterFactory();
const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
targetRouter.init();
const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? '');
@@ -134,7 +135,7 @@ function forward() {
function showMenu(ev: MouseEvent) {
os.popupMenu([{
icon: 'ti ti-hash',
icon: 'ti ti-id',
text: 'Copy ID',
action: () => {
copyToClipboard(props.report.id);

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { ref, shallowRef } 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 = useTemplateRef('uiWindow');
const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
const comment = ref(props.initialComment ?? '');
function send() {

View File

@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import { onMounted, onUnmounted, shallowRef } from 'vue';
import isChromatic from 'chromatic/isChromatic';
const canvasEl = useTemplateRef('canvasEl');
const canvasEl = shallowRef<HTMLCanvasElement>();
const props = withDefaults(defineProps<{
scale?: number;

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, useTemplateRef } from 'vue';
import { onMounted, shallowRef } 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 = useTemplateRef('rootEl');
const modal = useTemplateRef('modal');
const rootEl = shallowRef<HTMLDivElement>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
async function ok() {
if (props.announcement.needConfirmationToRead) {

View File

@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import { shallowRef } 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 = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna);

View File

@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import { markRaw, ref, shallowRef, 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 = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLDivElement>();
const fetching = ref(true);
const users = ref<any[]>([]);

View File

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

View File

@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { ref, shallowRef, 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 = useTemplateRef('captchaEl');
const captchaEl = shallowRef<HTMLDivElement | undefined>();
const captchaWidgetId = ref<string | undefined>(undefined);
const testcaptchaInput = ref('');
const testcaptchaPassed = ref(false);

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
<img :src="infoImageUrl" class="_ghost"/>
<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';

View File

@@ -45,8 +45,12 @@ export type ChartSrc =
</script>
<script lang="ts" setup>
import { onMounted, ref, useTemplateRef, watch } from 'vue';
/* 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 { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import { misskeyApiGet } from '@/utility/misskey-api.js';
@@ -92,7 +96,7 @@ const props = withDefaults(defineProps<{
nowForChromatic: undefined,
});
const legendEl = useTemplateRef('legendEl');
const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x);
@@ -130,7 +134,7 @@ let chartData: {
bytes?: boolean;
} | null = null;
const chartEl = useTemplateRef('chartEl');
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const fetching = ref(true);
const getDate = (ago: number) => {
@@ -845,7 +849,7 @@ watch(() => [props.src, props.span], fetchAndRender);
onMounted(() => {
fetchAndRender();
});
/* eslint-enable id-denylist */
</script>
<style lang="scss" module>

View File

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

View File

@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, watch, toRefs, useTemplateRef, nextTick } from 'vue';
import { ref, watch, toRefs, shallowRef, 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 = useTemplateRef('inputEl');
const inputEl = shallowRef<HTMLTextAreaElement>();
const focus = () => inputEl.value?.focus();

View File

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

View File

@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import { onMounted, onUnmounted, ref, shallowRef, 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 = useTemplateRef('rootEl');
const contentEl = useTemplateRef('contentEl');
const headerEl = useTemplateRef('headerEl');
const rootEl = shallowRef<HTMLElement>();
const contentEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const showBody = ref(props.expanded);
const ignoreOmit = ref(false);
const omitted = ref(false);

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
import { onMounted, onBeforeUnmount, shallowRef, 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 = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLDivElement>();
const zIndex = ref<number>(os.claimZIndex('high'));

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, useTemplateRef, ref } from 'vue';
import { onMounted, shallowRef, 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 = useTemplateRef('dialogEl');
const imgEl = useTemplateRef('imgEl');
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const imgEl = shallowRef<HTMLImageElement>();
let cropper: Cropper | null = null;
const loading = ref(true);

View File

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

View File

@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
</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>
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
<div v-if="text" :class="$style.text"><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, useTemplateRef, computed } from 'vue';
import { ref, shallowRef, 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 = useTemplateRef('modal');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);

View File

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

View File

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

View File

@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, 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 = useTemplateRef('loadMoreFiles');
const fileInput = useTemplateRef('fileInput');
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
const fileInput = shallowRef<HTMLInputElement>();
const folder = ref<Misskey.entities.DriveFolder | null>(null);
const files = ref<Misskey.entities.DriveFile[]>([]);

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef } from 'vue';
import { ref, shallowRef } 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 = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);

View File

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

View File

@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, computed, watch, onMounted } from 'vue';
import { ref, shallowRef, 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 = useTemplateRef('searchEl');
const emojisEl = useTemplateRef('emojisEl');
const searchEl = shallowRef<HTMLInputElement>();
const emojisEl = shallowRef<HTMLDivElement>();
const {
emojiPickerScale,

View File

@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { useTemplateRef } from 'vue';
import { shallowRef } 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 = useTemplateRef('modal');
const picker = useTemplateRef('picker');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
function chosen(emoji: string) {
emit('done', emoji);

View File

@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef, ref } from 'vue';
import { shallowRef, 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 = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const caption = ref(props.default);

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { onMounted, ref, shallowRef, 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 = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLElement>();
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);

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { nextTick, onMounted, ref, shallowRef } 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 = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLElement>();
const bgSame = ref(false);
const opened = ref(props.defaultOpen);
const openedAtLeastOnce = ref(props.defaultOpen);

View File

@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</div>
<div v-else class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
<img :src="infoImageUrl" class="_ghost"/>
<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, useTemplateRef } from 'vue';
import { reactive, shallowRef } 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 = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const values = reactive({});
for (const item in props.form) {

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch, useTemplateRef, ref } from 'vue';
import { onMounted, nextTick, watch, shallowRef, 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 = useTemplateRef('rootEl');
const chartEl = useTemplateRef('chartEl');
const rootEl = shallowRef<HTMLDivElement | null>(null);
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const now = new Date();
let chartInstance: Chart | null = null;
const fetching = ref(true);

View File

@@ -11,27 +11,18 @@ SPDX-License-Identifier: AGPL-3.0-only
@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>
<!-- 注意slot内の最上位要素に動的にkeyを設定すること -->
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
import { ref, shallowRef, 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 = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLDivElement>();
const tabModel = defineModel<string>('tab');

View File

@@ -14,34 +14,8 @@ 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"
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;"
/>
<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"/>
</TransitionGroup>
</div>
</template>
@@ -83,7 +57,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
</script>
<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch, ref } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha';
import { prefer } from '@/preferences.js';
@@ -120,9 +94,9 @@ const props = withDefaults(defineProps<{
});
const viewId = uuid();
const canvas = useTemplateRef('canvas');
const root = useTemplateRef('root');
const img = useTemplateRef('img');
const canvas = shallowRef<HTMLCanvasElement>();
const root = shallowRef<HTMLDivElement>();
const img = shallowRef<HTMLImageElement>();
const loaded = ref(false);
const canvasWidth = ref(64);
const canvasHeight = ref(64);

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
<div :class="[$style.root, { [$style.warn]: warn }]">
<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>

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="_selectable">
<div>
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
<div 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, useTemplateRef, watch, computed, toRefs } from 'vue';
import { debounce } from 'throttle-debounce';
import { useInterval } from '@@/js/use-interval.js';
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
import type { InputHTMLAttributes } from 'vue';
import type { SuggestionType } from '@/utility/autocomplete.js';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { useInterval } from '@@/js/use-interval.js';
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 = useTemplateRef('inputEl');
const prefixEl = useTemplateRef('prefixEl');
const suffixEl = useTemplateRef('suffixEl');
const inputEl = shallowRef<HTMLInputElement>();
const prefixEl = shallowRef<HTMLElement>();
const suffixEl = shallowRef<HTMLElement>();
const height =
props.small ? 33 :
props.large ? 39 :

View File

@@ -84,9 +84,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, useTemplateRef } from 'vue';
import { onMounted, ref, computed, shallowRef } 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';
@@ -96,6 +95,7 @@ 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 = useTemplateRef('subDoughnutEl');
const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: 'middle',

View File

@@ -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 class="_selectableAtomic">{{ invite.code }}</div>
<div>{{ invite.code }}</div>
</div>
<div v-if="moderator">
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
@@ -90,6 +90,7 @@ function deleteCode() {
function copyInviteCode() {
copyToClipboard(props.invite.code);
os.success();
}
</script>

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.key">
<slot name="key"></slot>
</div>
<div :class="$style.value" class="_selectable">
<div :class="$style.value">
<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,6 +31,7 @@ const props = withDefaults(defineProps<{
const copy_ = () => {
copyToClipboard(props.copy);
os.success();
};
</script>

View File

@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import { shallowRef } 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 = useTemplateRef('modal');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const menu = prefer.s.menu;

View File

@@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import { shallowRef, 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 = useTemplateRef('playerEl');
const audioEl = useTemplateRef('audioEl');
const playerEl = shallowRef<HTMLDivElement>();
const audioEl = shallowRef<HTMLAudioElement>();
// 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-hash',
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.audio.id);

View File

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

View File

@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
import { computed, onMounted, onUnmounted, shallowRef } 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 = useTemplateRef('gallery');
const gallery = shallowRef<HTMLDivElement>();
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);

View File

@@ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import { ref, shallowRef, 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-hash',
icon: 'ti ti-id',
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 = useTemplateRef('videoEl');
const playerEl = useTemplateRef('playerEl');
const videoEl = shallowRef<HTMLVideoElement>();
const playerEl = shallowRef<HTMLDivElement>();
const isHoverring = ref(false);
const controlsShowing = computed(() => {
if (!oncePlayed.value) return true;

View File

@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, useTemplateRef, watch } from 'vue';
import { nextTick, onMounted, onUnmounted, provide, shallowRef, 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 = useTemplateRef('el');
const el = shallowRef<HTMLElement>();
const align = 'left';
const SCROLLBAR_THICKNESS = 16;

View File

@@ -35,9 +35,6 @@ 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"
@@ -179,7 +176,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch } from 'vue';
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } 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';
@@ -212,11 +209,11 @@ const big = isTouchUsing;
const isNestingMenu = inject<boolean>('isNestingMenu', false);
const itemsEl = useTemplateRef('itemsEl');
const itemsEl = shallowRef<HTMLElement>();
const items2 = ref<InnerMenuItem[]>();
const child = useTemplateRef('child');
const child = shallowRef<InstanceType<typeof XChild>>();
const keymap = {
'up|k|shift+tab': {
@@ -257,7 +254,7 @@ watch(() => props.items, () => {
});
const childMenu = ref<MenuItem[] | null>();
const childTarget = useTemplateRef('childTarget');
const childTarget = shallowRef<HTMLElement | null>();
function closeChild() {
childMenu.value = null;

View File

@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, useTemplateRef, computed } from 'vue';
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue';
import type { Keymap } from '@/utility/hotkey.js';
import * as os from '@/os.js';
import { isTouchUsing } from '@/utility/touch.js';
@@ -100,8 +100,8 @@ const maxHeight = ref<number>();
const fixed = ref(false);
const transformOrigin = ref('center');
const showing = ref(true);
const modalRootEl = useTemplateRef('modalRootEl');
const content = useTemplateRef('content');
const modalRootEl = shallowRef<HTMLElement>();
const content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{
@@ -47,9 +47,9 @@ const emit = defineEmits<{
(event: 'esc'): void;
}>();
const modal = useTemplateRef('modal');
const rootEl = useTemplateRef('rootEl');
const headerEl = useTemplateRef('headerEl');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const bodyWidth = ref(0);
const bodyHeight = ref(0);

View File

@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock" :style="{ viewTransitionName: transitionName }"/>
<div :class="$style.main">
<MkNoteHeader :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
@@ -76,13 +76,12 @@ SPDX-License-Identifier: AGPL-3.0-only
:emojiUrls="appearNote.emojis"
:enableEmojiMenu="true"
:enableEmojiMenuReaction="true"
class="_selectable"
/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
</div>
@@ -178,7 +177,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref, useTemplateRef, watch, provide } from 'vue';
import { computed, inject, onMounted, ref, shallowRef, watch, provide, reactive, nextTick } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@@ -224,6 +223,7 @@ import { focusPrev, focusNext } from '@/utility/focus.js';
import { getAppearNote } from '@/utility/get-appear-note.js';
import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
import { prepareViewTransition } from '@/page.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
@@ -235,7 +235,18 @@ const props = withDefaults(defineProps<{
mock: false,
});
const transitionNames = reactive({
avatar: '',
});
provide(DI.mock, props.mock);
provide(DI.navHook, (path, flag) => {
const names = prepareViewTransition(path);
transitionNames.avatar = names.avatar;
nextTick(() => {
router.push(path, flag);
});
});
const emit = defineEmits<{
(ev: 'reaction', emoji: string): void;
@@ -271,14 +282,14 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
const renoteButton = useTemplateRef('renoteButton');
const renoteTime = useTemplateRef('renoteTime');
const reactButton = useTemplateRef('reactButton');
const clipButton = useTemplateRef('clipButton');
const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = useTemplateRef('galleryEl');
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
@@ -854,6 +865,8 @@ function emitUpdReaction(emoji: string, delta: number) {
position: sticky !important;
top: calc(22px + var(--MI-stickyTop, 0px));
left: 0;
contain: paint;
}
.main {

View File

@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<article :class="$style.note" @contextmenu.stop="onContextmenu">
<header :class="$style.noteHeader">
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview :style="{ viewTransitionName: transitionName }"/>
<div :class="$style.noteHeaderBody">
<div>
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
@@ -97,14 +97,13 @@ SPDX-License-Identifier: AGPL-3.0-only
:emojiUrls="appearNote.emojis"
:enableEmojiMenu="true"
:enableEmojiMenuReaction="true"
class="_selectable"
/>
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
<div v-if="appearNote.files && appearNote.files.length > 0">
@@ -211,7 +210,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@@ -256,6 +255,7 @@ import { isEnabledUrlPreview } from '@/instance.js';
import { getAppearNote } from '@/utility/get-appear-note.js';
import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
import { prepareViewTransition } from '@/page.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -264,6 +264,8 @@ const props = withDefaults(defineProps<{
initialTab: 'replies',
});
const transitionName = prepareViewTransition('note-noteDetailed', props.note.id).avatar;
const inChannel = inject('inChannel', null);
const note = ref(deepClone(props.note));
@@ -290,14 +292,14 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
const renoteButton = useTemplateRef('renoteButton');
const renoteTime = useTemplateRef('renoteTime');
const reactButton = useTemplateRef('reactButton');
const clipButton = useTemplateRef('clipButton');
const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = useTemplateRef('galleryEl');
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);
@@ -670,6 +672,8 @@ function loadConversation() {
flex-shrink: 0;
width: 58px;
height: 58px;
contain: paint;
}
.noteHeaderBody {

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.noNotes }}</div>
</div>
</template>
@@ -32,11 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import type { Paging } from '@/components/MkPagination.vue';
import { shallowRef } from 'vue';
import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkPagination from '@/components/MkPagination.vue';
import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
@@ -46,7 +46,7 @@ const props = defineProps<{
disableAutoLoad?: boolean;
}>();
const pagingComponent = useTemplateRef('pagingComponent');
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
defineExpose({
pagingComponent,

View File

@@ -169,10 +169,10 @@ import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { ensureSignin } from '@/i.js';
import { signinRequired } from '@/i.js';
import { infoImageUrl } from '@/instance.js';
const $i = ensureSignin();
const $i = signinRequired();
const props = withDefaults(defineProps<{
notification: Misskey.entities.Notification;

View File

@@ -30,13 +30,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef } from 'vue';
import { notificationTypes } from '@@/js/const.js';
import { ref, shallowRef } from 'vue';
import type { Ref } from 'vue';
import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue';
import type { Ref } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { notificationTypes } from '@@/js/const.js';
import { i18n } from '@/i18n.js';
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
@@ -52,7 +52,7 @@ const props = withDefaults(defineProps<{
excludeTypes: () => [],
});
const dialog = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination ref="pagingComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.noNotifications }}</div>
</div>
</template>
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue';
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
import * as Misskey from 'misskey-js';
import type { notificationTypes } from '@@/js/const.js';
import MkPagination from '@/components/MkPagination.vue';
@@ -41,7 +41,7 @@ const props = defineProps<{
excludeTypes?: typeof notificationTypes[number][];
}>();
const pagingComponent = useTemplateRef('pagingComponent');
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
const pagination = computed(() => prefer.r.useGroupedNotifications.value ? {
endpoint: 'i/notifications-grouped' as const,

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<{
maxHeight: 200,
});
const content = useTemplateRef('content');
const content = shallowRef<HTMLElement>();
const omitted = ref(false);
const ignoreOmit = ref(false);

View File

@@ -22,29 +22,30 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</template>
<div :class="$style.root">
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :key="reloadCount" :router="windowRouter"/>
<RouterView v-else :key="reloadCount" :router="windowRouter"/>
<div ref="contents" :class="$style.root" style="container-type: inline-size;">
<RouterView :key="reloadCount" :router="windowRouter"/>
</div>
</MkWindow>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef } from 'vue';
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { url } from '@@/js/config.js';
import { getScrollContainer } from '@@/js/scroll.js';
import type { PageMetadata } from '@/page.js';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/utility/popout.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/utility/achievements.js';
import { createRouter, mainRouter } from '@/router.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
import { analytics } from '@/analytics.js';
import { DI } from '@/di.js';
import { prefer } from '@/preferences.js';
const props = defineProps<{
initialPath: string;
@@ -54,12 +55,15 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const windowRouter = createRouter(props.initialPath);
const routerFactory = useRouterFactory();
const windowRouter = routerFactory(props.initialPath);
const contents = shallowRef<HTMLElement | null>(null);
const pageMetadata = ref<null | PageMetadata>(null);
const windowEl = useTemplateRef('windowEl');
const history = ref<{ path: string; }[]>([{
path: windowRouter.getCurrentFullPath(),
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: string; }[]>([{
path: windowRouter.getCurrentPath(),
key: windowRouter.getCurrentKey(),
}]);
const buttonsLeft = computed(() => {
const buttons: Record<string, unknown>[] = [];
@@ -97,20 +101,20 @@ function getSearchMarker(path: string) {
const searchMarkerId = ref<string | null>(getSearchMarker(props.initialPath));
windowRouter.addListener('push', ctx => {
history.value.push({ path: ctx.fullPath });
history.value.push({ path: ctx.path, key: ctx.key });
});
windowRouter.addListener('replace', ctx => {
history.value.pop();
history.value.push({ path: ctx.fullPath });
history.value.push({ path: ctx.path, key: ctx.key });
});
windowRouter.addListener('change', ctx => {
if (_DEV_) console.log('windowRouter: change', ctx.fullPath);
searchMarkerId.value = getSearchMarker(ctx.fullPath);
if (_DEV_) console.log('windowRouter: change', ctx.path);
searchMarkerId.value = getSearchMarker(ctx.path);
analytics.page({
path: ctx.fullPath,
title: ctx.fullPath,
path: ctx.path,
title: ctx.path,
});
});
@@ -139,20 +143,20 @@ const contextmenu = computed(() => ([{
icon: 'ti ti-external-link',
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + windowRouter.getCurrentFullPath(), '_blank', 'noopener');
window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
windowEl.value?.close();
},
}, {
icon: 'ti ti-link',
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(url + windowRouter.getCurrentFullPath());
copyToClipboard(url + windowRouter.getCurrentPath());
},
}]));
function back() {
history.value.pop();
windowRouter.replace(history.value.at(-1)!.path);
windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
}
function reload() {
@@ -164,15 +168,17 @@ function close() {
}
function expand() {
mainRouter.push(windowRouter.getCurrentFullPath(), 'forcePage');
mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
windowEl.value?.close();
}
function popout() {
_popout(windowRouter.getCurrentFullPath(), windowEl.value?.$el);
_popout(windowRouter.getCurrentPath(), windowEl.value?.$el);
windowEl.value?.close();
}
useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
onMounted(() => {
analytics.page({
path: props.initialPath,
@@ -196,7 +202,9 @@ defineExpose({
<style lang="scss" module>
.root {
height: 100%;
overscroll-behavior: contain;
min-height: 100%;
background: var(--MI_THEME-bg);
--MI-margin: var(--MI-marginHalf);

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty">
<div class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</slot>
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
@@ -106,7 +106,7 @@ const emit = defineEmits<{
(ev: 'status', error: boolean): void;
}>();
const rootEl = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLElement>();
// 遡り中かどうか
const backed = ref(false);

View File

@@ -39,14 +39,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, useTemplateRef, ref } from 'vue';
import { onMounted, shallowRef, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { ensureSignin } from '@/i.js';
import { signinRequired } from '@/i.js';
const $i = ensureSignin();
const $i = signinRequired();
const emit = defineEmits<{
(ev: 'done', v: { password: string; token: string | null; }): void;
@@ -54,8 +54,8 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const dialog = useTemplateRef('dialog');
const passwordInput = useTemplateRef('passwordInput');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
const password = ref('');
const isBackupCode = ref(false);
const token = ref<string | null>(null);

View File

@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, useTemplateRef } from 'vue';
import { ref, shallowRef } from 'vue';
import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: 'closing'): void;
}>();
const modal = useTemplateRef('modal');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const manualShowing = ref(true);
const hiding = ref(false);

View File

@@ -1,95 +0,0 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.textCountRoot]">
<div :class="$style.textCountLabel">{{ i18n.ts.textCount }}</div>
<div
:class="[$style.textCount,
{ [$style.danger]: textCountPercentage > 100 },
{ [$style.warning]: textCountPercentage > 90 && textCountPercentage <= 100 },
]"
>
<div :class="$style.textCountGraph"></div>
<div><span :class="$style.textCountCurrent">{{ number(textLength) }}</span> / {{ number(maxTextLength) }}</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, useTemplateRef } from 'vue';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import number from '@/filters/number.js';
const props = defineProps<{
textLength: number;
}>();
const maxTextLength = computed(() => {
return instance ? instance.maxNoteTextLength : 1000;
});
const textCountPercentage = computed(() => {
return props.textLength / maxTextLength.value * 100;
});
</script>
<style lang="scss" module>
.textCountRoot {
padding: 4px 14px;
}
.textCountLabel {
font-size: 11px;
opacity: 0.8;
margin-bottom: 4px;
}
.textCount {
display: flex;
gap: var(--MI-marginHalf);
align-items: center;
font-size: 12px;
--countColor: var(--MI_THEME-accent);
&.danger {
--countColor: var(--MI_THEME-error);
}
&.warning {
--countColor: var(--MI_THEME-warn);
}
.textCountGraph {
position: relative;
width: 24px;
height: 24px;
border-radius: 50%;
background-image: conic-gradient(
var(--countColor) 0% v-bind("Math.min(100, textCountPercentage) + '%'"),
rgba(0, 0, 0, .2) v-bind("Math.min(100, textCountPercentage) + '%'") 100%
);
&::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: var(--MI_THEME-popup);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.textCountCurrent {
color: var(--countColor);
font-weight: 700;
font-size: 18px;
}
}
</style>

View File

@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.headerRight">
<template v-if="!(channel != null && fixed)">
<button v-if="channel == null" ref="visibilityButton" v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
@@ -32,11 +32,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
</button>
</template>
<button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
<span v-else><i class="ti ti-rocket-off"></i></span>
</button>
<button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button>
<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
<span v-else><i class="ti ti-icons"></i></span>
</button>
<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
<div :class="$style.submitInner">
<template v-if="posted"></template>
@@ -99,7 +103,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue';
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@@ -107,11 +111,9 @@ import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
import type { ShallowRef } from 'vue';
import type { PostFormProps } from '@/types/post-form.js';
import type { MenuItem } from '@/types/menu.js';
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
import MkNotePreview from '@/components/MkNotePreview.vue';
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
import XTextCounter from '@/components/MkPostForm.TextCounter.vue';
import MkPollEditor from '@/components/MkPollEditor.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
import { erase, unique } from '@/utility/array.js';
@@ -125,7 +127,7 @@ import { store } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { ensureSignin, notesCount, incNotesCount } from '@/i.js';
import { signinRequired, notesCount, incNotesCount } from '@/i.js';
import { getAccounts, openAccountMenu as openAccountMenu_ } from '@/accounts.js';
import { uploadFile } from '@/utility/upload.js';
import { deepClone } from '@/utility/clone.js';
@@ -138,7 +140,7 @@ import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
import { DI } from '@/di.js';
const $i = ensureSignin();
const $i = signinRequired();
const modal = inject('modal');
@@ -165,11 +167,10 @@ const emit = defineEmits<{
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
}>();
const textareaEl = useTemplateRef('textareaEl');
const cwInputEl = useTemplateRef('cwInputEl');
const hashtagsInputEl = useTemplateRef('hashtagsInputEl');
const visibilityButton = useTemplateRef('visibilityButton');
const otherSettingsButton = useTemplateRef('otherSettingsButton');
const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement>();
const posting = ref(false);
const posted = ref(false);
@@ -555,47 +556,6 @@ async function toggleReactionAcceptance() {
reactionAcceptance.value = select.result;
}
//#region その他の設定メニューpopup
function showOtherSettings() {
let reactionAcceptanceIcon = 'ti ti-icons';
if (reactionAcceptance.value === 'likeOnly') {
reactionAcceptanceIcon = 'ti ti-heart _love';
} else if (reactionAcceptance.value === 'likeOnlyForRemote') {
reactionAcceptanceIcon = 'ti ti-heart-plus';
}
const menuItems = [{
type: 'component',
component: XTextCounter,
props: {
textLength: textLength,
},
}, { type: 'divider' }, {
icon: reactionAcceptanceIcon,
text: i18n.ts.reactionAcceptance,
action: () => {
toggleReactionAcceptance();
},
}, { type: 'divider' }, {
icon: 'ti ti-trash',
text: i18n.ts.reset,
danger: true,
action: async () => {
if (props.mock) return;
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.resetAreYouSure,
});
if (canceled) return;
clear();
},
}] satisfies MenuItem[];
os.popupMenu(menuItems, otherSettingsButton.value);
}
//#endregion
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);

View File

@@ -201,7 +201,7 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-hash',
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);

View File

@@ -25,10 +25,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import type { PostFormProps } from '@/types/post-form.js';
import { shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import type { PostFormProps } from '@/types/post-form.js';
const props = withDefaults(defineProps<PostFormProps & {
instant?: boolean;
@@ -42,7 +42,8 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = useTemplateRef('modal');
const modal = shallowRef<InstanceType<typeof MkModal>>();
const form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() {
modal.value?.close({

View File

@@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import { getScrollContainer } from '@@/js/scroll.js';
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
const SCROLL_STOP = 10;
@@ -43,7 +43,7 @@ const pullDistance = ref(0);
let supportPointerDesktop = false;
let startScreenY: number | null = null;
const rootEl = useTemplateRef('rootEl');
const rootEl = shallowRef<HTMLDivElement>();
let scrollEl: HTMLElement | null = null;
let disabled = false;

View File

@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import { isTouchUsing } from '@/utility/touch.js';
import * as os from '@/os.js';
@@ -58,8 +58,8 @@ const emit = defineEmits<{
(ev: 'dragEnded', value: number): void;
}>();
const containerEl = useTemplateRef('containerEl');
const thumbEl = useTemplateRef('thumbEl');
const containerEl = shallowRef<HTMLElement>();
const thumbEl = shallowRef<HTMLElement>();
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
const steppedRawValue = computed(() => {

View File

@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent, useTemplateRef } from 'vue';
import { defineAsyncComponent, shallowRef } from 'vue';
import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
@@ -20,7 +20,7 @@ const props = defineProps<{
withTooltip?: boolean;
}>();
const elRef = useTemplateRef('elRef');
const elRef = shallowRef();
if (props.withTooltip) {
useTooltip(elRef, (showing) => {

View File

@@ -12,13 +12,13 @@ SPDX-License-Identifier: AGPL-3.0-only
@click="toggleReaction()"
@contextmenu.prevent.stop="menu"
>
<MkReactionIcon style="pointer-events: none;" :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
<MkReactionIcon :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
<span :class="$style.count">{{ count }}</span>
</button>
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
import { computed, inject, onMounted, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { getUnicodeEmoji } from '@@/js/emojilist.js';
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
@@ -50,7 +50,7 @@ const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void;
}>();
const buttonEl = useTemplateRef('buttonEl');
const buttonEl = shallowRef<HTMLElement>();
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, useTemplateRef, ref } from 'vue';
import { onMounted, nextTick, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { store } from '@/store.js';
@@ -23,8 +23,8 @@ import { initChart } from '@/utility/init-chart.js';
initChart();
const rootEl = useTemplateRef('rootEl');
const chartEl = useTemplateRef('chartEl');
const rootEl = shallowRef<HTMLDivElement | null>(null);
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
let chartInstance: Chart | null = null;
const fetching = ref(true);

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, useTemplateRef } from 'vue';
import { onMounted, shallowRef } from 'vue';
import { Chart } from 'chart.js';
import tinycolor from 'tinycolor2';
import { store } from '@/store.js';
@@ -20,7 +20,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
initChart();
const chartEl = useTemplateRef('chartEl');
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const { handler: externalTooltipHandler } = useChartTooltip();

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { useTemplateRef } from 'vue';
import { shallowRef } from 'vue';
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import MkSignin from '@/components/MkSignin.vue';
import MkModal from '@/components/MkModal.vue';
@@ -46,7 +46,7 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const modal = useTemplateRef('modal');
const modal = shallowRef<InstanceType<typeof MkModal>>();
function onClose() {
emit('cancelled');

View File

@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef, ref } from 'vue';
import { shallowRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XSignup from '@/components/MkSignupDialog.form.vue';
import XServerRules from '@/components/MkSignupDialog.rules.vue';
@@ -52,7 +52,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const isAcceptedServerRule = ref(false);

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
const particles = ref<{
id: string,
@@ -66,7 +66,7 @@ const particles = ref<{
dur: number,
color: string
}[]>([]);
const el = useTemplateRef('el');
const el = shallowRef<HTMLElement>();
const width = ref(0);
const height = ref(0);
const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];

View File

@@ -98,7 +98,7 @@ import type { SearchIndexItem } from '@/utility/autogen/settings-search-index.js
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { useRouter } from '@/router.js';
import { useRouter } from '@/router/supplier.js';
import { initIntlString, compareStringIncludes } from '@/utility/intl-string.js';
const props = defineProps<{

View File

@@ -92,15 +92,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import type {
MkSystemWebhookEditorProps,
MkSystemWebhookResult,
SystemWebhookEventType,
} from '@/components/MkSystemWebhookEditor.impl.js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -122,7 +122,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialogEl = useTemplateRef('dialogEl');
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const props = defineProps<MkSystemWebhookEditorProps>();

View File

@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, watch, onBeforeUnmount, ref, useTemplateRef } from 'vue';
import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue';
import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas;
@@ -24,9 +24,9 @@ const computedStyle = getComputedStyle(document.documentElement);
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const available = ref(false);
const rootEl = useTemplateRef('rootEl');
const canvasEl = useTemplateRef('canvasEl');
const tagsEl = useTemplateRef('tagsEl');
const rootEl = shallowRef<HTMLElement | null>(null);
const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
const tagsEl = shallowRef<HTMLElement | null>(null);
const width = ref(300);
watch(available, () => {

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="_selectable">
<div>
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
<textarea
@@ -36,12 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, useTemplateRef } from 'vue';
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
import { debounce } from 'throttle-debounce';
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 | null;
@@ -75,7 +75,7 @@ const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = useTemplateRef('inputEl');
const inputEl = shallowRef<HTMLTextAreaElement>();
const preview = ref(false);
let autocompleteWorker: Autocomplete | null = null;

View File

@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide, useTemplateRef } from 'vue';
import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import type { BasicTimelineType } from '@/timelines.js';
import type { Paging } from '@/components/MkPagination.vue';
@@ -67,8 +67,8 @@ type TimelineQueryType = {
roleId?: string
};
const prComponent = useTemplateRef('prComponent');
const tlComponent = useTemplateRef('tlComponent');
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
let tlNotesCount = 0;

View File

@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef, ref } from 'vue';
import { shallowRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from './MkInput.vue';
import MkSwitch from './MkSwitch.vue';
@@ -77,7 +77,7 @@ const emit = defineEmits<{
const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
const dialog = useTemplateRef('dialog');
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const name = ref(props.initialName);
const permissionSwitches = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
const permissionSwitchesForAdmin = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);

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