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
|
||||
|
||||
### Client
|
||||
|
@ -1038,8 +1038,11 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
|
||||
displayOfNote: "Anzeige von Notizen"
|
||||
initialAccountSetting: "Kontoeinrichtung"
|
||||
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:
|
||||
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
|
||||
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
|
||||
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
|
||||
profileSetting: "Profileinstellungen"
|
||||
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."
|
||||
thisChannelArchived: "This channel has been archived."
|
||||
displayOfNote: "Note display"
|
||||
initialAccountSetting: "Profile configuration"
|
||||
initialAccountSetting: "Profile setup"
|
||||
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:
|
||||
accountCreated: "Your account was successfully created!"
|
||||
letsStartAccountSetup: "For starters, let's set up your profile."
|
||||
letsFillYourProfile: "First, let's set up your profile."
|
||||
profileSetting: "Profile settings"
|
||||
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."
|
||||
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."
|
||||
initialAccountSettingCompleted: "Profile configuration complete!"
|
||||
initialAccountSettingCompleted: "Profile setup complete!"
|
||||
haveFun: "Enjoy {name}!"
|
||||
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:
|
||||
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
||||
_accountMigration:
|
||||
|
@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
|
||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||
sensitiveWords: "センシティブワード"
|
||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||
sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
|
||||
notesSearchNotAvailable: "ノート検索は利用できません。"
|
||||
license: "ライセンス"
|
||||
unfavoriteConfirm: "お気に入り解除しますか?"
|
||||
@ -1038,6 +1039,8 @@ thisChannelArchived: "このチャンネルはアーカイブされています
|
||||
displayOfNote: "ノートの表示"
|
||||
initialAccountSetting: "初期設定"
|
||||
youFollowing: "フォロー中"
|
||||
preventAiLarning: "AIによる学習を拒否"
|
||||
preventAiLarningDescription: "投稿したノートや画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。この機能は実験的であり、AIによる学習を完全に防止するものではありません。"
|
||||
|
||||
_initialAccountSetting:
|
||||
accountCreated: "アカウントの作成が完了しました!"
|
||||
|
@ -1240,6 +1240,7 @@ _achievements:
|
||||
title: "잠깐 쉬어"
|
||||
description: "클라이언트를 시작하고 30분이 경과하였습니다"
|
||||
_client60min:
|
||||
title: "No \"Miss\" in Misskey"
|
||||
description: "클라이언트를 시작하고 60분이 경과하였습니다"
|
||||
_noteDeletedWithin1min:
|
||||
title: "있었는데요 없었습니다"
|
||||
|
@ -1036,7 +1036,21 @@ channelArchiveConfirmTitle: "要封存{name}嗎?"
|
||||
channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
|
||||
thisChannelArchived: "這個頻道已被封存。"
|
||||
displayOfNote: "顯示貼文"
|
||||
youFollowing: "關注中"
|
||||
initialAccountSetting: "初始設定"
|
||||
youFollowing: "追隨中"
|
||||
_initialAccountSetting:
|
||||
accountCreated: "帳戶已建立完成!"
|
||||
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
|
||||
letsFillYourProfile: "首先,來設定您的個人檔案吧。"
|
||||
profileSetting: "個人檔案設定"
|
||||
theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
|
||||
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
|
||||
followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
|
||||
pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。"
|
||||
initialAccountSettingCompleted: "初始設定完成了!"
|
||||
haveFun: "盡情享受{name}吧!"
|
||||
ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
|
||||
skipAreYouSure: "要略過初始設定嗎?"
|
||||
_serverRules:
|
||||
description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
|
||||
_accountMigration:
|
||||
@ -1464,7 +1478,7 @@ _channel:
|
||||
removeBanner: "移除橫幅圖像"
|
||||
featured: "熱門貼文"
|
||||
owned: "管理中"
|
||||
following: "關注中"
|
||||
following: "追隨中"
|
||||
usersCount: "有{n}人參與"
|
||||
notesCount: "有{n}個貼文"
|
||||
nameAndDescription: "名稱與說明"
|
||||
@ -1586,6 +1600,16 @@ _time:
|
||||
minute: "分鐘"
|
||||
hour: "小時"
|
||||
day: "日"
|
||||
_timelineTutorial:
|
||||
title: "Misskey的使用方法"
|
||||
step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」按照時間順序顯示。"
|
||||
step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。"
|
||||
step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。"
|
||||
step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。"
|
||||
step3_1: "貼文發出去了嗎?"
|
||||
step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
|
||||
step4_1: "可以對貼文標記「反應」。"
|
||||
step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。"
|
||||
_2fa:
|
||||
alreadyRegistered: "此設備已經被註冊過了"
|
||||
registerTOTP: "開始設定驗證應用程式"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.12.1",
|
||||
"version": "13.12.2",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"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);
|
||||
await serverService.launch();
|
||||
|
||||
app.get(ChartManagementService).start();
|
||||
app.get(JanitorService).start();
|
||||
app.get(QueueStatsService).start();
|
||||
app.get(ServerStatsService).start();
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.get(ChartManagementService).start();
|
||||
app.get(JanitorService).start();
|
||||
app.get(QueueStatsService).start();
|
||||
app.get(ServerStatsService).start();
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
|
||||
import { In, DataSource } from 'typeorm';
|
||||
import * as Redis from 'ioredis';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import RE2 from 're2';
|
||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.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.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';
|
||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||
data.visibility = 'home';
|
||||
@ -670,6 +672,31 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
// Register to search database
|
||||
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
|
||||
private incRenoteCount(renote: Note) {
|
||||
|
@ -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 { DI } from '@/di-symbols.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 DeliverQueue = Bull.Queue<DeliverJobData>;
|
||||
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 ObjectStorageQueue = Bull.Queue<ObjectStorageJobData>;
|
||||
export type ObjectStorageQueue = Bull.Queue;
|
||||
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||
|
||||
const $system: Provider = {
|
||||
@ -118,4 +119,36 @@ const $webhookDeliver: Provider = {
|
||||
$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,
|
||||
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
||||
noCrawle: profile!.noCrawle,
|
||||
preventAiLarning: profile!.preventAiLarning,
|
||||
isExplorable: user.isExplorable,
|
||||
isDeleted: user.isDeleted,
|
||||
hideOnlineStatus: user.hideOnlineStatus,
|
||||
|
@ -76,7 +76,7 @@ export class UserProfile {
|
||||
public emailNotificationTypes: string[];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
default: true,
|
||||
})
|
||||
public publicReactions: boolean;
|
||||
|
||||
@ -147,6 +147,11 @@ export class UserProfile {
|
||||
})
|
||||
public noCrawle: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public preventAiLarning: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
@ -302,7 +302,11 @@ export const packedMeDetailedOnlySchema = {
|
||||
},
|
||||
noCrawle: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
preventAiLarning: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
isExplorable: {
|
||||
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 { QueueLoggerService } from './QueueLoggerService.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 { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||
import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
|
||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||
@ -68,10 +64,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
||||
DeleteFileProcessorService,
|
||||
CleanRemoteFilesProcessorService,
|
||||
RelationshipProcessorService,
|
||||
SystemQueueProcessorsService,
|
||||
ObjectStorageQueueProcessorsService,
|
||||
DbQueueProcessorsService,
|
||||
RelationshipQueueProcessorsService,
|
||||
WebhookDeliverProcessorService,
|
||||
EndedPollNotificationProcessorService,
|
||||
DeliverProcessorService,
|
||||
|
@ -5,15 +5,36 @@ import type Logger from '@/logger.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { bindThis } from '@/decorators.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 { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.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 { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
|
||||
|
||||
@Injectable()
|
||||
export class QueueProcessorService {
|
||||
@ -25,14 +46,35 @@ export class QueueProcessorService {
|
||||
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private queueService: QueueService,
|
||||
private systemQueueProcessorsService: SystemQueueProcessorsService,
|
||||
private objectStorageQueueProcessorsService: ObjectStorageQueueProcessorsService,
|
||||
private dbQueueProcessorsService: DbQueueProcessorsService,
|
||||
private relationshipQueueProcessorsService: RelationshipQueueProcessorsService,
|
||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||
private deliverProcessorService: DeliverProcessorService,
|
||||
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;
|
||||
}
|
||||
@ -119,14 +161,6 @@ export class QueueProcessorService {
|
||||
.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}`));
|
||||
|
||||
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', {
|
||||
}, {
|
||||
repeat: { cron: '55 * * * *' },
|
||||
@ -163,6 +197,46 @@ export class QueueProcessorService {
|
||||
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,
|
||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||
noCrawle: profile.noCrawle,
|
||||
preventAiLarning: profile.preventAiLarning,
|
||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||
autoSensitive: profile.autoSensitive,
|
||||
carefulBot: profile.carefulBot,
|
||||
|
@ -98,7 +98,7 @@ export const meta = {
|
||||
message: 'This feature is restricted by your role.',
|
||||
code: 'RESTRICTED_BY_ROLE',
|
||||
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@ -138,6 +138,7 @@ export const paramDef = {
|
||||
carefulBot: { type: 'boolean' },
|
||||
autoAcceptFollowed: { type: 'boolean' },
|
||||
noCrawle: { type: 'boolean' },
|
||||
preventAiLarning: { type: 'boolean' },
|
||||
isBot: { type: 'boolean' },
|
||||
isCat: { 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.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||
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.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
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 { FeedService } from './FeedService.js';
|
||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
@ -437,6 +437,10 @@ export class ClientServerService {
|
||||
: [];
|
||||
|
||||
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', {
|
||||
user, profile, me,
|
||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||
@ -481,6 +485,10 @@ export class ClientServerService {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
||||
const meta = await this.metaService.fetch();
|
||||
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', {
|
||||
note: _note,
|
||||
profile,
|
||||
@ -520,6 +528,10 @@ export class ClientServerService {
|
||||
} else {
|
||||
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', {
|
||||
page: _page,
|
||||
profile,
|
||||
@ -544,6 +556,10 @@ export class ClientServerService {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
|
||||
const meta = await this.metaService.fetch();
|
||||
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', {
|
||||
flash: _flash,
|
||||
profile,
|
||||
@ -568,6 +584,10 @@ export class ClientServerService {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
|
||||
const meta = await this.metaService.fetch();
|
||||
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', {
|
||||
clip: _clip,
|
||||
profile,
|
||||
@ -590,6 +610,10 @@ export class ClientServerService {
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
|
||||
const meta = await this.metaService.fetch();
|
||||
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', {
|
||||
post: _post,
|
||||
profile,
|
||||
|
@ -21,6 +21,9 @@ block og
|
||||
block meta
|
||||
if profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -21,6 +21,9 @@ block og
|
||||
block meta
|
||||
if profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -21,6 +21,9 @@ block og
|
||||
block meta
|
||||
if user.host || profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -22,6 +22,9 @@ block og
|
||||
block meta
|
||||
if user.host || isRenote || profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -21,6 +21,9 @@ block og
|
||||
block meta
|
||||
if profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -20,6 +20,9 @@ block og
|
||||
block meta
|
||||
if user.host || profile.noCrawle
|
||||
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-id' content=user.id)
|
||||
|
@ -541,6 +541,61 @@ describe('Note', () => {
|
||||
|
||||
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', () => {
|
||||
|
@ -145,6 +145,7 @@ describe('ユーザー', () => {
|
||||
carefulBot: user.carefulBot,
|
||||
autoAcceptFollowed: user.autoAcceptFollowed,
|
||||
noCrawle: user.noCrawle,
|
||||
preventAiLarning: user.preventAiLarning,
|
||||
isExplorable: user.isExplorable,
|
||||
isDeleted: user.isDeleted,
|
||||
hideOnlineStatus: user.hideOnlineStatus,
|
||||
@ -370,7 +371,7 @@ describe('ユーザー', () => {
|
||||
assert.deepStrictEqual(response.pinnedNotes, []);
|
||||
assert.strictEqual(response.pinnedPageId, null);
|
||||
assert.strictEqual(response.pinnedPage, null);
|
||||
assert.strictEqual(response.publicReactions, false);
|
||||
assert.strictEqual(response.publicReactions, true);
|
||||
assert.strictEqual(response.ffVisibility, 'public');
|
||||
assert.strictEqual(response.twoFactorEnabled, false);
|
||||
assert.strictEqual(response.usePasswordLessLogin, false);
|
||||
@ -390,6 +391,7 @@ describe('ユーザー', () => {
|
||||
assert.strictEqual(response.carefulBot, false);
|
||||
assert.strictEqual(response.autoAcceptFollowed, true);
|
||||
assert.strictEqual(response.noCrawle, false);
|
||||
assert.strictEqual(response.preventAiLarning, true);
|
||||
assert.strictEqual(response.isExplorable, true);
|
||||
assert.strictEqual(response.isDeleted, false);
|
||||
assert.strictEqual(response.hideOnlineStatus, false);
|
||||
@ -462,6 +464,8 @@ describe('ユーザー', () => {
|
||||
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
||||
{ parameters: (): object => ({ noCrawle: true }) },
|
||||
{ parameters: (): object => ({ noCrawle: false }) },
|
||||
{ parameters: (): object => ({ preventAiLarning: false }) },
|
||||
{ parameters: (): object => ({ preventAiLarning: true }) },
|
||||
{ parameters: (): object => ({ isBot: true }) },
|
||||
{ parameters: (): object => ({ isBot: false }) },
|
||||
{ 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>
|
||||
<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="headerEl" class="header">
|
||||
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
|
||||
<span class="title">
|
||||
<div ref="rootEl" :class="$style.root" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown">
|
||||
<div ref="headerEl" :class="$style.header">
|
||||
<button v-if="withOkButton" :class="$style.headerButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
|
||||
<span :class="$style.title">
|
||||
<slot name="header"></slot>
|
||||
</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="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></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="$style.headerButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></i></button>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div :class="$style.body">
|
||||
<slot :width="bodyWidth" :height="bodyHeight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,8 +81,8 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ebkgoccj {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@ -96,51 +96,52 @@ defineExpose({
|
||||
--root-margin: 16px;
|
||||
}
|
||||
|
||||
> .header {
|
||||
$height: 46px;
|
||||
$height-narrow: 42px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
background: var(--windowHeader);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
--headerHeight: 46px;
|
||||
--headerHeightNarrow: 42px;
|
||||
}
|
||||
|
||||
> button {
|
||||
height: $height;
|
||||
width: $height;
|
||||
.header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
background: var(--windowHeader);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
height: $height-narrow;
|
||||
width: $height-narrow;
|
||||
}
|
||||
}
|
||||
.headerButton {
|
||||
height: var(--headerHeight);
|
||||
width: var(--headerHeight);
|
||||
|
||||
> .title {
|
||||
flex: 1;
|
||||
line-height: $height;
|
||||
padding-left: 32px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
line-height: $height-narrow;
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
> button + .title {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: var(--panel);
|
||||
container-type: size;
|
||||
@media (max-width: 500px) {
|
||||
height: var(--headerHeightNarrow);
|
||||
width: var(--headerHeightNarrow);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
line-height: var(--headerHeight);
|
||||
padding-left: 32px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
line-height: var(--headerHeightNarrow);
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.headerButton + .title {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: var(--panel);
|
||||
container-type: size;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-adaptive-border
|
||||
class="novjtctn"
|
||||
:class="{ disabled, checked }"
|
||||
:class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]"
|
||||
:aria-checked="checked"
|
||||
:aria-disabled="disabled"
|
||||
@click="toggle"
|
||||
@ -10,11 +9,12 @@
|
||||
<input
|
||||
type="radio"
|
||||
:disabled="disabled"
|
||||
:class="$style.input"
|
||||
>
|
||||
<span class="button">
|
||||
<span :class="$style.button">
|
||||
<span></span>
|
||||
</span>
|
||||
<span class="label"><slot></slot></span>
|
||||
<span :class="$style.label"><slot></slot></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -39,8 +39,8 @@ function toggle(): void {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.novjtctn {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
@ -53,17 +53,11 @@ function toggle(): void {
|
||||
border-radius: 6px;
|
||||
font-size: 90%;
|
||||
transition: all 0.2s;
|
||||
|
||||
> * {
|
||||
user-select: none;
|
||||
}
|
||||
user-select: none;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
|
||||
&, * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -74,10 +68,7 @@ function toggle(): void {
|
||||
background-color: var(--accentedBg) !important;
|
||||
border-color: var(--accentedBg) !important;
|
||||
color: var(--accent);
|
||||
|
||||
&, * {
|
||||
cursor: default !important;
|
||||
}
|
||||
cursor: default !important;
|
||||
|
||||
> .button {
|
||||
border-color: var(--accent);
|
||||
@ -89,44 +80,44 @@ function toggle(): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> input {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.input {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> .button {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: none;
|
||||
border: solid 2px var(--inputBorder);
|
||||
border-radius: 100%;
|
||||
transition: inherit;
|
||||
.button {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: none;
|
||||
border: solid 2px var(--inputBorder);
|
||||
border-radius: 100%;
|
||||
transition: inherit;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
left: 3px;
|
||||
border-radius: 100%;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
}
|
||||
|
||||
> .label {
|
||||
margin-left: 28px;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
left: 3px;
|
||||
border-radius: 100%;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 28px;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="vblkjoeq">
|
||||
<div class="label" @click="focus"><slot name="label"></slot></div>
|
||||
<div ref="container" class="input" :class="{ inline, disabled, focused }" @mousedown.prevent="show">
|
||||
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
|
||||
<div>
|
||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||
<div ref="container" :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]" @mousedown.prevent="show">
|
||||
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
|
||||
<select
|
||||
ref="inputEl"
|
||||
v-model="v"
|
||||
v-adaptive-border
|
||||
class="select"
|
||||
:class="$style.inputCore"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
@ -18,9 +18,9 @@
|
||||
>
|
||||
<slot></slot>
|
||||
</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 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>
|
||||
</div>
|
||||
@ -169,121 +169,116 @@ function show(ev: MouseEvent) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vblkjoeq {
|
||||
> .label {
|
||||
font-size: 0.85em;
|
||||
padding: 0 0 8px 0;
|
||||
user-select: none;
|
||||
<style lang="scss" module>
|
||||
.label {
|
||||
font-size: 0.85em;
|
||||
padding: 0 0 8px 0;
|
||||
user-select: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-size: 0.85em;
|
||||
padding: 8px 0 0 0;
|
||||
color: var(--fgTransparentWeak);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
> .inputCore {
|
||||
border-color: var(--accent) !important;
|
||||
//box-shadow: 0 0 0 4px var(--focus);
|
||||
}
|
||||
}
|
||||
|
||||
> .caption {
|
||||
font-size: 0.85em;
|
||||
padding: 8px 0 0 0;
|
||||
color: var(--fgTransparentWeak);
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
&,
|
||||
> .inputCore {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .input {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
> .select {
|
||||
border-color: var(--inputBorderHover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
height: v-bind("height + 'px'");
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--panel);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.1s ease-out;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
> .prefix,
|
||||
> .suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
padding: 0 12px;
|
||||
font-size: 1em;
|
||||
height: v-bind("height + 'px'");
|
||||
pointer-events: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
min-width: 16px;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix {
|
||||
left: 0;
|
||||
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;
|
||||
}
|
||||
&:hover {
|
||||
> .inputCore {
|
||||
border-color: var(--inputBorderHover) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.inputCore {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
height: v-bind("height + 'px'");
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--panel);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.1s ease-out;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
padding: 0 12px;
|
||||
font-size: 1em;
|
||||
height: v-bind("height + 'px'");
|
||||
min-width: 16px;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.prefix {
|
||||
left: 0;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
right: 0;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
<template>
|
||||
<div
|
||||
class="ziffeomt"
|
||||
:class="{ disabled, checked }"
|
||||
>
|
||||
<div :class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]">
|
||||
<input
|
||||
ref="input"
|
||||
type="checkbox"
|
||||
:disabled="disabled"
|
||||
:class="$style.input"
|
||||
@keydown.enter="toggle"
|
||||
>
|
||||
<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" data-cy-switch-toggle @click.prevent="toggle">
|
||||
<div class="knob"></div>
|
||||
<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" :class="$style.button" data-cy-switch-toggle @click.prevent="toggle">
|
||||
<div :class="$style.knob"></div>
|
||||
</span>
|
||||
<span class="label">
|
||||
<span :class="$style.body">
|
||||
<!-- TODO: 無名slotの方は廃止 -->
|
||||
<span @click="toggle"><slot name="label"></slot><slot></slot></span>
|
||||
<p class="caption"><slot name="caption"></slot></p>
|
||||
<span :class="$style.label" @click="toggle"><slot name="label"></slot><slot></slot></span>
|
||||
<p :class="$style.caption"><slot name="caption"></slot></p>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -45,52 +43,12 @@ const toggle = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ziffeomt {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
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: 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;
|
||||
}
|
||||
}
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
> .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 {
|
||||
opacity: 0.6;
|
||||
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>
|
||||
|
@ -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)"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export type Widget = {
|
||||
name: string;
|
||||
@ -42,6 +43,7 @@ export type DefaultStoredWidget = {
|
||||
place: string | null;
|
||||
} & Widget;
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
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">
|
||||
<img :class="$style.inner" :src="url" decoding="async"/>
|
||||
<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 v-if="false" :class="$style.layer">
|
||||
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
|
||||
@ -154,24 +154,6 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||
padding: 50%;
|
||||
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,
|
||||
> .earRight {
|
||||
contain: strict;
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
<MkTextarea v-model="sensitiveWords">
|
||||
<template #label>{{ i18n.ts.sensitiveWords }}</template>
|
||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template>
|
||||
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
|
@ -28,9 +28,9 @@
|
||||
<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>
|
||||
|
||||
<div class="vxjfqztj">
|
||||
<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 tagsRemote" :key="'remote:' + tag.tag" :to="`/user-tags/${tag.tag}`">{{ tag.tag }}</MkA>
|
||||
<div>
|
||||
<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}`" style="margin-right: 16px;">{{ tag.tag }}</MkA>
|
||||
</div>
|
||||
</MkFoldableSection>
|
||||
|
||||
@ -132,15 +132,3 @@ os.api('hashtags/list', {
|
||||
tagsRemote = tags;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vxjfqztj {
|
||||
> * {
|
||||
margin-right: 16px;
|
||||
|
||||
&.local {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -24,6 +24,10 @@
|
||||
{{ i18n.ts.noCrawle }}
|
||||
<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
|
||||
</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()">
|
||||
{{ i18n.ts.makeExplorable }}
|
||||
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
|
||||
@ -71,6 +75,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
let isLocked = $ref($i.isLocked);
|
||||
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
|
||||
let noCrawle = $ref($i.noCrawle);
|
||||
let preventAiLarning = $ref($i.preventAiLarning);
|
||||
let isExplorable = $ref($i.isExplorable);
|
||||
let hideOnlineStatus = $ref($i.hideOnlineStatus);
|
||||
let publicReactions = $ref($i.publicReactions);
|
||||
@ -86,6 +91,7 @@ function save() {
|
||||
isLocked: !!isLocked,
|
||||
autoAcceptFollowed: !!autoAcceptFollowed,
|
||||
noCrawle: !!noCrawle,
|
||||
preventAiLarning: !!preventAiLarning,
|
||||
isExplorable: !!isExplorable,
|
||||
hideOnlineStatus: !!hideOnlineStatus,
|
||||
publicReactions: !!publicReactions,
|
||||
|
Loading…
Reference in New Issue
Block a user