Update deps, Remove Reactivity Transform, Remove AI

This commit is contained in:
sim1222 2024-10-14 08:20:24 +09:00
parent 4cc3038ae4
commit 4f816857e5
Signed by: sim1222
GPG Key ID: D1AE30E316E44E5D
249 changed files with 7703 additions and 7954 deletions

View File

@ -1,40 +1,36 @@
version: "3"
services:
web:
build: .
restart: always
links:
- db
- redis
# - es
ports:
- "3000:3000"
networks:
- internal_network
- external_network
volumes:
- ./files:/misskey/files
- ./.config:/misskey/.config:ro
# web:
# build: .
# restart: always
# links:
# - db
# - redis
# # - es
# ports:
# - "3000:3000"
# networks:
# - internal_network
# - external_network
# volumes:
# - ./files:/misskey/files
# - ./.config:/misskey/.config:ro
redis:
restart: always
image: redis:4.0-alpine
networks:
- internal_network
image: redis:alpine
ports:
- "6379:6379"
volumes:
- ./redis:/data
db:
restart: always
image: postgres:12.2-alpine
networks:
- internal_network
image: postgres:17.0-alpine
ports:
- "5432:5432"
env_file:
- .config/docker.env
volumes:
- ./db:/var/lib/postgresql/data
# es:
# restart: always
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
@ -45,7 +41,6 @@ services:
# - internal_network
# volumes:
# - ./elasticsearch:/usr/share/elasticsearch/data
networks:
internal_network:
internal: true

View File

@ -8,8 +8,8 @@
},
"private": true,
"scripts": {
"postinstall": "node ./scripts/install-packages.js",
"build": "node ./scripts/build.js",
"postinstall": "node ./scripts/install-packages.mjs",
"build": "node ./scripts/build.mjs",
"start": "cd packages/backend && node --experimental-json-modules ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/boot/index.js",
"init": "npm run migrate",
@ -17,8 +17,8 @@
"migrateandstart": "npm run migrate && npm run start",
"gulp": "gulp build",
"watch": "npm run dev",
"dev": "node ./scripts/dev.js",
"lint": "node ./scripts/lint.js",
"dev": "node ./scripts/dev.mjs",
"lint": "node ./scripts/lint.mjs",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
@ -27,26 +27,27 @@
"test": "npm run jest",
"test-and-coverage": "npm run jest-and-coverage",
"format": "gulp format",
"clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js",
"clean": "node ./scripts/clean.mjs",
"clean-all": "node ./scripts/clean-all.mjs",
"cleanall": "npm run clean-all"
},
"dependencies": {
"execa": "5.1.1",
"gulp": "4.0.2",
"execa": "9.4.0",
"gulp": "5.0.0",
"gulp-cssnano": "2.1.3",
"gulp-rename": "2.0.0",
"gulp-replace": "1.1.3",
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"js-yaml": "4.1.0"
},
"devDependencies": {
"@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/parser": "5.38.0",
"@types/gulp": "4.0.17",
"@types/gulp-rename": "2.0.6",
"@typescript-eslint/parser": "8.8.1",
"cross-env": "7.0.3",
"cypress": "10.8.0",
"start-server-and-test": "1.14.0",
"typescript": "4.8.3"
}
"cypress": "13.15.0",
"start-server-and-test": "2.0.8",
"typescript": "5.6.3"
},
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}

View File

@ -15,9 +15,6 @@
"chokidar": "^3.3.1",
"lodash": "^4.17.21"
},
"optionalDependencies": {
"@tensorflow/tfjs-node": "3.20.0"
},
"dependencies": {
"@bull-board/koa": "4.3.1",
"@discordapp/twemoji": "14.0.2",
@ -38,7 +35,7 @@
"aws-sdk": "2.1213.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
"bull": "4.9.0",
"bull": "4.16.3",
"cacheable-lookup": "6.1.0",
"cbor": "8.1.0",
"chalk": "5.0.1",
@ -94,7 +91,7 @@
"qrcode": "1.5.1",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.17.7",
"re2": "1.21.4",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",

View File

@ -1,16 +1,16 @@
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import { Inject, Injectable } from "@nestjs/common";
import * as nsfw from "nsfwjs";
import si from "systeminformation";
import type { Config } from "@/config.js";
import { DI } from "@/di-symbols.js";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const REQUIRED_CPU_FLAGS = ['avx2', 'fma'];
const REQUIRED_CPU_FLAGS = ["avx2", "fma"];
let isSupportedCpu: undefined | boolean = undefined;
@Injectable()
@ -20,27 +20,33 @@ export class AiService {
constructor(
@Inject(DI.config)
private config: Config,
) {
}
) { }
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
public async detectSensitive(
path: string,
): Promise<nsfw.predictionType[] | null> {
try {
if (isSupportedCpu === undefined) {
const cpuFlags = await this.getCpuFlags();
isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
isSupportedCpu = REQUIRED_CPU_FLAGS.every((required) =>
cpuFlags.includes(required),
);
}
if (!isSupportedCpu) {
console.error('This CPU cannot use TensorFlow.');
console.error("This CPU cannot use TensorFlow.");
return null;
}
const tf = await import('@tensorflow/tfjs-node');
if (this.model == null) this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
const tf = await import("@tensorflow/tfjs-node");
if (this.model == null)
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, {
size: 299,
});
const buffer = await fs.promises.readFile(path);
const image = await tf.node.decodeImage(buffer, 3) as any;
const image = (await tf.node.decodeImage(buffer, 3)) as any;
try {
const predictions = await this.model.classify(image);
return predictions;

View File

@ -1,248 +1,576 @@
import { Module } from '@nestjs/common';
import { DI } from '../di-symbols.js';
import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js';
import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
import { CaptchaService } from './CaptchaService.js';
import { CreateNotificationService } from './CreateNotificationService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js';
import { DeleteAccountService } from './DeleteAccountService.js';
import { DownloadService } from './DownloadService.js';
import { DriveService } from './DriveService.js';
import { EmailService } from './EmailService.js';
import { FederatedInstanceService } from './FederatedInstanceService.js';
import { FetchInstanceMetadataService } from './FetchInstanceMetadataService.js';
import { GlobalEventService } from './GlobalEventService.js';
import { HashtagService } from './HashtagService.js';
import { HttpRequestService } from './HttpRequestService.js';
import { IdService } from './IdService.js';
import { ImageProcessingService } from './ImageProcessingService.js';
import { InstanceActorService } from './InstanceActorService.js';
import { InternalStorageService } from './InternalStorageService.js';
import { MessagingService } from './MessagingService.js';
import { MetaService } from './MetaService.js';
import { MfmService } from './MfmService.js';
import { ModerationLogService } from './ModerationLogService.js';
import { NoteCreateService } from './NoteCreateService.js';
import { NoteDeleteService } from './NoteDeleteService.js';
import { NotePiningService } from './NotePiningService.js';
import { NoteReadService } from './NoteReadService.js';
import { NotificationService } from './NotificationService.js';
import { PollService } from './PollService.js';
import { PushNotificationService } from './PushNotificationService.js';
import { QueryService } from './QueryService.js';
import { ReactionService } from './ReactionService.js';
import { RelayService } from './RelayService.js';
import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
import { UserBlockingService } from './UserBlockingService.js';
import { UserCacheService } from './UserCacheService.js';
import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairStoreService } from './UserKeypairStoreService.js';
import { UserListService } from './UserListService.js';
import { UserMutingService } from './UserMutingService.js';
import { UserSuspendService } from './UserSuspendService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
import { WebhookService } from './WebhookService.js';
import { ProxyAccountService } from './ProxyAccountService.js';
import { UtilityService } from './UtilityService.js';
import { FileInfoService } from './FileInfoService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
import NotesChart from './chart/charts/notes.js';
import UsersChart from './chart/charts/users.js';
import ActiveUsersChart from './chart/charts/active-users.js';
import InstanceChart from './chart/charts/instance.js';
import PerUserNotesChart from './chart/charts/per-user-notes.js';
import DriveChart from './chart/charts/drive.js';
import PerUserReactionsChart from './chart/charts/per-user-reactions.js';
import HashtagChart from './chart/charts/hashtag.js';
import PerUserFollowingChart from './chart/charts/per-user-following.js';
import PerUserDriveChart from './chart/charts/per-user-drive.js';
import ApRequestChart from './chart/charts/ap-request.js';
import { ChartManagementService } from './chart/ChartManagementService.js';
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
import { AntennaEntityService } from './entities/AntennaEntityService.js';
import { AppEntityService } from './entities/AppEntityService.js';
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
import { BlockingEntityService } from './entities/BlockingEntityService.js';
import { ChannelEntityService } from './entities/ChannelEntityService.js';
import { ClipEntityService } from './entities/ClipEntityService.js';
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
import { EmojiEntityService } from './entities/EmojiEntityService.js';
import { FollowingEntityService } from './entities/FollowingEntityService.js';
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js';
import { HashtagEntityService } from './entities/HashtagEntityService.js';
import { InstanceEntityService } from './entities/InstanceEntityService.js';
import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js';
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
import { MutingEntityService } from './entities/MutingEntityService.js';
import { NoteEntityService } from './entities/NoteEntityService.js';
import { NoteFavoriteEntityService } from './entities/NoteFavoriteEntityService.js';
import { NoteReactionEntityService } from './entities/NoteReactionEntityService.js';
import { NotificationEntityService } from './entities/NotificationEntityService.js';
import { PageEntityService } from './entities/PageEntityService.js';
import { PageLikeEntityService } from './entities/PageLikeEntityService.js';
import { SigninEntityService } from './entities/SigninEntityService.js';
import { UserEntityService } from './entities/UserEntityService.js';
import { UserGroupEntityService } from './entities/UserGroupEntityService.js';
import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js';
import { UserListEntityService } from './entities/UserListEntityService.js';
import { ApAudienceService } from './remote/activitypub/ApAudienceService.js';
import { ApDbResolverService } from './remote/activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './remote/activitypub/ApDeliverManagerService.js';
import { ApInboxService } from './remote/activitypub/ApInboxService.js';
import { ApLoggerService } from './remote/activitypub/ApLoggerService.js';
import { ApMfmService } from './remote/activitypub/ApMfmService.js';
import { ApRendererService } from './remote/activitypub/ApRendererService.js';
import { ApRequestService } from './remote/activitypub/ApRequestService.js';
import { ApResolverService } from './remote/activitypub/ApResolverService.js';
import { LdSignatureService } from './remote/activitypub/LdSignatureService.js';
import { RemoteLoggerService } from './remote/RemoteLoggerService.js';
import { ResolveUserService } from './remote/ResolveUserService.js';
import { WebfingerService } from './remote/WebfingerService.js';
import { ApImageService } from './remote/activitypub/models/ApImageService.js';
import { ApMentionService } from './remote/activitypub/models/ApMentionService.js';
import { ApNoteService } from './remote/activitypub/models/ApNoteService.js';
import { ApPersonService } from './remote/activitypub/models/ApPersonService.js';
import { ApQuestionService } from './remote/activitypub/models/ApQuestionService.js';
import { QueueModule } from './queue/QueueModule.js';
import { QueueService } from './QueueService.js';
import { LoggerService } from './LoggerService.js';
import type { Provider } from '@nestjs/common';
import { Module } from "@nestjs/common";
import { DI } from "../di-symbols.js";
import { AccountUpdateService } from "./AccountUpdateService.js";
// import { AiService } from './AiService.js';
import { AntennaService } from "./AntennaService.js";
import { AppLockService } from "./AppLockService.js";
import { CaptchaService } from "./CaptchaService.js";
import { CreateNotificationService } from "./CreateNotificationService.js";
import { CreateSystemUserService } from "./CreateSystemUserService.js";
import { CustomEmojiService } from "./CustomEmojiService.js";
import { DeleteAccountService } from "./DeleteAccountService.js";
import { DownloadService } from "./DownloadService.js";
import { DriveService } from "./DriveService.js";
import { EmailService } from "./EmailService.js";
import { FederatedInstanceService } from "./FederatedInstanceService.js";
import { FetchInstanceMetadataService } from "./FetchInstanceMetadataService.js";
import { GlobalEventService } from "./GlobalEventService.js";
import { HashtagService } from "./HashtagService.js";
import { HttpRequestService } from "./HttpRequestService.js";
import { IdService } from "./IdService.js";
import { ImageProcessingService } from "./ImageProcessingService.js";
import { InstanceActorService } from "./InstanceActorService.js";
import { InternalStorageService } from "./InternalStorageService.js";
import { MessagingService } from "./MessagingService.js";
import { MetaService } from "./MetaService.js";
import { MfmService } from "./MfmService.js";
import { ModerationLogService } from "./ModerationLogService.js";
import { NoteCreateService } from "./NoteCreateService.js";
import { NoteDeleteService } from "./NoteDeleteService.js";
import { NotePiningService } from "./NotePiningService.js";
import { NoteReadService } from "./NoteReadService.js";
import { NotificationService } from "./NotificationService.js";
import { PollService } from "./PollService.js";
import { PushNotificationService } from "./PushNotificationService.js";
import { QueryService } from "./QueryService.js";
import { ReactionService } from "./ReactionService.js";
import { RelayService } from "./RelayService.js";
import { S3Service } from "./S3Service.js";
import { SignupService } from "./SignupService.js";
import { TwoFactorAuthenticationService } from "./TwoFactorAuthenticationService.js";
import { UserBlockingService } from "./UserBlockingService.js";
import { UserCacheService } from "./UserCacheService.js";
import { UserFollowingService } from "./UserFollowingService.js";
import { UserKeypairStoreService } from "./UserKeypairStoreService.js";
import { UserListService } from "./UserListService.js";
import { UserMutingService } from "./UserMutingService.js";
import { UserSuspendService } from "./UserSuspendService.js";
import { VideoProcessingService } from "./VideoProcessingService.js";
import { WebhookService } from "./WebhookService.js";
import { ProxyAccountService } from "./ProxyAccountService.js";
import { UtilityService } from "./UtilityService.js";
import { FileInfoService } from "./FileInfoService.js";
import { ChartLoggerService } from "./chart/ChartLoggerService.js";
import FederationChart from "./chart/charts/federation.js";
import NotesChart from "./chart/charts/notes.js";
import UsersChart from "./chart/charts/users.js";
import ActiveUsersChart from "./chart/charts/active-users.js";
import InstanceChart from "./chart/charts/instance.js";
import PerUserNotesChart from "./chart/charts/per-user-notes.js";
import DriveChart from "./chart/charts/drive.js";
import PerUserReactionsChart from "./chart/charts/per-user-reactions.js";
import HashtagChart from "./chart/charts/hashtag.js";
import PerUserFollowingChart from "./chart/charts/per-user-following.js";
import PerUserDriveChart from "./chart/charts/per-user-drive.js";
import ApRequestChart from "./chart/charts/ap-request.js";
import { ChartManagementService } from "./chart/ChartManagementService.js";
import { AbuseUserReportEntityService } from "./entities/AbuseUserReportEntityService.js";
import { AntennaEntityService } from "./entities/AntennaEntityService.js";
import { AppEntityService } from "./entities/AppEntityService.js";
import { AuthSessionEntityService } from "./entities/AuthSessionEntityService.js";
import { BlockingEntityService } from "./entities/BlockingEntityService.js";
import { ChannelEntityService } from "./entities/ChannelEntityService.js";
import { ClipEntityService } from "./entities/ClipEntityService.js";
import { DriveFileEntityService } from "./entities/DriveFileEntityService.js";
import { DriveFolderEntityService } from "./entities/DriveFolderEntityService.js";
import { EmojiEntityService } from "./entities/EmojiEntityService.js";
import { FollowingEntityService } from "./entities/FollowingEntityService.js";
import { FollowRequestEntityService } from "./entities/FollowRequestEntityService.js";
import { GalleryLikeEntityService } from "./entities/GalleryLikeEntityService.js";
import { GalleryPostEntityService } from "./entities/GalleryPostEntityService.js";
import { HashtagEntityService } from "./entities/HashtagEntityService.js";
import { InstanceEntityService } from "./entities/InstanceEntityService.js";
import { MessagingMessageEntityService } from "./entities/MessagingMessageEntityService.js";
import { ModerationLogEntityService } from "./entities/ModerationLogEntityService.js";
import { MutingEntityService } from "./entities/MutingEntityService.js";
import { NoteEntityService } from "./entities/NoteEntityService.js";
import { NoteFavoriteEntityService } from "./entities/NoteFavoriteEntityService.js";
import { NoteReactionEntityService } from "./entities/NoteReactionEntityService.js";
import { NotificationEntityService } from "./entities/NotificationEntityService.js";
import { PageEntityService } from "./entities/PageEntityService.js";
import { PageLikeEntityService } from "./entities/PageLikeEntityService.js";
import { SigninEntityService } from "./entities/SigninEntityService.js";
import { UserEntityService } from "./entities/UserEntityService.js";
import { UserGroupEntityService } from "./entities/UserGroupEntityService.js";
import { UserGroupInvitationEntityService } from "./entities/UserGroupInvitationEntityService.js";
import { UserListEntityService } from "./entities/UserListEntityService.js";
import { ApAudienceService } from "./remote/activitypub/ApAudienceService.js";
import { ApDbResolverService } from "./remote/activitypub/ApDbResolverService.js";
import { ApDeliverManagerService } from "./remote/activitypub/ApDeliverManagerService.js";
import { ApInboxService } from "./remote/activitypub/ApInboxService.js";
import { ApLoggerService } from "./remote/activitypub/ApLoggerService.js";
import { ApMfmService } from "./remote/activitypub/ApMfmService.js";
import { ApRendererService } from "./remote/activitypub/ApRendererService.js";
import { ApRequestService } from "./remote/activitypub/ApRequestService.js";
import { ApResolverService } from "./remote/activitypub/ApResolverService.js";
import { LdSignatureService } from "./remote/activitypub/LdSignatureService.js";
import { RemoteLoggerService } from "./remote/RemoteLoggerService.js";
import { ResolveUserService } from "./remote/ResolveUserService.js";
import { WebfingerService } from "./remote/WebfingerService.js";
import { ApImageService } from "./remote/activitypub/models/ApImageService.js";
import { ApMentionService } from "./remote/activitypub/models/ApMentionService.js";
import { ApNoteService } from "./remote/activitypub/models/ApNoteService.js";
import { ApPersonService } from "./remote/activitypub/models/ApPersonService.js";
import { ApQuestionService } from "./remote/activitypub/models/ApQuestionService.js";
import { QueueModule } from "./queue/QueueModule.js";
import { QueueService } from "./QueueService.js";
import { LoggerService } from "./LoggerService.js";
import type { Provider } from "@nestjs/common";
//#region 文字列ベースでのinjection用(循環参照対応のため)
const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService };
const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService };
const $AiService: Provider = { provide: 'AiService', useExisting: AiService };
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
const $CreateNotificationService: Provider = { provide: 'CreateNotificationService', useExisting: CreateNotificationService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
const $DriveService: Provider = { provide: 'DriveService', useExisting: DriveService };
const $EmailService: Provider = { provide: 'EmailService', useExisting: EmailService };
const $FederatedInstanceService: Provider = { provide: 'FederatedInstanceService', useExisting: FederatedInstanceService };
const $FetchInstanceMetadataService: Provider = { provide: 'FetchInstanceMetadataService', useExisting: FetchInstanceMetadataService };
const $GlobalEventService: Provider = { provide: 'GlobalEventService', useExisting: GlobalEventService };
const $HashtagService: Provider = { provide: 'HashtagService', useExisting: HashtagService };
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
const $MessagingService: Provider = { provide: 'MessagingService', useExisting: MessagingService };
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService };
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExisting: ProxyAccountService };
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService };
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
const $NotesChart: Provider = { provide: 'NotesChart', useExisting: NotesChart };
const $UsersChart: Provider = { provide: 'UsersChart', useExisting: UsersChart };
const $ActiveUsersChart: Provider = { provide: 'ActiveUsersChart', useExisting: ActiveUsersChart };
const $InstanceChart: Provider = { provide: 'InstanceChart', useExisting: InstanceChart };
const $PerUserNotesChart: Provider = { provide: 'PerUserNotesChart', useExisting: PerUserNotesChart };
const $DriveChart: Provider = { provide: 'DriveChart', useExisting: DriveChart };
const $PerUserReactionsChart: Provider = { provide: 'PerUserReactionsChart', useExisting: PerUserReactionsChart };
const $HashtagChart: Provider = { provide: 'HashtagChart', useExisting: HashtagChart };
const $PerUserFollowingChart: Provider = { provide: 'PerUserFollowingChart', useExisting: PerUserFollowingChart };
const $PerUserDriveChart: Provider = { provide: 'PerUserDriveChart', useExisting: PerUserDriveChart };
const $ApRequestChart: Provider = { provide: 'ApRequestChart', useExisting: ApRequestChart };
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
const $LoggerService: Provider = {
provide: "LoggerService",
useExisting: LoggerService,
};
const $AccountUpdateService: Provider = {
provide: "AccountUpdateService",
useExisting: AccountUpdateService,
};
// const $AiService: Provider = { provide: "AiService", useExisting: AiService };
const $AntennaService: Provider = {
provide: "AntennaService",
useExisting: AntennaService,
};
const $AppLockService: Provider = {
provide: "AppLockService",
useExisting: AppLockService,
};
const $CaptchaService: Provider = {
provide: "CaptchaService",
useExisting: CaptchaService,
};
const $CreateNotificationService: Provider = {
provide: "CreateNotificationService",
useExisting: CreateNotificationService,
};
const $CreateSystemUserService: Provider = {
provide: "CreateSystemUserService",
useExisting: CreateSystemUserService,
};
const $CustomEmojiService: Provider = {
provide: "CustomEmojiService",
useExisting: CustomEmojiService,
};
const $DeleteAccountService: Provider = {
provide: "DeleteAccountService",
useExisting: DeleteAccountService,
};
const $DownloadService: Provider = {
provide: "DownloadService",
useExisting: DownloadService,
};
const $DriveService: Provider = {
provide: "DriveService",
useExisting: DriveService,
};
const $EmailService: Provider = {
provide: "EmailService",
useExisting: EmailService,
};
const $FederatedInstanceService: Provider = {
provide: "FederatedInstanceService",
useExisting: FederatedInstanceService,
};
const $FetchInstanceMetadataService: Provider = {
provide: "FetchInstanceMetadataService",
useExisting: FetchInstanceMetadataService,
};
const $GlobalEventService: Provider = {
provide: "GlobalEventService",
useExisting: GlobalEventService,
};
const $HashtagService: Provider = {
provide: "HashtagService",
useExisting: HashtagService,
};
const $HttpRequestService: Provider = {
provide: "HttpRequestService",
useExisting: HttpRequestService,
};
const $IdService: Provider = { provide: "IdService", useExisting: IdService };
const $ImageProcessingService: Provider = {
provide: "ImageProcessingService",
useExisting: ImageProcessingService,
};
const $InstanceActorService: Provider = {
provide: "InstanceActorService",
useExisting: InstanceActorService,
};
const $InternalStorageService: Provider = {
provide: "InternalStorageService",
useExisting: InternalStorageService,
};
const $MessagingService: Provider = {
provide: "MessagingService",
useExisting: MessagingService,
};
const $MetaService: Provider = {
provide: "MetaService",
useExisting: MetaService,
};
const $MfmService: Provider = {
provide: "MfmService",
useExisting: MfmService,
};
const $ModerationLogService: Provider = {
provide: "ModerationLogService",
useExisting: ModerationLogService,
};
const $NoteCreateService: Provider = {
provide: "NoteCreateService",
useExisting: NoteCreateService,
};
const $NoteDeleteService: Provider = {
provide: "NoteDeleteService",
useExisting: NoteDeleteService,
};
const $NotePiningService: Provider = {
provide: "NotePiningService",
useExisting: NotePiningService,
};
const $NoteReadService: Provider = {
provide: "NoteReadService",
useExisting: NoteReadService,
};
const $NotificationService: Provider = {
provide: "NotificationService",
useExisting: NotificationService,
};
const $PollService: Provider = {
provide: "PollService",
useExisting: PollService,
};
const $ProxyAccountService: Provider = {
provide: "ProxyAccountService",
useExisting: ProxyAccountService,
};
const $PushNotificationService: Provider = {
provide: "PushNotificationService",
useExisting: PushNotificationService,
};
const $QueryService: Provider = {
provide: "QueryService",
useExisting: QueryService,
};
const $ReactionService: Provider = {
provide: "ReactionService",
useExisting: ReactionService,
};
const $RelayService: Provider = {
provide: "RelayService",
useExisting: RelayService,
};
const $S3Service: Provider = { provide: "S3Service", useExisting: S3Service };
const $SignupService: Provider = {
provide: "SignupService",
useExisting: SignupService,
};
const $TwoFactorAuthenticationService: Provider = {
provide: "TwoFactorAuthenticationService",
useExisting: TwoFactorAuthenticationService,
};
const $UserBlockingService: Provider = {
provide: "UserBlockingService",
useExisting: UserBlockingService,
};
const $UserCacheService: Provider = {
provide: "UserCacheService",
useExisting: UserCacheService,
};
const $UserFollowingService: Provider = {
provide: "UserFollowingService",
useExisting: UserFollowingService,
};
const $UserKeypairStoreService: Provider = {
provide: "UserKeypairStoreService",
useExisting: UserKeypairStoreService,
};
const $UserListService: Provider = {
provide: "UserListService",
useExisting: UserListService,
};
const $UserMutingService: Provider = {
provide: "UserMutingService",
useExisting: UserMutingService,
};
const $UserSuspendService: Provider = {
provide: "UserSuspendService",
useExisting: UserSuspendService,
};
const $VideoProcessingService: Provider = {
provide: "VideoProcessingService",
useExisting: VideoProcessingService,
};
const $WebhookService: Provider = {
provide: "WebhookService",
useExisting: WebhookService,
};
const $UtilityService: Provider = {
provide: "UtilityService",
useExisting: UtilityService,
};
const $FileInfoService: Provider = {
provide: "FileInfoService",
useExisting: FileInfoService,
};
const $ChartLoggerService: Provider = {
provide: "ChartLoggerService",
useExisting: ChartLoggerService,
};
const $FederationChart: Provider = {
provide: "FederationChart",
useExisting: FederationChart,
};
const $NotesChart: Provider = {
provide: "NotesChart",
useExisting: NotesChart,
};
const $UsersChart: Provider = {
provide: "UsersChart",
useExisting: UsersChart,
};
const $ActiveUsersChart: Provider = {
provide: "ActiveUsersChart",
useExisting: ActiveUsersChart,
};
const $InstanceChart: Provider = {
provide: "InstanceChart",
useExisting: InstanceChart,
};
const $PerUserNotesChart: Provider = {
provide: "PerUserNotesChart",
useExisting: PerUserNotesChart,
};
const $DriveChart: Provider = {
provide: "DriveChart",
useExisting: DriveChart,
};
const $PerUserReactionsChart: Provider = {
provide: "PerUserReactionsChart",
useExisting: PerUserReactionsChart,
};
const $HashtagChart: Provider = {
provide: "HashtagChart",
useExisting: HashtagChart,
};
const $PerUserFollowingChart: Provider = {
provide: "PerUserFollowingChart",
useExisting: PerUserFollowingChart,
};
const $PerUserDriveChart: Provider = {
provide: "PerUserDriveChart",
useExisting: PerUserDriveChart,
};
const $ApRequestChart: Provider = {
provide: "ApRequestChart",
useExisting: ApRequestChart,
};
const $ChartManagementService: Provider = {
provide: "ChartManagementService",
useExisting: ChartManagementService,
};
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService };
const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService };
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
const $MessagingMessageEntityService: Provider = { provide: 'MessagingMessageEntityService', useExisting: MessagingMessageEntityService };
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
const $NoteFavoriteEntityService: Provider = { provide: 'NoteFavoriteEntityService', useExisting: NoteFavoriteEntityService };
const $NoteReactionEntityService: Provider = { provide: 'NoteReactionEntityService', useExisting: NoteReactionEntityService };
const $NotificationEntityService: Provider = { provide: 'NotificationEntityService', useExisting: NotificationEntityService };
const $PageEntityService: Provider = { provide: 'PageEntityService', useExisting: PageEntityService };
const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', useExisting: PageLikeEntityService };
const $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService };
const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting: UserEntityService };
const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService };
const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService };
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
const $AbuseUserReportEntityService: Provider = {
provide: "AbuseUserReportEntityService",
useExisting: AbuseUserReportEntityService,
};
const $AntennaEntityService: Provider = {
provide: "AntennaEntityService",
useExisting: AntennaEntityService,
};
const $AppEntityService: Provider = {
provide: "AppEntityService",
useExisting: AppEntityService,
};
const $AuthSessionEntityService: Provider = {
provide: "AuthSessionEntityService",
useExisting: AuthSessionEntityService,
};
const $BlockingEntityService: Provider = {
provide: "BlockingEntityService",
useExisting: BlockingEntityService,
};
const $ChannelEntityService: Provider = {
provide: "ChannelEntityService",
useExisting: ChannelEntityService,
};
const $ClipEntityService: Provider = {
provide: "ClipEntityService",
useExisting: ClipEntityService,
};
const $DriveFileEntityService: Provider = {
provide: "DriveFileEntityService",
useExisting: DriveFileEntityService,
};
const $DriveFolderEntityService: Provider = {
provide: "DriveFolderEntityService",
useExisting: DriveFolderEntityService,
};
const $EmojiEntityService: Provider = {
provide: "EmojiEntityService",
useExisting: EmojiEntityService,
};
const $FollowingEntityService: Provider = {
provide: "FollowingEntityService",
useExisting: FollowingEntityService,
};
const $FollowRequestEntityService: Provider = {
provide: "FollowRequestEntityService",
useExisting: FollowRequestEntityService,
};
const $GalleryLikeEntityService: Provider = {
provide: "GalleryLikeEntityService",
useExisting: GalleryLikeEntityService,
};
const $GalleryPostEntityService: Provider = {
provide: "GalleryPostEntityService",
useExisting: GalleryPostEntityService,
};
const $HashtagEntityService: Provider = {
provide: "HashtagEntityService",
useExisting: HashtagEntityService,
};
const $InstanceEntityService: Provider = {
provide: "InstanceEntityService",
useExisting: InstanceEntityService,
};
const $MessagingMessageEntityService: Provider = {
provide: "MessagingMessageEntityService",
useExisting: MessagingMessageEntityService,
};
const $ModerationLogEntityService: Provider = {
provide: "ModerationLogEntityService",
useExisting: ModerationLogEntityService,
};
const $MutingEntityService: Provider = {
provide: "MutingEntityService",
useExisting: MutingEntityService,
};
const $NoteEntityService: Provider = {
provide: "NoteEntityService",
useExisting: NoteEntityService,
};
const $NoteFavoriteEntityService: Provider = {
provide: "NoteFavoriteEntityService",
useExisting: NoteFavoriteEntityService,
};
const $NoteReactionEntityService: Provider = {
provide: "NoteReactionEntityService",
useExisting: NoteReactionEntityService,
};
const $NotificationEntityService: Provider = {
provide: "NotificationEntityService",
useExisting: NotificationEntityService,
};
const $PageEntityService: Provider = {
provide: "PageEntityService",
useExisting: PageEntityService,
};
const $PageLikeEntityService: Provider = {
provide: "PageLikeEntityService",
useExisting: PageLikeEntityService,
};
const $SigninEntityService: Provider = {
provide: "SigninEntityService",
useExisting: SigninEntityService,
};
const $UserEntityService: Provider = {
provide: "UserEntityService",
useExisting: UserEntityService,
};
const $UserGroupEntityService: Provider = {
provide: "UserGroupEntityService",
useExisting: UserGroupEntityService,
};
const $UserGroupInvitationEntityService: Provider = {
provide: "UserGroupInvitationEntityService",
useExisting: UserGroupInvitationEntityService,
};
const $UserListEntityService: Provider = {
provide: "UserListEntityService",
useExisting: UserListEntityService,
};
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
const $ApDeliverManagerService: Provider = { provide: 'ApDeliverManagerService', useExisting: ApDeliverManagerService };
const $ApInboxService: Provider = { provide: 'ApInboxService', useExisting: ApInboxService };
const $ApLoggerService: Provider = { provide: 'ApLoggerService', useExisting: ApLoggerService };
const $ApMfmService: Provider = { provide: 'ApMfmService', useExisting: ApMfmService };
const $ApRendererService: Provider = { provide: 'ApRendererService', useExisting: ApRendererService };
const $ApRequestService: Provider = { provide: 'ApRequestService', useExisting: ApRequestService };
const $ApResolverService: Provider = { provide: 'ApResolverService', useExisting: ApResolverService };
const $LdSignatureService: Provider = { provide: 'LdSignatureService', useExisting: LdSignatureService };
const $RemoteLoggerService: Provider = { provide: 'RemoteLoggerService', useExisting: RemoteLoggerService };
const $ResolveUserService: Provider = { provide: 'ResolveUserService', useExisting: ResolveUserService };
const $WebfingerService: Provider = { provide: 'WebfingerService', useExisting: WebfingerService };
const $ApImageService: Provider = { provide: 'ApImageService', useExisting: ApImageService };
const $ApMentionService: Provider = { provide: 'ApMentionService', useExisting: ApMentionService };
const $ApNoteService: Provider = { provide: 'ApNoteService', useExisting: ApNoteService };
const $ApPersonService: Provider = { provide: 'ApPersonService', useExisting: ApPersonService };
const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting: ApQuestionService };
const $ApAudienceService: Provider = {
provide: "ApAudienceService",
useExisting: ApAudienceService,
};
const $ApDbResolverService: Provider = {
provide: "ApDbResolverService",
useExisting: ApDbResolverService,
};
const $ApDeliverManagerService: Provider = {
provide: "ApDeliverManagerService",
useExisting: ApDeliverManagerService,
};
const $ApInboxService: Provider = {
provide: "ApInboxService",
useExisting: ApInboxService,
};
const $ApLoggerService: Provider = {
provide: "ApLoggerService",
useExisting: ApLoggerService,
};
const $ApMfmService: Provider = {
provide: "ApMfmService",
useExisting: ApMfmService,
};
const $ApRendererService: Provider = {
provide: "ApRendererService",
useExisting: ApRendererService,
};
const $ApRequestService: Provider = {
provide: "ApRequestService",
useExisting: ApRequestService,
};
const $ApResolverService: Provider = {
provide: "ApResolverService",
useExisting: ApResolverService,
};
const $LdSignatureService: Provider = {
provide: "LdSignatureService",
useExisting: LdSignatureService,
};
const $RemoteLoggerService: Provider = {
provide: "RemoteLoggerService",
useExisting: RemoteLoggerService,
};
const $ResolveUserService: Provider = {
provide: "ResolveUserService",
useExisting: ResolveUserService,
};
const $WebfingerService: Provider = {
provide: "WebfingerService",
useExisting: WebfingerService,
};
const $ApImageService: Provider = {
provide: "ApImageService",
useExisting: ApImageService,
};
const $ApMentionService: Provider = {
provide: "ApMentionService",
useExisting: ApMentionService,
};
const $ApNoteService: Provider = {
provide: "ApNoteService",
useExisting: ApNoteService,
};
const $ApPersonService: Provider = {
provide: "ApPersonService",
useExisting: ApPersonService,
};
const $ApQuestionService: Provider = {
provide: "ApQuestionService",
useExisting: ApQuestionService,
};
//#endregion
@Module({
imports: [
QueueModule,
],
imports: [QueueModule],
providers: [
LoggerService,
AccountUpdateService,
AiService,
// AiService,
AntennaService,
AppLockService,
CaptchaService,
@ -358,7 +686,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#region 文字列ベースでのinjection用(循環参照対応のため)
$LoggerService,
$AccountUpdateService,
$AiService,
// $AiService,
$AntennaService,
$AppLockService,
$CaptchaService,
@ -475,7 +803,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
QueueModule,
LoggerService,
AccountUpdateService,
AiService,
// AiService,
AntennaService,
AppLockService,
CaptchaService,
@ -590,7 +918,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#region 文字列ベースでのinjection用(循環参照対応のため)
$LoggerService,
$AccountUpdateService,
$AiService,
// $AiService,
$AntennaService,
$AppLockService,
$CaptchaService,
@ -703,4 +1031,4 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
//#endregion
],
})
export class CoreModule {}
export class CoreModule { }

View File

@ -1,19 +1,19 @@
import * as fs from 'node:fs';
import * as crypto from 'node:crypto';
import { join } from 'node:path';
import * as stream from 'node:stream';
import * as util from 'node:util';
import { Inject, Injectable } from '@nestjs/common';
import { FSWatcher } from 'chokidar';
import { fileTypeFromFile } from 'file-type';
import FFmpeg from 'fluent-ffmpeg';
import isSvg from 'is-svg';
import probeImageSize from 'probe-image-size';
import { type predictionType } from 'nsfwjs';
import sharp from 'sharp';
import { encode } from 'blurhash';
import { createTempDir } from '@/misc/create-temp.js';
import { AiService } from '@/core/AiService.js';
import * as fs from "node:fs";
import * as crypto from "node:crypto";
import { join } from "node:path";
import * as stream from "node:stream";
import * as util from "node:util";
import { Inject, Injectable } from "@nestjs/common";
import { FSWatcher } from "chokidar";
import { fileTypeFromFile } from "file-type";
import FFmpeg from "fluent-ffmpeg";
import isSvg from "is-svg";
import probeImageSize from "probe-image-size";
import { type predictionType } from "nsfwjs";
import sharp from "sharp";
import { encode } from "blurhash";
import { createTempDir } from "@/misc/create-temp.js";
// import { AiService } from '@/core/AiService.js';
const pipeline = util.promisify(stream.pipeline);
@ -34,30 +34,30 @@ export type FileInfo = {
};
const TYPE_OCTET_STREAM = {
mime: 'application/octet-stream',
mime: "application/octet-stream",
ext: null,
};
const TYPE_SVG = {
mime: 'image/svg+xml',
ext: 'svg',
mime: "image/svg+xml",
ext: "svg",
};
@Injectable()
export class FileInfoService {
constructor(
private aiService: AiService,
) {
}
constructor() {} // private aiService: AiService,
/**
* Get file information
*/
public async getFileInfo(path: string, opts: {
skipSensitiveDetection: boolean;
sensitiveThreshold?: number;
sensitiveThresholdForPorn?: number;
enableSensitiveMediaDetectionForVideos?: boolean;
}): Promise<FileInfo> {
public async getFileInfo(
path: string,
opts: {
skipSensitiveDetection: boolean;
sensitiveThreshold?: number;
sensitiveThresholdForPorn?: number;
enableSensitiveMediaDetectionForVideos?: boolean;
},
): Promise<FileInfo> {
const warnings = [] as string[];
const size = await this.getFileSize(path);
@ -70,24 +70,36 @@ export class FileInfoService {
let height: number | undefined;
let orientation: number | undefined;
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
const imageSize = await this.detectImageSize(path).catch(e => {
if (
[
"image/jpeg",
"image/gif",
"image/png",
"image/apng",
"image/webp",
"image/bmp",
"image/tiff",
"image/svg+xml",
"image/vnd.adobe.photoshop",
].includes(type.mime)
) {
const imageSize = await this.detectImageSize(path).catch((e) => {
warnings.push(`detectImageSize failed: ${e}`);
return undefined;
});
// うまく判定できない画像は octet-stream にする
if (!imageSize) {
warnings.push('cannot detect image dimensions');
warnings.push("cannot detect image dimensions");
type = TYPE_OCTET_STREAM;
} else if (imageSize.wUnits === 'px') {
} else if (imageSize.wUnits === "px") {
width = imageSize.width;
height = imageSize.height;
orientation = imageSize.orientation;
// 制限を超えている画像は octet-stream にする
if (imageSize.width > 16383 || imageSize.height > 16383) {
warnings.push('image dimensions exceeds limits');
warnings.push("image dimensions exceeds limits");
type = TYPE_OCTET_STREAM;
}
} else {
@ -97,8 +109,17 @@ export class FileInfoService {
let blurhash: string | undefined;
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) {
blurhash = await this.getBlurhash(path).catch(e => {
if (
[
"image/jpeg",
"image/gif",
"image/png",
"image/apng",
"image/webp",
"image/svg+xml",
].includes(type.mime)
) {
blurhash = await this.getBlurhash(path).catch((e) => {
warnings.push(`getBlurhash failed: ${e}`);
return undefined;
});
@ -114,11 +135,14 @@ export class FileInfoService {
opts.sensitiveThreshold ?? 0.5,
opts.sensitiveThresholdForPorn ?? 0.75,
opts.enableSensitiveMediaDetectionForVideos ?? false,
).then(value => {
[sensitive, porn] = value;
}, error => {
warnings.push(`detectSensitivity failed: ${error}`);
});
).then(
(value) => {
[sensitive, porn] = value;
},
(error) => {
warnings.push(`detectSensitivity failed: ${error}`);
},
);
}
return {
@ -135,71 +159,100 @@ export class FileInfoService {
};
}
private async detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> {
private async detectSensitivity(
source: string,
mime: string,
sensitiveThreshold: number,
sensitiveThresholdForPorn: number,
analyzeVideo: boolean,
): Promise<[sensitive: boolean, porn: boolean]> {
let sensitive = false;
let porn = false;
function judgePrediction(result: readonly predictionType[]): [sensitive: boolean, porn: boolean] {
function judgePrediction(
result: readonly predictionType[],
): [sensitive: boolean, porn: boolean] {
let sensitive = false;
let porn = false;
if ((result.find(x => x.className === 'Sexy')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Hentai')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThresholdForPorn) porn = true;
if (
(result.find((x) => x.className === "Sexy")?.probability ?? 0) >
sensitiveThreshold
)
sensitive = true;
if (
(result.find((x) => x.className === "Hentai")?.probability ?? 0) >
sensitiveThreshold
)
sensitive = true;
if (
(result.find((x) => x.className === "Porn")?.probability ?? 0) >
sensitiveThreshold
)
sensitive = true;
if (
(result.find((x) => x.className === "Porn")?.probability ?? 0) >
sensitiveThresholdForPorn
)
porn = true;
return [sensitive, porn];
}
if (['image/jpeg', 'image/png', 'image/webp'].includes(mime)) {
const result = await this.aiService.detectSensitive(source);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
} else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
if (["image/jpeg", "image/png", "image/webp"].includes(mime)) {
// const result = await this.aiService.detectSensitive(source);
// if (result) {
// [sensitive, porn] = judgePrediction(result);
// }
} else if (
analyzeVideo &&
(mime === "image/apng" || mime.startsWith("video/"))
) {
const [outDir, disposeOutDir] = await createTempDir();
try {
const command = FFmpeg()
.input(source)
.inputOptions([
'-skip_frame', 'nokey', // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
'-lowres', '3', // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
"-skip_frame",
"nokey", // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
"-lowres",
"3", // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
])
.noAudio()
.videoFilters([
{
filter: 'select', // フレームのフィルタリング
filter: "select", // フレームのフィルタリング
options: {
e: 'eq(pict_type,PICT_TYPE_I)', // I-Frame のみをフィルタするVP9 とかはデコードしてみないとわからないっぽい)
e: "eq(pict_type,PICT_TYPE_I)", // I-Frame のみをフィルタするVP9 とかはデコードしてみないとわからないっぽい)
},
},
{
filter: 'blackframe', // 暗いフレームの検出
filter: "blackframe", // 暗いフレームの検出
options: {
amount: '0', // 暗さに関わらず全てのフレームで測定値を取る
amount: "0", // 暗さに関わらず全てのフレームで測定値を取る
},
},
{
filter: 'metadata',
filter: "metadata",
options: {
mode: 'select', // フレーム選択モード
key: 'lavfi.blackframe.pblack', // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
value: '50',
function: 'less', // 50% 未満のフレームを選択する50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
mode: "select", // フレーム選択モード
key: "lavfi.blackframe.pblack", // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
value: "50",
function: "less", // 50% 未満のフレームを選択する50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
},
},
{
filter: 'scale',
filter: "scale",
options: {
w: 299,
h: 299,
},
},
])
.format('image2')
.output(join(outDir, '%d.png'))
.outputOptions(['-vsync', '0']); // 可変フレームレートにすることで穴埋めをさせない
.format("image2")
.output(join(outDir, "%d.png"))
.outputOptions(["-vsync", "0"]); // 可変フレームレートにすることで穴埋めをさせない
const results: ReturnType<typeof judgePrediction>[] = [];
let frameIndex = 0;
let targetIndex = 0;
@ -212,53 +265,63 @@ export class FileInfoService {
}
targetIndex = nextIndex;
nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける
const result = await this.aiService.detectSensitive(path);
if (result) {
results.push(judgePrediction(result));
}
// const result = await this.aiService.detectSensitive(path);
// if (result) {
// results.push(judgePrediction(result));
// }
} finally {
fs.promises.unlink(path);
}
}
sensitive = results.filter(x => x[0]).length >= Math.ceil(results.length * sensitiveThreshold);
porn = results.filter(x => x[1]).length >= Math.ceil(results.length * sensitiveThresholdForPorn);
sensitive =
results.filter((x) => x[0]).length >=
Math.ceil(results.length * sensitiveThreshold);
porn =
results.filter((x) => x[1]).length >=
Math.ceil(results.length * sensitiveThresholdForPorn);
} finally {
disposeOutDir();
}
}
return [sensitive, porn];
}
private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> {
private async *asyncIterateFrames(
cwd: string,
command: FFmpeg.FfmpegCommand,
): AsyncGenerator<string, void> {
const watcher = new FSWatcher({
cwd,
disableGlobbing: true,
});
let finished = false;
command.once('end', () => {
command.once("end", () => {
finished = true;
watcher.close();
});
command.run();
for (let i = 1; true; i++) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
for (let i = 1; true; i++) {
// eslint-disable-line @typescript-eslint/no-unnecessary-condition
const current = `${i}.png`;
const next = `${i + 1}.png`;
const framePath = join(cwd, current);
if (await this.exists(join(cwd, next))) {
yield framePath;
} else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
} else if (!finished) {
// eslint-disable-line @typescript-eslint/no-unnecessary-condition
watcher.add(next);
await new Promise<void>((resolve, reject) => {
watcher.on('add', function onAdd(path) {
if (path === next) { // 次フレームの書き出しが始まっているなら、現在フレームの書き出しは終わっている
watcher.on("add", function onAdd(path) {
if (path === next) {
// 次フレームの書き出しが始まっているなら、現在フレームの書き出しは終わっている
watcher.unwatch(current);
watcher.off('add', onAdd);
watcher.off("add", onAdd);
resolve();
}
});
command.once('end', resolve); // 全てのフレームを処理し終わったなら、最終フレームである現在フレームの書き出しは終わっている
command.once('error', reject);
command.once("end", resolve); // 全てのフレームを処理し終わったなら、最終フレームである現在フレームの書き出しは終わっている
command.once("error", reject);
});
yield framePath;
} else if (await this.exists(framePath)) {
@ -268,19 +331,22 @@ export class FileInfoService {
}
}
}
private exists(path: string): Promise<boolean> {
return fs.promises.access(path).then(() => true, () => false);
return fs.promises.access(path).then(
() => true,
() => false,
);
}
/**
* Detect MIME Type and extension
*/
public async detectType(path: string): Promise<{
mime: string;
ext: string | null;
}> {
// Check 0 byte
mime: string;
ext: string | null;
}> {
// Check 0 byte
const fileSize = await this.getFileSize(path);
if (fileSize === 0) {
return TYPE_OCTET_STREAM;
@ -289,8 +355,8 @@ export class FileInfoService {
const type = await fileTypeFromFile(path);
if (type) {
// XMLはSVGかもしれない
if (type.mime === 'application/xml' && await this.checkSvg(path)) {
// XMLはSVGかもしれない
if (type.mime === "application/xml" && (await this.checkSvg(path))) {
return TYPE_SVG;
}
@ -334,7 +400,7 @@ export class FileInfoService {
* Calculate MD5 hash
*/
private async calcHash(path: string): Promise<string> {
const hash = crypto.createHash('md5').setEncoding('hex');
const hash = crypto.createHash("md5").setEncoding("hex");
await pipeline(fs.createReadStream(path), hash);
return hash.read();
}
@ -343,12 +409,12 @@ export class FileInfoService {
* Detect dimensions of image
*/
private async detectImageSize(path: string): Promise<{
width: number;
height: number;
wUnits: string;
hUnits: string;
orientation?: number;
}> {
width: number;
height: number;
wUnits: string;
hUnits: string;
orientation?: number;
}> {
const readable = fs.createReadStream(path);
const imageSize = await probeImageSize(readable);
readable.destroy();
@ -363,7 +429,7 @@ export class FileInfoService {
sharp(path)
.raw()
.ensureAlpha()
.resize(64, 64, { fit: 'inside' })
.resize(64, 64, { fit: "inside" })
.toBuffer((err, buffer, { width, height }) => {
if (err) return reject(err);

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,98 @@
module.exports = {
root: true,
env: {
'node': false,
node: false,
},
parser: 'vue-eslint-parser',
parser: "vue-eslint-parser",
parserOptions: {
'parser': '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.vue'],
project: ["./tsconfig.json"],
extraFileExtensions: [".vue"],
},
extends: [
'../shared/.eslintrc.js',
'plugin:vue/vue3-recommended',
],
extends: ["../shared/.eslintrc.js", "plugin:vue/vue3-recommended"],
rules: {
'@typescript-eslint/no-empty-interface': [
'error',
"@typescript-eslint/no-empty-interface": [
"error",
{
'allowSingleExtends': true,
allowSingleExtends: true,
},
],
'@typescript-eslint/prefer-nullish-coalescing': [
'error',
],
"@typescript-eslint/prefer-nullish-coalescing": ["error"],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
'id-denylist': ['error', 'window', 'e'],
'no-shadow': ['warn'],
'vue/attributes-order': ['error', {
'alphabetical': false,
}],
'vue/no-use-v-if-with-v-for': ['error', {
'allowUsingIterationVar': false,
}],
'vue/no-ref-as-operand': 'error',
'vue/no-multi-spaces': ['error', {
'ignoreProperties': false,
}],
'vue/no-v-html': 'warn',
'vue/order-in-components': 'error',
'vue/html-indent': ['warn', 'tab', {
'attribute': 1,
'baseIndent': 0,
'closeBracket': 0,
'alignAttributesVertically': true,
'ignores': [],
}],
'vue/html-closing-bracket-spacing': ['warn', {
'startTag': 'never',
'endTag': 'never',
'selfClosingTag': 'never',
}],
'vue/multi-word-component-names': 'warn',
'vue/require-v-for-key': 'warn',
'vue/no-unused-components': 'warn',
'vue/valid-v-for': 'warn',
'vue/return-in-computed-property': 'warn',
'vue/no-setup-props-destructure': 'warn',
'vue/max-attributes-per-line': 'off',
'vue/html-self-closing': 'off',
'vue/singleline-html-element-content-newline': 'off',
"id-denylist": ["error", "window", "e"],
"no-shadow": ["warn"],
"vue/attributes-order": [
"error",
{
alphabetical: false,
},
],
"vue/no-use-v-if-with-v-for": [
"error",
{
allowUsingIterationVar: false,
},
],
"vue/no-ref-as-operand": "error",
"vue/no-multi-spaces": [
"error",
{
ignoreProperties: false,
},
],
"vue/no-v-html": "warn",
"vue/order-in-components": "error",
"vue/html-indent": [
"warn",
"tab",
{
attribute: 1,
baseIndent: 0,
closeBracket: 0,
alignAttributesVertically: true,
ignores: [],
},
],
"vue/html-closing-bracket-spacing": [
"warn",
{
startTag: "never",
endTag: "never",
selfClosingTag: "never",
},
],
"vue/multi-word-component-names": "warn",
"vue/require-v-for-key": "warn",
"vue/no-unused-components": "warn",
"vue/valid-v-for": "warn",
"vue/return-in-computed-property": "warn",
"vue/no-setup-props-destructure": "warn",
"vue/max-attributes-per-line": "off",
"vue/html-self-closing": "off",
"vue/singleline-html-element-content-newline": "off",
},
globals: {
// Node.js
'module': false,
'require': false,
'__dirname': false,
module: false,
require: false,
__dirname: false,
// Vue
'$$': false,
'$ref': false,
'$shallowRef': false,
'$computed': false,
// '$$': false,
// '$ref': false,
// '$shallowRef': false,
// '$computed': false,
// Misskey
'_DEV_': false,
'_LANGS_': false,
'_VERSION_': false,
'_ENV_': false,
'_PERF_PREFIX_': false,
'_DATA_TRANSFER_DRIVE_FILE_': false,
'_DATA_TRANSFER_DRIVE_FOLDER_': false,
'_DATA_TRANSFER_DECK_COLUMN_': false,
_DEV_: false,
_LANGS_: false,
_VERSION_: false,
_ENV_: false,
_PERF_PREFIX_: false,
_DATA_TRANSFER_DRIVE_FILE_: false,
_DATA_TRANSFER_DRIVE_FOLDER_: false,
_DATA_TRANSFER_DECK_COLUMN_: false,
},
};

View File

@ -3,88 +3,92 @@
"scripts": {
"watch": "vite build --watch --mode development",
"build": "vite build",
"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
"lint": "eslint --quiet \"src/**/*.{ts,vue}\"",
"typecheck:vue": "vue-tsc --noEmit",
"typecheck:tsc": "tsc -p . --noEmit"
},
"resolutions": {
"chokidar": "^3.3.1",
"lodash": "^4.17.21"
},
"dependencies": {
"@discordapp/twemoji": "14.0.2",
"@fortawesome/fontawesome-free": "6.1.2",
"@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0",
"@discordapp/twemoji": "15.1.0",
"@fortawesome/fontawesome-free": "6.6.0",
"@rollup/plugin-alias": "5.1.1",
"@rollup/plugin-json": "6.1.0",
"@syuilo/aiscript": "0.11.1",
"@vitejs/plugin-vue": "3.1.0",
"@vue/compiler-sfc": "3.2.39",
"@vitejs/plugin-vue": "5.1.4",
"@vue/compiler-sfc": "3.5.12",
"autobind-decorator": "2.4.0",
"autosize": "5.0.1",
"blurhash": "1.1.5",
"broadcast-channel": "4.17.0",
"autosize": "6.0.1",
"blurhash": "2.0.5",
"broadcast-channel": "7.0.0",
"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
"chart.js": "3.9.1",
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-gradient": "0.5.1",
"chartjs-plugin-zoom": "1.2.1",
"compare-versions": "5.0.1",
"chart.js": "4.4.4",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1",
"chokidar": "^4.0.1",
"compare-versions": "6.1.1",
"cropperjs": "2.0.0-beta",
"date-fns": "2.29.3",
"date-fns": "4.1.0",
"escape-regexp": "0.0.1",
"eventemitter3": "4.0.7",
"idb-keyval": "6.2.0",
"eventemitter3": "5.0.1",
"idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0",
"json5": "2.2.1",
"katex": "0.15.6",
"matter-js": "0.18.0",
"json5": "2.2.3",
"katex": "0.16.11",
"matter-js": "0.20.0",
"mfm-js": "git+https://github.com/sim1222/mfm.js.git",
"misetehoshii": "https://github.com/melt-adzuki/misetehoshii",
"misskey-js": "0.0.14",
"photoswipe": "5.3.2",
"photoswipe": "5.4.4",
"prismjs": "1.29.0",
"punycode": "2.1.1",
"punycode": "2.3.1",
"querystring": "0.2.1",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.54.9",
"sass": "1.79.5",
"seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.144.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.4.2",
"tsc-alias": "1.7.0",
"tsconfig-paths": "4.1.0",
"three": "0.169.0",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typescript": "4.8.3",
"uuid": "9.0.0",
"vanilla-tilt": "1.7.2",
"vite": "3.1.3",
"vue": "3.2.39",
"typescript": "5.6.3",
"uuid": "10.0.0",
"vanilla-tilt": "1.8.1",
"vite": "5.4.8",
"vue": "3.5.12",
"vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "4.0.1"
},
"devDependencies": {
"@types/escape-regexp": "0.0.1",
"@types/glob": "8.0.0",
"@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1",
"@types/katex": "0.14.0",
"@types/matter-js": "0.18.2",
"@types/punycode": "2.1.0",
"@types/seedrandom": "3.0.2",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "5.38.0",
"@typescript-eslint/parser": "5.38.0",
"@types/escape-regexp": "0.0.3",
"@types/glob": "8.1.0",
"@types/gulp": "4.0.17",
"@types/gulp-rename": "2.0.6",
"@types/katex": "0.16.7",
"@types/matter-js": "0.19.7",
"@types/punycode": "2.1.4",
"@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "8.8.1",
"@typescript-eslint/parser": "8.8.1",
"cross-env": "7.0.3",
"cypress": "10.8.0",
"eslint": "8.23.1",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.5.1",
"rollup": "2.79.0",
"start-server-and-test": "1.14.0"
"cypress": "13.15.0",
"eslint": "9.12.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "9.29.0",
"rollup": "4.24.0",
"start-server-and-test": "2.0.8",
"vue-tsc": "2.1.6"
}
}

View File

@ -36,6 +36,8 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
@ -51,11 +53,11 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
let forward = $ref(props.report.forwarded);
let forward = ref(props.report.forwarded);
function resolve() {
os.apiWithDialog('admin/resolve-abuse-user-report', {
forward: forward,
forward: forward.value,
reportId: props.report.id,
}).then(() => {
emit('resolved', props.report.id);

View File

@ -130,44 +130,44 @@ const texts = computed(() => {
});
let enabled = true;
let majorGraduationColor = $ref<string>();
let majorGraduationColor = ref<string>();
//let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>();
let mHandColor = $ref<string>();
let hHandColor = $ref<string>();
let nowColor = $ref<string>();
let h = $ref<number>(0);
let m = $ref<number>(0);
let s = $ref<number>(0);
let hAngle = $ref<number>(0);
let mAngle = $ref<number>(0);
let sAngle = $ref<number>(0);
let disableSAnimate = $ref(false);
let sHandColor = ref<string>();
let mHandColor = ref<string>();
let hHandColor = ref<string>();
let nowColor = ref<string>();
let h = ref<number>(0);
let m = ref<number>(0);
let s = ref<number>(0);
let hAngle = ref<number>(0);
let mAngle = ref<number>(0);
let sAngle = ref<number>(0);
let disableSAnimate = ref(false);
let sOneRound = false;
function tick() {
const now = new Date();
now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle = Math.PI * (m + s / 60) / 30;
s.value = now.getSeconds();
m.value = now.getMinutes();
h.value = now.getHours();
hAngle.value = Math.PI * (h.value % (props.twentyfour ? 24 : 12) + (m.value + s.value / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle.value = Math.PI * (m.value + s.value / 60) / 30;
if (sOneRound) { // (59->0)
sAngle = Math.PI * 60 / 30;
sAngle.value = Math.PI * 60 / 30;
window.setTimeout(() => {
disableSAnimate = true;
disableSAnimate.value = true;
window.setTimeout(() => {
sAngle = 0;
sAngle.value = 0;
window.setTimeout(() => {
disableSAnimate = false;
disableSAnimate.value = false;
}, 100);
}, 100);
}, 700);
} else {
sAngle = Math.PI * s / 30;
sAngle.value = Math.PI * s.value / 30;
}
sOneRound = s === 59;
sOneRound = s.value === 59;
}
tick();
@ -176,12 +176,12 @@ function calcColors() {
const computedStyle = getComputedStyle(document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
hHandColor = accent;
nowColor = accent;
sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
hHandColor.value = accent;
nowColor.value = accent;
}
calcColors();

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import { nextTick, onMounted, ref } from 'vue';
const props = defineProps<{
type?: 'button' | 'submit' | 'reset';
@ -45,13 +45,13 @@ const emit = defineEmits<{
(ev: 'click', payload: MouseEvent): void;
}>();
let el = $ref<HTMLElement | null>(null);
let ripples = $ref<HTMLElement | null>(null);
let el = ref<HTMLElement | null>(null);
let ripples = ref<HTMLElement | null>(null);
onMounted(() => {
if (props.autofocus) {
nextTick(() => {
el!.focus();
el.value!.focus();
});
}
});
@ -77,7 +77,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
ripples!.appendChild(ripple);
ripples.value!.appendChild(ripple);
const circleCenterX = evt.clientX - rect.left;
const circleCenterY = evt.clientY - rect.top;
@ -92,7 +92,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.opacity = '0';
}, 1000);
window.setTimeout(() => {
if (ripples) ripples.removeChild(ripple);
if (ripples.value) ripples.value.removeChild(ripple);
}, 2000);
}
</script>

View File

@ -7,7 +7,7 @@
</template>
<script lang="ts" setup>
import { onMounted, onBeforeUnmount } from 'vue';
import { onMounted, onBeforeUnmount, ref } from 'vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from './types/menu.vue';
import contains from '@/scripts/contains';
@ -22,16 +22,16 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let rootEl = $ref<HTMLDivElement>();
let rootEl = ref<HTMLDivElement>();
let zIndex = $ref<number>(os.claimZIndex('high'));
let zIndex = ref<number>(os.claimZIndex('high'));
onMounted(() => {
let left = props.ev.pageX + 1; // + 1
let top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight;
const width = rootEl.value.offsetWidth;
const height = rootEl.value.offsetHeight;
if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset;
@ -49,8 +49,8 @@ onMounted(() => {
left = 0;
}
rootEl.style.top = `${top}px`;
rootEl.style.left = `${left}px`;
rootEl.value.style.top = `${top}px`;
rootEl.value.style.left = `${left}px`;
for (const el of Array.from(document.querySelectorAll('body *'))) {
el.addEventListener('mousedown', onMousedown);
@ -64,7 +64,7 @@ onBeforeUnmount(() => {
});
function onMousedown(evt: Event) {
if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
if (!contains(rootEl.value, evt.target) && (rootEl.value !== evt.target)) emit('closed');
}
</script>

View File

@ -26,7 +26,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import { nextTick, onMounted, ref } from 'vue';
import * as misskey from 'misskey-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
@ -52,10 +52,10 @@ const props = defineProps<{
const imgUrl = `${url}/proxy/image.webp?${query({
url: props.file.url,
})}`;
let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
let imgEl = $ref<HTMLImageElement>();
let dialogEl = ref<InstanceType<typeof XModalWindow>>();
let imgEl = ref<HTMLImageElement>();
let cropper: Cropper | null = null;
let loading = $ref(true);
let loading = ref(true);
const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
@ -84,16 +84,16 @@ const ok = async () => {
const f = await promise;
emit('ok', f);
dialogEl.close();
dialogEl.value.close();
};
const cancel = () => {
emit('cancel');
dialogEl.close();
dialogEl.value.close();
};
const onImageLoad = () => {
loading = false;
loading.value = false;
if (cropper) {
cropper.getCropperImage()!.$center('contain');
@ -102,7 +102,7 @@ const onImageLoad = () => {
};
onMounted(() => {
cropper = new Cropper(imgEl, {
cropper = new Cropper(imgEl.value, {
});
const computedStyle = getComputedStyle(document.documentElement);

View File

@ -28,7 +28,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted } from 'vue';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os';
import { stream } from '@/stream';
@ -43,9 +43,9 @@ const props = withDefaults(defineProps<{
large: false,
});
let isFollowing = $ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
let wait = $ref(false);
let isFollowing = ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou);
let wait = ref(false);
const connection = stream.useChannel('main');
if (props.user.isFollowing == null) {
@ -57,16 +57,16 @@ if (props.user.isFollowing == null) {
function onFollowChange(user: Misskey.entities.UserDetailed) {
if (user.id === props.user.id) {
isFollowing = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
}
}
async function onClick() {
wait = true;
wait.value = true;
try {
if (isFollowing) {
if (isFollowing.value) {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
@ -78,22 +78,22 @@ async function onClick() {
userId: props.user.id
});
} else {
if (hasPendingFollowRequestFromYou) {
if (hasPendingFollowRequestFromYou.value) {
await os.api('following/requests/cancel', {
userId: props.user.id
});
hasPendingFollowRequestFromYou = false;
hasPendingFollowRequestFromYou.value = false;
} else {
await os.api('following/create', {
userId: props.user.id
});
hasPendingFollowRequestFromYou = true;
hasPendingFollowRequestFromYou.value = true;
}
}
} catch (err) {
console.error(err);
} finally {
wait = false;
wait.value = false;
}
}

View File

@ -32,7 +32,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import XModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
@ -45,20 +45,20 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let dialog: InstanceType<typeof XModalWindow> = $ref();
let dialog: InstanceType<typeof XModalWindow> = ref();
let username = $ref('');
let email = $ref('');
let processing = $ref(false);
let username = ref('');
let email = ref('');
let processing = ref(false);
async function onSubmit() {
processing = true;
processing.value = true;
await os.apiWithDialog('request-reset-password', {
username,
email,
username: username.value,
email: email.value,
});
emit('done');
dialog.close();
dialog.value.close();
}
</script>

View File

@ -13,7 +13,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import bytes from '@/filters/bytes';
import number from '@/filters/number';
@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>

View File

@ -6,7 +6,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import { decode } from 'blurhash';
import { defaultStore } from '@/store';
@ -30,20 +30,20 @@ const imgSrc = defaultStore.state.mediaProxy ?
:
props.src;
const canvas = $ref<HTMLCanvasElement>();
let loaded = $ref(false);
const canvas = ref<HTMLCanvasElement>();
let loaded = ref(false);
function draw() {
if (props.hash == null) return;
const pixels = decode(props.hash, props.size, props.size);
const ctx = canvas.getContext('2d');
const ctx = canvas.value.getContext('2d');
const imageData = ctx!.createImageData(props.size, props.size);
imageData.data.set(pixels);
ctx!.putImageData(imageData, 0, 0);
}
function onLoad() {
loaded = true;
loaded.value = true;
}
onMounted(() => {

View File

@ -10,6 +10,8 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import * as misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os';
@ -18,12 +20,12 @@ const props = defineProps<{
instance: misskey.entities.Instance;
}>();
let chartValues = $ref<number[] | null>(null);
let chartValues = ref<number[] | null>(null);
os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
//
res.requests.received.splice(0, 1);
chartValues = res.requests.received;
chartValues.value = res.requests.received;
});
</script>

View File

@ -48,7 +48,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import {
Chart,
ArcElement,
@ -98,10 +98,10 @@ const props = withDefaults(defineProps<{
chartLimit: 90,
});
const chartSpan = $ref<'hour' | 'day'>('hour');
const chartSrc = $ref('active-users');
let subDoughnutEl = $ref<HTMLCanvasElement>();
let pubDoughnutEl = $ref<HTMLCanvasElement>();
const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users');
let subDoughnutEl = ref<HTMLCanvasElement>();
let pubDoughnutEl = ref<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip();
const { handler: externalTooltipHandler2 } = useChartTooltip();
@ -156,7 +156,7 @@ function createDoughnut(chartEl, tooltip, data) {
onMounted(() => {
os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
@ -165,7 +165,7 @@ onMounted(() => {
},
})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
createDoughnut(pubDoughnutEl, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,

View File

@ -20,7 +20,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import MkModal from '@/components/MkModal.vue';
import { navbarItemDef } from '@/navbar';
import { instanceName } from '@/config';
@ -44,7 +44,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
deviceKind === 'smartphone' ? 'drawer' :
'dialog';
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
const menu = defaultStore.state.menu;
@ -58,7 +58,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k =>
}));
function close() {
modal.close();
modal.value.close();
}
</script>

View File

@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import { url as local } from '@/config';
import { useTooltip } from '@/scripts/use-tooltip';
import * as os from '@/os';
@ -23,13 +23,13 @@ const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
const el = $ref();
const el = ref();
useTooltip($$(el), (showing) => {
useTooltip((el), (showing) => {
os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
showing,
url: props.url,
source: el,
source: el.value,
}, {}, 'closed');
});
</script>

View File

@ -26,7 +26,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as misskey from 'misskey-js';
import { ColdDeviceStorage } from '@/store';
@ -35,16 +35,16 @@ const props = withDefaults(defineProps<{
}>(), {
});
const audioEl = $ref<HTMLAudioElement | null>();
let hide = $ref(true);
const audioEl = ref<HTMLAudioElement | null>();
let hide = ref(true);
function volumechange() {
if (audioEl) ColdDeviceStorage.set('mediaVolume', audioEl.volume);
if (audioEl.value) ColdDeviceStorage.set('mediaVolume', audioEl.value.volume);
}
onMounted(() => {
// if (audioEl) audioEl.volume = ColdDeviceStorage.get('mediaVolume');
if (audioEl) audioEl.volume = 0.2;
if (audioEl.value) audioEl.value.volume = 0.2;
});
</script>

View File

@ -21,7 +21,7 @@
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import { watch, ref } from 'vue';
import * as misskey from 'misskey-js';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
@ -32,7 +32,7 @@ const props = defineProps<{
raw?: boolean;
}>();
let hide = $ref(true);
let hide = ref(true);
const url = (props.raw || defaultStore.state.loadRawImages)
? props.image.url
@ -42,7 +42,7 @@ const url = (props.raw || defaultStore.state.loadRawImages)
// Plugin:register_note_view_interruptor 使watch
watch(() => props.image, () => {
hide = (defaultStore.state.nsfw === 'force') ? true : props.image.isSensitive && (defaultStore.state.nsfw !== 'ignore');
hide.value = (defaultStore.state.nsfw === 'force') ? true : props.image.isSensitive && (defaultStore.state.nsfw !== 'ignore');
}, {
deep: true,
immediate: true,

View File

@ -32,11 +32,11 @@ const props = defineProps<{
video: misskey.entities.DriveFile;
}>();
const videoEl = $ref<HTMLVideoElement | null>();
const videoEl = ref<HTMLVideoElement | null>();
const hide = ref((defaultStore.state.nsfw === 'force') ? true : props.video.isSensitive && (defaultStore.state.nsfw !== 'ignore'));
onMounted(() => {
if (videoEl) videoEl.volume = 0.2;
if (videoEl.value) videoEl.value.volume = 0.2;
});
</script>

View File

@ -56,7 +56,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch, computed } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus';
import FormSwitch from '@/components/form/switch.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
@ -78,19 +78,19 @@ const emit = defineEmits<{
(ev: 'close', actioned?: boolean): void;
}>();
let itemsEl = $ref<HTMLDivElement>();
let itemsEl = ref<HTMLDivElement>();
let items2: InnerMenuItem[] = $ref([]);
let items2: InnerMenuItem[] = ref([]);
let child = $ref<InstanceType<typeof XChild>>();
let child = ref<InstanceType<typeof XChild>>();
let keymap = $computed(() => ({
let keymap = computed(() => ({
'up|k|shift+tab': focusUp,
'down|j|tab': focusDown,
'esc': close,
}));
let childShowingItem = $ref<MenuItem | null>();
let childShowingItem = ref<MenuItem | null>();
watch(() => props.items, () => {
const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined);
@ -101,22 +101,22 @@ watch(() => props.items, () => {
if (item && 'then' in item) { // if item is Promise
items[i] = { type: 'pending' };
item.then(actualItem => {
items2[i] = actualItem;
items2.value[i] = actualItem;
});
}
}
items2 = items as InnerMenuItem[];
items2.value = items as InnerMenuItem[];
}, {
immediate: true,
});
let childMenu = $ref<MenuItem[] | null>();
let childTarget = $ref<HTMLElement | null>();
let childMenu = ref<MenuItem[] | null>();
let childTarget = ref<HTMLElement | null>();
function closeChild() {
childMenu = null;
childShowingItem = null;
childMenu.value = null;
childShowingItem.value = null;
}
function childActioned() {
@ -125,8 +125,8 @@ function childActioned() {
}
function onGlobalMousedown(event: MouseEvent) {
if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return;
if (child && child.checkHit(event)) return;
if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return;
if (child.value && child.value.checkHit(event)) return;
closeChild();
}
@ -145,9 +145,9 @@ async function showChildren(item: MenuItem, ev: MouseEvent) {
os.popupMenu(item.children, ev.currentTarget ?? ev.target);
close();
} else {
childTarget = ev.currentTarget ?? ev.target;
childMenu = item.children;
childShowingItem = item;
childTarget.value = ev.currentTarget ?? ev.target;
childMenu.value = item.children;
childShowingItem.value = item;
}
}
@ -171,7 +171,7 @@ function focusDown() {
onMounted(() => {
if (props.viaKeyboard) {
nextTick(() => {
focusNext(itemsEl.children[0], true, false);
focusNext(itemsEl.value.children[0], true, false);
});
}

View File

@ -26,7 +26,7 @@
</template>
<script lang="ts" setup>
import { onUnmounted, watch } from 'vue';
import { onUnmounted, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import tinycolor from 'tinycolor2';
import { useInterval } from '@/scripts/use-interval';
@ -38,11 +38,11 @@ const props = defineProps<{
const viewBoxX = 50;
const viewBoxY = 50;
const gradientId = uuid();
let polylinePoints = $ref('');
let polygonPoints = $ref('');
let headX = $ref<number | null>(null);
let headY = $ref<number | null>(null);
let clock = $ref<number | null>(null);
let polylinePoints = ref('');
let polygonPoints = ref('');
let headX = ref<number | null>(null);
let headY = ref<number | null>(null);
let clock = ref<number | null>(null);
const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
const color = accent.toRgbString();
@ -55,12 +55,12 @@ function draw(): void {
(1 - (n / peak)) * viewBoxY,
]);
polylinePoints = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
polylinePoints.value = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
polygonPoints = `0,${ viewBoxY } ${ polylinePoints } ${ viewBoxX },${ viewBoxY }`;
polygonPoints.value = `0,${ viewBoxY } ${ polylinePoints.value } ${ viewBoxX },${ viewBoxY }`;
headX = _polylinePoints[_polylinePoints.length - 1][0];
headY = _polylinePoints[_polylinePoints.length - 1][1];
headX.value = _polylinePoints[_polylinePoints.length - 1][0];
headY.value = _polylinePoints[_polylinePoints.length - 1][1];
}
watch(() => props.src, draw, { immediate: true });

View File

@ -10,7 +10,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, watch, provide } from 'vue';
import { nextTick, onMounted, watch, provide, ref, computed } from 'vue';
import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch';
import { defaultStore } from '@/store';
@ -57,13 +57,13 @@ const emit = defineEmits<{
provide('modal', true);
let maxHeight = $ref<number>();
let fixed = $ref(false);
let transformOrigin = $ref('center');
let showing = $ref(true);
let content = $ref<HTMLElement>();
let maxHeight = ref<number>();
let fixed = ref(false);
let transformOrigin = ref('center');
let showing = ref(true);
let content = ref<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
const type = $computed(() => {
const type = computed(() => {
if (props.preferType === 'auto') {
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
return 'drawer';
@ -80,7 +80,7 @@ let contentClicking = false;
const close = () => {
// eslint-disable-next-line vue/no-mutating-props
if (props.src) props.src.style.pointerEvents = 'auto';
showing = false;
showing.value = false;
emit('close');
};
@ -89,8 +89,8 @@ const onBgClick = () => {
emit('click');
};
if (type === 'drawer') {
maxHeight = window.innerHeight / 1.5;
if (type.value === 'drawer') {
maxHeight.value = window.innerHeight / 1.5;
}
const keymap = {
@ -101,21 +101,21 @@ const MARGIN = 16;
const align = () => {
if (props.src == null) return;
if (type === 'drawer') return;
if (type === 'dialog') return;
if (type.value === 'drawer') return;
if (type.value === 'dialog') return;
if (content == null) return;
if (content.value == null) return;
const srcRect = props.src.getBoundingClientRect();
const width = content!.offsetWidth;
const height = content!.offsetHeight;
const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight;
let left;
let top;
const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
if (props.anchor.x === 'center') {
left = x + (props.src.offsetWidth / 2) - (width / 2);
@ -133,7 +133,7 @@ const align = () => {
top = y + props.src.offsetHeight;
}
if (fixed) {
if (fixed.value) {
//
if (left + width > window.innerWidth) {
left = window.innerWidth - width;
@ -146,16 +146,16 @@ const align = () => {
if (top + height > (window.innerHeight - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = (upperSpace + MARGIN) - height;
}
} else {
top = (window.innerHeight - MARGIN) - height;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
} else {
//
@ -170,16 +170,16 @@ const align = () => {
if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = window.pageYOffset + ((upperSpace + MARGIN) - height);
}
} else {
top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
}
@ -194,29 +194,29 @@ const align = () => {
let transformOriginX = 'center';
let transformOriginY = 'center';
if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) {
if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) {
transformOriginY = 'top';
} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) {
transformOriginY = 'bottom';
}
if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) {
if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) {
transformOriginX = 'left';
} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) {
} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) {
transformOriginX = 'right';
}
transformOrigin = `${transformOriginX} ${transformOriginY}`;
transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
content.style.left = left + 'px';
content.style.top = top + 'px';
content.value.style.left = left + 'px';
content.value.style.top = top + 'px';
};
const onOpened = () => {
emit('opened');
//
const el = content!.children[0];
const el = content.value!.children[0];
el.addEventListener('mousedown', ev => {
contentClicking = true;
window.addEventListener('mouseup', ev => {
@ -234,7 +234,7 @@ onMounted(() => {
// eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = 'none';
}
fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
await nextTick();
@ -244,7 +244,7 @@ onMounted(() => {
nextTick(() => {
new ResizeObserver((entries, observer) => {
align();
}).observe(content!);
}).observe(content.value!);
});
});

View File

@ -21,7 +21,7 @@
</template>
<script lang="ts" setup>
import { ComputedRef, provide } from 'vue';
import { ComputedRef, provide, ref, computed } from 'vue';
import MkModal from '@/components/MkModal.vue';
import { popout as _popout } from '@/scripts/popout';
import copyToClipboard from '@/scripts/copy-to-clipboard';
@ -47,26 +47,26 @@ router.addListener('push', ctx => {
});
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let rootEl = $ref();
let modal = $ref<InstanceType<typeof MkModal>>();
let path = $ref(props.initialPath);
let width = $ref(860);
let height = $ref(660);
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let rootEl = ref();
let modal = ref<InstanceType<typeof MkModal>>();
let path = ref(props.initialPath);
let width = ref(860);
let height = ref(660);
const history = [];
provide('router', router);
provideMetadataReceiver((info) => {
pageMetadata = info;
pageMetadata.value = info;
});
provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true);
const pageUrl = $computed(() => url + path);
const contextmenu = $computed(() => {
const pageUrl = computed(() => url + path.value);
const contextmenu = computed(() => {
return [{
type: 'label',
text: path,
text: path.value,
}, {
icon: 'fas fa-expand-alt',
text: i18n.ts.showInPage,
@ -79,14 +79,14 @@ const contextmenu = $computed(() => {
icon: 'fas fa-external-link-alt',
text: i18n.ts.openInNewTab,
action: () => {
window.open(pageUrl, '_blank');
modal.close();
window.open(pageUrl.value, '_blank');
modal.value.close();
},
}, {
icon: 'fas fa-link',
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(pageUrl);
copyToClipboard(pageUrl.value);
},
}];
});
@ -101,17 +101,17 @@ function back() {
}
function expand() {
mainRouter.push(path);
modal.close();
mainRouter.push(path.value);
modal.value.close();
}
function popout() {
_popout(path, rootEl);
modal.close();
_popout(path.value, rootEl.value);
modal.value.close();
}
function onContextmenu(ev: MouseEvent) {
os.contextMenu(contextmenu, ev);
os.contextMenu(contextmenu.value, ev);
}
</script>

View File

@ -17,7 +17,7 @@
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import { onMounted, onUnmounted, ref } from 'vue';
import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{
@ -41,14 +41,14 @@ const emit = defineEmits<{
(event: 'ok'): void;
}>();
let modal = $ref<InstanceType<typeof MkModal>>();
let rootEl = $ref<HTMLElement>();
let headerEl = $ref<HTMLElement>();
let bodyWidth = $ref(0);
let bodyHeight = $ref(0);
let modal = ref<InstanceType<typeof MkModal>>();
let rootEl = ref<HTMLElement>();
let headerEl = ref<HTMLElement>();
let bodyWidth = ref(0);
let bodyHeight = ref(0);
const close = () => {
modal.close();
modal.value.close();
};
const onBgClick = () => {
@ -64,14 +64,14 @@ const onKeydown = (evt) => {
};
const ro = new ResizeObserver((entries, observer) => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
bodyWidth.value = rootEl.value.offsetWidth;
bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
});
onMounted(() => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
ro.observe(rootEl);
bodyWidth.value = rootEl.value.offsetWidth;
bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
ro.observe(rootEl.value);
});
onUnmounted(() => {

View File

@ -137,24 +137,24 @@ const props = defineProps<{
const inChannel = inject('inChannel', null);
let note = $ref(JSON.parse(JSON.stringify(props.note)));
let note = ref(JSON.parse(JSON.stringify(props.note)));
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result = JSON.parse(JSON.stringify(note));
let result = JSON.parse(JSON.stringify(note.value));
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note = result;
note.value = result;
});
}
const isRenote = (
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null
);
const el = ref<HTMLElement>();
@ -162,20 +162,20 @@ const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
let appearNote = computed(() => isRenote ? note.value.renote as misskey.entities.Note : note.value);
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isLong = (appearNote.cw == null && appearNote.text != null && (
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500)
const isLong = (appearNote.value.cw == null && appearNote.value.text != null && (
(appearNote.value.text.split('\n').length > 9) ||
(appearNote.value.text.length > 500)
));
const collapsed = ref(appearNote.cw == null && isLong);
const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
const muted = ref(checkWordMute(appearNote.value, $i, defaultStore.state.mutedWords));
const translation = ref(null);
const translating = ref(false);
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const urls = appearNote.value.text ? extractUrlFromMfm(mfm.parse(appearNote.value.text)) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const keymap = {
'r': () => reply(true),
@ -190,14 +190,14 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
note: (appearNote),
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: appearNote,
reply: appearNote.value,
animation: !viaKeyboard,
}, () => {
focus();
@ -209,12 +209,12 @@ function react(viaKeyboard = false): void {
blur();
reactionPicker.show(reactButton.value, results => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: results.reaction,
});
if (results.withRenote) {
os.api('notes/create', {
renoteId: appearNote.id,
renoteId: appearNote.value.id,
isRenote: true,
});
}
@ -247,12 +247,12 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClipPage }), ev).then(focus);
os.contextMenu(getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClipPage }), ev).then(focus);
}
}
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClipPage }), menuButton.value, {
os.popupMenu(getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClipPage }), menuButton.value, {
viaKeyboard,
}).then(focus);
}
@ -265,7 +265,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -292,7 +292,7 @@ function focusAfter() {
function readPromo() {
os.api('promo/read', {
noteId: appearNote.id,
noteId: appearNote.value.id,
});
isDeleted.value = true;
}

View File

@ -147,24 +147,24 @@ const props = defineProps<{
const inChannel = inject('inChannel', null);
let note = $ref(JSON.parse(JSON.stringify(props.note)));
let note = ref(JSON.parse(JSON.stringify(props.note)));
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result = JSON.parse(JSON.stringify(note));
let result = JSON.parse(JSON.stringify(note.value));
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note = result;
note.value = result;
});
}
const isRenote = (
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null
);
const el = ref<HTMLElement>();
@ -172,15 +172,15 @@ const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
let appearNote = computed(() => isRenote ? note.value.renote as misskey.entities.Note : note.value);
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
const muted = ref(checkWordMute(appearNote.value, $i, defaultStore.state.mutedWords));
const translation = ref(null);
const translating = ref(false);
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const urls = appearNote.value.text ? extractUrlFromMfm(mfm.parse(appearNote.value.text)) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const conversation = ref<misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]);
@ -195,14 +195,14 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
note: (appearNote),
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: appearNote,
reply: appearNote.value,
animation: !viaKeyboard,
}, () => {
focus();
@ -214,7 +214,7 @@ function react(viaKeyboard = false): void {
blur();
reactionPicker.show(reactButton.value, reaction => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction.reaction,
});
}, () => {
@ -244,12 +244,12 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), ev).then(focus);
os.contextMenu(getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted }), ev).then(focus);
}
}
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), menuButton.value, {
os.popupMenu(getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted }), menuButton.value, {
viaKeyboard,
}).then(focus);
}
@ -262,7 +262,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -280,15 +280,15 @@ function blur() {
}
os.api('notes/children', {
noteId: appearNote.id,
noteId: appearNote.value.id,
limit: 30,
}).then(res => {
replies.value = res;
});
if (appearNote.replyId) {
if (appearNote.value.replyId) {
os.api('notes/conversation', {
noteId: appearNote.replyId,
noteId: appearNote.value.replyId,
}).then(res => {
conversation.value = res.reverse();
});

View File

@ -17,7 +17,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import XNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -28,7 +28,7 @@ const props = defineProps<{
pinned?: boolean;
}>();
const showContent = $ref(false);
const showContent = ref(false);
</script>
<style lang="scss" scoped>

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import XNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -44,15 +44,15 @@ const props = withDefaults(defineProps<{
depth: 1,
});
let showContent = $ref(false);
let replies: misskey.entities.Note[] = $ref([]);
let showContent = ref(false);
let replies: misskey.entities.Note[] = ref([]);
if (props.detail) {
os.api('notes/children', {
noteId: props.note.id,
limit: 5,
}).then(res => {
replies = res;
replies.value = res;
});
}
</script>

View File

@ -28,7 +28,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed, ref } from 'vue';
import { notificationTypes } from 'misskey-js';
import MkSwitch from './form/switch.vue';
import MkInfo from './MkInfo.vue';
@ -49,39 +49,39 @@ const props = withDefaults(defineProps<{
showGlobalToggle: true,
});
let includingTypes = $computed(() => props.includingTypes || []);
let includingTypes = computed(() => props.includingTypes || []);
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
let typesMap = $ref<Record<typeof notificationTypes[number], boolean>>({});
let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);
let typesMap = ref<Record<typeof notificationTypes[number], boolean>>({});
let useGlobalSetting = ref((includingTypes.value === null || includingTypes.value.length === 0) && props.showGlobalToggle);
for (const ntype of notificationTypes) {
typesMap[ntype] = includingTypes.includes(ntype);
typesMap.value[ntype] = includingTypes.value.includes(ntype);
}
function ok() {
if (useGlobalSetting) {
if (useGlobalSetting.value) {
emit('done', { includingTypes: null });
} else {
emit('done', {
includingTypes: (Object.keys(typesMap) as typeof notificationTypes[number][])
.filter(type => typesMap[type]),
includingTypes: (Object.keys(typesMap.value) as typeof notificationTypes[number][])
.filter(type => typesMap.value[type]),
});
}
dialog.close();
dialog.value.close();
}
function disableAll() {
for (const type in typesMap) {
typesMap[type as typeof notificationTypes[number]] = false;
for (const type in typesMap.value) {
typesMap.value[type as typeof notificationTypes[number]] = false;
}
}
function enableAll() {
for (const type in typesMap) {
typesMap[type as typeof notificationTypes[number]] = true;
for (const type in typesMap.value) {
typesMap.value[type as typeof notificationTypes[number]] = true;
}
}
</script>

View File

@ -7,7 +7,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import XNotification from '@/components/MkNotification.vue';
import * as os from '@/os';
@ -20,11 +20,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('high');
let showing = $ref(true);
let showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 6000);
});
</script>

View File

@ -24,7 +24,7 @@
</template>
<script lang="ts" setup>
import { ComputedRef, inject, provide } from 'vue';
import { ComputedRef, inject, provide, ref, computed } from 'vue';
import RouterView from '@/components/global/RouterView.vue';
import XWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout';
@ -46,16 +46,16 @@ defineEmits<{
const router = new Router(routes, props.initialPath);
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let windowEl = $ref<InstanceType<typeof XWindow>>();
const history = $ref<{ path: string; key: any; }[]>([{
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = ref<InstanceType<typeof XWindow>>();
const history = ref<{ path: string; key: any; }[]>([{
path: router.getCurrentPath(),
key: router.getCurrentKey(),
}]);
const buttonsLeft = $computed(() => {
const buttonsLeft = computed(() => {
const buttons = [];
if (history.length > 1) {
if (history.value.length > 1) {
buttons.push({
icon: 'fas fa-arrow-left',
onClick: back,
@ -64,7 +64,7 @@ const buttonsLeft = $computed(() => {
return buttons;
});
const buttonsRight = $computed(() => {
const buttonsRight = computed(() => {
const buttons = [{
icon: 'fas fa-expand-alt',
title: i18n.ts.showInPage,
@ -75,17 +75,17 @@ const buttonsRight = $computed(() => {
});
router.addListener('push', ctx => {
history.push({ path: ctx.path, key: ctx.key });
history.value.push({ path: ctx.path, key: ctx.key });
});
provide('router', router);
provideMetadataReceiver((info) => {
pageMetadata = info;
pageMetadata.value = info;
});
provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true);
const contextmenu = $computed(() => ([{
const contextmenu = computed(() => ([{
icon: 'fas fa-expand-alt',
text: i18n.ts.showInPage,
action: expand,
@ -98,7 +98,7 @@ const contextmenu = $computed(() => ([{
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + router.getCurrentPath(), '_blank');
windowEl.close();
windowEl.value.close();
},
}, {
icon: 'fas fa-link',
@ -109,26 +109,26 @@ const contextmenu = $computed(() => ([{
}]));
function menu(ev) {
os.popupMenu(contextmenu, ev.currentTarget ?? ev.target);
os.popupMenu(contextmenu.value, ev.currentTarget ?? ev.target);
}
function back() {
history.pop();
router.replace(history[history.length - 1].path, history[history.length - 1].key);
history.value.pop();
router.replace(history.value[history.value.length - 1].path, history.value[history.value.length - 1].key);
}
function close() {
windowEl.close();
windowEl.value.close();
}
function expand() {
mainRouter.push(router.getCurrentPath(), 'forcePage');
windowEl.close();
windowEl.value.close();
}
function popout() {
_popout(router.getCurrentPath(), windowEl.$el);
windowEl.close();
_popout(router.getCurrentPath(), windowEl.value.$el);
windowEl.value.close();
}
defineExpose({

View File

@ -5,7 +5,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu';
@ -22,7 +22,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let modal = $ref<InstanceType<typeof MkModal>>();
let modal = ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>

View File

@ -65,7 +65,7 @@
</template>
<script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue';
import { inject, watch, nextTick, onMounted, defineAsyncComponent, ref, computed } from 'vue';
import * as mfm from 'mfm-js';
import * as misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@ -121,35 +121,35 @@ const emit = defineEmits<{
(ev: 'esc'): void;
}>();
const textareaEl = $ref<HTMLTextAreaElement | null>(null);
const cwInputEl = $ref<HTMLInputElement | null>(null);
const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
const visibilityButton = $ref<HTMLElement | null>(null);
const textareaEl = ref<HTMLTextAreaElement | null>(null);
const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = ref<HTMLElement | null>(null);
let posting = $ref(false);
let text = $ref(props.initialText ?? '');
let files = $ref(props.initialFiles ?? []);
let poll = $ref<{
let posting = ref(false);
let text = ref(props.initialText ?? '');
let files = ref(props.initialFiles ?? []);
let poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
let useCw = $ref(false);
let showPreview = $ref(false);
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]);
let visibleUsers = $ref([]);
let useCw = ref(false);
let showPreview = ref(false);
let cw = ref<string | null>(null);
let localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
let visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]);
let visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
let autocomplete = $ref(null);
let draghover = $ref(false);
let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]'));
let imeText = $ref('');
let autocomplete = ref(null);
let draghover = ref(false);
let quoteId = ref(null);
let hasNotSpecifiedMentions = ref(false);
let recentHashtags = ref(JSON.parse(localStorage.getItem('hashtags') || '[]'));
let imeText = ref('');
const typing = throttle(3000, () => {
if (props.channel) {
@ -157,7 +157,7 @@ const typing = throttle(3000, () => {
}
});
const draftKey = $computed((): string => {
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
if (props.renote) {
@ -171,7 +171,7 @@ const draftKey = $computed((): string => {
return key;
});
const placeholder = $computed((): string => {
const placeholder = computed((): string => {
if (props.renote) {
return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) {
@ -191,7 +191,7 @@ const placeholder = $computed((): string => {
}
});
const submitText = $computed((): string => {
const submitText = computed((): string => {
return props.renote
? i18n.ts.quote
: props.reply
@ -199,41 +199,41 @@ const submitText = $computed((): string => {
: i18n.ts.note;
});
const textLength = $computed((): number => {
return length((text + imeText).trim());
const textLength = computed((): number => {
return length((text.value + imeText.value).trim());
});
const maxTextLength = $computed((): number => {
const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000;
});
const canPost = $computed((): boolean => {
return !posting &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
(textLength <= maxTextLength) &&
(!poll || poll.choices.length >= 2);
const canPost = computed((): boolean => {
return !posting.value &&
(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote) &&
(textLength.value <= maxTextLength.value) &&
(!poll.value || poll.value.choices.length >= 2);
});
const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags'));
const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags'));
watch($$(text), () => {
watch((text), () => {
checkMissingMention();
});
watch($$(visibleUsers), () => {
watch((visibleUsers), () => {
checkMissingMention();
}, {
deep: true,
});
if (props.mention) {
text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text += ' ';
text.value = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text.value += ' ';
}
if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) {
text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
text.value = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
}
if (props.reply && props.reply.text != null) {
@ -251,20 +251,20 @@ if (props.reply && props.reply.text != null) {
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
//
if (text.includes(`${mention} `)) continue;
if (text.value.includes(`${mention} `)) continue;
text += `${mention} `;
text.value += `${mention} `;
}
}
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
}
//
if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) {
visibility = props.reply.visibility;
visibility.value = props.reply.visibility;
if (props.reply.visibility === 'specified') {
os.api('users/show', {
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
@ -281,57 +281,57 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
}
if (props.specified) {
visibility = 'specified';
visibility.value = 'specified';
pushVisibleUser(props.specified);
}
// keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true;
cw = props.reply.cw;
useCw.value = true;
cw.value = props.reply.cw;
}
function watchForDraft() {
watch($$(text), () => saveDraft());
watch($$(useCw), () => saveDraft());
watch($$(cw), () => saveDraft());
watch($$(poll), () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft());
watch($$(localOnly), () => saveDraft());
watch((text), () => saveDraft());
watch((useCw), () => saveDraft());
watch((cw), () => saveDraft());
watch((poll), () => saveDraft());
watch((files), () => saveDraft(), { deep: true });
watch((visibility), () => saveDraft());
watch((localOnly), () => saveDraft());
}
function checkMissingMention() {
if (visibility === 'specified') {
const ast = mfm.parse(text);
if (visibility.value === 'specified') {
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions = true;
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions.value = true;
return;
}
}
hasNotSpecifiedMentions = false;
hasNotSpecifiedMentions.value = false;
}
}
function addMissingMention() {
const ast = mfm.parse(text);
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
os.api('users/show', { username: x.username, host: x.host }).then(user => {
visibleUsers.push(user);
visibleUsers.value.push(user);
});
}
}
}
function togglePoll() {
if (poll) {
poll = null;
if (poll.value) {
poll.value = null;
} else {
poll = {
poll.value = {
choices: ['', ''],
multiple: false,
expiresAt: null,
@ -341,49 +341,49 @@ function togglePoll() {
}
function addTag(tag: string) {
insertTextAtCursor(textareaEl, ` #${tag} `);
insertTextAtCursor(textareaEl.value, ` #${tag} `);
}
function focus() {
if (textareaEl) {
textareaEl.focus();
textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length);
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(textareaEl.value.value.length, textareaEl.value.value.length);
}
}
function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
for (const file of files_) {
files.push(file);
files.value.push(file);
}
});
}
function chooseFileFromEnc(ev) {
selectFileEnc(ev.currentTarget ?? ev.target, i18n.ts.attachVideoFile).then(files_ => {
files.push(files_);
files.value.push(files_);
});
}
function detachFile(id) {
files = files.filter(x => x.id !== id);
files.value = files.value.filter(x => x.id !== id);
}
function updateFiles(_files) {
files = _files;
files.value = _files;
}
function updateFileSensitive(file, sensitive) {
files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive;
files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive;
}
function updateFileName(file, name) {
files[files.findIndex(x => x.id === file.id)].name = name;
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
}
function upload(file: File, name?: string) {
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.push(res);
files.value.push(res);
});
}
@ -394,28 +394,28 @@ function setVisibility() {
}
os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility,
currentLocalOnly: localOnly,
src: visibilityButton,
currentVisibility: visibility.value,
currentLocalOnly: localOnly.value,
src: visibilityButton.value,
}, {
changeVisibility: v => {
visibility = v;
visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('visibility', visibility);
defaultStore.set('visibility', visibility.value);
}
},
changeLocalOnly: v => {
localOnly = v;
localOnly.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('localOnly', localOnly);
defaultStore.set('localOnly', localOnly.value);
}
},
}, 'closed');
}
function pushVisibleUser(user) {
if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.push(user);
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);
}
}
@ -426,29 +426,29 @@ function addVisibleUser() {
}
function removeVisibleUser(user) {
visibleUsers = erase(user, visibleUsers);
visibleUsers.value = erase(user, visibleUsers.value);
}
function clear() {
text = '';
files = [];
poll = null;
quoteId = null;
text.value = '';
files.value = [];
poll.value = null;
quoteId.value = null;
}
function onKeydown(ev: KeyboardEvent) {
if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post();
if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost.value) post();
if (ev.which === 27) emit('esc');
typing();
}
function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data;
imeText.value = ev.data;
typing();
}
function onCompositionEnd(ev: CompositionEvent) {
imeText = '';
imeText.value = '';
}
async function onPaste(ev: ClipboardEvent) {
@ -464,7 +464,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData('text');
if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) {
ev.preventDefault();
os.confirm({
@ -472,11 +472,11 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => {
if (canceled) {
insertTextAtCursor(textareaEl, paste);
insertTextAtCursor(textareaEl.value, paste);
return;
}
quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
quoteId.value = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
});
}
}
@ -487,7 +487,7 @@ function onDragover(ev) {
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
ev.preventDefault();
draghover = true;
draghover.value = true;
switch (ev.dataTransfer.effectAllowed) {
case 'all':
case 'uninitialized':
@ -508,15 +508,15 @@ function onDragover(ev) {
}
function onDragenter(ev) {
draghover = true;
draghover.value = true;
}
function onDragleave(ev) {
draghover = false;
draghover.value = false;
}
function onDrop(ev): void {
draghover = false;
draghover.value = false;
//
if (ev.dataTransfer.files.length > 0) {
@ -529,7 +529,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
files.push(file);
files.value.push(file);
ev.preventDefault();
}
//#endregion
@ -538,16 +538,16 @@ function onDrop(ev): void {
function saveDraft() {
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
draftData[draftKey] = {
draftData[draftKey.value] = {
updatedAt: new Date(),
data: {
text: text,
useCw: useCw,
cw: cw,
visibility: visibility,
localOnly: localOnly,
files: files,
poll: poll,
text: text.value,
useCw: useCw.value,
cw: cw.value,
visibility: visibility.value,
localOnly: localOnly.value,
files: files.value,
poll: poll.value,
},
};
@ -557,27 +557,27 @@ function saveDraft() {
function deleteDraft() {
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
delete draftData[draftKey];
delete draftData[draftKey.value];
localStorage.setItem('drafts', JSON.stringify(draftData));
}
async function post() {
let postData = {
text: text === '' ? undefined : text,
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
text: text.value === '' ? undefined : text.value,
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
channelId: props.channel ? props.channel.id : undefined,
poll: poll,
cw: useCw ? cw || '' : undefined,
localOnly: localOnly,
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
poll: poll.value,
cw: useCw.value ? cw.value || '' : undefined,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
}
@ -590,12 +590,12 @@ async function post() {
let token = undefined;
if (postAccount) {
if (postAccount.value) {
const storedAccounts = await getAccounts();
token = storedAccounts.find(x => x.id === postAccount.id)?.token;
token = storedAccounts.find(x => x.id === postAccount.value.id)?.token;
}
posting = true;
posting.value = true;
os.api('notes/create', postData, token).then(() => {
clear();
nextTick(() => {
@ -606,11 +606,11 @@ async function post() {
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
}
posting = false;
postAccount = null;
posting.value = false;
postAccount.value = null;
});
}).catch(err => {
posting = false;
posting.value = false;
os.alert({
type: 'error',
text: err.message + '\n' + (err as any).id,
@ -624,12 +624,12 @@ function cancel() {
function insertMention() {
os.selectUser().then(user => {
insertTextAtCursor(textareaEl, '@' + Acct.toString(user) + ' ');
insertTextAtCursor(textareaEl.value, '@' + Acct.toString(user) + ' ');
});
}
async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl);
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl.value);
}
function showActions(ev) {
@ -637,26 +637,26 @@ function showActions(ev) {
text: action.title,
action: () => {
action.handler({
text: text,
text: text.value,
}, (key, value) => {
if (key === 'text') { text = value; }
if (key === 'text') { text.value = value; }
});
},
})), ev.currentTarget ?? ev.target);
}
let postAccount = $ref<misskey.entities.UserDetailed | null>(null);
let postAccount = ref<misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id,
active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => {
if (account.id === $i.id) {
postAccount = null;
postAccount.value = null;
} else {
postAccount = account;
postAccount.value = account;
}
},
}, ev);
@ -672,23 +672,23 @@ onMounted(() => {
}
// TODO: detach when unmount
new Autocomplete(textareaEl, $$(text));
new Autocomplete(cwInputEl, $$(cw));
new Autocomplete(hashtagsInputEl, $$(hashtags));
new Autocomplete(textareaEl.value, (text));
new Autocomplete(cwInputEl.value, (cw));
new Autocomplete(hashtagsInputEl.value, (hashtags));
nextTick(() => {
// 稿
if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey];
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey.value];
if (draft) {
text = draft.data.text;
useCw = draft.data.useCw;
cw = draft.data.cw;
visibility = draft.data.visibility;
localOnly = draft.data.localOnly;
files = (draft.data.files || []).filter(draftFile => draftFile);
text.value = draft.data.text;
useCw.value = draft.data.useCw;
cw.value = draft.data.cw;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
files.value = (draft.data.files || []).filter(draftFile => draftFile);
if (draft.data.poll) {
poll = draft.data.poll;
poll.value = draft.data.poll;
}
}
}
@ -696,21 +696,21 @@ onMounted(() => {
//
if (props.initialNote) {
const init = props.initialNote;
text = init.text ? init.text : '';
files = init.files;
cw = init.cw;
useCw = init.cw != null;
text.value = init.text ? init.text : '';
files.value = init.files;
cw.value = init.cw;
useCw.value = init.cw != null;
if (init.poll) {
poll = {
poll.value = {
choices: init.poll.choices.map(x => x.text),
multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt,
expiredAfter: init.poll.expiredAfter,
};
}
visibility = init.visibility;
localOnly = init.localOnly;
quoteId = init.renote ? init.renote.id : null;
visibility.value = init.visibility;
localOnly.value = init.localOnly;
quoteId.value = init.renote ? init.renote.id : null;
}
nextTick(() => watchForDraft());

View File

@ -43,7 +43,7 @@ type reactionUsers = {
users: UserLite[];
}[]
const dialog = $ref<InstanceType<typeof MkModalWindow>>();
const dialog = ref<InstanceType<typeof MkModalWindow>>();
const el = ref<HTMLElement>();
const reactions = ref([] as NoteReaction[]);
const reactUsers = ref([] as reactionUsers);

View File

@ -49,7 +49,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref, computed } from 'vue';
import { toUnicode } from 'punycode/';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
@ -62,20 +62,20 @@ import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
let signing = $ref(false);
let user = $ref(null);
let username = $ref('');
let password = $ref('');
let token = $ref('');
let host = $ref(toUnicode(configHost));
let totpLogin = $ref(false);
let credential = $ref(null);
let challengeData = $ref(null);
let queryingKey = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
let signing = ref(false);
let user = ref(null);
let username = ref('');
let password = ref('');
let token = ref('');
let host = ref(toUnicode(configHost));
let totpLogin = ref(false);
let credential = ref(null);
let challengeData = ref(null);
let queryingKey = ref(false);
let hCaptchaResponse = ref(null);
let reCaptchaResponse = ref(null);
const meta = $computed(() => instance);
const meta = computed(() => instance);
const emit = defineEmits<{
(ev: 'login', v: any): void;
@ -101,11 +101,11 @@ const props = defineProps({
function onUsernameChange() {
os.api('users/show', {
username: username
username: username.value
}).then(userResponse => {
user = userResponse;
user.value = userResponse;
}, () => {
user = null;
user.value = null;
});
}
@ -116,11 +116,11 @@ function onLogin(res) {
}
function queryKey() {
queryingKey = true;
queryingKey.value = true;
return navigator.credentials.get({
publicKey: {
challenge: byteify(challengeData.challenge, 'base64'),
allowCredentials: challengeData.securityKeys.map(key => ({
challenge: byteify(challengeData.value.challenge, 'base64'),
allowCredentials: challengeData.value.securityKeys.map(key => ({
id: byteify(key.id, 'hex'),
type: 'public-key',
transports: ['usb', 'nfc', 'ble', 'internal']
@ -128,21 +128,21 @@ function queryKey() {
timeout: 60 * 1000
}
}).catch(() => {
queryingKey = false;
queryingKey.value = false;
return Promise.reject(null);
}).then(credential => {
queryingKey = false;
signing = true;
queryingKey.value = false;
signing.value = true;
return os.api('signin', {
username,
password,
username: username.value,
password: password.value,
signature: hexify(credential.response.signature),
authenticatorData: hexify(credential.response.authenticatorData),
clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id,
challengeId: challengeData.challengeId,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
challengeId: challengeData.value.challengeId,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
});
}).then(res => {
emit('login', res);
@ -153,37 +153,37 @@ function queryKey() {
type: 'error',
text: i18n.ts.signinFailed
});
signing = false;
signing.value = false;
});
}
function onSubmit() {
signing = true;
signing.value = true;
console.log('submit');
if (!totpLogin && user && user.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) {
if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
if (window.PublicKeyCredential && user.value.securityKeys) {
os.api('signin', {
username,
password,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}).then(res => {
totpLogin = true;
signing = false;
challengeData = res;
totpLogin.value = true;
signing.value = false;
challengeData.value = res;
return queryKey();
}).catch(loginFailed);
} else {
totpLogin = true;
signing = false;
totpLogin.value = true;
signing.value = false;
}
} else {
os.api('signin', {
username,
password,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
token: user && user.twoFactorEnabled ? token : undefined
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
token: user.value && user.value.twoFactorEnabled ? token.value : undefined
}).then(res => {
emit('login', res);
onLogin(res);
@ -231,9 +231,9 @@ function loginFailed(err) {
}
}
challengeData = null;
totpLogin = false;
signing = false;
challengeData.value = null;
totpLogin.value = false;
signing.value = false;
}
function resetPassword() {

View File

@ -13,7 +13,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import MkSignin from '@/components/MkSignin.vue';
import XModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n';
@ -32,15 +32,15 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
function onClose() {
emit('cancelled');
dialog.close();
dialog.value.close();
}
function onLogin(res) {
emit('done', res);
dialog.close();
dialog.value.close();
}
</script>

View File

@ -65,7 +65,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import getPasswordStrength from 'syuilo-password-strength';
import { toUnicode } from 'punycode/';
import MkButton from './MkButton.vue';
@ -91,73 +91,73 @@ const emit = defineEmits<{
const host = toUnicode(config.host);
let hcaptcha = $ref();
let recaptcha = $ref();
let hcaptcha = ref();
let recaptcha = ref();
let username: string = $ref('');
let password: string = $ref('');
let retypedPassword: string = $ref('');
let invitationCode: string = $ref('');
let email = $ref('');
let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
let submitting: boolean = $ref(false);
let ToSAgreement: boolean = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
let username: string = ref('');
let password: string = ref('');
let retypedPassword: string = ref('');
let invitationCode: string = ref('');
let email = ref('');
let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = ref(null);
let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = ref(null);
let passwordStrength: '' | 'low' | 'medium' | 'high' = ref('');
let passwordRetypeState: null | 'match' | 'not-match' = ref(null);
let submitting: boolean = ref(false);
let ToSAgreement: boolean = ref(false);
let hCaptchaResponse = ref(null);
let reCaptchaResponse = ref(null);
const shouldDisableSubmitting = $computed((): boolean => {
return submitting ||
instance.tosUrl && !ToSAgreement ||
instance.enableHcaptcha && !hCaptchaResponse ||
instance.enableRecaptcha && !reCaptchaResponse ||
passwordRetypeState === 'not-match';
const shouldDisableSubmitting = computed((): boolean => {
return submitting.value ||
instance.tosUrl && !ToSAgreement.value ||
instance.enableHcaptcha && !hCaptchaResponse.value ||
instance.enableRecaptcha && !reCaptchaResponse.value ||
passwordRetypeState.value === 'not-match';
});
function onChangeUsername(): void {
if (username === '') {
usernameState = null;
if (username.value === '') {
usernameState.value = null;
return;
}
{
const err =
!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
username.length < 1 ? 'min-range' :
username.length > 20 ? 'max-range' :
!username.value.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
username.value.length < 1 ? 'min-range' :
username.value.length > 20 ? 'max-range' :
null;
if (err) {
usernameState = err;
usernameState.value = err;
return;
}
}
usernameState = 'wait';
usernameState.value = 'wait';
os.api('username/available', {
username,
username: username.value,
}).then(result => {
usernameState = result.available ? 'ok' : 'unavailable';
usernameState.value = result.available ? 'ok' : 'unavailable';
}).catch(() => {
usernameState = 'error';
usernameState.value = 'error';
});
}
function onChangeEmail(): void {
if (email === '') {
emailState = null;
if (email.value === '') {
emailState.value = null;
return;
}
emailState = 'wait';
emailState.value = 'wait';
os.api('email-address/available', {
emailAddress: email,
emailAddress: email.value,
}).then(result => {
emailState = result.available ? 'ok' :
emailState.value = result.available ? 'ok' :
result.reason === 'used' ? 'unavailable:used' :
result.reason === 'format' ? 'unavailable:format' :
result.reason === 'disposable' ? 'unavailable:disposable' :
@ -165,52 +165,52 @@ function onChangeEmail(): void {
result.reason === 'smtp' ? 'unavailable:smtp' :
'unavailable';
}).catch(() => {
emailState = 'error';
emailState.value = 'error';
});
}
function onChangePassword(): void {
if (password === '') {
passwordStrength = '';
if (password.value === '') {
passwordStrength.value = '';
return;
}
const strength = getPasswordStrength(password);
passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
const strength = getPasswordStrength(password.value);
passwordStrength.value = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
}
function onChangePasswordRetype(): void {
if (retypedPassword === '') {
passwordRetypeState = null;
if (retypedPassword.value === '') {
passwordRetypeState.value = null;
return;
}
passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
passwordRetypeState.value = password.value === retypedPassword.value ? 'match' : 'not-match';
}
function onSubmit(): void {
if (submitting) return;
submitting = true;
if (submitting.value) return;
submitting.value = true;
os.api('signup', {
username,
password,
emailAddress: email,
invitationCode,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
username: username.value,
password: password.value,
emailAddress: email.value,
invitationCode: invitationCode.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}).then(() => {
if (instance.emailRequiredForSignup) {
os.alert({
type: 'success',
title: i18n.ts._signup.almostThere,
text: i18n.t('_signup.emailSent', { email }),
text: i18n.t('_signup.emailSent', { email: email.value }),
});
emit('signupEmailPending');
} else {
os.api('signin', {
username,
password,
username: username.value,
password: password.value,
}).then(res => {
emit('signup', res);
@ -220,9 +220,9 @@ function onSubmit(): void {
});
}
}).catch(() => {
submitting = false;
hcaptcha.reset?.();
recaptcha.reset?.();
submitting.value = false;
hcaptcha.value.reset?.();
recaptcha.value.reset?.();
os.alert({
type: 'error',

View File

@ -17,7 +17,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import XSignup from '@/components/MkSignup.vue';
import XModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n';
@ -33,14 +33,14 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
function onSignup(res) {
emit('done', res);
dialog.close();
dialog.value.close();
}
function onSignupEmailPending() {
dialog.close();
dialog.value.close();
}
</script>

View File

@ -22,7 +22,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import XMediaList from '@/components/MkMediaList.vue';
import XPoll from '@/components/MkPoll.vue';
@ -32,7 +32,7 @@ const props = defineProps<{
note: misskey.entities.Note;
}>();
const collapsed = $ref(
const collapsed = ref(
props.note.cw == null && props.note.text != null && (
(props.note.text.split('\n').length > 9) ||
(props.note.text.length > 500)

View File

@ -18,13 +18,13 @@ const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
const computedStyle = getComputedStyle(document.documentElement);
const idForCanvas = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
let available = $ref(false);
let rootEl = $ref<HTMLElement | null>(null);
let canvasEl = $ref<HTMLCanvasElement | null>(null);
let tagsEl = $ref<HTMLElement | null>(null);
let width = $ref(300);
let available = ref(false);
let rootEl = ref<HTMLElement | null>(null);
let canvasEl = ref<HTMLCanvasElement | null>(null);
let tagsEl = ref<HTMLElement | null>(null);
let width = ref(300);
watch($$(available), () => {
watch((available), () => {
try {
window.TagCanvas.Start(idForCanvas, idForTags, {
textColour: '#ffffff',
@ -47,15 +47,15 @@ watch($$(available), () => {
});
onMounted(() => {
width = rootEl.offsetWidth;
width.value = rootEl.value.offsetWidth;
if (loaded) {
available = true;
available.value = true;
} else {
document.head.appendChild(Object.assign(document.createElement('script'), {
async: true,
src: '/client-assets/tagcanvas.min.js',
})).addEventListener('load', () => available = true);
})).addEventListener('load', () => available.value = true);
}
});

View File

@ -25,10 +25,10 @@ const emit = defineEmits<{
provide('inChannel', computed(() => props.src === 'channel'));
const tlComponent: InstanceType<typeof XNotes> = $ref();
const tlComponent: InstanceType<typeof XNotes> = ref();
const prepend = note => {
tlComponent.pagingComponent?.prepend(note);
tlComponent.value.pagingComponent?.prepend(note);
emit('note');
@ -38,16 +38,16 @@ const prepend = note => {
};
const onUserAdded = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value.pagingComponent?.reload();
};
const onUserRemoved = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value.pagingComponent?.reload();
};
const onChangeFollowing = () => {
if (!tlComponent.pagingComponent?.backed) {
tlComponent.pagingComponent?.reload();
if (!tlComponent.value.pagingComponent?.backed) {
tlComponent.value.pagingComponent?.reload();
}
};

View File

@ -23,11 +23,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('high');
let showing = $ref(true);
let showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 4000);
});
</script>

View File

@ -29,7 +29,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import { permissions as kinds } from 'misskey-js';
import MkInput from './form/input.vue';
import MkSwitch from './form/switch.vue';
@ -54,37 +54,37 @@ const emit = defineEmits<{
(ev: 'done', result: { name: string | null, permissions: string[] }): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
let name = $ref(props.initialName);
let permissions = $ref({});
const dialog = ref<InstanceType<typeof XModalWindow>>();
let name = ref(props.initialName);
let permissions = ref({});
if (props.initialPermissions) {
for (const kind of props.initialPermissions) {
permissions[kind] = true;
permissions.value[kind] = true;
}
} else {
for (const kind of kinds) {
permissions[kind] = false;
permissions.value[kind] = false;
}
}
function ok(): void {
emit('done', {
name: name,
permissions: Object.keys(permissions).filter(p => permissions[p]),
name: name.value,
permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
});
dialog.close();
dialog.value.close();
}
function disableAll(): void {
for (const p in permissions) {
permissions[p] = false;
for (const p in permissions.value) {
permissions.value[p] = false;
}
}
function enableAll(): void {
for (const p in permissions) {
permissions[p] = true;
for (const p in permissions.value) {
permissions.value[p] = true;
}
}
</script>

View File

@ -33,7 +33,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, onUnmounted } from 'vue';
import { defineAsyncComponent, onMounted, onUnmounted, ref } from 'vue';
import { url as local, lang } from '@/config';
import { i18n } from '@/i18n';
import * as os from '@/os';
@ -49,33 +49,33 @@ const props = withDefaults(defineProps<{
});
const MOBILE_THRESHOLD = 500;
const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
let fetching = $ref(true);
let title = $ref<string | null>(null);
let description = $ref<string | null>(null);
let thumbnail = $ref<string | null>(null);
let icon = $ref<string | null>(null);
let sitename = $ref<string | null>(null);
let player = $ref({
let fetching = ref(true);
let title = ref<string | null>(null);
let description = ref<string | null>(null);
let thumbnail = ref<string | null>(null);
let icon = ref<string | null>(null);
let sitename = ref<string | null>(null);
let player = ref({
url: null,
width: null,
height: null,
});
let playerEnabled = $ref(false);
let tweetId = $ref<string | null>(null);
let tweetExpanded = $ref(props.detail);
let playerEnabled = ref(false);
let tweetId = ref<string | null>(null);
let tweetExpanded = ref(props.detail);
const embedId = `embed${Math.random().toString().replace(/\D/,'')}`;
let tweetHeight = $ref(150);
let tweetHeight = ref(150);
const requestUrl = new URL(props.url);
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId = m[1];
if (m) tweetId.value = m[1];
}
if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
@ -89,13 +89,13 @@ requestUrl.hash = '';
fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
res.json().then(info => {
if (info.url == null) return;
title = info.title;
description = info.description;
thumbnail = info.thumbnail;
icon = info.icon;
sitename = info.sitename;
fetching = false;
player = info.player;
title.value = info.title;
description.value = info.description;
thumbnail.value = info.thumbnail;
icon.value = info.icon;
sitename.value = info.sitename;
fetching.value = false;
player.value = info.player;
});
});
@ -105,7 +105,7 @@ function adjustTweetHeight(message: any) {
if (embed?.method !== 'twttr.private.resize') return;
if (embed?.id !== embedId) return;
const height = embed?.params[0]?.height;
if (height) tweetHeight = height;
if (height) tweetHeight.value = height;
}
const openPlayer = (): void => {

View File

@ -7,7 +7,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import * as os from '@/os';
@ -22,16 +22,16 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('middle');
let top = $ref(0);
let left = $ref(0);
let top = ref(0);
let left = ref(0);
onMounted(() => {
const rect = props.source.getBoundingClientRect();
const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.pageXOffset;
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
top = y;
left = x;
top.value = y;
left.value = x;
});
</script>

View File

@ -10,6 +10,8 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import * as misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os';
@ -19,12 +21,12 @@ const props = defineProps<{
user: misskey.entities.User;
}>();
let chartValues = $ref<number[] | null>(null);
let chartValues = ref<number[] | null>(null);
os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
//
res.inc.splice(0, 1);
chartValues = res.inc;
chartValues.value = res.inc;
});
</script>

View File

@ -3,7 +3,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
import * as misskey from 'misskey-js';
import { i18n } from '@/i18n';
@ -11,7 +11,7 @@ const props = defineProps<{
user: misskey.entities.User;
}>();
const text = $computed(() => {
const text = computed(() => {
switch (props.user.onlineStatus) {
case 'online': return i18n.ts.online;
case 'active': return i18n.ts.active;

View File

@ -34,7 +34,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as Acct from 'misskey-js/built/acct';
import * as misskey from 'misskey-js';
import MkFollowButton from '@/components/MkFollowButton.vue';
@ -54,13 +54,13 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('middle');
let user = $ref<misskey.entities.UserDetailed | null>(null);
let top = $ref(0);
let left = $ref(0);
let user = ref<misskey.entities.UserDetailed | null>(null);
let top = ref(0);
let left = ref(0);
onMounted(() => {
if (typeof props.q === 'object') {
user = props.q;
user.value = props.q;
} else {
const query = props.q.startsWith('@') ?
Acct.parse(props.q.substr(1)) :
@ -68,7 +68,7 @@ onMounted(() => {
os.api('users/show', query).then(res => {
if (!props.showing) return;
user = res;
user.value = res;
});
}
@ -76,8 +76,8 @@ onMounted(() => {
const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.pageXOffset;
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
top = y;
left = x;
top.value = y;
left.value = x;
});
</script>

View File

@ -52,7 +52,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import { nextTick, onMounted, ref } from 'vue';
import * as misskey from 'misskey-js';
import MkInput from '@/components/form/input.vue';
import FormSplit from '@/components/form/split.vue';
@ -67,50 +67,50 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let username = $ref('');
let host = $ref('');
let users: misskey.entities.UserDetailed[] = $ref([]);
let recentUsers: misskey.entities.UserDetailed[] = $ref([]);
let selected: misskey.entities.UserDetailed | null = $ref(null);
let dialogEl = $ref();
let username = ref('');
let host = ref('');
let users: misskey.entities.UserDetailed[] = ref([]);
let recentUsers: misskey.entities.UserDetailed[] = ref([]);
let selected: misskey.entities.UserDetailed | null = ref(null);
let dialogEl = ref();
const search = () => {
if (username === '' && host === '') {
users = [];
if (username.value === '' && host.value === '') {
users.value = [];
return;
}
os.api('users/search-by-username-and-host', {
username: username,
host: host,
username: username.value,
host: host.value,
limit: 10,
detail: false,
}).then(_users => {
users = _users;
users.value = _users;
});
};
const ok = () => {
if (selected == null) return;
emit('ok', selected);
dialogEl.close();
if (selected.value == null) return;
emit('ok', selected.value);
dialogEl.value.close();
// 使
let recents = defaultStore.state.recentlyUsedUsers;
recents = recents.filter(x => x !== selected.id);
recents.unshift(selected.id);
recents = recents.filter(x => x !== selected.value.id);
recents.unshift(selected.value.id);
defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
};
const cancel = () => {
emit('cancel');
dialogEl.close();
dialogEl.value.close();
};
onMounted(() => {
os.api('users/show', {
userIds: defaultStore.state.recentlyUsedUsers,
}).then(users => {
recentUsers = users;
recentUsers.value = users;
});
});
</script>

View File

@ -21,10 +21,10 @@ const props = defineProps<{
},
}>();
const specified = $ref<HTMLElement>();
const specified = ref<HTMLElement>();
if (props.note.visibility === 'specified') {
useTooltip($$(specified), async (showing) => {
useTooltip((specified), async (showing) => {
const users = await os.api('users/show', {
userIds: props.note.visibleUserIds,
limit: 10,
@ -34,7 +34,7 @@ if (props.note.visibility === 'specified') {
showing,
users,
count: props.note.visibleUserIds.length,
targetElement: specified,
targetElement: specified.value,
}, {}, 'closed');
});
}

View File

@ -43,12 +43,12 @@
</template>
<script lang="ts" setup>
import { nextTick, watch } from 'vue';
import { nextTick, watch, ref } from 'vue';
import * as misskey from 'misskey-js';
import MkModal from '@/components/MkModal.vue';
import { i18n } from '@/i18n';
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
const props = withDefaults(defineProps<{
currentVisibility: typeof misskey.noteVisibilities[number];
@ -63,18 +63,18 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let v = $ref(props.currentVisibility);
let localOnly = $ref(props.currentLocalOnly);
let v = ref(props.currentVisibility);
let localOnly = ref(props.currentLocalOnly);
watch($$(localOnly), () => {
emit('changeLocalOnly', localOnly);
watch((localOnly), () => {
emit('changeLocalOnly', localOnly.value);
});
function choose(visibility: typeof misskey.noteVisibilities[number]): void {
v = visibility;
v.value = visibility;
emit('changeVisibility', visibility);
nextTick(() => {
modal.close();
modal.value.close();
});
}
</script>

View File

@ -35,7 +35,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide } from 'vue';
import { onBeforeUnmount, onMounted, provide, ref } from 'vue';
import contains from '@/scripts/contains';
import * as os from '@/os';
import { MenuItem } from '@/types/menu';
@ -87,17 +87,17 @@ const emit = defineEmits<{
provide('inWindow', true);
let rootEl = $ref<HTMLElement | null>();
let showing = $ref(true);
let rootEl = ref<HTMLElement | null>();
let showing = ref(true);
let beforeClickedAt = 0;
let maximized = $ref(false);
let maximized = ref(false);
let unMaximizedTop = '';
let unMaximizedLeft = '';
let unMaximizedWidth = '';
let unMaximizedHeight = '';
function close() {
showing = false;
showing.value = false;
}
function onKeydown(evt) {
@ -116,29 +116,29 @@ function onContextmenu(ev: MouseEvent) {
//
function top() {
if (rootEl) {
rootEl.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
if (rootEl.value) {
rootEl.value.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
}
}
function maximize() {
maximized = true;
unMaximizedTop = rootEl.style.top;
unMaximizedLeft = rootEl.style.left;
unMaximizedWidth = rootEl.style.width;
unMaximizedHeight = rootEl.style.height;
rootEl.style.top = '0';
rootEl.style.left = '0';
rootEl.style.width = '100%';
rootEl.style.height = '100%';
maximized.value = true;
unMaximizedTop = rootEl.value.style.top;
unMaximizedLeft = rootEl.value.style.left;
unMaximizedWidth = rootEl.value.style.width;
unMaximizedHeight = rootEl.value.style.height;
rootEl.value.style.top = '0';
rootEl.value.style.left = '0';
rootEl.value.style.width = '100%';
rootEl.value.style.height = '100%';
}
function unMaximize() {
maximized = false;
rootEl.style.top = unMaximizedTop;
rootEl.style.left = unMaximizedLeft;
rootEl.style.width = unMaximizedWidth;
rootEl.style.height = unMaximizedHeight;
maximized.value = false;
rootEl.value.style.top = unMaximizedTop;
rootEl.value.style.left = unMaximizedLeft;
rootEl.value.style.width = unMaximizedWidth;
rootEl.value.style.height = unMaximizedHeight;
}
function onBodyMousedown() {
@ -155,7 +155,7 @@ function onHeaderMousedown(evt: MouseEvent) {
let beforeMaximized = false;
if (maximized) {
if (maximized.value) {
beforeMaximized = true;
unMaximize();
}
@ -169,7 +169,7 @@ function onHeaderMousedown(evt: MouseEvent) {
beforeClickedAt = Date.now();
const main = rootEl;
const main = rootEl.value;
if (main == null) return;
if (!contains(main, document.activeElement)) main.focus();
@ -201,8 +201,8 @@ function onHeaderMousedown(evt: MouseEvent) {
//
if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
rootEl.style.left = moveLeft + 'px';
rootEl.style.top = moveTop + 'px';
rootEl.value.style.left = moveLeft + 'px';
rootEl.value.style.top = moveTop + 'px';
}
if (beforeMaximized) {
@ -220,7 +220,7 @@ function onHeaderMousedown(evt: MouseEvent) {
//
function onTopHandleMousedown(evt) {
const main = rootEl;
const main = rootEl.value;
const base = evt.clientY;
const height = parseInt(getComputedStyle(main, '').height, 10);
@ -246,7 +246,7 @@ function onTopHandleMousedown(evt) {
//
function onRightHandleMousedown(evt) {
const main = rootEl;
const main = rootEl.value;
const base = evt.clientX;
const width = parseInt(getComputedStyle(main, '').width, 10);
@ -270,7 +270,7 @@ function onRightHandleMousedown(evt) {
//
function onBottomHandleMousedown(evt) {
const main = rootEl;
const main = rootEl.value;
const base = evt.clientY;
const height = parseInt(getComputedStyle(main, '').height, 10);
@ -294,7 +294,7 @@ function onBottomHandleMousedown(evt) {
//
function onLeftHandleMousedown(evt) {
const main = rootEl;
const main = rootEl.value;
const base = evt.clientX;
const width = parseInt(getComputedStyle(main, '').width, 10);
@ -345,27 +345,27 @@ function onBottomLeftHandleMousedown(evt) {
//
function applyTransformHeight(height) {
if (height > window.innerHeight) height = window.innerHeight;
rootEl.style.height = height + 'px';
rootEl.value.style.height = height + 'px';
}
//
function applyTransformWidth(width) {
if (width > window.innerWidth) width = window.innerWidth;
rootEl.style.width = width + 'px';
rootEl.value.style.width = width + 'px';
}
// Y
function applyTransformTop(top) {
rootEl.style.top = top + 'px';
rootEl.value.style.top = top + 'px';
}
// X
function applyTransformLeft(left) {
rootEl.style.left = left + 'px';
rootEl.value.style.left = left + 'px';
}
function onBrowserResize() {
const main = rootEl;
const main = rootEl.value;
const position = main.getBoundingClientRect();
const browserWidth = window.innerWidth;
const browserHeight = window.innerHeight;
@ -381,8 +381,8 @@ onMounted(() => {
if (props.initialWidth) applyTransformWidth(props.initialWidth);
if (props.initialHeight) applyTransformHeight(props.initialHeight);
applyTransformTop((window.innerHeight / 2) - (rootEl.offsetHeight / 2));
applyTransformLeft((window.innerWidth / 2) - (rootEl.offsetWidth / 2));
applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));
applyTransformLeft((window.innerWidth / 2) - (rootEl.value.offsetWidth / 2));
//
top();

View File

@ -18,6 +18,8 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import XWindow from '@/components/MkWindow.vue';
import { lang } from '@/config';
@ -27,9 +29,9 @@ const props = defineProps<{
const requestUrl = new URL(props.url);
let fetching = $ref(true);
let title = $ref<string | null>(null);
let player = $ref({
let fetching = ref(true);
let title = ref<string | null>(null);
let player = ref({
url: null,
width: null,
height: null,
@ -38,13 +40,13 @@ let player = $ref({
const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
const ytFetch = (): void => {
fetching = true;
fetching.value = true;
fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
res.json().then(info => {
if (info.url == null) return;
title = info.title;
fetching = false;
player = info.player;
title.value = info.title;
fetching.value = false;
player.value = info.player;
});
});
};

View File

@ -21,7 +21,7 @@
</template>
<script lang="ts" setup>
import { toRefs, Ref } from 'vue';
import { toRefs, Ref, ref } from 'vue';
import * as os from '@/os';
import Ripple from '@/components/MkRipple.vue';
import { i18n } from '@/i18n';
@ -35,16 +35,16 @@ const emit = defineEmits<{
(ev: 'update:modelValue', v: boolean): void;
}>();
let button = $ref<HTMLElement>();
let button = ref<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);
const rect = button.value.getBoundingClientRect();
const x = rect.left + (button.value.offsetWidth / 2);
const y = rect.top + (button.value.offsetHeight / 2);
os.popup(Ripple, { x, y, particle: false }, {}, 'end');
}
};

View File

@ -20,19 +20,21 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const props = withDefaults(defineProps<{
defaultOpen: boolean;
}>(), {
defaultOpen: false,
});
let opened = $ref(props.defaultOpen);
let openedAtLeastOnce = $ref(props.defaultOpen);
let opened = ref(props.defaultOpen);
let openedAtLeastOnce = ref(props.defaultOpen);
const toggle = () => {
opened = !opened;
if (opened) {
openedAtLeastOnce = true;
opened.value = !opened.value;
if (opened.value) {
openedAtLeastOnce.value = true;
}
};
</script>

View File

@ -19,7 +19,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
const props = defineProps<{
modelValue: any;
@ -31,7 +31,7 @@ const emit = defineEmits<{
(ev: 'update:modelValue', value: any): void;
}>();
let checked = $computed(() => props.modelValue === props.value);
let checked = computed(() => props.modelValue === props.value);
function toggle(): void {
if (props.disabled) return;

View File

@ -21,7 +21,7 @@
</template>
<script lang="ts" setup>
import { toRefs, Ref } from 'vue';
import { toRefs, Ref, ref } from 'vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
@ -34,7 +34,7 @@ const emit = defineEmits<{
(ev: 'update:modelValue', v: boolean): void;
}>();
let button = $ref<HTMLElement>();
let button = ref<HTMLElement>();
const checked = toRefs(props).modelValue;
const toggle = () => {
if (props.disabled) return;

View File

@ -5,7 +5,7 @@
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import { inject, computed } from 'vue';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { url } from '@/config';
@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
const router = useRouter();
const active = $computed(() => {
const active = computed(() => {
if (props.activeClass == null) return false;
const resolved = router.resolve(props.to);
if (resolved == null) return false;

View File

@ -10,7 +10,7 @@
</template>
<script lang="ts" setup>
import { onMounted, watch } from 'vue';
import { onMounted, watch, ref } from 'vue';
import * as misskey from 'misskey-js';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
@ -48,10 +48,10 @@ function onClick(ev: MouseEvent) {
emit('click', ev);
}
let color = $ref();
let color = ref();
watch(() => props.user.avatarBlurhash, () => {
color = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
color.value = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
}, {
immediate: true,
});

View File

@ -78,21 +78,21 @@ const metadata = injectPageMetadata();
const hideTitle = inject('shouldOmitHeaderTitle', false);
const thin_ = props.thin || inject('shouldHeaderThin', false);
const el = $ref<HTMLElement | null>(null);
const el = ref<HTMLElement | null>(null);
const tabRefs = {};
const tabHighlightEl = $ref<HTMLElement | null>(null);
const tabHighlightEl = ref<HTMLElement | null>(null);
const bg = ref(null);
let narrow = $ref(false);
let narrow = ref(false);
const height = ref(0);
const hasTabs = $computed(() => props.tabs && props.tabs.length > 0);
const hasActions = $computed(() => props.actions && props.actions.length > 0);
const show = $computed(() => {
return !hideTitle || hasTabs || hasActions;
const hasTabs = computed(() => props.tabs && props.tabs.length > 0);
const hasActions = computed(() => props.actions && props.actions.length > 0);
const show = computed(() => {
return !hideTitle || hasTabs.value || hasActions.value;
});
const showTabsPopup = (ev: MouseEvent) => {
if (!hasTabs) return;
if (!narrow) return;
if (!hasTabs.value) return;
if (!narrow.value) return;
ev.preventDefault();
ev.stopPropagation();
const menu = props.tabs.map(tab => ({
@ -111,7 +111,7 @@ const preventDrag = (ev: TouchEvent) => {
};
const onClick = () => {
scrollToTop(el, { behavior: 'smooth' });
scrollToTop(el.value, { behavior: 'smooth' });
};
function onTabMousedown(tab: Tab, ev: MouseEvent): void {
@ -148,27 +148,27 @@ onMounted(() => {
watch(() => [props.tab, props.tabs], () => {
nextTick(() => {
const tabEl = tabRefs[props.tab];
if (tabEl && tabHighlightEl) {
if (tabEl && tabHighlightEl.value) {
// offsetWidth offsetLeft getBoundingClientRect 使
// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
const parentRect = tabEl.parentElement.getBoundingClientRect();
const rect = tabEl.getBoundingClientRect();
tabHighlightEl.style.width = rect.width + 'px';
tabHighlightEl.style.left = (rect.left - parentRect.left) + 'px';
tabHighlightEl.value.style.width = rect.width + 'px';
tabHighlightEl.value.style.left = (rect.left - parentRect.left) + 'px';
}
});
}, {
immediate: true,
});
if (el && el.parentElement) {
narrow = el.parentElement.offsetWidth < 500;
if (el.value && el.value.parentElement) {
narrow.value = el.value.parentElement.offsetWidth < 500;
ro = new ResizeObserver((entries, observer) => {
if (el.parentElement && document.body.contains(el)) {
narrow = el.parentElement.offsetWidth < 500;
if (el.value.parentElement && document.body.contains(el.value)) {
narrow.value = el.value.parentElement.offsetWidth < 500;
}
});
ro.observe(el.parentElement);
ro.observe(el.value.parentElement);
}
});

View File

@ -21,21 +21,21 @@ const props = withDefaults(defineProps<{
});
let ro: ResizeObserver;
let root = $ref<HTMLElement>();
let content = $ref<HTMLElement>();
let margin = $ref(0);
let root = ref<HTMLElement>();
let content = ref<HTMLElement>();
let margin = ref(0);
const shouldSpacerMin = inject('shouldSpacerMin', false);
const adjust = (rect: { width: number; height: number; }) => {
if (shouldSpacerMin || deviceKind === 'smartphone') {
margin = props.marginMin;
margin.value = props.marginMin;
return;
}
if (rect.width > (props.contentMax ?? 0) || (rect.width > 360 && window.innerWidth > 400)) {
margin = props.marginMax;
margin.value = props.marginMax;
} else {
margin = props.marginMin;
margin.value = props.marginMin;
}
};
@ -48,14 +48,14 @@ onMounted(() => {
});
*/
adjust({
width: root!.offsetWidth,
height: root!.offsetHeight,
width: root.value!.offsetWidth,
height: root.value!.offsetHeight,
});
});
ro.observe(root!);
ro.observe(root.value!);
if (props.contentMax) {
content!.style.maxWidth = `${props.contentMax}px`;
content.value!.style.maxWidth = `${props.contentMax}px`;
}
});

View File

@ -18,18 +18,18 @@ const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
<script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue';
const rootEl = $ref<HTMLElement>();
const headerEl = $ref<HTMLElement>();
const bodyEl = $ref<HTMLElement>();
const rootEl = ref<HTMLElement>();
const headerEl = ref<HTMLElement>();
const bodyEl = ref<HTMLElement>();
let headerHeight = $ref<string | undefined>();
let childStickyTop = $ref(0);
let headerHeight = ref<string | undefined>();
let childStickyTop = ref(0);
const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
provide(CURRENT_STICKY_TOP, $$(childStickyTop));
provide(CURRENT_STICKY_TOP, (childStickyTop));
const calc = () => {
childStickyTop = parentStickyTop.value + headerEl.offsetHeight;
headerHeight = headerEl.offsetHeight.toString();
childStickyTop.value = parentStickyTop.value + headerEl.value.offsetHeight;
headerHeight.value = headerEl.value.offsetHeight.toString();
};
const observer = new ResizeObserver(() => {
@ -43,17 +43,17 @@ onMounted(() => {
watch(parentStickyTop, calc);
watch($$(childStickyTop), () => {
bodyEl.style.setProperty('--stickyTop', `${childStickyTop}px`);
watch((childStickyTop), () => {
bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`);
}, {
immediate: true,
});
headerEl.style.position = 'sticky';
headerEl.style.top = 'var(--stickyTop, 0)';
headerEl.style.zIndex = '1000';
headerEl.value.style.position = 'sticky';
headerEl.value.style.top = 'var(--stickyTop, 0)';
headerEl.value.style.zIndex = '1000';
observer.observe(headerEl);
observer.observe(headerEl.value);
});
onUnmounted(() => {

View File

@ -7,7 +7,7 @@
</template>
<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { onUnmounted, shallowRef, computed } from 'vue';
import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
@ -20,9 +20,9 @@ const props = withDefaults(defineProps<{
const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
const absolute = _time.toLocaleString();
let now = $shallowRef(new Date());
const relative = $computed(() => {
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
let now = shallowRef(new Date());
const relative = computed(() => {
const ago = (now.value.getTime() - _time.getTime()) / 1000/*ms*/;
return (
ago >= 86400 ? _time.toLocaleDateString() :
ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) :
@ -32,8 +32,8 @@ const relative = $computed(() => {
i18n.ts._ago.future);
});
const allRelative = $computed(() => {
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
const allRelative = computed(() => {
const ago = (now.value.getTime() - _time.getTime()) / 1000/*ms*/;
return (
ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) :
ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) :
@ -48,7 +48,7 @@ const allRelative = $computed(() => {
function tick() {
// TODO:
now = new Date();
now.value = new Date();
tickId = window.setTimeout(() => {
window.requestAnimationFrame(tick);

View File

@ -11,7 +11,7 @@
</template>
<script lang="ts" setup>
import { inject, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, watch } from 'vue';
import { inject, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, watch, shallowRef, ref } from 'vue';
import { Resolved, Router } from '@/nirax';
import { defaultStore } from '@/store';
@ -41,16 +41,16 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
}
const current = resolveNested(router.current)!;
let currentPageComponent = $shallowRef(current.route.component);
let currentPageProps = $ref(current.props);
let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
let currentPageComponent = shallowRef(current.route.component);
let currentPageProps = ref(current.props);
let key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
function onChange({ resolved, key: newKey }) {
const current = resolveNested(resolved);
if (current == null) return;
currentPageComponent = current.route.component;
currentPageProps = current.props;
key = current.route.path + JSON.stringify(Object.fromEntries(current.props));
currentPageComponent.value = current.route.component;
currentPageProps.value = current.props;
key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
}
router.addListener('change', onChange);

View File

@ -18,7 +18,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import * as misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import { version } from '@/config';
@ -32,29 +32,29 @@ const props = withDefaults(defineProps<{
}>(), {
});
let loaded = $ref(false);
let serverIsDead = $ref(false);
let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null);
let loaded = ref(false);
let serverIsDead = ref(false);
let meta = ref<misskey.entities.LiteInstanceMetadata | null>(null);
os.api('meta', {
detail: false,
}).then(res => {
loaded = true;
serverIsDead = false;
meta = res;
loaded.value = true;
serverIsDead.value = false;
meta.value = res;
localStorage.setItem('v', res.version);
}, () => {
loaded = true;
serverIsDead = true;
loaded.value = true;
serverIsDead.value = true;
});
function reload() {
unisonReload();
}
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.error,

View File

@ -63,7 +63,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onBeforeUnmount } from 'vue';
import { nextTick, onBeforeUnmount, ref, computed } from 'vue';
import { version } from '@/config';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
@ -157,15 +157,15 @@ const patrons = [
];
let easterEggReady = false;
let easterEggEmojis = $ref([]);
let easterEggEngine = $ref(null);
const containerEl = $ref<HTMLElement>();
let easterEggEmojis = ref([]);
let easterEggEngine = ref(null);
const containerEl = ref<HTMLElement>();
function iconLoaded() {
const emojis = defaultStore.state.reactions;
const containerWidth = containerEl.offsetWidth;
const containerWidth = containerEl.value.offsetWidth;
for (let i = 0; i < 32; i++) {
easterEggEmojis.push({
easterEggEmojis.value.push({
id: i.toString(),
top: -(128 + (Math.random() * 256)),
left: (Math.random() * containerWidth),
@ -181,7 +181,7 @@ function iconLoaded() {
function gravity() {
if (!easterEggReady) return;
easterEggReady = false;
easterEggEngine = physics(containerEl);
easterEggEngine.value = physics(containerEl.value);
}
function iLoveMisskey() {
@ -192,14 +192,14 @@ function iLoveMisskey() {
}
onBeforeUnmount(() => {
if (easterEggEngine) {
easterEggEngine.stop();
if (easterEggEngine.value) {
easterEggEngine.value.stop();
}
});
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.aboutMisskey,

View File

@ -47,7 +47,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@ -57,23 +57,23 @@ import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
let host = $ref('');
let state = $ref('federating');
let sort = $ref('+pubSub');
let host = ref('');
let state = ref('federating');
let sort = ref('+pubSub');
const pagination = {
endpoint: 'federation/instances' as const,
limit: 10,
offsetMode: true,
params: computed(() => ({
sort: sort,
host: host !== '' ? host : null,
sort: sort.value,
host: host.value !== '' ? host.value : null,
...(
state === 'federating' ? { federating: true } :
state === 'subscribing' ? { subscribing: true } :
state === 'publishing' ? { publishing: true } :
state === 'suspended' ? { suspended: true } :
state === 'blocked' ? { blocked: true } :
state === 'notResponding' ? { notResponding: true } :
state.value === 'federating' ? { federating: true } :
state.value === 'subscribing' ? { subscribing: true } :
state.value === 'publishing' ? { publishing: true } :
state.value === 'suspended' ? { suspended: true } :
state.value === 'blocked' ? { blocked: true } :
state.value === 'notResponding' ? { notResponding: true } :
{}),
})),
};

View File

@ -103,17 +103,17 @@ const props = withDefaults(defineProps<{
initialTab: 'overview',
});
let stats = $ref(null);
let tab = $ref(props.initialTab);
let stats = ref(null);
let tab = ref(props.initialTab);
const initStats = () => os.api('stats', {
}).then((res) => {
stats = res;
stats.value = res;
});
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => [{
const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
}, {

View File

@ -62,7 +62,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkObjectView from '@/components/MkObjectView.vue';
@ -78,19 +78,19 @@ import { definePageMetadata } from '@/scripts/page-metadata';
import { acct } from '@/filters/user';
import { iAmAdmin, iAmModerator } from '@/account';
let tab = $ref('overview');
let file: any = $ref(null);
let info: any = $ref(null);
let isSensitive: boolean = $ref(false);
let tab = ref('overview');
let file: any = ref(null);
let info: any = ref(null);
let isSensitive: boolean = ref(false);
const props = defineProps<{
fileId: string,
}>();
async function fetch() {
file = await os.api('drive/files/show', { fileId: props.fileId });
info = await os.api('admin/drive/show-file', { fileId: props.fileId });
isSensitive = file.isSensitive;
file.value = await os.api('drive/files/show', { fileId: props.fileId });
info.value = await os.api('admin/drive/show-file', { fileId: props.fileId });
isSensitive.value = file.value.isSensitive;
}
fetch();
@ -98,29 +98,29 @@ fetch();
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('removeAreYouSure', { x: file.name }),
text: i18n.t('removeAreYouSure', { x: file.value.name }),
});
if (canceled) return;
os.apiWithDialog('drive/files/delete', {
fileId: file.id,
fileId: file.value.id,
});
}
async function toggleIsSensitive(v) {
await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
isSensitive = v;
isSensitive.value = v;
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
text: i18n.ts.openInNewTab,
icon: 'fas fa-external-link-alt',
handler: () => {
window.open(file.url, '_blank');
window.open(file.value.url, '_blank');
},
}]);
const headerTabs = $computed(() => [{
const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
icon: 'fas fa-info-circle',
@ -135,7 +135,7 @@ const headerTabs = $computed(() => [{
}]);
definePageMetadata(computed(() => ({
title: file ? i18n.ts.file + ': ' + file.name : i18n.ts.file,
title: file.value ? i18n.ts.file + ': ' + file.value.name : i18n.ts.file,
icon: 'fas fa-file',
})));
</script>

View File

@ -66,7 +66,7 @@ const metadata = injectPageMetadata();
const el = ref<HTMLElement>(null);
const tabRefs = {};
const tabHighlightEl = $ref<HTMLElement | null>(null);
const tabHighlightEl = ref<HTMLElement | null>(null);
const bg = ref(null);
const height = ref(0);
const hasTabs = computed(() => {
@ -128,13 +128,13 @@ onMounted(() => {
watch(() => [props.tab, props.tabs], () => {
nextTick(() => {
const tabEl = tabRefs[props.tab];
if (tabEl && tabHighlightEl) {
if (tabEl && tabHighlightEl.value) {
// offsetWidth offsetLeft getBoundingClientRect 使
// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
const parentRect = tabEl.parentElement.getBoundingClientRect();
const rect = tabEl.getBoundingClientRect();
tabHighlightEl.style.width = rect.width + 'px';
tabHighlightEl.style.left = (rect.left - parentRect.left) + 'px';
tabHighlightEl.value.style.width = rect.width + 'px';
tabHighlightEl.value.style.left = (rect.left - parentRect.left) + 'px';
}
});
}, {

View File

@ -47,7 +47,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import XHeader from './_header_.vue';
import MkInput from '@/components/form/input.vue';
@ -58,31 +58,31 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let reports = $ref<InstanceType<typeof MkPagination>>();
let reports = ref<InstanceType<typeof MkPagination>>();
let state = $ref('unresolved');
let reporterOrigin = $ref('combined');
let targetUserOrigin = $ref('combined');
let searchUsername = $ref('');
let searchHost = $ref('');
let state = ref('unresolved');
let reporterOrigin = ref('combined');
let targetUserOrigin = ref('combined');
let searchUsername = ref('');
let searchHost = ref('');
const pagination = {
endpoint: 'admin/abuse-user-reports' as const,
limit: 10,
params: computed(() => ({
state,
reporterOrigin,
targetUserOrigin,
state: state.value,
reporterOrigin: reporterOrigin.value,
targetUserOrigin: targetUserOrigin.value,
})),
};
function resolved(reportId) {
reports.removeItem(item => item.id === reportId);
reports.value.removeItem(item => item.id === reportId);
}
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.abuseReports,

View File

@ -47,7 +47,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
@ -58,14 +58,14 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let ads: any[] = $ref([]);
let ads: any[] = ref([]);
os.api('admin/ad/list').then(adsResponse => {
ads = adsResponse;
ads.value = adsResponse;
});
function add() {
ads.unshift({
ads.value.unshift({
id: null,
memo: '',
place: 'square',
@ -83,7 +83,7 @@ function remove(ad) {
text: i18n.t('removeAreYouSure', { x: ad.url }),
}).then(({ canceled }) => {
if (canceled) return;
ads = ads.filter(x => x !== ad);
ads.value = ads.value.filter(x => x !== ad);
os.apiWithDialog('admin/ad/delete', {
id: ad.id,
});
@ -104,14 +104,14 @@ function save(ad) {
}
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.ts.add,
handler: add,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.ads,

View File

@ -27,7 +27,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
@ -36,14 +36,14 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let announcements: any[] = $ref([]);
let announcements: any[] = ref([]);
os.api('admin/announcements/list').then(announcementResponse => {
announcements = announcementResponse;
announcements.value = announcementResponse;
});
function add() {
announcements.unshift({
announcements.value.unshift({
id: null,
title: '',
text: '',
@ -57,7 +57,7 @@ function remove(announcement) {
text: i18n.t('removeAreYouSure', { x: announcement.title }),
}).then(({ canceled }) => {
if (canceled) return;
announcements = announcements.filter(x => x !== announcement);
announcements.value = announcements.value.filter(x => x !== announcement);
os.api('admin/announcements/delete', announcement);
});
}
@ -90,14 +90,14 @@ function save(announcement) {
}
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.ts.add,
handler: add,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.announcements,

View File

@ -44,7 +44,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import FormRadios from '@/components/form/radios.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/MkButton.vue';
@ -56,30 +56,30 @@ import { i18n } from '@/i18n';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
let provider = $ref(null);
let hcaptchaSiteKey: string | null = $ref(null);
let hcaptchaSecretKey: string | null = $ref(null);
let recaptchaSiteKey: string | null = $ref(null);
let recaptchaSecretKey: string | null = $ref(null);
let provider = ref(null);
let hcaptchaSiteKey: string | null = ref(null);
let hcaptchaSecretKey: string | null = ref(null);
let recaptchaSiteKey: string | null = ref(null);
let recaptchaSecretKey: string | null = ref(null);
async function init() {
const meta = await os.api('admin/meta');
hcaptchaSiteKey = meta.hcaptchaSiteKey;
hcaptchaSecretKey = meta.hcaptchaSecretKey;
recaptchaSiteKey = meta.recaptchaSiteKey;
recaptchaSecretKey = meta.recaptchaSecretKey;
hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
recaptchaSiteKey.value = meta.recaptchaSiteKey;
recaptchaSecretKey.value = meta.recaptchaSecretKey;
provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null;
provider.value = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableHcaptcha: provider === 'hcaptcha',
hcaptchaSiteKey,
hcaptchaSecretKey,
enableRecaptcha: provider === 'recaptcha',
recaptchaSiteKey,
recaptchaSecretKey,
enableHcaptcha: provider.value === 'hcaptcha',
hcaptchaSiteKey: hcaptchaSiteKey.value,
hcaptchaSecretKey: hcaptchaSecretKey.value,
enableRecaptcha: provider.value === 'recaptcha',
recaptchaSiteKey: recaptchaSiteKey.value,
recaptchaSecretKey: recaptchaSecretKey.value,
}).then(() => {
fetchInstance();
});

View File

@ -11,7 +11,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import * as os from '@/os';
@ -22,9 +22,9 @@ import { definePageMetadata } from '@/scripts/page-metadata';
const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.database,

View File

@ -46,7 +46,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
@ -59,23 +59,23 @@ import { fetchInstance, instance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let enableEmail: boolean = $ref(false);
let email: any = $ref(null);
let smtpSecure: boolean = $ref(false);
let smtpHost: string = $ref('');
let smtpPort: number = $ref(0);
let smtpUser: string = $ref('');
let smtpPass: string = $ref('');
let enableEmail: boolean = ref(false);
let email: any = ref(null);
let smtpSecure: boolean = ref(false);
let smtpHost: string = ref('');
let smtpPort: number = ref(0);
let smtpUser: string = ref('');
let smtpPass: string = ref('');
async function init() {
const meta = await os.api('admin/meta');
enableEmail = meta.enableEmail;
email = meta.email;
smtpSecure = meta.smtpSecure;
smtpHost = meta.smtpHost;
smtpPort = meta.smtpPort;
smtpUser = meta.smtpUser;
smtpPass = meta.smtpPass;
enableEmail.value = meta.enableEmail;
email.value = meta.email;
smtpSecure.value = meta.smtpSecure;
smtpHost.value = meta.smtpHost;
smtpPort.value = meta.smtpPort;
smtpUser.value = meta.smtpUser;
smtpPass.value = meta.smtpPass;
}
async function testEmail() {
@ -94,19 +94,19 @@ async function testEmail() {
function save() {
os.apiWithDialog('admin/update-meta', {
enableEmail,
email,
smtpSecure,
smtpHost,
smtpPort,
smtpUser,
smtpPass,
enableEmail: enableEmail.value,
email: email.value,
smtpSecure: smtpSecure.value,
smtpHost: smtpHost.value,
smtpPort: smtpPort.value,
smtpUser: smtpUser.value,
smtpPass: smtpPass.value,
}).then(() => {
fetchInstance();
});
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
text: i18n.ts.testEmail,
handler: testEmail,
@ -117,7 +117,7 @@ const headerActions = $computed(() => [{
handler: save,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.emailServer,

View File

@ -29,7 +29,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import XModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/form/input.vue';
@ -42,11 +42,11 @@ const props = defineProps<{
emoji: any,
}>();
let dialog = $ref(null);
let name: string = $ref(props.emoji.name);
let category: string = $ref(props.emoji.category);
let aliases: string = $ref(props.emoji.aliases.join(' '));
let categories: string[] = $ref(emojiCategories);
let dialog = ref(null);
let name: string = ref(props.emoji.name);
let category: string = ref(props.emoji.category);
let aliases: string = ref(props.emoji.aliases.join(' '));
let categories: string[] = ref(emojiCategories);
const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
@ -60,27 +60,27 @@ function ok() {
async function update() {
await os.apiWithDialog('admin/emoji/update', {
id: props.emoji.id,
name,
category,
aliases: aliases.split(' '),
name: name.value,
category: category.value,
aliases: aliases.value.split(' '),
});
emit('done', {
updated: {
id: props.emoji.id,
name,
category,
aliases: aliases.split(' '),
name: name.value,
category: category.value,
aliases: aliases.value.split(' '),
},
});
dialog.close();
dialog.value.close();
}
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('removeAreYouSure', { x: name }),
text: i18n.t('removeAreYouSure', { x: name.value }),
});
if (canceled) return;
@ -90,7 +90,7 @@ async function del() {
emit('done', {
deleted: true,
});
dialog.close();
dialog.value.close();
});
}
</script>

View File

@ -134,7 +134,7 @@ definePageMetadata(computed(() => ({
})));
const tab = ref('string');
const headerTabs = $computed(() => [{
const headerTabs = computed(() => [{
key: 'string',
title: i18n.ts._simkey.emojiNormal,
}, {

View File

@ -271,7 +271,7 @@ const delBulk = async () => {
emojisPaginationComponent.value.reload();
};
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.ts.addEmoji,
@ -281,7 +281,7 @@ const headerActions = $computed(() => [{
handler: menu,
}]);
const headerTabs = $computed(() => [{
const headerTabs = computed(() => [{
key: 'local',
title: i18n.ts.local,
}, {

View File

@ -33,7 +33,7 @@
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent } from 'vue';
import { computed, defineAsyncComponent, ref } from 'vue';
import * as Acct from 'misskey-js/built/acct';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
@ -45,19 +45,19 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let origin = $ref('local');
let type = $ref(null);
let searchHost = $ref('');
let userId = $ref('');
let viewMode = $ref('grid');
let origin = ref('local');
let type = ref(null);
let searchHost = ref('');
let userId = ref('');
let viewMode = ref('grid');
const pagination = {
endpoint: 'admin/drive/files' as const,
limit: 10,
params: computed(() => ({
type: (type && type !== '') ? type : null,
userId: (userId && userId !== '') ? userId : null,
origin: origin,
hostname: (searchHost && searchHost !== '') ? searchHost : null,
type: (type.value && type.value !== '') ? type.value : null,
userId: (userId.value && userId.value !== '') ? userId.value : null,
origin: origin.value,
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
})),
};
@ -95,7 +95,7 @@ async function find() {
});
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
text: i18n.ts.lookup,
icon: 'fas fa-search',
handler: find,
@ -105,7 +105,7 @@ const headerActions = $computed(() => [{
handler: clear,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata(computed(() => ({
title: i18n.ts.files,

View File

@ -23,7 +23,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch } from 'vue';
import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
import { $i } from '../../account';
import { i18n } from '@/i18n';
import MkSuperMenu from '@/components/MkSuperMenu.vue';
@ -47,32 +47,32 @@ const indexInfo = {
provide('shouldOmitHeaderTitle', false);
let INFO = $ref(indexInfo);
let childInfo = $ref(null);
let narrow = $ref(false);
let view = $ref(null);
let el = $ref(null);
let pageProps = $ref({});
let INFO = ref(indexInfo);
let childInfo = ref(null);
let narrow = ref(false);
let view = ref(null);
let el = ref(null);
let pageProps = ref({});
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha;
let noEmailServer = !instance.enableEmail;
let thereIsUnresolvedAbuseReport = $ref(false);
let currentPage = $computed(() => router.currentRef.value.child);
let thereIsUnresolvedAbuseReport = ref(false);
let currentPage = computed(() => router.currentRef.value.child);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
limit: 1,
}).then(reports => {
if (reports.length > 0) thereIsUnresolvedAbuseReport = true;
if (reports.length > 0) thereIsUnresolvedAbuseReport.value = true;
});
const NARROW_THRESHOLD = 600;
const ro = new ResizeObserver((entries, observer) => {
if (entries.length === 0) return;
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
});
const menuDef = $computed(() => [{
const menuDef = computed(() => [{
title: i18n.ts.quickAction,
items: [{
type: 'button',
@ -91,52 +91,52 @@ const menuDef = $computed(() => [{
icon: 'fas fa-tachometer-alt',
text: i18n.ts.dashboard,
to: '/admin/overview',
active: currentPage?.route.name === 'overview',
active: currentPage.value?.route.name === 'overview',
}, {
icon: 'fas fa-users',
text: i18n.ts.users,
to: '/admin/users',
active: currentPage?.route.name === 'users',
active: currentPage.value?.route.name === 'users',
}, {
icon: 'fas fa-laugh',
text: i18n.ts.customEmojis,
to: '/admin/emojis',
active: currentPage?.route.name === 'emojis',
active: currentPage.value?.route.name === 'emojis',
}, {
icon: 'fas fa-kiss-wink-heart',
text: i18n.ts._simkey.emojiGen,
to: '/admin/emojigen',
active: currentPage?.route.name === 'emojigen',
active: currentPage.value?.route.name === 'emojigen',
}, {
icon: 'fas fa-globe',
text: i18n.ts.federation,
to: '/about#federation',
active: currentPage?.route.name === 'federation',
active: currentPage.value?.route.name === 'federation',
}, {
icon: 'fas fa-clipboard-list',
text: i18n.ts.jobQueue,
to: '/admin/queue',
active: currentPage?.route.name === 'queue',
active: currentPage.value?.route.name === 'queue',
}, {
icon: 'fas fa-cloud',
text: i18n.ts.files,
to: '/admin/files',
active: currentPage?.route.name === 'files',
active: currentPage.value?.route.name === 'files',
}, {
icon: 'fas fa-broadcast-tower',
text: i18n.ts.announcements,
to: '/admin/announcements',
active: currentPage?.route.name === 'announcements',
active: currentPage.value?.route.name === 'announcements',
}, {
icon: 'fas fa-audio-description',
text: i18n.ts.ads,
to: '/admin/ads',
active: currentPage?.route.name === 'ads',
active: currentPage.value?.route.name === 'ads',
}, {
icon: 'fas fa-exclamation-circle',
text: i18n.ts.abuseReports,
to: '/admin/abuses',
active: currentPage?.route.name === 'abuses',
active: currentPage.value?.route.name === 'abuses',
}],
}, {
title: i18n.ts.settings,
@ -144,47 +144,47 @@ const menuDef = $computed(() => [{
icon: 'fas fa-cog',
text: i18n.ts.general,
to: '/admin/settings',
active: currentPage?.route.name === 'settings',
active: currentPage.value?.route.name === 'settings',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-envelope',
text: i18n.ts.emailServer,
to: '/admin/email-settings',
active: currentPage?.route.name === 'email-settings',
active: currentPage.value?.route.name === 'email-settings',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-cloud',
text: i18n.ts.objectStorage,
to: '/admin/object-storage',
active: currentPage?.route.name === 'object-storage',
active: currentPage.value?.route.name === 'object-storage',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-lock',
text: i18n.ts.security,
to: '/admin/security',
active: currentPage?.route.name === 'security',
active: currentPage.value?.route.name === 'security',
}] : []), {
icon: 'fas fa-globe',
text: i18n.ts.relays,
to: '/admin/relays',
active: currentPage?.route.name === 'relays',
active: currentPage.value?.route.name === 'relays',
}, ...($i?.isAdmin ? [{
icon: 'fas fa-share-alt',
text: i18n.ts.integration,
to: '/admin/integrations',
active: currentPage?.route.name === 'integrations',
active: currentPage.value?.route.name === 'integrations',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-ban',
text: i18n.ts.instanceBlocking,
to: '/admin/instance-block',
active: currentPage?.route.name === 'instance-block',
active: currentPage.value?.route.name === 'instance-block',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-ghost',
text: i18n.ts.proxyAccount,
to: '/admin/proxy-account',
active: currentPage?.route.name === 'proxy-account',
active: currentPage.value?.route.name === 'proxy-account',
}] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-cogs',
text: i18n.ts.other,
to: '/admin/other-settings',
active: currentPage?.route.name === 'other-settings',
active: currentPage.value?.route.name === 'other-settings',
}] : [])],
}, {
title: i18n.ts.info,
@ -192,21 +192,21 @@ const menuDef = $computed(() => [{
icon: 'fas fa-database',
text: i18n.ts.database,
to: '/admin/database',
active: currentPage?.route.name === 'database',
active: currentPage.value?.route.name === 'database',
}],
}]);
watch(narrow, () => {
if (currentPage?.route.name == null && !narrow) {
watch(narrow.value, () => {
if (currentPage.value?.route.name == null && !narrow.value) {
router.push('/admin/overview');
}
});
onMounted(() => {
ro.observe(el);
ro.observe(el.value);
narrow = el.offsetWidth < NARROW_THRESHOLD;
if (currentPage?.route.name == null && !narrow) {
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
if (currentPage.value?.route.name == null && !narrow.value) {
router.push('/admin/overview');
}
});
@ -217,9 +217,9 @@ onUnmounted(() => {
provideMetadataReceiver((info) => {
if (info == null) {
childInfo = null;
childInfo.value = null;
} else {
childInfo = info;
childInfo.value = info;
}
});
@ -265,11 +265,11 @@ const lookup = (ev) => {
}], ev.currentTarget ?? ev.target);
};
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata(INFO);
definePageMetadata(INFO.value);
defineExpose({
header: {

View File

@ -15,7 +15,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import FormButton from '@/components/MkButton.vue';
import FormTextarea from '@/components/form/textarea.vue';
@ -25,24 +25,24 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let blockedHosts: string = $ref('');
let blockedHosts: string = ref('');
async function init() {
const meta = await os.api('admin/meta');
blockedHosts = meta.blockedHosts.join('\n');
blockedHosts.value = meta.blockedHosts.join('\n');
}
function save() {
os.apiWithDialog('admin/update-meta', {
blockedHosts: blockedHosts.split('\n') || [],
blockedHosts: blockedHosts.value.split('\n') || [],
}).then(() => {
fetchInstance();
});
}
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.instanceBlocking,

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/MkButton.vue';
@ -35,24 +35,24 @@ import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableDiscordIntegration: boolean = $ref(false);
let discordClientId: string | null = $ref(null);
let discordClientSecret: string | null = $ref(null);
let uri: string = ref('');
let enableDiscordIntegration: boolean = ref(false);
let discordClientId: string | null = ref(null);
let discordClientSecret: string | null = ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableDiscordIntegration = meta.enableDiscordIntegration;
discordClientId = meta.discordClientId;
discordClientSecret = meta.discordClientSecret;
uri.value = meta.uri;
enableDiscordIntegration.value = meta.enableDiscordIntegration;
discordClientId.value = meta.discordClientId;
discordClientSecret.value = meta.discordClientSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableDiscordIntegration,
discordClientId,
discordClientSecret,
enableDiscordIntegration: enableDiscordIntegration.value,
discordClientId: discordClientId.value,
discordClientSecret: discordClientSecret.value,
}).then(() => {
fetchInstance();
});

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/MkButton.vue';
@ -35,24 +35,24 @@ import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableGithubIntegration: boolean = $ref(false);
let githubClientId: string | null = $ref(null);
let githubClientSecret: string | null = $ref(null);
let uri: string = ref('');
let enableGithubIntegration: boolean = ref(false);
let githubClientId: string | null = ref(null);
let githubClientSecret: string | null = ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableGithubIntegration = meta.enableGithubIntegration;
githubClientId = meta.githubClientId;
githubClientSecret = meta.githubClientSecret;
uri.value = meta.uri;
enableGithubIntegration.value = meta.enableGithubIntegration;
githubClientId.value = meta.githubClientId;
githubClientSecret.value = meta.githubClientSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableGithubIntegration,
githubClientId,
githubClientSecret,
enableGithubIntegration: enableGithubIntegration.value,
githubClientId: githubClientId.value,
githubClientSecret: githubClientSecret.value,
}).then(() => {
fetchInstance();
});

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { defineComponent } from 'vue';
import { defineComponent, ref } from 'vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/MkButton.vue';
@ -35,24 +35,24 @@ import * as os from '@/os';
import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
let uri: string = $ref('');
let enableTwitterIntegration: boolean = $ref(false);
let twitterConsumerKey: string | null = $ref(null);
let twitterConsumerSecret: string | null = $ref(null);
let uri: string = ref('');
let enableTwitterIntegration: boolean = ref(false);
let twitterConsumerKey: string | null = ref(null);
let twitterConsumerSecret: string | null = ref(null);
async function init() {
const meta = await os.api('admin/meta');
uri = meta.uri;
enableTwitterIntegration = meta.enableTwitterIntegration;
twitterConsumerKey = meta.twitterConsumerKey;
twitterConsumerSecret = meta.twitterConsumerSecret;
uri.value = meta.uri;
enableTwitterIntegration.value = meta.enableTwitterIntegration;
twitterConsumerKey.value = meta.twitterConsumerKey;
twitterConsumerSecret.value = meta.twitterConsumerSecret;
}
function save() {
os.apiWithDialog('admin/update-meta', {
enableTwitterIntegration,
twitterConsumerKey,
twitterConsumerSecret,
enableTwitterIntegration: enableTwitterIntegration.value,
twitterConsumerKey: twitterConsumerKey.value,
twitterConsumerSecret: twitterConsumerSecret.value,
}).then(() => {
fetchInstance();
});

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XTwitter from './integrations.twitter.vue';
import XGithub from './integrations.github.vue';
import XDiscord from './integrations.discord.vue';
@ -35,20 +35,20 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let enableTwitterIntegration: boolean = $ref(false);
let enableGithubIntegration: boolean = $ref(false);
let enableDiscordIntegration: boolean = $ref(false);
let enableTwitterIntegration: boolean = ref(false);
let enableGithubIntegration: boolean = ref(false);
let enableDiscordIntegration: boolean = ref(false);
async function init() {
const meta = await os.api('admin/meta');
enableTwitterIntegration = meta.enableTwitterIntegration;
enableGithubIntegration = meta.enableGithubIntegration;
enableDiscordIntegration = meta.enableDiscordIntegration;
enableTwitterIntegration.value = meta.enableTwitterIntegration;
enableGithubIntegration.value = meta.enableGithubIntegration;
enableDiscordIntegration.value = meta.enableDiscordIntegration;
}
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.integration,

View File

@ -69,7 +69,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormInput from '@/components/form/input.vue';
@ -81,65 +81,65 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let useObjectStorage: boolean = $ref(false);
let objectStorageBaseUrl: string | null = $ref(null);
let objectStorageBucket: string | null = $ref(null);
let objectStoragePrefix: string | null = $ref(null);
let objectStorageEndpoint: string | null = $ref(null);
let objectStorageRegion: string | null = $ref(null);
let objectStoragePort: number | null = $ref(null);
let objectStorageAccessKey: string | null = $ref(null);
let objectStorageSecretKey: string | null = $ref(null);
let objectStorageUseSSL: boolean = $ref(false);
let objectStorageUseProxy: boolean = $ref(false);
let objectStorageSetPublicRead: boolean = $ref(false);
let objectStorageS3ForcePathStyle: boolean = $ref(true);
let useObjectStorage: boolean = ref(false);
let objectStorageBaseUrl: string | null = ref(null);
let objectStorageBucket: string | null = ref(null);
let objectStoragePrefix: string | null = ref(null);
let objectStorageEndpoint: string | null = ref(null);
let objectStorageRegion: string | null = ref(null);
let objectStoragePort: number | null = ref(null);
let objectStorageAccessKey: string | null = ref(null);
let objectStorageSecretKey: string | null = ref(null);
let objectStorageUseSSL: boolean = ref(false);
let objectStorageUseProxy: boolean = ref(false);
let objectStorageSetPublicRead: boolean = ref(false);
let objectStorageS3ForcePathStyle: boolean = ref(true);
async function init() {
const meta = await os.api('admin/meta');
useObjectStorage = meta.useObjectStorage;
objectStorageBaseUrl = meta.objectStorageBaseUrl;
objectStorageBucket = meta.objectStorageBucket;
objectStoragePrefix = meta.objectStoragePrefix;
objectStorageEndpoint = meta.objectStorageEndpoint;
objectStorageRegion = meta.objectStorageRegion;
objectStoragePort = meta.objectStoragePort;
objectStorageAccessKey = meta.objectStorageAccessKey;
objectStorageSecretKey = meta.objectStorageSecretKey;
objectStorageUseSSL = meta.objectStorageUseSSL;
objectStorageUseProxy = meta.objectStorageUseProxy;
objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
useObjectStorage.value = meta.useObjectStorage;
objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
objectStorageBucket.value = meta.objectStorageBucket;
objectStoragePrefix.value = meta.objectStoragePrefix;
objectStorageEndpoint.value = meta.objectStorageEndpoint;
objectStorageRegion.value = meta.objectStorageRegion;
objectStoragePort.value = meta.objectStoragePort;
objectStorageAccessKey.value = meta.objectStorageAccessKey;
objectStorageSecretKey.value = meta.objectStorageSecretKey;
objectStorageUseSSL.value = meta.objectStorageUseSSL;
objectStorageUseProxy.value = meta.objectStorageUseProxy;
objectStorageSetPublicRead.value = meta.objectStorageSetPublicRead;
objectStorageS3ForcePathStyle.value = meta.objectStorageS3ForcePathStyle;
}
function save() {
os.apiWithDialog('admin/update-meta', {
useObjectStorage,
objectStorageBaseUrl,
objectStorageBucket,
objectStoragePrefix,
objectStorageEndpoint,
objectStorageRegion,
objectStoragePort,
objectStorageAccessKey,
objectStorageSecretKey,
objectStorageUseSSL,
objectStorageUseProxy,
objectStorageSetPublicRead,
objectStorageS3ForcePathStyle,
useObjectStorage: useObjectStorage.value,
objectStorageBaseUrl: objectStorageBaseUrl.value,
objectStorageBucket: objectStorageBucket.value,
objectStoragePrefix: objectStoragePrefix.value,
objectStorageEndpoint: objectStorageEndpoint.value,
objectStorageRegion: objectStorageRegion.value,
objectStoragePort: objectStoragePort.value,
objectStorageAccessKey: objectStorageAccessKey.value,
objectStorageSecretKey: objectStorageSecretKey.value,
objectStorageUseSSL: objectStorageUseSSL.value,
objectStorageUseProxy: objectStorageUseProxy.value,
objectStorageSetPublicRead: objectStorageSetPublicRead.value,
objectStorageS3ForcePathStyle: objectStorageS3ForcePathStyle.value,
}).then(() => {
fetchInstance();
});
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-check',
text: i18n.ts.save,
handler: save,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.objectStorage,

View File

@ -10,7 +10,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
import XHeader from './_header_.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
@ -28,14 +28,14 @@ function save() {
});
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-check',
text: i18n.ts.save,
handler: save,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.other,

View File

@ -10,6 +10,8 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import * as misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os';
@ -19,10 +21,10 @@ const props = defineProps<{
user: misskey.entities.User;
}>();
let chart = $ref(null);
let chart = ref(null);
os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16, span: 'day' }).then(res => {
chart = res;
chart.value = res;
});
</script>

View File

@ -139,7 +139,7 @@
</template>
<script lang="ts" setup>
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, ref, shallowRef, computed } from 'vue';
import {
Chart,
ArcElement,
@ -196,20 +196,20 @@ Chart.register(
//gradient,
);
const rootEl = $ref<HTMLElement>();
const chartEl = $ref<HTMLCanvasElement>(null);
let stats: any = $ref(null);
let serverInfo: any = $ref(null);
let topSubInstancesForPie: any = $ref(null);
let topPubInstancesForPie: any = $ref(null);
let usersComparedToThePrevDay: any = $ref(null);
let notesComparedToThePrevDay: any = $ref(null);
let federationPubActive = $ref<number | null>(null);
let federationPubActiveDiff = $ref<number | null>(null);
let federationSubActive = $ref<number | null>(null);
let federationSubActiveDiff = $ref<number | null>(null);
let newUsers = $ref(null);
let activeInstances = $shallowRef(null);
const rootEl = ref<HTMLElement>();
const chartEl = ref<HTMLCanvasElement>(null);
let stats: any = ref(null);
let serverInfo: any = ref(null);
let topSubInstancesForPie: any = ref(null);
let topPubInstancesForPie: any = ref(null);
let usersComparedToThePrevDay: any = ref(null);
let notesComparedToThePrevDay: any = ref(null);
let federationPubActive = ref<number | null>(null);
let federationPubActiveDiff = ref<number | null>(null);
let federationSubActive = ref<number | null>(null);
let federationSubActiveDiff = ref<number | null>(null);
let newUsers = ref(null);
let activeInstances = shallowRef(null);
const queueStatsConnection = markRaw(stream.useChannel('queueStats'));
const now = new Date();
let chartInstance: Chart = null;
@ -252,7 +252,7 @@ async function renderChart() {
const color = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
chartInstance = new Chart(chartEl, {
chartInstance = new Chart(chartEl.value, {
type: 'bar',
data: {
//labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
@ -394,26 +394,26 @@ onMounted(async () => {
renderChart();
os.api('stats', {}).then(statsResponse => {
stats = statsResponse;
stats.value = statsResponse;
os.apiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
usersComparedToThePrevDay.value = stats.value.originalUsersCount - chart.local.total[1];
});
os.apiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
notesComparedToThePrevDay.value = stats.value.originalNotesCount - chart.local.total[1];
});
});
os.apiGet('charts/federation', { limit: 2, span: 'day' }).then(chart => {
federationPubActive = chart.pubActive[0];
federationPubActiveDiff = chart.pubActive[0] - chart.pubActive[1];
federationSubActive = chart.subActive[0];
federationSubActiveDiff = chart.subActive[0] - chart.subActive[1];
federationPubActive.value = chart.pubActive[0];
federationPubActiveDiff.value = chart.pubActive[0] - chart.pubActive[1];
federationSubActive.value = chart.subActive[0];
federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
});
os.apiGet('federation/stats', { limit: 10 }).then(res => {
topSubInstancesForPie = res.topSubInstances.map(x => ({
topSubInstancesForPie.value = res.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
@ -421,7 +421,7 @@ onMounted(async () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]);
topPubInstancesForPie = res.topPubInstances.map(x => ({
topPubInstancesForPie.value = res.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,
@ -432,21 +432,21 @@ onMounted(async () => {
});
os.api('admin/server-info').then(serverInfoResponse => {
serverInfo = serverInfoResponse;
serverInfo.value = serverInfoResponse;
});
os.api('admin/show-users', {
limit: 5,
sort: '+createdAt',
}).then(res => {
newUsers = res;
newUsers.value = res;
});
os.api('federation/instances', {
sort: '+lastCommunicatedAt',
limit: 25,
}).then(res => {
activeInstances = res;
activeInstances.value = res;
});
nextTick(() => {
@ -461,9 +461,9 @@ onBeforeUnmount(() => {
queueStatsConnection.dispose();
});
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.dashboard,

View File

@ -14,7 +14,7 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import FormButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
@ -24,36 +24,36 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let proxyAccount: any = $ref(null);
let proxyAccountId: any = $ref(null);
let proxyAccount: any = ref(null);
let proxyAccountId: any = ref(null);
async function init() {
const meta = await os.api('admin/meta');
proxyAccountId = meta.proxyAccountId;
if (proxyAccountId) {
proxyAccount = await os.api('users/show', { userId: proxyAccountId });
proxyAccountId.value = meta.proxyAccountId;
if (proxyAccountId.value) {
proxyAccount.value = await os.api('users/show', { userId: proxyAccountId.value });
}
}
function chooseProxyAccount() {
os.selectUser().then(user => {
proxyAccount = user;
proxyAccountId = user.id;
proxyAccount.value = user;
proxyAccountId.value = user.id;
save();
});
}
function save() {
os.apiWithDialog('admin/update-meta', {
proxyAccountId: proxyAccountId,
proxyAccountId: proxyAccountId.value,
}).then(() => {
fetchInstance();
});
}
const headerActions = $computed(() => []);
const headerActions = computed(() => []);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.proxyAccount,

View File

@ -53,10 +53,10 @@ const active = ref(0);
const delayed = ref(0);
const waiting = ref(0);
const jobs = ref([]);
let chartProcess = $ref<InstanceType<typeof XChart>>();
let chartActive = $ref<InstanceType<typeof XChart>>();
let chartDelayed = $ref<InstanceType<typeof XChart>>();
let chartWaiting = $ref<InstanceType<typeof XChart>>();
let chartProcess = ref<InstanceType<typeof XChart>>();
let chartActive = ref<InstanceType<typeof XChart>>();
let chartDelayed = ref<InstanceType<typeof XChart>>();
let chartWaiting = ref<InstanceType<typeof XChart>>();
const props = defineProps<{
domain: string;
@ -68,10 +68,10 @@ const onStats = (stats) => {
delayed.value = stats[props.domain].delayed;
waiting.value = stats[props.domain].waiting;
chartProcess.pushData(stats[props.domain].activeSincePrevTick);
chartActive.pushData(stats[props.domain].active);
chartDelayed.pushData(stats[props.domain].delayed);
chartWaiting.pushData(stats[props.domain].waiting);
chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
chartActive.value.pushData(stats[props.domain].active);
chartDelayed.value.pushData(stats[props.domain].delayed);
chartWaiting.value.pushData(stats[props.domain].waiting);
};
const onStatsLog = (statsLog) => {
@ -87,10 +87,10 @@ const onStatsLog = (statsLog) => {
dataWaiting.push(stats[props.domain].waiting);
}
chartProcess.setData(dataProcess);
chartActive.setData(dataActive);
chartDelayed.setData(dataDelayed);
chartWaiting.setData(dataWaiting);
chartProcess.value.setData(dataProcess);
chartActive.value.setData(dataActive);
chartDelayed.value.setData(dataDelayed);
chartWaiting.value.setData(dataWaiting);
};
onMounted(() => {

View File

@ -9,7 +9,7 @@
</template>
<script lang="ts" setup>
import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { markRaw, onMounted, onBeforeUnmount, nextTick, ref, computed } from 'vue';
import XQueue from './queue.chart.vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
@ -18,7 +18,7 @@ import * as config from '@/config';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let tab = $ref('deliver');
let tab = ref('deliver');
function clear() {
os.confirm({
@ -32,7 +32,7 @@ function clear() {
});
}
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-up-right-from-square',
text: i18n.ts.dashboard,
@ -41,7 +41,7 @@ const headerActions = $computed(() => [{
},
}]);
const headerTabs = $computed(() => [{
const headerTabs = computed(() => [{
key: 'deliver',
title: 'Deliver',
}, {

View File

@ -17,14 +17,14 @@
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
let relays: any[] = $ref([]);
let relays: any[] = ref([]);
async function addRelay() {
const { canceled, result: inbox } = await os.inputText({
@ -60,20 +60,20 @@ function remove(inbox: string) {
function refresh() {
os.api('admin/relays/list').then((relayList: any) => {
relays = relayList;
relays.value = relayList;
});
}
refresh();
const headerActions = $computed(() => [{
const headerActions = computed(() => [{
asFullButton: true,
icon: 'fas fa-plus',
text: i18n.ts.addRelay,
handler: addRelay,
}]);
const headerTabs = $computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.relays,

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