Merge remote-tracking branch 'upstream/develop' into merge-upstream
This commit is contained in:
commit
f3fd0cd93e
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -12,6 +12,17 @@
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 13.12.2
|
||||||
|
|
||||||
|
### General
|
||||||
|
- 投稿したコンテンツのAIによる学習を軽減するオプションを追加
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- センシティブワードの登録にAnd、正規表現が使用できるようになりました。
|
||||||
|
|
||||||
## 13.12.1
|
## 13.12.1
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
|
@ -1038,8 +1038,11 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
|
||||||
displayOfNote: "Anzeige von Notizen"
|
displayOfNote: "Anzeige von Notizen"
|
||||||
initialAccountSetting: "Kontoeinrichtung"
|
initialAccountSetting: "Kontoeinrichtung"
|
||||||
youFollowing: "Gefolgt"
|
youFollowing: "Gefolgt"
|
||||||
|
preventAiLarning: "Verwendung in machinellem Lernen (AI/KI) ablehnen"
|
||||||
|
preventAiLarningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (AI/KI) zu verwenden. Dies wird durch das Hinzufügen eines \"noai\"-HTML-Tags an den jeweiligen Inhalt erreicht. Da dieser Tag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
|
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
|
||||||
|
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
|
||||||
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
|
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
|
||||||
profileSetting: "Profileinstellungen"
|
profileSetting: "Profileinstellungen"
|
||||||
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
|
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
|
||||||
|
|
|
@ -1036,20 +1036,23 @@ channelArchiveConfirmTitle: "Really archive {name}?"
|
||||||
channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
|
channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
|
||||||
thisChannelArchived: "This channel has been archived."
|
thisChannelArchived: "This channel has been archived."
|
||||||
displayOfNote: "Note display"
|
displayOfNote: "Note display"
|
||||||
initialAccountSetting: "Profile configuration"
|
initialAccountSetting: "Profile setup"
|
||||||
youFollowing: "Followed"
|
youFollowing: "Followed"
|
||||||
|
preventAiLarning: "Reject usage in Machine Learning (AI)"
|
||||||
|
preventAiLarningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (AI) data sets. This is achieved by adding a \"noai\" HTML-Tag to the respective content. A complete prevention can however not be achieved through this tag, as it may simply be ignored."
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "Your account was successfully created!"
|
accountCreated: "Your account was successfully created!"
|
||||||
|
letsStartAccountSetup: "For starters, let's set up your profile."
|
||||||
letsFillYourProfile: "First, let's set up your profile."
|
letsFillYourProfile: "First, let's set up your profile."
|
||||||
profileSetting: "Profile settings"
|
profileSetting: "Profile settings"
|
||||||
theseSettingsCanEditLater: "You can always change these settings later."
|
theseSettingsCanEditLater: "You can always change these settings later."
|
||||||
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
|
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
|
||||||
followUsers: "Try following some users that interest you to build up your timeline."
|
followUsers: "Try following some users that interest you to build up your timeline."
|
||||||
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
|
pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
|
||||||
initialAccountSettingCompleted: "Profile configuration complete!"
|
initialAccountSettingCompleted: "Profile setup complete!"
|
||||||
haveFun: "Enjoy {name}!"
|
haveFun: "Enjoy {name}!"
|
||||||
ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
|
ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
|
||||||
skipAreYouSure: "Really skip profile configuration?"
|
skipAreYouSure: "Really skip profile setup?"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
|
|
|
@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
|
||||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||||
sensitiveWords: "センシティブワード"
|
sensitiveWords: "センシティブワード"
|
||||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||||
|
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
unfavoriteConfirm: "お気に入り解除しますか?"
|
unfavoriteConfirm: "お気に入り解除しますか?"
|
||||||
|
@ -1038,6 +1039,8 @@ thisChannelArchived: "このチャンネルはアーカイブされています
|
||||||
displayOfNote: "ノートの表示"
|
displayOfNote: "ノートの表示"
|
||||||
initialAccountSetting: "初期設定"
|
initialAccountSetting: "初期設定"
|
||||||
youFollowing: "フォロー中"
|
youFollowing: "フォロー中"
|
||||||
|
preventAiLarning: "AIによる学習を拒否"
|
||||||
|
preventAiLarningDescription: "投稿したノートや画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。この機能は実験的であり、AIによる学習を完全に防止するものではありません。"
|
||||||
|
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
accountCreated: "アカウントの作成が完了しました!"
|
accountCreated: "アカウントの作成が完了しました!"
|
||||||
|
|
|
@ -1240,6 +1240,7 @@ _achievements:
|
||||||
title: "잠깐 쉬어"
|
title: "잠깐 쉬어"
|
||||||
description: "클라이언트를 시작하고 30분이 경과하였습니다"
|
description: "클라이언트를 시작하고 30분이 경과하였습니다"
|
||||||
_client60min:
|
_client60min:
|
||||||
|
title: "No \"Miss\" in Misskey"
|
||||||
description: "클라이언트를 시작하고 60분이 경과하였습니다"
|
description: "클라이언트를 시작하고 60분이 경과하였습니다"
|
||||||
_noteDeletedWithin1min:
|
_noteDeletedWithin1min:
|
||||||
title: "있었는데요 없었습니다"
|
title: "있었는데요 없었습니다"
|
||||||
|
|
|
@ -1036,7 +1036,21 @@ channelArchiveConfirmTitle: "要封存{name}嗎?"
|
||||||
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
|
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
|
||||||
thisChannelArchived: "這個頻道已被封存。"
|
thisChannelArchived: "這個頻道已被封存。"
|
||||||
displayOfNote: "顯示貼文"
|
displayOfNote: "顯示貼文"
|
||||||
youFollowing: "關注中"
|
initialAccountSetting: "初始設定"
|
||||||
|
youFollowing: "追隨中"
|
||||||
|
_initialAccountSetting:
|
||||||
|
accountCreated: "帳戶已建立完成!"
|
||||||
|
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
|
||||||
|
letsFillYourProfile: "首先,來設定您的個人檔案吧。"
|
||||||
|
profileSetting: "個人檔案設定"
|
||||||
|
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
|
||||||
|
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
|
||||||
|
followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
|
||||||
|
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
|
||||||
|
initialAccountSettingCompleted: "初始設定完成了!"
|
||||||
|
haveFun: "盡情享受{name}吧!"
|
||||||
|
ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
|
||||||
|
skipAreYouSure: "要略過初始設定嗎?"
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
|
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
|
@ -1464,7 +1478,7 @@ _channel:
|
||||||
removeBanner: "移除橫幅圖像"
|
removeBanner: "移除橫幅圖像"
|
||||||
featured: "熱門貼文"
|
featured: "熱門貼文"
|
||||||
owned: "管理中"
|
owned: "管理中"
|
||||||
following: "關注中"
|
following: "追隨中"
|
||||||
usersCount: "有{n}人參與"
|
usersCount: "有{n}人參與"
|
||||||
notesCount: "有{n}個貼文"
|
notesCount: "有{n}個貼文"
|
||||||
nameAndDescription: "名稱與說明"
|
nameAndDescription: "名稱與說明"
|
||||||
|
@ -1586,6 +1600,16 @@ _time:
|
||||||
minute: "分鐘"
|
minute: "分鐘"
|
||||||
hour: "小時"
|
hour: "小時"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
_timelineTutorial:
|
||||||
|
title: "Misskey的使用方法"
|
||||||
|
step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」按照時間順序顯示。"
|
||||||
|
step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。"
|
||||||
|
step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。"
|
||||||
|
step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。"
|
||||||
|
step3_1: "貼文發出去了嗎?"
|
||||||
|
step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
|
||||||
|
step4_1: "可以對貼文標記「反應」。"
|
||||||
|
step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。"
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "此設備已經被註冊過了"
|
alreadyRegistered: "此設備已經被註冊過了"
|
||||||
registerTOTP: "開始設定驗證應用程式"
|
registerTOTP: "開始設定驗證應用程式"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "13.12.1",
|
"version": "13.12.2",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class PreventAiLarning1683682889948 {
|
||||||
|
name = 'PreventAiLarning1683682889948'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLarning" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "preventAiLarning"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class PublicReactionsDefaultTrue1683683083083 {
|
||||||
|
name = 'PublicReactionsDefaultTrue1683683083083'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT false`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,10 +18,12 @@ export async function server() {
|
||||||
const serverService = app.get(ServerService);
|
const serverService = app.get(ServerService);
|
||||||
await serverService.launch();
|
await serverService.launch();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
app.get(ChartManagementService).start();
|
app.get(ChartManagementService).start();
|
||||||
app.get(JanitorService).start();
|
app.get(JanitorService).start();
|
||||||
app.get(QueueStatsService).start();
|
app.get(QueueStatsService).start();
|
||||||
app.get(ServerStatsService).start();
|
app.get(ServerStatsService).start();
|
||||||
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
|
||||||
import { In, DataSource } from 'typeorm';
|
import { In, DataSource } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import RE2 from 're2';
|
||||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||||
|
@ -238,7 +239,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (data.channel != null) data.localOnly = true;
|
if (data.channel != null) data.localOnly = true;
|
||||||
|
|
||||||
if (data.visibility === 'public' && data.channel == null) {
|
if (data.visibility === 'public' && data.channel == null) {
|
||||||
if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
|
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
|
||||||
|
if (this.isSensitive(data, sensitiveWords)) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
@ -671,6 +673,31 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
this.index(note);
|
this.index(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
|
||||||
|
if (sensitiveWord.length > 0) {
|
||||||
|
const text = note.cw ?? note.text ?? '';
|
||||||
|
if (text === '') return false;
|
||||||
|
const matched = sensitiveWord.some(filter => {
|
||||||
|
// represents RegExp
|
||||||
|
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||||
|
// This should never happen due to input sanitisation.
|
||||||
|
if (!regexp) {
|
||||||
|
const words = filter.split(' ');
|
||||||
|
return words.every(keyword => text.includes(keyword));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new RE2(regexp[1], regexp[2]).test(text);
|
||||||
|
} catch (err) {
|
||||||
|
// This should never happen due to input sanitisation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (matched) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private incRenoteCount(renote: Note) {
|
private incRenoteCount(renote: Note) {
|
||||||
this.notesRepository.createQueryBuilder().update()
|
this.notesRepository.createQueryBuilder().update()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
|
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import Bull from 'bull';
|
import Bull from 'bull';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
@ -41,9 +42,9 @@ export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||||
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
export type DeliverQueue = Bull.Queue<DeliverJobData>;
|
||||||
export type InboxQueue = Bull.Queue<InboxJobData>;
|
export type InboxQueue = Bull.Queue<InboxJobData>;
|
||||||
export type DbQueue = Bull.Queue<DbJobData<keyof DbJobMap>>;
|
export type DbQueue = Bull.Queue;
|
||||||
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
||||||
export type ObjectStorageQueue = Bull.Queue<ObjectStorageJobData>;
|
export type ObjectStorageQueue = Bull.Queue;
|
||||||
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||||
|
|
||||||
const $system: Provider = {
|
const $system: Provider = {
|
||||||
|
@ -118,4 +119,36 @@ const $webhookDeliver: Provider = {
|
||||||
$webhookDeliver,
|
$webhookDeliver,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class QueueModule {}
|
export class QueueModule implements OnApplicationShutdown {
|
||||||
|
constructor(
|
||||||
|
@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:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
// XXX:
|
||||||
|
// Shutting down the existing connections causes errors on Jest as
|
||||||
|
// Misskey has asynchronous postgres/redis connections that are not
|
||||||
|
// awaited.
|
||||||
|
// Let's wait for some random time for them to finish.
|
||||||
|
await setTimeout(5000);
|
||||||
|
}
|
||||||
|
await Promise.all([
|
||||||
|
this.systemQueue.close(),
|
||||||
|
this.endedPollNotificationQueue.close(),
|
||||||
|
this.deliverQueue.close(),
|
||||||
|
this.inboxQueue.close(),
|
||||||
|
this.dbQueue.close(),
|
||||||
|
this.relationshipQueue.close(),
|
||||||
|
this.objectStorageQueue.close(),
|
||||||
|
this.webhookDeliverQueue.close(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -445,6 +445,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
carefulBot: profile!.carefulBot,
|
carefulBot: profile!.carefulBot,
|
||||||
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
||||||
noCrawle: profile!.noCrawle,
|
noCrawle: profile!.noCrawle,
|
||||||
|
preventAiLarning: profile!.preventAiLarning,
|
||||||
isExplorable: user.isExplorable,
|
isExplorable: user.isExplorable,
|
||||||
isDeleted: user.isDeleted,
|
isDeleted: user.isDeleted,
|
||||||
hideOnlineStatus: user.hideOnlineStatus,
|
hideOnlineStatus: user.hideOnlineStatus,
|
||||||
|
|
|
@ -76,7 +76,7 @@ export class UserProfile {
|
||||||
public emailNotificationTypes: string[];
|
public emailNotificationTypes: string[];
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: true,
|
||||||
})
|
})
|
||||||
public publicReactions: boolean;
|
public publicReactions: boolean;
|
||||||
|
|
||||||
|
@ -147,6 +147,11 @@ export class UserProfile {
|
||||||
})
|
})
|
||||||
public noCrawle: boolean;
|
public noCrawle: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public preventAiLarning: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -302,7 +302,11 @@ export const packedMeDetailedOnlySchema = {
|
||||||
},
|
},
|
||||||
noCrawle: {
|
noCrawle: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: true, optional: false,
|
nullable: false, optional: false,
|
||||||
|
},
|
||||||
|
preventAiLarning: {
|
||||||
|
type: 'boolean',
|
||||||
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
isExplorable: {
|
isExplorable: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
|
||||||
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
|
|
||||||
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
|
|
||||||
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
|
|
||||||
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
|
|
||||||
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
|
|
||||||
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
|
|
||||||
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
|
|
||||||
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
|
|
||||||
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
|
|
||||||
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
|
|
||||||
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
|
|
||||||
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
|
|
||||||
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
|
|
||||||
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
|
|
||||||
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DbQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
|
||||||
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
|
|
||||||
private exportNotesProcessorService: ExportNotesProcessorService,
|
|
||||||
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
|
|
||||||
private exportFollowingProcessorService: ExportFollowingProcessorService,
|
|
||||||
private exportMutingProcessorService: ExportMutingProcessorService,
|
|
||||||
private exportBlockingProcessorService: ExportBlockingProcessorService,
|
|
||||||
private exportUserListsProcessorService: ExportUserListsProcessorService,
|
|
||||||
private exportAntennasProcessorService: ExportAntennasProcessorService,
|
|
||||||
private importFollowingProcessorService: ImportFollowingProcessorService,
|
|
||||||
private importMutingProcessorService: ImportMutingProcessorService,
|
|
||||||
private importBlockingProcessorService: ImportBlockingProcessorService,
|
|
||||||
private importUserListsProcessorService: ImportUserListsProcessorService,
|
|
||||||
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
|
|
||||||
private importAntennasProcessorService: ImportAntennasProcessorService,
|
|
||||||
private deleteAccountProcessorService: DeleteAccountProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
|
|
||||||
q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
|
|
||||||
q.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
|
|
||||||
q.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
|
|
||||||
q.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
|
|
||||||
q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
|
|
||||||
q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
|
|
||||||
q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
|
|
||||||
q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
|
|
||||||
q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
|
|
||||||
q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
|
|
||||||
q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
|
|
||||||
q.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
|
|
||||||
q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
|
|
||||||
q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
|
|
||||||
q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
|
|
||||||
q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
|
|
||||||
q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
|
||||||
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ObjectStorageQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private deleteFileProcessorService: DeleteFileProcessorService,
|
|
||||||
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
|
|
||||||
q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,14 +3,10 @@ import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
import { QueueProcessorService } from './QueueProcessorService.js';
|
import { QueueProcessorService } from './QueueProcessorService.js';
|
||||||
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
|
|
||||||
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
|
|
||||||
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
|
|
||||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||||
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
|
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
|
@ -68,10 +64,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
||||||
DeleteFileProcessorService,
|
DeleteFileProcessorService,
|
||||||
CleanRemoteFilesProcessorService,
|
CleanRemoteFilesProcessorService,
|
||||||
RelationshipProcessorService,
|
RelationshipProcessorService,
|
||||||
SystemQueueProcessorsService,
|
|
||||||
ObjectStorageQueueProcessorsService,
|
|
||||||
DbQueueProcessorsService,
|
|
||||||
RelationshipQueueProcessorsService,
|
|
||||||
WebhookDeliverProcessorService,
|
WebhookDeliverProcessorService,
|
||||||
EndedPollNotificationProcessorService,
|
EndedPollNotificationProcessorService,
|
||||||
DeliverProcessorService,
|
DeliverProcessorService,
|
||||||
|
|
|
@ -5,15 +5,36 @@ import type Logger from '@/logger.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { getJobInfo } from './get-job-info.js';
|
import { getJobInfo } from './get-job-info.js';
|
||||||
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
|
|
||||||
import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
|
|
||||||
import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
|
|
||||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||||
|
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
|
||||||
|
import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
|
||||||
|
import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
|
||||||
|
import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
|
||||||
|
import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
|
||||||
|
import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
|
||||||
|
import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
|
||||||
|
import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
|
||||||
|
import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
|
||||||
|
import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
|
||||||
|
import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
|
||||||
|
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
|
||||||
|
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
|
||||||
|
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
|
||||||
|
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
|
||||||
|
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
|
||||||
|
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
|
||||||
|
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
|
||||||
|
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
|
||||||
|
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
|
||||||
|
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
||||||
|
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||||
|
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||||
|
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||||
|
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
||||||
import { QueueLoggerService } from './QueueLoggerService.js';
|
import { QueueLoggerService } from './QueueLoggerService.js';
|
||||||
import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueProcessorService {
|
export class QueueProcessorService {
|
||||||
|
@ -25,14 +46,35 @@ export class QueueProcessorService {
|
||||||
|
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private systemQueueProcessorsService: SystemQueueProcessorsService,
|
|
||||||
private objectStorageQueueProcessorsService: ObjectStorageQueueProcessorsService,
|
|
||||||
private dbQueueProcessorsService: DbQueueProcessorsService,
|
|
||||||
private relationshipQueueProcessorsService: RelationshipQueueProcessorsService,
|
|
||||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||||
private deliverProcessorService: DeliverProcessorService,
|
private deliverProcessorService: DeliverProcessorService,
|
||||||
private inboxProcessorService: InboxProcessorService,
|
private inboxProcessorService: InboxProcessorService,
|
||||||
|
private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
|
||||||
|
private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
|
||||||
|
private exportNotesProcessorService: ExportNotesProcessorService,
|
||||||
|
private exportFavoritesProcessorService: ExportFavoritesProcessorService,
|
||||||
|
private exportFollowingProcessorService: ExportFollowingProcessorService,
|
||||||
|
private exportMutingProcessorService: ExportMutingProcessorService,
|
||||||
|
private exportBlockingProcessorService: ExportBlockingProcessorService,
|
||||||
|
private exportUserListsProcessorService: ExportUserListsProcessorService,
|
||||||
|
private exportAntennasProcessorService: ExportAntennasProcessorService,
|
||||||
|
private importFollowingProcessorService: ImportFollowingProcessorService,
|
||||||
|
private importMutingProcessorService: ImportMutingProcessorService,
|
||||||
|
private importBlockingProcessorService: ImportBlockingProcessorService,
|
||||||
|
private importUserListsProcessorService: ImportUserListsProcessorService,
|
||||||
|
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
|
||||||
|
private importAntennasProcessorService: ImportAntennasProcessorService,
|
||||||
|
private deleteAccountProcessorService: DeleteAccountProcessorService,
|
||||||
|
private deleteFileProcessorService: DeleteFileProcessorService,
|
||||||
|
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
|
||||||
|
private relationshipProcessorService: RelationshipProcessorService,
|
||||||
|
private tickChartsProcessorService: TickChartsProcessorService,
|
||||||
|
private resyncChartsProcessorService: ResyncChartsProcessorService,
|
||||||
|
private cleanChartsProcessorService: CleanChartsProcessorService,
|
||||||
|
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
||||||
|
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
||||||
|
private cleanProcessorService: CleanProcessorService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger;
|
this.logger = this.queueLoggerService.logger;
|
||||||
}
|
}
|
||||||
|
@ -119,14 +161,6 @@ export class QueueProcessorService {
|
||||||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||||
|
|
||||||
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
|
|
||||||
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
|
|
||||||
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
|
|
||||||
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
|
|
||||||
this.dbQueueProcessorsService.start(this.queueService.dbQueue);
|
|
||||||
this.relationshipQueueProcessorsService.start(this.queueService.relationshipQueue);
|
|
||||||
this.objectStorageQueueProcessorsService.start(this.queueService.objectStorageQueue);
|
|
||||||
|
|
||||||
this.queueService.systemQueue.add('tickCharts', {
|
this.queueService.systemQueue.add('tickCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { cron: '55 * * * *' },
|
repeat: { cron: '55 * * * *' },
|
||||||
|
@ -163,6 +197,46 @@ export class QueueProcessorService {
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.systemQueueProcessorsService.start(this.queueService.systemQueue);
|
this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
|
||||||
|
this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
|
||||||
|
this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
|
||||||
|
this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
|
||||||
|
|
||||||
|
this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
|
||||||
|
this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
|
||||||
|
this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
|
||||||
|
this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
|
||||||
|
|
||||||
|
this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
|
||||||
|
this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
|
||||||
|
|
||||||
|
{
|
||||||
|
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
||||||
|
this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
||||||
|
this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
||||||
|
this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
||||||
|
this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
||||||
|
this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RelationshipQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private relationshipProcessorService: RelationshipProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
|
|
||||||
q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
|
|
||||||
q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
|
|
||||||
q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
|
|
||||||
q.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
|
|
||||||
import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
|
|
||||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
|
||||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
|
||||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
|
||||||
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
|
|
||||||
import type Bull from 'bull';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SystemQueueProcessorsService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.config)
|
|
||||||
private config: Config,
|
|
||||||
|
|
||||||
private tickChartsProcessorService: TickChartsProcessorService,
|
|
||||||
private resyncChartsProcessorService: ResyncChartsProcessorService,
|
|
||||||
private cleanChartsProcessorService: CleanChartsProcessorService,
|
|
||||||
private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
|
|
||||||
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
|
|
||||||
private cleanProcessorService: CleanProcessorService,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public start(q: Bull.Queue): void {
|
|
||||||
q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
|
|
||||||
q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
|
|
||||||
q.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
|
|
||||||
q.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
|
|
||||||
q.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
|
|
||||||
q.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -68,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
emailVerified: profile.emailVerified,
|
emailVerified: profile.emailVerified,
|
||||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||||
noCrawle: profile.noCrawle,
|
noCrawle: profile.noCrawle,
|
||||||
|
preventAiLarning: profile.preventAiLarning,
|
||||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||||
autoSensitive: profile.autoSensitive,
|
autoSensitive: profile.autoSensitive,
|
||||||
carefulBot: profile.carefulBot,
|
carefulBot: profile.carefulBot,
|
||||||
|
|
|
@ -98,7 +98,7 @@ export const meta = {
|
||||||
message: 'This feature is restricted by your role.',
|
message: 'This feature is restricted by your role.',
|
||||||
code: 'RESTRICTED_BY_ROLE',
|
code: 'RESTRICTED_BY_ROLE',
|
||||||
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
@ -138,6 +138,7 @@ export const paramDef = {
|
||||||
carefulBot: { type: 'boolean' },
|
carefulBot: { type: 'boolean' },
|
||||||
autoAcceptFollowed: { type: 'boolean' },
|
autoAcceptFollowed: { type: 'boolean' },
|
||||||
noCrawle: { type: 'boolean' },
|
noCrawle: { type: 'boolean' },
|
||||||
|
preventAiLarning: { type: 'boolean' },
|
||||||
isBot: { type: 'boolean' },
|
isBot: { type: 'boolean' },
|
||||||
isCat: { type: 'boolean' },
|
isCat: { type: 'boolean' },
|
||||||
showTimelineReplies: { type: 'boolean' },
|
showTimelineReplies: { type: 'boolean' },
|
||||||
|
@ -242,6 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
||||||
|
if (typeof ps.preventAiLarning === 'boolean') profileUpdates.preventAiLarning = ps.preventAiLarning;
|
||||||
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
||||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||||
|
|
|
@ -36,8 +36,8 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import manifest from './manifest.json' assert { type: 'json' };
|
import manifest from './manifest.json' assert { type: 'json' };
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
@ -437,6 +437,10 @@ export class ClientServerService {
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('user', {
|
return await reply.view('user', {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||||
|
@ -481,6 +485,10 @@ export class ClientServerService {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('note', {
|
return await reply.view('note', {
|
||||||
note: _note,
|
note: _note,
|
||||||
profile,
|
profile,
|
||||||
|
@ -520,6 +528,10 @@ export class ClientServerService {
|
||||||
} else {
|
} else {
|
||||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
}
|
}
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('page', {
|
return await reply.view('page', {
|
||||||
page: _page,
|
page: _page,
|
||||||
profile,
|
profile,
|
||||||
|
@ -544,6 +556,10 @@ export class ClientServerService {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('flash', {
|
return await reply.view('flash', {
|
||||||
flash: _flash,
|
flash: _flash,
|
||||||
profile,
|
profile,
|
||||||
|
@ -568,6 +584,10 @@ export class ClientServerService {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('clip', {
|
return await reply.view('clip', {
|
||||||
clip: _clip,
|
clip: _clip,
|
||||||
profile,
|
profile,
|
||||||
|
@ -590,6 +610,10 @@ export class ClientServerService {
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
reply.header('Cache-Control', 'public, max-age=15');
|
reply.header('Cache-Control', 'public, max-age=15');
|
||||||
|
if (profile.preventAiLarning) {
|
||||||
|
reply.header('X-Robots-Tag', 'noimageai');
|
||||||
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
|
}
|
||||||
return await reply.view('gallery-post', {
|
return await reply.view('gallery-post', {
|
||||||
post: _post,
|
post: _post,
|
||||||
profile,
|
profile,
|
||||||
|
|
|
@ -21,6 +21,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -21,6 +21,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -21,6 +21,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if user.host || profile.noCrawle
|
if user.host || profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -22,6 +22,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if user.host || isRenote || profile.noCrawle
|
if user.host || isRenote || profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -21,6 +21,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -20,6 +20,9 @@ block og
|
||||||
block meta
|
block meta
|
||||||
if user.host || profile.noCrawle
|
if user.host || profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
if profile.preventAiLarning
|
||||||
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='robots' content='noai')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -541,6 +541,61 @@ describe('Note', () => {
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('センシティブな投稿はhomeになる (単語指定)', async () => {
|
||||||
|
const sensitive = await api('admin/update-meta', {
|
||||||
|
sensitiveWords: [
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(sensitive.status, 204);
|
||||||
|
|
||||||
|
await new Promise(x => setTimeout(x, 2));
|
||||||
|
|
||||||
|
const note1 = await api('/notes/create', {
|
||||||
|
text: 'hogetesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note1.status, 200);
|
||||||
|
assert.strictEqual(note1.body.createdNote.visibility, 'home');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('センシティブな投稿はhomeになる (正規表現)', async () => {
|
||||||
|
const sensitive = await api('admin/update-meta', {
|
||||||
|
sensitiveWords: [
|
||||||
|
"/Test/i",
|
||||||
|
]
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(sensitive.status, 204);
|
||||||
|
|
||||||
|
const note2 = await api('/notes/create', {
|
||||||
|
text: 'hogetesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note2.status, 200);
|
||||||
|
assert.strictEqual(note2.body.createdNote.visibility, 'home');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('センシティブな投稿はhomeになる (スペースアンド)', async () => {
|
||||||
|
const sensitive = await api('admin/update-meta', {
|
||||||
|
sensitiveWords: [
|
||||||
|
"Test hoge"
|
||||||
|
]
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(sensitive.status, 204);
|
||||||
|
|
||||||
|
const note2 = await api('/notes/create', {
|
||||||
|
text: 'hogeTesthuge',
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(note2.status, 200);
|
||||||
|
assert.strictEqual(note2.body.createdNote.visibility, 'home');
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('notes/delete', () => {
|
describe('notes/delete', () => {
|
||||||
|
|
|
@ -145,6 +145,7 @@ describe('ユーザー', () => {
|
||||||
carefulBot: user.carefulBot,
|
carefulBot: user.carefulBot,
|
||||||
autoAcceptFollowed: user.autoAcceptFollowed,
|
autoAcceptFollowed: user.autoAcceptFollowed,
|
||||||
noCrawle: user.noCrawle,
|
noCrawle: user.noCrawle,
|
||||||
|
preventAiLarning: user.preventAiLarning,
|
||||||
isExplorable: user.isExplorable,
|
isExplorable: user.isExplorable,
|
||||||
isDeleted: user.isDeleted,
|
isDeleted: user.isDeleted,
|
||||||
hideOnlineStatus: user.hideOnlineStatus,
|
hideOnlineStatus: user.hideOnlineStatus,
|
||||||
|
@ -370,7 +371,7 @@ describe('ユーザー', () => {
|
||||||
assert.deepStrictEqual(response.pinnedNotes, []);
|
assert.deepStrictEqual(response.pinnedNotes, []);
|
||||||
assert.strictEqual(response.pinnedPageId, null);
|
assert.strictEqual(response.pinnedPageId, null);
|
||||||
assert.strictEqual(response.pinnedPage, null);
|
assert.strictEqual(response.pinnedPage, null);
|
||||||
assert.strictEqual(response.publicReactions, false);
|
assert.strictEqual(response.publicReactions, true);
|
||||||
assert.strictEqual(response.ffVisibility, 'public');
|
assert.strictEqual(response.ffVisibility, 'public');
|
||||||
assert.strictEqual(response.twoFactorEnabled, false);
|
assert.strictEqual(response.twoFactorEnabled, false);
|
||||||
assert.strictEqual(response.usePasswordLessLogin, false);
|
assert.strictEqual(response.usePasswordLessLogin, false);
|
||||||
|
@ -390,6 +391,7 @@ describe('ユーザー', () => {
|
||||||
assert.strictEqual(response.carefulBot, false);
|
assert.strictEqual(response.carefulBot, false);
|
||||||
assert.strictEqual(response.autoAcceptFollowed, true);
|
assert.strictEqual(response.autoAcceptFollowed, true);
|
||||||
assert.strictEqual(response.noCrawle, false);
|
assert.strictEqual(response.noCrawle, false);
|
||||||
|
assert.strictEqual(response.preventAiLarning, true);
|
||||||
assert.strictEqual(response.isExplorable, true);
|
assert.strictEqual(response.isExplorable, true);
|
||||||
assert.strictEqual(response.isDeleted, false);
|
assert.strictEqual(response.isDeleted, false);
|
||||||
assert.strictEqual(response.hideOnlineStatus, false);
|
assert.strictEqual(response.hideOnlineStatus, false);
|
||||||
|
@ -462,6 +464,8 @@ describe('ユーザー', () => {
|
||||||
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
||||||
{ parameters: (): object => ({ noCrawle: true }) },
|
{ parameters: (): object => ({ noCrawle: true }) },
|
||||||
{ parameters: (): object => ({ noCrawle: false }) },
|
{ parameters: (): object => ({ noCrawle: false }) },
|
||||||
|
{ parameters: (): object => ({ preventAiLarning: false }) },
|
||||||
|
{ parameters: (): object => ({ preventAiLarning: true }) },
|
||||||
{ parameters: (): object => ({ isBot: true }) },
|
{ parameters: (): object => ({ isBot: true }) },
|
||||||
{ parameters: (): object => ({ isBot: false }) },
|
{ parameters: (): object => ({ isBot: false }) },
|
||||||
{ parameters: (): object => ({ isCat: true }) },
|
{ parameters: (): object => ({ isCat: true }) },
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="ziffeoms"
|
|
||||||
:class="{ disabled, checked }"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
ref="input"
|
|
||||||
type="checkbox"
|
|
||||||
:disabled="disabled"
|
|
||||||
@keydown.enter="toggle"
|
|
||||||
>
|
|
||||||
<span ref="button" v-adaptive-border v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle">
|
|
||||||
<i class="check ti ti-check"></i>
|
|
||||||
</span>
|
|
||||||
<span class="label">
|
|
||||||
<!-- TODO: 無名slotの方は廃止 -->
|
|
||||||
<span @click="toggle"><slot name="label"></slot><slot></slot></span>
|
|
||||||
<p class="caption"><slot name="caption"></slot></p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { toRefs, Ref } from 'vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
|
||||||
import { i18n } from '@/i18n';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue: boolean | Ref<boolean>;
|
|
||||||
disabled?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'update:modelValue', v: boolean): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let button = $shallowRef<HTMLElement>();
|
|
||||||
const checked = toRefs(props).modelValue;
|
|
||||||
const toggle = () => {
|
|
||||||
if (props.disabled) return;
|
|
||||||
emit('update:modelValue', !checked.value);
|
|
||||||
|
|
||||||
if (!checked.value) {
|
|
||||||
const rect = button.getBoundingClientRect();
|
|
||||||
const x = rect.left + (button.offsetWidth / 2);
|
|
||||||
const y = rect.top + (button.offsetHeight / 2);
|
|
||||||
os.popup(MkRippleEffect, { x, y, particle: false }, {}, 'end');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ziffeoms {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> input {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 23px;
|
|
||||||
height: 23px;
|
|
||||||
outline: none;
|
|
||||||
background: var(--panel);
|
|
||||||
border: solid 1px var(--panel);
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: inherit;
|
|
||||||
|
|
||||||
> .check {
|
|
||||||
margin: auto;
|
|
||||||
opacity: 0;
|
|
||||||
color: var(--fgOnAccent);
|
|
||||||
font-size: 13px;
|
|
||||||
transform: scale(0.5);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
> .button {
|
|
||||||
border-color: var(--inputBorderHover) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .label {
|
|
||||||
margin-left: 12px;
|
|
||||||
margin-top: 2px;
|
|
||||||
display: block;
|
|
||||||
transition: inherit;
|
|
||||||
color: var(--fg);
|
|
||||||
|
|
||||||
> span {
|
|
||||||
display: block;
|
|
||||||
line-height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .caption {
|
|
||||||
margin: 8px 0 0 0;
|
|
||||||
color: var(--fgTransparentWeak);
|
|
||||||
font-size: 0.85em;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
> .button {
|
|
||||||
background-color: var(--accent) !important;
|
|
||||||
border-color: var(--accent) !important;
|
|
||||||
|
|
||||||
> .check {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
|
||||||
<div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown">
|
<div ref="rootEl" :class="$style.root" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown">
|
||||||
<div ref="headerEl" class="header">
|
<div ref="headerEl" :class="$style.header">
|
||||||
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
|
<button v-if="withOkButton" :class="$style.headerButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
|
||||||
<span class="title">
|
<span :class="$style.title">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</span>
|
</span>
|
||||||
<button v-if="!withOkButton" class="_button" data-cy-modal-window-close @click="$emit('close')"><i class="ti ti-x"></i></button>
|
<button v-if="!withOkButton" :class="$style.headerButton" class="_button" data-cy-modal-window-close @click="$emit('close')"><i class="ti ti-x"></i></button>
|
||||||
<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></i></button>
|
<button v-if="withOkButton" :class="$style.headerButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div :class="$style.body">
|
||||||
<slot :width="bodyWidth" :height="bodyHeight"></slot>
|
<slot :width="bodyWidth" :height="bodyHeight"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,8 +81,8 @@ defineExpose({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.ebkgoccj {
|
.root {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -96,28 +96,31 @@ defineExpose({
|
||||||
--root-margin: 16px;
|
--root-margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .header {
|
--headerHeight: 46px;
|
||||||
$height: 46px;
|
--headerHeightNarrow: 42px;
|
||||||
$height-narrow: 42px;
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: var(--windowHeader);
|
background: var(--windowHeader);
|
||||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||||
backdrop-filter: var(--blur, blur(15px));
|
backdrop-filter: var(--blur, blur(15px));
|
||||||
|
}
|
||||||
|
|
||||||
> button {
|
.headerButton {
|
||||||
height: $height;
|
height: var(--headerHeight);
|
||||||
width: $height;
|
width: var(--headerHeight);
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
height: $height-narrow;
|
height: var(--headerHeightNarrow);
|
||||||
width: $height-narrow;
|
width: var(--headerHeightNarrow);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .title {
|
.title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
line-height: $height;
|
line-height: var(--headerHeight);
|
||||||
padding-left: 32px;
|
padding-left: 32px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -126,21 +129,19 @@ defineExpose({
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
line-height: $height-narrow;
|
line-height: var(--headerHeightNarrow);
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> button + .title {
|
.headerButton + .title {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
.body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
container-type: size;
|
container-type: size;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-adaptive-border
|
v-adaptive-border
|
||||||
class="novjtctn"
|
:class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]"
|
||||||
:class="{ disabled, checked }"
|
|
||||||
:aria-checked="checked"
|
:aria-checked="checked"
|
||||||
:aria-disabled="disabled"
|
:aria-disabled="disabled"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
|
@ -10,11 +9,12 @@
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:class="$style.input"
|
||||||
>
|
>
|
||||||
<span class="button">
|
<span :class="$style.button">
|
||||||
<span></span>
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="label"><slot></slot></span>
|
<span :class="$style.label"><slot></slot></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ function toggle(): void {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.novjtctn {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -53,18 +53,12 @@ function toggle(): void {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
> * {
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
||||||
&, * {
|
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--inputBorderHover) !important;
|
border-color: var(--inputBorderHover) !important;
|
||||||
|
@ -74,10 +68,7 @@ function toggle(): void {
|
||||||
background-color: var(--accentedBg) !important;
|
background-color: var(--accentedBg) !important;
|
||||||
border-color: var(--accentedBg) !important;
|
border-color: var(--accentedBg) !important;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
|
|
||||||
&, * {
|
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
|
||||||
|
|
||||||
> .button {
|
> .button {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
|
@ -89,16 +80,17 @@ function toggle(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> input {
|
.input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .button {
|
.button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
@ -120,13 +112,12 @@ function toggle(): void {
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .label {
|
.label {
|
||||||
margin-left: 28px;
|
margin-left: 28px;
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vblkjoeq">
|
<div>
|
||||||
<div class="label" @click="focus"><slot name="label"></slot></div>
|
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||||
<div ref="container" class="input" :class="{ inline, disabled, focused }" @mousedown.prevent="show">
|
<div ref="container" :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]" @mousedown.prevent="show">
|
||||||
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
|
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
|
||||||
<select
|
<select
|
||||||
ref="inputEl"
|
ref="inputEl"
|
||||||
v-model="v"
|
v-model="v"
|
||||||
v-adaptive-border
|
v-adaptive-border
|
||||||
class="select"
|
:class="$style.inputCore"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:required="required"
|
:required="required"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
|
@ -18,9 +18,9 @@
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</select>
|
</select>
|
||||||
<div ref="suffixEl" class="suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div>
|
<div ref="suffixEl" :class="$style.suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="caption"><slot name="caption"></slot></div>
|
<div :class="$style.caption"><slot name="caption"></slot></div>
|
||||||
|
|
||||||
<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,9 +169,8 @@ function show(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.vblkjoeq {
|
.label {
|
||||||
> .label {
|
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
padding: 0 0 8px 0;
|
padding: 0 0 8px 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -179,9 +178,9 @@ function show(ev: MouseEvent) {
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .caption {
|
.caption {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
padding: 8px 0 0 0;
|
padding: 8px 0 0 0;
|
||||||
color: var(--fgTransparentWeak);
|
color: var(--fgTransparentWeak);
|
||||||
|
@ -189,19 +188,41 @@ function show(ev: MouseEvent) {
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .input {
|
.input {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&.inline {
|
||||||
> .select {
|
display: inline-block;
|
||||||
border-color: var(--inputBorderHover) !important;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
> .inputCore {
|
||||||
|
border-color: var(--accent) !important;
|
||||||
|
//box-shadow: 0 0 0 4px var(--focus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .select {
|
&.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
&,
|
||||||
|
> .inputCore {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
> .inputCore {
|
||||||
|
border-color: var(--inputBorderHover) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputCore {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -219,14 +240,14 @@ function show(ev: MouseEvent) {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.1s ease-out;
|
transition: border-color 0.1s ease-out;
|
||||||
|
cursor: pointer;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .prefix,
|
.prefix,
|
||||||
> .suffix {
|
.suffix {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -235,55 +256,29 @@ function show(ev: MouseEvent) {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: v-bind("height + 'px'");
|
height: v-bind("height + 'px'");
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
box-sizing: border-box;
|
||||||
}
|
pointer-events: none;
|
||||||
|
|
||||||
> .prefix {
|
&:empty {
|
||||||
left: 0;
|
display: none;
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .suffix {
|
|
||||||
right: 0;
|
|
||||||
padding-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inline {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.focused {
|
|
||||||
> select {
|
|
||||||
border-color: var(--accent) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
&, * {
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
.prefix {
|
||||||
|
left: 0;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suffix {
|
||||||
|
right: 0;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.chevron {
|
.chevron {
|
||||||
transition: transform 0.1s ease-out;
|
transition: transform 0.1s ease-out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]">
|
||||||
class="ziffeomt"
|
|
||||||
:class="{ disabled, checked }"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:class="$style.input"
|
||||||
@keydown.enter="toggle"
|
@keydown.enter="toggle"
|
||||||
>
|
>
|
||||||
<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" data-cy-switch-toggle @click.prevent="toggle">
|
<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" :class="$style.button" data-cy-switch-toggle @click.prevent="toggle">
|
||||||
<div class="knob"></div>
|
<div :class="$style.knob"></div>
|
||||||
</span>
|
</span>
|
||||||
<span class="label">
|
<span :class="$style.body">
|
||||||
<!-- TODO: 無名slotの方は廃止 -->
|
<!-- TODO: 無名slotの方は廃止 -->
|
||||||
<span @click="toggle"><slot name="label"></slot><slot></slot></span>
|
<span :class="$style.label" @click="toggle"><slot name="label"></slot><slot></slot></span>
|
||||||
<p class="caption"><slot name="caption"></slot></p>
|
<p :class="$style.caption"><slot name="caption"></slot></p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,52 +43,12 @@ const toggle = () => {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" module>
|
||||||
.ziffeomt {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
> * {
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
|
||||||
|
|
||||||
> input {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 32px;
|
|
||||||
height: 23px;
|
|
||||||
outline: none;
|
|
||||||
background: var(--switchOffBg);
|
|
||||||
background-clip: content-box;
|
|
||||||
border: solid 1px var(--switchOffBg);
|
|
||||||
border-radius: 999px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: inherit;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
> .knob {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
left: 3px;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
background: var(--switchOffFg);
|
|
||||||
border-radius: 999px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
> .button {
|
> .button {
|
||||||
|
@ -98,31 +56,6 @@ const toggle = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .label {
|
|
||||||
margin-left: 12px;
|
|
||||||
margin-top: 2px;
|
|
||||||
display: block;
|
|
||||||
transition: inherit;
|
|
||||||
color: var(--fg);
|
|
||||||
|
|
||||||
> span {
|
|
||||||
display: block;
|
|
||||||
line-height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .caption {
|
|
||||||
margin: 8px 0 0 0;
|
|
||||||
color: var(--fgTransparentWeak);
|
|
||||||
font-size: 0.85em;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
@ -140,4 +73,66 @@ const toggle = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 32px;
|
||||||
|
height: 23px;
|
||||||
|
outline: none;
|
||||||
|
background: var(--switchOffBg);
|
||||||
|
background-clip: content-box;
|
||||||
|
border: solid 1px var(--switchOffBg);
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: inherit;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.knob {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
background: var(--switchOffFg);
|
||||||
|
border-radius: 999px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
display: block;
|
||||||
|
transition: inherit;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
margin: 8px 0 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
font-size: 0.85em;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export type Widget = {
|
export type Widget = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -42,6 +43,7 @@ export type DefaultStoredWidget = {
|
||||||
place: string | null;
|
place: string | null;
|
||||||
} & Widget;
|
} & Widget;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue';
|
import { defineAsyncComponent, ref } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
|
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||||
<img :class="$style.inner" :src="url" decoding="async"/>
|
<img :class="$style.inner" :src="url" decoding="async"/>
|
||||||
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
|
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
|
||||||
<div v-if="user.isCat" :class="[$style.ears, { [$style.mask]: useBlurEffect }]">
|
<div v-if="user.isCat" :class="[$style.ears]">
|
||||||
<div :class="$style.earLeft">
|
<div :class="$style.earLeft">
|
||||||
<div v-if="false" :class="$style.layer">
|
<div v-if="false" :class="$style.layer">
|
||||||
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
|
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
|
||||||
|
@ -154,24 +154,6 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||||
padding: 50%;
|
padding: 50%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&.mask {
|
|
||||||
-webkit-mask:
|
|
||||||
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') center / 50% 50%,
|
|
||||||
linear-gradient(#fff, #fff);
|
|
||||||
-webkit-mask-composite: destination-out, source-over;
|
|
||||||
mask:
|
|
||||||
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') exclude center / 50% 50%,
|
|
||||||
linear-gradient(#fff, #fff); // polyfill of `image(#fff)`
|
|
||||||
|
|
||||||
> .earLeft {
|
|
||||||
animation: eartightleft 6s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .earRight {
|
|
||||||
animation: eartightright 6s infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .earLeft,
|
> .earLeft,
|
||||||
> .earRight {
|
> .earRight {
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<MkTextarea v-model="sensitiveWords">
|
<MkTextarea v-model="sensitiveWords">
|
||||||
<template #label>{{ i18n.ts.sensitiveWords }}</template>
|
<template #label>{{ i18n.ts.sensitiveWords }}</template>
|
||||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template>
|
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
<MkFoldableSection ref="tagsEl" :foldable="true" :expanded="false" class="_margin">
|
<MkFoldableSection ref="tagsEl" :foldable="true" :expanded="false" class="_margin">
|
||||||
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularTags }}</template>
|
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularTags }}</template>
|
||||||
|
|
||||||
<div class="vxjfqztj">
|
<div>
|
||||||
<MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/user-tags/${tag.tag}`" class="local">{{ tag.tag }}</MkA>
|
<MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/user-tags/${tag.tag}`" style="margin-right: 16px; font-weight: bold;">{{ tag.tag }}</MkA>
|
||||||
<MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/user-tags/${tag.tag}`">{{ tag.tag }}</MkA>
|
<MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/user-tags/${tag.tag}`" style="margin-right: 16px;">{{ tag.tag }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
|
|
||||||
|
@ -132,15 +132,3 @@ os.api('hashtags/list', {
|
||||||
tagsRemote = tags;
|
tagsRemote = tags;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vxjfqztj {
|
|
||||||
> * {
|
|
||||||
margin-right: 16px;
|
|
||||||
|
|
||||||
&.local {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
{{ i18n.ts.noCrawle }}
|
{{ i18n.ts.noCrawle }}
|
||||||
<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
|
<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="preventAiLarning" @update:model-value="save()">
|
||||||
|
{{ i18n.ts.preventAiLarning }}<span class="_beta">{{ i18n.ts.beta }}</span>
|
||||||
|
<template #caption>{{ i18n.ts.preventAiLarningDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
<MkSwitch v-model="isExplorable" @update:model-value="save()">
|
<MkSwitch v-model="isExplorable" @update:model-value="save()">
|
||||||
{{ i18n.ts.makeExplorable }}
|
{{ i18n.ts.makeExplorable }}
|
||||||
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
||||||
|
@ -71,6 +75,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
let isLocked = $ref($i.isLocked);
|
let isLocked = $ref($i.isLocked);
|
||||||
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
|
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
|
||||||
let noCrawle = $ref($i.noCrawle);
|
let noCrawle = $ref($i.noCrawle);
|
||||||
|
let preventAiLarning = $ref($i.preventAiLarning);
|
||||||
let isExplorable = $ref($i.isExplorable);
|
let isExplorable = $ref($i.isExplorable);
|
||||||
let hideOnlineStatus = $ref($i.hideOnlineStatus);
|
let hideOnlineStatus = $ref($i.hideOnlineStatus);
|
||||||
let publicReactions = $ref($i.publicReactions);
|
let publicReactions = $ref($i.publicReactions);
|
||||||
|
@ -86,6 +91,7 @@ function save() {
|
||||||
isLocked: !!isLocked,
|
isLocked: !!isLocked,
|
||||||
autoAcceptFollowed: !!autoAcceptFollowed,
|
autoAcceptFollowed: !!autoAcceptFollowed,
|
||||||
noCrawle: !!noCrawle,
|
noCrawle: !!noCrawle,
|
||||||
|
preventAiLarning: !!preventAiLarning,
|
||||||
isExplorable: !!isExplorable,
|
isExplorable: !!isExplorable,
|
||||||
hideOnlineStatus: !!hideOnlineStatus,
|
hideOnlineStatus: !!hideOnlineStatus,
|
||||||
publicReactions: !!publicReactions,
|
publicReactions: !!publicReactions,
|
||||||
|
|
Loading…
Reference in a new issue