Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f9180e045 | ||
|
|
e8b5aa5c2f |
@@ -12,20 +12,8 @@
|
||||
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
|
||||
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
|
||||
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
||||
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
||||
- mfm.jsをアップデートしました
|
||||
- Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応
|
||||
- Enhance: acctに `.` が入っているユーザーのメンションに対応
|
||||
- Fix: Unicode絵文字に隣接する異体字セレクタ(`U+FE0F`)が絵文字として認識される問題を修正
|
||||
|
||||
### Client
|
||||
- Feat: セーフモード
|
||||
- プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます
|
||||
- 以下の方法でセーフモードを起動できます
|
||||
- `g` キーを連打する
|
||||
- URLに`?safemode=true`を付ける
|
||||
- PWAのショートカットで Safemode を選択して起動する
|
||||
- Enhance: コントロールパネルを検索できるように
|
||||
- Fix: 一部の設定検索結果が存在しないパスになる問題を修正
|
||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171)
|
||||
- Fix: テーマエディタが動作しない問題を修正
|
||||
|
||||
Vendored
+15
-27
@@ -315,11 +315,11 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"mention": string;
|
||||
/**
|
||||
* メンション
|
||||
* あなた宛て
|
||||
*/
|
||||
"mentions": string;
|
||||
/**
|
||||
* 指名
|
||||
* ダイレクト投稿
|
||||
*/
|
||||
"directNotes": string;
|
||||
/**
|
||||
@@ -5493,6 +5493,10 @@ export interface Locale extends ILocale {
|
||||
* 低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。
|
||||
*/
|
||||
"defaultImageCompressionLevel_description": string;
|
||||
/**
|
||||
* 低電力モード
|
||||
*/
|
||||
"lowPowerMode": string;
|
||||
/**
|
||||
* 分
|
||||
*/
|
||||
@@ -5501,22 +5505,6 @@ export interface Locale extends ILocale {
|
||||
* 日
|
||||
*/
|
||||
"inDays": string;
|
||||
/**
|
||||
* セーフモードが有効です
|
||||
*/
|
||||
"safeModeEnabled": string;
|
||||
/**
|
||||
* セーフモードが有効なため、プラグインはすべて無効化されています。
|
||||
*/
|
||||
"pluginsAreDisabledBecauseSafeMode": string;
|
||||
/**
|
||||
* セーフモードが有効なため、カスタムCSSは適用されていません。
|
||||
*/
|
||||
"customCssIsDisabledBecauseSafeMode": string;
|
||||
/**
|
||||
* セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。
|
||||
*/
|
||||
"themeIsDefaultBecauseSafeMode": string;
|
||||
"_order": {
|
||||
/**
|
||||
* 新しい順
|
||||
@@ -5823,6 +5811,10 @@ export interface Locale extends ILocale {
|
||||
* UIのアニメーション
|
||||
*/
|
||||
"uiAnimations": string;
|
||||
/**
|
||||
* アニメーション画像を再生
|
||||
*/
|
||||
"playAnimatedImages": string;
|
||||
/**
|
||||
* ナビゲーションバーに副ボタンを表示
|
||||
*/
|
||||
@@ -6353,7 +6345,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"followers": string;
|
||||
/**
|
||||
* 指定したユーザーにのみ公開され、また相手に通知が入ります。
|
||||
* 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。
|
||||
*/
|
||||
"direct": string;
|
||||
/**
|
||||
@@ -6361,7 +6353,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"doNotSendConfidencialOnDirect1": string;
|
||||
/**
|
||||
* 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。
|
||||
* 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。
|
||||
*/
|
||||
"doNotSendConfidencialOnDirect2": string;
|
||||
/**
|
||||
@@ -9641,7 +9633,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"followersDescription": string;
|
||||
/**
|
||||
* 指名
|
||||
* ダイレクト
|
||||
*/
|
||||
"specified": string;
|
||||
/**
|
||||
@@ -10530,11 +10522,11 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"channel": string;
|
||||
/**
|
||||
* メンション
|
||||
* あなた宛て
|
||||
*/
|
||||
"mentions": string;
|
||||
/**
|
||||
* 指名
|
||||
* ダイレクト
|
||||
*/
|
||||
"direct": string;
|
||||
/**
|
||||
@@ -11855,10 +11847,6 @@ export interface Locale extends ILocale {
|
||||
* 修復ツールを起動
|
||||
*/
|
||||
"otherOption3": string;
|
||||
/**
|
||||
* Misskeyをセーフモードで起動
|
||||
*/
|
||||
"otherOption4": string;
|
||||
};
|
||||
"_search": {
|
||||
/**
|
||||
|
||||
+9
-12
@@ -74,8 +74,8 @@ youGotNewFollower: "フォローされました"
|
||||
receiveFollowRequest: "フォローリクエストされました"
|
||||
followRequestAccepted: "フォローが承認されました"
|
||||
mention: "メンション"
|
||||
mentions: "メンション"
|
||||
directNotes: "指名"
|
||||
mentions: "あなた宛て"
|
||||
directNotes: "ダイレクト投稿"
|
||||
importAndExport: "インポートとエクスポート"
|
||||
import: "インポート"
|
||||
export: "エクスポート"
|
||||
@@ -1368,12 +1368,9 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示"
|
||||
hideAllTips: "全ての「ヒントとコツ」を非表示"
|
||||
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
|
||||
defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。<br>高くするとファイルサイズを減らせますが、画質は低下します。"
|
||||
lowPowerMode: "低電力モード"
|
||||
inMinutes: "分"
|
||||
inDays: "日"
|
||||
safeModeEnabled: "セーフモードが有効です"
|
||||
pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プラグインはすべて無効化されています。"
|
||||
customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。"
|
||||
themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。"
|
||||
|
||||
_order:
|
||||
newest: "新しい順"
|
||||
@@ -1457,6 +1454,7 @@ _settings:
|
||||
useStickyIcons: "アイコンをスクロールに追従させる"
|
||||
enableHighQualityImagePlaceholders: "高品質な画像のプレースホルダを表示"
|
||||
uiAnimations: "UIのアニメーション"
|
||||
playAnimatedImages: "アニメーション画像を再生"
|
||||
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
|
||||
ifOn: "オンのとき"
|
||||
ifOff: "オフのとき"
|
||||
@@ -1609,9 +1607,9 @@ _initialTutorial:
|
||||
public: "すべてのユーザーに公開。"
|
||||
home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。"
|
||||
followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。"
|
||||
direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。"
|
||||
direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。"
|
||||
doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。"
|
||||
doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーが含まれる限定公開のノートを作成する際は、機密情報の扱いに注意が必要です。"
|
||||
doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。"
|
||||
localOnly: "他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。"
|
||||
_cw:
|
||||
title: "内容を隠す(CW)"
|
||||
@@ -2536,7 +2534,7 @@ _visibility:
|
||||
homeDescription: "ホームタイムラインのみに公開"
|
||||
followers: "フォロワー"
|
||||
followersDescription: "自分のフォロワーのみに公開"
|
||||
specified: "指名"
|
||||
specified: "ダイレクト"
|
||||
specifiedDescription: "指定したユーザーのみに公開"
|
||||
disableFederation: "連合なし"
|
||||
disableFederationDescription: "他サーバーへの配信を行いません"
|
||||
@@ -2782,8 +2780,8 @@ _deck:
|
||||
antenna: "アンテナ"
|
||||
list: "リスト"
|
||||
channel: "チャンネル"
|
||||
mentions: "メンション"
|
||||
direct: "指名"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト"
|
||||
roleTimeline: "ロールタイムライン"
|
||||
chat: "チャット"
|
||||
|
||||
@@ -3168,7 +3166,6 @@ _bootErrors:
|
||||
otherOption1: "クライアント設定とキャッシュを削除"
|
||||
otherOption2: "簡易クライアントを起動"
|
||||
otherOption3: "修復ツールを起動"
|
||||
otherOption4: "Misskeyをセーフモードで起動"
|
||||
|
||||
_search:
|
||||
searchScopeAll: "全て"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.8.0-alpha.4",
|
||||
"version": "2025.8.0-alpha.1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -7,7 +7,7 @@ export class RemoteNotesCleaning1753863104203 {
|
||||
name = 'RemoteNotesCleaning1753863104203'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT true`);
|
||||
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\'');
|
||||
await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\'');
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ export class TweakDefaultFederationSettings1754019326356 {
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'none'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "enableRemoteNotesCleaning" SET DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "federation" SET DEFAULT 'all'`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.826.0",
|
||||
"@aws-sdk/lib-storage": "3.826.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
"@fastify/cors": "10.1.0",
|
||||
@@ -93,7 +93,7 @@
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.7.7",
|
||||
"@swc/core": "1.12.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@types/redis-info": "3.0.3",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.17.1",
|
||||
@@ -135,7 +135,7 @@
|
||||
"jsrsasign": "11.1.0",
|
||||
"juice": "11.0.1",
|
||||
"meilisearch": "0.51.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.3",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
|
||||
@@ -16,13 +16,11 @@ import {
|
||||
RelationshipJobData,
|
||||
UserWebhookDeliverJobData,
|
||||
SystemWebhookDeliverJobData,
|
||||
DeleteUserMutingsJobData,
|
||||
} from '../queue/types.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||
export type DeleteUserMutingQueue = Bull.Queue<DeleteUserMutingsJobData>;
|
||||
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
||||
export type InboxQueue = Bull.Queue<InboxJobData>;
|
||||
export type DbQueue = Bull.Queue;
|
||||
@@ -43,12 +41,6 @@ const $endedPollNotification: Provider = {
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $deleteUserMuting: Provider = {
|
||||
provide: 'queue:deleteUserMuting',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELETE_USER_MUTING, baseQueueOptions(config, QUEUE.DELETE_USER_MUTING)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $deliver: Provider = {
|
||||
provide: 'queue:deliver',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
|
||||
@@ -97,7 +89,6 @@ const $systemWebhookDeliver: Provider = {
|
||||
providers: [
|
||||
$system,
|
||||
$endedPollNotification,
|
||||
$deleteUserMuting,
|
||||
$deliver,
|
||||
$inbox,
|
||||
$db,
|
||||
@@ -109,7 +100,6 @@ const $systemWebhookDeliver: Provider = {
|
||||
exports: [
|
||||
$system,
|
||||
$endedPollNotification,
|
||||
$deleteUserMuting,
|
||||
$deliver,
|
||||
$inbox,
|
||||
$db,
|
||||
@@ -123,7 +113,6 @@ export class QueueModule implements OnApplicationShutdown {
|
||||
constructor(
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:deleteUserMuting') public deleteUserMutingQueue: DeleteUserMutingQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@@ -140,7 +129,6 @@ export class QueueModule implements OnApplicationShutdown {
|
||||
await Promise.all([
|
||||
this.systemQueue.close(),
|
||||
this.endedPollNotificationQueue.close(),
|
||||
this.deleteUserMutingQueue.close(),
|
||||
this.deliverQueue.close(),
|
||||
this.inboxQueue.close(),
|
||||
this.dbQueue.close(),
|
||||
|
||||
@@ -31,7 +31,6 @@ import type {
|
||||
DbQueue,
|
||||
DeliverQueue,
|
||||
EndedPollNotificationQueue,
|
||||
DeleteUserMutingQueue,
|
||||
InboxQueue,
|
||||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
@@ -45,7 +44,6 @@ import type * as Bull from 'bullmq';
|
||||
export const QUEUE_TYPES = [
|
||||
'system',
|
||||
'endedPollNotification',
|
||||
'deleteUserMuting',
|
||||
'deliver',
|
||||
'inbox',
|
||||
'db',
|
||||
@@ -72,16 +70,18 @@ const REPEATABLE_SYSTEM_JOB_DEF = [{
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'checkExpiredMutings',
|
||||
pattern: '0 */3 * * *', // 3時間ごと
|
||||
pattern: '*/5 * * * *',
|
||||
}, {
|
||||
name: 'bakeBufferedReactions',
|
||||
pattern: '0 0 * * *',
|
||||
}, {
|
||||
name: 'checkModeratorsActivity',
|
||||
pattern: '30 * * * *', // 毎時30分に起動
|
||||
// 毎時30分に起動
|
||||
pattern: '30 * * * *',
|
||||
}, {
|
||||
name: 'cleanRemoteNotes',
|
||||
pattern: '0 4 * * *', // 毎日午前4時に起動(最も人の少ない時間帯)
|
||||
// 毎日午前4時に起動(最も人の少ない時間帯)
|
||||
pattern: '0 4 * * *',
|
||||
}];
|
||||
|
||||
@Injectable()
|
||||
@@ -92,7 +92,6 @@ export class QueueService {
|
||||
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:deleteUserMutingQueue') public deleteUserMutingQueue: DeleteUserMutingQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@@ -717,7 +716,6 @@ export class QueueService {
|
||||
switch (type) {
|
||||
case 'system': return this.systemQueue;
|
||||
case 'endedPollNotification': return this.endedPollNotificationQueue;
|
||||
case 'deleteUserMuting': return this.deleteUserMutingQueue;
|
||||
case 'deliver': return this.deliverQueue;
|
||||
case 'inbox': return this.inboxQueue;
|
||||
case 'db': return this.dbQueue;
|
||||
|
||||
@@ -227,9 +227,9 @@ export class SearchService {
|
||||
|
||||
if (opts.host) {
|
||||
if (opts.host === '.') {
|
||||
query.andWhere('note.userHost IS NULL');
|
||||
query.andWhere('user.host IS NULL');
|
||||
} else {
|
||||
query.andWhere('note.userHost = :host', { host: opts.host });
|
||||
query.andWhere('user.host = :host', { host: opts.host });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserMutingService {
|
||||
@@ -21,13 +20,12 @@ export class UserMutingService {
|
||||
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
||||
const inserted = await this.mutingsRepository.insertOne({
|
||||
await this.mutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
expiresAt: expiresAt ?? null,
|
||||
muterId: user.id,
|
||||
@@ -35,23 +33,6 @@ export class UserMutingService {
|
||||
});
|
||||
|
||||
this.cacheService.userMutingsCache.refresh(user.id);
|
||||
|
||||
if (expiresAt != null) {
|
||||
const delay = expiresAt.getTime() - Date.now();
|
||||
this.queueService.deleteUserMutingQueue.add(inserted.id, {
|
||||
mutingId: inserted.id,
|
||||
}, {
|
||||
delay,
|
||||
removeOnComplete: {
|
||||
age: 3600 * 24 * 7, // keep up to 7 days
|
||||
count: 30,
|
||||
},
|
||||
removeOnFail: {
|
||||
age: 3600 * 24 * 7, // keep up to 7 days
|
||||
count: 100,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -67,25 +48,4 @@ export class UserMutingService {
|
||||
this.cacheService.userMutingsCache.refresh(muterId);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteExpired(): Promise<void> {
|
||||
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
||||
.where('muting.expiresAt IS NOT NULL')
|
||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||
.getMany();
|
||||
|
||||
if (expired.length > 0) {
|
||||
await this.unmute(expired);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async unmuteById(mutingId: MiMuting['id']): Promise<void> {
|
||||
const muting = await this.mutingsRepository.findOneBy({ id: mutingId });
|
||||
if (muting == null) return;
|
||||
|
||||
await this.unmute([muting]);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -14,7 +14,6 @@ import { CheckModeratorsActivityProcessorService } from '@/queue/processors/Chec
|
||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { DeleteUserMutingsProcessorService } from './processors/DeleteUserMutingsProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
||||
@@ -86,7 +85,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
private relationshipQueueWorker: Bull.Worker;
|
||||
private objectStorageQueueWorker: Bull.Worker;
|
||||
private endedPollNotificationQueueWorker: Bull.Worker;
|
||||
private deleteUserMutingQueueWorker: Bull.Worker;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
@@ -96,7 +94,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
private userWebhookDeliverProcessorService: UserWebhookDeliverProcessorService,
|
||||
private systemWebhookDeliverProcessorService: SystemWebhookDeliverProcessorService,
|
||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||
private deleteUserMutingsProcessorService: DeleteUserMutingsProcessorService,
|
||||
private deliverProcessorService: DeliverProcessorService,
|
||||
private inboxProcessorService: InboxProcessorService,
|
||||
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
||||
@@ -523,21 +520,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region delete user muting
|
||||
{
|
||||
this.deleteUserMutingQueueWorker = new Bull.Worker(QUEUE.DELETE_USER_MUTING, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: DeleteUserMutings' }, () => this.deleteUserMutingsProcessorService.process(job));
|
||||
} else {
|
||||
return this.deleteUserMutingsProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseWorkerOptions(this.config, QUEUE.DELETE_USER_MUTING),
|
||||
autorun: false,
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -552,7 +534,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
this.relationshipQueueWorker.run(),
|
||||
this.objectStorageQueueWorker.run(),
|
||||
this.endedPollNotificationQueueWorker.run(),
|
||||
this.deleteUserMutingQueueWorker.run(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -568,7 +549,6 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
this.relationshipQueueWorker.close(),
|
||||
this.objectStorageQueueWorker.close(),
|
||||
this.endedPollNotificationQueueWorker.close(),
|
||||
this.deleteUserMutingQueueWorker.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ export const QUEUE = {
|
||||
INBOX: 'inbox',
|
||||
SYSTEM: 'system',
|
||||
ENDED_POLL_NOTIFICATION: 'endedPollNotification',
|
||||
DELETE_USER_MUTING: 'deleteUserMuting',
|
||||
DB: 'db',
|
||||
RELATIONSHIP: 'relationship',
|
||||
OBJECT_STORAGE: 'objectStorage',
|
||||
|
||||
@@ -3,17 +3,24 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MutingsRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@Injectable()
|
||||
export class CheckExpiredMutingsProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.mutingsRepository)
|
||||
private mutingsRepository: MutingsRepository,
|
||||
|
||||
private userMutingService: UserMutingService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
@@ -24,7 +31,15 @@ export class CheckExpiredMutingsProcessorService {
|
||||
public async process(): Promise<void> {
|
||||
this.logger.info('Checking expired mutings...');
|
||||
|
||||
await this.userMutingService.deleteExpired();
|
||||
const expired = await this.mutingsRepository.createQueryBuilder('muting')
|
||||
.where('muting.expiresAt IS NOT NULL')
|
||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||
.getMany();
|
||||
|
||||
if (expired.length > 0) {
|
||||
await this.userMutingService.unmute(expired);
|
||||
}
|
||||
|
||||
this.logger.succ('All expired mutings checked.');
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserMutingService } from '@/core/UserMutingService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
import type { DeleteUserMutingsJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteUserMutingsProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
private userMutingService: UserMutingService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('delete-user-mutings');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<DeleteUserMutingsJobData>): Promise<void> {
|
||||
await this.userMutingService.unmuteById(job.data.mutingId);
|
||||
}
|
||||
}
|
||||
@@ -109,10 +109,6 @@ export type EndedPollNotificationJobData = {
|
||||
noteId: MiNote['id'];
|
||||
};
|
||||
|
||||
export type DeleteUserMutingsJobData = {
|
||||
mutingId: string;
|
||||
};
|
||||
|
||||
export type SystemWebhookDeliverJobData<T extends SystemWebhookEventType = SystemWebhookEventType> = {
|
||||
type: T;
|
||||
content: SystemWebhookPayload<T>;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue, DeleteUserMutingQueue } from '@/core/QueueModule.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -49,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
constructor(
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:deleteUserMuting') public deleteUserMutingQueue: DeleteUserMutingQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
|
||||
@@ -20,6 +20,17 @@ import type { Config } from '@/config.js';
|
||||
import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type {
|
||||
DbQueue,
|
||||
DeliverQueue,
|
||||
EndedPollNotificationQueue,
|
||||
InboxQueue,
|
||||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
SystemQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
} from '@/core/QueueModule.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||
@@ -118,6 +129,16 @@ export class ClientServerService {
|
||||
private feedService: FeedService,
|
||||
private roleService: RoleService,
|
||||
private clientLoggerService: ClientLoggerService,
|
||||
|
||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
|
||||
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
@@ -167,10 +188,6 @@ export class ClientServerService {
|
||||
'url': 'url',
|
||||
},
|
||||
},
|
||||
'shortcuts': [{
|
||||
'name': 'Safemode',
|
||||
'url': '/?safemode=true',
|
||||
}],
|
||||
};
|
||||
|
||||
manifest = {
|
||||
|
||||
@@ -94,37 +94,23 @@
|
||||
}
|
||||
//#endregion
|
||||
|
||||
let isSafeMode = (localStorage.getItem('isSafeMode') === 'true');
|
||||
|
||||
if (!isSafeMode) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') {
|
||||
localStorage.setItem('isSafeMode', 'true');
|
||||
isSafeMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
//#region Theme
|
||||
if (!isSafeMode) {
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
}
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const colorScheme = localStorage.getItem('colorScheme');
|
||||
if (colorScheme) {
|
||||
document.documentElement.style.setProperty('color-scheme', colorScheme);
|
||||
@@ -141,13 +127,11 @@
|
||||
document.documentElement.classList.add('useSystemFont');
|
||||
}
|
||||
|
||||
if (!isSafeMode) {
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
async function addStyle(styleText) {
|
||||
@@ -175,13 +159,9 @@
|
||||
otherOption1: 'Clear preferences and cache',
|
||||
otherOption2: 'Start the simple client',
|
||||
otherOption3: 'Start the repair tool',
|
||||
otherOption4: 'Start Misskey in safe mode',
|
||||
}, locale?._bootErrors || {});
|
||||
const reload = locale?.reload || 'Reload';
|
||||
|
||||
const safeModeUrl = new URL(window.location.href);
|
||||
safeModeUrl.searchParams.set('safemode', 'true');
|
||||
|
||||
let errorsElement = document.getElementById('errors');
|
||||
|
||||
if (!errorsElement) {
|
||||
@@ -202,12 +182,6 @@
|
||||
<p>${messages.solution4}</p>
|
||||
<details style="color: #86b300;">
|
||||
<summary>${messages.otherOption}</summary>
|
||||
<a href="${safeModeUrl}">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">${messages.otherOption4}</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/flush">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">${messages.otherOption1}</span>
|
||||
|
||||
@@ -34,11 +34,5 @@
|
||||
"text": "text",
|
||||
"url": "url"
|
||||
}
|
||||
},
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Safemode",
|
||||
"url": "/?safemode=true"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +79,6 @@ async function createAdmin(host: Host): Promise<Misskey.entities.SignupResponse
|
||||
rateLimitFactor: 0 as never,
|
||||
},
|
||||
}, res.token);
|
||||
await client.request('admin/update-meta', {
|
||||
federation: 'all',
|
||||
}, res.token);
|
||||
return res;
|
||||
}).catch(err => {
|
||||
if (err.info.e.message === 'access denied') return undefined;
|
||||
|
||||
@@ -24,7 +24,6 @@ describe('Endpoints', () => {
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
dave = await signup({ username: 'dave' });
|
||||
await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse);
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
describe('signup', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import type { SimpleGetResponse } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
@@ -78,7 +78,6 @@ describe('Webリソース', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse);
|
||||
aliceUploadedFile = (await uploadFile(alice)).body;
|
||||
alicesPost = await post(alice, {
|
||||
text: 'test',
|
||||
|
||||
@@ -16,7 +16,6 @@ describe('FF visibility', () => {
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse);
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { api, host, origin, relativeFetch, signup } from '../utils.js';
|
||||
import { host, origin, relativeFetch, signup } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('.well-known', () => {
|
||||
@@ -14,7 +14,6 @@ describe('.well-known', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
await api('admin/update-meta', { federation: 'all' }, alice as misskey.entities.SignupResponse);
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
test('nodeinfo', async () => {
|
||||
|
||||
@@ -10,53 +10,53 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@rollup/pluginutils": "5.2.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "3.5.18",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"@vue/compiler-sfc": "3.5.17",
|
||||
"astring": "1.9.0",
|
||||
"buraha": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
"icons-subsetter": "workspace:*",
|
||||
"frontend-shared": "workspace:*",
|
||||
"json5": "2.2.3",
|
||||
"mfm-js": "0.25.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"misskey-js": "workspace:*",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.46.2",
|
||||
"rollup": "4.45.1",
|
||||
"sass": "1.89.2",
|
||||
"shiki": "3.9.1",
|
||||
"shiki": "3.8.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"typescript": "5.8.3",
|
||||
"uuid": "11.1.0",
|
||||
"vite": "7.0.6",
|
||||
"vue": "3.5.18"
|
||||
"vite": "6.3.5",
|
||||
"vue": "3.5.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.3",
|
||||
"@tabler/icons-webfont": "3.34.1",
|
||||
"@misskey-dev/summaly": "5.2.2",
|
||||
"@tabler/icons-webfont": "3.34.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.17.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/runtime-core": "3.5.18",
|
||||
"@vue/runtime-core": "3.5.17",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"eslint-plugin-vue": "10.3.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "18.0.1",
|
||||
"happy-dom": "17.6.3",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.10.4",
|
||||
@@ -64,8 +64,8 @@
|
||||
"prettier": "3.6.2",
|
||||
"start-server-and-test": "2.0.12",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.0.5",
|
||||
"vue-component-type-helpers": "2.2.12",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.5"
|
||||
"vue-tsc": "2.2.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export const version = _VERSION_;
|
||||
export const instanceName = (siteName === 'Misskey' || siteName == null) ? host : siteName;
|
||||
export const ui = localStorage.getItem('ui');
|
||||
export const debug = localStorage.getItem('debug') === 'true';
|
||||
export const isSafeMode = localStorage.getItem('isSafeMode') === 'true';
|
||||
|
||||
export function updateLocale(newLocale: Locale): void {
|
||||
locale = newLocale;
|
||||
|
||||
@@ -44,8 +44,6 @@
|
||||
["😑", "expressionless", 0],
|
||||
["😒", "unamused", 0],
|
||||
["🙄", "roll_eyes", 0],
|
||||
["🙂↔️", "head_shaking_horizontally", 0],
|
||||
["🙂↕️", "head_shaking_vertically", 0],
|
||||
["🤔", "thinking", 0],
|
||||
["🤥", "lying_face", 0],
|
||||
["🤭", "hand_over_mouth", 0],
|
||||
@@ -78,7 +76,6 @@
|
||||
["😥", "disappointed_relieved", 0],
|
||||
["🤤", "drooling_face", 0],
|
||||
["😪", "sleepy", 0],
|
||||
["", "face_with_bags_under_eyes", 0],
|
||||
["😓", "sweat", 0],
|
||||
["🥵", "hot", 0],
|
||||
["🥶", "cold", 0],
|
||||
@@ -95,7 +92,6 @@
|
||||
["🥴", "woozy", 0],
|
||||
["🥱", "yawning", 0],
|
||||
["😴", "sleeping", 0],
|
||||
["👁️🗨️", "eye_in_speech_bubble", 0],
|
||||
["💤", "zzz", 0],
|
||||
["😶🌫️", "face_in_clouds", 0],
|
||||
["😮💨", "face_exhaling", 0],
|
||||
@@ -189,7 +185,6 @@
|
||||
["👤", "bust_in_silhouette", 1],
|
||||
["👥", "busts_in_silhouette", 1],
|
||||
["🗣", "speaking_head", 1],
|
||||
["", "fingerprint", 1],
|
||||
["👶", "baby", 1],
|
||||
["🧒", "child", 1],
|
||||
["👦", "boy", 1],
|
||||
@@ -203,9 +198,8 @@
|
||||
["🧑🦰", "red_hair", 1],
|
||||
["👩🦰", "red_hair_woman", 1],
|
||||
["👨🦰", "red_hair_man", 1],
|
||||
["👱", "blonde_person", 1],
|
||||
["👱♀️", "blonde_woman", 1],
|
||||
["👱♂️", "blonde_man", 1],
|
||||
["👱", "blonde_man", 1],
|
||||
["🧑🦳", "white_hair", 1],
|
||||
["👩🦳", "white_hair_woman", 1],
|
||||
["👨🦳", "white_hair_man", 1],
|
||||
@@ -213,28 +207,21 @@
|
||||
["👩🦲", "bald_woman", 1],
|
||||
["👨🦲", "bald_man", 1],
|
||||
["🧔", "bearded_person", 1],
|
||||
["🧔♀️", "beard_woman", 1],
|
||||
["🧔♂️", "beard_man", 1],
|
||||
["🧓", "older_adult", 1],
|
||||
["👴", "older_man", 1],
|
||||
["👵", "older_woman", 1],
|
||||
["👲", "man_with_gua_pi_mao", 1],
|
||||
["🧕", "woman_with_headscarf", 1],
|
||||
["👳", "person_with_turban", 1],
|
||||
["👳♀️", "woman_with_turban", 1],
|
||||
["👳♂️", "man_with_turban", 1],
|
||||
["👮", "police_officer", 1],
|
||||
["👳", "man_with_turban", 1],
|
||||
["👮♀️", "policewoman", 1],
|
||||
["👮♂️", "policeman", 1],
|
||||
["👷", "construction_worker", 1],
|
||||
["👮", "policeman", 1],
|
||||
["👷♀️", "construction_worker_woman", 1],
|
||||
["👷♂️", "construction_worker_man", 1],
|
||||
["💂", "guard", 1],
|
||||
["👷", "construction_worker_man", 1],
|
||||
["💂♀️", "guardswoman", 1],
|
||||
["💂♂️", "guardsman", 1],
|
||||
["🕵", "detective", 1],
|
||||
["💂", "guardsman", 1],
|
||||
["🕵️♀️", "female_detective", 1],
|
||||
["🕵️♂️", "male_detective", 1],
|
||||
["🕵", "male_detective", 1],
|
||||
["🧑⚕️", "health_worker", 1],
|
||||
["👩⚕️", "woman_health_worker", 1],
|
||||
["👨⚕️", "man_health_worker", 1],
|
||||
@@ -283,35 +270,26 @@
|
||||
["🧑⚖️", "judge", 1],
|
||||
["👩⚖️", "woman_judge", 1],
|
||||
["👨⚖️", "man_judge", 1],
|
||||
["🦸", "superhero", 1],
|
||||
["🦸♀️", "woman_superhero", 1],
|
||||
["🦸♂️", "man_superhero", 1],
|
||||
["🦹", "supervillain", 1],
|
||||
["🦹♀️", "woman_supervillain", 1],
|
||||
["🦹♂️", "man_supervillain", 1],
|
||||
["🤶", "mrs_claus", 1],
|
||||
["🧑🎄", "mx_claus", 1],
|
||||
["🎅", "santa", 1],
|
||||
["🥷", "ninja", 1],
|
||||
["🧙", "mage", 1],
|
||||
["🧙♀️", "sorceress", 1],
|
||||
["🧙♂️", "wizard", 1],
|
||||
["🧝", "elf", 1],
|
||||
["🧝♀️", "woman_elf", 1],
|
||||
["🧝♂️", "man_elf", 1],
|
||||
["🧛", "vampire", 1],
|
||||
["🧛♀️", "woman_vampire", 1],
|
||||
["🧛♂️", "man_vampire", 1],
|
||||
["🧟", "zombie", 1],
|
||||
["🧟♀️", "woman_zombie", 1],
|
||||
["🧟♂️", "man_zombie", 1],
|
||||
["🧞", "genie", 1],
|
||||
["🧞♀️", "woman_genie", 1],
|
||||
["🧞♂️", "man_genie", 1],
|
||||
["🧜", "merperson", 1],
|
||||
["🧜♀️", "mermaid", 1],
|
||||
["🧜♂️", "merman", 1],
|
||||
["🧚", "fairy", 1],
|
||||
["🧚♀️", "woman_fairy", 1],
|
||||
["🧚♂️", "man_fairy", 1],
|
||||
["👼", "angel", 1],
|
||||
@@ -327,108 +305,68 @@
|
||||
["👸", "princess", 1],
|
||||
["🤴", "prince", 1],
|
||||
["👰", "person_with_veil", 1],
|
||||
["👰♀️", "bride_with_veil", 1],
|
||||
["👰♂️", "man_with_veil", 1],
|
||||
["👰", "bride_with_veil", 1],
|
||||
["🤵", "person_in_tuxedo", 1],
|
||||
["🤵♀️", "woman_in_tuxedo", 1],
|
||||
["🤵♂️", "man_in_tuxedo", 1],
|
||||
["🏃", "running_person", 1],
|
||||
["🏃➡️", "running_person_facing_right", 1],
|
||||
["🤵", "man_in_tuxedo", 1],
|
||||
["🏃♀️", "running_woman", 1],
|
||||
["🏃♀️➡️", "running_woman_facing_right", 1],
|
||||
["🏃♂️", "running_man", 1],
|
||||
["🏃♂️➡️", "running_man_facing_right", 1],
|
||||
["🚶", "walking_person", 1],
|
||||
["🚶➡️", "walking_person_facing_right", 1],
|
||||
["🏃", "running_man", 1],
|
||||
["🚶♀️", "walking_woman", 1],
|
||||
["🚶♀️➡️", "walking_woman_facing_right", 1],
|
||||
["🚶♂️", "walking_man", 1],
|
||||
["🚶♂️➡️", "walking_man_facing_right", 1],
|
||||
["🚶", "walking_man", 1],
|
||||
["💃", "dancer", 1],
|
||||
["🕺", "man_dancing", 1],
|
||||
["👯", "dancing_people", 1],
|
||||
["👯♀️", "dancing_women", 1],
|
||||
["👯", "dancing_women", 1],
|
||||
["👯♂️", "dancing_men", 1],
|
||||
["👫", "couple", 1],
|
||||
["🧑🤝🧑", "people_holding_hands", 1],
|
||||
["👬", "two_men_holding_hands", 1],
|
||||
["👭", "two_women_holding_hands", 1],
|
||||
["🫂", "people_hugging", 1],
|
||||
["🙇", "bowing_person", 1],
|
||||
["🙇♀️", "bowing_woman", 1],
|
||||
["🙇♂️", "bowing_man", 1],
|
||||
["🤦", "person_facepalming", 1],
|
||||
["🙇", "bowing_man", 1],
|
||||
["🤦♂️", "man_facepalming", 1],
|
||||
["🤦♀️", "woman_facepalming", 1],
|
||||
["🤷", "person_shrugging", 1],
|
||||
["🤷♀️", "woman_shrugging", 1],
|
||||
["🤷", "woman_shrugging", 1],
|
||||
["🤷♂️", "man_shrugging", 1],
|
||||
["💁", "tipping_hand_person", 1],
|
||||
["💁♀️", "tipping_hand_woman", 1],
|
||||
["💁", "tipping_hand_woman", 1],
|
||||
["💁♂️", "tipping_hand_man", 1],
|
||||
["🙅", "no_good_person", 1],
|
||||
["🙅♀️", "no_good_woman", 1],
|
||||
["🙅", "no_good_woman", 1],
|
||||
["🙅♂️", "no_good_man", 1],
|
||||
["🙆", "ok_person", 1],
|
||||
["🙆♀️", "ok_woman", 1],
|
||||
["🙆", "ok_woman", 1],
|
||||
["🙆♂️", "ok_man", 1],
|
||||
["🙋", "raising_hand_person", 1],
|
||||
["🙋♀️", "raising_hand_woman", 1],
|
||||
["🙋", "raising_hand_woman", 1],
|
||||
["🙋♂️", "raising_hand_man", 1],
|
||||
["🙎", "pouting_person", 1],
|
||||
["🙎♀️", "pouting_woman", 1],
|
||||
["🙎", "pouting_woman", 1],
|
||||
["🙎♂️", "pouting_man", 1],
|
||||
["🙍", "frowning_person", 1],
|
||||
["🙍♀️", "frowning_woman", 1],
|
||||
["🙍", "frowning_woman", 1],
|
||||
["🙍♂️", "frowning_man", 1],
|
||||
["💇", "haircut_person", 1],
|
||||
["💇♀️", "haircut_woman", 1],
|
||||
["💇", "haircut_woman", 1],
|
||||
["💇♂️", "haircut_man", 1],
|
||||
["💆", "massage_person", 1],
|
||||
["💆♀️", "massage_woman", 1],
|
||||
["💆", "massage_woman", 1],
|
||||
["💆♂️", "massage_man", 1],
|
||||
["🧖", "person_in_steamy_room", 1],
|
||||
["🧖♀️", "woman_in_steamy_room", 1],
|
||||
["🧖♂️", "man_in_steamy_room", 1],
|
||||
["🧏", "person_deaf", 1],
|
||||
["🧏♀️", "woman_deaf", 1],
|
||||
["🧏♂️", "man_deaf", 1],
|
||||
["🧍", "person_standing", 1],
|
||||
["🧍♀️", "woman_standing", 1],
|
||||
["🧍♂️", "man_standing", 1],
|
||||
["🧎", "person_kneeling", 1],
|
||||
["🧎➡️", "person_kneeling_facing_right", 1],
|
||||
["🧎♀️", "woman_kneeling", 1],
|
||||
["🧎♀️➡️", "woman_kneeling_facing_right", 1],
|
||||
["🧎♂️", "man_kneeling", 1],
|
||||
["🧎♂️➡️", "man_kneeling_facing_right", 1],
|
||||
["🧑🦯", "person_with_probing_cane", 1],
|
||||
["🧑🦯➡️", "person_with_probing_cane_facing_right", 1],
|
||||
["👩🦯", "woman_with_probing_cane", 1],
|
||||
["👩🦯➡️", "woman_with_probing_cane_facing_right", 1],
|
||||
["👨🦯", "man_with_probing_cane", 1],
|
||||
["👨🦯➡️", "man_with_probing_cane_facing_right", 1],
|
||||
["🧑🦼", "person_in_motorized_wheelchair", 1],
|
||||
["🧑🦼➡️", "person_in_motorized_wheelchair_facing_right", 1],
|
||||
["👩🦼", "woman_in_motorized_wheelchair", 1],
|
||||
["👩🦼➡️", "woman_in_motorized_wheelchair_facing_right", 1],
|
||||
["👨🦼", "man_in_motorized_wheelchair", 1],
|
||||
["👨🦼➡️", "man_in_motorized_wheelchair_facing_right", 1],
|
||||
["🧑🦽", "person_in_manual_wheelchair", 1],
|
||||
["🧑🦽➡️", "person_in_manual_wheelchair_facing_right", 1],
|
||||
["👩🦽", "woman_in_manual_wheelchair", 1],
|
||||
["👩🦽➡️", "woman_in_manual_wheelchair_facing_right", 1],
|
||||
["👨🦽", "man_in_manual_wheelchair", 1],
|
||||
["👨🦽➡️", "man_in_manual_wheelchair_facing_right", 1],
|
||||
["💑", "couple_with_heart", 1],
|
||||
["👩❤️👨", "couple_with_heart_woman_man", 1],
|
||||
["💑", "couple_with_heart_woman_man", 1],
|
||||
["👩❤️👩", "couple_with_heart_woman_woman", 1],
|
||||
["👨❤️👨", "couple_with_heart_man_man", 1],
|
||||
["💏", "couplekiss", 1],
|
||||
["👩❤️💋👨", "couplekiss_woman_man", 1],
|
||||
["💏", "couplekiss_man_woman", 1],
|
||||
["👩❤️💋👩", "couplekiss_woman_woman", 1],
|
||||
["👨❤️💋👨", "couplekiss_man_man", 1],
|
||||
["👨👩👦", "family_man_woman_boy", 1],
|
||||
["👪", "family_man_woman_boy", 1],
|
||||
["👨👩👧", "family_man_woman_girl", 1],
|
||||
["👨👩👧👦", "family_man_woman_girl_boy", 1],
|
||||
["👨👩👦👦", "family_man_woman_boy_boy", 1],
|
||||
@@ -453,11 +391,6 @@
|
||||
["👨👧👦", "family_man_girl_boy", 1],
|
||||
["👨👦👦", "family_man_boy_boy", 1],
|
||||
["👨👧👧", "family_man_girl_girl", 1],
|
||||
["👪", "family", 1],
|
||||
["🧑🧑🧒", "family_adult_adult_child", 1],
|
||||
["🧑🧑🧒🧒", "family_adult_adult_child_child", 1],
|
||||
["🧑🧒", "family_adult_child", 1],
|
||||
["🧑🧒🧒", "family_adult_child_child", 1],
|
||||
["🧶", "yarn", 1],
|
||||
["🧵", "thread", 1],
|
||||
["🧥", "coat", 1],
|
||||
@@ -542,7 +475,6 @@
|
||||
["🐦⬛", "black_bird", 2],
|
||||
["🦅", "eagle", 2],
|
||||
["🦉", "owl", 2],
|
||||
["🐦🔥", "phoenix", 2],
|
||||
["🦇", "bat", 2],
|
||||
["🐺", "wolf", 2],
|
||||
["🐗", "boar", 2],
|
||||
@@ -643,7 +575,6 @@
|
||||
["🌿", "herb", 2],
|
||||
["☘", "shamrock", 2],
|
||||
["🍀", "four_leaf_clover", 2],
|
||||
["", "leafless_tree", 2],
|
||||
["🎍", "bamboo", 2],
|
||||
["🎋", "tanabata_tree", 2],
|
||||
["🍃", "leaves", 2],
|
||||
@@ -717,7 +648,6 @@
|
||||
["🪸", "coral", 2],
|
||||
["🪹", "empty_nest", 2],
|
||||
["🪺", "nest_with_eggs", 2],
|
||||
["🍋🟩", "lime", 3],
|
||||
["🍏", "green_apple", 3],
|
||||
["🍎", "apple", 3],
|
||||
["🍐", "pear", 3],
|
||||
@@ -737,8 +667,6 @@
|
||||
["🥑", "avocado", 3],
|
||||
["🫛", "pea_pod", 3],
|
||||
["🥦", "broccoli", 3],
|
||||
["🍄🟫", "brown_mushroom", 3],
|
||||
["", "root_vegetable", 3],
|
||||
["🍅", "tomato", 3],
|
||||
["🍆", "eggplant", 3],
|
||||
["🥒", "cucumber", 3],
|
||||
@@ -858,9 +786,8 @@
|
||||
["🥏", "flying_disc", 4],
|
||||
["🎱", "8ball", 4],
|
||||
["⛳", "golf", 4],
|
||||
["🏌", "golfing_person", 4],
|
||||
["🏌️♀️", "golfing_woman", 4],
|
||||
["🏌️♂️", "golfing_man", 4],
|
||||
["🏌", "golfing_man", 4],
|
||||
["🏓", "ping_pong", 4],
|
||||
["🏸", "badminton", 4],
|
||||
["🥅", "goal_net", 4],
|
||||
@@ -872,13 +799,10 @@
|
||||
["⛷", "skier", 4],
|
||||
["🏂", "snowboarder", 4],
|
||||
["🤺", "person_fencing", 4],
|
||||
["🤼", "people_wrestling", 4],
|
||||
["🤼♀️", "women_wrestling", 4],
|
||||
["🤼♂️", "men_wrestling", 4],
|
||||
["🤸", "person_cartwheeling", 4],
|
||||
["🤸♀️", "woman_cartwheeling", 4],
|
||||
["🤸♂️", "man_cartwheeling", 4],
|
||||
["🤾", "person_playing_handball", 4],
|
||||
["🤾♀️", "woman_playing_handball", 4],
|
||||
["🤾♂️", "man_playing_handball", 4],
|
||||
["⛸", "ice_skate", 4],
|
||||
@@ -889,37 +813,27 @@
|
||||
["🎣", "fishing_pole_and_fish", 4],
|
||||
["🥊", "boxing_glove", 4],
|
||||
["🥋", "martial_arts_uniform", 4],
|
||||
["🚣", "rowing_person", 4],
|
||||
["🚣♀️", "rowing_woman", 4],
|
||||
["🚣♂️", "rowing_man", 4],
|
||||
["🧗", "climbing_person", 4],
|
||||
["🚣", "rowing_man", 4],
|
||||
["🧗♀️", "climbing_woman", 4],
|
||||
["🧗♂️", "climbing_man", 4],
|
||||
["🏊", "swimming_person", 4],
|
||||
["🏊♀️", "swimming_woman", 4],
|
||||
["🏊♂️", "swimming_man", 4],
|
||||
["🤽", "person_playing_water_polo", 4],
|
||||
["🏊", "swimming_man", 4],
|
||||
["🤽♀️", "woman_playing_water_polo", 4],
|
||||
["🤽♂️", "man_playing_water_polo", 4],
|
||||
["🧘", "person_in_lotus_position", 4],
|
||||
["🧘♀️", "woman_in_lotus_position", 4],
|
||||
["🧘♂️", "man_in_lotus_position", 4],
|
||||
["🏄", "surfing_person", 4],
|
||||
["🏄♀️", "surfing_woman", 4],
|
||||
["🏄♂️", "surfing_man", 4],
|
||||
["🏄", "surfing_man", 4],
|
||||
["🛀", "bath", 4],
|
||||
["⛹", "basketball_person", 4],
|
||||
["⛹️♀️", "basketball_woman", 4],
|
||||
["⛹️♂️", "basketball_man", 4],
|
||||
["🏋", "weight_lifting_person", 4],
|
||||
["⛹", "basketball_man", 4],
|
||||
["🏋️♀️", "weight_lifting_woman", 4],
|
||||
["🏋️♂️", "weight_lifting_man", 4],
|
||||
["🚴", "biking_person", 4],
|
||||
["🏋", "weight_lifting_man", 4],
|
||||
["🚴♀️", "biking_woman", 4],
|
||||
["🚴♂️", "biking_man", 4],
|
||||
["🚵", "mountain_biking_person", 4],
|
||||
["🚴", "biking_man", 4],
|
||||
["🚵♀️", "mountain_biking_woman", 4],
|
||||
["🚵♂️", "mountain_biking_man", 4],
|
||||
["🚵", "mountain_biking_man", 4],
|
||||
["🏇", "horse_racing", 4],
|
||||
["🤿", "diving_mask", 4],
|
||||
["🪀", "yo_yo", 4],
|
||||
@@ -942,7 +856,6 @@
|
||||
["🎭", "performing_arts", 4],
|
||||
["🎨", "art", 4],
|
||||
["🎪", "circus_tent", 4],
|
||||
["🤹", "person_juggling", 4],
|
||||
["🤹♀️", "woman_juggling", 4],
|
||||
["🤹♂️", "man_juggling", 4],
|
||||
["🎤", "microphone", 4],
|
||||
@@ -959,7 +872,6 @@
|
||||
["🪕", "banjo", 4],
|
||||
["🪗", "accordion", 4],
|
||||
["🪘", "long_drum", 4],
|
||||
["", "harp", 4],
|
||||
["🎬", "clapper", 4],
|
||||
["🎮", "video_game", 4],
|
||||
["👾", "space_invader", 4],
|
||||
@@ -1164,10 +1076,8 @@
|
||||
["🪙", "coin", 6],
|
||||
["💳", "credit_card", 6],
|
||||
["🪪", "identification_card", 6],
|
||||
["🥾", "hiking_boot", 6],
|
||||
["💎", "gem", 6],
|
||||
["⚖", "balance_scale", 6],
|
||||
["⛓️💥", "broken_chain", 6],
|
||||
["🧰", "toolbox", 6],
|
||||
["🔧", "wrench", 6],
|
||||
["🔨", "hammer", 6],
|
||||
@@ -1183,7 +1093,6 @@
|
||||
["🪛", "screwdriver", 6],
|
||||
["🪝", "hook", 6],
|
||||
["🪜", "ladder", 6],
|
||||
["", "shovel", 6],
|
||||
["🧱", "brick", 6],
|
||||
["⛓", "chains", 6],
|
||||
["🧲", "magnet", 6],
|
||||
@@ -1395,8 +1304,6 @@
|
||||
["♓", "pisces", 7],
|
||||
["🆔", "id", 7],
|
||||
["⚛", "atom_symbol", 7],
|
||||
["♀️", "female_sign", 7],
|
||||
["♂️", "male_sign", 7],
|
||||
["⚧️", "transgender_symbol", 7],
|
||||
["🈳", "u7a7a", 7],
|
||||
["🈹", "u5272", 7],
|
||||
@@ -1556,11 +1463,9 @@
|
||||
["♾", "infinity", 7],
|
||||
["💲", "heavy_dollar_sign", 7],
|
||||
["💱", "currency_exchange", 7],
|
||||
["⚕️", "medical_symbol", 7],
|
||||
["©️", "copyright", 7],
|
||||
["®️", "registered", 7],
|
||||
["™️", "tm", 7],
|
||||
["", "splatter", 7],
|
||||
["🔚", "end", 7],
|
||||
["🔙", "back", 7],
|
||||
["🔛", "on", 7],
|
||||
@@ -1671,7 +1576,6 @@
|
||||
["🇧🇲", "bermuda", 8],
|
||||
["🇧🇹", "bhutan", 8],
|
||||
["🇧🇴", "bolivia", 8],
|
||||
["🇧🇻", "bouvet_island", 8],
|
||||
["🇧🇶", "caribbean_netherlands", 8],
|
||||
["🇧🇦", "bosnia_herzegovina", 8],
|
||||
["🇧🇼", "botswana", 8],
|
||||
@@ -1689,12 +1593,10 @@
|
||||
["🇮🇨", "canary_islands", 8],
|
||||
["🇰🇾", "cayman_islands", 8],
|
||||
["🇨🇫", "central_african_republic", 8],
|
||||
["🇪🇦", "ceuta_melilla", 8],
|
||||
["🇹🇩", "chad", 8],
|
||||
["🇨🇱", "chile", 8],
|
||||
["🇨🇳", "cn", 8],
|
||||
["🇨🇽", "christmas_island", 8],
|
||||
["🇨🇵", "clipperton_island", 8],
|
||||
["🇨🇨", "cocos_islands", 8],
|
||||
["🇨🇴", "colombia", 8],
|
||||
["🇰🇲", "comoros", 8],
|
||||
@@ -1708,7 +1610,6 @@
|
||||
["🇨🇾", "cyprus", 8],
|
||||
["🇨🇿", "czech_republic", 8],
|
||||
["🇩🇰", "denmark", 8],
|
||||
["🇩🇬", "diego_garcia", 8],
|
||||
["🇩🇯", "djibouti", 8],
|
||||
["🇩🇲", "dominica", 8],
|
||||
["🇩🇴", "dominican_republic", 8],
|
||||
@@ -1745,7 +1646,6 @@
|
||||
["🇬🇼", "guinea_bissau", 8],
|
||||
["🇬🇾", "guyana", 8],
|
||||
["🇭🇹", "haiti", 8],
|
||||
["🇭🇲", "heard_mcdonald_islands", 8],
|
||||
["🇭🇳", "honduras", 8],
|
||||
["🇭🇰", "hong_kong", 8],
|
||||
["🇭🇺", "hungary", 8],
|
||||
@@ -1833,12 +1733,10 @@
|
||||
["🇷🇴", "romania", 8],
|
||||
["🇷🇺", "ru", 8],
|
||||
["🇷🇼", "rwanda", 8],
|
||||
["🇨🇶", "sark", 8],
|
||||
["🇧🇱", "st_barthelemy", 8],
|
||||
["🇸🇭", "st_helena", 8],
|
||||
["🇰🇳", "st_kitts_nevis", 8],
|
||||
["🇱🇨", "st_lucia", 8],
|
||||
["🇲🇫", "st_martin", 8],
|
||||
["🇵🇲", "st_pierre_miquelon", 8],
|
||||
["🇻🇨", "st_vincent_grenadines", 8],
|
||||
["🇼🇸", "samoa", 8],
|
||||
@@ -1864,7 +1762,6 @@
|
||||
["🇸🇩", "sudan", 8],
|
||||
["🇸🇷", "suriname", 8],
|
||||
["🇸🇿", "swaziland", 8],
|
||||
["🇸🇯", "svalbard_jan_mayen", 8],
|
||||
["🇸🇪", "sweden", 8],
|
||||
["🇨🇭", "switzerland", 8],
|
||||
["🇸🇾", "syria", 8],
|
||||
@@ -1891,7 +1788,6 @@
|
||||
["🏴", "scotland", 8],
|
||||
["🏴", "wales", 8],
|
||||
["🇺🇸", "us", 8],
|
||||
["🇺🇲", "us_outlying_islands", 8],
|
||||
["🇻🇮", "us_virgin_islands", 8],
|
||||
["🇺🇾", "uruguay", 8],
|
||||
["🇺🇿", "uzbekistan", 8],
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"esbuild": "0.25.8",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"esbuild": "0.25.6",
|
||||
"eslint-plugin-vue": "10.3.0",
|
||||
"nodemon": "3.1.10",
|
||||
"typescript": "5.9.2",
|
||||
"typescript": "5.8.3",
|
||||
"vue-eslint-parser": "10.2.0"
|
||||
},
|
||||
"files": [
|
||||
@@ -35,6 +35,6 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"misskey-js": "workspace:*",
|
||||
"vue": "3.5.18"
|
||||
"vue": "3.5.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const commonHandlers = [
|
||||
}),
|
||||
http.get('/twemoji/:codepoints.svg', async ({ params }) => {
|
||||
const { codepoints } = params;
|
||||
const value = await fetch(`https://unpkg.com/@discordapp/twemoji@16.0.1/dist/svg/${codepoints}.svg`).then((response) => response.blob());
|
||||
const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
|
||||
return new HttpResponse(value, {
|
||||
headers: {
|
||||
'Content-Type': 'image/svg+xml',
|
||||
|
||||
@@ -39,7 +39,6 @@ export interface SearchIndexItem {
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
texts: string[];
|
||||
icon?: string;
|
||||
inlining?: string[];
|
||||
}
|
||||
@@ -228,14 +227,14 @@ function extractElementText2Inner(node: TemplateChildNode, processingNodeName: s
|
||||
// region extractUsageInfoFromTemplateAst
|
||||
|
||||
/**
|
||||
* SearchLabel/SearchText/SearchIconを探して抽出する関数
|
||||
* SearchLabel/SearchKeyword/SearchIconを探して抽出する関数
|
||||
*/
|
||||
function extractSugarTags(nodes: TemplateChildNode[], id: string): { label: string | null; texts: string[]; icon: string | null; } {
|
||||
function extractSugarTags(nodes: TemplateChildNode[], id: string): { label: string | null, keywords: string[], icon: string | null } {
|
||||
let label: string | null | undefined = undefined;
|
||||
let icon: string | null | undefined = undefined;
|
||||
const texts: string[] = [];
|
||||
const keywords: string[] = [];
|
||||
|
||||
logger.info(`Extracting labels and texts from ${nodes.length} nodes`);
|
||||
logger.info(`Extracting labels and keywords from ${nodes.length} nodes`);
|
||||
|
||||
walkVueElements(nodes, null, (node) => {
|
||||
switch (node.tag) {
|
||||
@@ -249,10 +248,10 @@ function extractSugarTags(nodes: TemplateChildNode[], id: string): { label: stri
|
||||
|
||||
label = extractElementText(node, id);
|
||||
return;
|
||||
case 'SearchText':
|
||||
case 'SearchKeyword':
|
||||
const content = extractElementText(node, id);
|
||||
if (content) {
|
||||
texts.push(content);
|
||||
keywords.push(content);
|
||||
}
|
||||
return;
|
||||
case 'SearchIcon':
|
||||
@@ -279,8 +278,8 @@ function extractSugarTags(nodes: TemplateChildNode[], id: string): { label: stri
|
||||
});
|
||||
|
||||
// デバッグ情報
|
||||
logger.info(`Extraction completed: label=${label}, text=[${texts.join(', ')}, icon=${icon}]`);
|
||||
return { label: label ?? null, texts, icon: icon ?? null };
|
||||
logger.info(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}, icon=${icon}]`);
|
||||
return { label: label ?? null, keywords, icon: icon ?? null };
|
||||
}
|
||||
|
||||
function getStringProp(attr: AttributeNode | DirectiveNode | null, id: string): string | null {
|
||||
@@ -352,36 +351,33 @@ function extractUsageInfoFromTemplateAst(
|
||||
parentId: parentId ?? undefined,
|
||||
label: '', // デフォルト値
|
||||
keywords: [],
|
||||
texts: [],
|
||||
};
|
||||
|
||||
// バインドプロパティを取得
|
||||
const path = getStringProp(findAttribute(node.props, 'path'), id);
|
||||
const icon = getStringProp(findAttribute(node.props, 'icon'), id);
|
||||
const label = getStringProp(findAttribute(node.props, 'label'), id);
|
||||
const inlining = getStringArrayProp(findAttribute(node.props, 'inlining'), id);
|
||||
const keywords = getStringArrayProp(findAttribute(node.props, 'keywords'), id);
|
||||
const texts = getStringArrayProp(findAttribute(node.props, 'texts'), id);
|
||||
const path = getStringProp(findAttribute(node.props, 'path'), id)
|
||||
const icon = getStringProp(findAttribute(node.props, 'icon'), id)
|
||||
const label = getStringProp(findAttribute(node.props, 'label'), id)
|
||||
const inlining = getStringArrayProp(findAttribute(node.props, 'inlining'), id)
|
||||
const keywords = getStringArrayProp(findAttribute(node.props, 'keywords'), id)
|
||||
|
||||
if (path) markerInfo.path = path;
|
||||
if (icon) markerInfo.icon = icon;
|
||||
if (label) markerInfo.label = label;
|
||||
if (inlining) markerInfo.inlining = inlining;
|
||||
if (keywords) markerInfo.keywords = keywords;
|
||||
if (texts) markerInfo.texts = texts;
|
||||
|
||||
// pathがない場合はファイルパスを設定
|
||||
//pathがない場合はファイルパスを設定
|
||||
if (markerInfo.path == null && parentId == null) {
|
||||
markerInfo.path = id.match(/.*(\/(admin|settings)\/[^\/]+)\.vue$/)?.[1];
|
||||
}
|
||||
|
||||
// SearchLabelとSearchTextを抽出 (AST全体を探索)
|
||||
// SearchLabelとSearchKeywordを抽出 (AST全体を探索)
|
||||
{
|
||||
const extracted = extractSugarTags(node.children, id);
|
||||
if (extracted.label && markerInfo.label) logger.warn(`Duplicate label found for ${markerId} at ${id}:${node.loc.start.line}`);
|
||||
if (extracted.icon && markerInfo.icon) logger.warn(`Duplicate icon found for ${markerId} at ${id}:${node.loc.start.line}`);
|
||||
markerInfo.label = extracted.label ?? markerInfo.label ?? '';
|
||||
markerInfo.texts = [...extracted.texts, ...markerInfo.texts];
|
||||
markerInfo.keywords = [...extracted.keywords, ...markerInfo.keywords];
|
||||
markerInfo.icon = extracted.icon ?? markerInfo.icon ?? undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@analytics/google-analytics": "1.1.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@github/webauthn-json": "2.1.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@rollup/pluginutils": "5.2.0",
|
||||
"@sentry/vue": "10.0.0",
|
||||
"@sentry/vue": "9.39.0",
|
||||
"@syuilo/aiscript": "0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "3.5.18",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"@vitejs/plugin-vue": "5.2.4",
|
||||
"@vue/compiler-sfc": "3.5.17",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||
"analytics": "0.8.16",
|
||||
"astring": "1.9.0",
|
||||
@@ -37,12 +37,12 @@
|
||||
"canvas-confetti": "1.9.3",
|
||||
"chart.js": "4.5.0",
|
||||
"chartjs-adapter-date-fns": "3.0.0",
|
||||
"chartjs-chart-matrix": "3.0.0",
|
||||
"chartjs-chart-matrix": "2.1.1",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"chromatic": "13.1.3",
|
||||
"chromatic": "11.29.0",
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "2.0.1",
|
||||
"cropperjs": "2.0.0",
|
||||
"date-fns": "4.1.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"eventemitter3": "5.0.1",
|
||||
@@ -54,36 +54,36 @@
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.17",
|
||||
"matter-js": "0.20.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.4",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.46.2",
|
||||
"rollup": "4.45.1",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.89.2",
|
||||
"shiki": "3.9.1",
|
||||
"shiki": "3.8.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.179.1",
|
||||
"three": "0.178.0",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"typescript": "5.8.3",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.0.6",
|
||||
"vue": "3.5.18",
|
||||
"vite": "6.3.5",
|
||||
"vue": "3.5.17",
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.3",
|
||||
"@storybook/addon-actions": "9.0.8",
|
||||
"@misskey-dev/summaly": "5.2.2",
|
||||
"@storybook/addon-actions": "8.6.14",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-interactions": "8.6.14",
|
||||
"@storybook/addon-links": "9.1.0",
|
||||
"@storybook/addon-links": "8.6.14",
|
||||
"@storybook/addon-mdx-gfm": "8.6.14",
|
||||
"@storybook/addon-storysource": "8.6.14",
|
||||
"@storybook/blocks": "8.6.14",
|
||||
@@ -91,38 +91,38 @@
|
||||
"@storybook/core-events": "8.6.14",
|
||||
"@storybook/manager-api": "8.6.14",
|
||||
"@storybook/preview-api": "8.6.14",
|
||||
"@storybook/react": "9.1.0",
|
||||
"@storybook/react-vite": "9.1.0",
|
||||
"@storybook/react": "8.6.14",
|
||||
"@storybook/react-vite": "8.6.14",
|
||||
"@storybook/test": "8.6.14",
|
||||
"@storybook/theming": "8.6.14",
|
||||
"@storybook/types": "8.6.14",
|
||||
"@storybook/vue3": "9.1.0",
|
||||
"@storybook/vue3-vite": "9.1.0",
|
||||
"@tabler/icons-webfont": "3.34.1",
|
||||
"@storybook/vue3": "8.6.14",
|
||||
"@storybook/vue3-vite": "8.6.14",
|
||||
"@tabler/icons-webfont": "3.34.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/matter-js": "0.19.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.17.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/runtime-core": "3.5.18",
|
||||
"@vue/compiler-core": "3.5.17",
|
||||
"@vue/runtime-core": "3.5.17",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.0.0",
|
||||
"cypress": "14.5.3",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "14.5.2",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"eslint-plugin-vue": "10.3.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "18.0.1",
|
||||
"happy-dom": "17.6.3",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.0.3",
|
||||
@@ -130,17 +130,17 @@
|
||||
"msw-storybook-addon": "2.0.5",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.0.12",
|
||||
"storybook": "9.1.0",
|
||||
"storybook": "8.6.14",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.0.5",
|
||||
"vue-component-type-helpers": "2.2.12",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.5"
|
||||
"vue-tsc": "2.2.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { computed, watch, version as vueVersion } from 'vue';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { version, lang, updateLocale, locale, apiUrl, isSafeMode } from '@@/js/config.js';
|
||||
import { version, lang, updateLocale, locale, apiUrl } from '@@/js/config.js';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { App } from 'vue';
|
||||
@@ -168,35 +168,28 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
|
||||
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
|
||||
watch(store.r.darkMode, (darkMode) => {
|
||||
const theme = (() => {
|
||||
if (darkMode) {
|
||||
return isSafeMode ? defaultDarkTheme : (prefer.s.darkTheme ?? defaultDarkTheme);
|
||||
} else {
|
||||
return isSafeMode ? defaultLightTheme : (prefer.s.lightTheme ?? defaultLightTheme);
|
||||
}
|
||||
})();
|
||||
|
||||
applyTheme(theme);
|
||||
}, { immediate: isSafeMode || miLocalStorage.getItem('theme') == null });
|
||||
applyTheme(darkMode
|
||||
? (prefer.s.darkTheme ?? defaultDarkTheme)
|
||||
: (prefer.s.lightTheme ?? defaultLightTheme),
|
||||
);
|
||||
}, { immediate: miLocalStorage.getItem('theme') == null });
|
||||
|
||||
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||
|
||||
if (!isSafeMode) {
|
||||
const darkTheme = prefer.model('darkTheme');
|
||||
const lightTheme = prefer.model('lightTheme');
|
||||
const darkTheme = prefer.model('darkTheme');
|
||||
const lightTheme = prefer.model('lightTheme');
|
||||
|
||||
watch(darkTheme, (theme) => {
|
||||
if (store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultDarkTheme);
|
||||
}
|
||||
});
|
||||
watch(darkTheme, (theme) => {
|
||||
if (store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultDarkTheme);
|
||||
}
|
||||
});
|
||||
|
||||
watch(lightTheme, (theme) => {
|
||||
if (!store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
watch(lightTheme, (theme) => {
|
||||
if (!store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
|
||||
//#region Sync dark mode
|
||||
if (prefer.s.syncDeviceDarkMode) {
|
||||
@@ -210,20 +203,18 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
||||
});
|
||||
//#endregion
|
||||
|
||||
if (!isSafeMode) {
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
});
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
});
|
||||
|
||||
watch(prefer.r.overridedDeviceKind, (kind) => {
|
||||
updateDeviceKind(kind);
|
||||
}, { immediate: true });
|
||||
|
||||
@@ -28,8 +28,8 @@ import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { launchPlugins } from '@/plugin.js';
|
||||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||
import { signout } from '@/signout.js';
|
||||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated, lastVersion } = await common(async () => {
|
||||
@@ -391,8 +391,6 @@ export async function mainBoot() {
|
||||
}
|
||||
|
||||
// shortcut
|
||||
let safemodeRequestCount = 0;
|
||||
let safemodeRequestTimer: number | null = null;
|
||||
const keymap = {
|
||||
'p|n': () => {
|
||||
if ($i == null) return;
|
||||
@@ -404,24 +402,6 @@ export async function mainBoot() {
|
||||
's': () => {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
'g': {
|
||||
callback: () => {
|
||||
// mを5回押すとセーフモードに入る
|
||||
safemodeRequestCount++;
|
||||
if (safemodeRequestCount >= 5) {
|
||||
miLocalStorage.setItem('isSafeMode', 'true');
|
||||
unisonReload();
|
||||
} else {
|
||||
if (safemodeRequestTimer != null) {
|
||||
window.clearTimeout(safemodeRequestTimer);
|
||||
}
|
||||
safemodeRequestTimer = window.setTimeout(() => {
|
||||
safemodeRequestCount = 0;
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
allowRepeat: true,
|
||||
}
|
||||
} as const satisfies Keymap;
|
||||
window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@contextmenu.stop
|
||||
@keydown.stop
|
||||
>
|
||||
<button v-if="hide" :class="$style.hidden" @click="reveal">
|
||||
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ prefer.s.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
||||
@@ -157,7 +157,7 @@ const audioEl = useTemplateRef('audioEl');
|
||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
|
||||
|
||||
async function reveal() {
|
||||
async function show() {
|
||||
if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="reveal">
|
||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
|
||||
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
||||
<b>{{ i18n.ts.sensitive }}</b>
|
||||
<span>{{ i18n.ts.clickToShow }}</span>
|
||||
@@ -37,7 +37,7 @@ const props = defineProps<{
|
||||
|
||||
const hide = ref(true);
|
||||
|
||||
async function reveal() {
|
||||
async function show() {
|
||||
if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="reveal">
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
|
||||
<component
|
||||
:is="disableImageLink ? 'div' : 'a'"
|
||||
v-bind="disableImageLink ? {
|
||||
@@ -96,10 +96,10 @@ const url = computed(() => (props.raw || prefer.s.loadRawImages)
|
||||
? props.image.url
|
||||
: prefer.s.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(props.image.url)
|
||||
: props.image.thumbnailUrl!,
|
||||
: props.image.thumbnailUrl,
|
||||
);
|
||||
|
||||
async function reveal(ev: MouseEvent) {
|
||||
async function onclick(ev: MouseEvent) {
|
||||
if (!props.controls) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@contextmenu.stop
|
||||
@keydown.stop
|
||||
>
|
||||
<button v-if="hide" :class="$style.hidden" @click="reveal">
|
||||
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ prefer.s.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
|
||||
@@ -178,7 +178,7 @@ function hasFocus() {
|
||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore'));
|
||||
|
||||
async function reveal() {
|
||||
async function show() {
|
||||
if (props.video.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
|
||||
@@ -52,9 +52,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
{{ item.label }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span style="opacity: 0.7; font-size: 90%; word-break: break-word;">{{ item.parentLabels.join(' > ') }}</span>
|
||||
<span style="opacity: 0.7; font-size: 90%;">{{ item.parentLabels.join(' > ') }}</span>
|
||||
<br>
|
||||
<span style="word-break: break-word;">{{ item.label }}</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</MkA>
|
||||
@@ -95,7 +95,7 @@ export type SuperMenuDef = {
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef, ref, watch, nextTick, computed } from 'vue';
|
||||
import { getScrollContainer } from '@@/js/scroll.js';
|
||||
import type { SearchIndexItem } from '@/utility/inapp-search.js';
|
||||
import type { SearchIndexItem } from '@/utility/settings-search-index.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
@@ -165,28 +165,12 @@ watch(rawSearchQuery, (value) => {
|
||||
});
|
||||
};
|
||||
|
||||
// label, keywords, texts の順に優先して表示
|
||||
|
||||
let items = Array.from(searchIndexItemById.values());
|
||||
|
||||
for (const item of items) {
|
||||
if (compareStringIncludes(item.label, value)) {
|
||||
for (const item of searchIndexItemById.values()) {
|
||||
if (
|
||||
compareStringIncludes(item.label, value) ||
|
||||
item.keywords.some((x) => compareStringIncludes(x, value))
|
||||
) {
|
||||
addSearchResult(item);
|
||||
items = items.filter(i => i.id !== item.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (item.keywords.some((x) => compareStringIncludes(x, value))) {
|
||||
addSearchResult(item);
|
||||
items = items.filter(i => i.id !== item.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (item.texts.some((x) => compareStringIncludes(x, value))) {
|
||||
addSearchResult(item);
|
||||
items = items.filter(i => i.id !== item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
</style>
|
||||
@@ -31,7 +31,7 @@ import PageWithHeader from './global/PageWithHeader.vue';
|
||||
import PageWithAnimBg from './global/PageWithAnimBg.vue';
|
||||
import SearchMarker from './global/SearchMarker.vue';
|
||||
import SearchLabel from './global/SearchLabel.vue';
|
||||
import SearchText from './global/SearchText.vue';
|
||||
import SearchKeyword from './global/SearchKeyword.vue';
|
||||
import SearchIcon from './global/SearchIcon.vue';
|
||||
|
||||
import type { App } from 'vue';
|
||||
@@ -71,7 +71,7 @@ export const components = {
|
||||
PageWithAnimBg: PageWithAnimBg,
|
||||
SearchMarker: SearchMarker,
|
||||
SearchLabel: SearchLabel,
|
||||
SearchText: SearchText,
|
||||
SearchKeyword: SearchKeyword,
|
||||
SearchIcon: SearchIcon,
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ declare module '@vue/runtime-core' {
|
||||
PageWithAnimBg: typeof PageWithAnimBg;
|
||||
SearchMarker: typeof SearchMarker;
|
||||
SearchLabel: typeof SearchLabel;
|
||||
SearchText: typeof SearchText;
|
||||
SearchKeyword: typeof SearchKeyword;
|
||||
SearchIcon: typeof SearchIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +165,6 @@ function buildFullPath(args: {
|
||||
const replaceRegex = new RegExp(`:${key}(\\?)?`, 'g');
|
||||
fullPath = fullPath.replace(replaceRegex, value ? encodeURIComponent(value) : '');
|
||||
}
|
||||
// remove any optional parameters that are not provided
|
||||
fullPath = fullPath.replace(/\/:\w+\?(?=\/|$)/g, '');
|
||||
}
|
||||
|
||||
if (args.query) {
|
||||
|
||||
@@ -33,7 +33,6 @@ export type Keys = (
|
||||
'preferences' |
|
||||
'latestPreferencesUpdate' |
|
||||
'hidePreferencesRestoreSuggestion' |
|
||||
'isSafeMode' |
|
||||
`miux:${string}` |
|
||||
`ui:folder:${string}` |
|
||||
`themes:${string}` | // DEPRECATED
|
||||
|
||||
@@ -4,161 +4,158 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker markerId="botProtection" :keywords="['bot', 'protection', 'captcha', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-shield"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.botProtection }}</SearchLabel></template>
|
||||
<template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
|
||||
<template #footer>
|
||||
<MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-shield"></i></template>
|
||||
<template #label>{{ i18n.ts.botProtection }}</template>
|
||||
<template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
|
||||
<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
|
||||
<template #footer>
|
||||
<MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkRadios v-model="botProtectionForm.state.provider">
|
||||
<option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
|
||||
<option value="hcaptcha">hCaptcha</option>
|
||||
<option value="mcaptcha">mCaptcha</option>
|
||||
<option value="recaptcha">reCAPTCHA</option>
|
||||
<option value="turnstile">Turnstile</option>
|
||||
<option value="testcaptcha">testCaptcha</option>
|
||||
</MkRadios>
|
||||
|
||||
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="hcaptcha"
|
||||
:sitekey="botProtectionForm.state.hcaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.hcaptchaSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||
<div>
|
||||
<span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkRadios v-model="botProtectionForm.state.provider">
|
||||
<option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
|
||||
<option value="hcaptcha">hCaptcha</option>
|
||||
<option value="mcaptcha">mCaptcha</option>
|
||||
<option value="recaptcha">reCAPTCHA</option>
|
||||
<option value="turnstile">Turnstile</option>
|
||||
<option value="testcaptcha">testCaptcha</option>
|
||||
</MkRadios>
|
||||
<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="mcaptcha"
|
||||
:sitekey="botProtectionForm.state.mcaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.mcaptchaSecretKey"
|
||||
:instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
|
||||
/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
|
||||
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="hcaptcha"
|
||||
:sitekey="botProtectionForm.state.hcaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.hcaptchaSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||
<div>
|
||||
<span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
|
||||
</div>
|
||||
<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="recaptcha"
|
||||
:sitekey="botProtectionForm.state.recaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.recaptchaSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||
<div>
|
||||
<span>ref: </span>
|
||||
<a
|
||||
href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do"
|
||||
target="_blank"
|
||||
>reCAPTCHA FAQ</a>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
|
||||
<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="mcaptcha"
|
||||
:sitekey="botProtectionForm.state.mcaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.mcaptchaSecretKey"
|
||||
:instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
|
||||
/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
|
||||
<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
|
||||
<MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="recaptcha"
|
||||
:sitekey="botProtectionForm.state.recaptchaSiteKey"
|
||||
:secretKey="botProtectionForm.state.recaptchaSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
|
||||
<div>
|
||||
<span>ref: </span>
|
||||
<a
|
||||
href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do"
|
||||
target="_blank"
|
||||
>reCAPTCHA FAQ</a>
|
||||
</div>
|
||||
<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
|
||||
<MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="turnstile"
|
||||
:sitekey="botProtectionForm.state.turnstileSiteKey"
|
||||
:secretKey="botProtectionForm.state.turnstileSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>
|
||||
{{ i18n.ts._captcha.testSiteKeyMessage }}
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
|
||||
<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
|
||||
<MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
|
||||
</MkInput>
|
||||
<FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha
|
||||
v-model="captchaResult"
|
||||
provider="turnstile"
|
||||
:sitekey="botProtectionForm.state.turnstileSiteKey"
|
||||
:secretKey="botProtectionForm.state.turnstileSecretKey"
|
||||
/>
|
||||
</FormSlot>
|
||||
<MkInfo>
|
||||
<div :class="$style.captchaInfoMsg">
|
||||
<div>
|
||||
{{ i18n.ts._captcha.testSiteKeyMessage }}
|
||||
</div>
|
||||
<div>
|
||||
<span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</template>
|
||||
|
||||
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
|
||||
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
|
||||
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._captcha.verify }}</template>
|
||||
<MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
|
||||
</FormSlot>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { ApiWithDialogCustomErrors } from '@/os.js';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
@@ -170,6 +167,7 @@ import { useForm } from '@/composables/use-form.js';
|
||||
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import type { ApiWithDialogCustomErrors } from '@/os.js';
|
||||
|
||||
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
|
||||
|
||||
|
||||
@@ -6,117 +6,89 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/branding" :label="i18n.ts.branding" :keywords="['branding']" icon="ti ti-paint">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['icon', 'image']">
|
||||
<MkInput v-model="iconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="iconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts._serverSettings.iconUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['icon', 'image']">
|
||||
<MkInput v-model="app192IconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</SearchLabel></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
|
||||
</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="app192IconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
|
||||
</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['icon', 'image']">
|
||||
<MkInput v-model="app512IconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</SearchLabel></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
|
||||
</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="app512IconUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
|
||||
</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['banner', 'image']">
|
||||
<MkInput v-model="bannerUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.bannerUrl }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="bannerUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.bannerUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['background', 'image']">
|
||||
<MkInput v-model="backgroundImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.backgroundImageUrl }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="backgroundImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['image']">
|
||||
<MkInput v-model="notFoundImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.notFoundDescription }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="notFoundImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.notFoundDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['image']">
|
||||
<MkInput v-model="infoImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.nothing }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.nothing }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['image']">
|
||||
<MkInput v-model="serverErrorImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.somethingHappened }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="serverErrorImageUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.somethingHappened }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['theme', 'color']">
|
||||
<MkColorInput v-model="themeColor">
|
||||
<template #label><SearchLabel>{{ i18n.ts.themeColor }}</SearchLabel></template>
|
||||
</MkColorInput>
|
||||
</SearchMarker>
|
||||
<MkColorInput v-model="themeColor">
|
||||
<template #label>{{ i18n.ts.themeColor }}</template>
|
||||
</MkColorInput>
|
||||
|
||||
<SearchMarker :keywords="['theme', 'default', 'light']">
|
||||
<MkTextarea v-model="defaultLightTheme">
|
||||
<template #label><SearchLabel>{{ i18n.ts.instanceDefaultLightTheme }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
<MkTextarea v-model="defaultLightTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<SearchMarker :keywords="['theme', 'default', 'dark']">
|
||||
<MkTextarea v-model="defaultDarkTheme">
|
||||
<template #label><SearchLabel>{{ i18n.ts.instanceDefaultDarkTheme }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
<MkTextarea v-model="defaultDarkTheme">
|
||||
<template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="repositoryUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.repositoryUrl }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="repositoryUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.repositoryUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="feedbackUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.feedbackUrl }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="feedbackUrl" type="url">
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts.feedbackUrl }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkTextarea v-model="manifestJsonOverride">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.manifestJsonOverride }}</SearchLabel></template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
<MkTextarea v-model="manifestJsonOverride">
|
||||
<template #label>{{ i18n.ts._serverSettings.manifestJsonOverride }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
@@ -134,6 +106,7 @@ import JSON5 from 'json5';
|
||||
import { host } from '@@/js/config.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { instance, fetchInstance } from '@/instance.js';
|
||||
@@ -142,22 +115,38 @@ import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkColorInput from '@/components/MkColorInput.vue';
|
||||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
const iconUrl = ref<string | null>(null);
|
||||
const app192IconUrl = ref<string | null>(null);
|
||||
const app512IconUrl = ref<string | null>(null);
|
||||
const bannerUrl = ref<string | null>(null);
|
||||
const backgroundImageUrl = ref<string | null>(null);
|
||||
const themeColor = ref<string | null>(null);
|
||||
const defaultLightTheme = ref<string | null>(null);
|
||||
const defaultDarkTheme = ref<string | null>(null);
|
||||
const serverErrorImageUrl = ref<string | null>(null);
|
||||
const infoImageUrl = ref<string | null>(null);
|
||||
const notFoundImageUrl = ref<string | null>(null);
|
||||
const repositoryUrl = ref<string | null>(null);
|
||||
const feedbackUrl = ref<string | null>(null);
|
||||
const manifestJsonOverride = ref<string>('{}');
|
||||
|
||||
const iconUrl = ref(meta.iconUrl);
|
||||
const app192IconUrl = ref(meta.app192IconUrl);
|
||||
const app512IconUrl = ref(meta.app512IconUrl);
|
||||
const bannerUrl = ref(meta.bannerUrl);
|
||||
const backgroundImageUrl = ref(meta.backgroundImageUrl);
|
||||
const themeColor = ref(meta.themeColor);
|
||||
const defaultLightTheme = ref(meta.defaultLightTheme);
|
||||
const defaultDarkTheme = ref(meta.defaultDarkTheme);
|
||||
const serverErrorImageUrl = ref(meta.serverErrorImageUrl);
|
||||
const infoImageUrl = ref(meta.infoImageUrl);
|
||||
const notFoundImageUrl = ref(meta.notFoundImageUrl);
|
||||
const repositoryUrl = ref(meta.repositoryUrl);
|
||||
const feedbackUrl = ref(meta.feedbackUrl);
|
||||
const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t'));
|
||||
async function init() {
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
iconUrl.value = meta.iconUrl;
|
||||
app192IconUrl.value = meta.app192IconUrl;
|
||||
app512IconUrl.value = meta.app512IconUrl;
|
||||
bannerUrl.value = meta.bannerUrl;
|
||||
backgroundImageUrl.value = meta.backgroundImageUrl;
|
||||
themeColor.value = meta.themeColor;
|
||||
defaultLightTheme.value = meta.defaultLightTheme;
|
||||
defaultDarkTheme.value = meta.defaultDarkTheme;
|
||||
serverErrorImageUrl.value = meta.serverErrorImageUrl;
|
||||
infoImageUrl.value = meta.infoImageUrl;
|
||||
notFoundImageUrl.value = meta.notFoundImageUrl;
|
||||
repositoryUrl.value = meta.repositoryUrl;
|
||||
feedbackUrl.value = meta.feedbackUrl;
|
||||
manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
|
||||
@@ -6,67 +6,48 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/email-settings" :label="i18n.ts.emailServer" :keywords="['email']" icon="ti ti-mail">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="enableEmail">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableEmail }}</SearchLabel> ({{ i18n.ts.recommended }})</template>
|
||||
<template #caption><SearchText>{{ i18n.ts.emailConfigInfo }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="enableEmail">
|
||||
<template #label>{{ i18n.ts.enableEmail }} ({{ i18n.ts.recommended }})</template>
|
||||
<template #caption>{{ i18n.ts.emailConfigInfo }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="enableEmail">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="email" type="email">
|
||||
<template #label><SearchLabel>{{ i18n.ts.emailAddress }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="email" type="email">
|
||||
<template #label>{{ i18n.ts.emailAddress }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpConfig }}</SearchLabel></template>
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.smtpConfig }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<FormSplit :minWidth="280">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="smtpHost">
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpHost }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<SearchMarker>
|
||||
<MkInput v-model="smtpPort" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpPort }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</FormSplit>
|
||||
|
||||
<FormSplit :minWidth="280">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="smtpUser">
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpUser }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<SearchMarker>
|
||||
<MkInput v-model="smtpPass" type="password">
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpPass }}</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</FormSplit>
|
||||
|
||||
<FormInfo>{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="smtpSecure">
|
||||
<template #label><SearchLabel>{{ i18n.ts.smtpSecure }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.smtpSecureInfo }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<FormSplit :minWidth="280">
|
||||
<MkInput v-model="smtpHost">
|
||||
<template #label>{{ i18n.ts.smtpHost }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="smtpPort" type="number">
|
||||
<template #label>{{ i18n.ts.smtpPort }}</template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
<FormSplit :minWidth="280">
|
||||
<MkInput v-model="smtpUser">
|
||||
<template #label>{{ i18n.ts.smtpUser }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="smtpPass" type="password">
|
||||
<template #label>{{ i18n.ts.smtpPass }}</template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
<FormInfo>{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
|
||||
<MkSwitch v-model="smtpSecure">
|
||||
<template #label>{{ i18n.ts.smtpSecure }}</template>
|
||||
<template #caption>{{ i18n.ts.smtpSecureInfo }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</template>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
@@ -86,6 +67,7 @@ import { ref, computed } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -95,15 +77,24 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
const enableEmail = ref<boolean>(false);
|
||||
const email = ref<string | null>(null);
|
||||
const smtpSecure = ref<boolean>(false);
|
||||
const smtpHost = ref<string>('');
|
||||
const smtpPort = ref<number>(0);
|
||||
const smtpUser = ref<string>('');
|
||||
const smtpPass = ref<string>('');
|
||||
|
||||
const enableEmail = ref(meta.enableEmail);
|
||||
const email = ref(meta.email);
|
||||
const smtpSecure = ref(meta.smtpSecure);
|
||||
const smtpHost = ref(meta.smtpHost);
|
||||
const smtpPort = ref(meta.smtpPort);
|
||||
const smtpUser = ref(meta.smtpUser);
|
||||
const smtpPass = ref(meta.smtpPass);
|
||||
async function init() {
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
enableEmail.value = meta.enableEmail;
|
||||
email.value = meta.email;
|
||||
smtpSecure.value = meta.smtpSecure;
|
||||
smtpHost.value = meta.smtpHost;
|
||||
smtpPort.value = meta.smtpPort;
|
||||
smtpUser.value = meta.smtpUser;
|
||||
smtpPass.value = meta.smtpPass;
|
||||
}
|
||||
|
||||
async function testEmail() {
|
||||
const { canceled, result: destination } = await os.inputText({
|
||||
|
||||
@@ -6,49 +6,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/external-services" :label="i18n.ts.externalServices" :keywords="['external', 'services', 'thirdparty']" icon="ti ti-link">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker v-slot="slotProps">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #label><SearchLabel>Google Analytics</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<MkFolder>
|
||||
<template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="googleAnalyticsMeasurementId">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>Measurement ID</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="googleAnalyticsMeasurementId">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Measurement ID</template>
|
||||
</MkInput>
|
||||
<MkButton primary @click="save_googleAnalytics">Save</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkButton primary @click="save_googleAnalytics">Save</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<MkFolder>
|
||||
<template #label>DeepL Translation</template>
|
||||
|
||||
<SearchMarker v-slot="slotProps">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #label><SearchLabel>DeepL Translation</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="deeplAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>Auth Key</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="deeplIsPro">
|
||||
<template #label><SearchLabel>Pro account</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkButton primary @click="save_deepl">Save</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="deeplAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>DeepL Auth Key</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="deeplIsPro">
|
||||
<template #label>Pro account</template>
|
||||
</MkSwitch>
|
||||
<MkButton primary @click="save_deepl">Save</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
@@ -58,6 +45,7 @@ import { ref, computed } from 'vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { fetchInstance } from '@/instance.js';
|
||||
@@ -65,11 +53,17 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
const deeplAuthKey = ref<string>('');
|
||||
const deeplIsPro = ref<boolean>(false);
|
||||
|
||||
const deeplAuthKey = ref(meta.deeplAuthKey ?? '');
|
||||
const deeplIsPro = ref(meta.deeplIsPro);
|
||||
const googleAnalyticsMeasurementId = ref(meta.googleAnalyticsMeasurementId ?? '');
|
||||
const googleAnalyticsMeasurementId = ref<string>('');
|
||||
|
||||
async function init() {
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
deeplAuthKey.value = meta.deeplAuthKey ?? '';
|
||||
deeplIsPro.value = meta.deeplIsPro;
|
||||
googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? '';
|
||||
}
|
||||
|
||||
function save_deepl() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkInfo v-if="noEmailServer" warn>{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
|
||||
</div>
|
||||
|
||||
<MkSuperMenu :def="menuDef" :searchIndex="searchIndex" :grid="narrow"></MkSuperMenu>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,9 +44,6 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
|
||||
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import { genSearchIndexes } from '@/utility/inapp-search.js';
|
||||
|
||||
const searchIndex = await import('search-index:admin').then(({ searchIndexes }) => genSearchIndexes(searchIndexes));
|
||||
|
||||
const isEmpty = (x: string | null) => x == null || x === '';
|
||||
|
||||
@@ -327,6 +324,12 @@ const headerActions = computed(() => []);
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => INFO.value);
|
||||
|
||||
defineExpose({
|
||||
header: {
|
||||
title: i18n.ts.controlPanel,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -6,162 +6,140 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/moderation" :label="i18n.ts.moderation" :keywords="['moderation']" icon="ti ti-shield" :inlining="['serverRules']">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['open', 'registration']">
|
||||
<MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.openRegistration }}</SearchLabel></template>
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</SearchText></div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.openRegistrationWarning }}</SearchText></div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
|
||||
<template #label>{{ i18n.ts._serverSettings.openRegistration }}</template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.openRegistrationWarning }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker :keywords="['email', 'required', 'signup']">
|
||||
<MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
|
||||
<template #label><SearchLabel>{{ i18n.ts.emailRequiredForSignup }}</SearchLabel> ({{ i18n.ts.recommended }})</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
|
||||
<template #label>{{ i18n.ts.emailRequiredForSignup }} ({{ i18n.ts.recommended }})</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker :keywords="['ugc', 'content', 'visibility', 'visitor', 'guest']">
|
||||
<MkSelect v-model="ugcVisibilityForVisitor" @update:modelValue="onChange_ugcVisibilityForVisitor">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</SearchLabel></template>
|
||||
<option value="all">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all }}</option>
|
||||
<option value="local">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly }} ({{ i18n.ts.recommended }})</option>
|
||||
<option value="none">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none }}</option>
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</SearchText></div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</SearchText></div>
|
||||
</template>
|
||||
</MkSelect>
|
||||
</SearchMarker>
|
||||
<MkSelect v-model="ugcVisibilityForVisitor" @update:modelValue="onChange_ugcVisibilityForVisitor">
|
||||
<template #label>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</template>
|
||||
<option value="all">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all }}</option>
|
||||
<option value="local">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly }} ({{ i18n.ts.recommended }})</option>
|
||||
<option value="none">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none }}</option>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</div>
|
||||
</template>
|
||||
</MkSelect>
|
||||
|
||||
<XServerRules/>
|
||||
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
||||
|
||||
<SearchMarker :keywords="['preserved', 'usernames']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-lock-star"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.preservedUsernames }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-lock-star"></i></template>
|
||||
<template #label>{{ i18n.ts.preservedUsernames }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="preservedUsernames">
|
||||
<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="preservedUsernames">
|
||||
<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['sensitive', 'words']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-message-exclamation"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.sensitiveWords }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-exclamation"></i></template>
|
||||
<template #label>{{ i18n.ts.sensitiveWords }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="sensitiveWords">
|
||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="sensitiveWords">
|
||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['prohibited', 'words']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-message-x"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.prohibitedWords }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-x"></i></template>
|
||||
<template #label>{{ i18n.ts.prohibitedWords }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="prohibitedWords">
|
||||
<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="prohibitedWords">
|
||||
<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['prohibited', 'name', 'user']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-user-x"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.prohibitedWordsForNameOfUser }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-user-x"></i></template>
|
||||
<template #label>{{ i18n.ts.prohibitedWordsForNameOfUser }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="prohibitedWordsForNameOfUser">
|
||||
<template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="prohibitedWordsForNameOfUser">
|
||||
<template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['hidden', 'tags', 'hashtags']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.hiddenTags }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.hiddenTags }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="hiddenTags">
|
||||
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="hiddenTags">
|
||||
<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['silenced', 'servers', 'hosts']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.silencedInstances }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.silencedInstances }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="silencedHosts">
|
||||
<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="silencedHosts">
|
||||
<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['media', 'silenced', 'servers', 'hosts']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.mediaSilencedInstances }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mediaSilencedInstances }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="mediaSilencedHosts">
|
||||
<template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="mediaSilencedHosts">
|
||||
<template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['blocked', 'servers', 'hosts']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-ban"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.blockedInstances }}</SearchLabel></template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-ban"></i></template>
|
||||
<template #label>{{ i18n.ts.blockedInstances }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="blockedHosts">
|
||||
<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkTextarea v-model="blockedHosts">
|
||||
<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import XServerRules from './server-rules.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { fetchInstance } from '@/instance.js';
|
||||
@@ -172,19 +150,32 @@ import FormLink from '@/components/form/link.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
const enableRegistration = ref<boolean>(false);
|
||||
const emailRequiredForSignup = ref<boolean>(false);
|
||||
const ugcVisibilityForVisitor = ref<string>('all');
|
||||
const sensitiveWords = ref<string>('');
|
||||
const prohibitedWords = ref<string>('');
|
||||
const prohibitedWordsForNameOfUser = ref<string>('');
|
||||
const hiddenTags = ref<string>('');
|
||||
const preservedUsernames = ref<string>('');
|
||||
const blockedHosts = ref<string>('');
|
||||
const silencedHosts = ref<string>('');
|
||||
const mediaSilencedHosts = ref<string>('');
|
||||
|
||||
const enableRegistration = ref(!meta.disableRegistration);
|
||||
const emailRequiredForSignup = ref(meta.emailRequiredForSignup);
|
||||
const ugcVisibilityForVisitor = ref(meta.ugcVisibilityForVisitor);
|
||||
const sensitiveWords = ref(meta.sensitiveWords.join('\n'));
|
||||
const prohibitedWords = ref(meta.prohibitedWords.join('\n'));
|
||||
const prohibitedWordsForNameOfUser = ref(meta.prohibitedWordsForNameOfUser.join('\n'));
|
||||
const hiddenTags = ref(meta.hiddenTags.join('\n'));
|
||||
const preservedUsernames = ref(meta.preservedUsernames.join('\n'));
|
||||
const blockedHosts = ref(meta.blockedHosts.join('\n'));
|
||||
const silencedHosts = ref(meta.silencedHosts?.join('\n') ?? '');
|
||||
const mediaSilencedHosts = ref(meta.mediaSilencedHosts.join('\n'));
|
||||
async function init() {
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
enableRegistration.value = !meta.disableRegistration;
|
||||
emailRequiredForSignup.value = meta.emailRequiredForSignup;
|
||||
ugcVisibilityForVisitor.value = meta.ugcVisibilityForVisitor;
|
||||
sensitiveWords.value = meta.sensitiveWords.join('\n');
|
||||
prohibitedWords.value = meta.prohibitedWords.join('\n');
|
||||
prohibitedWordsForNameOfUser.value = meta.prohibitedWordsForNameOfUser.join('\n');
|
||||
hiddenTags.value = meta.hiddenTags.join('\n');
|
||||
preservedUsernames.value = meta.preservedUsernames.join('\n');
|
||||
blockedHosts.value = meta.blockedHosts.join('\n');
|
||||
silencedHosts.value = meta.silencedHosts?.join('\n') ?? '';
|
||||
mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
|
||||
}
|
||||
|
||||
async function onChange_enableRegistration(value: boolean) {
|
||||
if (value) {
|
||||
|
||||
@@ -6,94 +6,70 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/object-storage" :label="i18n.ts.objectStorage" :keywords="['objectStorage']" icon="ti ti-cloud">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="useObjectStorage"><SearchLabel>{{ i18n.ts.useObjectStorage }}</SearchLabel></MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch>
|
||||
|
||||
<template v-if="useObjectStorage">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageBaseUrl }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageBaseUrlDesc }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
|
||||
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageBucket">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageBucket }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageBucketDesc }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageBucket">
|
||||
<template #label>{{ i18n.ts.objectStorageBucket }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStoragePrefix">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStoragePrefix }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStoragePrefixDesc }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStoragePrefix">
|
||||
<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageEndpoint }}</SearchLabel></template>
|
||||
<template #prefix>https://</template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageEndpointDesc }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
|
||||
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
||||
<template #prefix>https://</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageRegion">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageRegion }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageRegionDesc }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageRegion">
|
||||
<template #label>{{ i18n.ts.objectStorageRegion }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<FormSplit :minWidth="280">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageAccessKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>Access key</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageAccessKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Access key</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="objectStorageSecretKey" type="password">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>Secret key</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="objectStorageSecretKey" type="password">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Secret key</template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="objectStorageUseSSL">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageUseSSL }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageUseSSLDesc }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="objectStorageUseSSL">
|
||||
<template #label>{{ i18n.ts.objectStorageUseSSL }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="objectStorageUseProxy">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageUseProxy }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.objectStorageUseProxyDesc }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="objectStorageUseProxy">
|
||||
<template #label>{{ i18n.ts.objectStorageUseProxy }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="objectStorageSetPublicRead">
|
||||
<template #label><SearchLabel>{{ i18n.ts.objectStorageSetPublicRead }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="objectStorageSetPublicRead">
|
||||
<template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="objectStorageS3ForcePathStyle">
|
||||
<template #label><SearchLabel>s3ForcePathStyle</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.s3ForcePathStyleDesc }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="objectStorageS3ForcePathStyle">
|
||||
<template #label>s3ForcePathStyle</template>
|
||||
<template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
@@ -118,21 +94,36 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
const useObjectStorage = ref<boolean>(false);
|
||||
const objectStorageBaseUrl = ref<string | null>(null);
|
||||
const objectStorageBucket = ref<string | null>(null);
|
||||
const objectStoragePrefix = ref<string | null>(null);
|
||||
const objectStorageEndpoint = ref<string | null>(null);
|
||||
const objectStorageRegion = ref<string | null>(null);
|
||||
const objectStoragePort = ref<number | null>(null);
|
||||
const objectStorageAccessKey = ref<string | null>(null);
|
||||
const objectStorageSecretKey = ref<string | null>(null);
|
||||
const objectStorageUseSSL = ref<boolean>(false);
|
||||
const objectStorageUseProxy = ref<boolean>(false);
|
||||
const objectStorageSetPublicRead = ref<boolean>(false);
|
||||
const objectStorageS3ForcePathStyle = ref<boolean>(true);
|
||||
|
||||
const useObjectStorage = ref(meta.useObjectStorage);
|
||||
const objectStorageBaseUrl = ref(meta.objectStorageBaseUrl);
|
||||
const objectStorageBucket = ref(meta.objectStorageBucket);
|
||||
const objectStoragePrefix = ref(meta.objectStoragePrefix);
|
||||
const objectStorageEndpoint = ref(meta.objectStorageEndpoint);
|
||||
const objectStorageRegion = ref(meta.objectStorageRegion);
|
||||
const objectStoragePort = ref(meta.objectStoragePort);
|
||||
const objectStorageAccessKey = ref(meta.objectStorageAccessKey);
|
||||
const objectStorageSecretKey = ref(meta.objectStorageSecretKey);
|
||||
const objectStorageUseSSL = ref(meta.objectStorageUseSSL);
|
||||
const objectStorageUseProxy = ref(meta.objectStorageUseProxy);
|
||||
const objectStorageSetPublicRead = ref(meta.objectStorageSetPublicRead);
|
||||
const objectStorageS3ForcePathStyle = ref(meta.objectStorageS3ForcePathStyle);
|
||||
async function init() {
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
useObjectStorage.value = meta.useObjectStorage;
|
||||
objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
|
||||
objectStorageBucket.value = meta.objectStorageBucket;
|
||||
objectStoragePrefix.value = meta.objectStoragePrefix;
|
||||
objectStorageEndpoint.value = meta.objectStorageEndpoint;
|
||||
objectStorageRegion.value = meta.objectStorageRegion;
|
||||
objectStoragePort.value = meta.objectStoragePort;
|
||||
objectStorageAccessKey.value = meta.objectStorageAccessKey;
|
||||
objectStorageSecretKey.value = meta.objectStorageSecretKey;
|
||||
objectStorageUseSSL.value = meta.objectStorageUseSSL;
|
||||
objectStorageUseProxy.value = meta.objectStorageUseProxy;
|
||||
objectStorageSetPublicRead.value = meta.objectStorageSetPublicRead;
|
||||
objectStorageS3ForcePathStyle.value = meta.objectStorageS3ForcePathStyle;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
|
||||
@@ -6,163 +6,131 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/performance" :label="i18n.ts.performance" :keywords="['performance']" icon="ti ti-bolt">
|
||||
<div class="_gaps">
|
||||
<SearchMarker>
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableServerMachineStats }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableIdenticonGeneration }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableChartsForRemoteUser }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableStatsForFederatedInstances }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableChartsForFederatedInstances }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>Misskey® Fan-out Timeline Technology™ (FTT)</SearchLabel></template>
|
||||
<template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="fttForm.modified.value" #footer>
|
||||
<MkFormFooter :form="fttForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="fttForm.state.enableFanoutTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</SearchText></div>
|
||||
<div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<template v-if="fttForm.state.enableFanoutTimeline">
|
||||
<SearchMarker :keywords="['db', 'database', 'fallback']">
|
||||
<MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</SearchLabel><span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number">
|
||||
<template #label><SearchLabel>perLocalUserUserTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number">
|
||||
<template #label><SearchLabel>perRemoteUserUserTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number">
|
||||
<template #label><SearchLabel>perUserHomeTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number">
|
||||
<template #label><SearchLabel>perUserListTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>Misskey® Reactions Boost Technology™ (RBT)</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="rbtForm.modified.value" #footer>
|
||||
<MkFormFooter :form="rbtForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="rbtForm.state.enableReactionsBuffering">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><SearchIcon><i class="ti ti-recycle"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>Remote Notes Cleaning (仮)</SearchLabel></template>
|
||||
<template v-if="remoteNotesCleaningForm.savedState.enableRemoteNotesCleaning" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="remoteNotesCleaningForm.modified.value" #footer>
|
||||
<MkFormFooter :form="remoteNotesCleaningForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="remoteNotesCleaningForm.modifiedStates.enableRemoteNotesCleaning" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.remoteNotesCleaning_description }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningExpiryDaysForEachNotes" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.remoteNotesCleaningExpiryDaysForEachNotes }}</SearchLabel> ({{ i18n.ts.inDays }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningExpiryDaysForEachNotes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.day }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningMaxProcessingDurationInMinutes" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.remoteNotesCleaningMaxProcessingDuration }}</SearchLabel> ({{ i18n.ts.inMinutes }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningMaxProcessingDurationInMinutes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.minute }}</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats">
|
||||
<template #label>{{ i18n.ts.enableServerMachineStats }}</template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration">
|
||||
<template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser">
|
||||
<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
|
||||
<template #label>{{ i18n.ts.enableStatsForFederatedInstances }}</template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div class="_panel" style="padding: 16px;">
|
||||
<MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
|
||||
<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
|
||||
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-bolt"></i></template>
|
||||
<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
|
||||
<template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="fttForm.modified.value" #footer>
|
||||
<MkFormFooter :form="fttForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="fttForm.state.enableFanoutTimeline">
|
||||
<template #label>{{ i18n.ts.enable }}<span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</div>
|
||||
<div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="fttForm.state.enableFanoutTimeline">
|
||||
<MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback">
|
||||
<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}<span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number">
|
||||
<template #label>perLocalUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number">
|
||||
<template #label>perRemoteUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number">
|
||||
<template #label>perUserHomeTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number">
|
||||
<template #label>perUserListTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-bolt"></i></template>
|
||||
<template #label>Misskey® Reactions Boost Technology™ (RBT)<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="rbtForm.modified.value" #footer>
|
||||
<MkFormFooter :form="rbtForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="rbtForm.state.enableReactionsBuffering">
|
||||
<template #label>{{ i18n.ts.enable }}<span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-recycle"></i></template>
|
||||
<template #label>Remote Notes Cleaning (仮)</template>
|
||||
<template v-if="remoteNotesCleaningForm.savedState.enableRemoteNotesCleaning" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="remoteNotesCleaningForm.modified.value" #footer>
|
||||
<MkFormFooter :form="remoteNotesCleaningForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<template #label>{{ i18n.ts.enable }}<span v-if="remoteNotesCleaningForm.modifiedStates.enableRemoteNotesCleaning" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.remoteNotesCleaning_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningExpiryDaysForEachNotes" type="number">
|
||||
<template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningExpiryDaysForEachNotes }} ({{ i18n.ts.inDays }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningExpiryDaysForEachNotes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.day }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningMaxProcessingDurationInMinutes" type="number">
|
||||
<template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningMaxProcessingDuration }} ({{ i18n.ts.inMinutes }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningMaxProcessingDurationInMinutes" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #suffix>{{ i18n.ts._time.minute }}</template>
|
||||
</MkInput>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
@@ -275,7 +243,7 @@ const headerActions = computed(() => []);
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.performance,
|
||||
icon: 'ti ti-bolt',
|
||||
title: i18n.ts.other,
|
||||
icon: 'ti ti-adjustments',
|
||||
}));
|
||||
</script>
|
||||
|
||||
@@ -6,20 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<SearchMarker path="/admin/relays" :label="i18n.ts.relays" :keywords="['relays']" icon="ti ti-planet">
|
||||
<div class="_gaps">
|
||||
<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
|
||||
<div>{{ relay.inbox }}</div>
|
||||
<div style="margin: 8px 0;">
|
||||
<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i>
|
||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i>
|
||||
<i v-else class="ti ti-clock" :class="$style.icon"></i>
|
||||
<span>{{ i18n.ts._relayStatus[relay.status] }}</span>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
<div class="_gaps">
|
||||
<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
|
||||
<div>{{ relay.inbox }}</div>
|
||||
<div style="margin: 8px 0;">
|
||||
<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i>
|
||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i>
|
||||
<i v-else class="ti ti-clock" :class="$style.icon"></i>
|
||||
<span>{{ i18n.ts._relayStatus[relay.status] }}</span>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
@@ -6,153 +6,115 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['botProtection']">
|
||||
<div class="_gaps_m">
|
||||
<XBotProtection/>
|
||||
<div class="_gaps_m">
|
||||
<XBotProtection/>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['sensitive', 'media', 'detection']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.sensitiveMediaDetection }}</SearchLabel></template>
|
||||
<template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
|
||||
<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
|
||||
<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }}</template>
|
||||
<template v-if="sensitiveMediaDetectionForm.modified.value" #footer>
|
||||
<MkFormFooter :form="sensitiveMediaDetectionForm"/>
|
||||
</template>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||
<template #label>{{ i18n.ts.sensitiveMediaDetection }}</template>
|
||||
<template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
|
||||
<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
|
||||
<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }}</template>
|
||||
<template v-if="sensitiveMediaDetectionForm.modified.value" #footer>
|
||||
<MkFormFooter :form="sensitiveMediaDetectionForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div><SearchText>{{ i18n.ts._sensitiveMediaDetection.description }}</SearchText></div>
|
||||
<div class="_gaps_m">
|
||||
<span>{{ i18n.ts._sensitiveMediaDetection.description }}</span>
|
||||
|
||||
<MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
|
||||
<option value="none">{{ i18n.ts.none }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="local">{{ i18n.ts.localOnly }}</option>
|
||||
<option value="remote">{{ i18n.ts.remoteOnly }}</option>
|
||||
</MkRadios>
|
||||
<MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
|
||||
<option value="none">{{ i18n.ts.none }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="local">{{ i18n.ts.localOnly }}</option>
|
||||
<option value="remote">{{ i18n.ts.remoteOnly }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<SearchMarker :keywords="['sensitivity']">
|
||||
<MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
|
||||
<template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</SearchText></template>
|
||||
</MkRange>
|
||||
</SearchMarker>
|
||||
<MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
|
||||
<template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template>
|
||||
<template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template>
|
||||
</MkRange>
|
||||
|
||||
<SearchMarker :keywords="['video', 'analyze']">
|
||||
<MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos">
|
||||
<template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos">
|
||||
<template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<SearchMarker :keywords="['flag', 'automatically']">
|
||||
<MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically">
|
||||
<template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }}</SearchLabel> ({{ i18n.ts.notRecommended }})</template>
|
||||
<template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically">
|
||||
<template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template>
|
||||
<template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<!-- 現状 false positive が多すぎて実用に耐えない
|
||||
<!-- 現状 false positive が多すぎて実用に耐えない
|
||||
<MkSwitch v-model="disallowUploadWhenPredictedAsPorn">
|
||||
<template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template>
|
||||
</MkSwitch>
|
||||
-->
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['email', 'validation']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #label><SearchLabel>Active Email Validation</SearchLabel></template>
|
||||
<template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="emailValidationForm.modified.value" #footer>
|
||||
<MkFormFooter :form="emailValidationForm"/>
|
||||
</template>
|
||||
<MkFolder>
|
||||
<template #label>Active Email Validation</template>
|
||||
<template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="emailValidationForm.modified.value" #footer>
|
||||
<MkFormFooter :form="emailValidationForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div><SearchText>{{ i18n.ts.activeEmailValidationDescription }}</SearchText></div>
|
||||
<div class="_gaps_m">
|
||||
<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
|
||||
<template #label>Enable</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
|
||||
<template #label>Use Verifymail.io API</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="emailValidationForm.state.verifymailAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>Verifymail.io API Auth Key</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
|
||||
<template #label>Use TrueMail API</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="emailValidationForm.state.truemailInstance">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>TrueMail API Instance</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="emailValidationForm.state.truemailAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>TrueMail API Auth Key</template>
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
|
||||
<template #label><SearchLabel>Enable</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkFolder>
|
||||
<template #label>Banned Email Domains</template>
|
||||
<template v-if="bannedEmailDomainsForm.modified.value" #footer>
|
||||
<MkFormFooter :form="bannedEmailDomainsForm"/>
|
||||
</template>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
|
||||
<template #label><SearchLabel>Use Verifymail.io API</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
|
||||
<template #label>Banned Email Domains List</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="emailValidationForm.state.verifymailAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>Verifymail.io API Auth Key</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkFolder>
|
||||
<template #label>Log IP address</template>
|
||||
<template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="ipLoggingForm.modified.value" #footer>
|
||||
<MkFormFooter :form="ipLoggingForm"/>
|
||||
</template>
|
||||
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
|
||||
<template #label><SearchLabel>Use TrueMail API</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="emailValidationForm.state.truemailInstance">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>TrueMail API Instance</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="emailValidationForm.state.truemailAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label><SearchLabel>TrueMail API Auth Key</SearchLabel></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['banned', 'email', 'domains', 'blacklist']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #label><SearchLabel>Banned Email Domains</SearchLabel></template>
|
||||
<template v-if="bannedEmailDomainsForm.modified.value" #footer>
|
||||
<MkFormFooter :form="bannedEmailDomainsForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
|
||||
<template #label><SearchLabel>Banned Email Domains List</SearchLabel></template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['log', 'ipAddress']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #label><SearchLabel>Log IP address</SearchLabel></template>
|
||||
<template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
<template v-if="ipLoggingForm.modified.value" #footer>
|
||||
<MkFormFooter :form="ipLoggingForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
|
||||
<template #label><SearchLabel>Enable</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
|
||||
<template #label>Enable</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
@@ -4,14 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker markerId="serverRules" :keywords="['rules']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-checkbox"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.serverRules }}</SearchLabel></template>
|
||||
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<div class="_gaps_m">
|
||||
<div><SearchText>{{ i18n.ts._serverRules.description }}</SearchText></div>
|
||||
|
||||
<div>{{ i18n.ts._serverRules.description }}</div>
|
||||
<Sortable
|
||||
v-model="serverRules"
|
||||
class="_gaps_m"
|
||||
@@ -37,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -46,9 +42,9 @@ import { defineAsyncComponent, ref, computed } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import { fetchInstance, instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
@@ -64,6 +60,13 @@ const save = async () => {
|
||||
const remove = (index: number): void => {
|
||||
serverRules.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.serverRules,
|
||||
icon: 'ti ti-checkbox',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
@@ -6,369 +6,292 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<SearchMarker path="/admin/settings" :label="i18n.ts.general" :keywords="['general', 'settings']" icon="ti ti-settings">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker v-slot="slotProps" :keywords="['information', 'meta']">
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><SearchIcon><i class="ti ti-info-circle"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.info }}</SearchLabel></template>
|
||||
<template v-if="infoForm.modified.value" #footer>
|
||||
<MkFormFooter :form="infoForm"/>
|
||||
</template>
|
||||
<div class="_gaps_m">
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-info-circle"></i></template>
|
||||
<template #label>{{ i18n.ts.info }}</template>
|
||||
<template v-if="infoForm.modified.value" #footer>
|
||||
<MkFormFooter :form="infoForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker :keywords="['name']">
|
||||
<MkInput v-model="infoForm.state.name">
|
||||
<template #label><SearchLabel>{{ i18n.ts.instanceName }}</SearchLabel><span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkInput v-model="infoForm.state.name">
|
||||
<template #label>{{ i18n.ts.instanceName }}<span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['shortName']">
|
||||
<MkInput v-model="infoForm.state.shortName">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.shortName }}</SearchLabel> ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.shortNameDescription }}</SearchText></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.shortName">
|
||||
<template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['description']">
|
||||
<MkTextarea v-model="infoForm.state.description">
|
||||
<template #label><SearchLabel>{{ i18n.ts.instanceDescription }}</SearchLabel><span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
<MkTextarea v-model="infoForm.state.description">
|
||||
<template #label>{{ i18n.ts.instanceDescription }}<span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkTextarea>
|
||||
|
||||
<FormSplit :minWidth="300">
|
||||
<SearchMarker :keywords="['maintainer', 'name']">
|
||||
<MkInput v-model="infoForm.state.maintainerName">
|
||||
<template #label><SearchLabel>{{ i18n.ts.maintainerName }}</SearchLabel><span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<FormSplit :minWidth="300">
|
||||
<MkInput v-model="infoForm.state.maintainerName">
|
||||
<template #label>{{ i18n.ts.maintainerName }}<span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['maintainer', 'email', 'contact']">
|
||||
<MkInput v-model="infoForm.state.maintainerEmail" type="email">
|
||||
<template #label><SearchLabel>{{ i18n.ts.maintainerEmail }}</SearchLabel><span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-mail"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</FormSplit>
|
||||
<MkInput v-model="infoForm.state.maintainerEmail" type="email">
|
||||
<template #label>{{ i18n.ts.maintainerEmail }}<span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-mail"></i></template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
|
||||
<SearchMarker :keywords="['tos', 'termsOfService']">
|
||||
<MkInput v-model="infoForm.state.tosUrl" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts.tosUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.tosUrl" type="url">
|
||||
<template #label>{{ i18n.ts.tosUrl }}<span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['privacyPolicy']">
|
||||
<MkInput v-model="infoForm.state.privacyPolicyUrl" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts.privacyPolicyUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.privacyPolicyUrl" type="url">
|
||||
<template #label>{{ i18n.ts.privacyPolicyUrl }}<span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['inquiry', 'contact']">
|
||||
<MkInput v-model="infoForm.state.inquiryUrl" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.inquiryUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</SearchText></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.inquiryUrl" type="url">
|
||||
<template #label>{{ i18n.ts._serverSettings.inquiryUrl }}<span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<SearchMarker :keywords="['repository', 'url']">
|
||||
<MkInput v-model="infoForm.state.repositoryUrl" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts.repositoryUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.repositoryUrlDescription }}</SearchText></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.repositoryUrl" type="url">
|
||||
<template #label>{{ i18n.ts.repositoryUrl }}<span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
|
||||
{{ i18n.ts.repositoryUrlOrTarballRequired }}
|
||||
</MkInfo>
|
||||
<MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
|
||||
{{ i18n.ts.repositoryUrlOrTarballRequired }}
|
||||
</MkInfo>
|
||||
|
||||
<SearchMarker :keywords="['impressum', 'legalNotice']">
|
||||
<MkInput v-model="infoForm.state.impressumUrl" type="url">
|
||||
<template #label><SearchLabel>{{ i18n.ts.impressumUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.impressumDescription }}</SearchText></template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkInput v-model="infoForm.state.impressumUrl" type="url">
|
||||
<template #label>{{ i18n.ts.impressumUrl }}<span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.impressumDescription }}</template>
|
||||
<template #prefix><i class="ti ti-link"></i></template>
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-user-star"></i></template>
|
||||
<template #label>{{ i18n.ts.pinnedUsers }}</template>
|
||||
<template v-if="pinnedUsersForm.modified.value" #footer>
|
||||
<MkFormFooter :form="pinnedUsersForm"/>
|
||||
</template>
|
||||
|
||||
<MkTextarea v-model="pinnedUsersForm.state.pinnedUsers">
|
||||
<template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
|
||||
</MkTextarea>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-world-cog"></i></template>
|
||||
<template #label>ServiceWorker</template>
|
||||
<template v-if="serviceWorkerForm.modified.value" #footer>
|
||||
<MkFormFooter :form="serviceWorkerForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker">
|
||||
<template #label>{{ i18n.ts.enableServiceworker }}<span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="serviceWorkerForm.state.enableServiceWorker">
|
||||
<MkInput v-model="serviceWorkerForm.state.swPublicKey">
|
||||
<template #label>Public key<span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="serviceWorkerForm.state.swPrivateKey">
|
||||
<template #label>Private key<span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
</MkInput>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-ad"></i></template>
|
||||
<template #label>{{ i18n.ts._ad.adsSettings }}</template>
|
||||
<template v-if="adForm.modified.value" #footer>
|
||||
<MkFormFooter :form="adForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<div class="_gaps_s">
|
||||
<MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number">
|
||||
<template #label>{{ i18n.ts._ad.notesPerOneAd }}<span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
|
||||
</MkInput>
|
||||
<MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true">
|
||||
{{ i18n.ts._ad.adsTooClose }}
|
||||
</MkInfo>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-world-search"></i></template>
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.title }}</template>
|
||||
<template v-if="urlPreviewForm.modified.value" #footer>
|
||||
<MkFormFooter :form="urlPreviewForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.enable }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="urlPreviewForm.state.urlPreviewEnabled">
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.allowRedirect }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.timeout }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<div>
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text">
|
||||
<template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
|
||||
</MkInput>
|
||||
|
||||
<div :class="$style.subCaption">
|
||||
{{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }}
|
||||
<ul style="padding-left: 20px; margin: 4px 0">
|
||||
<li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['pinned', 'users']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-user-star"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.pinnedUsers }}</SearchLabel></template>
|
||||
<template v-if="pinnedUsersForm.modified.value" #footer>
|
||||
<MkFormFooter :form="pinnedUsersForm"/>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-planet"></i></template>
|
||||
<template #label>{{ i18n.ts.federation }}</template>
|
||||
<template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
|
||||
<template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
|
||||
<template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
|
||||
<template v-if="federationForm.modified.value" #footer>
|
||||
<MkFormFooter :form="federationForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkRadios v-model="federationForm.state.federation">
|
||||
<template #label>{{ i18n.ts.behavior }}<span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="specified">{{ i18n.ts.specifyHost }}</option>
|
||||
<option value="none">{{ i18n.ts.none }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
|
||||
<template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-list"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.deliverSuspendedSoftware }}</SearchLabel></template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton @click="federationForm.state.deliverSuspendedSoftware.push({software: '', versionRange: ''})"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<MkTextarea v-model="pinnedUsersForm.state.pinnedUsers">
|
||||
<template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.pinnedUsersDescription }}</SearchText></template>
|
||||
</MkTextarea>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['serviceWorker']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-world-cog"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>ServiceWorker</SearchLabel></template>
|
||||
<template v-if="serviceWorkerForm.modified.value" #footer>
|
||||
<MkFormFooter :form="serviceWorkerForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableServiceworker }}</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.serviceworkerInfo }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<template v-if="serviceWorkerForm.state.enableServiceWorker">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="serviceWorkerForm.state.swPublicKey">
|
||||
<template #label><SearchLabel>Public key</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker>
|
||||
<MkInput v-model="serviceWorkerForm.state.swPrivateKey">
|
||||
<template #label><SearchLabel>Private key</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['ads']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-ad"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._ad.adsSettings }}</SearchLabel></template>
|
||||
<template v-if="adForm.modified.value" #footer>
|
||||
<MkFormFooter :form="adForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker>
|
||||
<MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts._ad.notesPerOneAd }}</SearchLabel><span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true">
|
||||
{{ i18n.ts._ad.adsTooClose }}
|
||||
</MkInfo>
|
||||
<div :class="$style.metadataRoot" class="_gaps_s">
|
||||
<MkInfo>{{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }}</MkInfo>
|
||||
<div v-for="(element, index) in federationForm.state.deliverSuspendedSoftware" :key="index" v-panel :class="$style.fieldDragItem">
|
||||
<button class="_button" :class="$style.dragItemRemove" @click="federationForm.state.deliverSuspendedSoftware.splice(index, 1)"><i class="ti ti-x"></i></button>
|
||||
<div :class="$style.dragItemForm">
|
||||
<FormSplit :minWidth="200">
|
||||
<MkInput v-model="element.software" small :placeholder="i18n.ts.softwareName">
|
||||
</MkInput>
|
||||
<MkInput v-model="element.versionRange" small :placeholder="i18n.ts.version">
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['url', 'preview']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-world-search"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.title }}</SearchLabel></template>
|
||||
<template v-if="urlPreviewForm.modified.value" #footer>
|
||||
<MkFormFooter :form="urlPreviewForm"/>
|
||||
<MkSwitch v-model="federationForm.state.signToActivityPubGet">
|
||||
<template #label>{{ i18n.ts._serverSettings.signToActivityPubGet }}<span v-if="federationForm.modifiedStates.signToActivityPubGet" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.signToActivityPubGet_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.proxyRemoteFiles">
|
||||
<template #label>{{ i18n.ts._serverSettings.proxyRemoteFiles }}<span v-if="federationForm.modifiedStates.proxyRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.proxyRemoteFiles_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.allowExternalApRedirect">
|
||||
<template #label>{{ i18n.ts._serverSettings.allowExternalApRedirect }}<span v-if="federationForm.modifiedStates.allowExternalApRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._serverSettings.allowExternalApRedirect_description }}</div>
|
||||
<div>{{ i18n.ts.needToRestartServerToApply }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker>
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.enable }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="urlPreviewForm.state.urlPreviewEnabled">
|
||||
<SearchMarker :keywords="['allow', 'redirect']">
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.allowRedirect }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<template v-if="federationForm.state.cacheRemoteFiles">
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteSensitiveFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['contentLength']">
|
||||
<MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-ghost"></i></template>
|
||||
<template #label>{{ i18n.ts.proxyAccount }}</template>
|
||||
<template v-if="proxyAccountForm.modified.value" #footer>
|
||||
<MkFormFooter :form="proxyAccountForm"/>
|
||||
</template>
|
||||
|
||||
<SearchMarker :keywords="['contentLength']">
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<div class="_gaps">
|
||||
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
||||
|
||||
<SearchMarker :keywords="['timeout']">
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.timeout }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
<MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
|
||||
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<SearchMarker :keywords="['userAgent']">
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.userAgent }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<div>
|
||||
<SearchMarker :keywords="['proxy']">
|
||||
<MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text">
|
||||
<template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
|
||||
</MkInput>
|
||||
</SearchMarker>
|
||||
|
||||
<div :class="$style.subCaption">
|
||||
{{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }}
|
||||
<ul style="padding-left: 20px; margin: 4px 0">
|
||||
<li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li>
|
||||
<li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['federation']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-planet"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.federation }}</SearchLabel></template>
|
||||
<template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
|
||||
<template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
|
||||
<template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
|
||||
<template v-if="federationForm.modified.value" #footer>
|
||||
<MkFormFooter :form="federationForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<SearchMarker>
|
||||
<MkRadios v-model="federationForm.state.federation">
|
||||
<template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel><span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="specified">{{ i18n.ts.specifyHost }}</option>
|
||||
<option value="none">{{ i18n.ts.none }}</option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['hosts']">
|
||||
<MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
|
||||
<template #label><SearchLabel>{{ i18n.ts.federationAllowedHosts }}</SearchLabel><span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['suspended', 'software']">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-list"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.deliverSuspendedSoftware }}</SearchLabel></template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton @click="federationForm.state.deliverSuspendedSoftware.push({software: '', versionRange: ''})"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :class="$style.metadataRoot" class="_gaps_s">
|
||||
<MkInfo>{{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }}</MkInfo>
|
||||
<div v-for="(element, index) in federationForm.state.deliverSuspendedSoftware" :key="index" v-panel :class="$style.fieldDragItem">
|
||||
<button class="_button" :class="$style.dragItemRemove" @click="federationForm.state.deliverSuspendedSoftware.splice(index, 1)"><i class="ti ti-x"></i></button>
|
||||
<div :class="$style.dragItemForm">
|
||||
<FormSplit :minWidth="200">
|
||||
<MkInput v-model="element.software" small :placeholder="i18n.ts.softwareName">
|
||||
</MkInput>
|
||||
<MkInput v-model="element.versionRange" small :placeholder="i18n.ts.version">
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sign', 'get']">
|
||||
<MkSwitch v-model="federationForm.state.signToActivityPubGet">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.signToActivityPubGet }}</SearchLabel><span v-if="federationForm.modifiedStates.signToActivityPubGet" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.signToActivityPubGet_description }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['proxy', 'remote', 'files']">
|
||||
<MkSwitch v-model="federationForm.state.proxyRemoteFiles">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.proxyRemoteFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.proxyRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._serverSettings.proxyRemoteFiles_description }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['allow', 'external', 'redirect']">
|
||||
<MkSwitch v-model="federationForm.state.allowExternalApRedirect">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.allowExternalApRedirect }}</SearchLabel><span v-if="federationForm.modifiedStates.allowExternalApRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._serverSettings.allowExternalApRedirect_description }}</SearchText></div>
|
||||
<div>{{ i18n.ts.needToRestartServerToApply }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['cache', 'remote', 'files']">
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteFiles">
|
||||
<template #label><SearchLabel>{{ i18n.ts.cacheRemoteFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.cacheRemoteFilesDescription }}</SearchText>{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<template v-if="federationForm.state.cacheRemoteFiles">
|
||||
<SearchMarker :keywords="['cache', 'remote', 'sensitive', 'files']">
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteSensitiveFiles">
|
||||
<template #label><SearchLabel>{{ i18n.ts.cacheRemoteSensitiveFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker v-slot="slotProps" :keywords="['proxy', 'account']">
|
||||
<MkFolder :defaultOpen="slotProps.isParentOfTarget">
|
||||
<template #icon><SearchIcon><i class="ti ti-ghost"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.proxyAccount }}</SearchLabel></template>
|
||||
<template v-if="proxyAccountForm.modified.value" #footer>
|
||||
<MkFormFooter :form="proxyAccountForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
||||
|
||||
<SearchMarker :keywords="['description']">
|
||||
<MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.description }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
|
||||
</MkTextarea>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<MkButton primary @click="openSetupWizard">
|
||||
Open setup wizard
|
||||
</MkButton>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
<MkButton primary @click="openSetupWizard">
|
||||
Open setup wizard
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
@@ -6,21 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 900px;">
|
||||
<SearchMarker path="/admin/system-webhook" label="SystemWebhook" :keywords="['webhook']" icon="ti ti-webhook">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker>
|
||||
<MkButton primary @click="onCreateWebhookClicked">
|
||||
<i class="ti ti-plus"></i> <SearchLabel>{{ i18n.ts._webhookSettings.createWebhook }}</SearchLabel>
|
||||
</MkButton>
|
||||
</SearchMarker>
|
||||
<div class="_gaps_m">
|
||||
<MkButton primary @click="onCreateWebhookClicked">
|
||||
<i class="ti ti-plus"></i> {{ i18n.ts._webhookSettings.createWebhook }}
|
||||
</MkButton>
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps">
|
||||
<XItem v-for="item in webhooks" :key="item.id" :entity="item" @edit="onEditButtonClicked" @delete="onDeleteButtonClicked"/>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
<FormSection>
|
||||
<div class="_gaps">
|
||||
<XItem v-for="item in webhooks" :key="item.id" :entity="item" @edit="onEditButtonClicked" @delete="onDeleteButtonClicked"/>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #icon><i class="ti ti-shield-lock"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.totp }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.totpDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.totpDescription }}</SearchKeyword></template>
|
||||
<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
|
||||
|
||||
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
||||
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
|
||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||
<template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.passwordLessLoginDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.passwordLessLoginDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/account-data" :label="i18n.ts._settings.accountData" :keywords="['import', 'export', 'data', 'archive']" icon="ti ti-package">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/package_3d.png" color="#ff9100">
|
||||
<SearchText>{{ i18n.ts._settings.accountDataBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.accountDataBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/connect" :label="i18n.ts._settings.serviceConnection" :keywords="['app', 'service', 'connect', 'webhook', 'api', 'token']" icon="ti ti-link">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/link_3d.png" color="#ff0088">
|
||||
<SearchText>{{ i18n.ts._settings.serviceConnectionBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.serviceConnectionBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<SearchMarker :keywords="['api', 'app', 'token', 'accessToken']">
|
||||
|
||||
@@ -7,8 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
|
||||
|
||||
<FormInfo v-if="isSafeMode" warn>{{ i18n.ts.customCssIsDisabledBecauseSafeMode }}</FormInfo>
|
||||
|
||||
<MkCodeEditor v-model="localCustomCss" manualSave lang="css">
|
||||
<template #label>CSS</template>
|
||||
</MkCodeEditor>
|
||||
@@ -19,7 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
@@ -97,8 +97,8 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import { selectFile } from '@/utility/drive.js';
|
||||
import { suggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const navWindow = prefer.model('deck.navWindow');
|
||||
const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');
|
||||
@@ -109,8 +109,8 @@ const menuPosition = prefer.model('deck.menuPosition');
|
||||
const navbarPosition = prefer.model('deck.navbarPosition');
|
||||
const wallpaper = prefer.model('deck.wallpaper');
|
||||
|
||||
watch(wallpaper, () => {
|
||||
suggestReload();
|
||||
watch(wallpaper, async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
function setWallpaper(ev: MouseEvent) {
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/drive" :label="i18n.ts.drive" :keywords="['drive']" icon="ti ti-cloud">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/cloud_3d.png" color="#0059ff">
|
||||
<SearchText>{{ i18n.ts._settings.driveBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.driveBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<SearchMarker :keywords="['capacity', 'usage']">
|
||||
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPreferenceContainer k="keepOriginalFilename">
|
||||
<MkSwitch v-model="keepOriginalFilename">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker :keywords="['auto', 'nsfw', 'sensitive', 'media', 'file']">
|
||||
<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAutoSensitive }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.enableAutoSensitiveDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div>
|
||||
<div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div>
|
||||
</MkInfo>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="searchIndex"></MkSuperMenu>
|
||||
<MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="SETTING_INDEX"></MkSuperMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
|
||||
@@ -42,12 +42,12 @@ import { instance } from '@/instance.js';
|
||||
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
|
||||
import * as os from '@/os.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import { searchIndexes } from '@/utility/settings-search-index.js';
|
||||
import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js';
|
||||
import { store } from '@/store.js';
|
||||
import { signout } from '@/signout.js';
|
||||
import { genSearchIndexes } from '@/utility/inapp-search.js';
|
||||
|
||||
const searchIndex = await import('search-index:settings').then(({ searchIndexes }) => genSearchIndexes(searchIndexes));
|
||||
const SETTING_INDEX = searchIndexes; // TODO: lazy load
|
||||
|
||||
const indexInfo = {
|
||||
title: i18n.ts.settings,
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/mute-block" :label="i18n.ts.muteAndBlock" icon="ti ti-ban" :keywords="['mute', 'block']">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/prohibited_3d.png" color="#ff2600">
|
||||
<SearchText>{{ i18n.ts._settings.muteAndBlockBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.muteAndBlockBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
@@ -189,10 +189,10 @@ import { ensureSignin } from '@/i.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import { Paginator } from '@/utility/paginator.js';
|
||||
import { suggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
@@ -216,8 +216,8 @@ const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord');
|
||||
|
||||
watch([
|
||||
showSoftWordMutedWord,
|
||||
], () => {
|
||||
suggestReload();
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
async function unrenoteMute(user, ev) {
|
||||
|
||||
@@ -64,6 +64,7 @@ import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import { store } from '@/store.js';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/notifications" :label="i18n.ts.notifications" :keywords="['notifications']" icon="ti ti-bell">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/bell_3d.png" color="#ffff00">
|
||||
<SearchText>{{ i18n.ts._settings.notificationsBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.notificationsBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<FormSection first>
|
||||
|
||||
@@ -75,7 +75,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
|
||||
<FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
|
||||
<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchText>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchText></MkButton>
|
||||
<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton>
|
||||
<MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
@@ -155,13 +155,13 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import { signout } from '@/signout.js';
|
||||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
||||
import { suggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
@@ -172,8 +172,8 @@ const devMode = prefer.model('devMode');
|
||||
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
||||
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
||||
|
||||
watch(skipNoteRender, () => {
|
||||
suggestReload();
|
||||
watch(skipNoteRender, async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
async function deleteAccount() {
|
||||
|
||||
@@ -7,12 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin', 'addon', 'extension']" icon="ti ti-plug">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/electric_plug_3d.png" color="#ffbb00">
|
||||
<SearchText>{{ i18n.ts._settings.pluginBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.pluginBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<MkInfo v-if="isSafeMode" warn>{{ i18n.ts.pluginsAreDisabledBecauseSafeMode }}</MkInfo>
|
||||
|
||||
<FormLink v-else to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
<FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.manage }}</template>
|
||||
@@ -105,12 +103,10 @@ import MkCode from '@/components/MkCode.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin, reloadPlugin } from '@/plugin.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const plugins = prefer.r.plugins;
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/preferences" :label="i18n.ts.preferences" :keywords="['general', 'preferences']" icon="ti ti-adjustments">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/gear_3d.png" color="#00ff9d">
|
||||
<SearchText>{{ i18n.ts._settings.preferencesBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.preferencesBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker :keywords="['realtimemode']">
|
||||
<MkSwitch v-model="realtimeMode">
|
||||
<template #label><i class="ti ti-bolt"></i> <SearchLabel>{{ i18n.ts.realtimeMode }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._settings.realtimeMode_description }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts._settings.realtimeMode_description }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPreferenceContainer k="pollingInterval">
|
||||
<MkRange v-model="pollingInterval" :min="1" :max="3" :step="1" easing :showTicks="true" :textConverter="(v) => v === 1 ? i18n.ts.low : v === 2 ? i18n.ts.middle : v === 3 ? i18n.ts.high : ''">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.contentsUpdateFrequency }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._settings.contentsUpdateFrequency_description }}</SearchText><br><SearchText>{{ i18n.ts._settings.contentsUpdateFrequency_description2 }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description }}</SearchKeyword><br><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description2 }}</SearchKeyword></template>
|
||||
<template #prefix><i class="ti ti-player-play"></i></template>
|
||||
<template #suffix><i class="ti ti-player-track-next"></i></template>
|
||||
</MkRange>
|
||||
@@ -165,7 +165,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPreferenceContainer k="collapseRenotes">
|
||||
<MkSwitch v-model="collapseRenotes">
|
||||
<template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.collapseRenotesDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
@@ -449,7 +449,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/mens_room_3d.png" color="#0011ff">
|
||||
<SearchText>{{ i18n.ts._settings.accessibilityBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.accessibilityBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
@@ -489,7 +489,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPreferenceContainer k="enablePullToRefresh">
|
||||
<MkSwitch v-model="enablePullToRefresh">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.enablePullToRefresh }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts._settings.enablePullToRefresh_description }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts._settings.enablePullToRefresh_description }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
@@ -567,50 +567,73 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #icon><SearchIcon><i class="ti ti-battery-vertical-eco"></i></SearchIcon></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['animation', 'motion', 'reduce']">
|
||||
<MkPreferenceContainer k="animation">
|
||||
<MkSwitch :modelValue="!reduceAnimation" @update:modelValue="v => reduceAnimation = !v">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.uiAnimations }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.turnOffToImprovePerformance }}</SearchText></template>
|
||||
<SearchMarker :keywords="['lowpowermode', 'battery', 'eco', 'save']">
|
||||
<MkPreferenceContainer k="lowPowerMode">
|
||||
<MkSwitch v-model="lowPowerMode">
|
||||
<template #label><SearchLabel>{{ i18n.ts.lowPowerMode }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkPreferenceContainer k="useBlurEffect">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.turnOffToImprovePerformance }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
<hr>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkPreferenceContainer k="useBlurEffectForModal">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.turnOffToImprovePerformance }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
<MkDisableSection :disabled="lowPowerMode">
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['animation', 'image', 'gif']">
|
||||
<MkPreferenceContainer k="disableShowingAnimatedImages">
|
||||
<MkSwitch :modelValue="!disableShowingAnimatedImages" @update:modelValue="v => disableShowingAnimatedImages = !v">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.playAnimatedImages }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blurhash', 'image', 'photo', 'picture', 'thumbnail', 'placeholder']">
|
||||
<MkPreferenceContainer k="enableHighQualityImagePlaceholders">
|
||||
<MkSwitch v-model="enableHighQualityImagePlaceholders">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.enableHighQualityImagePlaceholders }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.turnOffToImprovePerformance }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
<SearchMarker :keywords="['animation', 'motion', 'reduce']">
|
||||
<MkPreferenceContainer k="animation">
|
||||
<MkSwitch :modelValue="!reduceAnimation" @update:modelValue="v => reduceAnimation = !v">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.uiAnimations }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sticky']">
|
||||
<MkPreferenceContainer k="useStickyIcons">
|
||||
<MkSwitch v-model="useStickyIcons">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.turnOffToImprovePerformance }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkPreferenceContainer k="useBlurEffect">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkPreferenceContainer k="useBlurEffectForModal">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blurhash', 'image', 'photo', 'picture', 'thumbnail', 'placeholder']">
|
||||
<MkPreferenceContainer k="enableHighQualityImagePlaceholders">
|
||||
<MkSwitch v-model="enableHighQualityImagePlaceholders">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.enableHighQualityImagePlaceholders }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sticky']">
|
||||
<MkPreferenceContainer k="useStickyIcons">
|
||||
<MkSwitch v-model="useStickyIcons">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.turnOffToImprovePerformance }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkDisableSection>
|
||||
|
||||
<MkInfo>
|
||||
<div class="_gaps_s">
|
||||
@@ -795,6 +818,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||
import { store } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
@@ -806,7 +830,6 @@ import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { suggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
@@ -871,6 +894,7 @@ const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPla
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
const menuStyle = prefer.model('menuStyle');
|
||||
const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
|
||||
const lowPowerMode = prefer.model('lowPowerMode');
|
||||
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
@@ -928,8 +952,9 @@ watch([
|
||||
enablePullToRefresh,
|
||||
reduceAnimation,
|
||||
showAvailableReactionsFirstInNote,
|
||||
], () => {
|
||||
suggestReload();
|
||||
lowPowerMode,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||
|
||||
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/privacy" :label="i18n.ts.privacy" :keywords="['privacy']" icon="ti ti-lock-open">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/unlocked_3d.png" color="#aeff00">
|
||||
<SearchText>{{ i18n.ts._settings.privacyBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.privacyBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<SearchMarker :keywords="['follow', 'lock']">
|
||||
<MkSwitch v-model="isLocked" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeFollowManuallyApprove }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.lockedAccountInfo }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.lockedAccountInfo }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker :keywords="['reaction', 'public']">
|
||||
<MkSwitch v-model="publicReactions" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeReactionsPublic }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.makeReactionsPublicDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.makeReactionsPublicDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -53,28 +53,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker :keywords="['online', 'status']">
|
||||
<MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.hideOnlineStatus }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.hideOnlineStatusDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.hideOnlineStatusDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['crawle', 'index', 'search']">
|
||||
<MkSwitch v-model="noCrawle" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.noCrawle }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.noCrawleDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.noCrawleDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['crawle', 'ai']">
|
||||
<MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.preventAiLearning }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.preventAiLearningDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.preventAiLearningDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['explore']">
|
||||
<MkSwitch v-model="isExplorable" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.makeExplorable }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.makeExplorableDescription }}</SearchText></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.makeExplorableDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</SearchText></div>
|
||||
<div><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</SearchKeyword></div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
</SearchMarker>
|
||||
@@ -183,7 +183,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</SearchText></div>
|
||||
<div><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</SearchKeyword></div>
|
||||
</template>
|
||||
</FormSlot>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
|
||||
<template #label><SearchLabel>{{ i18n.ts._profile.followedMessage }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
|
||||
<template #caption>
|
||||
<div><SearchText>{{ i18n.ts._profile.followedMessageDescription }}</SearchText></div>
|
||||
<div><SearchKeyword>{{ i18n.ts._profile.followedMessageDescription }}</SearchKeyword></div>
|
||||
<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
|
||||
</template>
|
||||
</MkInput>
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['2fa']">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/locked_with_key_3d.png" color="#ffbf00">
|
||||
<SearchText>{{ i18n.ts._settings.securityBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.securityBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<SearchMarker :keywords="['password']">
|
||||
@@ -24,34 +24,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<X2fa/>
|
||||
|
||||
<SearchMarker :keywords="['signin', 'login', 'history', 'log']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts.signinHistory }}</SearchLabel></template>
|
||||
<MkPagination :paginator="paginator" withControl>
|
||||
<template #default="{items}">
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||
<header>
|
||||
<i v-if="item.success" class="ti ti-check icon succ"></i>
|
||||
<i v-else class="ti ti-circle-x icon fail"></i>
|
||||
<code class="ip _monospace">{{ item.ip }}</code>
|
||||
<MkTime :time="item.createdAt" class="time"/>
|
||||
</header>
|
||||
</div>
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.signinHistory }}</template>
|
||||
<MkPagination :paginator="paginator" withControl>
|
||||
<template #default="{items}">
|
||||
<div>
|
||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||
<header>
|
||||
<i v-if="item.success" class="ti ti-check icon succ"></i>
|
||||
<i v-else class="ti ti-circle-x icon fail"></i>
|
||||
<code class="ip _monospace">{{ item.ip }}</code>
|
||||
<MkTime :time="item.createdAt" class="time"/>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</FormSection>
|
||||
|
||||
<SearchMarker :keywords="['regenerate', 'refresh', 'reset', 'token']">
|
||||
<FormSection>
|
||||
<FormSlot>
|
||||
<MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> <SearchLabel>{{ i18n.ts.regenerateLoginToken }}</SearchLabel></MkButton>
|
||||
<template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
|
||||
</FormSlot>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
<FormSection>
|
||||
<FormSlot>
|
||||
<MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> {{ i18n.ts.regenerateLoginToken }}</MkButton>
|
||||
<template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
|
||||
</FormSlot>
|
||||
</FormSection>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<SearchMarker path="/settings/sounds" :label="i18n.ts.sounds" :keywords="['sounds']" icon="ti ti-music">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/speaker_high_volume_3d.png" color="#ff006f">
|
||||
<SearchText>{{ i18n.ts._settings.soundsBanner }}</SearchText>
|
||||
<SearchKeyword>{{ i18n.ts._settings.soundsBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<SearchMarker :keywords="['mute']">
|
||||
|
||||
@@ -35,9 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkInfo v-if="isSafeMode" warn>{{ i18n.ts.themeIsDefaultBecauseSafeMode }}</MkInfo>
|
||||
|
||||
<div v-else class="_gaps">
|
||||
<div class="_gaps">
|
||||
<template v-if="!store.r.darkMode.value">
|
||||
<SearchMarker :keywords="['light', 'theme']">
|
||||
<MkFolder :defaultOpen="true" :max-height="500">
|
||||
@@ -206,14 +204,12 @@ import JSON5 from 'json5';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkThemePreview from '@/components/MkThemePreview.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { getBuiltinThemesRef, getThemesRef, removeTheme } from '@/theme.js';
|
||||
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { ref, defineAsyncComponent } from 'vue';
|
||||
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
|
||||
@@ -233,7 +232,6 @@ export function launchPlugins() {
|
||||
}
|
||||
|
||||
async function launchPlugin(id: Plugin['installId']): Promise<void> {
|
||||
if (isSafeMode) return;
|
||||
const plugin = prefer.s.plugins.find(x => x.installId === id);
|
||||
if (!plugin) return;
|
||||
|
||||
|
||||
@@ -35,464 +35,472 @@ export type SoundStore = {
|
||||
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
||||
|
||||
export const PREF_DEF = definePreferences({
|
||||
accounts: {
|
||||
default: [] as [host: string, user: {
|
||||
id: string;
|
||||
username: string;
|
||||
}][],
|
||||
},
|
||||
|
||||
pinnedUserLists: {
|
||||
accountDependent: true,
|
||||
default: [] as Misskey.entities.UserList[],
|
||||
},
|
||||
uploadFolder: {
|
||||
accountDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
widgets: {
|
||||
accountDependent: true,
|
||||
default: () => [{
|
||||
name: 'calendar',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}] as {
|
||||
name: string;
|
||||
id: string;
|
||||
place: string | null;
|
||||
data: Record<string, any>;
|
||||
}[],
|
||||
},
|
||||
'deck.profile': {
|
||||
accountDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
'deck.profiles': {
|
||||
accountDependent: true,
|
||||
default: [] as DeckProfile[],
|
||||
},
|
||||
|
||||
emojiPalettes: {
|
||||
serverDependent: true,
|
||||
default: () => [{
|
||||
id: genId(),
|
||||
name: '',
|
||||
emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
}] as {
|
||||
id: string;
|
||||
name: string;
|
||||
emojis: string[];
|
||||
}[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
}
|
||||
return mergedItems;
|
||||
states: {
|
||||
accounts: {
|
||||
default: [] as [host: string, user: {
|
||||
id: string;
|
||||
username: string;
|
||||
}][],
|
||||
},
|
||||
},
|
||||
emojiPaletteForReaction: {
|
||||
serverDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
emojiPaletteForMain: {
|
||||
serverDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
|
||||
overridedDeviceKind: {
|
||||
default: null as DeviceKind | null,
|
||||
},
|
||||
themes: {
|
||||
default: [] as Theme[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
}
|
||||
return mergedItems;
|
||||
pinnedUserLists: {
|
||||
accountDependent: true,
|
||||
default: [] as Misskey.entities.UserList[],
|
||||
},
|
||||
},
|
||||
lightTheme: {
|
||||
default: null as Theme | null,
|
||||
},
|
||||
darkTheme: {
|
||||
default: null as Theme | null,
|
||||
},
|
||||
syncDeviceDarkMode: {
|
||||
default: true,
|
||||
},
|
||||
defaultNoteVisibility: {
|
||||
default: 'public' as (typeof Misskey.noteVisibilities)[number],
|
||||
},
|
||||
defaultNoteLocalOnly: {
|
||||
default: false,
|
||||
},
|
||||
keepCw: {
|
||||
default: true,
|
||||
},
|
||||
rememberNoteVisibility: {
|
||||
default: false,
|
||||
},
|
||||
reportError: {
|
||||
default: false,
|
||||
},
|
||||
collapseRenotes: {
|
||||
default: true,
|
||||
},
|
||||
menu: {
|
||||
default: [
|
||||
'notifications',
|
||||
'clips',
|
||||
'drive',
|
||||
'followRequests',
|
||||
'chat',
|
||||
'-',
|
||||
'explore',
|
||||
'announcements',
|
||||
'channels',
|
||||
'search',
|
||||
'-',
|
||||
'ui',
|
||||
],
|
||||
},
|
||||
statusbars: {
|
||||
default: [] as {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
||||
black: boolean;
|
||||
props: Record<string, any>;
|
||||
}[],
|
||||
},
|
||||
serverDisconnectedBehavior: {
|
||||
default: 'quiet' as 'quiet' | 'reload' | 'dialog',
|
||||
},
|
||||
nsfw: {
|
||||
default: 'respect' as 'respect' | 'force' | 'ignore',
|
||||
},
|
||||
highlightSensitiveMedia: {
|
||||
default: false,
|
||||
},
|
||||
animation: {
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
animatedMfm: {
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
advancedMfm: {
|
||||
default: true,
|
||||
},
|
||||
showReactionsCount: {
|
||||
default: false,
|
||||
},
|
||||
enableQuickAddMfmFunction: {
|
||||
default: false,
|
||||
},
|
||||
loadRawImages: {
|
||||
default: false,
|
||||
},
|
||||
imageNewTab: {
|
||||
default: false,
|
||||
},
|
||||
disableShowingAnimatedImages: {
|
||||
default: window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
emojiStyle: {
|
||||
default: 'twemoji', // twemoji / fluentEmoji / native
|
||||
},
|
||||
menuStyle: {
|
||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
||||
},
|
||||
useBlurEffectForModal: {
|
||||
default: true,
|
||||
},
|
||||
useBlurEffect: {
|
||||
default: true,
|
||||
},
|
||||
useStickyIcons: {
|
||||
default: true,
|
||||
},
|
||||
enableHighQualityImagePlaceholders: {
|
||||
default: true,
|
||||
},
|
||||
showFixedPostForm: {
|
||||
default: false,
|
||||
},
|
||||
showFixedPostFormInChannel: {
|
||||
default: false,
|
||||
},
|
||||
enableInfiniteScroll: {
|
||||
default: true,
|
||||
},
|
||||
useReactionPickerForContextMenu: {
|
||||
default: false,
|
||||
},
|
||||
instanceTicker: {
|
||||
default: 'remote' as 'none' | 'remote' | 'always',
|
||||
},
|
||||
emojiPickerScale: {
|
||||
default: 2,
|
||||
},
|
||||
emojiPickerWidth: {
|
||||
default: 2,
|
||||
},
|
||||
emojiPickerHeight: {
|
||||
default: 3,
|
||||
},
|
||||
emojiPickerStyle: {
|
||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
||||
},
|
||||
squareAvatars: {
|
||||
default: false,
|
||||
},
|
||||
showAvatarDecorations: {
|
||||
default: true,
|
||||
},
|
||||
numberOfPageCache: {
|
||||
default: 3,
|
||||
},
|
||||
pollingInterval: {
|
||||
uploadFolder: {
|
||||
accountDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
widgets: {
|
||||
accountDependent: true,
|
||||
default: () => [{
|
||||
name: 'calendar',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: genId(), place: 'right', data: {},
|
||||
}] as {
|
||||
name: string;
|
||||
id: string;
|
||||
place: string | null;
|
||||
data: Record<string, any>;
|
||||
}[],
|
||||
},
|
||||
'deck.profile': {
|
||||
accountDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
'deck.profiles': {
|
||||
accountDependent: true,
|
||||
default: [] as DeckProfile[],
|
||||
},
|
||||
|
||||
emojiPalettes: {
|
||||
serverDependent: true,
|
||||
default: () => [{
|
||||
id: genId(),
|
||||
name: '',
|
||||
emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
}] as {
|
||||
id: string;
|
||||
name: string;
|
||||
emojis: string[];
|
||||
}[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
}
|
||||
return mergedItems;
|
||||
},
|
||||
},
|
||||
emojiPaletteForReaction: {
|
||||
serverDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
emojiPaletteForMain: {
|
||||
serverDependent: true,
|
||||
default: null as string | null,
|
||||
},
|
||||
|
||||
overridedDeviceKind: {
|
||||
default: null as DeviceKind | null,
|
||||
},
|
||||
themes: {
|
||||
default: [] as Theme[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
}
|
||||
return mergedItems;
|
||||
},
|
||||
},
|
||||
lightTheme: {
|
||||
default: null as Theme | null,
|
||||
},
|
||||
darkTheme: {
|
||||
default: null as Theme | null,
|
||||
},
|
||||
syncDeviceDarkMode: {
|
||||
default: true,
|
||||
},
|
||||
defaultNoteVisibility: {
|
||||
default: 'public' as (typeof Misskey.noteVisibilities)[number],
|
||||
},
|
||||
defaultNoteLocalOnly: {
|
||||
default: false,
|
||||
},
|
||||
keepCw: {
|
||||
default: true,
|
||||
},
|
||||
rememberNoteVisibility: {
|
||||
default: false,
|
||||
},
|
||||
reportError: {
|
||||
default: false,
|
||||
},
|
||||
collapseRenotes: {
|
||||
default: true,
|
||||
},
|
||||
menu: {
|
||||
default: [
|
||||
'notifications',
|
||||
'clips',
|
||||
'drive',
|
||||
'followRequests',
|
||||
'chat',
|
||||
'-',
|
||||
'explore',
|
||||
'announcements',
|
||||
'channels',
|
||||
'search',
|
||||
'-',
|
||||
'ui',
|
||||
],
|
||||
},
|
||||
statusbars: {
|
||||
default: [] as {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
||||
black: boolean;
|
||||
props: Record<string, any>;
|
||||
}[],
|
||||
},
|
||||
serverDisconnectedBehavior: {
|
||||
default: 'quiet' as 'quiet' | 'reload' | 'dialog',
|
||||
},
|
||||
nsfw: {
|
||||
default: 'respect' as 'respect' | 'force' | 'ignore',
|
||||
},
|
||||
highlightSensitiveMedia: {
|
||||
default: false,
|
||||
},
|
||||
animation: {
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
animatedMfm: {
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
advancedMfm: {
|
||||
default: true,
|
||||
},
|
||||
showReactionsCount: {
|
||||
default: false,
|
||||
},
|
||||
enableQuickAddMfmFunction: {
|
||||
default: false,
|
||||
},
|
||||
loadRawImages: {
|
||||
default: false,
|
||||
},
|
||||
imageNewTab: {
|
||||
default: false,
|
||||
},
|
||||
disableShowingAnimatedImages: {
|
||||
default: window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
emojiStyle: {
|
||||
default: 'twemoji', // twemoji / fluentEmoji / native
|
||||
},
|
||||
menuStyle: {
|
||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
||||
},
|
||||
useBlurEffectForModal: {
|
||||
default: true,
|
||||
},
|
||||
useBlurEffect: {
|
||||
default: true,
|
||||
},
|
||||
useStickyIcons: {
|
||||
default: true,
|
||||
},
|
||||
enableHighQualityImagePlaceholders: {
|
||||
default: true,
|
||||
},
|
||||
showFixedPostForm: {
|
||||
default: false,
|
||||
},
|
||||
showFixedPostFormInChannel: {
|
||||
default: false,
|
||||
},
|
||||
enableInfiniteScroll: {
|
||||
default: true,
|
||||
},
|
||||
useReactionPickerForContextMenu: {
|
||||
default: false,
|
||||
},
|
||||
instanceTicker: {
|
||||
default: 'remote' as 'none' | 'remote' | 'always',
|
||||
},
|
||||
emojiPickerScale: {
|
||||
default: 2,
|
||||
},
|
||||
emojiPickerWidth: {
|
||||
default: 2,
|
||||
},
|
||||
emojiPickerHeight: {
|
||||
default: 3,
|
||||
},
|
||||
emojiPickerStyle: {
|
||||
default: 'auto' as 'auto' | 'popup' | 'drawer',
|
||||
},
|
||||
squareAvatars: {
|
||||
default: false,
|
||||
},
|
||||
showAvatarDecorations: {
|
||||
default: true,
|
||||
},
|
||||
numberOfPageCache: {
|
||||
default: 3,
|
||||
},
|
||||
pollingInterval: {
|
||||
// 1 ... 低
|
||||
// 2 ... 中
|
||||
// 3 ... 高
|
||||
default: 2,
|
||||
},
|
||||
showNoteActionsOnlyHover: {
|
||||
default: false,
|
||||
},
|
||||
showClipButtonInNoteFooter: {
|
||||
default: false,
|
||||
},
|
||||
reactionsDisplaySize: {
|
||||
default: 'medium' as 'small' | 'medium' | 'large',
|
||||
},
|
||||
limitWidthOfReaction: {
|
||||
default: true,
|
||||
},
|
||||
forceShowAds: {
|
||||
default: false,
|
||||
},
|
||||
aiChanMode: {
|
||||
default: false,
|
||||
},
|
||||
devMode: {
|
||||
default: false,
|
||||
},
|
||||
mediaListWithOneImageAppearance: {
|
||||
default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
|
||||
},
|
||||
notificationPosition: {
|
||||
default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom',
|
||||
},
|
||||
notificationStackAxis: {
|
||||
default: 'horizontal' as 'vertical' | 'horizontal',
|
||||
},
|
||||
enableCondensedLine: {
|
||||
default: true,
|
||||
},
|
||||
keepScreenOn: {
|
||||
default: false,
|
||||
},
|
||||
useGroupedNotifications: {
|
||||
default: true,
|
||||
},
|
||||
dataSaver: {
|
||||
default: {
|
||||
media: false,
|
||||
avatar: false,
|
||||
urlPreviewThumbnail: false,
|
||||
disableUrlPreview: false,
|
||||
code: false,
|
||||
} satisfies Record<string, boolean>,
|
||||
},
|
||||
hemisphere: {
|
||||
default: hemisphere as 'N' | 'S',
|
||||
},
|
||||
enableSeasonalScreenEffect: {
|
||||
default: false,
|
||||
},
|
||||
enableHorizontalSwipe: {
|
||||
default: false,
|
||||
},
|
||||
enablePullToRefresh: {
|
||||
default: true,
|
||||
},
|
||||
useNativeUiForVideoAudioPlayer: {
|
||||
default: false,
|
||||
},
|
||||
keepOriginalFilename: {
|
||||
default: true,
|
||||
},
|
||||
alwaysConfirmFollow: {
|
||||
default: true,
|
||||
},
|
||||
confirmWhenRevealingSensitiveMedia: {
|
||||
default: false,
|
||||
},
|
||||
contextMenu: {
|
||||
default: 'app' as 'app' | 'appWithShift' | 'native',
|
||||
},
|
||||
skipNoteRender: {
|
||||
default: true,
|
||||
},
|
||||
showSoftWordMutedWord: {
|
||||
default: false,
|
||||
},
|
||||
confirmOnReact: {
|
||||
default: false,
|
||||
},
|
||||
defaultFollowWithReplies: {
|
||||
default: false,
|
||||
},
|
||||
makeEveryTextElementsSelectable: {
|
||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
||||
},
|
||||
showNavbarSubButtons: {
|
||||
default: true,
|
||||
},
|
||||
showTitlebar: {
|
||||
default: false,
|
||||
},
|
||||
showAvailableReactionsFirstInNote: {
|
||||
default: false,
|
||||
},
|
||||
plugins: {
|
||||
default: [] as Plugin[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const sameIdExists = a.some(x => b.some(y => x.installId === y.installId));
|
||||
if (sameIdExists) throw new Error();
|
||||
const sameNameExists = a.some(x => b.some(y => x.name === y.name));
|
||||
if (sameNameExists) throw new Error();
|
||||
return a.concat(b);
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
mutingEmojis: {
|
||||
default: [] as string[],
|
||||
mergeStrategy: (a, b) => {
|
||||
return [...new Set(a.concat(b))];
|
||||
showNoteActionsOnlyHover: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watermarkPresets: {
|
||||
accountDependent: true,
|
||||
default: [] as WatermarkPreset[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
showClipButtonInNoteFooter: {
|
||||
default: false,
|
||||
},
|
||||
reactionsDisplaySize: {
|
||||
default: 'medium' as 'small' | 'medium' | 'large',
|
||||
},
|
||||
limitWidthOfReaction: {
|
||||
default: true,
|
||||
},
|
||||
forceShowAds: {
|
||||
default: false,
|
||||
},
|
||||
aiChanMode: {
|
||||
default: false,
|
||||
},
|
||||
devMode: {
|
||||
default: false,
|
||||
},
|
||||
mediaListWithOneImageAppearance: {
|
||||
default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
|
||||
},
|
||||
notificationPosition: {
|
||||
default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom',
|
||||
},
|
||||
notificationStackAxis: {
|
||||
default: 'horizontal' as 'vertical' | 'horizontal',
|
||||
},
|
||||
enableCondensedLine: {
|
||||
default: true,
|
||||
},
|
||||
keepScreenOn: {
|
||||
default: false,
|
||||
},
|
||||
useGroupedNotifications: {
|
||||
default: true,
|
||||
},
|
||||
dataSaver: {
|
||||
default: {
|
||||
media: false,
|
||||
avatar: false,
|
||||
urlPreviewThumbnail: false,
|
||||
disableUrlPreview: false,
|
||||
code: false,
|
||||
} satisfies Record<string, boolean>,
|
||||
},
|
||||
hemisphere: {
|
||||
default: hemisphere as 'N' | 'S',
|
||||
},
|
||||
enableSeasonalScreenEffect: {
|
||||
default: false,
|
||||
},
|
||||
enableHorizontalSwipe: {
|
||||
default: false,
|
||||
},
|
||||
enablePullToRefresh: {
|
||||
default: true,
|
||||
},
|
||||
useNativeUiForVideoAudioPlayer: {
|
||||
default: false,
|
||||
},
|
||||
keepOriginalFilename: {
|
||||
default: true,
|
||||
},
|
||||
alwaysConfirmFollow: {
|
||||
default: true,
|
||||
},
|
||||
confirmWhenRevealingSensitiveMedia: {
|
||||
default: false,
|
||||
},
|
||||
contextMenu: {
|
||||
default: 'app' as 'app' | 'appWithShift' | 'native',
|
||||
},
|
||||
skipNoteRender: {
|
||||
default: true,
|
||||
},
|
||||
showSoftWordMutedWord: {
|
||||
default: false,
|
||||
},
|
||||
confirmOnReact: {
|
||||
default: false,
|
||||
},
|
||||
defaultFollowWithReplies: {
|
||||
default: false,
|
||||
},
|
||||
makeEveryTextElementsSelectable: {
|
||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
||||
},
|
||||
showNavbarSubButtons: {
|
||||
default: true,
|
||||
},
|
||||
showTitlebar: {
|
||||
default: false,
|
||||
},
|
||||
showAvailableReactionsFirstInNote: {
|
||||
default: false,
|
||||
},
|
||||
plugins: {
|
||||
default: [] as Plugin[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const sameIdExists = a.some(x => b.some(y => x.installId === y.installId));
|
||||
if (sameIdExists) throw new Error();
|
||||
const sameNameExists = a.some(x => b.some(y => x.name === y.name));
|
||||
if (sameNameExists) throw new Error();
|
||||
return a.concat(b);
|
||||
},
|
||||
},
|
||||
mutingEmojis: {
|
||||
default: [] as string[],
|
||||
mergeStrategy: (a, b) => {
|
||||
return [...new Set(a.concat(b))];
|
||||
},
|
||||
},
|
||||
watermarkPresets: {
|
||||
accountDependent: true,
|
||||
default: [] as WatermarkPreset[],
|
||||
mergeStrategy: (a, b) => {
|
||||
const mergedItems = [] as typeof a;
|
||||
for (const x of a.concat(b)) {
|
||||
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||
if (sameIdItem != null) {
|
||||
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||
continue;
|
||||
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
} else {
|
||||
mergedItems.push(x);
|
||||
}
|
||||
}
|
||||
return mergedItems;
|
||||
return mergedItems;
|
||||
},
|
||||
},
|
||||
defaultWatermarkPresetId: {
|
||||
accountDependent: true,
|
||||
default: null as WatermarkPreset['id'] | null,
|
||||
},
|
||||
defaultImageCompressionLevel: {
|
||||
default: 2 as 0 | 1 | 2 | 3,
|
||||
},
|
||||
lowPowerMode: {
|
||||
default: false,
|
||||
},
|
||||
|
||||
'sound.masterVolume': {
|
||||
default: 0.5,
|
||||
},
|
||||
'sound.notUseSound': {
|
||||
default: false,
|
||||
},
|
||||
'sound.useSoundOnlyWhenActive': {
|
||||
default: false,
|
||||
},
|
||||
'sound.on.note': {
|
||||
default: { type: 'syuilo/n-aec', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.noteMy': {
|
||||
default: { type: 'syuilo/n-cea-4va', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.notification': {
|
||||
default: { type: 'syuilo/n-ea', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.reaction': {
|
||||
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.chatMessage': {
|
||||
default: { type: 'syuilo/waon', volume: 1 } as SoundStore,
|
||||
},
|
||||
|
||||
'deck.alwaysShowMainColumn': {
|
||||
default: true,
|
||||
},
|
||||
'deck.navWindow': {
|
||||
default: true,
|
||||
},
|
||||
'deck.useSimpleUiForNonRootPages': {
|
||||
default: true,
|
||||
},
|
||||
'deck.columnAlign': {
|
||||
default: 'center' as 'left' | 'right' | 'center',
|
||||
},
|
||||
'deck.columnGap': {
|
||||
default: 6,
|
||||
},
|
||||
'deck.menuPosition': {
|
||||
default: 'bottom' as 'right' | 'bottom',
|
||||
},
|
||||
'deck.navbarPosition': {
|
||||
default: 'left' as 'left' | 'top' | 'bottom',
|
||||
},
|
||||
'deck.wallpaper': {
|
||||
default: null as string | null,
|
||||
},
|
||||
|
||||
'chat.showSenderName': {
|
||||
default: false,
|
||||
},
|
||||
'chat.sendOnEnter': {
|
||||
default: false,
|
||||
},
|
||||
|
||||
'game.dropAndFusion': {
|
||||
default: {
|
||||
bgmVolume: 0.25,
|
||||
sfxVolume: 1,
|
||||
},
|
||||
},
|
||||
|
||||
'experimental.stackingRouterView': {
|
||||
default: false,
|
||||
},
|
||||
'experimental.enableFolderPageView': {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
defaultWatermarkPresetId: {
|
||||
accountDependent: true,
|
||||
default: null as WatermarkPreset['id'] | null,
|
||||
},
|
||||
defaultImageCompressionLevel: {
|
||||
default: 2 as 0 | 1 | 2 | 3,
|
||||
},
|
||||
|
||||
'sound.masterVolume': {
|
||||
default: 0.5,
|
||||
},
|
||||
'sound.notUseSound': {
|
||||
default: false,
|
||||
},
|
||||
'sound.useSoundOnlyWhenActive': {
|
||||
default: false,
|
||||
},
|
||||
'sound.on.note': {
|
||||
default: { type: 'syuilo/n-aec', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.noteMy': {
|
||||
default: { type: 'syuilo/n-cea-4va', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.notification': {
|
||||
default: { type: 'syuilo/n-ea', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.reaction': {
|
||||
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
||||
},
|
||||
'sound.on.chatMessage': {
|
||||
default: { type: 'syuilo/waon', volume: 1 } as SoundStore,
|
||||
},
|
||||
|
||||
'deck.alwaysShowMainColumn': {
|
||||
default: true,
|
||||
},
|
||||
'deck.navWindow': {
|
||||
default: true,
|
||||
},
|
||||
'deck.useSimpleUiForNonRootPages': {
|
||||
default: true,
|
||||
},
|
||||
'deck.columnAlign': {
|
||||
default: 'center' as 'left' | 'right' | 'center',
|
||||
},
|
||||
'deck.columnGap': {
|
||||
default: 6,
|
||||
},
|
||||
'deck.menuPosition': {
|
||||
default: 'bottom' as 'right' | 'bottom',
|
||||
},
|
||||
'deck.navbarPosition': {
|
||||
default: 'left' as 'left' | 'top' | 'bottom',
|
||||
},
|
||||
'deck.wallpaper': {
|
||||
default: null as string | null,
|
||||
},
|
||||
|
||||
'chat.showSenderName': {
|
||||
default: false,
|
||||
},
|
||||
'chat.sendOnEnter': {
|
||||
default: false,
|
||||
},
|
||||
|
||||
'game.dropAndFusion': {
|
||||
default: {
|
||||
bgmVolume: 0.25,
|
||||
sfxVolume: 1,
|
||||
},
|
||||
},
|
||||
|
||||
'experimental.stackingRouterView': {
|
||||
default: false,
|
||||
},
|
||||
'experimental.enableFolderPageView': {
|
||||
default: false,
|
||||
computed: {
|
||||
disableShowingAnimatedImages: (s) => s.disableShowingAnimatedImages || s.lowPowerMode,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -101,9 +101,19 @@ type PreferencesDefinitionRecord<Default, T = Default extends (...args: any) =>
|
||||
export type PreferencesDefinition = Record<string, PreferencesDefinitionRecord<any>>;
|
||||
|
||||
export function definePreferences<T extends Record<string, unknown>>(x: {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>
|
||||
states: {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>;
|
||||
};
|
||||
computed: {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>;
|
||||
};
|
||||
}): {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>
|
||||
states: {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>;
|
||||
};
|
||||
computed: {
|
||||
[K in keyof T]: PreferencesDefinitionRecord<T[K]>;
|
||||
};
|
||||
} {
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -491,6 +491,10 @@ export const ROUTE_DEF = [{
|
||||
path: '/performance',
|
||||
name: 'performance',
|
||||
component: page(() => import('@/pages/admin/performance.vue')),
|
||||
}, {
|
||||
path: '/server-rules',
|
||||
name: 'server-rules',
|
||||
component: page(() => import('@/pages/admin/server-rules.vue')),
|
||||
}, {
|
||||
path: '/invites',
|
||||
name: 'invites',
|
||||
|
||||
@@ -37,6 +37,11 @@ html {
|
||||
color: var(--MI_THEME-fg);
|
||||
accent-color: var(--MI_THEME-accent);
|
||||
|
||||
&, * {
|
||||
scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
&.f-1 {
|
||||
font-size: 15px;
|
||||
}
|
||||
@@ -86,11 +91,7 @@ html::selection {
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
html, body, main, div {
|
||||
scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
html,
|
||||
|
||||
@@ -48,6 +48,10 @@ function skip() {
|
||||
.title {
|
||||
padding: 0 10px;
|
||||
font-weight: bold;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<span :class="$style.icon">
|
||||
<i class="ti ti-info-circle"></i>
|
||||
</span>
|
||||
<span :class="$style.title">{{ i18n.ts.reloadRequiredToApplySettings }}</span>
|
||||
<span :class="$style.body"><button class="_textButton" style="color: var(--MI_THEME-fgOnAccent);" @click="reload">{{ i18n.ts.reload }}</button> | <button class="_textButton" style="color: var(--MI_THEME-fgOnAccent);" @click="skip">{{ i18n.ts.skip }}</button></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { shouldSuggestReload } from '@/utility/reload-suggest.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
|
||||
function reload() {
|
||||
unisonReload();
|
||||
}
|
||||
|
||||
function skip() {
|
||||
shouldSuggestReload.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
--height: 24px;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
vertical-align: bottom;
|
||||
width: 100%;
|
||||
line-height: var(--height);
|
||||
height: var(--height);
|
||||
overflow: clip;
|
||||
contain: strict;
|
||||
background: var(--MI_THEME-accent);
|
||||
color: var(--MI_THEME-fgOnAccent);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 10px;
|
||||
animation: blink 2s infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 0 10px;
|
||||
font-weight: bold;
|
||||
animation: blink 2s infinite;
|
||||
}
|
||||
|
||||
.body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: clip;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
10% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
90% { opacity: 1; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
@@ -94,11 +94,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="dev" id="devTicker"><span style="animation: dev-ticker-blink 2s infinite;">DEV BUILD</span></div>
|
||||
|
||||
<div v-if="$i && $i.isBot" id="botWarn"><span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.loggedInAsBot }}</span></div>
|
||||
|
||||
<div v-if="isSafeMode" id="safemodeWarn">
|
||||
<span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.safeModeEnabled }}</span>
|
||||
<button class="_textButton" style="pointer-events: all;" @click="exitSafeMode">{{ i18n.ts.turnItOff }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -106,10 +101,7 @@ import { defineAsyncComponent, ref, TransitionGroup } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { swInject } from './sw-inject.js';
|
||||
import XNotification from './notification.vue';
|
||||
import { isSafeMode } from '@@/js/config.js';
|
||||
import { popups } from '@/os.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { pendingApiRequestsCount } from '@/utility/misskey-api.js';
|
||||
import * as sound from '@/utility/sound.js';
|
||||
import { $i } from '@/i.js';
|
||||
@@ -152,13 +144,6 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
|
||||
sound.playMisskeySfx('notification');
|
||||
}
|
||||
|
||||
function exitSafeMode() {
|
||||
miLocalStorage.removeItem('isSafeMode');
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('safemode');
|
||||
unisonReload(url.toString());
|
||||
}
|
||||
|
||||
if ($i) {
|
||||
if (store.s.realtimeMode) {
|
||||
const connection = useStream().useChannel('main');
|
||||
@@ -411,7 +396,7 @@ if ($i) {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
text-align: center;
|
||||
z-index: 2147483646;
|
||||
z-index: 2147483647;
|
||||
color: #ff0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 4px 7px;
|
||||
@@ -420,11 +405,6 @@ if ($i) {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#safemodeWarn {
|
||||
@extend #botWarn;
|
||||
z-index: 2147483647;
|
||||
}
|
||||
|
||||
#devTicker {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -13,8 +13,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : null }">
|
||||
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/>
|
||||
|
||||
<XReloadSuggest v-if="shouldSuggestReload"/>
|
||||
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
|
||||
<XAnnouncements v-if="$i"/>
|
||||
<XStatusBars/>
|
||||
<div :class="$style.columnsWrapper">
|
||||
@@ -83,14 +81,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref, useTemplateRef } from 'vue';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import XSidebar from '@/ui/_common_/navbar.vue';
|
||||
import XNavbarH from '@/ui/_common_/navbar-h.vue';
|
||||
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
|
||||
import XTitlebar from '@/ui/_common_/titlebar.vue';
|
||||
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
|
||||
import XReloadSuggest from '@/ui/_common_/ReloadSuggest.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -109,8 +105,6 @@ import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
|
||||
import XChatColumn from '@/ui/deck/chat-column.vue';
|
||||
import { mainRouter } from '@/router.js';
|
||||
import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js';
|
||||
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
|
||||
import { shouldSuggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
||||
|
||||
@@ -12,7 +12,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div :class="[$style.contents, !isMobile && prefer.r.showTitlebar.value ? $style.withSidebarAndTitlebar : null]" @contextmenu.stop="onContextmenu">
|
||||
<div>
|
||||
<XReloadSuggest v-if="shouldSuggestReload"/>
|
||||
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
|
||||
<XAnnouncements v-if="$i"/>
|
||||
<XStatusBars :class="$style.statusbars"/>
|
||||
@@ -39,7 +38,6 @@ import XCommon from './_common_/common.vue';
|
||||
import type { PageMetadata } from '@/page.js';
|
||||
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
|
||||
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
|
||||
import XReloadSuggest from '@/ui/_common_/ReloadSuggest.vue';
|
||||
import XTitlebar from '@/ui/_common_/titlebar.vue';
|
||||
import XSidebar from '@/ui/_common_/navbar.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -52,7 +50,6 @@ import { mainRouter } from '@/router.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { shouldSuggestReload } from '@/utility/reload-suggest.js';
|
||||
|
||||
const XWidgets = defineAsyncComponent(() => import('./_common_/widgets.vue'));
|
||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||
|
||||
@@ -44,8 +44,6 @@
|
||||
"😑": ["face", "indifferent", "-_-", "meh", "deadpan"],
|
||||
"😒": ["indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"],
|
||||
"🙄": ["face", "eyeroll", "frustrated"],
|
||||
"🙂↔️": ["face", "head", "horizontally", "no", "shake", "shaking"],
|
||||
"🙂↕️": ["face", "head", "nod", "shaking", "vertically", "yes"],
|
||||
"🤔": ["face", "hmmm", "think", "consider"],
|
||||
"🤥": ["face", "lie", "pinocchio"],
|
||||
"🤭": ["face", "whoops", "shock", "surprise"],
|
||||
@@ -78,7 +76,6 @@
|
||||
"😥": ["face", "phew", "sweat", "nervous"],
|
||||
"🤤": ["face"],
|
||||
"😪": ["face", "tired", "rest", "nap"],
|
||||
"": ["face", "bags", "bored", "exhausted", "eyes", "fatigued", "late", "sleepy", "tired", "weary"],
|
||||
"😓": ["face", "hot", "sad", "tired", "exercise"],
|
||||
"🥵": ["face", "feverish", "heat", "red", "sweating"],
|
||||
"🥶": ["face", "blue", "freezing", "frozen", "frostbite", "icicles"],
|
||||
@@ -95,11 +92,10 @@
|
||||
"🥴": ["face", "dizzy", "intoxicated", "tipsy", "wavy"],
|
||||
"🥱": ["face", "tired", "yawning"],
|
||||
"😴": ["face", "tired", "sleepy", "night", "zzz"],
|
||||
"👁️🗨️": ["balloon", "bubble", "eye", "speech", "witness"],
|
||||
"💤": ["sleepy", "tired", "dream"],
|
||||
"😶🌫️": ["face", "absentminded", "clouds", "fog", "head"],
|
||||
"😮💨": ["face", "blow", "blowing", "exhale", "exhaling", "exhausted", "gasp", "groan", "relief", "sigh", "smiley", "smoke", "whisper", "whistle"],
|
||||
"😵💫": ["face", "confused", "dizzy", "eyes", "hypnotized", "omg", "smiley", "spiral", "trouble", "whoa", "woah", "woozy"],
|
||||
"😶🌫️": [],
|
||||
"😮💨": [],
|
||||
"😵💫": [],
|
||||
"🫠": ["disappear", "dissolve", "liquid", "melt", "toketa"],
|
||||
"🫢": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"],
|
||||
"🫣": ["captivated", "peep", "stare", "chunibyo"],
|
||||
@@ -163,19 +159,19 @@
|
||||
"🤞": ["good", "lucky"],
|
||||
"🖖": ["hand", "fingers", "spock", "star trek"],
|
||||
"✍": ["lower_left_ballpoint_pen", "stationery", "write", "compose"],
|
||||
"🫰": ["<3", "crossed", "expensive", "finger", "hand", "heart", "index", "love", "money", "snap", "thumb"],
|
||||
"🫱": ["hand", "handshake", "hold", "reach", "right", "rightward", "rightwards", "shake"],
|
||||
"🫲": ["hand", "handshake", "hold", "left", "leftward", "leftwards", "reach", "shake"],
|
||||
"🫳": ["dismiss", "down", "drop", "dropped", "hand", "palm", "pick", "shoo", "up"],
|
||||
"🫴": ["beckon", "catch", "come", "hand", "hold", "know", "lift", "me", "offer", "palm", "tell"],
|
||||
"🫵": ["at", "finger", "hand", "index", "pointing", "poke", "viewer", "you"],
|
||||
"🫰": [],
|
||||
"🫱": [],
|
||||
"🫲": [],
|
||||
"🫳": [],
|
||||
"🫴": [],
|
||||
"🫵": [],
|
||||
"🫶": ["moemoekyun"],
|
||||
"🤏": ["hand", "fingers"],
|
||||
"🤌": ["hand", "fingers"],
|
||||
"🤳": ["camera", "phone"],
|
||||
"💅": ["beauty", "manicure", "finger", "fashion", "nail"],
|
||||
"👄": ["mouth", "kiss"],
|
||||
"🫦": ["anxious", "bite", "biting", "fear", "flirt", "flirting", "kiss", "lip", "lipstick", "nervous", "sexy", "uncomfortable", "worried", "worry"],
|
||||
"🫦": [],
|
||||
"🦷": ["teeth", "dentist"],
|
||||
"👅": ["mouth", "playful"],
|
||||
"👂": ["face", "hear", "sound", "listen"],
|
||||
@@ -184,12 +180,11 @@
|
||||
"👁": ["face", "look", "see", "watch", "stare"],
|
||||
"👀": ["look", "watch", "stalk", "peek", "see"],
|
||||
"🧠": ["smart", "intelligent"],
|
||||
"🫀": ["anatomical", "beat", "cardiology", "heart", "heartbeat", "organ", "pulse", "real", "red"],
|
||||
"🫁": ["breath", "breathe", "exhalation", "inhalation", "lung", "lungs", "organ", "respiration"],
|
||||
"🫀": [],
|
||||
"🫁": [],
|
||||
"👤": ["user", "person", "human"],
|
||||
"👥": ["user", "person", "human", "group", "team"],
|
||||
"🗣": ["user", "person", "human", "sing", "say", "talk"],
|
||||
"": ["clue", "crime", "detective", "fingerprint", "forensics", "identity", "mystery", "print", "safety", "trace"],
|
||||
"👶": ["child", "boy", "girl", "toddler"],
|
||||
"🧒": ["gender-neutral", "young"],
|
||||
"👦": ["man", "male", "guy", "teenager"],
|
||||
@@ -204,7 +199,6 @@
|
||||
"👩🦰": ["woman", "female", "girl", "ginger", "redhead"],
|
||||
"👨🦰": ["man", "male", "boy", "guy", "ginger", "redhead"],
|
||||
"👱♀️": ["woman", "female", "girl", "blonde", "person"],
|
||||
"👱♂️": ["blond", "blond-haired", "hair", "man"],
|
||||
"👱": ["man", "male", "boy", "blonde", "guy", "person"],
|
||||
"🧑🦳": ["gray", "old", "white"],
|
||||
"👩🦳": ["woman", "female", "girl", "gray", "old", "white"],
|
||||
@@ -213,27 +207,20 @@
|
||||
"👩🦲": ["woman", "female", "girl", "bald", "chemotherapy", "hairless", "shaven"],
|
||||
"👨🦲": ["man", "male", "boy", "guy", "bald", "chemotherapy", "hairless", "shaven"],
|
||||
"🧔": ["person", "bewhiskered"],
|
||||
"🧔♀️": ["beard", "bearded", "whiskers", "woman"],
|
||||
"🧔♂️": ["beard", "bearded", "man", "whiskers"],
|
||||
"🧓": ["human", "elder", "senior", "gender-neutral"],
|
||||
"👴": ["human", "male", "men", "old", "elder", "senior"],
|
||||
"👵": ["human", "female", "women", "lady", "old", "elder", "senior"],
|
||||
"👲": ["male", "boy", "chinese"],
|
||||
"🧕": ["female", "hijab", "mantilla", "tichel"],
|
||||
"👳♀️": ["female", "indian", "hinduism", "arabs", "woman"],
|
||||
"👳♂️": ["man", "turban", "wearing"],
|
||||
"👳": ["male", "indian", "hinduism", "arabs"],
|
||||
"👮♀️": ["woman", "police", "law", "legal", "enforcement", "arrest", "911", "female"],
|
||||
"👮♂️": ["apprehend", "arrest", "citation", "cop", "law", "man", "officer", "over", "police", "pulled", "undercover"],
|
||||
"👮": ["man", "police", "law", "legal", "enforcement", "arrest", "911"],
|
||||
"👷♀️": ["female", "human", "wip", "build", "construction", "worker", "labor", "woman"],
|
||||
"👷♂️": ["build", "construction", "fix", "hardhat", "hat", "man", "rebuild", "remodel", "repair", "work", "worker"],
|
||||
"👷": ["male", "human", "wip", "guy", "build", "construction", "worker", "labor"],
|
||||
"💂♀️": ["uk", "gb", "british", "female", "royal", "woman"],
|
||||
"💂♂️": ["buckingham", "guard", "helmet", "london", "man", "palace"],
|
||||
"💂": ["uk", "gb", "british", "male", "guy", "royal"],
|
||||
"🕵️♀️": ["human", "spy", "detective", "female", "woman"],
|
||||
"🕵️♂️": ["detective", "man", "sleuth", "spy"],
|
||||
"🕵": ["human", "spy", "detective"],
|
||||
"🧑⚕️": ["doctor", "nurse", "therapist", "healthcare", "human"],
|
||||
"👩⚕️": ["doctor", "nurse", "therapist", "healthcare", "woman", "human"],
|
||||
@@ -283,152 +270,102 @@
|
||||
"🧑⚖️": ["justice", "court", "human"],
|
||||
"👩⚖️": ["justice", "court", "woman", "human"],
|
||||
"👨⚖️": ["justice", "court", "man", "human"],
|
||||
"🦸": ["good", "hero", "superhero", "superpower"],
|
||||
"🦸♀️": ["woman", "female", "good", "heroine", "superpowers"],
|
||||
"🦸♂️": ["man", "male", "good", "hero", "superpowers"],
|
||||
"🦹": ["bad", "criminal", "evil", "superpower", "supervillain", "villain"],
|
||||
"🦹♀️": ["woman", "female", "evil", "bad", "criminal", "heroine", "superpowers"],
|
||||
"🦹♂️": ["man", "male", "evil", "bad", "criminal", "hero", "superpowers"],
|
||||
"🤶": ["woman", "female", "xmas", "mother christmas"],
|
||||
"🧑🎄": ["xmas", "christmas"],
|
||||
"🎅": ["festival", "man", "male", "xmas", "father christmas"],
|
||||
"🥷": ["assassin", "fight", "fighter", "hidden", "ninja", "person", "secret", "skills", "sly", "soldier", "stealth", "war"],
|
||||
"🧙": ["fantasy", "mage", "magic", "play", "sorcerer", "sorceress", "sorcery", "spell", "summon", "witch", "wizard"],
|
||||
"🥷": [],
|
||||
"🧙♀️": ["woman", "female", "mage", "witch"],
|
||||
"🧙♂️": ["man", "male", "mage", "sorcerer"],
|
||||
"🧝": ["elf", "elves", "enchantment", "fantasy", "folklore", "magic", "magical", "myth"],
|
||||
"🧝♀️": ["woman", "female"],
|
||||
"🧝♂️": ["man", "male"],
|
||||
"🧛": ["blood", "dracula", "fangs", "halloween", "scary", "supernatural", "teeth", "undead", "vampire"],
|
||||
"🧛♀️": ["woman", "female"],
|
||||
"🧛♂️": ["man", "male", "dracula"],
|
||||
"🧟": ["apocalypse", "dead", "halloween", "horror", "scary", "undead", "walking", "zombie"],
|
||||
"🧟♀️": ["woman", "female", "undead", "walking dead"],
|
||||
"🧟♂️": ["man", "male", "dracula", "undead", "walking dead"],
|
||||
"🧞": ["djinn", "fantasy", "genie", "jinn", "lamp", "myth", "rub", "wishes"],
|
||||
"🧞♀️": ["woman", "female"],
|
||||
"🧞♂️": ["man", "male"],
|
||||
"🧜": ["creature", "fairytale", "folklore", "merperson", "ocean", "sea", "siren", "trident"],
|
||||
"🧜♀️": ["woman", "female", "merwoman", "ariel"],
|
||||
"🧜♂️": ["man", "male", "triton"],
|
||||
"🧚": ["fairy", "fairytale", "fantasy", "myth", "person", "pixie", "tale", "wings"],
|
||||
"🧚♀️": ["woman", "female"],
|
||||
"🧚♂️": ["man", "male"],
|
||||
"👼": ["heaven", "wings", "halo"],
|
||||
"🧌": ["fairy", "fantasy", "monster", "tale", "troll", "trolling"],
|
||||
"🧌": [],
|
||||
"🤰": ["baby"],
|
||||
"🫃": ["belly", "bloated", "full", "man", "overeat", "pregnant"],
|
||||
"🫄": ["belly", "bloated", "full", "overeat", "person", "pregnant", "stuffed"],
|
||||
"🫅": ["crown", "monarch", "noble", "person", "regal", "royal", "royalty"],
|
||||
"🫃": [],
|
||||
"🫄": [],
|
||||
"🫅": [],
|
||||
"🤱": ["nursing", "baby"],
|
||||
"👩🍼": ["baby", "feed", "feeding", "mom", "mother", "nanny", "newborn", "nursing", "woman"],
|
||||
"👨🍼": ["baby", "dad", "father", "feed", "feeding", "man", "nanny", "newborn", "nursing"],
|
||||
"🧑🍼": ["baby", "feed", "feeding", "nanny", "newborn", "nursing", "parent"],
|
||||
"👩🍼": [],
|
||||
"👨🍼": [],
|
||||
"🧑🍼": [],
|
||||
"👸": ["girl", "woman", "female", "blond", "crown", "royal", "queen"],
|
||||
"🤴": ["boy", "man", "male", "crown", "royal", "king"],
|
||||
"👰": ["couple", "marriage", "wedding", "woman", "bride"],
|
||||
"👰♀️": ["bride", "veil", "wedding", "woman"],
|
||||
"👰♂️": ["man", "veil", "wedding"],
|
||||
"👰": ["couple", "marriage", "wedding", "woman", "bride"],
|
||||
"🤵": ["couple", "marriage", "wedding", "groom"],
|
||||
"🤵": ["couple", "marriage", "wedding", "groom"],
|
||||
"🤵♀️": ["formal", "tuxedo", "wedding", "woman"],
|
||||
"🤵♂️": ["formal", "groom", "man", "tuxedo", "wedding"],
|
||||
"🏃➡️": ["facing", "fast", "hurry", "marathon", "move", "person", "quick", "race", "racing", "right", "run", "rush", "speed"],
|
||||
"🏃♀️": ["woman", "walking", "exercise", "race", "running", "female"],
|
||||
"🏃": ["man", "walking", "exercise", "race", "running"],
|
||||
"🏃♀️➡️": ["facing", "fast", "hurry", "marathon", "move", "quick", "race", "racing", "right", "run", "rush", "speed", "woman"],
|
||||
"🏃♂️": ["fast", "hurry", "man", "marathon", "move", "quick", "race", "racing", "run", "rush", "speed"],
|
||||
"🏃♂️➡️": ["facing", "fast", "hurry", "man", "marathon", "move", "quick", "race", "racing", "right", "run", "rush", "speed"],
|
||||
"🚶➡️": ["amble", "facing", "gait", "hike", "man", "pace", "pedestrian", "person", "right", "stride", "stroll", "walk", "walking"],
|
||||
"🚶♀️": ["human", "feet", "steps", "woman", "female"],
|
||||
"🚶": ["human", "feet", "steps"],
|
||||
"🚶♀️➡️": ["amble", "facing", "gait", "hike", "man", "pace", "pedestrian", "right", "stride", "stroll", "walk", "walking", "woman"],
|
||||
"🚶♂️": ["amble", "gait", "hike", "man", "pace", "pedestrian", "stride", "stroll", "walk", "walking"],
|
||||
"🚶♂️➡️": ["amble", "facing", "gait", "hike", "man", "pace", "pedestrian", "right", "stride", "stroll", "walk", "walking"],
|
||||
"💃": ["female", "girl", "woman", "fun"],
|
||||
"🕺": ["male", "boy", "fun", "dancer"],
|
||||
"👯": ["female", "bunny", "women", "girls"],
|
||||
"👯♀️": ["bestie", "bff", "bunny", "counterpart", "dancer", "double", "ear", "identical", "pair", "party", "partying", "people", "soulmate", "twin", "twinsies", "women"],
|
||||
"👯♂️": ["male", "bunny", "men", "boys"],
|
||||
"👫": ["pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage"],
|
||||
"🧑🤝🧑": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "human"],
|
||||
"👬": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "man", "human"],
|
||||
"👭": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "female", "human"],
|
||||
"🫂": ["comfort", "embrace", "farewell", "friendship", "goodbye", "hello", "hug", "hugging", "love", "people", "thanks"],
|
||||
"🫂": [],
|
||||
"🙇♀️": ["woman", "female", "girl"],
|
||||
"🙇": ["man", "male", "boy"],
|
||||
"🙇♂️": ["apology", "ask", "beg", "bow", "bowing", "favor", "forgive", "gesture", "man", "meditate", "meditation", "pity", "regret", "sorry"],
|
||||
"🤦": ["again", "bewilder", "disbelief", "exasperation", "facepalm", "no", "not", "oh", "omg", "person", "shock", "smh"],
|
||||
"🤦♂️": ["man", "male", "boy", "disbelief"],
|
||||
"🤦♀️": ["woman", "female", "girl", "disbelief"],
|
||||
"🤷": ["woman", "female", "girl", "confused", "indifferent", "doubt"],
|
||||
"🤷♀️": ["doubt", "dunno", "guess", "idk", "ignorance", "indifference", "knows", "maybe", "shrug", "shrugging", "whatever", "who", "woman"],
|
||||
"🤷♂️": ["man", "male", "boy", "confused", "indifferent", "doubt"],
|
||||
"💁": ["female", "girl", "woman", "human", "information"],
|
||||
"💁♀️": ["fetch", "flick", "flip", "gossip", "hand", "sarcasm", "sarcastic", "sassy", "seriously", "tipping", "whatever", "woman"],
|
||||
"💁♂️": ["male", "boy", "man", "human", "information"],
|
||||
"🙅": ["female", "girl", "woman", "nope"],
|
||||
"🙅♀️": ["forbidden", "gesture", "hand", "no", "not", "prohibit", "woman"],
|
||||
"🙅♂️": ["male", "boy", "man", "nope"],
|
||||
"🙆": ["women", "girl", "female", "pink", "human", "woman"],
|
||||
"🙆♀️": ["exercise", "gesture", "gesturing", "hand", "ok", "omg", "woman"],
|
||||
"🙆♂️": ["men", "boy", "male", "blue", "human", "man"],
|
||||
"🙋": ["female", "girl", "woman"],
|
||||
"🙋♀️": ["gesture", "hand", "here", "know", "me", "pick", "question", "raise", "raising", "woman"],
|
||||
"🙋♂️": ["male", "boy", "man"],
|
||||
"🙎": ["female", "girl", "woman"],
|
||||
"🙎♀️": ["disappointed", "downtrodden", "frown", "grimace", "pouting", "scowl", "sulk", "upset", "whine", "woman"],
|
||||
"🙎♂️": ["male", "boy", "man"],
|
||||
"🙍": ["female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy"],
|
||||
"🙍♀️": ["annoyed", "disappointed", "disgruntled", "disturbed", "frown", "frowning", "frustrated", "gesture", "irritated", "upset", "woman"],
|
||||
"🙍♂️": ["male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"],
|
||||
"💇": ["female", "girl", "woman"],
|
||||
"💇♀️": ["barber", "beauty", "chop", "cosmetology", "cut", "groom", "hair", "haircut", "parlor", "person", "shears", "style", "woman"],
|
||||
"💇♂️": ["male", "boy", "man"],
|
||||
"💆": ["female", "girl", "woman", "head"],
|
||||
"💆♀️": ["face", "getting", "headache", "massage", "relax", "relaxing", "salon", "soothe", "spa", "tension", "therapy", "treatment", "woman"],
|
||||
"💆♂️": ["male", "boy", "man", "head"],
|
||||
"🧖": ["day", "luxurious", "pamper", "person", "relax", "room", "sauna", "spa", "steam", "steambath", "unwind"],
|
||||
"🧖♀️": ["female", "woman", "spa", "steamroom", "sauna"],
|
||||
"🧖♂️": ["male", "man", "spa", "steamroom", "sauna"],
|
||||
"🧏": ["accessibility", "deaf", "ear", "gesture", "hear", "person"],
|
||||
"🧏♀️": ["woman", "female"],
|
||||
"🧏♂️": ["man", "male"],
|
||||
"🧍": ["person", "stand", "standing"],
|
||||
"🧍♀️": ["woman", "female"],
|
||||
"🧍♂️": ["man", "male"],
|
||||
"🧎": ["kneel", "kneeling", "knees", "person"],
|
||||
"🧎➡️": ["facing", "kneel", "kneeling", "knees", "person", "right"],
|
||||
"🧎♀️": ["woman", "female"],
|
||||
"🧎♀️➡️": ["facing", "kneel", "kneeling", "knees", "right", "woman"],
|
||||
"🧎♂️": ["man", "male"],
|
||||
"🧎♂️➡️": ["facing", "kneel", "kneeling", "knees", "man", "right"],
|
||||
"🧑🦯": ["accessibility", "blind"],
|
||||
"🧑🦯➡️": ["accessibility", "blind", "cane", "facing", "person", "probing", "right", "white"],
|
||||
"👩🦯": ["woman", "female", "accessibility", "blind"],
|
||||
"👩🦯➡️": ["accessibility", "blind", "cane", "facing", "probing", "right", "white", "woman"],
|
||||
"👨🦯": ["man", "male", "accessibility", "blind"],
|
||||
"👨🦯➡️": ["accessibility", "blind", "cane", "facing", "man", "probing", "right", "white"],
|
||||
"🧑🦼": ["accessibility"],
|
||||
"🧑🦼➡️": ["accessibility", "facing", "motorized", "person", "right", "wheelchair"],
|
||||
"👩🦼": ["woman", "female", "accessibility"],
|
||||
"👩🦼➡️": ["accessibility", "facing", "motorized", "right", "wheelchair", "woman"],
|
||||
"👨🦼": ["man", "male", "accessibility"],
|
||||
"👨🦼➡️": ["accessibility", "facing", "man", "motorized", "right", "wheelchair"],
|
||||
"🧑🦽": ["accessibility"],
|
||||
"🧑🦽➡️": ["accessibility", "facing", "manual", "person", "right", "wheelchair"],
|
||||
"👩🦽": ["woman", "female", "accessibility"],
|
||||
"👩🦽➡️": ["accessibility", "facing", "manual", "right", "wheelchair", "woman"],
|
||||
"👨🦽": ["man", "male", "accessibility"],
|
||||
"👨🦽➡️": ["accessibility", "facing", "man", "manual", "right", "wheelchair"],
|
||||
"💑": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
|
||||
"👩❤️👨": ["anniversary", "babe", "bae", "couple", "dating", "heart", "kiss", "love", "man", "person", "relationship", "romance", "together", "woman", "you"],
|
||||
"👩❤️👩": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
|
||||
"👨❤️👨": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
|
||||
"💏": ["pair", "valentines", "love", "like", "dating", "marriage"],
|
||||
"👩❤️💋👨": ["anniversary", "babe", "bae", "couple", "date", "dating", "heart", "kiss", "love", "man", "mwah", "person", "romance", "together", "woman", "xoxo"],
|
||||
"👩❤️💋👩": ["pair", "valentines", "love", "like", "dating", "marriage"],
|
||||
"👨❤️💋👨": ["pair", "valentines", "love", "like", "dating", "marriage"],
|
||||
"👨👩👦": ["boy", "child", "family", "man", "woman"],
|
||||
"👪": ["home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"],
|
||||
"👨👩👧": ["home", "parents", "people", "human", "child"],
|
||||
"👨👩👧👦": ["home", "parents", "people", "human", "children"],
|
||||
@@ -454,10 +391,6 @@
|
||||
"👨👧👦": ["home", "parent", "people", "human", "children"],
|
||||
"👨👦👦": ["home", "parent", "people", "human", "children"],
|
||||
"👨👧👧": ["home", "parent", "people", "human", "children"],
|
||||
"🧑🧑🧒": ["adult", "child", "family"],
|
||||
"🧑🧑🧒🧒": ["adult", "child", "family"],
|
||||
"🧑🧒": ["adult", "child", "family"],
|
||||
"🧑🧒🧒": ["adult", "child", "family"],
|
||||
"🧶": ["ball", "crochet", "knit"],
|
||||
"🧵": ["needle", "sewing", "spool", "string"],
|
||||
"🧥": ["jacket"],
|
||||
@@ -482,7 +415,7 @@
|
||||
"👢": ["shoes", "fashion"],
|
||||
"👞": ["fashion", "male"],
|
||||
"👟": ["shoes", "sports", "sneakers"],
|
||||
"🩴": ["beach", "flip", "flop", "sandal", "sandals", "shoe", "thong", "thongs", "zōri"],
|
||||
"🩴": [],
|
||||
"🩰": ["shoes", "sports"],
|
||||
"🧦": ["stockings", "clothes"],
|
||||
"🧤": ["hands", "winter", "clothes"],
|
||||
@@ -491,7 +424,7 @@
|
||||
"🎩": ["magic", "gentleman", "classy", "circus"],
|
||||
"🧢": ["cap", "baseball"],
|
||||
"⛑": ["construction", "build"],
|
||||
"🪖": ["army", "helmet", "military", "soldier", "war", "warrior"],
|
||||
"🪖": [],
|
||||
"🎓": ["school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education"],
|
||||
"👑": ["king", "kod", "leader", "royalty", "lord"],
|
||||
"🎒": ["student", "education", "bag", "backpack"],
|
||||
@@ -542,7 +475,6 @@
|
||||
"🐦⬛": ["animal", "nature", "bird", "black", "crow", "raven", "rook"],
|
||||
"🦅": ["animal", "nature", "bird"],
|
||||
"🦉": ["animal", "nature", "bird", "hoot"],
|
||||
"🐦🔥": ["animal", "nature", "ascend", "ascension", "emerge", "fantasy", "firebird", "glory", "immortal", "phoenix", "rebirth", "reincarnation", "reinvent", "renewal", "revival", "revive", "rise", "transform"],
|
||||
"🦇": ["animal", "nature", "blind", "vampire"],
|
||||
"🐺": ["animal", "nature", "wild"],
|
||||
"🐗": ["animal", "nature"],
|
||||
@@ -643,7 +575,6 @@
|
||||
"🌿": ["vegetable", "plant", "medicine", "weed", "grass", "lawn"],
|
||||
"☘": ["vegetable", "plant", "nature", "irish", "clover"],
|
||||
"🍀": ["vegetable", "plant", "nature", "lucky", "irish"],
|
||||
"": ["plant", "nature", "bare", "barren", "branches", "dead", "drought", "leafless", "tree", "trunk", "winter", "wood"],
|
||||
"🎍": ["plant", "nature", "vegetable", "panda", "pine_decoration"],
|
||||
"🎋": ["plant", "nature", "branch", "summer"],
|
||||
"🍃": ["nature", "plant", "tree", "vegetable", "grass", "lawn", "spring"],
|
||||
@@ -713,11 +644,10 @@
|
||||
"💧": ["water", "drip", "faucet", "spring"],
|
||||
"💦": ["water", "drip", "oops"],
|
||||
"🌊": ["sea", "water", "wave", "nature", "tsunami", "disaster"],
|
||||
"🪷": ["plant", "nature", "beauty", "buddhism", "calm", "flower", "hinduism", "lotus", "peace", "purity", "serenity"],
|
||||
"🪸": ["animal", "nature", "change", "climate", "coral", "ocean", "reef", "sea"],
|
||||
"🪹": ["plant", "nature", "branch", "empty", "home", "nest", "nesting"],
|
||||
"🪺": ["plant", "nature", "bird", "branch", "egg", "eggs", "nest", "nesting"],
|
||||
"🍋🟩": ["acidity", "citrus", "cocktail", "fruit", "garnish", "key", "lime", "margarita", "mojito", "refreshing", "salsa", "sour", "tangy", "tequila", "tropical", "zest"],
|
||||
"🪷": [],
|
||||
"🪸": [],
|
||||
"🪹": [],
|
||||
"🪺": [],
|
||||
"🍏": ["fruit", "nature"],
|
||||
"🍎": ["fruit", "mac", "school"],
|
||||
"🍐": ["fruit", "nature", "food"],
|
||||
@@ -737,8 +667,6 @@
|
||||
"🥑": ["fruit", "food"],
|
||||
"🫛": ["beans", "edamame", "legume", "pea", "pod", "vegetable", "food"],
|
||||
"🥦": ["fruit", "food", "vegetable"],
|
||||
"🍄🟫": ["food", "fungi", "fungus", "mushroom", "nature", "pizza", "portobello", "shiitake", "shroom", "spore", "sprout", "toppings", "truffle", "vegetable", "vegetarian", "veggie"],
|
||||
"": ["beet", "food", "garden", "radish", "root", "salad", "turnip", "vegetable", "vegetarian"],
|
||||
"🍅": ["fruit", "vegetable", "nature", "food"],
|
||||
"🍆": ["vegetable", "nature", "food", "aubergine"],
|
||||
"🥒": ["fruit", "food", "pickle"],
|
||||
@@ -831,7 +759,7 @@
|
||||
"🍵": ["drink", "bowl", "breakfast", "green", "british"],
|
||||
"🥤": ["drink", "soda"],
|
||||
"☕": ["beverage", "caffeine", "latte", "espresso"],
|
||||
"🫖": ["brew", "drink", "food", "pot", "tea", "teapot"],
|
||||
"🫖": [],
|
||||
"🧋": ["tapioca"],
|
||||
"🍼": ["food", "container", "milk"],
|
||||
"🧃": ["food", "drink"],
|
||||
@@ -844,9 +772,9 @@
|
||||
"🥣": ["food", "breakfast", "cereal", "oatmeal", "porridge"],
|
||||
"🥡": ["food", "leftovers"],
|
||||
"🥢": ["food"],
|
||||
"🫗": ["accident", "drink", "empty", "glass", "liquid", "oops", "pour", "pouring", "spill", "water"],
|
||||
"🫘": ["beans", "food", "kidney", "legume", "small"],
|
||||
"🫙": ["condiment", "container", "empty", "jar", "nothing", "sauce", "store"],
|
||||
"🫗": [],
|
||||
"🫘": [],
|
||||
"🫙": [],
|
||||
"⚽": ["sports", "football"],
|
||||
"🏀": ["sports", "balls", "NBA"],
|
||||
"🏈": ["sports", "balls", "NFL"],
|
||||
@@ -860,7 +788,6 @@
|
||||
"⛳": ["sports", "business", "flag", "hole", "summer"],
|
||||
"🏌️♀️": ["sports", "business", "woman", "female"],
|
||||
"🏌": ["sports", "business"],
|
||||
"🏌️♂️": ["sport", "ball", "birdie", "caddy", "driving", "golf", "golfing", "green", "man", "pga", "putt", "range", "tee"],
|
||||
"🏓": ["sports", "pingpong"],
|
||||
"🏸": ["sports"],
|
||||
"🥅": ["sports"],
|
||||
@@ -872,13 +799,10 @@
|
||||
"⛷": ["sports", "winter", "snow"],
|
||||
"🏂": ["sports", "winter"],
|
||||
"🤺": ["sports", "fencing", "sword"],
|
||||
"🤼": ["sport", "combat", "duel", "grapple", "people", "ring", "tournament", "wrestle", "wrestling"],
|
||||
"🤼♀️": ["sports", "wrestlers"],
|
||||
"🤼♂️": ["sports", "wrestlers"],
|
||||
"🤸": ["sport", "active", "cartwheel", "cartwheeling", "excited", "flip", "gymnastics", "happy", "person", "somersault"],
|
||||
"🤸♀️": ["gymnastics"],
|
||||
"🤸♂️": ["gymnastics"],
|
||||
"🤾": ["sport", "athletics", "ball", "catch", "chuck", "handball", "hurl", "lob", "person", "pitch", "playing", "throw", "toss"],
|
||||
"🤾♀️": ["sports"],
|
||||
"🤾♂️": ["sports"],
|
||||
"⛸": ["sports"],
|
||||
@@ -891,42 +815,32 @@
|
||||
"🥋": ["judo", "karate", "taekwondo"],
|
||||
"🚣♀️": ["sports", "hobby", "water", "ship", "woman", "female"],
|
||||
"🚣": ["sports", "hobby", "water", "ship"],
|
||||
"🚣♂️": ["sport", "boat", "canoe", "cruise", "fishing", "lake", "man", "oar", "paddle", "raft", "river", "row", "rowboat", "rowing"],
|
||||
"🧗": ["climb", "climber", "climbing", "mountain", "person", "rock", "scale", "up"],
|
||||
"🧗♀️": ["sports", "hobby", "woman", "female", "rock"],
|
||||
"🧗♂️": ["sports", "hobby", "man", "male", "rock"],
|
||||
"🏊♀️": ["sports", "exercise", "human", "athlete", "water", "summer", "woman", "female"],
|
||||
"🏊": ["sports", "exercise", "human", "athlete", "water", "summer"],
|
||||
"🏊♂️": ["sport", "freestyle", "man", "swim", "swimmer", "swimming", "triathlon"],
|
||||
"🤽": ["sport", "person", "playing", "polo", "swimming", "water", "waterpolo"],
|
||||
"🤽♀️": ["sports", "pool"],
|
||||
"🤽♂️": ["sports", "pool"],
|
||||
"🧘": ["cross", "legged", "legs", "lotus", "meditation", "peace", "person", "position", "relax", "serenity", "yoga", "yogi", "zen"],
|
||||
"🧘♀️": ["woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"],
|
||||
"🧘♂️": ["man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"],
|
||||
"🏄♀️": ["sports", "ocean", "sea", "summer", "beach", "woman", "female"],
|
||||
"🏄": ["sports", "ocean", "sea", "summer", "beach"],
|
||||
"🏄♂️": ["sport", "beach", "man", "ocean", "surf", "surfer", "surfing", "swell", "waves"],
|
||||
"🛀": ["clean", "shower", "bathroom"],
|
||||
"⛹️♀️": ["sports", "human", "woman", "female"],
|
||||
"⛹": ["sports", "human"],
|
||||
"⛹️♂️": ["sport", "athletic", "ball", "basketball", "bouncing", "championship", "dribble", "man", "net", "player", "throw"],
|
||||
"🏋️♀️": ["sports", "training", "exercise", "woman", "female"],
|
||||
"🏋": ["sports", "training", "exercise"],
|
||||
"🏋️♂️": ["sport", "barbell", "bodybuilder", "deadlift", "lifter", "lifting", "man", "powerlifting", "weight", "weightlifter", "weights", "workout"],
|
||||
"🚴♀️": ["sports", "bike", "exercise", "hipster", "woman", "female"],
|
||||
"🚴": ["sports", "bike", "exercise", "hipster"],
|
||||
"🚴♂️": ["sport", "bicycle", "bicyclist", "bike", "biking", "cycle", "cyclist", "man", "riding"],
|
||||
"🚵♀️": ["transportation", "sports", "human", "race", "bike", "woman", "female"],
|
||||
"🚵": ["transportation", "sports", "human", "race", "bike"],
|
||||
"🚵♂️": ["sport", "bicycle", "bicyclist", "bike", "biking", "cycle", "cyclist", "man", "mountain", "riding"],
|
||||
"🏇": ["animal", "betting", "competition", "gambling", "luck"],
|
||||
"🤿": ["sports"],
|
||||
"🪀": ["sports"],
|
||||
"🪁": ["sports"],
|
||||
"🦺": ["sports"],
|
||||
"🪡": ["embroidery", "needle", "sew", "sewing", "stitches", "sutures", "tailoring", "thread"],
|
||||
"🪢": ["cord", "knot", "rope", "tangled", "tie", "twine", "twist"],
|
||||
"🪡": [],
|
||||
"🪢": [],
|
||||
"🕴": ["suit", "business", "levitate", "hover", "jump"],
|
||||
"🏆": ["win", "award", "contest", "place", "ftw", "ceremony"],
|
||||
"🎽": ["play", "pageant"],
|
||||
@@ -942,7 +856,6 @@
|
||||
"🎭": ["acting", "theater", "drama"],
|
||||
"🎨": ["design", "paint", "draw", "colors"],
|
||||
"🎪": ["festival", "carnival", "party"],
|
||||
"🤹": ["sport", "act", "balance", "balancing", "handle", "juggle", "juggling", "manage", "multitask", "person", "skill"],
|
||||
"🤹♀️": ["juggle", "balance", "skill", "multitask"],
|
||||
"🤹♂️": ["juggle", "balance", "skill", "multitask"],
|
||||
"🎤": ["sound", "music", "PA", "sing", "talkshow"],
|
||||
@@ -959,7 +872,6 @@
|
||||
"🪕": ["music", "instrument"],
|
||||
"🪗": ["music", "instrument"],
|
||||
"🪘": ["music", "instrument"],
|
||||
"": ["cupid", "harp", "instrument", "love", "music", "orchestra"],
|
||||
"🎬": ["movie", "film", "record"],
|
||||
"🎮": ["play", "console", "PS4", "controller"],
|
||||
"👾": ["game", "arcade", "play"],
|
||||
@@ -969,11 +881,11 @@
|
||||
"🎰": ["bet", "gamble", "vegas", "fruit machine", "luck", "casino"],
|
||||
"🧩": ["interlocking", "puzzle", "piece"],
|
||||
"🎳": ["sports", "fun", "play"],
|
||||
"🪄": ["magic", "magician", "wand", "witch", "wizard"],
|
||||
"🪅": ["candy", "celebrate", "celebration", "cinco", "de", "festive", "mayo", "party", "pinada", "pinata", "piñata"],
|
||||
"🪆": ["babooshka", "baboushka", "babushka", "doll", "dolls", "matryoshka", "nesting", "russia"],
|
||||
"🪬": ["amulet", "fatima", "fortune", "guide", "hamsa", "hand", "mary", "miriam", "palm", "protect", "protection"],
|
||||
"🪩": ["ball", "dance", "disco", "glitter", "mirror", "party"],
|
||||
"🪄": [],
|
||||
"🪅": [],
|
||||
"🪆": [],
|
||||
"🪬": [],
|
||||
"🪩": [],
|
||||
"🚗": ["red", "transportation", "vehicle"],
|
||||
"🚕": ["uber", "vehicle", "cars", "transportation"],
|
||||
"🚙": ["transportation", "vehicle"],
|
||||
@@ -1029,7 +941,7 @@
|
||||
"🚀": ["launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"],
|
||||
"🛰": ["communication", "gps", "orbit", "spaceflight", "NASA", "ISS"],
|
||||
"🛻": ["car"],
|
||||
"🛼": ["blades", "roller", "skate", "skates", "sport"],
|
||||
"🛼": [],
|
||||
"💺": ["sit", "airplane", "transport", "bus", "flight", "fly"],
|
||||
"🛶": ["boat", "paddle", "water", "ship"],
|
||||
"⚓": ["ship", "ferry", "sea", "boat"],
|
||||
@@ -1101,12 +1013,12 @@
|
||||
"🕋": ["mecca", "mosque", "islam"],
|
||||
"⛩": ["temple", "japan", "kyoto"],
|
||||
"🛕": ["temple"],
|
||||
"🪨": ["boulder", "heavy", "rock", "solid", "stone", "tough"],
|
||||
"🪵": ["log", "lumber", "timber", "wood"],
|
||||
"🛖": ["home", "house", "hut", "roundhouse", "shelter", "yurt"],
|
||||
"🛝": ["amusement", "park", "play", "playground", "playing", "slide", "sliding", "theme"],
|
||||
"🛞": ["car", "circle", "tire", "turn", "vehicle", "wheel"],
|
||||
"🛟": ["buoy", "float", "life", "lifesaver", "preserver", "rescue", "ring", "safety", "save", "saver", "swim"],
|
||||
"🪨": [],
|
||||
"🪵": [],
|
||||
"🛖": [],
|
||||
"🛝": [],
|
||||
"🛞": [],
|
||||
"🛟": [],
|
||||
"⌚": ["time", "accessories"],
|
||||
"📱": ["technology", "apple", "gadgets", "dial"],
|
||||
"📲": ["iphone", "incoming"],
|
||||
@@ -1147,7 +1059,7 @@
|
||||
"⌛": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"],
|
||||
"📡": ["communication", "future", "radio", "space"],
|
||||
"🔋": ["power", "energy", "sustain"],
|
||||
"🪫": ["battery", "drained", "electronic", "energy", "low", "power"],
|
||||
"🪫": [],
|
||||
"🔌": ["charger", "power"],
|
||||
"💡": ["light", "electricity", "idea"],
|
||||
"🔦": ["dark", "camping", "sight", "night"],
|
||||
@@ -1163,11 +1075,9 @@
|
||||
"💰": ["dollar", "payment", "coins", "sale"],
|
||||
"🪙": ["dollar", "payment", "coins", "sale"],
|
||||
"💳": ["money", "sales", "dollar", "bill", "payment", "shopping"],
|
||||
"🪪": ["card", "credentials", "document", "id", "identification", "license", "security"],
|
||||
"🥾": ["backpacking", "boot", "brown", "camping", "hiking", "outdoors", "shoe"],
|
||||
"🪪": [],
|
||||
"💎": ["blue", "ruby", "diamond", "jewelry"],
|
||||
"⚖": ["law", "fairness", "weight"],
|
||||
"⛓️💥": ["break", "breaking", "broken", "chain", "cuffs", "freedom"],
|
||||
"🧰": ["tools", "diy", "fix", "maintainer", "mechanic"],
|
||||
"🔧": ["tools", "diy", "ikea", "fix", "maintainer"],
|
||||
"🔨": ["tools", "build", "create"],
|
||||
@@ -1183,7 +1093,6 @@
|
||||
"🪛": ["tool"],
|
||||
"🪝": ["tool"],
|
||||
"🪜": ["tool"],
|
||||
"": ["bury", "dig", "garden", "hole", "plant", "scoop", "shovel", "snow", "spade"],
|
||||
"🧱": ["bricks"],
|
||||
"⛓": ["lock", "arrest"],
|
||||
"🧲": ["attraction", "magnetic"],
|
||||
@@ -1214,8 +1123,8 @@
|
||||
"🩺": ["health", "hospital", "medicine", "needle", "doctor", "nurse"],
|
||||
"🪒": ["health"],
|
||||
"🪮": ["afro", "comb", "hair", "pick"],
|
||||
"🩻": ["bones", "doctor", "medical", "skeleton", "skull", "x-ray", "xray"],
|
||||
"🩼": ["aid", "cane", "crutch", "disability", "help", "hurt", "injured", "mobility", "stick"],
|
||||
"🩻": [],
|
||||
"🩼": [],
|
||||
"🧬": ["biologist", "genetics", "life"],
|
||||
"🧫": ["bacteria", "biology", "culture", "lab"],
|
||||
"🧪": ["chemistry", "experiment", "lab", "science"],
|
||||
@@ -1250,7 +1159,7 @@
|
||||
"🪤": ["household"],
|
||||
"🪣": ["household"],
|
||||
"🪥": ["household"],
|
||||
"🫧": ["bubble", "bubbles", "burp", "clean", "floating", "pearl", "soap", "underwater"],
|
||||
"🫧": [],
|
||||
"⛱": ["weather", "summer"],
|
||||
"🗿": ["rock", "easter island", "moai"],
|
||||
"🛍": ["mall", "buy", "purchase"],
|
||||
@@ -1340,8 +1249,8 @@
|
||||
"🖌": ["drawing", "creativity", "art"],
|
||||
"🔍": ["search", "zoom", "find", "detective"],
|
||||
"🔎": ["search", "zoom", "find", "detective"],
|
||||
"🪦": ["cemetery", "dead", "grave", "graveyard", "headstone", "memorial", "rip", "tomb", "tombstone"],
|
||||
"🪧": ["card", "demonstration", "notice", "picket", "placard", "plaque", "protest", "sign"],
|
||||
"🪦": [],
|
||||
"🪧": [],
|
||||
"💯": ["score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred"],
|
||||
"🔢": ["numbers", "blue-square"],
|
||||
"🩷": ["love", "like", "affection", "valentines"],
|
||||
@@ -1366,8 +1275,8 @@
|
||||
"💘": ["love", "like", "heart", "affection", "valentines"],
|
||||
"💝": ["love", "valentines"],
|
||||
"💟": ["purple-square", "love", "like"],
|
||||
"❤️🔥": ["burn", "fire", "heart", "love", "lust", "sacred"],
|
||||
"❤️🩹": ["healthier", "heart", "improving", "mending", "recovering", "recuperating", "well"],
|
||||
"❤️🔥": [],
|
||||
"❤️🩹": [],
|
||||
"☮": ["hippie"],
|
||||
"✝": ["christianity"],
|
||||
"☪": ["islam"],
|
||||
@@ -1395,8 +1304,6 @@
|
||||
"♓": ["purple-square", "sign", "zodiac", "astrology"],
|
||||
"🆔": ["purple-square", "words"],
|
||||
"⚛": ["science", "physics", "chemistry"],
|
||||
"♀️": ["female", "sign", "woman", "pink-square"],
|
||||
"♂️": ["male", "man", "sign", "blue-square"],
|
||||
"⚧️": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"],
|
||||
"🈳": ["kanji", "japanese", "chinese", "empty", "sky", "blue-square", "aki"],
|
||||
"🈹": ["cut", "divide", "chinese", "kanji", "pink-square", "waribiki"],
|
||||
@@ -1552,15 +1459,13 @@
|
||||
"➖": ["math", "calculation", "subtract", "less"],
|
||||
"➗": ["divide", "math", "calculation"],
|
||||
"✖️": ["math", "calculation"],
|
||||
"🟰": ["answer", "equal", "equality", "equals", "heavy", "math", "sign"],
|
||||
"🟰": [],
|
||||
"♾": ["forever"],
|
||||
"💲": ["money", "sales", "payment", "currency", "buck"],
|
||||
"💱": ["money", "sales", "dollar", "travel"],
|
||||
"⚕️": ["aesculapius", "medical", "medicine", "staff", "symbol"],
|
||||
"©️": ["ip", "license", "circle", "law", "legal"],
|
||||
"®️": ["alphabet", "circle"],
|
||||
"™️": ["trademark", "brand", "law", "legal"],
|
||||
"": ["drip", "holi", "ink", "liquid", "mess", "paint", "spill", "splatter", "stain"],
|
||||
"🔚": ["words", "arrow"],
|
||||
"🔙": ["arrow", "words", "return"],
|
||||
"🔛": ["arrow", "words"],
|
||||
@@ -1671,7 +1576,6 @@
|
||||
"🇧🇲": ["bm", "bermuda", "flag", "nation", "country", "banner"],
|
||||
"🇧🇹": ["bt", "bhutan", "flag", "nation", "country", "banner"],
|
||||
"🇧🇴": ["bo", "bolivia", "flag", "nation", "country", "banner"],
|
||||
"🇧🇻": ["bv", "bouvet", "island", "flag", "nation", "country", "banner"],
|
||||
"🇧🇶": ["bq", "bonaire", "flag", "nation", "country", "banner"],
|
||||
"🇧🇦": ["ba", "bosnia", "herzegovina", "flag", "nation", "country", "banner"],
|
||||
"🇧🇼": ["bw", "botswana", "flag", "nation", "country", "banner"],
|
||||
@@ -1689,12 +1593,10 @@
|
||||
"🇮🇨": ["ic", "canary", "islands", "flag", "nation", "country", "banner"],
|
||||
"🇰🇾": ["ky", "cayman", "islands", "flag", "nation", "country", "banner"],
|
||||
"🇨🇫": ["cf", "central", "african", "republic", "flag", "nation", "country", "banner"],
|
||||
"🇪🇦": ["ea", "ceuta", "melilla", "flag", "nation", "country", "banner"],
|
||||
"🇹🇩": ["td", "chad", "flag", "nation", "country", "banner"],
|
||||
"🇨🇱": ["cl", "chile", "flag", "nation", "country", "banner"],
|
||||
"🇨🇳": ["cn", "china", "chinese", "prc", "flag", "country", "nation", "banner"],
|
||||
"🇨🇽": ["cx", "christmas", "island", "flag", "nation", "country", "banner"],
|
||||
"🇨🇵": ["cp", "clipperton", "island", "flag", "nation", "country", "banner"],
|
||||
"🇨🇨": ["cc", "cocos", "keeling", "islands", "flag", "nation", "country", "banner"],
|
||||
"🇨🇴": ["co", "colombia", "flag", "nation", "country", "banner"],
|
||||
"🇰🇲": ["km", "comoros", "flag", "nation", "country", "banner"],
|
||||
@@ -1708,7 +1610,6 @@
|
||||
"🇨🇾": ["cy", "cyprus", "flag", "nation", "country", "banner"],
|
||||
"🇨🇿": ["cz", "czech", "republic", "flag", "nation", "country", "banner"],
|
||||
"🇩🇰": ["dk", "denmark", "flag", "nation", "country", "banner"],
|
||||
"🇩🇬": ["dg", "diego", "garcia", "flag", "nation", "country", "banner"],
|
||||
"🇩🇯": ["dj", "djibouti", "flag", "nation", "country", "banner"],
|
||||
"🇩🇲": ["dm", "dominica", "flag", "nation", "country", "banner"],
|
||||
"🇩🇴": ["do", "dominican", "republic", "flag", "nation", "country", "banner"],
|
||||
@@ -1745,7 +1646,6 @@
|
||||
"🇬🇼": ["gw", "guiana", "bissau", "flag", "nation", "country", "banner"],
|
||||
"🇬🇾": ["gy", "guyana", "flag", "nation", "country", "banner"],
|
||||
"🇭🇹": ["ht", "haiti", "flag", "nation", "country", "banner"],
|
||||
"🇭🇲": ["hm", "heard", "mcdonald", "islands", "flag", "nation", "country", "banner"],
|
||||
"🇭🇳": ["hn", "honduras", "flag", "nation", "country", "banner"],
|
||||
"🇭🇰": ["hk", "hong", "kong", "flag", "nation", "country", "banner"],
|
||||
"🇭🇺": ["hu", "hungary", "flag", "nation", "country", "banner"],
|
||||
@@ -1833,12 +1733,10 @@
|
||||
"🇷🇴": ["ro", "romania", "flag", "nation", "country", "banner"],
|
||||
"🇷🇺": ["ru", "russian", "federation", "flag", "nation", "country", "banner"],
|
||||
"🇷🇼": ["rw", "rwanda", "flag", "nation", "country", "banner"],
|
||||
"🇨🇶": ["cq", "sark", "flag", "nation", "country", "banner"],
|
||||
"🇧🇱": ["bl", "saint", "barthélemy", "flag", "nation", "country", "banner"],
|
||||
"🇸🇭": ["sh", "saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner"],
|
||||
"🇰🇳": ["kn", "saint", "kitts", "nevis", "flag", "nation", "country", "banner"],
|
||||
"🇱🇨": ["lc", "saint", "lucia", "flag", "nation", "country", "banner"],
|
||||
"🇲🇫": ["mf", "st", "martin", "flag", "nation", "country", "banner"],
|
||||
"🇵🇲": ["pm", "saint", "pierre", "miquelon", "flag", "nation", "country", "banner"],
|
||||
"🇻🇨": ["vc", "saint", "vincent", "grenadines", "flag", "nation", "country", "banner"],
|
||||
"🇼🇸": ["ws", "western", "samoa", "flag", "nation", "country", "banner"],
|
||||
@@ -1864,7 +1762,6 @@
|
||||
"🇸🇩": ["sd", "sudan", "flag", "nation", "country", "banner"],
|
||||
"🇸🇷": ["sr", "suriname", "flag", "nation", "country", "banner"],
|
||||
"🇸🇿": ["sz", "eswatini", "flag", "nation", "country", "banner"],
|
||||
"🇸🇯": ["sj", "svalbard", "jan", "mayen", "flag", "nation", "country", "banner"],
|
||||
"🇸🇪": ["se", "sweden", "flag", "nation", "country", "banner"],
|
||||
"🇨🇭": ["ch", "switzerland", "confoederatio", "helvetica", "flag", "nation", "country", "banner"],
|
||||
"🇸🇾": ["sy", "syrian", "arab", "republic", "flag", "nation", "country", "banner"],
|
||||
@@ -1891,7 +1788,6 @@
|
||||
"🏴": ["flag", "scottish"],
|
||||
"🏴": ["flag", "welsh"],
|
||||
"🇺🇸": ["us", "usa", "united", "states", "america", "flag", "nation", "country", "banner"],
|
||||
"🇺🇲": ["um", "us", "outlying", "islands", "flag", "nation", "country", "banner"],
|
||||
"🇻🇮": ["vi", "virgin", "islands", "us", "flag", "nation", "country", "banner"],
|
||||
"🇺🇾": ["uy", "uruguay", "flag", "nation", "country", "banner"],
|
||||
"🇺🇿": ["uz", "uzbekistan", "flag", "nation", "country", "banner"],
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
"🤡":["ピエロの顔","ピエロ","顔"],
|
||||
"😏":["にやにやした顔","顔","にやにや"],
|
||||
"😶":["口のない顔","顔","口","静かに","沈黙"],
|
||||
"🙂↔️":["いいえ","不賛成","顔","首を振る","首を横に振る"],
|
||||
"🙂↕️":["はい","頷く","顔","首を振る","首を縦に振る"],
|
||||
"":["クマ","徹夜","疲れた","眠い","顔"],
|
||||
"🫥":["点線の顔","落ち込んだ","消える","隠れる","内向的","目に見えない"],
|
||||
"😐":["普通の顔","無表情","顔","平静"],
|
||||
"🫤":["口が斜めになった顔","がっかり","無関心","疑い深い","不安"],
|
||||
@@ -114,38 +111,6 @@
|
||||
"💩":["うんち","マンガ","漫画","フン","顔","モンスター"],
|
||||
"👻":["お化け","妖怪","顔","おとぎ話","ファンタジー","幽霊","モンスター","ハロウィーン"],
|
||||
"💀":["ドクロ","体","死","顔","おとぎ話","モンスター","骸骨","ハロウィーン"],
|
||||
"":["指紋","鑑識","生体認証","セキュリティ"],
|
||||
"🏃➡️":["ジョギング","マラソン","ランナー","ランニング","右向き","急ぐ","走る","走る人","駆け足"],
|
||||
"🏃♀️➡️":["ジョギング","マラソン","ランナー","ランニング","右向き","女性","急ぐ","走る","走る女","駆け足"],
|
||||
"🏃♂️➡️":["ジョギング","マラソン","ランナー","ランニング","右向き","男性","走る男"],
|
||||
"🚶➡️":["ウォーキング","ハイキング","ぶらつく","人","右向き","大股","散歩","歩く","歩行","歩行者"],
|
||||
"🚶♀️➡️":["ウォーキング","ハイキング","右向き","女性","散歩","歩く","歩く女","歩行","歩行者"],
|
||||
"🚶♂️➡️":["ウォーキング","右向き","歩く男","歩行","歩行者","男性"],
|
||||
"🧎➡️":["ひざまずく","人","右向き","座る","正座する人"],
|
||||
"🧎♀️➡️":["ひざまずく","右向き","女性","座る","正座する女性"],
|
||||
"🧎♂️➡️":["ひざまずく","右向き","座る","正座する男性","男性"],
|
||||
"🧑🦯➡️":["アクセシビリティ","右向き","杖をついた人","目","視覚","障がい"],
|
||||
"👩🦯➡️":["アクセシビリティ","右向き","女性","杖をついた女性","白杖をついた女性","目","視覚","障がい"],
|
||||
"👨🦯➡️":["アクセシビリティ","右向き","杖をついた男性","男性","白杖をついた男性","目","視覚","障がい"],
|
||||
"🧑🦼➡️":["アクセシビリティ","右向き","車いす","障がい","電動車椅子の人"],
|
||||
"👩🦼➡️":["アクセシビリティ","右向き","女性","車いす","障がい","電動車椅子の女性"],
|
||||
"👨🦼➡️":["アクセシビリティ","右向き","男性","車いす","障がい","電動車椅子の男性"],
|
||||
"🧑🦽➡️":["アクセシビリティ","右向き","手動式車椅子の人","車いす","障がい"],
|
||||
"👩🦽➡️":["アクセシビリティ","右向き","女性","手動式車椅子の女性","車いす","障がい"],
|
||||
"👨🦽➡️":["アクセシビリティ","右向き","手動式車椅子の男性","男性","車いす","障がい"],
|
||||
"👨👩👦":["女性","子供","家族","男の子","男性","親子"],
|
||||
"🧑🧑🧒":["大人二人","子供一人","家族","大人二人と子供一人","親子"],
|
||||
"🧑🧑🧒🧒":["大人二人","子供二人","家族","大人二人と子供二人","親子"],
|
||||
"🧑🧒":["大人一人","子供一人","家族","大人一人と子供一人","親子"],
|
||||
"🧑🧒🧒":["大人一人","子供二人","家族","大人一人と子供二人","親子"],
|
||||
"🐦🔥":["ファンタジー","フェニックス","不死鳥","再生","復活","火の鳥","生まれ変わり","神話","転生","輪廻"],
|
||||
"":["不毛","干ばつ","葉のない木","冬"],
|
||||
"🍋🟩":["さわやか","トロピカル","フルーツ","マルガリータ","モヒート","ライム","果物","柑橘類","緑","酸っぱい"],
|
||||
"🍄🟫":["きのこ","キノコ","しいたけ","トリュフ","ブラウンマッシュルーム","ポートベロー","マッシュルーム","椎茸","茸","菌類"],
|
||||
"":["ビーツ","庭","根","カブ","野菜"],
|
||||
"":["オーケストラ","キューピッド","ハープ","弦楽器","愛","楽器","音楽"],
|
||||
"⛓️💥":["くさり","チェーン","壊れた","壊れた鎖"],
|
||||
"":["シャベル","ショベル","スコップ","掘る","穴","鋤"],
|
||||
"☠":["ドクロマーク","体","交差した骨","死","顔","モンスター","骸骨","ハロウィーン"],
|
||||
"👽":["宇宙人","怪獣","異星人","顔","おとぎ話","ファンタジー","モンスター","宇宙","UFO"],
|
||||
"🤖":["ロボットの顔","顔","モンスター","ロボット"],
|
||||
@@ -1553,7 +1518,6 @@
|
||||
"©️":["コピーライトマーク","著作権"],
|
||||
"®️":["登録商標マーク","登録済み","商標"],
|
||||
"™️":["商標マーク","マーク","tm","商標"],
|
||||
"":["しぶき","ペンキ","飛沫","飛び散り","スプラッシュ"],
|
||||
"🔚":["ENDと左矢印","矢印","端"],
|
||||
"🔙":["BACKと左矢印","矢印","戻る"],
|
||||
"🔛":["ON!と左右矢印","矢印","マーク","オン"],
|
||||
@@ -1679,7 +1643,6 @@
|
||||
"🇧🇷":["ブラジル国旗","ブラジル","国旗"],
|
||||
"🇧🇸":["バハマ国旗","バハマ","国旗"],
|
||||
"🇧🇹":["ブータン国旗","ブータン","国旗"],
|
||||
"🇧🇻":["ブーベ島の旗","ブーベ島","国旗"],
|
||||
"🇧🇼":["ボツワナ国旗","ボツワナ","国旗"],
|
||||
"🇧🇾":["ベラルーシ国旗","ベラルーシ","国旗"],
|
||||
"🇧🇿":["ベリーズ国旗","ベリーズ","国旗"],
|
||||
@@ -1695,8 +1658,6 @@
|
||||
"🇨🇲":["カメルーン国旗","カメルーン","国旗"],
|
||||
"🇨🇳":["中国国旗","中国","国旗"],
|
||||
"🇨🇴":["コロンビア国旗","コロンビア","国旗"],
|
||||
"🇨🇵":["クリッパートン島の旗","クリッパートン島","国旗"],
|
||||
"🇨🇶":["サーク島の旗", "サーク島", "国旗"],
|
||||
"🇨🇷":["コスタリカ国旗","コスタリカ","国旗"],
|
||||
"🇨🇺":["キューバ国旗","キューバ","国旗"],
|
||||
"🇨🇻":["カーボベルデ国旗","カーボ","ケープ","国旗","ベルデ"],
|
||||
@@ -1705,13 +1666,11 @@
|
||||
"🇨🇾":["キプロス国旗","キプロス","国旗"],
|
||||
"🇨🇿":["チェコ国旗","チェコ共和国","国旗"],
|
||||
"🇩🇪":["ドイツ国旗","国旗","ドイツ"],
|
||||
"🇩🇬":["ディエゴガルシア島の旗","ディエゴガルシア島","国旗"],
|
||||
"🇩🇯":["ジブチ国旗","ジブチ","国旗"],
|
||||
"🇩🇰":["デンマーク国旗","デンマーク","国旗"],
|
||||
"🇩🇲":["ドミニカ国旗","ドミニカ","国旗"],
|
||||
"🇩🇴":["ドミニカ共和国国旗","ドミニカ共和国","国旗"],
|
||||
"🇩🇿":["アルジェリア国旗","アルジェリア","国旗"],
|
||||
"🇪🇦":["セウタ・メリリャの旗","セウタ・メリリャ","国旗"],
|
||||
"🇪🇨":["エクアドル国旗","エクアドル","国旗"],
|
||||
"🏴":["イングランドの旗","イングランド","旗"],
|
||||
"🇪🇪":["エストニア国旗","エストニア","国旗"],
|
||||
@@ -1747,7 +1706,6 @@
|
||||
"🇬🇼":["ギニアビサウ国旗","ビサウ","国旗","ギニア"],
|
||||
"🇬🇾":["ガイアナ国旗","国旗","ガイアナ"],
|
||||
"🇭🇰":["香港の旗","中国","国旗","香港"],
|
||||
"🇭🇲":["ハード島・マクドナルド諸島の旗","ハード島・マクドナルド諸島","国旗"],
|
||||
"🇭🇳":["ホンジュラス国旗","国旗","ホンジュラス"],
|
||||
"🇭🇷":["クロアチア国旗","クロアチア","国旗"],
|
||||
"🇭🇹":["ハイチ国旗","国旗","ハイチ"],
|
||||
@@ -1793,7 +1751,6 @@
|
||||
"🇲🇨":["モナコ国旗","国旗","モナコ"],
|
||||
"🇲🇩":["モルドバ国旗","国旗","モルドバ"],
|
||||
"🇲🇪":["モンテネグロ国旗","国旗","モンテネグロ"],
|
||||
"🇲🇫":["サン・マルタンの旗","サン・マルタン","国旗"],
|
||||
"🇲🇬":["マダガスカル国旗","国旗","マダガスカル"],
|
||||
"🇲🇭":["マーシャル諸島国旗","国旗","諸島","マーシャル"],
|
||||
"🇲🇰":["マケドニア国旗","国旗","マケドニア"],
|
||||
@@ -1854,7 +1811,6 @@
|
||||
"🇸🇬":["シンガポール国旗","国旗","シンガポール"],
|
||||
"🇸🇭":["セントヘレナ島の旗","旗","ヘレナ","セント"],
|
||||
"🇸🇮":["スロベニア国旗","国旗","スロベニア"],
|
||||
"🇸🇯":["スバールバル諸島・ヤンマイエン島の旗","スバールバル諸島・ヤンマイエン島","国旗"],
|
||||
"🇸🇰":["スロバキア国旗","国旗","スロバキア"],
|
||||
"🇸🇱":["シエラレオネ国旗","国旗","シエラレオネ"],
|
||||
"🇸🇲":["サンマリノ国旗","国旗","サンマリノ"],
|
||||
@@ -1886,7 +1842,6 @@
|
||||
"🇹🇿":["タンザニア国旗","国旗","タンザニア"],
|
||||
"🇺🇦":["ウクライナ国旗","国旗","ウクライナ"],
|
||||
"🇺🇬":["ウガンダ国旗","国旗","ウガンダ"],
|
||||
"🇺🇲":["合衆国領有小離島の旗","合衆国領有小離島","国旗"],
|
||||
"🇺🇳":["国連の旗","旗","国連","連合","国際"],
|
||||
"🇺🇸":["アメリカ国旗","アメリカ","旗","合衆","合衆国","アメリカ合衆国","合衆国領有小離島"],
|
||||
"🇺🇾":["ウルグアイ国旗","国旗","ウルグアイ"],
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
"🤡": ["ぴえろのかお","ぴえろ","かお"],
|
||||
"😏": ["にやにやしたかお","かお","にやにや"],
|
||||
"😶": ["くちのないかお","かお","くち","しずかに","ちんもく"],
|
||||
"🙂↔️": ["いいえ","ふさんせい","かお","くびをふる","くびをよこにふる"],
|
||||
"🙂↕️": ["はい","うなずく","かお","くびをふる","くびをたてにふる"],
|
||||
"": ["くま","てつや","つかれた","ねむい","かお"],
|
||||
"🫥": ["てんせんのかお","おちこんだ","きえる","かくれる","ないこうてき","めにみえない"],
|
||||
"😐": ["ふつうのかお","むひょうじょう","かお","へいせい"],
|
||||
"🫤": ["くちがななめになったかお","がっかり","むかんしん","うたがいぶかい","ふあん"],
|
||||
@@ -114,38 +111,6 @@
|
||||
"💩": ["うんち","まんが","ふん","かお","もんすたー"],
|
||||
"👻": ["おばけ","ようかい","かお","おとぎばなし","ふぁんたじー","ゆうれい","もんすたー","はろうぃーん"],
|
||||
"💀": ["どくろ","からだ","し","かお","おとぎばなし","もんすたー","がいこつ","はろうぃーん"],
|
||||
"": ["しもん","かんしき","せいたいにんしょう","せきゅりてぃ"],
|
||||
"🏃➡️": ["じょぎんぐ","まらそん","らんなー","らんにんぐ","みぎむき","いそぐ","はしる","はしるひと","かけあし"],
|
||||
"🏃♀️➡️": ["じょぎんぐ","まらそん","らんなー","らんにんぐ","みぎむき","じょせい","いそぐ","はしる","はしるおんな","かけあし"],
|
||||
"🏃♂️➡️": ["じょぎんぐ","まらそん","らんなー","らんにんぐ","みぎむき","だんせい","はしるおとこ"],
|
||||
"🚶➡️": ["うぉーきんぐ","はいきんぐ","ぶらつく","ひと","みぎむき","おおまた","さんぽ","あるく","ほこう","ほこうしゃ"],
|
||||
"🚶♀️➡️": ["うぉーきんぐ","はいきんぐ","みぎむき","じょせい","さんぽ","あるく","あるくおんな","ほこう","ほこうしゃ"],
|
||||
"🚶♂️➡️": ["うぉーきんぐ","みぎむき","あるくおとこ","ほこう","ほこうしゃ","だんせい"],
|
||||
"🧎➡️": ["ひざまずく","ひと","みぎむき","すわる","せいざするひと"],
|
||||
"🧎♀️➡️": ["ひざまずく","みぎむき","じょせい","すわる","せいざするじょせい"],
|
||||
"🧎♂️➡️": ["ひざまずく","みぎむき","すわる","せいざするだんせい","だんせい"],
|
||||
"🧑🦯➡️": ["あくせしびりてぃ","みぎむき","つえをついたひと","め","しかく","しょうがい"],
|
||||
"👩🦯➡️": ["あくせしびりてぃ","みぎむき","じょせい","つえをついたじょせい","はくじょうをついたじょせい","め","しかく","しょうがい"],
|
||||
"👨🦯➡️": ["あくせしびりてぃ","みぎむき","つえをついただんせい","だんせい","はくじょうをついただんせい","め","しかく","しょうがい"],
|
||||
"🧑🦼➡️": ["あくせしびりてぃ","みぎむき","くるまいす","しょうがい","でんどうくるまいすのひと"],
|
||||
"👩🦼➡️": ["あくせしびりてぃ","みぎむき","じょせい","くるまいす","しょうがい","でんどうくるまいすのじょせい"],
|
||||
"👨🦼➡️": ["あくせしびりてぃ","みぎむき","だんせい","くるまいす","しょうがい","でんどうくるまいすのだんせい"],
|
||||
"🧑🦽➡️": ["あくせしびりてぃ","みぎむき","しゅどうしきくるまいすのひと","くるまいす","しょうがい"],
|
||||
"👩🦽➡️": ["あくせしびりてぃ","みぎむき","じょせい","しゅどうしきくるまいすのじょせい","くるまいす","しょうがい"],
|
||||
"👨🦽➡️": ["あくせしびりてぃ","みぎむき","しゅどうしきくるまいすのだんせい","だんせい","くるまいす","しょうがい"],
|
||||
"👨👩👦": ["じょせい","こども","かぞく","おとこのこ","だんせい","おやこ"],
|
||||
"🧑🧑🧒": ["おとなふたり","こどもひとり","かぞく","おとなふたりとこどもひとり","おやこ"],
|
||||
"🧑🧑🧒🧒": ["おとなふたり","こどもふたり","かぞく","おとなふたりとこどもふたり","おやこ"],
|
||||
"🧑🧒": ["おとなひとり","こどもひとり","かぞく","おとなひとりとこどもひとり","おやこ"],
|
||||
"🧑🧒🧒": ["おとなひとり","こどもふたり","かぞく","おとなひとりとこどもふたり","おやこ"],
|
||||
"🐦🔥": ["ふぁんたじー","ふぇにっくす","ふしちょう","さいせい","ふっかつ","ひのとり","うまれかわり","しんわ","てんせい","りんね"],
|
||||
"": ["ふもう","かんばつ","はのないき","ふゆ"],
|
||||
"🍋🟩": ["さわやか","とろぴかる","ふるーつ","まるがりーた","もひーと","らいむ","くだもの","かんきつるい","みどり","すっぱい"],
|
||||
"🍄🟫": ["きのこ","しいたけ","とりゅふ","ぶらうんまっしゅるーむ","ぽーとべろー","まっしゅるーむ","たけ","きんるい"],
|
||||
"": ["びーつ","にわ","ね","かぶ","やさい"],
|
||||
"": ["おーけすとら","きゅーぴっど","はーぷ","げんがっき","あい","がっき","おんがく"],
|
||||
"⛓️💥": ["くさり","ちぇーん","こわれた","こわれたくさり"],
|
||||
"": ["しゃべる","しょべる","すこっぷ","ほる","あな","すき"],
|
||||
"☠": ["どくろまーく","からだ","こうさしたほね","し","かお","もんすたー","がいこつ","はろうぃーん"],
|
||||
"👽": ["うちゅうじん","かいじゅう","いせいじん","かお","おとぎばなし","ふぁんたじー","もんすたー","うちゅう","UFO"],
|
||||
"🤖": ["ろぼっとのかお","かお","もんすたー","ろぼっと"],
|
||||
@@ -417,9 +382,9 @@
|
||||
"🚶♀️": ["あるくじょせい","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ","じょせい","おんな"],
|
||||
"🚶": ["あるくひと","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ"],
|
||||
"🚶♂️": ["あるくだんせい","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ","おとこ","だんせい"],
|
||||
"👩🦯": ["はくじょうをもったじょせい","あくせしびりてぃ","めがふじゆう","じょせい","おんな"],
|
||||
"🧑🦯": ["はくじょうをもったひと","あくせしびりてぃ","めがふじゆう"],
|
||||
"👨🦯": ["はくじょうをもっただんせい","あくせしびりてぃ","めがふじゆう","だんせい","おとこ"],
|
||||
"👩🦯": ["しろつえをもったじょせい","あくせしびりてぃ","めがふじゆう","じょせい","おんな"],
|
||||
"🧑🦯": ["しろつえをもったひと","あくせしびりてぃ","めがふじゆう"],
|
||||
"👨🦯": ["しろつえをもっただんせい","あくせしびりてぃ","めがふじゆう","だんせい","おとこ"],
|
||||
"🏃♀️": ["はしるじょせい","まらそん","らんなー","らんにんぐ","じょせい","おんな"],
|
||||
"🏃": ["はしるひと","まらそん","らんなー","らんにんぐ"],
|
||||
"🏃♂️": ["はしるだんせい","まらそん","らんなー","らんにんぐ","おとこ","だんせい"],
|
||||
@@ -1164,7 +1129,7 @@
|
||||
"🧾": ["りょうしゅうしょ","かいけい","ぼき","しょうこ","しょうめい"],
|
||||
"💎": ["ほうせき","だいあもんど","じゅえる","ろまんす"],
|
||||
"⚖": ["はかり","てんびん","こうせい","てんびんざ","ものさし","どうぐ","じゅうりょう","せいざ"],
|
||||
"🦯": ["はくじょう","あくせしびりてぃ","めがふじゆう"],
|
||||
"🦯": ["しろつえ","あくせしびりてぃ","めがふじゆう"],
|
||||
"🧰": ["どうぐばこ","むね","せいびし","こうぐ"],
|
||||
"🔧": ["れんち","どうぐ"],
|
||||
"🪛": ["どらいばー","ねじ","こうぐ"],
|
||||
@@ -1553,7 +1518,6 @@
|
||||
"©️": ["こぴーらいとまーく","ちょさくけん"],
|
||||
"®️": ["とうろくしょうひょうまーく","とうろくずみ","しょうひょう"],
|
||||
"™️": ["しょうひょうまーく","まーく","tm","しょうひょう"],
|
||||
"": ["しぶき","ぺんき","ひまつ","とびちり","すぷらっしゅ"],
|
||||
"🔚": ["ENDとひだりやじるし","やじるし","はじ"],
|
||||
"🔙": ["BACKとひだりやじるし","やじるし","もどる"],
|
||||
"🔛": ["ON!とさゆうやじるし","やじるし","まーく","おん"],
|
||||
@@ -1679,7 +1643,6 @@
|
||||
"🇧🇷": ["ぶらじるこっき","ぶらじる","こっき"],
|
||||
"🇧🇸": ["ばはまこっき","ばはま","こっき"],
|
||||
"🇧🇹": ["ぶーたんこっき","ぶーたん","こっき"],
|
||||
"🇧🇻": ["ぶーべとうのはた","ぶーべとう","こっき"],
|
||||
"🇧🇼": ["ぼつわなこっき","ぼつわな","こっき"],
|
||||
"🇧🇾": ["べらるーしこっき","べらるーし","こっき"],
|
||||
"🇧🇿": ["べりーずこっき","べりーず","こっき"],
|
||||
@@ -1695,8 +1658,6 @@
|
||||
"🇨🇲": ["かめるーんこっき","かめるーん","こっき"],
|
||||
"🇨🇳": ["ちゅうごくこっき","ちゅうごく","こっき"],
|
||||
"🇨🇴": ["ころんびあこっき","ころんびあ","こっき"],
|
||||
"🇨🇵": ["くりっぱーとんとうのはた","くりっぱーとんとう","こっき"],
|
||||
"🇨🇶": ["さーくとうのはた", "さーくとう", "こっき"],
|
||||
"🇨🇷": ["こすたりかこっき","こすたりか","こっき"],
|
||||
"🇨🇺": ["きゅーばこっき","きゅーば","こっき"],
|
||||
"🇨🇻": ["かーぼべるでこっき","かーぼ","けーぷ","こっき","べるで"],
|
||||
@@ -1705,13 +1666,11 @@
|
||||
"🇨🇾": ["きぷろすこっき","きぷろす","こっき"],
|
||||
"🇨🇿": ["ちぇここっき","ちぇこきょうわこく","こっき"],
|
||||
"🇩🇪": ["どいつこっき","こっき","どいつ"],
|
||||
"🇩🇬": ["でぃえごがるしあとうのはた","でぃえごがるしあとう","こっき"],
|
||||
"🇩🇯": ["じぶちこっき","じぶち","こっき"],
|
||||
"🇩🇰": ["でんまーくこっき","でんまーく","こっき"],
|
||||
"🇩🇲": ["どみにかこっき","どみにか","こっき"],
|
||||
"🇩🇴": ["どみにかきょうわこくこっき","どみにかきょうわこく","こっき"],
|
||||
"🇩🇿": ["あるじぇりあこっき","あるじぇりあ","こっき"],
|
||||
"🇪🇦": ["せうた・めりりゃのはた","せうた・めりりゃ","こっき"],
|
||||
"🇪🇨": ["えくあどるこっき","えくあどる","こっき"],
|
||||
"🏴": ["いんぐらんどのはた","いんぐらんど","こっき"],
|
||||
"🇪🇪": ["えすとにあこっき","えすとにあ","こっき"],
|
||||
@@ -1747,7 +1706,6 @@
|
||||
"🇬🇼": ["ぎにあびさうこっき","びさう","こっき","ぎにあ"],
|
||||
"🇬🇾": ["がいあなこっき","こっき","がいあな"],
|
||||
"🇭🇰": ["ほんこんのはた","ちゅうごく","こっき","ほんこん"],
|
||||
"🇭🇲": ["はーどとう・まくどなるどしょとうのはた","はーどとう・まくどなるどしょとう","こっき"],
|
||||
"🇭🇳": ["ほんじゅらすこっき","こっき","ほんじゅらす"],
|
||||
"🇭🇷": ["くろあちあこっき","くろあちあ","こっき"],
|
||||
"🇭🇹": ["はいちこっき","こっき","はいち"],
|
||||
@@ -1793,7 +1751,6 @@
|
||||
"🇲🇨": ["もなここっき","こっき","もなこ"],
|
||||
"🇲🇩": ["もるどばこっき","こっき","もるどば"],
|
||||
"🇲🇪": ["もんてねぐろこっき","こっき","もんてねぐろ"],
|
||||
"🇲🇫": ["さん・まるたんのはた","さん・まるたん","こっき"],
|
||||
"🇲🇬": ["まだがすかるこっき","こっき","まだがすかる"],
|
||||
"🇲🇭": ["まーしゃるしょとうこっき","こっき","しょとう","まーしゃる"],
|
||||
"🇲🇰": ["まけどにあこっき","こっき","まけどにあ"],
|
||||
@@ -1854,7 +1811,6 @@
|
||||
"🇸🇬": ["しんがぽーるこっき","こっき","しんがぽーる"],
|
||||
"🇸🇭": ["せんとへれなとうのはた","はた","へれな","せんと"],
|
||||
"🇸🇮": ["すろべにあこっき","こっき","すろべにあ"],
|
||||
"🇸🇯": ["すばーるばるしょとう・やんまいえんとうのはた","すばーるばるしょとう・やんまいえんとう","こっき"],
|
||||
"🇸🇰": ["すろばきあこっき","こっき","すろばきあ"],
|
||||
"🇸🇱": ["しえられおねこっき","こっき","しえられおね"],
|
||||
"🇸🇲": ["さんまりのこっき","こっき","さんまりの"],
|
||||
@@ -1886,7 +1842,6 @@
|
||||
"🇹🇿": ["たんざにあこっき","こっき","たんざにあ"],
|
||||
"🇺🇦": ["うくらいなこっき","こっき","うくらいな"],
|
||||
"🇺🇬": ["うがんだこっき","こっき","うがんだ"],
|
||||
"🇺🇲": ["がっしゅうこくりょうゆうしょうりとうのはた","がっしゅうこくりょうゆうしょうりとう","こっき"],
|
||||
"🇺🇳": ["こくれんのはた","はた","こくれん","れんごう","こくさい"],
|
||||
"🇺🇸": ["あめりかこっき","あめりか","はた","ごうしゅう","がっしゅうこく","あめりかがっしゅうこく","がっしゅうこくりょうゆうしょうりとう"],
|
||||
"🇺🇾": ["うるぐあいこっき","こっき","うるぐあい"],
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { GeneratedSearchIndexItem } from 'search-index';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
texts: string[];
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
export function genSearchIndexes(generated: GeneratedSearchIndexItem[]): SearchIndexItem[] {
|
||||
const rootMods = new Map(generated.map(item => [item.id, item]));
|
||||
|
||||
// link inlining here
|
||||
for (const item of generated) {
|
||||
if (item.inlining) {
|
||||
for (const id of item.inlining) {
|
||||
const inline = rootMods.get(id);
|
||||
if (inline) {
|
||||
inline.parentId = item.id;
|
||||
inline.path = item.path;
|
||||
} else {
|
||||
console.log('[Settings Search Index] Failed to inline', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { unisonReload } from '@/utility/unison-reload.js';
|
||||
|
||||
let isReloadConfirming = false;
|
||||
|
||||
export async function reloadAsk(opts: {
|
||||
unison?: boolean;
|
||||
reason?: string;
|
||||
}) {
|
||||
if (isReloadConfirming) {
|
||||
return;
|
||||
}
|
||||
|
||||
isReloadConfirming = true;
|
||||
|
||||
const { canceled } = await os.confirm(opts.reason == null ? {
|
||||
type: 'info',
|
||||
text: i18n.ts.reloadConfirm,
|
||||
} : {
|
||||
type: 'info',
|
||||
title: i18n.ts.reloadConfirm,
|
||||
text: opts.reason,
|
||||
}).finally(() => {
|
||||
isReloadConfirming = false;
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
if (opts.unison) {
|
||||
unisonReload();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const shouldSuggestReload = ref(false);
|
||||
|
||||
export function suggestReload() {
|
||||
shouldSuggestReload.value = true;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { searchIndexes as generated } from 'search-index:settings';
|
||||
import type { GeneratedSearchIndexItem } from 'search-index:settings';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
const rootMods = new Map(generated.map(item => [item.id, item]));
|
||||
|
||||
// link inlining here
|
||||
for (const item of generated) {
|
||||
if (item.inlining) {
|
||||
for (const id of item.inlining) {
|
||||
const inline = rootMods.get(id);
|
||||
if (inline) {
|
||||
inline.parentId = item.id;
|
||||
inline.path = item.path;
|
||||
} else {
|
||||
console.log('[Settings Search Index] Failed to inline', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchIndexes: SearchIndexItem[] = generated;
|
||||
|
||||
+10
-19
@@ -3,25 +3,16 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
type XGeneratedSearchIndexItem = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
texts: string[];
|
||||
icon?: string;
|
||||
inlining?: string[];
|
||||
};
|
||||
|
||||
declare module 'search-index' {
|
||||
export type GeneratedSearchIndexItem = XGeneratedSearchIndexItem;
|
||||
}
|
||||
|
||||
declare module 'search-index:settings' {
|
||||
export const searchIndexes: XGeneratedSearchIndexItem[];
|
||||
}
|
||||
export type GeneratedSearchIndexItem = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string[];
|
||||
icon?: string;
|
||||
inlining?: string[];
|
||||
};
|
||||
|
||||
declare module 'search-index:admin' {
|
||||
export const searchIndexes: XGeneratedSearchIndexItem[];
|
||||
export const searchIndexes: GeneratedSearchIndexItem[];
|
||||
}
|
||||
|
||||
@@ -28,11 +28,6 @@ export const searchIndexes = [{
|
||||
mainVirtualModule: 'search-index:settings',
|
||||
modulesToHmrOnUpdate: ['src/pages/settings/index.vue'],
|
||||
verbose: process.env.FRONTEND_SEARCH_INDEX_VERBOSE === 'true',
|
||||
}, {
|
||||
targetFilePaths: ['src/pages/admin/*.vue'],
|
||||
mainVirtualModule: 'search-index:admin',
|
||||
modulesToHmrOnUpdate: ['src/pages/admin/index.vue'],
|
||||
verbose: process.env.FRONTEND_SEARCH_INDEX_VERBOSE === 'true',
|
||||
}] satisfies SearchIndexOptions[];
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.17.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-webfont": "3.34.1",
|
||||
"harfbuzzjs": "0.4.8",
|
||||
"@tabler/icons-webfont": "3.34.0",
|
||||
"harfbuzzjs": "0.4.7",
|
||||
"tiny-glob": "0.2.9",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "5.9.2",
|
||||
"typescript": "5.8.3",
|
||||
"wawoff2": "2.0.1"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.19.8",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/node": "22.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"nodemon": "3.1.10",
|
||||
"execa": "9.6.0",
|
||||
"typescript": "5.9.2",
|
||||
"esbuild": "0.25.8",
|
||||
"typescript": "5.8.3",
|
||||
"esbuild": "0.25.6",
|
||||
"glob": "11.0.3"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://swc.rs/schema.json",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"dynamicImport": true,
|
||||
"decorators": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"experimental": {
|
||||
"keepImportAssertions": true
|
||||
},
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@/*": ["*"]
|
||||
},
|
||||
"target": "es2022"
|
||||
},
|
||||
"minify": false
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export default [
|
||||
'**/node_modules',
|
||||
'built',
|
||||
'coverage',
|
||||
'vitest.config.ts',
|
||||
'jest.config.ts',
|
||||
'test',
|
||||
'test-d',
|
||||
'generator',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "5.0.0",
|
||||
"@readme/openapi-parser": "2.7.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'assert';
|
||||
import { mkdir, readFile, writeFile } from 'fs/promises';
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
import { toPascal } from 'ts-case-convert';
|
||||
import { parse } from '@readme/openapi-parser';
|
||||
import OpenAPIParser from '@readme/openapi-parser';
|
||||
import openapiTS, { astToString } from 'openapi-typescript';
|
||||
import type { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
|
||||
import ts from 'typescript';
|
||||
@@ -401,7 +401,7 @@ async function main() {
|
||||
await mkdir(generatePath, { recursive: true });
|
||||
|
||||
const openApiJsonPath = './api.json';
|
||||
const openApiDocs = await parse(openApiJsonPath) as OpenAPIV3_1.Document;
|
||||
const openApiDocs = await OpenAPIParser.parse(openApiJsonPath) as OpenAPIV3_1.Document;
|
||||
|
||||
const typeFileName = './built/autogen/types.ts';
|
||||
await generateBaseTypes(openApiDocs, openApiJsonPath, typeFileName);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user