Compare commits
	
		
			17 Commits
		
	
	
		
			2025.3.2-b
			...
			2025.3.2-b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ![github-actions[bot]](/assets/img/avatar_default.png)  | fbd9f47182 | ||
|   | 1c9e25470a | ||
|   | ce6b2448ce | ||
|   | 7d44b47fdf | ||
|   | dca42fd6e6 | ||
|   | 43153311c6 | ||
|   | 2b23c7e7f5 | ||
|   | 2d4e85b466 | ||
|   | 7bfada9792 | ||
|   | 4fef9c670a | ||
|   | 9599261fc3 | ||
|   | c2940fd77c | ||
|   | 2ddedd0ce6 | ||
|   | c88f5f5195 | ||
| ![github-actions[bot]](/assets/img/avatar_default.png)  | 30de6d80bb | ||
|   | 63993dace6 | ||
|   | 0929410d36 | 
							
								
								
									
										3
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -24,9 +24,6 @@ updates: | |||||||
|     aws-sdk: |     aws-sdk: | ||||||
|       patterns: |       patterns: | ||||||
|         - "@aws-sdk/*" |         - "@aws-sdk/*" | ||||||
|     bull-board: |  | ||||||
|       patterns: |  | ||||||
|         - "@bull-board/*" |  | ||||||
|     nestjs: |     nestjs: | ||||||
|       patterns: |       patterns: | ||||||
|         - "@nestjs/*" |         - "@nestjs/*" | ||||||
|   | |||||||
| @@ -1,18 +1,22 @@ | |||||||
| ## 2025.3.2 | ## 2025.3.2 | ||||||
|  |  | ||||||
| ### General | ### General | ||||||
| - | - セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 | ||||||
|  |   - Misskeyネイティブでダッシュボードを実装予定です | ||||||
|  |  | ||||||
| ### Client | ### Client | ||||||
| - Feat: 設定の管理が強化されました | - Feat: 設定の管理が強化されました | ||||||
|   - 自動でバックアップされるように |   - 自動でバックアップされるように | ||||||
| 	- 任意の設定項目をデバイス間で同期できるように(実験的) |   - 任意の設定項目をデバイス間で同期できるように | ||||||
| - Enhance: プラグインの管理が強化されました | - Enhance: プラグインの管理が強化されました | ||||||
|   - インストール/アンインストール/設定の変更時にリロード不要になりました |   - インストール/アンインストール/設定の変更時にリロード不要になりました | ||||||
| - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように | - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように | ||||||
| - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに | - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに | ||||||
| - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように | - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように | ||||||
| - Enhance: テーマ設定画面のデザインを改善 | - Enhance: テーマ設定画面のデザインを改善 | ||||||
|  | - Enhance: 投稿フォームの設定メニューを改良 | ||||||
|  |   - 投稿フォームをリセットできるように | ||||||
|  |   - 文字数カウントを復活 | ||||||
| - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 | - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 | ||||||
|  |  | ||||||
| ### Server | ### Server | ||||||
|   | |||||||
| @@ -1190,7 +1190,7 @@ pastAnnouncements: "Informes passats" | |||||||
| youHaveUnreadAnnouncements: "Tens informes per llegir." | youHaveUnreadAnnouncements: "Tens informes per llegir." | ||||||
| useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." | useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." | ||||||
| replies: "Respostes" | replies: "Respostes" | ||||||
| renotes: "Impulsar" | renotes: "Impulsos" | ||||||
| loadReplies: "Mostrar les respostes" | loadReplies: "Mostrar les respostes" | ||||||
| loadConversation: "Mostrar la conversació " | loadConversation: "Mostrar la conversació " | ||||||
| pinnedList: "Llista fixada" | pinnedList: "Llista fixada" | ||||||
| @@ -1326,7 +1326,17 @@ restore: "Restaurar " | |||||||
| syncBetweenDevices: "Sincronització entre dispositius" | syncBetweenDevices: "Sincronització entre dispositius" | ||||||
| preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" | preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" | ||||||
| preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" | preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" | ||||||
|  | preferenceSyncConflictChoiceServer: "Valors de configuració del servidor" | ||||||
|  | preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu " | ||||||
|  | preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització " | ||||||
|  | paste: "Pegar" | ||||||
|  | emojiPalette: "Calaix d'emojis" | ||||||
| postForm: "Formulari de publicació" | postForm: "Formulari de publicació" | ||||||
|  | _emojiPalette: | ||||||
|  |   palettes: "Calaixos d'emojis" | ||||||
|  |   enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius" | ||||||
|  |   paletteForMain: "Calaix d'emojis principal" | ||||||
|  |   paletteForReaction: "Calaix d'emojis per reaccions" | ||||||
| _settings: | _settings: | ||||||
|   driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius." |   driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius." | ||||||
|   pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se." |   pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se." | ||||||
| @@ -1344,6 +1354,7 @@ _settings: | |||||||
|   preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències." |   preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències." | ||||||
|   appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer." |   appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer." | ||||||
|   soundsBanner: "Configuració dels sons que reproduirà el client." |   soundsBanner: "Configuració dels sons que reproduirà el client." | ||||||
|  |   timelineAndNote: "Línia de temps i nota" | ||||||
| _preferencesProfile: | _preferencesProfile: | ||||||
|   profileName: "Nom del perfil" |   profileName: "Nom del perfil" | ||||||
|   profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." |   profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." | ||||||
| @@ -2056,7 +2067,7 @@ _theme: | |||||||
|     hashtag: "Etiqueta" |     hashtag: "Etiqueta" | ||||||
|     mention: "Menció" |     mention: "Menció" | ||||||
|     mentionMe: "Mencions (jo)" |     mentionMe: "Mencions (jo)" | ||||||
|     renote: "Renotar" |     renote: "Impulsar" | ||||||
|     modalBg: "Fons del modal" |     modalBg: "Fons del modal" | ||||||
|     divider: "Divisor" |     divider: "Divisor" | ||||||
|     scrollbarHandle: "Maneta de la barra de desplaçament" |     scrollbarHandle: "Maneta de la barra de desplaçament" | ||||||
| @@ -2498,7 +2509,7 @@ _notification: | |||||||
|     follow: "Segueix-me" |     follow: "Segueix-me" | ||||||
|     mention: "Menció" |     mention: "Menció" | ||||||
|     reply: "Respostes" |     reply: "Respostes" | ||||||
|     renote: "Impulsar" |     renote: "Impulsos" | ||||||
|     quote: "Citar" |     quote: "Citar" | ||||||
|     reaction: "Reaccions" |     reaction: "Reaccions" | ||||||
|     pollEnded: "Enquesta terminada" |     pollEnded: "Enquesta terminada" | ||||||
| @@ -2513,7 +2524,7 @@ _notification: | |||||||
|   _actions: |   _actions: | ||||||
|     followBack: "També et segueix" |     followBack: "També et segueix" | ||||||
|     reply: "Respondre" |     reply: "Respondre" | ||||||
|     renote: "Impulsos" |     renote: "Impulsar" | ||||||
| _deck: | _deck: | ||||||
|   alwaysShowMainColumn: "Mostrar sempre la columna principal" |   alwaysShowMainColumn: "Mostrar sempre la columna principal" | ||||||
|   columnAlign: "Alinea les columnes" |   columnAlign: "Alinea les columnes" | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ pin: "An dein Profil anheften" | |||||||
| unpin: "Von deinem Profil lösen" | unpin: "Von deinem Profil lösen" | ||||||
| copyContent: "Inhalt kopieren" | copyContent: "Inhalt kopieren" | ||||||
| copyLink: "Link kopieren" | copyLink: "Link kopieren" | ||||||
| copyRemoteLink: "Renote-Link kopieren" | copyRemoteLink: "Remote-Link kopieren" | ||||||
| copyLinkRenote: "Renote-Link kopieren" | copyLinkRenote: "Renote-Link kopieren" | ||||||
| delete: "Löschen" | delete: "Löschen" | ||||||
| deleteAndEdit: "Löschen und Bearbeiten" | deleteAndEdit: "Löschen und Bearbeiten" | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -5346,6 +5346,10 @@ export interface Locale extends ILocale { | |||||||
|      * 投稿フォーム |      * 投稿フォーム | ||||||
|      */ |      */ | ||||||
|     "postForm": string; |     "postForm": string; | ||||||
|  |     /** | ||||||
|  |      * 文字数 | ||||||
|  |      */ | ||||||
|  |     "textCount": string; | ||||||
|     "_emojiPalette": { |     "_emojiPalette": { | ||||||
|         /** |         /** | ||||||
|          * パレット |          * パレット | ||||||
| @@ -5433,6 +5437,14 @@ export interface Locale extends ILocale { | |||||||
|          * タイムラインとノート |          * タイムラインとノート | ||||||
|          */ |          */ | ||||||
|         "timelineAndNote": string; |         "timelineAndNote": string; | ||||||
|  |         /** | ||||||
|  |          * 全てのテキスト要素を選択可能にする | ||||||
|  |          */ | ||||||
|  |         "makeEveryTextElementsSelectable": string; | ||||||
|  |         /** | ||||||
|  |          * 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。 | ||||||
|  |          */ | ||||||
|  |         "makeEveryTextElementsSelectable_description": string; | ||||||
|     }; |     }; | ||||||
|     "_preferencesProfile": { |     "_preferencesProfile": { | ||||||
|         /** |         /** | ||||||
| @@ -9777,6 +9789,10 @@ export interface Locale extends ILocale { | |||||||
|              * ログイン |              * ログイン | ||||||
|              */ |              */ | ||||||
|             "login": string; |             "login": string; | ||||||
|  |             /** | ||||||
|  |              * アクセストークンの作成 | ||||||
|  |              */ | ||||||
|  |             "createToken": string; | ||||||
|             /** |             /** | ||||||
|              * 通知のテスト |              * 通知のテスト | ||||||
|              */ |              */ | ||||||
|   | |||||||
| @@ -1332,6 +1332,7 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル" | |||||||
| paste: "ペースト" | paste: "ペースト" | ||||||
| emojiPalette: "絵文字パレット" | emojiPalette: "絵文字パレット" | ||||||
| postForm: "投稿フォーム" | postForm: "投稿フォーム" | ||||||
|  | textCount: "文字数" | ||||||
|  |  | ||||||
| _emojiPalette: | _emojiPalette: | ||||||
|   palettes: "パレット" |   palettes: "パレット" | ||||||
| @@ -1357,6 +1358,8 @@ _settings: | |||||||
|   appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" |   appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" | ||||||
|   soundsBanner: "クライアントで再生するサウンドの設定が行えます。" |   soundsBanner: "クライアントで再生するサウンドの設定が行えます。" | ||||||
|   timelineAndNote: "タイムラインとノート" |   timelineAndNote: "タイムラインとノート" | ||||||
|  |   makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする" | ||||||
|  |   makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。" | ||||||
|  |  | ||||||
| _preferencesProfile: | _preferencesProfile: | ||||||
|   profileName: "プロファイル名" |   profileName: "プロファイル名" | ||||||
| @@ -2584,6 +2587,7 @@ _notification: | |||||||
|     achievementEarned: "実績の獲得" |     achievementEarned: "実績の獲得" | ||||||
|     exportCompleted: "エクスポートが完了した" |     exportCompleted: "エクスポートが完了した" | ||||||
|     login: "ログイン" |     login: "ログイン" | ||||||
|  |     createToken: "アクセストークンの作成" | ||||||
|     test: "通知のテスト" |     test: "通知のテスト" | ||||||
|     app: "連携アプリからの通知" |     app: "連携アプリからの通知" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -746,7 +746,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。 | |||||||
| public: "公开" | public: "公开" | ||||||
| private: "私密" | private: "私密" | ||||||
| i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" | i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" | ||||||
| manageAccessTokens: "管理 Access Tokens" | manageAccessTokens: "管理访问令牌" | ||||||
| accountInfo: "账户信息" | accountInfo: "账户信息" | ||||||
| notesCount: "帖子数量" | notesCount: "帖子数量" | ||||||
| repliesCount: "回复数量" | repliesCount: "回复数量" | ||||||
| @@ -1339,13 +1339,24 @@ _emojiPalette: | |||||||
|   paletteForReaction: "回应用调色板" |   paletteForReaction: "回应用调色板" | ||||||
| _settings: | _settings: | ||||||
|   driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。" |   driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。" | ||||||
|  |   pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。" | ||||||
|  |   notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。" | ||||||
|   api: "API" |   api: "API" | ||||||
|   webhook: "Webhook" |   webhook: "Webhook" | ||||||
|  |   serviceConnection: "连接服务" | ||||||
|  |   serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。" | ||||||
|  |   accountData: "账户数据" | ||||||
|  |   accountDataBanner: "可在此导入或导出帐户数据的存档。" | ||||||
|  |   muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。" | ||||||
|  |   accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。" | ||||||
|   privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。" |   privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。" | ||||||
|   securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。" |   securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。" | ||||||
|   preferencesBanner: "可在此设置客户端的整体运作行为。" |   preferencesBanner: "可在此设置客户端的整体运作行为。" | ||||||
|   appearanceBanner: "可在此设置客户端的外观及显示方式。" |   appearanceBanner: "可在此设置客户端的外观及显示方式。" | ||||||
|   soundsBanner: "可在此设置客户端播放的声音。" |   soundsBanner: "可在此设置客户端播放的声音。" | ||||||
|  |   timelineAndNote: "时间线和帖子" | ||||||
|  |   makeEveryTextElementsSelectable: "使所有的文字均可选择" | ||||||
|  |   makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。" | ||||||
| _preferencesProfile: | _preferencesProfile: | ||||||
|   profileName: "配置名" |   profileName: "配置名" | ||||||
|   profileNameDescription: "请指定用于识别此设备的名称" |   profileNameDescription: "请指定用于识别此设备的名称" | ||||||
| @@ -2510,6 +2521,7 @@ _notification: | |||||||
|     achievementEarned: "取得的成就" |     achievementEarned: "取得的成就" | ||||||
|     exportCompleted: "已完成导出" |     exportCompleted: "已完成导出" | ||||||
|     login: "登录" |     login: "登录" | ||||||
|  |     createToken: "创建访问令牌" | ||||||
|     test: "测试通知" |     test: "测试通知" | ||||||
|     app: "关联应用的通知" |     app: "关联应用的通知" | ||||||
|   _actions: |   _actions: | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"version": "2025.3.2-beta.0", | 	"version": "2025.3.2-beta.2", | ||||||
| 	"codename": "nasubi", | 	"codename": "nasubi", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
|   | |||||||
| @@ -69,9 +69,6 @@ | |||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@aws-sdk/client-s3": "3.749.0", | 		"@aws-sdk/client-s3": "3.749.0", | ||||||
| 		"@aws-sdk/lib-storage": "3.749.0", | 		"@aws-sdk/lib-storage": "3.749.0", | ||||||
| 		"@bull-board/api": "6.7.7", |  | ||||||
| 		"@bull-board/fastify": "6.7.7", |  | ||||||
| 		"@bull-board/ui": "6.7.7", |  | ||||||
| 		"@discordapp/twemoji": "15.1.0", | 		"@discordapp/twemoji": "15.1.0", | ||||||
| 		"@fastify/accepts": "5.0.2", | 		"@fastify/accepts": "5.0.2", | ||||||
| 		"@fastify/cookie": "11.0.2", | 		"@fastify/cookie": "11.0.2", | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
| import { Inject, Injectable } from '@nestjs/common'; | import { Inject, Injectable } from '@nestjs/common'; | ||||||
| import cors from '@fastify/cors'; | import cors from '@fastify/cors'; | ||||||
| import multipart from '@fastify/multipart'; | import multipart from '@fastify/multipart'; | ||||||
| import fastifyCookie from '@fastify/cookie'; |  | ||||||
| import { ModuleRef } from '@nestjs/core'; | import { ModuleRef } from '@nestjs/core'; | ||||||
| import { AuthenticationResponseJSON } from '@simplewebauthn/types'; | import { AuthenticationResponseJSON } from '@simplewebauthn/types'; | ||||||
| import type { Config } from '@/config.js'; | import type { Config } from '@/config.js'; | ||||||
| @@ -57,8 +56,6 @@ export class ApiServerService { | |||||||
| 			}, | 			}, | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		fastify.register(fastifyCookie, {}); |  | ||||||
|  |  | ||||||
| 		// Prevent cache | 		// Prevent cache | ||||||
| 		fastify.addHook('onRequest', (request, reply, done) => { | 		fastify.addHook('onRequest', (request, reply, done) => { | ||||||
| 			reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); | 			reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); | ||||||
|   | |||||||
| @@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto'; | |||||||
| import { dirname } from 'node:path'; | import { dirname } from 'node:path'; | ||||||
| import { fileURLToPath } from 'node:url'; | import { fileURLToPath } from 'node:url'; | ||||||
| import { Inject, Injectable } from '@nestjs/common'; | import { Inject, Injectable } from '@nestjs/common'; | ||||||
| import { createBullBoard } from '@bull-board/api'; |  | ||||||
| import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js'; |  | ||||||
| import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify'; |  | ||||||
| import ms from 'ms'; | import ms from 'ms'; | ||||||
| import sharp from 'sharp'; | import sharp from 'sharp'; | ||||||
| import pug from 'pug'; | import pug from 'pug'; | ||||||
| import { In, IsNull } from 'typeorm'; | import { In, IsNull } from 'typeorm'; | ||||||
| import fastifyStatic from '@fastify/static'; | import fastifyStatic from '@fastify/static'; | ||||||
| import fastifyView from '@fastify/view'; | import fastifyView from '@fastify/view'; | ||||||
| import fastifyCookie from '@fastify/cookie'; |  | ||||||
| import fastifyProxy from '@fastify/http-proxy'; | import fastifyProxy from '@fastify/http-proxy'; | ||||||
| import vary from 'vary'; | import vary from 'vary'; | ||||||
| import htmlSafeJsonStringify from 'htmlescape'; | import htmlSafeJsonStringify from 'htmlescape'; | ||||||
| @@ -221,64 +217,6 @@ export class ClientServerService { | |||||||
|  |  | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { | 	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { | ||||||
| 		fastify.register(fastifyCookie, {}); |  | ||||||
|  |  | ||||||
| 		//#region Bull Dashboard |  | ||||||
| 		const bullBoardPath = '/queue'; |  | ||||||
|  |  | ||||||
| 		// Authenticate |  | ||||||
| 		fastify.addHook('onRequest', async (request, reply) => { |  | ||||||
| 			if (request.routeOptions.url == null) { |  | ||||||
| 				reply.code(404).send('Not found'); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// %71ueueとかでリクエストされたら困るため |  | ||||||
| 			const url = decodeURI(request.routeOptions.url); |  | ||||||
| 			if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { |  | ||||||
| 				if (!url.startsWith(bullBoardPath + '/static/')) { |  | ||||||
| 					reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				const token = request.cookies.token; |  | ||||||
| 				if (token == null) { |  | ||||||
| 					reply.code(401).send('Login required'); |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 				const user = await this.usersRepository.findOneBy({ token }); |  | ||||||
| 				if (user == null) { |  | ||||||
| 					reply.code(403).send('No such user'); |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 				const isAdministrator = await this.roleService.isAdministrator(user); |  | ||||||
| 				if (!isAdministrator) { |  | ||||||
| 					reply.code(403).send('Access denied'); |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		const bullBoardServerAdapter = new BullBoardFastifyAdapter(); |  | ||||||
|  |  | ||||||
| 		createBullBoard({ |  | ||||||
| 			queues: [ |  | ||||||
| 				this.systemQueue, |  | ||||||
| 				this.endedPollNotificationQueue, |  | ||||||
| 				this.deliverQueue, |  | ||||||
| 				this.inboxQueue, |  | ||||||
| 				this.dbQueue, |  | ||||||
| 				this.relationshipQueue, |  | ||||||
| 				this.objectStorageQueue, |  | ||||||
| 				this.userWebhookDeliverQueue, |  | ||||||
| 				this.systemWebhookDeliverQueue, |  | ||||||
| 			].map(q => new BullMQAdapter(q)), |  | ||||||
| 			serverAdapter: bullBoardServerAdapter, |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		bullBoardServerAdapter.setBasePath(bullBoardPath); |  | ||||||
| 		(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath }); |  | ||||||
| 		//#endregion |  | ||||||
|  |  | ||||||
| 		fastify.register(fastifyView, { | 		fastify.register(fastifyView, { | ||||||
| 			root: _dirname + '/views', | 			root: _dirname + '/views', | ||||||
| 			engine: { | 			engine: { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| process.env.NODE_ENV = 'test'; | process.env.NODE_ENV = 'test'; | ||||||
|  |  | ||||||
| import * as assert from 'assert'; | import * as assert from 'assert'; | ||||||
| import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; | import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; | ||||||
| import type { SimpleGetResponse } from '../utils.js'; | import type { SimpleGetResponse } from '../utils.js'; | ||||||
| import type * as misskey from 'misskey-js'; | import type * as misskey from 'misskey-js'; | ||||||
|  |  | ||||||
| @@ -156,20 +156,20 @@ describe('Webリソース', () => { | |||||||
|  |  | ||||||
| 		describe(' has entry such ', () => { | 		describe(' has entry such ', () => { | ||||||
| 			beforeEach(() => { | 			beforeEach(() => { | ||||||
| 				post(alice, { text: "**a**" }) | 				post(alice, { text: '**a**' }); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			test('MFMを含まない。', async () => { | 			test('MFMを含まない。', async () => { | ||||||
| 				const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text()); | 				const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text()); | ||||||
| 				const _body: unknown = content.body; | 				const _body: unknown = content.body; | ||||||
| 				// JSONフィードのときは改めて文字列化する | 				// JSONフィードのときは改めて文字列化する | ||||||
| 				const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string; | 				const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string; | ||||||
|  |  | ||||||
| 				if (body.includes("**a**")) { | 				if (body.includes('**a**')) { | ||||||
| 					throw new Error("MFM shouldn't be included"); | 					throw new Error('MFM shouldn\'t be included'); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| 		}) | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { | 	describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { | ||||||
| @@ -180,24 +180,6 @@ describe('Webリソース', () => { | |||||||
| 		})); | 		})); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	describe.each([{ path: '/queue' }])('$path', ({ path }) => { |  | ||||||
| 		test('はログインしないとGETできない。', async () => await notOk({ |  | ||||||
| 			path, |  | ||||||
| 			status: 401, |  | ||||||
| 		})); |  | ||||||
|  |  | ||||||
| 		test('はadminでなければGETできない。', async () => await notOk({ |  | ||||||
| 			path, |  | ||||||
| 			cookie: cookie(bob), |  | ||||||
| 			status: 403, |  | ||||||
| 		})); |  | ||||||
|  |  | ||||||
| 		test('はadminならGETできる。', async () => await ok({ |  | ||||||
| 			path, |  | ||||||
| 			cookie: cookie(alice), |  | ||||||
| 		})); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	describe.each([{ path: '/streaming' }])('$path', ({ path }) => { | 	describe.each([{ path: '/streaming' }])('$path', ({ path }) => { | ||||||
| 		test('はGETできない。', async () => await notOk({ | 		test('はGETできない。', async () => await notOk({ | ||||||
| 			path, | 			path, | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ export type SystemWebhookPayload = { | |||||||
| 	createdAt: string; | 	createdAt: string; | ||||||
| 	type: string; | 	type: string; | ||||||
| 	body: any; | 	body: any; | ||||||
| } | }; | ||||||
|  |  | ||||||
| const config = loadConfig(); | const config = loadConfig(); | ||||||
| export const port = config.port; | export const port = config.port; | ||||||
| @@ -45,10 +45,6 @@ export const host = new URL(config.url).host; | |||||||
| export const WEBHOOK_HOST = 'http://localhost:15080'; | export const WEBHOOK_HOST = 'http://localhost:15080'; | ||||||
| export const WEBHOOK_PORT = 15080; | export const WEBHOOK_PORT = 15080; | ||||||
|  |  | ||||||
| export const cookie = (me: UserToken): string => { |  | ||||||
| 	return `token=${me.token};`; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = { | export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = { | ||||||
| 	endpoint: E, | 	endpoint: E, | ||||||
| 	parameters: P, | 	parameters: P, | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
| import { inject, watch, ref } from 'vue'; | import { watch, ref } from 'vue'; | ||||||
| import XReaction from '@/components/EmReactionsViewer.reaction.vue'; | import XReaction from '@/components/EmReactionsViewer.reaction.vue'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| @@ -22,12 +22,6 @@ const props = withDefaults(defineProps<{ | |||||||
| 	maxNumber: Infinity, | 	maxNumber: Infinity, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const mock = inject<boolean>('mock', false); |  | ||||||
|  |  | ||||||
| const emit = defineEmits<{ |  | ||||||
| 	(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; |  | ||||||
| }>(); |  | ||||||
|  |  | ||||||
| const initialReactions = new Set(Object.keys(props.note.reactions)); | const initialReactions = new Set(Object.keys(props.note.reactions)); | ||||||
|  |  | ||||||
| const reactions = ref<[string, number][]>([]); | const reactions = ref<[string, number][]>([]); | ||||||
| @@ -38,12 +32,8 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m | |||||||
| } | } | ||||||
|  |  | ||||||
| function onMockToggleReaction(emoji: string, count: number) { | function onMockToggleReaction(emoji: string, count: number) { | ||||||
| 	if (!mock) return; |  | ||||||
|  |  | ||||||
| 	const i = reactions.value.findIndex((item) => item[0] === emoji); | 	const i = reactions.value.findIndex((item) => item[0] === emoji); | ||||||
| 	if (i < 0) return; | 	if (i < 0) return; | ||||||
|  |  | ||||||
| 	emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1])); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { | watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<div class="_fullinfo"> | 	<div class="_fullinfo"> | ||||||
| 		<img :src="notFoundImageUrl" class="_ghost"/> | 		<img :src="notFoundImageUrl" draggable="false"/> | ||||||
| 		<div>{{ i18n.ts.notFoundDescription }}</div> | 		<div>{{ i18n.ts.notFoundDescription }}</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| @@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js'; | |||||||
|  |  | ||||||
| const serverMetadata = inject(DI.serverMetadata)!; | const serverMetadata = inject(DI.serverMetadata)!; | ||||||
|  |  | ||||||
| const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); | const notFoundImageUrl = computed(() => serverMetadata.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -234,6 +234,10 @@ export async function common(createVue: () => App<Element>) { | |||||||
| 			}); | 			}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (prefer.s.makeEveryTextElementsSelectable) { | ||||||
|  | 		document.documentElement.classList.add('forceSelectableAll'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	//#region Fetch user | 	//#region Fetch user | ||||||
| 	if ($i && $i.token) { | 	if ($i && $i.token) { | ||||||
| 		if (_DEV_) { | 		if (_DEV_) { | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <MkPagination :pagination="pagination"> | <MkPagination :pagination="pagination"> | ||||||
| 	<template #empty> | 	<template #empty> | ||||||
| 		<div class="_fullinfo"> | 		<div class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.notFound }}</div> | 			<div>{{ i18n.ts.notFound }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</template> | 	</template> | ||||||
| @@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|  | import type { Paging } from '@/components/MkPagination.vue'; | ||||||
| import MkChannelPreview from '@/components/MkChannelPreview.vue'; | import MkChannelPreview from '@/components/MkChannelPreview.vue'; | ||||||
| import MkPagination from '@/components/MkPagination.vue'; | import MkPagination from '@/components/MkPagination.vue'; | ||||||
| import type { Paging } from '@/components/MkPagination.vue'; |  | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { infoImageUrl } from '@/instance.js'; | import { infoImageUrl } from '@/instance.js'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i> | 			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i> | ||||||
| 			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> | 			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> | ||||||
| 		</div> | 		</div> | ||||||
| 		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header> | 		<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header> | ||||||
| 		<div v-if="text" :class="$style.text"><Mfm :text="text"/></div> | 		<div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div> | ||||||
| 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> | 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> | ||||||
| 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> | 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> | ||||||
| 			<template #caption> | 			<template #caption> | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			</template> | 			</template> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else class="_fullinfo"> | 		<div v-else class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.nothing }}</div> | 			<div>{{ i18n.ts.nothing }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkSpacer> | 	</MkSpacer> | ||||||
|   | |||||||
| @@ -14,8 +14,34 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined" | 		:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined" | ||||||
| 		:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined" | 		:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined" | ||||||
| 	> | 	> | ||||||
| 		<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/> | 		<canvas | ||||||
| 		<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/> | 			v-show="hide" | ||||||
|  | 			key="canvas" | ||||||
|  | 			ref="canvas" | ||||||
|  | 			:class="$style.canvas" | ||||||
|  | 			:width="canvasWidth" | ||||||
|  | 			:height="canvasHeight" | ||||||
|  | 			:title="title ?? undefined" | ||||||
|  | 			draggable="false" | ||||||
|  | 			tabindex="-1" | ||||||
|  | 			style="-webkit-user-drag: none;" | ||||||
|  | 		/> | ||||||
|  | 		<img | ||||||
|  | 			v-show="!hide" | ||||||
|  | 			key="img" | ||||||
|  | 			ref="img" | ||||||
|  | 			:height="imgHeight ?? undefined" | ||||||
|  | 			:width="imgWidth ?? undefined" | ||||||
|  | 			:class="$style.img" | ||||||
|  | 			:src="src ?? undefined" | ||||||
|  | 			:title="title ?? undefined" | ||||||
|  | 			:alt="alt ?? undefined" | ||||||
|  | 			loading="eager" | ||||||
|  | 			decoding="async" | ||||||
|  | 			draggable="false" | ||||||
|  | 			tabindex="-1" | ||||||
|  | 			style="-webkit-user-drag: none;" | ||||||
|  | 		/> | ||||||
| 	</TransitionGroup> | 	</TransitionGroup> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <div :class="[$style.root, { [$style.warn]: warn }]"> | <div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable"> | ||||||
| 	<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i> | 	<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i> | ||||||
| 	<i v-else class="ti ti-info-circle" :class="$style.i"></i> | 	<i v-else class="ti ti-info-circle" :class="$style.i"></i> | ||||||
| 	<div><slot></slot></div> | 	<div><slot></slot></div> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <div> | <div class="_selectable"> | ||||||
| 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div> | 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div> | ||||||
| 	<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]"> | 	<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]"> | ||||||
| 		<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> | 		<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> | ||||||
| @@ -45,13 +45,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; | import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; | ||||||
| import type { InputHTMLAttributes } from 'vue'; |  | ||||||
| import { debounce } from 'throttle-debounce'; | import { debounce } from 'throttle-debounce'; | ||||||
| import MkButton from '@/components/MkButton.vue'; |  | ||||||
| import { useInterval } from '@@/js/use-interval.js'; | import { useInterval } from '@@/js/use-interval.js'; | ||||||
|  | import type { InputHTMLAttributes } from 'vue'; | ||||||
|  | import type { SuggestionType } from '@/utility/autocomplete.js'; | ||||||
|  | import MkButton from '@/components/MkButton.vue'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { Autocomplete } from '@/utility/autocomplete.js'; | import { Autocomplete } from '@/utility/autocomplete.js'; | ||||||
| import type { SuggestionType } from '@/utility/autocomplete.js'; |  | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	modelValue: string | number | null; | 	modelValue: string | number | null; | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		<div :class="$style.items"> | 		<div :class="$style.items"> | ||||||
| 			<div> | 			<div> | ||||||
| 				<div :class="$style.label">{{ i18n.ts.invitationCode }}</div> | 				<div :class="$style.label">{{ i18n.ts.invitationCode }}</div> | ||||||
| 				<div>{{ invite.code }}</div> | 				<div class="_selectableAtomic">{{ invite.code }}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div v-if="moderator"> | 			<div v-if="moderator"> | ||||||
| 				<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div> | 				<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<div :class="$style.key"> | 	<div :class="$style.key"> | ||||||
| 		<slot name="key"></slot> | 		<slot name="key"></slot> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div :class="$style.value"> | 	<div :class="$style.value" class="_selectable"> | ||||||
| 		<slot name="value"></slot> | 		<slot name="value"></slot> | ||||||
| 		<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button> | 		<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button> | ||||||
| 	</div> | 	</div> | ||||||
|   | |||||||
| @@ -15,9 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	@focusin.passive.stop="() => {}" | 	@focusin.passive.stop="() => {}" | ||||||
| > | > | ||||||
| 	<div | 	<div | ||||||
| 		ref="itemsEl" |  | ||||||
| 		v-hotkey="keymap" |  | ||||||
| 		tabindex="0" |  | ||||||
| 		class="_popup _shadow" | 		class="_popup _shadow" | ||||||
| 		:class="$style.menu" | 		:class="$style.menu" | ||||||
| 		:style="{ | 		:style="{ | ||||||
| @@ -26,6 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		}" | 		}" | ||||||
| 		@keydown.stop="() => {}" | 		@keydown.stop="() => {}" | ||||||
| 		@contextmenu.self.prevent="() => {}" | 		@contextmenu.self.prevent="() => {}" | ||||||
|  | 	> | ||||||
|  | 		<slot name="header"></slot> | ||||||
|  | 		<div | ||||||
|  | 			ref="itemsEl" | ||||||
|  | 			v-hotkey="keymap" | ||||||
|  | 			tabindex="0" | ||||||
|  | 			:class="$style.menuItems" | ||||||
| 		> | 		> | ||||||
| 			<template v-for="item in (items2 ?? [])"> | 			<template v-for="item in (items2 ?? [])"> | ||||||
| 				<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div> | 				<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div> | ||||||
| @@ -169,6 +173,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				<span>{{ i18n.ts.none }}</span> | 				<span>{{ i18n.ts.none }}</span> | ||||||
| 			</span> | 			</span> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<slot name="footer"></slot> | ||||||
|  | 	</div> | ||||||
| 	<div v-if="childMenu"> | 	<div v-if="childMenu"> | ||||||
| 		<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/> | 		<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -429,7 +435,7 @@ onBeforeUnmount(() => { | |||||||
| .root { | .root { | ||||||
| 	&.center { | 	&.center { | ||||||
| 		> .menu { | 		> .menu { | ||||||
| 			> .item { | 			.item { | ||||||
| 				text-align: center; | 				text-align: center; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -439,7 +445,7 @@ onBeforeUnmount(() => { | |||||||
| 		> .menu { | 		> .menu { | ||||||
| 			min-width: 230px; | 			min-width: 230px; | ||||||
|  |  | ||||||
| 			> .item { | 			.item { | ||||||
| 				padding: 6px 20px; | 				padding: 6px 20px; | ||||||
| 				font-size: 0.95em; | 				font-size: 0.95em; | ||||||
| 				line-height: 24px; | 				line-height: 24px; | ||||||
| @@ -452,12 +458,14 @@ onBeforeUnmount(() => { | |||||||
| 		margin: auto; | 		margin: auto; | ||||||
|  |  | ||||||
| 		> .menu { | 		> .menu { | ||||||
| 			padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; |  | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
| 			border-radius: 24px; | 			border-radius: 24px; | ||||||
| 			border-bottom-right-radius: 0; | 			border-bottom-right-radius: 0; | ||||||
| 			border-bottom-left-radius: 0; | 			border-bottom-left-radius: 0; | ||||||
|  |  | ||||||
|  | 			> .menuItems { | ||||||
|  | 				padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; | ||||||
|  |  | ||||||
| 				> .item { | 				> .item { | ||||||
| 					font-size: 1em; | 					font-size: 1em; | ||||||
| 					padding: 12px 24px; | 					padding: 12px 24px; | ||||||
| @@ -478,10 +486,10 @@ onBeforeUnmount(() => { | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| .menu { | .menu { | ||||||
| 	padding: 8px 0; |  | ||||||
| 	box-sizing: border-box; | 	box-sizing: border-box; | ||||||
| 	max-width: 100vw; | 	max-width: 100vw; | ||||||
| 	min-width: 200px; | 	min-width: 200px; | ||||||
| @@ -493,6 +501,11 @@ onBeforeUnmount(() => { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .menuItems { | ||||||
|  | 	padding: 8px 0; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  | } | ||||||
|  |  | ||||||
| .item { | .item { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: center; | 	align-items: center; | ||||||
|   | |||||||
| @@ -76,12 +76,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 							:emojiUrls="appearNote.emojis" | 							:emojiUrls="appearNote.emojis" | ||||||
| 							:enableEmojiMenu="true" | 							:enableEmojiMenu="true" | ||||||
| 							:enableEmojiMenuReaction="true" | 							:enableEmojiMenuReaction="true" | ||||||
|  | 							class="_selectable" | ||||||
| 						/> | 						/> | ||||||
| 						<div v-if="translating || translation" :class="$style.translation"> | 						<div v-if="translating || translation" :class="$style.translation"> | ||||||
| 							<MkLoading v-if="translating" mini/> | 							<MkLoading v-if="translating" mini/> | ||||||
| 							<div v-else-if="translation"> | 							<div v-else-if="translation"> | ||||||
| 								<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> | 								<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> | ||||||
| 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> | 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/> | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| @@ -223,6 +224,7 @@ import { focusPrev, focusNext } from '@/utility/focus.js'; | |||||||
| import { getAppearNote } from '@/utility/get-appear-note.js'; | import { getAppearNote } from '@/utility/get-appear-note.js'; | ||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
| import { getPluginHandlers } from '@/plugin.js'; | import { getPluginHandlers } from '@/plugin.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| @@ -233,7 +235,7 @@ const props = withDefaults(defineProps<{ | |||||||
| 	mock: false, | 	mock: false, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| provide('mock', props.mock); | provide(DI.mock, props.mock); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'reaction', emoji: string): void; | 	(ev: 'reaction', emoji: string): void; | ||||||
|   | |||||||
| @@ -97,13 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					:emojiUrls="appearNote.emojis" | 					:emojiUrls="appearNote.emojis" | ||||||
| 					:enableEmojiMenu="true" | 					:enableEmojiMenu="true" | ||||||
| 					:enableEmojiMenuReaction="true" | 					:enableEmojiMenuReaction="true" | ||||||
|  | 					class="_selectable" | ||||||
| 				/> | 				/> | ||||||
| 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> | 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> | ||||||
| 				<div v-if="translating || translation" :class="$style.translation"> | 				<div v-if="translating || translation" :class="$style.translation"> | ||||||
| 					<MkLoading v-if="translating" mini/> | 					<MkLoading v-if="translating" mini/> | ||||||
| 					<div v-else-if="translation"> | 					<div v-else-if="translation"> | ||||||
| 						<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> | 						<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b> | ||||||
| 						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> | 						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div v-if="appearNote.files && appearNote.files.length > 0"> | 				<div v-if="appearNote.files && appearNote.files.length > 0"> | ||||||
|   | |||||||
| @@ -40,12 +40,13 @@ import * as Misskey from 'misskey-js'; | |||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { notePage } from '@/filters/note.js'; | import { notePage } from '@/filters/note.js'; | ||||||
| import { userPage } from '@/filters/user.js'; | import { userPage } from '@/filters/user.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| defineProps<{ | defineProps<{ | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const mock = inject<boolean>('mock', false); | const mock = inject(DI.mock, false); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" module> | <style lang="scss" module> | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad"> | <MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad"> | ||||||
| 	<template #empty> | 	<template #empty> | ||||||
| 		<div class="_fullinfo"> | 		<div class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.noNotes }}</div> | 			<div>{{ i18n.ts.noNotes }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</template> | 	</template> | ||||||
| @@ -33,10 +33,10 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { shallowRef } from 'vue'; | import { shallowRef } from 'vue'; | ||||||
|  | import type { Paging } from '@/components/MkPagination.vue'; | ||||||
| import MkNote from '@/components/MkNote.vue'; | import MkNote from '@/components/MkNote.vue'; | ||||||
| import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; | import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; | ||||||
| import MkPagination from '@/components/MkPagination.vue'; | import MkPagination from '@/components/MkPagination.vue'; | ||||||
| import type { Paging } from '@/components/MkPagination.vue'; |  | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { infoImageUrl } from '@/instance.js'; | import { infoImageUrl } from '@/instance.js'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<MkPagination ref="pagingComponent" :pagination="pagination"> | 	<MkPagination ref="pagingComponent" :pagination="pagination"> | ||||||
| 		<template #empty> | 		<template #empty> | ||||||
| 			<div class="_fullinfo"> | 			<div class="_fullinfo"> | ||||||
| 				<img :src="infoImageUrl" class="_ghost"/> | 				<img :src="infoImageUrl" draggable="false"/> | ||||||
| 				<div>{{ i18n.ts.noNotifications }}</div> | 				<div>{{ i18n.ts.noNotifications }}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</template> | 		</template> | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<div v-else-if="empty" key="_empty_" class="empty"> | 	<div v-else-if="empty" key="_empty_" class="empty"> | ||||||
| 		<slot name="empty"> | 		<slot name="empty"> | ||||||
| 			<div class="_fullinfo"> | 			<div class="_fullinfo"> | ||||||
| 				<img :src="infoImageUrl" class="_ghost"/> | 				<img :src="infoImageUrl" draggable="false"/> | ||||||
| 				<div>{{ i18n.ts.nothing }}</div> | 				<div>{{ i18n.ts.nothing }}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</slot> | 		</slot> | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		</div> | 		</div> | ||||||
| 		<div :class="$style.headerRight"> | 		<div :class="$style.headerRight"> | ||||||
| 			<template v-if="!(channel != null && fixed)"> | 			<template v-if="!(channel != null && fixed)"> | ||||||
| 				<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility"> | 				<button v-if="channel == null" ref="visibilityButton" 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 === 'public'"><i class="ti ti-world"></i></span> | ||||||
| 					<span v-if="visibility === 'home'"><i class="ti ti-home"></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> | 					<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span> | ||||||
| @@ -32,15 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					<span :class="$style.headerRightButtonText">{{ channel.name }}</span> | 					<span :class="$style.headerRightButtonText">{{ channel.name }}</span> | ||||||
| 				</button> | 				</button> | ||||||
| 			</template> | 			</template> | ||||||
| 			<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"> | 			<button 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-if="!localOnly"><i class="ti ti-rocket"></i></span> | ||||||
| 				<span v-else><i class="ti ti-rocket-off"></i></span> | 				<span v-else><i class="ti ti-rocket-off"></i></span> | ||||||
| 			</button> | 			</button> | ||||||
| 			<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance"> | 			<button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button> | ||||||
| 				<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"> | 			<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post"> | ||||||
| 				<div :class="$style.submitInner"> | 				<div :class="$style.submitInner"> | ||||||
| 					<template v-if="posted"></template> | 					<template v-if="posted"></template> | ||||||
| @@ -111,6 +107,7 @@ import { toASCII } from 'punycode.js'; | |||||||
| import { host, url } from '@@/js/config.js'; | import { host, url } from '@@/js/config.js'; | ||||||
| import type { ShallowRef } from 'vue'; | import type { ShallowRef } from 'vue'; | ||||||
| import type { PostFormProps } from '@/types/post-form.js'; | import type { PostFormProps } from '@/types/post-form.js'; | ||||||
|  | import type { MenuItem } from '@/types/menu.js'; | ||||||
| import type { PollEditorModelValue } from '@/components/MkPollEditor.vue'; | import type { PollEditorModelValue } from '@/components/MkPollEditor.vue'; | ||||||
| import MkNotePreview from '@/components/MkNotePreview.vue'; | import MkNotePreview from '@/components/MkNotePreview.vue'; | ||||||
| import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; | import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; | ||||||
| @@ -138,6 +135,7 @@ import { emojiPicker } from '@/utility/emoji-picker.js'; | |||||||
| import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js'; | import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js'; | ||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
| import { getPluginHandlers } from '@/plugin.js'; | import { getPluginHandlers } from '@/plugin.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| const $i = signinRequired(); | const $i = signinRequired(); | ||||||
|  |  | ||||||
| @@ -155,7 +153,7 @@ const props = withDefaults(defineProps<PostFormProps & { | |||||||
| 	initialLocalOnly: undefined, | 	initialLocalOnly: undefined, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| provide('mock', props.mock); | provide(DI.mock, props.mock); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'posted'): void; | 	(ev: 'posted'): void; | ||||||
| @@ -170,6 +168,7 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null); | |||||||
| const cwInputEl = shallowRef<HTMLInputElement | null>(null); | const cwInputEl = shallowRef<HTMLInputElement | null>(null); | ||||||
| const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null); | const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null); | ||||||
| const visibilityButton = shallowRef<HTMLElement>(); | const visibilityButton = shallowRef<HTMLElement>(); | ||||||
|  | const otherSettingsButton = shallowRef<HTMLElement>(); | ||||||
|  |  | ||||||
| const posting = ref(false); | const posting = ref(false); | ||||||
| const posted = ref(false); | const posted = ref(false); | ||||||
| @@ -555,6 +554,47 @@ async function toggleReactionAcceptance() { | |||||||
| 	reactionAcceptance.value = select.result; | 	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 menuDef = [{ | ||||||
|  | 		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[]; | ||||||
|  |  | ||||||
|  | 	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkPostFormOtherMenu.vue')), { | ||||||
|  | 		items: menuDef, | ||||||
|  | 		textLength: textLength.value, | ||||||
|  | 		src: otherSettingsButton.value, | ||||||
|  | 	}, { | ||||||
|  | 		closed: () => dispose(), | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | //#endregion | ||||||
|  |  | ||||||
| function pushVisibleUser(user: Misskey.entities.UserDetailed) { | function pushVisibleUser(user: Misskey.entities.UserDetailed) { | ||||||
| 	if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) { | 	if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) { | ||||||
| 		visibleUsers.value.push(user); | 		visibleUsers.value.push(user); | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ import * as os from '@/os.js'; | |||||||
| import { misskeyApi } from '@/utility/misskey-api.js'; | import { misskeyApi } from '@/utility/misskey-api.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | ||||||
|  |  | ||||||
| @@ -50,7 +51,7 @@ const props = defineProps<{ | |||||||
| 	detachMediaFn?: (id: string) => void; | 	detachMediaFn?: (id: string) => void; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const mock = inject<boolean>('mock', false); | const mock = inject(DI.mock, false); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void; | 	(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void; | ||||||
|   | |||||||
							
								
								
									
										128
									
								
								packages/frontend/src/components/MkPostFormOtherMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								packages/frontend/src/components/MkPostFormOtherMenu.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | |||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: syuilo and misskey-project | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  | <MkModal ref="modal" v-slot="{ type, maxHeight }" :zPriority="'high'" :src="src" :transparentBg="true" @click="modal?.close()" @closed="emit('closed')" @esc="modal?.close()"> | ||||||
|  | 	<MkMenu | ||||||
|  | 		:items="items" | ||||||
|  | 		:align="align" | ||||||
|  | 		:width="width" | ||||||
|  | 		:maxHeight="maxHeight" | ||||||
|  | 		:asDrawer="type === 'drawer'" | ||||||
|  | 		@close="modal?.close()" | ||||||
|  | 	> | ||||||
|  | 		<template #header> | ||||||
|  | 			<div :class="[$style.textCountRoot, { [$style.asDrawer]: type === 'drawer' }]"> | ||||||
|  | 				<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> | ||||||
|  | 	</MkMenu> | ||||||
|  | </MkModal> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed, useTemplateRef } from 'vue'; | ||||||
|  | import * as Misskey from 'misskey-js'; | ||||||
|  | import MkModal from '@/components/MkModal.vue'; | ||||||
|  | import MkMenu from '@/components/MkMenu.vue'; | ||||||
|  | import { instance } from '@/instance.js'; | ||||||
|  | import { i18n } from '@/i18n.js'; | ||||||
|  | import number from '@/filters/number.js'; | ||||||
|  | import type { MenuItem } from '@/types/menu.js'; | ||||||
|  |  | ||||||
|  | const modal = useTemplateRef('modal'); | ||||||
|  |  | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	items: MenuItem[]; | ||||||
|  | 	textLength: number; | ||||||
|  | 	align?: 'center' | string; | ||||||
|  | 	width?: number; | ||||||
|  | 	src?: HTMLElement; | ||||||
|  | }>(); | ||||||
|  |  | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'closed'): void; | ||||||
|  | }>(); | ||||||
|  |  | ||||||
|  | const maxTextLength = computed(() => { | ||||||
|  | 	return instance ? instance.maxNoteTextLength : 1000; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const textCountPercentage = computed(() => { | ||||||
|  | 	return props.textLength / maxTextLength.value * 100; | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" module> | ||||||
|  | .textCountRoot { | ||||||
|  | 	--textCountBg: color-mix(in srgb, var(--MI_THEME-panel), var(--MI_THEME-fg) 15%); | ||||||
|  | 	background-color: var(--textCountBg); | ||||||
|  | 	padding: 10px 14px; | ||||||
|  |  | ||||||
|  | 	&.asDrawer { | ||||||
|  | 		padding: 12px 24px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .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(--textCountBg); | ||||||
|  | 			top: 50%; | ||||||
|  | 			left: 50%; | ||||||
|  | 			transform: translate(-50%, -50%); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	.textCountCurrent { | ||||||
|  | 		color: var(--countColor); | ||||||
|  | 		font-weight: 700; | ||||||
|  | 		font-size: 18px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	@click="toggleReaction()" | 	@click="toggleReaction()" | ||||||
| 	@contextmenu.prevent.stop="menu" | 	@contextmenu.prevent.stop="menu" | ||||||
| > | > | ||||||
| 	<MkReactionIcon :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/> | 	<MkReactionIcon style="pointer-events: none;" :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/> | ||||||
| 	<span :class="$style.count">{{ count }}</span> | 	<span :class="$style.count">{{ count }}</span> | ||||||
| </button> | </button> | ||||||
| </template> | </template> | ||||||
| @@ -35,6 +35,7 @@ import * as sound from '@/utility/sound.js'; | |||||||
| import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; | import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; | ||||||
| import { customEmojisMap } from '@/custom-emojis.js'; | import { customEmojisMap } from '@/custom-emojis.js'; | ||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	reaction: string; | 	reaction: string; | ||||||
| @@ -43,7 +44,7 @@ const props = defineProps<{ | |||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const mock = inject<boolean>('mock', false); | const mock = inject(DI.mock, false); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'reactionToggled', emoji: string, newCount: number): void; | 	(ev: 'reactionToggled', emoji: string, newCount: number): void; | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import * as Misskey from 'misskey-js'; | |||||||
| import { inject, watch, ref } from 'vue'; | import { inject, watch, ref } from 'vue'; | ||||||
| import XReaction from '@/components/MkReactionsViewer.reaction.vue'; | import XReaction from '@/components/MkReactionsViewer.reaction.vue'; | ||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
|  | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
| @@ -30,7 +31,7 @@ const props = withDefaults(defineProps<{ | |||||||
| 	maxNumber: Infinity, | 	maxNumber: Infinity, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const mock = inject<boolean>('mock', false); | const mock = inject(DI.mock, false); | ||||||
|  |  | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; | 	(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <div> | <div class="_selectable"> | ||||||
| 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div> | 	<div :class="$style.label" @click="focus"><slot name="label"></slot></div> | ||||||
| 	<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;"> | 	<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;"> | ||||||
| 		<textarea | 		<textarea | ||||||
| @@ -38,10 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue'; | import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue'; | ||||||
| import { debounce } from 'throttle-debounce'; | import { debounce } from 'throttle-debounce'; | ||||||
|  | import type { SuggestionType } from '@/utility/autocomplete.js'; | ||||||
| import MkButton from '@/components/MkButton.vue'; | import MkButton from '@/components/MkButton.vue'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { Autocomplete } from '@/utility/autocomplete.js'; | import { Autocomplete } from '@/utility/autocomplete.js'; | ||||||
| import type { SuggestionType } from '@/utility/autocomplete.js'; |  | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	modelValue: string | null; | 	modelValue: string | null; | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <MkPagination :pagination="pagination"> | <MkPagination :pagination="pagination"> | ||||||
| 	<template #empty> | 	<template #empty> | ||||||
| 		<div class="_fullinfo"> | 		<div class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.noUsers }}</div> | 			<div>{{ i18n.ts.noUsers }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</template> | 	</template> | ||||||
| @@ -21,9 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|  | import type { Paging } from '@/components/MkPagination.vue'; | ||||||
| import MkUserInfo from '@/components/MkUserInfo.vue'; | import MkUserInfo from '@/components/MkUserInfo.vue'; | ||||||
| import MkPagination from '@/components/MkPagination.vue'; | import MkPagination from '@/components/MkPagination.vue'; | ||||||
| import type { Paging } from '@/components/MkPagination.vue'; |  | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { infoImageUrl } from '@/instance.js'; | import { infoImageUrl } from '@/instance.js'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,6 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				translate: getDecorationOffset(decoration), | 				translate: getDecorationOffset(decoration), | ||||||
| 			}" | 			}" | ||||||
| 			alt="" | 			alt="" | ||||||
|  | 			draggable="false" | ||||||
|  | 			style="-webkit-user-drag: none;" | ||||||
| 		> | 		> | ||||||
| 	</template> | 	</template> | ||||||
| </component> | </component> | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" | 	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" | ||||||
| 	src="/client-assets/dummy.png" | 	src="/client-assets/dummy.png" | ||||||
| 	:title="alt" | 	:title="alt" | ||||||
|  | 	draggable="false" | ||||||
|  | 	style="-webkit-user-drag: none;" | ||||||
| /> | /> | ||||||
| <span v-else-if="errored">:{{ customEmojiName }}:</span> | <span v-else-if="errored">:{{ customEmojiName }}:</span> | ||||||
| <img | <img | ||||||
| @@ -18,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	:alt="alt" | 	:alt="alt" | ||||||
| 	:title="alt" | 	:title="alt" | ||||||
| 	decoding="async" | 	decoding="async" | ||||||
|  | 	draggable="false" | ||||||
| 	@error="errored = true" | 	@error="errored = true" | ||||||
| 	@load="errored = false" | 	@load="errored = false" | ||||||
| 	@click="onClick" | 	@click="onClick" | ||||||
| @@ -157,6 +160,7 @@ async function edit(name: string) { | |||||||
| .root { | .root { | ||||||
| 	height: 2em; | 	height: 2em; | ||||||
| 	vertical-align: middle; | 	vertical-align: middle; | ||||||
|  | 	-webkit-user-drag: none; | ||||||
| 	transition: transform 0.2s ease; | 	transition: transform 0.2s ease; | ||||||
|  |  | ||||||
| 	&:hover { | 	&:hover { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <template> | <template> | ||||||
| <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> | <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> | ||||||
| 	<div :class="$style.root"> | 	<div :class="$style.root"> | ||||||
| 		<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> | 		<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/> | ||||||
| 		<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> | 		<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> | ||||||
| 		<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton> | 		<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton> | ||||||
| 	</div> | 	</div> | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| <!-- |  | ||||||
| SPDX-FileCopyrightText: syuilo and misskey-project |  | ||||||
| SPDX-License-Identifier: AGPL-3.0-only |  | ||||||
| --> |  | ||||||
|  |  | ||||||
| <template> |  | ||||||
| <div :class="[$style.spacer, store.r.darkMode.value ? $style.dark : $style.light]"></div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts" setup> |  | ||||||
| import { store } from '@/store.js'; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style lang="scss" module> |  | ||||||
| .spacer { |  | ||||||
| 	box-sizing: border-box; |  | ||||||
| 	padding: 32px; |  | ||||||
| 	margin: 0 auto; |  | ||||||
| 	height: 300px; |  | ||||||
| 	background-clip: content-box; |  | ||||||
| 	background-size: auto auto; |  | ||||||
| 	background-color: rgba(255, 255, 255, 0); |  | ||||||
|  |  | ||||||
| 	&.light { |  | ||||||
| 		background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #00000010 16px, #00000010 20px ); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	&.dark { |  | ||||||
| 		background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #FFFFFF16 16px, #FFFFFF16 20px ); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
| @@ -21,7 +21,6 @@ import MkError from './global/MkError.vue'; | |||||||
| import MkAd from './global/MkAd.vue'; | import MkAd from './global/MkAd.vue'; | ||||||
| import MkPageHeader from './global/MkPageHeader.vue'; | import MkPageHeader from './global/MkPageHeader.vue'; | ||||||
| import MkSpacer from './global/MkSpacer.vue'; | import MkSpacer from './global/MkSpacer.vue'; | ||||||
| import MkFooterSpacer from './global/MkFooterSpacer.vue'; |  | ||||||
| import MkStickyContainer from './global/MkStickyContainer.vue'; | import MkStickyContainer from './global/MkStickyContainer.vue'; | ||||||
| import MkLazy from './global/MkLazy.vue'; | import MkLazy from './global/MkLazy.vue'; | ||||||
| import SearchMarker from './global/SearchMarker.vue'; | import SearchMarker from './global/SearchMarker.vue'; | ||||||
| @@ -55,7 +54,6 @@ export const components = { | |||||||
| 	MkAd: MkAd, | 	MkAd: MkAd, | ||||||
| 	MkPageHeader: MkPageHeader, | 	MkPageHeader: MkPageHeader, | ||||||
| 	MkSpacer: MkSpacer, | 	MkSpacer: MkSpacer, | ||||||
| 	MkFooterSpacer: MkFooterSpacer, |  | ||||||
| 	MkStickyContainer: MkStickyContainer, | 	MkStickyContainer: MkStickyContainer, | ||||||
| 	MkLazy: MkLazy, | 	MkLazy: MkLazy, | ||||||
| 	SearchMarker: SearchMarker, | 	SearchMarker: SearchMarker, | ||||||
| @@ -83,7 +81,6 @@ declare module '@vue/runtime-core' { | |||||||
| 		MkAd: typeof MkAd; | 		MkAd: typeof MkAd; | ||||||
| 		MkPageHeader: typeof MkPageHeader; | 		MkPageHeader: typeof MkPageHeader; | ||||||
| 		MkSpacer: typeof MkSpacer; | 		MkSpacer: typeof MkSpacer; | ||||||
| 		MkFooterSpacer: typeof MkFooterSpacer; |  | ||||||
| 		MkStickyContainer: typeof MkStickyContainer; | 		MkStickyContainer: typeof MkStickyContainer; | ||||||
| 		MkLazy: typeof MkLazy; | 		MkLazy: typeof MkLazy; | ||||||
| 		SearchMarker: typeof SearchMarker; | 		SearchMarker: typeof SearchMarker; | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ import type { IRouter } from '@/nirax.js'; | |||||||
| export const DI = { | export const DI = { | ||||||
| 	routerCurrentDepth: Symbol() as InjectionKey<number>, | 	routerCurrentDepth: Symbol() as InjectionKey<number>, | ||||||
| 	router: Symbol() as InjectionKey<IRouter>, | 	router: Symbol() as InjectionKey<IRouter>, | ||||||
|  | 	mock: Symbol() as InjectionKey<boolean>, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -5,4 +5,4 @@ | |||||||
|  |  | ||||||
| import { numberFormat } from '@@/js/intl-const.js'; | import { numberFormat } from '@@/js/intl-const.js'; | ||||||
|  |  | ||||||
| export default n => n == null ? 'N/A' : numberFormat.format(n); | export default (n?: number) => n == null ? 'N/A' : numberFormat.format(n); | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <MkLoading v-if="!loaded"/> | <MkLoading v-if="!loaded"/> | ||||||
| <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> | <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> | ||||||
| 	<div v-show="loaded" :class="$style.root"> | 	<div v-show="loaded" :class="$style.root"> | ||||||
| 		<img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/> | 		<img :src="serverErrorImageUrl" draggable="false" :class="$style.img"/> | ||||||
| 		<div class="_gaps"> | 		<div class="_gaps"> | ||||||
| 			<div><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></div> | 			<div><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></div> | ||||||
| 			<div v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</div> | 			<div v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</div> | ||||||
|   | |||||||
| @@ -17,11 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { ref, computed } from 'vue'; | import { ref, computed } from 'vue'; | ||||||
| import type { Ref } from 'vue'; | import * as config from '@@/js/config.js'; | ||||||
| import XQueue from './queue.chart.vue'; | import XQueue from './queue.chart.vue'; | ||||||
| import XHeader from './_header_.vue'; | import XHeader from './_header_.vue'; | ||||||
|  | import type { Ref } from 'vue'; | ||||||
| import * as os from '@/os.js'; | import * as os from '@/os.js'; | ||||||
| import * as config from '@@/js/config.js'; |  | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { definePage } from '@/page.js'; | import { definePage } from '@/page.js'; | ||||||
| import MkButton from '@/components/MkButton.vue'; | import MkButton from '@/components/MkButton.vue'; | ||||||
| @@ -54,14 +54,7 @@ function promoteAllQueues() { | |||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| const headerActions = computed(() => [{ | const headerActions = computed(() => []); | ||||||
| 	asFullButton: true, |  | ||||||
| 	icon: 'ti ti-external-link', |  | ||||||
| 	text: i18n.ts.dashboard, |  | ||||||
| 	handler: () => { |  | ||||||
| 		window.open(config.url + '/queue', '_blank', 'noopener'); |  | ||||||
| 	}, |  | ||||||
| }]); |  | ||||||
|  |  | ||||||
| const headerTabs = computed(() => [{ | const headerTabs = computed(() => [{ | ||||||
| 	key: 'deliver', | 	key: 'deliver', | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 						<MkPagination :pagination="usersPagination"> | 						<MkPagination :pagination="usersPagination"> | ||||||
| 							<template #empty> | 							<template #empty> | ||||||
| 								<div class="_fullinfo"> | 								<div class="_fullinfo"> | ||||||
| 									<img :src="infoImageUrl" class="_ghost"/> | 									<img :src="infoImageUrl" draggable="false"/> | ||||||
| 									<div>{{ i18n.ts.noUsers }}</div> | 									<div>{{ i18n.ts.noUsers }}</div> | ||||||
| 								</div> | 								</div> | ||||||
| 							</template> | 							</template> | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div v-else class="_fullinfo"> | 	<div v-else class="_fullinfo"> | ||||||
| 		<img :src="infoImageUrl" class="_ghost"/> | 		<img :src="infoImageUrl" draggable="false"/> | ||||||
| 		<div>{{ i18n.ts.nothing }}</div> | 		<div>{{ i18n.ts.nothing }}</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		<MkPagination :pagination="pagination"> | 		<MkPagination :pagination="pagination"> | ||||||
| 			<template #empty> | 			<template #empty> | ||||||
| 				<div class="_fullinfo"> | 				<div class="_fullinfo"> | ||||||
| 					<img :src="infoImageUrl" class="_ghost"/> | 					<img :src="infoImageUrl" draggable="false"/> | ||||||
| 					<div>{{ i18n.ts.noNotes }}</div> | 					<div>{{ i18n.ts.noNotes }}</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</template> | 			</template> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				<MkPagination ref="paginationComponent" :pagination="pagination"> | 				<MkPagination ref="paginationComponent" :pagination="pagination"> | ||||||
| 					<template #empty> | 					<template #empty> | ||||||
| 						<div class="_fullinfo"> | 						<div class="_fullinfo"> | ||||||
| 							<img :src="infoImageUrl" class="_ghost"/> | 							<img :src="infoImageUrl" draggable="false"/> | ||||||
| 							<div>{{ i18n.ts.noFollowRequests }}</div> | 							<div>{{ i18n.ts.noFollowRequests }}</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</template> | 					</template> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<template #header><MkPageHeader/></template> | 	<template #header><MkPageHeader/></template> | ||||||
| 	<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200"> | 	<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200"> | ||||||
| 		<div :class="$style.root"> | 		<div :class="$style.root"> | ||||||
| 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> | 			<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/> | ||||||
| 			<div :class="$style.text"> | 			<div :class="$style.text"> | ||||||
| 				<i class="ti ti-alert-triangle"></i> | 				<i class="ti ti-alert-triangle"></i> | ||||||
| 				{{ i18n.ts.nothing }} | 				{{ i18n.ts.nothing }} | ||||||
| @@ -36,12 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, ref, shallowRef } from 'vue'; | import { computed, ref, shallowRef } from 'vue'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
|  | import type { Paging } from '@/components/MkPagination.vue'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import * as os from '@/os.js'; | import * as os from '@/os.js'; | ||||||
| import { misskeyApi } from '@/utility/misskey-api.js'; | import { misskeyApi } from '@/utility/misskey-api.js'; | ||||||
| import MkButton from '@/components/MkButton.vue'; | import MkButton from '@/components/MkButton.vue'; | ||||||
| import MkPagination from '@/components/MkPagination.vue'; | import MkPagination from '@/components/MkPagination.vue'; | ||||||
| import type { Paging } from '@/components/MkPagination.vue'; |  | ||||||
| import MkInviteCode from '@/components/MkInviteCode.vue'; | import MkInviteCode from '@/components/MkInviteCode.vue'; | ||||||
| import { definePage } from '@/page.js'; | import { definePage } from '@/page.js'; | ||||||
| import { serverErrorImageUrl, instance } from '@/instance.js'; | import { serverErrorImageUrl, instance } from '@/instance.js'; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> | 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> | ||||||
| 	<MkSpacer v-if="error != null" :contentMax="1200"> | 	<MkSpacer v-if="error != null" :contentMax="1200"> | ||||||
| 		<div :class="$style.root"> | 		<div :class="$style.root"> | ||||||
| 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> | 			<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/> | ||||||
| 			<p :class="$style.text"> | 			<p :class="$style.text"> | ||||||
| 				<i class="ti ti-alert-triangle"></i> | 				<i class="ti ti-alert-triangle"></i> | ||||||
| 				{{ i18n.ts.nothing }} | 				{{ i18n.ts.nothing }} | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		<div> | 		<div> | ||||||
| 			<div v-if="antennas.length === 0" class="empty"> | 			<div v-if="antennas.length === 0" class="empty"> | ||||||
| 				<div class="_fullinfo"> | 				<div class="_fullinfo"> | ||||||
| 					<img :src="infoImageUrl" class="_ghost"/> | 					<img :src="infoImageUrl" draggable="false"/> | ||||||
| 					<div>{{ i18n.ts.nothing }}</div> | 					<div>{{ i18n.ts.nothing }}</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 		<div class="_gaps"> | 		<div class="_gaps"> | ||||||
| 			<div v-if="items.length === 0" class="empty"> | 			<div v-if="items.length === 0" class="empty"> | ||||||
| 				<div class="_fullinfo"> | 				<div class="_fullinfo"> | ||||||
| 					<img :src="infoImageUrl" class="_ghost"/> | 					<img :src="infoImageUrl" draggable="false"/> | ||||||
| 					<div>{{ i18n.ts.nothing }}</div> | 					<div>{{ i18n.ts.nothing }}</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<div class="_fullinfo"> | 	<div class="_fullinfo"> | ||||||
| 		<img :src="notFoundImageUrl" class="_ghost"/> | 		<img :src="notFoundImageUrl" draggable="false"/> | ||||||
| 		<div>{{ i18n.ts.notFoundDescription }}</div> | 		<div>{{ i18n.ts.notFoundDescription }}</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> | 	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> | ||||||
| 	<MkSpacer v-if="error != null" :contentMax="1200"> | 	<MkSpacer v-if="error != null" :contentMax="1200"> | ||||||
| 		<div :class="$style.root"> | 		<div :class="$style.root"> | ||||||
| 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> | 			<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/> | ||||||
| 			<p :class="$style.text"> | 			<p :class="$style.text"> | ||||||
| 				<i class="ti ti-alert-triangle"></i> | 				<i class="ti ti-alert-triangle"></i> | ||||||
| 				{{ error }} | 				{{ error }} | ||||||
| @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			<div v-if="role">{{ role.description }}</div> | 			<div v-if="role">{{ role.description }}</div> | ||||||
| 			<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/> | 			<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/> | ||||||
| 			<div v-else-if="!visible" class="_fullinfo"> | 			<div v-else-if="!visible" class="_fullinfo"> | ||||||
| 				<img :src="infoImageUrl" class="_ghost"/> | 				<img :src="infoImageUrl" draggable="false"/> | ||||||
| 				<div>{{ i18n.ts.nothing }}</div> | 				<div>{{ i18n.ts.nothing }}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700"> | 	<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700"> | ||||||
| 		<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/> | 		<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/> | ||||||
| 		<div v-else-if="!visible" class="_fullinfo"> | 		<div v-else-if="!visible" class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.nothing }}</div> | 			<div>{{ i18n.ts.nothing }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkSpacer> | 	</MkSpacer> | ||||||
| @@ -38,12 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, watch, ref } from 'vue'; | import { computed, watch, ref } from 'vue'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
|  | import { instanceName } from '@@/js/config.js'; | ||||||
| import { misskeyApi } from '@/utility/misskey-api.js'; | import { misskeyApi } from '@/utility/misskey-api.js'; | ||||||
| import MkUserList from '@/components/MkUserList.vue'; | import MkUserList from '@/components/MkUserList.vue'; | ||||||
| import { definePage } from '@/page.js'; | import { definePage } from '@/page.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import MkTimeline from '@/components/MkTimeline.vue'; | import MkTimeline from '@/components/MkTimeline.vue'; | ||||||
| import { instanceName } from '@@/js/config.js'; |  | ||||||
| import { serverErrorImageUrl, infoImageUrl } from '@/instance.js'; | import { serverErrorImageUrl, infoImageUrl } from '@/instance.js'; | ||||||
|  |  | ||||||
| const props = withDefaults(defineProps<{ | const props = withDefaults(defineProps<{ | ||||||
|   | |||||||
| @@ -58,6 +58,15 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					</MkSwitch> | 					</MkSwitch> | ||||||
| 				</MkPreferenceContainer> | 				</MkPreferenceContainer> | ||||||
| 			</SearchMarker> | 			</SearchMarker> | ||||||
|  |  | ||||||
|  | 			<SearchMarker :keywords="['text', 'selectable']"> | ||||||
|  | 				<MkPreferenceContainer k="makeEveryTextElementsSelectable"> | ||||||
|  | 					<MkSwitch v-model="makeEveryTextElementsSelectable"> | ||||||
|  | 						<template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template> | ||||||
|  | 						<template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template> | ||||||
|  | 					</MkSwitch> | ||||||
|  | 				</MkPreferenceContainer> | ||||||
|  | 			</SearchMarker> | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
| 		<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> | 		<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> | ||||||
| @@ -122,6 +131,7 @@ const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); | |||||||
| const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); | const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); | ||||||
| const contextMenu = prefer.model('contextMenu'); | const contextMenu = prefer.model('contextMenu'); | ||||||
| const menuStyle = prefer.model('menuStyle'); | const menuStyle = prefer.model('menuStyle'); | ||||||
|  | const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable'); | ||||||
|  |  | ||||||
| const fontSize = ref(miLocalStorage.getItem('fontSize')); | const fontSize = ref(miLocalStorage.getItem('fontSize')); | ||||||
| const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); | const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); | ||||||
| @@ -147,6 +157,7 @@ watch([ | |||||||
| 	contextMenu, | 	contextMenu, | ||||||
| 	fontSize, | 	fontSize, | ||||||
| 	useSystemFont, | 	useSystemFont, | ||||||
|  | 	makeEveryTextElementsSelectable, | ||||||
| ], async () => { | ], async () => { | ||||||
| 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<FormPagination ref="list" :pagination="pagination"> | 	<FormPagination ref="list" :pagination="pagination"> | ||||||
| 		<template #empty> | 		<template #empty> | ||||||
| 			<div class="_fullinfo"> | 			<div class="_fullinfo"> | ||||||
| 				<img :src="infoImageUrl" class="_ghost"/> | 				<img :src="infoImageUrl" draggable="false"/> | ||||||
| 				<div>{{ i18n.ts.nothing }}</div> | 				<div>{{ i18n.ts.nothing }}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</template> | 		</template> | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					</FormLink> | 					</FormLink> | ||||||
|  |  | ||||||
| 					<MkFolder :defaultOpen="true"> | 					<MkFolder :defaultOpen="true"> | ||||||
| 						<template #label><SearchLabel>{{ i18n.ts.manage }}</SearchLabel></template> | 						<template #label>{{ i18n.ts.manage }}</template> | ||||||
|  |  | ||||||
| 						<MkPagination :pagination="pagination"> | 						<MkPagination :pagination="pagination"> | ||||||
| 							<template #default="{items}"> | 							<template #default="{items}"> | ||||||
|   | |||||||
| @@ -27,7 +27,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkSpacer> | 	</MkSpacer> | ||||||
| 	<MkFooterSpacer/> |  | ||||||
| </mkstickycontainer> | </mkstickycontainer> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					<MkPagination :pagination="renoteMutingPagination"> | 					<MkPagination :pagination="renoteMutingPagination"> | ||||||
| 						<template #empty> | 						<template #empty> | ||||||
| 							<div class="_fullinfo"> | 							<div class="_fullinfo"> | ||||||
| 								<img :src="infoImageUrl" class="_ghost"/> | 								<img :src="infoImageUrl" draggable="false"/> | ||||||
| 								<div>{{ i18n.ts.noUsers }}</div> | 								<div>{{ i18n.ts.noUsers }}</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						</template> | 						</template> | ||||||
| @@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					<MkPagination :pagination="mutingPagination"> | 					<MkPagination :pagination="mutingPagination"> | ||||||
| 						<template #empty> | 						<template #empty> | ||||||
| 							<div class="_fullinfo"> | 							<div class="_fullinfo"> | ||||||
| 								<img :src="infoImageUrl" class="_ghost"/> | 								<img :src="infoImageUrl" draggable="false"/> | ||||||
| 								<div>{{ i18n.ts.noUsers }}</div> | 								<div>{{ i18n.ts.noUsers }}</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						</template> | 						</template> | ||||||
| @@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					<MkPagination :pagination="blockingPagination"> | 					<MkPagination :pagination="blockingPagination"> | ||||||
| 						<template #empty> | 						<template #empty> | ||||||
| 							<div class="_fullinfo"> | 							<div class="_fullinfo"> | ||||||
| 								<img :src="infoImageUrl" class="_ghost"/> | 								<img :src="infoImageUrl" draggable="false"/> | ||||||
| 								<div>{{ i18n.ts.noUsers }}</div> | 								<div>{{ i18n.ts.noUsers }}</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						</template> | 						</template> | ||||||
|   | |||||||
| @@ -89,7 +89,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 							<template #caption> | 							<template #caption> | ||||||
| 								<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div> | 								<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div> | ||||||
| 								<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div> | 								<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div> | ||||||
| 								<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div> |  | ||||||
| 							</template> | 							</template> | ||||||
| 						</MkSwitch> | 						</MkSwitch> | ||||||
| 					</SearchMarker> | 					</SearchMarker> | ||||||
|   | |||||||
| @@ -46,7 +46,16 @@ export const PREF_DEF = { | |||||||
| 	}, | 	}, | ||||||
| 	widgets: { | 	widgets: { | ||||||
| 		accountDependent: true, | 		accountDependent: true, | ||||||
| 		default: [] as { | 		default: [{ | ||||||
|  | 			name: 'calendar', | ||||||
|  | 			id: 'a', place: 'right', data: {}, | ||||||
|  | 		}, { | ||||||
|  | 			name: 'notifications', | ||||||
|  | 			id: 'b', place: 'right', data: {}, | ||||||
|  | 		}, { | ||||||
|  | 			name: 'trends', | ||||||
|  | 			id: 'c', place: 'right', data: {}, | ||||||
|  | 		}] as { | ||||||
| 			name: string; | 			name: string; | ||||||
| 			id: string; | 			id: string; | ||||||
| 			place: string | null; | 			place: string | null; | ||||||
| @@ -313,6 +322,9 @@ export const PREF_DEF = { | |||||||
| 	defaultFollowWithReplies: { | 	defaultFollowWithReplies: { | ||||||
| 		default: false, | 		default: false, | ||||||
| 	}, | 	}, | ||||||
|  | 	makeEveryTextElementsSelectable: { | ||||||
|  | 		default: DEFAULT_DEVICE_KIND === 'desktop', | ||||||
|  | 	}, | ||||||
| 	plugins: { | 	plugins: { | ||||||
| 		default: [] as Plugin[], | 		default: [] as Plugin[], | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -23,11 +23,10 @@ import { deepEqual } from '@/utility/deep-equal.js'; | |||||||
|  |  | ||||||
| type PREF = typeof PREF_DEF; | type PREF = typeof PREF_DEF; | ||||||
| type ValueOf<K extends keyof PREF> = PREF[K]['default']; | type ValueOf<K extends keyof PREF> = PREF[K]['default']; | ||||||
| type Account = string; // <host>/<userId> |  | ||||||
|  |  | ||||||
| type Scope = Partial<{ | type Scope = Partial<{ | ||||||
| 	server: string | null; // 将来のため | 	server: string | null; // host | ||||||
| 	account: Account | null; | 	account: string | null; // userId | ||||||
| 	device: string | null; // 将来のため | 	device: string | null; // 将来のため | ||||||
| }>; | }>; | ||||||
|  |  | ||||||
| @@ -39,7 +38,7 @@ type PrefRecord<K extends keyof PREF> = [scope: Scope, value: ValueOf<K>, meta: | |||||||
|  |  | ||||||
| function parseScope(scope: Scope): { | function parseScope(scope: Scope): { | ||||||
| 	server: string | null; | 	server: string | null; | ||||||
| 	account: Account | null; | 	account: string | null; | ||||||
| 	device: string | null; | 	device: string | null; | ||||||
| } { | } { | ||||||
| 	return { | 	return { | ||||||
| @@ -51,7 +50,7 @@ function parseScope(scope: Scope): { | |||||||
|  |  | ||||||
| function makeScope(scope: Partial<{ | function makeScope(scope: Partial<{ | ||||||
| 	server: string | null; | 	server: string | null; | ||||||
| 	account: Account | null; | 	account: string | null; | ||||||
| 	device: string | null; | 	device: string | null; | ||||||
| }>): Scope { | }>): Scope { | ||||||
| 	const c = {} as Scope; | 	const c = {} as Scope; | ||||||
| @@ -130,6 +129,10 @@ export class PreferencesManager { | |||||||
| 		return (PREF_DEF as PreferencesDefinition)[key].accountDependent === true; | 		return (PREF_DEF as PreferencesDefinition)[key].accountDependent === true; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private isServerDependentKey<K extends keyof PREF>(key: K): boolean { | ||||||
|  | 		return (PREF_DEF as PreferencesDefinition)[key].serverDependent === true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) { | 	private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) { | ||||||
| 		const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 | 		const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 | ||||||
| 		this.r[key].value = this.s[key] = v; | 		this.r[key].value = this.s[key] = v; | ||||||
| @@ -141,9 +144,19 @@ export class PreferencesManager { | |||||||
| 		this.rewriteRawState(key, value); | 		this.rewriteRawState(key, value); | ||||||
|  |  | ||||||
| 		const record = this.getMatchedRecordOf(key); | 		const record = this.getMatchedRecordOf(key); | ||||||
|  |  | ||||||
| 		if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) { | 		if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) { | ||||||
| 			this.profile.preferences[key].push([makeScope({ | 			this.profile.preferences[key].push([makeScope({ | ||||||
| 				account: `${host}/${$i!.id}`, | 				server: host, | ||||||
|  | 				account: $i!.id, | ||||||
|  | 			}), value, {}]); | ||||||
|  | 			this.save(); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (parseScope(record[0]).server == null && this.isServerDependentKey(key)) { | ||||||
|  | 			this.profile.preferences[key].push([makeScope({ | ||||||
|  | 				server: host, | ||||||
| 			}), value, {}]); | 			}), value, {}]); | ||||||
| 			this.save(); | 			this.save(); | ||||||
| 			return; | 			return; | ||||||
| @@ -291,16 +304,19 @@ export class PreferencesManager { | |||||||
|  |  | ||||||
| 		if ($i == null) return records.find(([scope, v]) => parseScope(scope).account == null)!; | 		if ($i == null) return records.find(([scope, v]) => parseScope(scope).account == null)!; | ||||||
|  |  | ||||||
| 		const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`); | 		const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id); | ||||||
| 		if (accountOverrideRecord) return accountOverrideRecord; | 		if (accountOverrideRecord) return accountOverrideRecord; | ||||||
|  |  | ||||||
|  | 		const serverOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account == null); | ||||||
|  | 		if (serverOverrideRecord) return serverOverrideRecord; | ||||||
|  |  | ||||||
| 		const record = records.find(([scope, v]) => parseScope(scope).account == null); | 		const record = records.find(([scope, v]) => parseScope(scope).account == null); | ||||||
| 		return record!; | 		return record!; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public isAccountOverrided<K extends keyof PREF>(key: K): boolean { | 	public isAccountOverrided<K extends keyof PREF>(key: K): boolean { | ||||||
| 		if ($i == null) return false; | 		if ($i == null) return false; | ||||||
| 		return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`) ?? false; | 		return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id) ?? false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public setAccountOverride<K extends keyof PREF>(key: K) { | 	public setAccountOverride<K extends keyof PREF>(key: K) { | ||||||
| @@ -310,7 +326,8 @@ export class PreferencesManager { | |||||||
|  |  | ||||||
| 		const records = this.profile.preferences[key]; | 		const records = this.profile.preferences[key]; | ||||||
| 		records.push([makeScope({ | 		records.push([makeScope({ | ||||||
| 			account: `${host}/${$i!.id}`, | 			server: host, | ||||||
|  | 			account: $i!.id, | ||||||
| 		}), this.s[key], {}]); | 		}), this.s[key], {}]); | ||||||
|  |  | ||||||
| 		this.save(); | 		this.save(); | ||||||
| @@ -322,7 +339,7 @@ export class PreferencesManager { | |||||||
|  |  | ||||||
| 		const records = this.profile.preferences[key]; | 		const records = this.profile.preferences[key]; | ||||||
|  |  | ||||||
| 		const index = records.findIndex(([scope, v]) => parseScope(scope).account === `${host}/${$i!.id}`); | 		const index = records.findIndex(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id); | ||||||
| 		if (index === -1) return; | 		if (index === -1) return; | ||||||
|  |  | ||||||
| 		records.splice(index, 1); | 		records.splice(index, 1); | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ | |||||||
|  |  | ||||||
| import { inject } from 'vue'; | import { inject } from 'vue'; | ||||||
| import type { IRouter } from '@/nirax.js'; | import type { IRouter } from '@/nirax.js'; | ||||||
| import { Router } from '@/nirax.js'; |  | ||||||
| import { mainRouter } from '@/router/main.js'; | import { mainRouter } from '@/router/main.js'; | ||||||
| import { DI } from '@/di.js'; | import { DI } from '@/di.js'; | ||||||
|  |  | ||||||
| @@ -14,7 +13,7 @@ import { DI } from '@/di.js'; | |||||||
|  * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない) |  * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない) | ||||||
|  */ |  */ | ||||||
| export function useRouter(): IRouter { | export function useRouter(): IRouter { | ||||||
| 	return inject<Router | null>(DI.router, null) ?? mainRouter; | 	return inject(DI.router, null) ?? mainRouter; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -178,7 +178,18 @@ export const store = markRaw(new Storage('base', { | |||||||
| 	}, | 	}, | ||||||
| 	menu: { | 	menu: { | ||||||
| 		where: 'deviceAccount', | 		where: 'deviceAccount', | ||||||
| 		default: [], | 		default: [ | ||||||
|  | 			'notifications', | ||||||
|  | 			'clips', | ||||||
|  | 			'drive', | ||||||
|  | 			'followRequests', | ||||||
|  | 			'-', | ||||||
|  | 			'explore', | ||||||
|  | 			'announcements', | ||||||
|  | 			'search', | ||||||
|  | 			'-', | ||||||
|  | 			'ui', | ||||||
|  | 		], | ||||||
| 	}, | 	}, | ||||||
| 	statusbars: { | 	statusbars: { | ||||||
| 		where: 'deviceAccount', | 		where: 'deviceAccount', | ||||||
|   | |||||||
| @@ -81,6 +81,11 @@ html { | |||||||
| 	&.useSystemFont { | 	&.useSystemFont { | ||||||
| 		font-family: system-ui; | 		font-family: system-ui; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	&:not(.forceSelectableAll) { | ||||||
|  | 		user-select: none; | ||||||
|  | 		-webkit-user-select: none; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| html._themeChanging_ { | html._themeChanging_ { | ||||||
| @@ -120,6 +125,8 @@ a { | |||||||
| textarea, input { | textarea, input { | ||||||
| 	tap-highlight-color: transparent; | 	tap-highlight-color: transparent; | ||||||
| 	-webkit-tap-highlight-color: transparent; | 	-webkit-tap-highlight-color: transparent; | ||||||
|  | 	user-select: text; | ||||||
|  | 	-webkit-user-select: text; | ||||||
| } | } | ||||||
|  |  | ||||||
| optgroup, option { | optgroup, option { | ||||||
| @@ -184,6 +191,16 @@ rt { | |||||||
| 	padding: 0.3em 0.5em; | 	padding: 0.3em 0.5em; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ._selectable { | ||||||
|  | 	user-select: text; | ||||||
|  | 	-webkit-user-select: text; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ._selectableAtomic { | ||||||
|  | 	user-select: all; | ||||||
|  | 	-webkit-user-select: all; | ||||||
|  | } | ||||||
|  |  | ||||||
| ._noSelect { | ._noSelect { | ||||||
| 	user-select: none; | 	user-select: none; | ||||||
| 	-webkit-user-select: none; | 	-webkit-user-select: none; | ||||||
| @@ -197,11 +214,6 @@ rt { | |||||||
| 	text-overflow: ellipsis; | 	text-overflow: ellipsis; | ||||||
| } | } | ||||||
|  |  | ||||||
| ._ghost { |  | ||||||
| 	@extend ._noSelect; |  | ||||||
| 	pointer-events: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ._modalBg { | ._modalBg { | ||||||
| 	position: fixed; | 	position: fixed; | ||||||
| 	top: 0; | 	top: 0; | ||||||
| @@ -424,6 +436,10 @@ rt { | |||||||
| 	color: var(--MI_THEME-link); | 	color: var(--MI_THEME-link); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ._love { | ||||||
|  | 	color: var(--MI_THEME-love); | ||||||
|  | } | ||||||
|  |  | ||||||
| ._caption { | ._caption { | ||||||
| 	font-size: 0.8em; | 	font-size: 0.8em; | ||||||
| 	opacity: 0.7; | 	opacity: 0.7; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<div class="body"> | 	<div class="body"> | ||||||
| 		<div class="left"> | 		<div class="left"> | ||||||
| 			<button v-click-anime class="item _button instance" @click="openInstanceMenu"> | 			<button v-click-anime class="item _button instance" @click="openInstanceMenu"> | ||||||
| 				<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/> | 				<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/> | ||||||
| 			</button> | 			</button> | ||||||
| 			<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact> | 			<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact> | ||||||
| 				<i class="ti ti-home ti-fw"></i> | 				<i class="ti ti-home ti-fw"></i> | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<div class="divider"></div> | 	<div class="divider"></div> | ||||||
| 	<div class="about"> | 	<div class="about"> | ||||||
| 		<button v-click-anime class="item _button" @click="openInstanceMenu"> | 		<button v-click-anime class="item _button" @click="openInstanceMenu"> | ||||||
| 			<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/> | 			<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/> | ||||||
| 		</button> | 		</button> | ||||||
| 	</div> | 	</div> | ||||||
| 	<!--<MisskeyLogo class="misskey"/>--> | 	<!--<MisskeyLogo class="misskey"/>--> | ||||||
|   | |||||||
| @@ -144,19 +144,6 @@ if (window.innerWidth < 1024) { | |||||||
|  |  | ||||||
| document.documentElement.style.overflowY = 'scroll'; | document.documentElement.style.overflowY = 'scroll'; | ||||||
|  |  | ||||||
| if (prefer.s.widgets.length === 0) { |  | ||||||
| 	prefer.commit('widgets', [{ |  | ||||||
| 		name: 'calendar', |  | ||||||
| 		id: 'a', place: null, data: {}, |  | ||||||
| 	}, { |  | ||||||
| 		name: 'notifications', |  | ||||||
| 		id: 'b', place: null, data: {}, |  | ||||||
| 	}, { |  | ||||||
| 		name: 'trends', |  | ||||||
| 		id: 'c', place: null, data: {}, |  | ||||||
| 	}]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	window.addEventListener('resize', () => { | 	window.addEventListener('resize', () => { | ||||||
| 		isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD); | 		isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD); | ||||||
|   | |||||||
| @@ -178,19 +178,6 @@ if (window.innerWidth > 1024) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| if (prefer.s.widgets.length === 0) { |  | ||||||
| 	prefer.commit('widgets', [{ |  | ||||||
| 		name: 'calendar', |  | ||||||
| 		id: 'a', place: 'right', data: {}, |  | ||||||
| 	}, { |  | ||||||
| 		name: 'notifications', |  | ||||||
| 		id: 'b', place: 'right', data: {}, |  | ||||||
| 	}, { |  | ||||||
| 		name: 'trends', |  | ||||||
| 		id: 'c', place: 'right', data: {}, |  | ||||||
| 	}]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	if (!isDesktop.value) { | 	if (!isDesktop.value) { | ||||||
| 		window.addEventListener('resize', () => { | 		window.addEventListener('resize', () => { | ||||||
|   | |||||||
| @@ -248,12 +248,12 @@ export const searchIndexes: SearchIndexItem[] = [ | |||||||
| 						keywords: ['login', 'signin'], | 						keywords: ['login', 'signin'], | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						id: 'lUtOQbnwi', | 						id: '5RbESWefG', | ||||||
| 						label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore, | 						label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore, | ||||||
| 						keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription], | 						keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription], | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						id: '83WWcjwS9', | 						id: 'hdzwDs3qd', | ||||||
| 						label: i18n.ts._accountSettings.makeNotesHiddenBefore, | 						label: i18n.ts._accountSettings.makeNotesHiddenBefore, | ||||||
| 						keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription], | 						keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription], | ||||||
| 					}, | 					}, | ||||||
| @@ -791,7 +791,7 @@ export const searchIndexes: SearchIndexItem[] = [ | |||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				id: '5VSGOVYR0', | 				id: '5VSGOVYR0', | ||||||
| 				label: i18n.ts.manage, | 				label: i18n.ts._settings.webhook, | ||||||
| 				keywords: ['webhook'], | 				keywords: ['webhook'], | ||||||
| 			}, | 			}, | ||||||
| 		], | 		], | ||||||
| @@ -897,22 +897,27 @@ export const searchIndexes: SearchIndexItem[] = [ | |||||||
| 				keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], | 				keywords: ['native', 'system', 'video', 'audio', 'player', 'media'], | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				id: '1fV9WINCQ', | 				id: 'b1GYEEJeh', | ||||||
|  | 				label: i18n.ts._settings.makeEveryTextElementsSelectable, | ||||||
|  | 				keywords: ['text', 'selectable'], | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				id: 'vVLxwINTJ', | ||||||
| 				label: i18n.ts.menuStyle, | 				label: i18n.ts.menuStyle, | ||||||
| 				keywords: ['menu', 'style', 'popup', 'drawer'], | 				keywords: ['menu', 'style', 'popup', 'drawer'], | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				id: 'mLQzlKUNu', | 				id: '14cMhMLHL', | ||||||
| 				label: i18n.ts._contextMenu.title, | 				label: i18n.ts._contextMenu.title, | ||||||
| 				keywords: ['contextmenu', 'system', 'native'], | 				keywords: ['contextmenu', 'system', 'native'], | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				id: 'yP96aA3j9', | 				id: 'oSo4LXMX9', | ||||||
| 				label: i18n.ts.fontSize, | 				label: i18n.ts.fontSize, | ||||||
| 				keywords: ['font', 'size'], | 				keywords: ['font', 'size'], | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				id: 'jQeiMopFE', | 				id: '7LQSAThST', | ||||||
| 				label: i18n.ts.useSystemFont, | 				label: i18n.ts.useSystemFont, | ||||||
| 				keywords: ['font', 'system', 'native'], | 				keywords: ['font', 'system', 'native'], | ||||||
| 			}, | 			}, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar> | 			<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else :class="$style.bdayFFallback"> | 		<div v-else :class="$style.bdayFFallback"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost" :class="$style.bdayFFallbackImage"/> | 			<img :src="infoImageUrl" draggable="false" :class="$style.bdayFFallbackImage"/> | ||||||
| 			<div>{{ i18n.ts.nothing }}</div> | 			<div>{{ i18n.ts.nothing }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<div class="ekmkgxbj"> | 	<div class="ekmkgxbj"> | ||||||
| 		<MkLoading v-if="fetching"/> | 		<MkLoading v-if="fetching"/> | ||||||
| 		<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo"> | 		<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo"> | ||||||
| 			<img :src="infoImageUrl" class="_ghost"/> | 			<img :src="infoImageUrl" draggable="false"/> | ||||||
| 			<div>{{ i18n.ts.nothing }}</div> | 			<div>{{ i18n.ts.nothing }}</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else :class="$style.feed"> | 		<div v-else :class="$style.feed"> | ||||||
| @@ -25,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { ref, watch, computed } from 'vue'; | import { ref, watch, computed } from 'vue'; | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
|  | import { url as base } from '@@/js/config.js'; | ||||||
|  | import { useInterval } from '@@/js/use-interval.js'; | ||||||
| import { useWidgetPropsManager } from './widget.js'; | import { useWidgetPropsManager } from './widget.js'; | ||||||
| import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; | import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; | ||||||
| import type { GetFormResultType } from '@/utility/form.js'; | import type { GetFormResultType } from '@/utility/form.js'; | ||||||
| import MkContainer from '@/components/MkContainer.vue'; | import MkContainer from '@/components/MkContainer.vue'; | ||||||
| import { url as base } from '@@/js/config.js'; |  | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { useInterval } from '@@/js/use-interval.js'; |  | ||||||
| import { infoImageUrl } from '@/instance.js'; | import { infoImageUrl } from '@/instance.js'; | ||||||
|  |  | ||||||
| const name = 'rss'; | const name = 'rss'; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"type": "module", | 	"type": "module", | ||||||
| 	"name": "misskey-js", | 	"name": "misskey-js", | ||||||
| 	"version": "2025.3.2-beta.0", | 	"version": "2025.3.2-beta.2", | ||||||
| 	"description": "Misskey SDK for JavaScript", | 	"description": "Misskey SDK for JavaScript", | ||||||
| 	"license": "MIT", | 	"license": "MIT", | ||||||
| 	"main": "./built/index.js", | 	"main": "./built/index.js", | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										72
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -98,15 +98,6 @@ importers: | |||||||
|       '@aws-sdk/lib-storage': |       '@aws-sdk/lib-storage': | ||||||
|         specifier: 3.749.0 |         specifier: 3.749.0 | ||||||
|         version: 3.749.0(@aws-sdk/client-s3@3.749.0) |         version: 3.749.0(@aws-sdk/client-s3@3.749.0) | ||||||
|       '@bull-board/api': |  | ||||||
|         specifier: 6.7.7 |  | ||||||
|         version: 6.7.7(@bull-board/ui@6.7.7) |  | ||||||
|       '@bull-board/fastify': |  | ||||||
|         specifier: 6.7.7 |  | ||||||
|         version: 6.7.7 |  | ||||||
|       '@bull-board/ui': |  | ||||||
|         specifier: 6.7.7 |  | ||||||
|         version: 6.7.7 |  | ||||||
|       '@discordapp/twemoji': |       '@discordapp/twemoji': | ||||||
|         specifier: 15.1.0 |         specifier: 15.1.0 | ||||||
|         version: 15.1.0 |         version: 15.1.0 | ||||||
| @@ -1868,17 +1859,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} |     resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} | ||||||
|     engines: {node: '>=18'} |     engines: {node: '>=18'} | ||||||
|  |  | ||||||
|   '@bull-board/api@6.7.7': |  | ||||||
|     resolution: {integrity: sha512-jSBe+aeNs41T/BUJNutKSM17hJigDLoOaAzUZyFwT63/Yt00hiqQo90THXmDi3vGdXtTruGlkrC9OhVxBKo1eQ==} |  | ||||||
|     peerDependencies: |  | ||||||
|       '@bull-board/ui': 6.7.7 |  | ||||||
|  |  | ||||||
|   '@bull-board/fastify@6.7.7': |  | ||||||
|     resolution: {integrity: sha512-EVxpRW0ag/tVPqfHm6s/3P6X5DnzKTr0J5lI1EgOvpe+OXavaPWkU0iLPepyyC6ls+k0djdkd1ix1PP/caqufw==} |  | ||||||
|  |  | ||||||
|   '@bull-board/ui@6.7.7': |  | ||||||
|     resolution: {integrity: sha512-QU3OkaJVIUt1SpRRV/XxPSTD9tmJcwBWi1oa4ND+qGWQigQ2H1PYfpQCNFOlyW8qCkBwkSDn8pLwlyGbppWqJg==} |  | ||||||
|  |  | ||||||
|   '@bundled-es-modules/cookie@2.0.1': |   '@bundled-es-modules/cookie@2.0.1': | ||||||
|     resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} |     resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} | ||||||
|  |  | ||||||
| @@ -5911,11 +5891,6 @@ packages: | |||||||
|   ee-first@1.1.1: |   ee-first@1.1.1: | ||||||
|     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} |     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} | ||||||
|  |  | ||||||
|   ejs@3.1.10: |  | ||||||
|     resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} |  | ||||||
|     engines: {node: '>=0.10.0'} |  | ||||||
|     hasBin: true |  | ||||||
|  |  | ||||||
|   electron-to-chromium@1.5.83: |   electron-to-chromium@1.5.83: | ||||||
|     resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} |     resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} | ||||||
|  |  | ||||||
| @@ -6366,9 +6341,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} |     resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} | ||||||
|     engines: {node: '>=18'} |     engines: {node: '>=18'} | ||||||
|  |  | ||||||
|   filelist@1.0.4: |  | ||||||
|     resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} |  | ||||||
|  |  | ||||||
|   filename-reserved-regex@3.0.0: |   filename-reserved-regex@3.0.0: | ||||||
|     resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} |     resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} | ||||||
|     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} |     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} | ||||||
| @@ -7238,11 +7210,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} |     resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} | ||||||
|     engines: {node: 20 || >=22} |     engines: {node: 20 || >=22} | ||||||
|  |  | ||||||
|   jake@10.8.5: |  | ||||||
|     resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} |  | ||||||
|     engines: {node: '>=10'} |  | ||||||
|     hasBin: true |  | ||||||
|  |  | ||||||
|   jest-changed-files@29.7.0: |   jest-changed-files@29.7.0: | ||||||
|     resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} |     resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} | ||||||
|     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} |     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} | ||||||
| @@ -9151,9 +9118,6 @@ packages: | |||||||
|     resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} |     resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} | ||||||
|     engines: {node: '>=4'} |     engines: {node: '>=4'} | ||||||
|  |  | ||||||
|   redis-info@3.1.0: |  | ||||||
|     resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==} |  | ||||||
|  |  | ||||||
|   redis-lock@0.1.4: |   redis-lock@0.1.4: | ||||||
|     resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==} |     resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==} | ||||||
|     engines: {node: '>=0.6'} |     engines: {node: '>=0.6'} | ||||||
| @@ -11618,23 +11582,6 @@ snapshots: | |||||||
|  |  | ||||||
|   '@bcoe/v8-coverage@1.0.2': {} |   '@bcoe/v8-coverage@1.0.2': {} | ||||||
|  |  | ||||||
|   '@bull-board/api@6.7.7(@bull-board/ui@6.7.7)': |  | ||||||
|     dependencies: |  | ||||||
|       '@bull-board/ui': 6.7.7 |  | ||||||
|       redis-info: 3.1.0 |  | ||||||
|  |  | ||||||
|   '@bull-board/fastify@6.7.7': |  | ||||||
|     dependencies: |  | ||||||
|       '@bull-board/api': 6.7.7(@bull-board/ui@6.7.7) |  | ||||||
|       '@bull-board/ui': 6.7.7 |  | ||||||
|       '@fastify/static': 8.1.0 |  | ||||||
|       '@fastify/view': 10.0.2 |  | ||||||
|       ejs: 3.1.10 |  | ||||||
|  |  | ||||||
|   '@bull-board/ui@6.7.7': |  | ||||||
|     dependencies: |  | ||||||
|       '@bull-board/api': 6.7.7(@bull-board/ui@6.7.7) |  | ||||||
|  |  | ||||||
|   '@bundled-es-modules/cookie@2.0.1': |   '@bundled-es-modules/cookie@2.0.1': | ||||||
|     dependencies: |     dependencies: | ||||||
|       cookie: 0.7.2 |       cookie: 0.7.2 | ||||||
| @@ -16450,10 +16397,6 @@ snapshots: | |||||||
|  |  | ||||||
|   ee-first@1.1.1: {} |   ee-first@1.1.1: {} | ||||||
|  |  | ||||||
|   ejs@3.1.10: |  | ||||||
|     dependencies: |  | ||||||
|       jake: 10.8.5 |  | ||||||
|  |  | ||||||
|   electron-to-chromium@1.5.83: {} |   electron-to-chromium@1.5.83: {} | ||||||
|  |  | ||||||
|   emittery@0.13.1: {} |   emittery@0.13.1: {} | ||||||
| @@ -17214,10 +17157,6 @@ snapshots: | |||||||
|       token-types: 6.0.0 |       token-types: 6.0.0 | ||||||
|       uint8array-extras: 1.4.0 |       uint8array-extras: 1.4.0 | ||||||
|  |  | ||||||
|   filelist@1.0.4: |  | ||||||
|     dependencies: |  | ||||||
|       minimatch: 5.1.2 |  | ||||||
|  |  | ||||||
|   filename-reserved-regex@3.0.0: {} |   filename-reserved-regex@3.0.0: {} | ||||||
|  |  | ||||||
|   filenamify@6.0.0: |   filenamify@6.0.0: | ||||||
| @@ -18134,13 +18073,6 @@ snapshots: | |||||||
|     optionalDependencies: |     optionalDependencies: | ||||||
|       '@pkgjs/parseargs': 0.11.0 |       '@pkgjs/parseargs': 0.11.0 | ||||||
|  |  | ||||||
|   jake@10.8.5: |  | ||||||
|     dependencies: |  | ||||||
|       async: 3.2.4 |  | ||||||
|       chalk: 4.1.2 |  | ||||||
|       filelist: 1.0.4 |  | ||||||
|       minimatch: 3.1.2 |  | ||||||
|  |  | ||||||
|   jest-changed-files@29.7.0: |   jest-changed-files@29.7.0: | ||||||
|     dependencies: |     dependencies: | ||||||
|       execa: 5.1.1 |       execa: 5.1.1 | ||||||
| @@ -20546,10 +20478,6 @@ snapshots: | |||||||
|  |  | ||||||
|   redis-errors@1.2.0: {} |   redis-errors@1.2.0: {} | ||||||
|  |  | ||||||
|   redis-info@3.1.0: |  | ||||||
|     dependencies: |  | ||||||
|       lodash: 4.17.21 |  | ||||||
|  |  | ||||||
|   redis-lock@0.1.4: {} |   redis-lock@0.1.4: {} | ||||||
|  |  | ||||||
|   redis-parser@3.0.0: |   redis-parser@3.0.0: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user