mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-08-21 10:33:37 +00:00
lint and type fixes
This commit is contained in:
parent
54071efaea
commit
6ac37b4d6c
84 changed files with 188 additions and 374 deletions
|
@ -178,15 +178,13 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
// Wait for all potential DB queries
|
// Wait for all potential DB queries
|
||||||
await allSettled();
|
await allSettled();
|
||||||
// And then disconnect from DB
|
// And then disconnect from DB
|
||||||
await Promise.all([
|
await this.db.destroy();
|
||||||
this.db.destroy(),
|
this.redisClient.disconnect();
|
||||||
this.redisClient.disconnect(),
|
this.redisForPub.disconnect();
|
||||||
this.redisForPub.disconnect(),
|
this.redisForSub.disconnect();
|
||||||
this.redisForSub.disconnect(),
|
this.redisForTimelines.disconnect();
|
||||||
this.redisForTimelines.disconnect(),
|
this.redisForReactions.disconnect();
|
||||||
this.redisForReactions.disconnect(),
|
this.redisForRateLimit.disconnect();
|
||||||
this.redisForRateLimit.disconnect(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onApplicationShutdown(signal: string): Promise<void> {
|
async onApplicationShutdown(signal: string): Promise<void> {
|
||||||
|
|
|
@ -139,6 +139,24 @@ export class ApLogService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all logged inbox activities from a user or users
|
||||||
|
* @param userIds IDs of the users to delete
|
||||||
|
*/
|
||||||
|
public async deleteInboxLogs(userIds: string | string[]): Promise<number> {
|
||||||
|
if (Array.isArray(userIds)) {
|
||||||
|
const logsDeleted = await this.apInboxLogsRepository.delete({
|
||||||
|
authUserId: In(userIds),
|
||||||
|
});
|
||||||
|
return logsDeleted.affected ?? 0;
|
||||||
|
} else {
|
||||||
|
const logsDeleted = await this.apInboxLogsRepository.delete({
|
||||||
|
authUserId: userIds,
|
||||||
|
});
|
||||||
|
return logsDeleted.affected ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all expired AP logs and garbage-collects the AP context cache.
|
* Deletes all expired AP logs and garbage-collects the AP context cache.
|
||||||
* Returns the total number of deleted rows.
|
* Returns the total number of deleted rows.
|
||||||
|
|
|
@ -571,7 +571,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
if (this.meta.enableStatsForFederatedInstances) {
|
if (this.meta.enableStatsForFederatedInstances) {
|
||||||
if (this.userEntityService.isRemoteUser(user)) {
|
if (this.userEntityService.isRemoteUser(user)) {
|
||||||
this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
|
this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
|
||||||
if (note.renote && note.text || !note.renote) {
|
if (!this.isRenote(note) || this.isQuote(note)) {
|
||||||
this.updateNotesCountQueue.enqueue(i.id, 1);
|
this.updateNotesCountQueue.enqueue(i.id, 1);
|
||||||
}
|
}
|
||||||
if (this.meta.enableChartsForFederatedInstances) {
|
if (this.meta.enableChartsForFederatedInstances) {
|
||||||
|
@ -583,17 +583,12 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// ハッシュタグ更新
|
// ハッシュタグ更新
|
||||||
if (data.visibility === 'public' || data.visibility === 'home') {
|
if (data.visibility === 'public' || data.visibility === 'home') {
|
||||||
if (user.isBot && this.meta.enableBotTrending) {
|
if (!user.isBot || this.meta.enableBotTrending) {
|
||||||
this.hashtagService.updateHashtags(user, tags);
|
|
||||||
} else if (!user.isBot) {
|
|
||||||
this.hashtagService.updateHashtags(user, tags);
|
this.hashtagService.updateHashtags(user, tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.renote && data.text) {
|
if (!this.isRenote(note) || this.isQuote(note)) {
|
||||||
// Increment notes count (user)
|
|
||||||
this.incNotesCountOfUser(user);
|
|
||||||
} else if (!data.renote) {
|
|
||||||
// Increment notes count (user)
|
// Increment notes count (user)
|
||||||
this.incNotesCountOfUser(user);
|
this.incNotesCountOfUser(user);
|
||||||
}
|
}
|
||||||
|
@ -631,7 +626,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.renote && data.text == null && data.renote.userId !== user.id && !user.isBot) {
|
if (this.isRenote(data) && !this.isQuote(data) && data.renote.userId !== user.id && !user.isBot) {
|
||||||
this.incRenoteCount(data.renote);
|
this.incRenoteCount(data.renote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,13 +701,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const muted = data.renote.userId && isUserRelated(note, await this.cacheService.userMutingsCache.fetch(data.renote.userId));
|
||||||
userIdsWhoMeMuting,
|
|
||||||
] = data.renote.userId ? await Promise.all([
|
|
||||||
this.cacheService.userMutingsCache.fetch(data.renote.userId),
|
|
||||||
]) : [new Set<string>()];
|
|
||||||
|
|
||||||
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
|
||||||
|
|
||||||
if (!isThreadMuted && !muted) {
|
if (!isThreadMuted && !muted) {
|
||||||
nm.push(data.renote.userId, type);
|
nm.push(data.renote.userId, type);
|
||||||
|
@ -848,13 +837,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [
|
const muted = u.id && isUserRelated(note, await this.cacheService.userMutingsCache.fetch(u.id));
|
||||||
userIdsWhoMeMuting,
|
|
||||||
] = u.id ? await Promise.all([
|
|
||||||
this.cacheService.userMutingsCache.fetch(u.id),
|
|
||||||
]) : [new Set<string>()];
|
|
||||||
|
|
||||||
const muted = isUserRelated(note, userIdsWhoMeMuting);
|
|
||||||
|
|
||||||
if (isThreadMuted || muted) {
|
if (isThreadMuted || muted) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
import type { Response } from 'node-fetch';
|
import type { Response } from 'node-fetch';
|
||||||
|
|
||||||
|
// TODO throw identifiable or unrecoverable errors
|
||||||
|
|
||||||
export function validateContentTypeSetAsActivityPub(response: Response): void {
|
export function validateContentTypeSetAsActivityPub(response: Response): void {
|
||||||
const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
|
const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
|
||||||
|
|
||||||
|
|
|
@ -322,6 +322,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
|
||||||
|
|
||||||
const host = this.utilityService.punyHost(uri);
|
const host = this.utilityService.punyHost(uri);
|
||||||
if (host === this.utilityService.toPuny(this.config.host)) {
|
if (host === this.utilityService.toPuny(this.config.host)) {
|
||||||
|
// TODO convert to unrecoverable error
|
||||||
throw new StatusError(`cannot resolve local user: ${uri}`, 400, 'cannot resolve local user');
|
throw new StatusError(`cannot resolve local user: ${uri}`, 400, 'cannot resolve local user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +571,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (!(err instanceof StatusError) || err.isRetryable) {
|
if (!(err instanceof StatusError) || err.isRetryable) {
|
||||||
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
|
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
|
||||||
// Do not update the visibiility on transient errors.
|
// Do not update the visibility on transient errors.
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return 'private';
|
return 'private';
|
||||||
|
|
|
@ -479,14 +479,6 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
mentions: note.mentions && note.mentions.length > 0 ? note.mentions : undefined,
|
mentions: note.mentions && note.mentions.length > 0 ? note.mentions : undefined,
|
||||||
uri: note.uri ?? undefined,
|
uri: note.uri ?? undefined,
|
||||||
url: note.url ?? undefined,
|
url: note.url ?? undefined,
|
||||||
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
|
||||||
...(meId && Object.keys(reactions).length > 0 ? {
|
|
||||||
myReaction: this.populateMyReaction({
|
|
||||||
id: note.id,
|
|
||||||
reactions: reactions,
|
|
||||||
reactionAndUserPairCache: reactionAndUserPairCache,
|
|
||||||
}, meId, options?._hint_),
|
|
||||||
} : {}),
|
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
clippedCount: note.clippedCount,
|
clippedCount: note.clippedCount,
|
||||||
|
@ -505,6 +497,16 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
|
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
|
||||||
_hint_: options?._hint_,
|
_hint_: options?._hint_,
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
|
|
||||||
|
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||||
|
|
||||||
|
...(meId && Object.keys(reactions).length > 0 ? {
|
||||||
|
myReaction: this.populateMyReaction({
|
||||||
|
id: note.id,
|
||||||
|
reactions: reactions,
|
||||||
|
reactionAndUserPairCache: reactionAndUserPairCache,
|
||||||
|
}, meId, options?._hint_),
|
||||||
|
} : {}),
|
||||||
} : {}),
|
} : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ const sideN = Math.floor(n / 2);
|
||||||
/**
|
/**
|
||||||
* Generate buffer of an identicon by seed
|
* Generate buffer of an identicon by seed
|
||||||
*/
|
*/
|
||||||
export async function genIdenticon(seed: string): Promise<Buffer> {
|
export function genIdenticon(seed: string): Buffer {
|
||||||
const rand = gen.create(seed);
|
const rand = gen.create(seed);
|
||||||
const canvas = createCanvas(size, size);
|
const canvas = createCanvas(size, size);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
@ -100,5 +100,5 @@ export async function genIdenticon(seed: string): Promise<Buffer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await canvas.toBuffer('image/png');
|
return canvas.toBuffer('image/png');
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ type MinimumUser = {
|
||||||
uri: MiUser['uri'];
|
uri: MiUser['uri'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MiScheduleNoteType={
|
export type MiScheduleNoteType = {
|
||||||
visibility: 'public' | 'home' | 'followers' | 'specified';
|
visibility: 'public' | 'home' | 'followers' | 'specified';
|
||||||
visibleUsers: MinimumUser[];
|
visibleUsers: MinimumUser[];
|
||||||
channel?: MiChannel['id'];
|
channel?: MiChannel['id'];
|
||||||
|
@ -37,7 +37,7 @@ export type MiScheduleNoteType={
|
||||||
apMentions?: MinimumUser[] | null;
|
apMentions?: MinimumUser[] | null;
|
||||||
apHashtags?: string[] | null;
|
apHashtags?: string[] | null;
|
||||||
apEmojis?: string[] | null;
|
apEmojis?: string[] | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
@Entity('note_schedule')
|
@Entity('note_schedule')
|
||||||
export class MiNoteSchedule {
|
export class MiNoteSchedule {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||||
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, noteVisibilities, defaultCWPriorities } from '@/types.js';
|
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, defaultCWPriorities } from '@/types.js';
|
||||||
import { id } from './util/id.js';
|
import { id } from './util/id.js';
|
||||||
import { MiUser } from './User.js';
|
import { MiUser } from './User.js';
|
||||||
import { MiPage } from './Page.js';
|
import { MiPage } from './Page.js';
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: marie and other Sharkey contributors
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const packedNoteEdit = {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: {
|
|
||||||
type: "string",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
format: "id",
|
|
||||||
example: "xxxxxxxxxx",
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: "string",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
format: "date-time",
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
type: "object",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
ref: "Note",
|
|
||||||
},
|
|
||||||
noteId: {
|
|
||||||
type: "string",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
format: "id",
|
|
||||||
},
|
|
||||||
oldText: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
newText: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
cw: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
fileIds: {
|
|
||||||
type: "array",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
items: {
|
|
||||||
type: "string",
|
|
||||||
format: "id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const;
|
|
|
@ -184,6 +184,11 @@ export class DeleteAccountProcessorService {
|
||||||
await this.apLogService.deleteObjectLogs(user.uri)
|
await this.apLogService.deleteObjectLogs(user.uri)
|
||||||
.catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`));
|
.catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.apLogService.deleteInboxLogs(user.id)
|
||||||
|
.catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`));
|
||||||
|
|
||||||
|
this.logger.succ('All AP logs deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Send email notification
|
{ // Send email notification
|
||||||
|
|
|
@ -25,8 +25,6 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
|
||||||
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
//import { CollapsedQueue } from '@/misc/collapsed-queue.js';
|
|
||||||
//import { MiNote } from '@/models/Note.js';
|
|
||||||
import { MiMeta } from '@/models/Meta.js';
|
import { MiMeta } from '@/models/Meta.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { SkApInboxLog } from '@/models/_.js';
|
import { SkApInboxLog } from '@/models/_.js';
|
||||||
|
@ -68,7 +66,6 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
||||||
private readonly updateInstanceQueue: UpdateInstanceQueue,
|
private readonly updateInstanceQueue: UpdateInstanceQueue,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('inbox');
|
this.logger = this.queueLoggerService.logger.createSubLogger('inbox');
|
||||||
//this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -28,7 +28,6 @@ import { ActivityPubServerService } from './ActivityPubServerService.js';
|
||||||
import { ApiLoggerService } from './api/ApiLoggerService.js';
|
import { ApiLoggerService } from './api/ApiLoggerService.js';
|
||||||
import { ApiServerService } from './api/ApiServerService.js';
|
import { ApiServerService } from './api/ApiServerService.js';
|
||||||
import { AuthenticateService } from './api/AuthenticateService.js';
|
import { AuthenticateService } from './api/AuthenticateService.js';
|
||||||
import { RateLimiterService } from './api/RateLimiterService.js';
|
|
||||||
import { SigninApiService } from './api/SigninApiService.js';
|
import { SigninApiService } from './api/SigninApiService.js';
|
||||||
import { SigninService } from './api/SigninService.js';
|
import { SigninService } from './api/SigninService.js';
|
||||||
import { SignupApiService } from './api/SignupApiService.js';
|
import { SignupApiService } from './api/SignupApiService.js';
|
||||||
|
@ -88,8 +87,6 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
||||||
ApiServerService,
|
ApiServerService,
|
||||||
AuthenticateService,
|
AuthenticateService,
|
||||||
SkRateLimiterService,
|
SkRateLimiterService,
|
||||||
// No longer used, but kept for backwards compatibility
|
|
||||||
RateLimiterService,
|
|
||||||
SigninApiService,
|
SigninApiService,
|
||||||
SigninWithPasskeyApiService,
|
SigninWithPasskeyApiService,
|
||||||
SigninService,
|
SigninService,
|
||||||
|
|
|
@ -229,12 +229,12 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => {
|
fastify.get<{ Params: { x: string } }>('/identicon/:x', (request, reply) => {
|
||||||
reply.header('Content-Type', 'image/png');
|
reply.header('Content-Type', 'image/png');
|
||||||
reply.header('Cache-Control', 'public, max-age=86400');
|
reply.header('Cache-Control', 'public, max-age=86400');
|
||||||
|
|
||||||
if (this.meta.enableIdenticonGeneration) {
|
if (this.meta.enableIdenticonGeneration) {
|
||||||
return await genIdenticon(request.params.x);
|
return genIdenticon(request.params.x);
|
||||||
} else {
|
} else {
|
||||||
return reply.redirect('/static-assets/avatar.png');
|
return reply.redirect('/static-assets/avatar.png');
|
||||||
}
|
}
|
||||||
|
@ -293,13 +293,14 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
if (fs.existsSync(this.config.socket)) {
|
if (fs.existsSync(this.config.socket)) {
|
||||||
fs.unlinkSync(this.config.socket);
|
fs.unlinkSync(this.config.socket);
|
||||||
}
|
}
|
||||||
fastify.listen({ path: this.config.socket }, (err, address) => {
|
|
||||||
if (this.config.chmodSocket) {
|
await fastify.listen({ path: this.config.socket });
|
||||||
fs.chmodSync(this.config.socket!, this.config.chmodSocket);
|
|
||||||
}
|
if (this.config.chmodSocket) {
|
||||||
});
|
fs.chmodSync(this.config.socket!, this.config.chmodSocket);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fastify.listen({ port: this.config.port, host: this.config.address });
|
await fastify.listen({ port: this.config.port, host: this.config.address });
|
||||||
}
|
}
|
||||||
|
|
||||||
await fastify.ready();
|
await fastify.ready();
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import Limiter from 'ratelimiter';
|
|
||||||
import * as Redis from 'ioredis';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type Logger from '@/logger.js';
|
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { LegacyRateLimit } from '@/misc/rate-limit-utils.js';
|
|
||||||
import type { IEndpointMeta } from './endpoints.js';
|
|
||||||
|
|
||||||
/** @deprecated Use SkRateLimiterService instead */
|
|
||||||
@Injectable()
|
|
||||||
export class RateLimiterService {
|
|
||||||
private logger: Logger;
|
|
||||||
private disabled = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.redis)
|
|
||||||
private redisClient: Redis.Redis,
|
|
||||||
|
|
||||||
private loggerService: LoggerService,
|
|
||||||
) {
|
|
||||||
this.logger = this.loggerService.getLogger('limiter');
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
this.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public limit(limitation: LegacyRateLimit & { key: NonNullable<string> }, actor: string, factor = 1) {
|
|
||||||
return new Promise<void>((ok, reject) => {
|
|
||||||
if (this.disabled) ok();
|
|
||||||
|
|
||||||
// Short-term limit
|
|
||||||
const minP = (): void => {
|
|
||||||
const minIntervalLimiter = new Limiter({
|
|
||||||
id: `${actor}:${limitation.key}:min`,
|
|
||||||
duration: limitation.minInterval! * factor,
|
|
||||||
max: 1,
|
|
||||||
db: this.redisClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
minIntervalLimiter.get((err, info) => {
|
|
||||||
if (err) {
|
|
||||||
return reject({ code: 'ERR', info });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
|
||||||
return reject({ code: 'BRIEF_REQUEST_INTERVAL', info });
|
|
||||||
} else {
|
|
||||||
if (hasLongTermLimit) {
|
|
||||||
return maxP();
|
|
||||||
} else {
|
|
||||||
return ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Long term limit
|
|
||||||
const maxP = (): void => {
|
|
||||||
const limiter = new Limiter({
|
|
||||||
id: `${actor}:${limitation.key}`,
|
|
||||||
duration: limitation.duration! * factor,
|
|
||||||
max: limitation.max! / factor,
|
|
||||||
db: this.redisClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
limiter.get((err, info) => {
|
|
||||||
if (err) {
|
|
||||||
return reject({ code: 'ERR', info });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
|
||||||
return reject({ code: 'RATE_LIMIT_EXCEEDED', info });
|
|
||||||
} else {
|
|
||||||
return ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasShortTermLimit = typeof limitation.minInterval === 'number';
|
|
||||||
|
|
||||||
const hasLongTermLimit =
|
|
||||||
typeof limitation.duration === 'number' &&
|
|
||||||
typeof limitation.max === 'number';
|
|
||||||
|
|
||||||
if (hasShortTermLimit) {
|
|
||||||
minP();
|
|
||||||
} else if (hasLongTermLimit) {
|
|
||||||
maxP();
|
|
||||||
} else {
|
|
||||||
ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,8 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
// Up to 10 attempts, then 1 per minute
|
// Up to 10 attempts, then 1 per minute
|
||||||
const signinRateLimit: Keyed<RateLimit> = {
|
const signinRateLimit: Keyed<RateLimit> = {
|
||||||
key: 'signin',
|
key: 'signin',
|
||||||
max: 10,
|
type: 'bucket',
|
||||||
|
size: 10,
|
||||||
dripRate: 1000 * 60,
|
dripRate: 1000 * 60,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ export class SigninApiService {
|
||||||
|
|
||||||
if (isSystemAccount(user)) {
|
if (isSystemAccount(user)) {
|
||||||
return error(403, {
|
return error(403, {
|
||||||
id: 's8dhsj9s-a93j-493j-ja9k-kas9sj20aml2',
|
id: 'ba4ba3bc-ef1e-4c74-ad88-1d2b7d69a100',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +244,7 @@ export class SigninApiService {
|
||||||
if (profile.password!.startsWith('$2')) {
|
if (profile.password!.startsWith('$2')) {
|
||||||
const newHash = await argon2.hash(password);
|
const newHash = await argon2.hash(password);
|
||||||
this.userProfilesRepository.update(user.id, {
|
this.userProfilesRepository.update(user.id, {
|
||||||
password: newHash
|
password: newHash,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
|
if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
|
||||||
|
@ -267,7 +268,7 @@ export class SigninApiService {
|
||||||
if (profile.password!.startsWith('$2')) {
|
if (profile.password!.startsWith('$2')) {
|
||||||
const newHash = await argon2.hash(password);
|
const newHash = await argon2.hash(password);
|
||||||
this.userProfilesRepository.update(user.id, {
|
this.userProfilesRepository.update(user.id, {
|
||||||
password: newHash
|
password: newHash,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -205,7 +204,6 @@ export class SignupApiService {
|
||||||
const code = secureRndstr(16, { chars: L_CHARS });
|
const code = secureRndstr(16, { chars: L_CHARS });
|
||||||
|
|
||||||
// Generate hash of password
|
// Generate hash of password
|
||||||
//const salt = await bcrypt.genSalt(8);
|
|
||||||
const hash = await argon2.hash(password);
|
const hash = await argon2.hash(password);
|
||||||
|
|
||||||
const pendingUser = await this.userPendingsRepository.insertOne({
|
const pendingUser = await this.userPendingsRepository.insertOne({
|
||||||
|
|
|
@ -124,9 +124,11 @@ export class StreamingApiServerService {
|
||||||
const requestIp = proxyAddr(request, () => true );
|
const requestIp = proxyAddr(request, () => true );
|
||||||
const limitActor = user?.id ?? getIpHash(requestIp);
|
const limitActor = user?.id ?? getIpHash(requestIp);
|
||||||
if (await this.rateLimitThis(limitActor, {
|
if (await this.rateLimitThis(limitActor, {
|
||||||
|
// Up to 32 connections, then 1 every 10 seconds
|
||||||
|
type: 'bucket',
|
||||||
key: 'wsconnect',
|
key: 'wsconnect',
|
||||||
duration: ms('5min'),
|
size: 32,
|
||||||
max: 32,
|
dripRate: 10 * 1000,
|
||||||
})) {
|
})) {
|
||||||
socket.write('HTTP/1.1 429 Rate Limit Exceeded\r\n\r\n');
|
socket.write('HTTP/1.1 429 Rate Limit Exceeded\r\n\r\n');
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private readonly driveFilesRepository: DriveFilesRepository,
|
private readonly driveFilesRepository: DriveFilesRepository,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const file = await driveFilesRepository.findOneByOrFail({ id: ps.fileId });
|
const file = await this.driveFilesRepository.findOneByOrFail({ id: ps.fileId });
|
||||||
await this.moderationLogService.log(me, 'importCustomEmojis', {
|
await this.moderationLogService.log(me, 'importCustomEmojis', {
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const keys = await generateVAPIDKeys();
|
const keys = await generateVAPIDKeys();
|
||||||
|
|
||||||
|
// TODO add moderation log
|
||||||
|
|
||||||
return { public: keys.publicKey, private: keys.privateKey };
|
return { public: keys.publicKey, private: keys.privateKey };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||||
|
|
|
@ -404,14 +404,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
set.turnstileSecretKey = ps.turnstileSecretKey;
|
set.turnstileSecretKey = ps.turnstileSecretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.enableFC !== undefined) {
|
|
||||||
set.enableFC = ps.enableFC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.enableTestcaptcha !== undefined) {
|
if (ps.enableTestcaptcha !== undefined) {
|
||||||
set.enableTestcaptcha = ps.enableTestcaptcha;
|
set.enableTestcaptcha = ps.enableTestcaptcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.enableFC !== undefined) {
|
||||||
|
set.enableFC = ps.enableFC;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.fcSiteKey !== undefined) {
|
if (ps.fcSiteKey !== undefined) {
|
||||||
set.fcSiteKey = ps.fcSiteKey;
|
set.fcSiteKey = ps.fcSiteKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ export const meta = {
|
||||||
|
|
||||||
// Up to 30 calls, then 1 per 1/2 second
|
// Up to 30 calls, then 1 per 1/2 second
|
||||||
limit: {
|
limit: {
|
||||||
max: 30,
|
type: 'bucket',
|
||||||
|
size: 30,
|
||||||
dripRate: 500,
|
dripRate: 500,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ export const meta = {
|
||||||
// Up to 10 calls, then 4 / second.
|
// Up to 10 calls, then 4 / second.
|
||||||
// This allows for reliable automation.
|
// This allows for reliable automation.
|
||||||
limit: {
|
limit: {
|
||||||
max: 10,
|
type: 'bucket',
|
||||||
|
size: 10,
|
||||||
dripRate: 250,
|
dripRate: 250,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -34,7 +34,8 @@ export const meta = {
|
||||||
// up to 20 calls, then 1 per second.
|
// up to 20 calls, then 1 per second.
|
||||||
// This handles bursty traffic when all tabs reload as a group
|
// This handles bursty traffic when all tabs reload as a group
|
||||||
limit: {
|
limit: {
|
||||||
max: 20,
|
type: 'bucket',
|
||||||
|
size: 20,
|
||||||
dripSize: 1,
|
dripSize: 1,
|
||||||
dripRate: 1000,
|
dripRate: 1000,
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import * as OTPAuth from 'otpauth';
|
import * as OTPAuth from 'otpauth';
|
||||||
import * as QRCode from 'qrcode';
|
import * as QRCode from 'qrcode';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
@ -65,7 +64,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate hash of password
|
// Generate hash of password
|
||||||
//const salt = await bcrypt.genSalt(8);
|
|
||||||
const hash = await argon2.hash(ps.newPassword);
|
const hash = await argon2.hash(ps.newPassword);
|
||||||
|
|
||||||
await this.userProfilesRepository.update(me.id, {
|
await this.userProfilesRepository.update(me.id, {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
|
||||||
|
|
|
@ -76,11 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.gtlDisabled);
|
throw new ApiError(meta.errors.gtlDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
const followings = me ? await this.cacheService.userFollowingsCache.fetch(me.id) : {};
|
||||||
followings,
|
|
||||||
] = me ? await Promise.all([
|
|
||||||
this.cacheService.userFollowingsCache.fetch(me.id),
|
|
||||||
]) : [undefined];
|
|
||||||
|
|
||||||
//#region Construct query
|
//#region Construct query
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||||
|
|
|
@ -100,11 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
|
if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||||
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
|
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||||
|
|
||||||
const [
|
const followings = me ? await this.cacheService.userFollowingsCache.fetch(me.id) : {};
|
||||||
followings,
|
|
||||||
] = me ? await Promise.all([
|
|
||||||
this.cacheService.userFollowingsCache.fetch(me.id),
|
|
||||||
]) : [undefined];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ps.tag) {
|
if (ps.tag) {
|
||||||
|
|
|
@ -66,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
renoteId: note.id,
|
renoteId: note.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO inline this into the above query
|
||||||
for (const note of renotes) {
|
for (const note of renotes) {
|
||||||
if (ps.quote) {
|
if (ps.quote) {
|
||||||
if (note.text) this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
|
if (note.text) this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//import bcrypt from 'bcryptjs';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js';
|
import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js';
|
||||||
|
@ -60,7 +59,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate hash of password
|
// Generate hash of password
|
||||||
//const salt = await bcrypt.genSalt(8);
|
|
||||||
const hash = await argon2.hash(ps.password);
|
const hash = await argon2.hash(ps.password);
|
||||||
|
|
||||||
await this.userProfilesRepository.update(req.userId, {
|
await this.userProfilesRepository.update(req.userId, {
|
||||||
|
|
|
@ -66,7 +66,8 @@ export const meta = {
|
||||||
|
|
||||||
// 24 calls, then 7 per second-ish (1 for each type of server info graph)
|
// 24 calls, then 7 per second-ish (1 for each type of server info graph)
|
||||||
limit: {
|
limit: {
|
||||||
max: 24,
|
type: 'bucket',
|
||||||
|
size: 24,
|
||||||
dripSize: 7,
|
dripSize: 7,
|
||||||
dripRate: 900,
|
dripRate: 900,
|
||||||
},
|
},
|
||||||
|
|
|
@ -59,7 +59,8 @@ export const meta = {
|
||||||
|
|
||||||
// up to 50 calls @ 4 per second
|
// up to 50 calls @ 4 per second
|
||||||
limit: {
|
limit: {
|
||||||
max: 50,
|
type: 'bucket',
|
||||||
|
size: 50,
|
||||||
dripRate: 250,
|
dripRate: 250,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -23,7 +23,6 @@ import type Channel from './channel.js';
|
||||||
|
|
||||||
const MAX_CHANNELS_PER_CONNECTION = 32;
|
const MAX_CHANNELS_PER_CONNECTION = 32;
|
||||||
const MAX_SUBSCRIPTIONS_PER_CONNECTION = 512;
|
const MAX_SUBSCRIPTIONS_PER_CONNECTION = 512;
|
||||||
const MAX_CACHED_NOTES_PER_CONNECTION = 64;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main stream connection
|
* Main stream connection
|
||||||
|
|
|
@ -6,9 +6,6 @@
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
/* import { kinds } from '@/misc/api-permissions.js';
|
|
||||||
import type { Config } from '@/config.js';
|
|
||||||
import { DI } from '@/di-symbols.js'; */
|
|
||||||
import multer from 'fastify-multer';
|
import multer from 'fastify-multer';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
|
|
@ -427,7 +427,7 @@ export class ClientServerService {
|
||||||
fastify.get('/robots.txt', async (request, reply) => {
|
fastify.get('/robots.txt', async (request, reply) => {
|
||||||
if (this.meta.robotsTxt) {
|
if (this.meta.robotsTxt) {
|
||||||
reply.header('Content-Type', 'text/plain');
|
reply.header('Content-Type', 'text/plain');
|
||||||
return await reply.send(this.meta.robotsTxt);
|
return reply.send(this.meta.robotsTxt);
|
||||||
} else {
|
} else {
|
||||||
return await reply.sendFile('/robots.txt', staticAssets);
|
return await reply.sendFile('/robots.txt', staticAssets);
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,6 @@ const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value
|
||||||
const isLong = shouldCollapsed(appearNote.value, []);
|
const isLong = shouldCollapsed(appearNote.value, []);
|
||||||
const collapsed = ref(appearNote.value.cw == null && isLong);
|
const collapsed = ref(appearNote.value.cw == null && isLong);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,6 @@ const isDeleted = ref(false);
|
||||||
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
|
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
|
||||||
const isLong = shouldCollapsed(appearNote.value, []);
|
const isLong = shouldCollapsed(appearNote.value, []);
|
||||||
const collapsed = ref(appearNote.value.cw == null && isLong);
|
const collapsed = ref(appearNote.value.cw == null && isLong);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(props.note));
|
const mergedCW = computed(() => computeMergedCw(props.note));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(props.note));
|
const mergedCW = computed(() => computeMergedCw(props.note));
|
||||||
|
|
||||||
if (props.detail) {
|
if (props.detail) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { computed, defineAsyncComponent, shallowRef } from 'vue';
|
import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
|
||||||
import type { ComponentExposed } from 'vue-component-type-helpers';
|
import type { ComponentExposed } from 'vue-component-type-helpers';
|
||||||
import type MkNote from '@/components/MkNote.vue';
|
import type MkNote from '@/components/MkNote.vue';
|
||||||
import type SkNote from '@/components/SkNote.vue';
|
import type SkNote from '@/components/SkNote.vue';
|
||||||
|
@ -31,7 +31,7 @@ const XNote = computed(() =>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootEl = shallowRef<ComponentExposed<typeof MkNote | typeof SkNote>>();
|
const rootEl = useTemplateRef<ComponentExposed<typeof MkNote | typeof SkNote>>('rootEl');
|
||||||
|
|
||||||
defineExpose({ rootEl });
|
defineExpose({ rootEl });
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { computed, defineAsyncComponent, shallowRef } from 'vue';
|
import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
|
||||||
import type { ComponentExposed } from 'vue-component-type-helpers';
|
import type { ComponentExposed } from 'vue-component-type-helpers';
|
||||||
import type MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
import type MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
||||||
import type SkNoteDetailed from '@/components/SkNoteDetailed.vue';
|
import type SkNoteDetailed from '@/components/SkNoteDetailed.vue';
|
||||||
|
@ -28,7 +28,7 @@ const XNoteDetailed = computed(() =>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootEl = shallowRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>();
|
const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>('rootEl');
|
||||||
|
|
||||||
defineExpose({ rootEl });
|
defineExpose({ rootEl });
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { computed, defineAsyncComponent, shallowRef } from 'vue';
|
import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
|
||||||
import type { ComponentExposed } from 'vue-component-type-helpers';
|
import type { ComponentExposed } from 'vue-component-type-helpers';
|
||||||
import type MkNoteSimple from '@/components/MkNoteSimple.vue';
|
import type MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
import type SkNoteSimple from '@/components/SkNoteSimple.vue';
|
import type SkNoteSimple from '@/components/SkNoteSimple.vue';
|
||||||
|
@ -29,7 +29,7 @@ const XNoteSimple = computed(() =>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootEl = shallowRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>();
|
const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>('rootEl');
|
||||||
|
|
||||||
defineExpose({ rootEl });
|
defineExpose({ rootEl });
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ const src = computed(() => {
|
||||||
case 'fc': return 'https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.18/widget.min.js';
|
case 'fc': return 'https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.18/widget.min.js';
|
||||||
case 'mcaptcha': return null;
|
case 'mcaptcha': return null;
|
||||||
case 'testcaptcha': return null;
|
case 'testcaptcha': return null;
|
||||||
|
default: return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,9 @@ function waitForDecode() {
|
||||||
.then(() => img.value?.decode())
|
.then(() => img.value?.decode())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loaded.value = true;
|
loaded.value = true;
|
||||||
});
|
})
|
||||||
|
// Ignore decoding errors
|
||||||
|
.catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
loaded.value = false;
|
loaded.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -805,8 +805,8 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
||||||
os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
|
os.contextMenu(menu, ev).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,8 +815,8 @@ function showMenu(): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function menuVersions(): Promise<void> {
|
async function menuVersions(): Promise<void> {
|
||||||
|
|
|
@ -749,14 +749,14 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
||||||
os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
|
os.contextMenu(menu, ev).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu(): void {
|
function showMenu(): void {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function menuVersions(): Promise<void> {
|
async function menuVersions(): Promise<void> {
|
||||||
|
|
|
@ -54,11 +54,9 @@ const props = defineProps<{
|
||||||
|
|
||||||
const menuVersionsButton = shallowRef<HTMLElement>();
|
const menuVersionsButton = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
async function menuVersions(viaKeyboard = false): Promise<void> {
|
async function menuVersions(): Promise<void> {
|
||||||
const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuVersionsButton });
|
const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuButton: menuVersionsButton });
|
||||||
popupMenu(menu, menuVersionsButton.value, {
|
popupMenu(menu, menuVersionsButton.value).then(focus).finally(cleanup);
|
||||||
viaKeyboard,
|
|
||||||
}).then(focus).finally(cleanup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = inject(DI.mock, false);
|
const mock = inject(DI.mock, false);
|
||||||
|
|
|
@ -46,7 +46,7 @@ const props = defineProps<{
|
||||||
hideFiles?: boolean;
|
hideFiles?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let showContent = ref(prefer.s.uncollapseCW);
|
const showContent = ref(prefer.s.uncollapseCW);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(props.note));
|
const mergedCW = computed(() => computeMergedCw(props.note));
|
||||||
|
|
|
@ -129,7 +129,7 @@ const props = withDefaults(defineProps<{
|
||||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
||||||
|
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
|
const muted = computed(() => $i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
|
||||||
const translation = ref<any>(null);
|
const translation = ref<any>(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
|
@ -142,7 +142,7 @@ const likeButton = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const renoteTooltip = computeRenoteTooltip(renoted);
|
const renoteTooltip = computeRenoteTooltip(renoted);
|
||||||
|
|
||||||
let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
const appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
||||||
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
|
|
||||||
|
@ -377,8 +377,8 @@ function quote() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(): void {
|
function menu(): void {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.detail) {
|
if (props.detail) {
|
||||||
|
|
|
@ -219,9 +219,22 @@ const props = withDefaults(defineProps<{
|
||||||
full: false,
|
full: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const userDetailed: Ref<UserDetailed | null> = ref(null);
|
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
|
||||||
|
|
||||||
|
const exportEntityName = {
|
||||||
|
antenna: i18n.ts.antennas,
|
||||||
|
blocking: i18n.ts.blockedUsers,
|
||||||
|
clip: i18n.ts.clips,
|
||||||
|
customEmoji: i18n.ts.customEmojis,
|
||||||
|
favorite: i18n.ts.favorites,
|
||||||
|
following: i18n.ts.following,
|
||||||
|
muting: i18n.ts.mutedUsers,
|
||||||
|
note: i18n.ts.notes,
|
||||||
|
userList: i18n.ts.lists,
|
||||||
|
} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
|
||||||
|
|
||||||
const followRequestDone = ref(true);
|
const followRequestDone = ref(true);
|
||||||
|
const userDetailed: Ref<UserDetailed | null> = ref(null);
|
||||||
|
|
||||||
// watch() is required because computed() doesn't support async.
|
// watch() is required because computed() doesn't support async.
|
||||||
watch(props, async () => {
|
watch(props, async () => {
|
||||||
|
@ -241,20 +254,6 @@ watch(props, async () => {
|
||||||
}
|
}
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
|
|
||||||
|
|
||||||
const exportEntityName = {
|
|
||||||
antenna: i18n.ts.antennas,
|
|
||||||
blocking: i18n.ts.blockedUsers,
|
|
||||||
clip: i18n.ts.clips,
|
|
||||||
customEmoji: i18n.ts.customEmojis,
|
|
||||||
favorite: i18n.ts.favorites,
|
|
||||||
following: i18n.ts.following,
|
|
||||||
muting: i18n.ts.mutedUsers,
|
|
||||||
note: i18n.ts.notes,
|
|
||||||
userList: i18n.ts.lists,
|
|
||||||
} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
|
|
||||||
|
|
||||||
const acceptFollowRequest = () => {
|
const acceptFollowRequest = () => {
|
||||||
if (!('user' in props.notification)) return;
|
if (!('user' in props.notification)) return;
|
||||||
followRequestDone.value = true;
|
followRequestDone.value = true;
|
||||||
|
|
|
@ -62,7 +62,6 @@ onUnmounted(() => {
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
//background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
|
|
||||||
|
|
||||||
> .fadeLabel {
|
> .fadeLabel {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -110,7 +110,6 @@ watch(() => props.expandAllCws, (expandAllCws) => {
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
// background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
|
|
||||||
|
|
||||||
> .fadeLabel {
|
> .fadeLabel {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -805,8 +805,8 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
||||||
os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
|
os.contextMenu(menu, ev).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,8 +815,8 @@ function showMenu(): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function menuVersions(): Promise<void> {
|
async function menuVersions(): Promise<void> {
|
||||||
|
|
|
@ -755,14 +755,14 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
react();
|
react();
|
||||||
} else {
|
} else {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
||||||
os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
|
os.contextMenu(menu, ev).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu(): void {
|
function showMenu(): void {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function menuVersions(): Promise<void> {
|
async function menuVersions(): Promise<void> {
|
||||||
|
|
|
@ -391,8 +391,8 @@ function quote() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(): void {
|
function menu(): void {
|
||||||
const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
||||||
os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.detail) {
|
if (props.detail) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent, computed } from 'vue';
|
||||||
import * as mfm from '@transfem-org/sfm-js';
|
import * as mfm from '@transfem-org/sfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
||||||
|
@ -26,7 +26,10 @@ const props = defineProps<{
|
||||||
page: Misskey.entities.Page,
|
page: Misskey.entities.Page,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const urls = props.block.text ? extractUrlFromMfm(mfm.parse(props.block.text)) : [];
|
const urls = computed(() => {
|
||||||
|
if (!props.block.text) return [];
|
||||||
|
return extractUrlFromMfm(mfm.parse(props.block.text));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<FormSplit style="margin-top: var(--MI-margin);">
|
<FormSplit style="margin-top: var(--MI-margin);">
|
||||||
|
<!-- TODO translate -->
|
||||||
<MkSelect v-model="state">
|
<MkSelect v-model="state">
|
||||||
<template #label>{{ i18n.ts.state }}</template>
|
<template #label>{{ i18n.ts.state }}</template>
|
||||||
<option value="all">{{ i18n.ts.all }}</option>
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
|
@ -61,7 +62,7 @@ import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/i';
|
||||||
|
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const state = ref('federating');
|
const state = ref('federating');
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||||
<div style="overflow: clip;">
|
<div style="overflow: clip;">
|
||||||
<img :src="instance.sidebarLogoUrl ?? instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
<img :src="instance.sidebarLogoUrl ?? instance.iconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
||||||
<div :class="$style.bannerName">
|
<div :class="$style.bannerName">
|
||||||
<b>{{ instance.name ?? host }}</b>
|
<b>{{ instance.name ?? host }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkKeyValue :copy="version">
|
<MkKeyValue :copy="version">
|
||||||
|
<!-- TODO translate -->
|
||||||
<template #key>Sharkey</template>
|
<template #key>Sharkey</template>
|
||||||
<template #value>{{ version }}</template>
|
<template #value>{{ version }}</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
@ -101,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSuspense v-slot="{ result: stats }" :p="initStats">
|
<FormSuspense v-slot="{ result: stats }" :p="initStats">
|
||||||
<FormSection>
|
<FormSection v-if="stats">
|
||||||
<template #label>{{ i18n.ts.statistics }}</template>
|
<template #label>{{ i18n.ts.statistics }}</template>
|
||||||
<FormSplit>
|
<FormSplit>
|
||||||
<MkKeyValue>
|
<MkKeyValue>
|
||||||
|
@ -116,12 +117,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
|
|
||||||
<FormSection v-if="sponsors[0].length > 0">
|
<FormSection v-if="sponsors.length > 0">
|
||||||
<template #label>Our lovely Sponsors</template>
|
<template #label>Our lovely Sponsors</template>
|
||||||
<div :class="$style.contributors">
|
<div :class="$style.contributors">
|
||||||
<span
|
<span
|
||||||
v-for="sponsor in sponsors[0]"
|
v-for="(sponsor, i) of sponsors"
|
||||||
:key="sponsor"
|
:key="i"
|
||||||
style="margin-bottom: 0.5rem;"
|
style="margin-bottom: 0.5rem;"
|
||||||
>
|
>
|
||||||
<a :href="sponsor.website || sponsor.profile" target="_blank" :class="$style.contributor">
|
<a :href="sponsor.website || sponsor.profile" target="_blank" :class="$style.contributor">
|
||||||
|
@ -147,7 +148,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import sanitizeHtml from '@/utility/sanitize-html.js';
|
|
||||||
import { host, version } from '@@/js/config.js';
|
import { host, version } from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
@ -160,11 +160,12 @@ import FormSuspense from '@/components/form/suspense.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkLink from '@/components/MkLink.vue';
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
import sanitizeHtml from '@/utility/sanitize-html.js';
|
||||||
|
|
||||||
const sponsors = ref([]);
|
const sponsors = ref<{ name: string, image: string | null, website: string | null, profile: string }[]>([]);
|
||||||
|
|
||||||
const initStats = () => misskeyApi('stats', {});
|
const initStats = () => misskeyApi('stats', {});
|
||||||
await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.push(res.sponsor_data));
|
await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value = res.sponsor_data);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -108,6 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-password"></i></template>
|
<template #icon><i class="ti ti-password"></i></template>
|
||||||
<template #label>IP</template>
|
<template #label>IP</template>
|
||||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||||
|
<!-- TODO translate -->
|
||||||
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
||||||
<template v-if="iAmAdmin && ips">
|
<template v-if="iAmAdmin && ips">
|
||||||
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
||||||
|
|
|
@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
||||||
|
|
||||||
|
<!-- TODO translate -->
|
||||||
<MkFolder v-if="bubbleTimelineEnabled">
|
<MkFolder v-if="bubbleTimelineEnabled">
|
||||||
<template #icon><i class="ph-drop ph-bold ph-lg"></i></template>
|
<template #icon><i class="ph-drop ph-bold ph-lg"></i></template>
|
||||||
<template #label>Bubble timeline</template>
|
<template #label>Bubble timeline</template>
|
||||||
|
|
|
@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<!-- TODO translate -->
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.btlAvailable, 'btlAvailable'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.btlAvailable, 'btlAvailable'])">
|
||||||
<template #label>{{ i18n.ts._role._options.btlAvailable }}</template>
|
<template #label>{{ i18n.ts._role._options.btlAvailable }}</template>
|
||||||
<template #suffix>{{ policies.btlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.btlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
|
|
@ -26,11 +26,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import DynamicNote from '@/components/DynamicNote.vue';
|
||||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
import DynamicNote from '@/components/DynamicNote.vue';
|
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'i/favorites' as const,
|
endpoint: 'i/favorites' as const,
|
||||||
|
|
|
@ -52,6 +52,7 @@ import { computed, watch, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
|
import DynamicNoteDetailed from '@/components/DynamicNoteDetailed.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -66,7 +67,6 @@ import { pleaseLogin } from '@/utility/please-login.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import DynamicNoteDetailed from '@/components/DynamicNoteDetailed.vue';
|
|
||||||
|
|
||||||
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
||||||
|
|
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, shallowRef, watch } from 'vue';
|
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||||
import type { StyleValue } from 'vue';
|
import type { StyleValue } from 'vue';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
@ -61,10 +61,9 @@ import { i18n } from '@/i18n.js';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
|
|
||||||
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
const paginationComponent = useTemplateRef<InstanceType<typeof MkPagination>>('paginationComponent');
|
||||||
|
|
||||||
const sortMode = ref('+size');
|
const sortMode = ref('+size');
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
|
|
@ -861,7 +861,6 @@ import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import Search from '@/pages/search.vue';
|
|
||||||
|
|
||||||
// Sharkey imports
|
// Sharkey imports
|
||||||
import { searchEngineMap } from '@/utility/search-engine-map.js';
|
import { searchEngineMap } from '@/utility/search-engine-map.js';
|
||||||
|
|
|
@ -187,7 +187,6 @@ import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import { globalEvents } from '@/events.js';
|
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.formContainer">
|
<div :class="$style.formContainer">
|
||||||
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
|
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
|
||||||
<div :class="$style.title">
|
<div :class="$style.title">
|
||||||
|
<!-- TODO translate -->
|
||||||
<div>Welcome to Sharkey!</div>
|
<div>Welcome to Sharkey!</div>
|
||||||
<div :class="$style.version">v{{ version }}</div>
|
<div :class="$style.version">v{{ version }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,7 @@ export interface PostFormProps {
|
||||||
initialFiles?: Misskey.entities.DriveFile[];
|
initialFiles?: Misskey.entities.DriveFile[];
|
||||||
initialLocalOnly?: boolean;
|
initialLocalOnly?: boolean;
|
||||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
||||||
|
/* TODO inline this into the entity */
|
||||||
initialNote?: Misskey.entities.Note & {
|
initialNote?: Misskey.entities.Note & {
|
||||||
isSchedule?: boolean,
|
isSchedule?: boolean,
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,11 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:enterFromClass="$style.transition_notification_enterFrom"
|
:enterFromClass="$style.transition_notification_enterFrom"
|
||||||
:leaveToClass="$style.transition_notification_leaveTo"
|
:leaveToClass="$style.transition_notification_leaveTo"
|
||||||
>
|
>
|
||||||
<div
|
<div v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{ pointerEvents: getPointerEvents() }">
|
||||||
v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{
|
|
||||||
pointerEvents: getPointerEvents()
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<XNotification :notification="notification"/>
|
<XNotification :notification="notification"/>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
|
@ -67,10 +63,9 @@ import { prefer } from '@/preferences.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
|
|
||||||
const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
|
|
||||||
|
|
||||||
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
|
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
|
||||||
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
|
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
|
||||||
|
const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
|
||||||
|
|
||||||
const dev = _DEV_;
|
const dev = _DEV_;
|
||||||
|
|
||||||
|
@ -99,7 +94,6 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
|
||||||
if ($i) {
|
if ($i) {
|
||||||
const connection = useStream().useChannel('main', null, 'UI');
|
const connection = useStream().useChannel('main', null, 'UI');
|
||||||
connection.on('notification', onNotification);
|
connection.on('notification', onNotification);
|
||||||
|
|
||||||
globalEvents.on('clientNotification', notification => onNotification(notification, true));
|
globalEvents.on('clientNotification', notification => onNotification(notification, true));
|
||||||
|
|
||||||
//#region Listen message from SW
|
//#region Listen message from SW
|
||||||
|
@ -132,8 +126,8 @@ function getPointerEvents() {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 3900000;
|
z-index: 3900000;
|
||||||
padding: 0 var(--MI-margin);
|
padding: 0 var(--MI-margin);
|
||||||
display: flex;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
&.notificationsPosition_leftTop {
|
&.notificationsPosition_leftTop {
|
||||||
top: var(--MI-margin);
|
top: var(--MI-margin);
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.top">
|
<div :class="$style.top">
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
|
||||||
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
|
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
|
||||||
<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
|
<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.middle">
|
<div :class="$style.middle">
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { onUnmounted } from 'vue';
|
import { onUnmounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import type { Ref, ShallowRef } from 'vue';
|
import type { Ref, ShallowRef } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike | undefined } | Map<string, JsonLike>;
|
type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike } | Map<string, JsonLike>;
|
||||||
|
|
||||||
export function deepEqual(a: JsonLike, b: JsonLike): boolean {
|
export function deepEqual(a: JsonLike, b: JsonLike): boolean {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
|
|
|
@ -14,20 +14,18 @@ class FavIconDot {
|
||||||
private hasLoaded: Promise<void> | undefined;
|
private hasLoaded: Promise<void> | undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = window.document.createElement('canvas');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called before calling any other functions
|
* Must be called before calling any other functions
|
||||||
*/
|
*/
|
||||||
public async setup() {
|
public async setup() {
|
||||||
const element: HTMLLinkElement = await this.getOrMakeFaviconElement();
|
this.faviconEL = await this.getOrMakeFaviconElement();
|
||||||
|
|
||||||
this.faviconEL = element;
|
|
||||||
this.src = this.faviconEL.getAttribute('href');
|
this.src = this.faviconEL.getAttribute('href');
|
||||||
this.ctx = this.canvas.getContext('2d');
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
|
||||||
this.faviconImage = document.createElement('img');
|
this.faviconImage = window.document.createElement('img');
|
||||||
this.faviconImage.crossOrigin = 'anonymous';
|
this.faviconImage.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
this.hasLoaded = new Promise((resolve, reject) => {
|
this.hasLoaded = new Promise((resolve, reject) => {
|
||||||
|
@ -46,7 +44,7 @@ class FavIconDot {
|
||||||
|
|
||||||
private async getOrMakeFaviconElement(): Promise<HTMLLinkElement> {
|
private async getOrMakeFaviconElement(): Promise<HTMLLinkElement> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const favicon = (document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement;
|
const favicon = (window.document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement;
|
||||||
favicon.addEventListener('load', () => {
|
favicon.addEventListener('load', () => {
|
||||||
resolve(favicon);
|
resolve(favicon);
|
||||||
});
|
});
|
||||||
|
@ -59,12 +57,12 @@ class FavIconDot {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFaviconElem() {
|
private createFaviconElem() {
|
||||||
const newLink = document.createElement('link');
|
const newLink = window.document.createElement('link');
|
||||||
newLink.setAttribute('rel', 'icon');
|
newLink.setAttribute('rel', 'icon');
|
||||||
newLink.setAttribute('href', '/favicon.ico');
|
newLink.setAttribute('href', '/favicon.ico');
|
||||||
newLink.setAttribute('type', 'image/x-icon');
|
newLink.setAttribute('type', 'image/x-icon');
|
||||||
|
|
||||||
document.head.appendChild(newLink);
|
window.document.head.appendChild(newLink);
|
||||||
return newLink;
|
return newLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +77,7 @@ class FavIconDot {
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
const radius = Math.min(this.faviconImage.width, this.faviconImage.height) * 0.2;
|
const radius = Math.min(this.faviconImage.width, this.faviconImage.height) * 0.2;
|
||||||
this.ctx.arc(this.faviconImage.width - radius, radius, radius, 0, 2 * Math.PI);
|
this.ctx.arc(this.faviconImage.width - radius, radius, radius, 0, 2 * Math.PI);
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
const computedStyle = getComputedStyle(window.document.documentElement);
|
||||||
this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString();
|
this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString();
|
||||||
this.ctx.strokeStyle = 'white';
|
this.ctx.strokeStyle = 'white';
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
|
@ -111,7 +109,7 @@ class FavIconDot {
|
||||||
public async worksOnInstance() {
|
public async worksOnInstance() {
|
||||||
try {
|
try {
|
||||||
await this.setVisible(true);
|
await this.setVisible(true);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => window.setTimeout(resolve, 1000));
|
||||||
await this.setVisible(false);
|
await this.setVisible(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error setting notification dot', error);
|
console.error('error setting notification dot', error);
|
||||||
|
@ -138,7 +136,7 @@ export async function setFavIconDot(visible: boolean) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// If document is already loaded, set visibility immediately
|
// If document is already loaded, set visibility immediately
|
||||||
if (document.readyState === 'complete') {
|
if (window.document.readyState === 'complete') {
|
||||||
await setIconVisibility();
|
await setIconVisibility();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, set visibility when window loads
|
// Otherwise, set visibility when window loads
|
||||||
|
|
|
@ -541,7 +541,7 @@ export function getNoteMenu(props: {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
popupMenu: menuItems,
|
menu: menuItems,
|
||||||
cleanup,
|
cleanup,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ type ExeResult = {
|
||||||
root: AsUiRoot;
|
root: AsUiRoot;
|
||||||
get: (id: string) => AsUiComponent;
|
get: (id: string) => AsUiComponent;
|
||||||
outputs: values.Value[];
|
outputs: values.Value[];
|
||||||
}
|
};
|
||||||
|
|
||||||
async function exe(script: string): Promise<ExeResult> {
|
async function exe(script: string): Promise<ExeResult> {
|
||||||
const rootRef = ref<AsUiRoot>();
|
const rootRef = ref<AsUiRoot>();
|
||||||
|
|
Loading…
Add table
Reference in a new issue