mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-21 11:05:37 +02:00
Merge branch 'develop' into room
This commit is contained in:
@@ -71,8 +71,8 @@
|
||||
"utf-8-validate": "6.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.1000.0",
|
||||
"@aws-sdk/lib-storage": "3.1000.0",
|
||||
"@aws-sdk/client-s3": "3.1008.0",
|
||||
"@aws-sdk/lib-storage": "3.1008.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.4",
|
||||
"@fastify/cors": "11.2.0",
|
||||
@@ -83,16 +83,16 @@
|
||||
"@kitajs/html": "4.2.13",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@napi-rs/canvas": "0.1.95",
|
||||
"@nestjs/common": "11.1.14",
|
||||
"@nestjs/core": "11.1.14",
|
||||
"@nestjs/testing": "11.1.14",
|
||||
"@napi-rs/canvas": "0.1.96",
|
||||
"@nestjs/common": "11.1.16",
|
||||
"@nestjs/core": "11.1.16",
|
||||
"@nestjs/testing": "11.1.16",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.40.0",
|
||||
"@sentry/profiling-node": "10.40.0",
|
||||
"@simplewebauthn/server": "13.2.3",
|
||||
"@sinonjs/fake-timers": "15.1.0",
|
||||
"@smithy/node-http-handler": "4.4.12",
|
||||
"@sentry/node": "10.43.0",
|
||||
"@sentry/profiling-node": "10.43.0",
|
||||
"@simplewebauthn/server": "13.3.0",
|
||||
"@sinonjs/fake-timers": "15.1.1",
|
||||
"@smithy/node-http-handler": "4.4.16",
|
||||
"@swc/cli": "0.8.0",
|
||||
"@swc/core": "1.15.18",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
@@ -103,7 +103,7 @@
|
||||
"bcryptjs": "3.0.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "2.2.2",
|
||||
"bullmq": "5.70.1",
|
||||
"bullmq": "5.71.0",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"chalk": "5.6.2",
|
||||
"chalk-template": "1.1.2",
|
||||
@@ -112,10 +112,10 @@
|
||||
"content-disposition": "1.0.1",
|
||||
"date-fns": "4.1.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "5.8.1",
|
||||
"fastify": "5.8.2",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "5.2.0",
|
||||
"file-type": "21.3.0",
|
||||
"file-type": "21.3.2",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.5",
|
||||
"got": "14.6.6",
|
||||
@@ -138,14 +138,14 @@
|
||||
"nanoid": "5.1.6",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"node-html-parser": "7.0.2",
|
||||
"nodemailer": "8.0.1",
|
||||
"node-html-parser": "7.1.0",
|
||||
"nodemailer": "8.0.2",
|
||||
"nsfwjs": "4.2.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.5.0",
|
||||
"pg": "8.19.0",
|
||||
"pg": "8.20.0",
|
||||
"pkce-challenge": "6.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
@@ -164,7 +164,7 @@
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.31.1",
|
||||
"systeminformation": "5.31.4",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
@@ -178,8 +178,8 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.4",
|
||||
"@nestjs/platform-express": "11.1.14",
|
||||
"@sentry/vue": "10.40.0",
|
||||
"@nestjs/platform-express": "11.1.16",
|
||||
"@sentry/vue": "10.43.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -193,7 +193,7 @@
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/nodemailer": "7.0.11",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
@@ -202,7 +202,7 @@
|
||||
"@types/random-seed": "0.3.5",
|
||||
"@types/ratelimiter": "3.4.6",
|
||||
"@types/rename": "1.0.7",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sanitize-html": "2.16.1",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/simple-oauth2": "5.0.8",
|
||||
"@types/sinonjs__fake-timers": "15.0.1",
|
||||
@@ -212,10 +212,10 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.11",
|
||||
"cbor": "10.0.12",
|
||||
"cross-env": "10.1.0",
|
||||
"esbuild-plugin-swc": "1.0.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
|
||||
@@ -10,7 +10,6 @@ import { type FastifyServerOptions } from 'fastify';
|
||||
import type * as Sentry from '@sentry/node';
|
||||
import type * as SentryVue from '@sentry/vue';
|
||||
import type { RedisOptions } from 'ioredis';
|
||||
import type { ManifestChunk } from 'vite';
|
||||
|
||||
type RedisOptionsSource = Partial<RedisOptions> & {
|
||||
host: string;
|
||||
@@ -189,9 +188,7 @@ export type Config = {
|
||||
authUrl: string;
|
||||
driveUrl: string;
|
||||
userAgent: string;
|
||||
frontendEntry: ManifestChunk;
|
||||
frontendManifestExists: boolean;
|
||||
frontendEmbedEntry: ManifestChunk;
|
||||
frontendEmbedManifestExists: boolean;
|
||||
mediaProxy: string;
|
||||
externalMediaProxyEnabled: boolean;
|
||||
@@ -250,12 +247,6 @@ export function loadConfig(): Config {
|
||||
|
||||
const frontendManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'));
|
||||
const frontendEmbedManifestExists = fs.existsSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'));
|
||||
const frontendManifest = frontendManifestExists ?
|
||||
JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_vite_/manifest.json'), 'utf-8'))
|
||||
: { 'src/_boot_.ts': { file: null } };
|
||||
const frontendEmbedManifest = frontendEmbedManifestExists ?
|
||||
JSON.parse(fs.readFileSync(resolve(projectBuiltDir, '_frontend_embed_vite_/manifest.json'), 'utf-8'))
|
||||
: { 'src/boot.ts': { file: null } };
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(compiledConfigFilePath, 'utf-8')) as Source;
|
||||
|
||||
@@ -337,9 +328,7 @@ export function loadConfig(): Config {
|
||||
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
||||
: null,
|
||||
userAgent: `Misskey/${version} (${config.url})`,
|
||||
frontendEntry: frontendManifest['src/_boot_.ts'],
|
||||
frontendManifestExists: frontendManifestExists,
|
||||
frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
|
||||
frontendEmbedManifestExists: frontendEmbedManifestExists,
|
||||
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
|
||||
@@ -81,7 +81,7 @@ export class ApRequestCreator {
|
||||
}, args.additionalHeaders),
|
||||
};
|
||||
|
||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']);
|
||||
const result = this.#signToRequest(request, args.key, ['(request-target)', 'date', 'host']);
|
||||
|
||||
return {
|
||||
request,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointServ
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { Brackets } from 'typeorm';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes', 'channels'],
|
||||
@@ -132,7 +133,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.then(x => x.map(x => x.id).filter(x => x !== ps.channelId));
|
||||
if (mutingChannelIds.length > 0) {
|
||||
query.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
query.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb.orWhere('note.renoteChannelId IS NULL');
|
||||
qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}));
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -177,7 +177,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.andWhere('note.channelId IS NULL')
|
||||
.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
|
||||
if (mutingChannelIds.length > 0) {
|
||||
qb.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
qb.andWhere(new Brackets(qb2 => {
|
||||
qb2.orWhere('note.renoteChannelId IS NULL');
|
||||
qb2.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}));
|
||||
}
|
||||
}));
|
||||
} else if (followingChannelIds.length > 0) {
|
||||
|
||||
@@ -185,7 +185,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (ps.withChannelNotes) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
if (mutingChannelIds.length > 0) {
|
||||
qb.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds: mutingChannelIds });
|
||||
qb.andWhere(new Brackets(qb2 => {
|
||||
qb2.orWhere('note.channelId IS NULL');
|
||||
qb2.orWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds });
|
||||
}));
|
||||
}
|
||||
|
||||
if (!isSelf) {
|
||||
|
||||
@@ -6,16 +6,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { deepClone } from '@/misc/clone.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
|
||||
type HiddenLayer = 'note' | 'renote' | 'renoteRenote';
|
||||
|
||||
type LockdownCheckResult =
|
||||
| { shouldSkip: true }
|
||||
| { shouldSkip: false; hiddenLayers: Set<HiddenLayer> };
|
||||
|
||||
/** Streamにおいて、ノートを隠す(hideNote)を適用するためのService */
|
||||
@Injectable()
|
||||
export class NoteStreamingHidingService {
|
||||
@@ -23,110 +18,52 @@ export class NoteStreamingHidingService {
|
||||
private noteEntityService: NoteEntityService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ノートの可視性を判定する
|
||||
*
|
||||
* @param note - 判定対象のノート
|
||||
* @param meId - 閲覧者のユーザーID(未ログインの場合はnull)
|
||||
* @returns shouldSkip: true の場合はノートを流さない、false の場合は hiddenLayers に基づいて隠す
|
||||
*/
|
||||
@bindThis
|
||||
public async shouldHide(
|
||||
note: Packed<'Note'>,
|
||||
meId: MiUser['id'] | null,
|
||||
): Promise<LockdownCheckResult> {
|
||||
const hiddenLayers = new Set<HiddenLayer>();
|
||||
private collectRenoteChain(note: Packed<'Note'>): Packed<'Note'>[] {
|
||||
const renoteChain: Packed<'Note'>[] = [];
|
||||
|
||||
// 1階層目: note自体
|
||||
const shouldHideThisNote = await this.noteEntityService.shouldHideNote(note, meId);
|
||||
if (shouldHideThisNote) {
|
||||
if (isRenotePacked(note) && isQuotePacked(note)) {
|
||||
// 引用リノートの場合、内容を隠して流す
|
||||
hiddenLayers.add('note');
|
||||
} else if (isRenotePacked(note)) {
|
||||
// 純粋リノートの場合、流さない
|
||||
return { shouldSkip: true };
|
||||
} else {
|
||||
// 通常ノートの場合、内容を隠して流す
|
||||
hiddenLayers.add('note');
|
||||
}
|
||||
for (let current: Packed<'Note'> | null | undefined = note; current != null; current = current.renote) {
|
||||
renoteChain.push(current);
|
||||
}
|
||||
|
||||
// 2階層目: note.renote
|
||||
if (isRenotePacked(note) && note.renote) {
|
||||
const shouldHideRenote = await this.noteEntityService.shouldHideNote(note.renote, meId);
|
||||
if (shouldHideRenote) {
|
||||
if (isQuotePacked(note)) {
|
||||
// noteが引用リノートの場合、renote部分だけ隠す
|
||||
hiddenLayers.add('renote');
|
||||
} else {
|
||||
// noteが純粋リノートの場合、流さない
|
||||
return { shouldSkip: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3階層目: note.renote.renote
|
||||
if (isRenotePacked(note) && note.renote &&
|
||||
isRenotePacked(note.renote) && note.renote.renote) {
|
||||
const shouldHideRenoteRenote = await this.noteEntityService.shouldHideNote(note.renote.renote, meId);
|
||||
if (shouldHideRenoteRenote) {
|
||||
if (isQuotePacked(note.renote)) {
|
||||
// note.renoteが引用リノートの場合、renote.renote部分だけ隠す
|
||||
hiddenLayers.add('renoteRenote');
|
||||
} else {
|
||||
// note.renoteが純粋リノートの場合、note.renoteの意味がなくなるので流さない
|
||||
return { shouldSkip: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { shouldSkip: false, hiddenLayers };
|
||||
return renoteChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* hiddenLayersに基づいてノートの内容を隠す。
|
||||
* ストリーミング配信用にノートの内容を隠す(あるいはそもそも送信しない)判定及び処理を行う。
|
||||
*
|
||||
* この処理は渡された `note` を直接変更します。
|
||||
* 隠す処理が必要な場合は元のノートをクローンして変更を適用したものを返し、
|
||||
* 送信すべきでない場合は `null` を返す。
|
||||
* 変更が不要な場合は元のノートの参照をそのまま返す。
|
||||
*
|
||||
* @param note - 処理対象のノート
|
||||
* @param hiddenLayers - 隠す階層のセット
|
||||
* @param meId - 閲覧者のユーザー ID (未ログインの場合は `null`)
|
||||
* @returns 配信するノートオブジェクト、または配信スキップの場合は `null`
|
||||
*/
|
||||
@bindThis
|
||||
public applyHiding(
|
||||
note: Packed<'Note'>,
|
||||
hiddenLayers: Set<HiddenLayer>,
|
||||
): void {
|
||||
if (hiddenLayers.has('note')) {
|
||||
this.noteEntityService.hideNote(note);
|
||||
}
|
||||
if (hiddenLayers.has('renote') && note.renote) {
|
||||
this.noteEntityService.hideNote(note.renote);
|
||||
}
|
||||
if (hiddenLayers.has('renoteRenote') && note.renote && note.renote.renote) {
|
||||
this.noteEntityService.hideNote(note.renote.renote);
|
||||
}
|
||||
}
|
||||
public async filter(note: Packed<'Note'>, meId: MiUser['id'] | null): Promise<Packed<'Note'> | null> {
|
||||
const renoteChain = this.collectRenoteChain(note);
|
||||
const shouldHide = await Promise.all(renoteChain.map(n => this.noteEntityService.shouldHideNote(n, meId)));
|
||||
|
||||
/**
|
||||
* ストリーミング配信用にノートを隠す(あるいはそもそも送信しない)の判定及び処理を行う。
|
||||
*
|
||||
* この処理は渡された `note` を直接変更します。
|
||||
*
|
||||
* @param note - 処理対象のノート(必要に応じて内容が隠される)
|
||||
* @param meId - 閲覧者のユーザーID(未ログインの場合はnull)
|
||||
* @returns shouldSkip: true の場合はノートを流さない
|
||||
*/
|
||||
@bindThis
|
||||
public async processHiding(
|
||||
note: Packed<'Note'>,
|
||||
meId: MiUser['id'] | null,
|
||||
): Promise<{ shouldSkip: boolean }> {
|
||||
const result = await this.shouldHide(note, meId);
|
||||
if (result.shouldSkip) {
|
||||
return { shouldSkip: true };
|
||||
if (!shouldHide.some(h => h)) {
|
||||
// 隠す必要がない場合は元のノートをそのまま返す
|
||||
return note;
|
||||
}
|
||||
this.applyHiding(note, result.hiddenLayers);
|
||||
return { shouldSkip: false };
|
||||
|
||||
if (renoteChain.some(n => isRenotePacked(n) && !isQuotePacked(n))) {
|
||||
// 純粋リノートの場合は配信をスキップする
|
||||
return null;
|
||||
}
|
||||
|
||||
const clonedNote = deepClone(note);
|
||||
let currentCloned = clonedNote;
|
||||
|
||||
for (let i = 0; i < renoteChain.length; i++) {
|
||||
if (shouldHide[i]) {
|
||||
this.noteEntityService.hideNote(currentCloned);
|
||||
}
|
||||
currentCloned = currentCloned.renote!;
|
||||
}
|
||||
|
||||
return clonedNote;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,14 @@ export class AntennaChannel extends Channel {
|
||||
@bindThis
|
||||
private async onEvent(data: GlobalEvents['antenna']['payload']) {
|
||||
if (data.type === 'note') {
|
||||
const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
|
||||
let note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
|
||||
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -53,8 +53,10 @@ export class ChannelChannel extends Channel {
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -62,8 +62,10 @@ export class GlobalTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -59,8 +59,10 @@ export class HashtagChannel extends Channel {
|
||||
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -82,8 +82,10 @@ export class HomeTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -101,8 +101,10 @@ export class HybridTimelineChannel extends Channel {
|
||||
}
|
||||
}
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -72,8 +72,10 @@ export class LocalTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -44,7 +44,7 @@ export class RoleTimelineChannel extends Channel {
|
||||
@bindThis
|
||||
private async onEvent(data: GlobalEvents['roleTimeline']['payload']) {
|
||||
if (data.type === 'note') {
|
||||
const note = data.body;
|
||||
let note = data.body;
|
||||
|
||||
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
|
||||
return;
|
||||
@@ -56,8 +56,9 @@ export class RoleTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -117,8 +117,10 @@ export class UserListChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
const filtered = await this.noteStreamingHidingService.filter(note, this.user?.id ?? null);
|
||||
if (!filtered) return;
|
||||
// eslint-disable-next-line no-param-reassign -- これ以降元の Note オブジェクトは見てはいけないので、いっそ再代入した方が安全
|
||||
note = filtered;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { dirname } from 'node:path';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { promises as fsp, existsSync } from 'node:fs';
|
||||
import { languages } from 'i18n/const';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -13,21 +13,34 @@ import { bindThis } from '@/decorators.js';
|
||||
import { htmlSafeJsonStringify } from '@/misc/json-stringify-html-safe.js';
|
||||
import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
|
||||
import type { FastifyReply } from 'fastify';
|
||||
import type { Manifest } from 'vite';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import type { CommonData } from './views/_.js';
|
||||
import type { CommonData, ViteFiles } from './views/_.js';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const frontendVitePublic = `${_dirname}/../../../../frontend/public/`;
|
||||
const frontendEmbedVitePublic = `${_dirname}/../../../../frontend-embed/public/`;
|
||||
let rootDir = _dirname;
|
||||
// 見つかるまで上に遡る
|
||||
while (!existsSync(resolve(rootDir, 'packages'))) {
|
||||
const parentDir = dirname(rootDir);
|
||||
if (parentDir === rootDir) {
|
||||
throw new Error('Cannot find root directory');
|
||||
}
|
||||
rootDir = parentDir;
|
||||
}
|
||||
|
||||
const frontendViteBuilt = resolve(rootDir, 'built/_frontend_vite_');
|
||||
const frontendEmbedViteBuilt = resolve(rootDir, 'built/_frontend_embed_vite_');
|
||||
|
||||
@Injectable()
|
||||
export class HtmlTemplateService {
|
||||
private frontendBootloadersFetched = false;
|
||||
private frontendAssetsFetched = false;
|
||||
public frontendViteFiles: ViteFiles | null = null;
|
||||
public frontendBootloaderJs: string | null = null;
|
||||
public frontendBootloaderCss: string | null = null;
|
||||
public frontendEmbedViteFiles: ViteFiles | null = null;
|
||||
public frontendEmbedBootloaderJs: string | null = null;
|
||||
public frontendEmbedBootloaderCss: string | null = null;
|
||||
|
||||
@@ -42,18 +55,92 @@ export class HtmlTemplateService {
|
||||
) {
|
||||
}
|
||||
|
||||
// 初期ロードで読み込むべきファイルのパスを収集する。
|
||||
// See https://ja.vite.dev/guide/backend-integration
|
||||
@bindThis
|
||||
private async prepareFrontendBootloaders() {
|
||||
if (this.frontendBootloadersFetched) return;
|
||||
this.frontendBootloadersFetched = true;
|
||||
private collectViteAssetFiles(manifest: Manifest): ViteFiles {
|
||||
const entryFile = Object.values(manifest).find((chunk) => chunk.isEntry);
|
||||
if (!entryFile) return {
|
||||
entryJs: null,
|
||||
css: [],
|
||||
modulePreloads: [],
|
||||
};
|
||||
|
||||
const [bootJs, bootCss, embedBootJs, embedBootCss] = await Promise.all([
|
||||
fsp.readFile(`${frontendVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
|
||||
fsp.readFile(`${frontendVitePublic}loader/style.css`, 'utf-8').catch(() => null),
|
||||
fsp.readFile(`${frontendEmbedVitePublic}loader/boot.js`, 'utf-8').catch(() => null),
|
||||
fsp.readFile(`${frontendEmbedVitePublic}loader/style.css`, 'utf-8').catch(() => null),
|
||||
const seenChunkIds = new Set<string>();
|
||||
const cssFiles = new Set<string>();
|
||||
const modulePreloads = new Set<string>();
|
||||
|
||||
if (entryFile.css) {
|
||||
entryFile.css.forEach((css) => cssFiles.add(css));
|
||||
}
|
||||
|
||||
if (entryFile.imports != null && Array.isArray(entryFile.imports)) {
|
||||
function collectImports(imports: string[], recursive = false) {
|
||||
for (const importId of imports) {
|
||||
if (seenChunkIds.has(importId)) continue;
|
||||
seenChunkIds.add(importId);
|
||||
|
||||
const importedChunk = manifest[importId];
|
||||
if (!importedChunk) return;
|
||||
|
||||
if (importedChunk.css) {
|
||||
importedChunk.css.forEach((css) => cssFiles.add(css));
|
||||
}
|
||||
|
||||
if (importedChunk.imports != null && Array.isArray(importedChunk.imports)) {
|
||||
collectImports(importedChunk.imports, true);
|
||||
}
|
||||
|
||||
if (!recursive) {
|
||||
modulePreloads.add(importedChunk.file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectImports(entryFile.imports);
|
||||
}
|
||||
|
||||
return {
|
||||
entryJs: entryFile.file,
|
||||
css: Array.from(cssFiles),
|
||||
modulePreloads: Array.from(modulePreloads),
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async prepareFrontendAssets() {
|
||||
if (this.frontendAssetsFetched) return;
|
||||
this.frontendAssetsFetched = true;
|
||||
|
||||
const [
|
||||
bootJs,
|
||||
bootCss,
|
||||
embedBootJs,
|
||||
embedBootCss,
|
||||
] = await Promise.all([
|
||||
fsp.readFile(resolve(frontendViteBuilt, 'loader/boot.js'), 'utf-8').catch(() => null),
|
||||
fsp.readFile(resolve(frontendViteBuilt, 'loader/style.css'), 'utf-8').catch(() => null),
|
||||
fsp.readFile(resolve(frontendEmbedViteBuilt, 'loader/boot.js'), 'utf-8').catch(() => null),
|
||||
fsp.readFile(resolve(frontendEmbedViteBuilt, 'loader/style.css'), 'utf-8').catch(() => null),
|
||||
]);
|
||||
|
||||
let feViteManifest: Manifest | null = null;
|
||||
let embedFeViteManifest: Manifest | null = null;
|
||||
|
||||
if (this.config.frontendManifestExists) {
|
||||
const manifestContent = await fsp.readFile(resolve(frontendViteBuilt, 'manifest.json'), 'utf-8').catch(() => null);
|
||||
feViteManifest = manifestContent ? JSON.parse(manifestContent) : null;
|
||||
}
|
||||
|
||||
if (this.config.frontendEmbedManifestExists) {
|
||||
const manifestContent = await fsp.readFile(resolve(frontendEmbedViteBuilt, 'manifest.json'), 'utf-8').catch(() => null);
|
||||
embedFeViteManifest = manifestContent ? JSON.parse(manifestContent) : null;
|
||||
}
|
||||
|
||||
if (feViteManifest != null) {
|
||||
this.frontendViteFiles = this.collectViteAssetFiles(feViteManifest);
|
||||
}
|
||||
|
||||
if (bootJs != null) {
|
||||
this.frontendBootloaderJs = bootJs;
|
||||
}
|
||||
@@ -62,6 +149,10 @@ export class HtmlTemplateService {
|
||||
this.frontendBootloaderCss = bootCss;
|
||||
}
|
||||
|
||||
if (embedFeViteManifest != null) {
|
||||
this.frontendEmbedViteFiles = this.collectViteAssetFiles(embedFeViteManifest);
|
||||
}
|
||||
|
||||
if (embedBootJs != null) {
|
||||
this.frontendEmbedBootloaderJs = embedBootJs;
|
||||
}
|
||||
@@ -73,7 +164,7 @@ export class HtmlTemplateService {
|
||||
|
||||
@bindThis
|
||||
public async getCommonData(): Promise<CommonData> {
|
||||
await this.prepareFrontendBootloaders();
|
||||
await this.prepareFrontendAssets();
|
||||
|
||||
return {
|
||||
version: this.config.version,
|
||||
@@ -90,8 +181,10 @@ export class HtmlTemplateService {
|
||||
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(this.meta)),
|
||||
now: Date.now(),
|
||||
federationEnabled: this.meta.federation !== 'none',
|
||||
frontendViteFiles: this.frontendViteFiles,
|
||||
frontendBootloaderJs: this.frontendBootloaderJs,
|
||||
frontendBootloaderCss: this.frontendBootloaderCss,
|
||||
frontendEmbedViteFiles: this.frontendEmbedViteFiles,
|
||||
frontendEmbedBootloaderJs: this.frontendEmbedBootloaderJs,
|
||||
frontendEmbedBootloaderCss: this.frontendEmbedBootloaderCss,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,12 @@ export type MinimumCommonData = {
|
||||
config: Config;
|
||||
};
|
||||
|
||||
export type ViteFiles = {
|
||||
entryJs: string | null;
|
||||
css: string[];
|
||||
modulePreloads: string[];
|
||||
};
|
||||
|
||||
export type CommonData = MinimumCommonData & {
|
||||
langs: string[];
|
||||
instanceName: string;
|
||||
@@ -36,8 +42,10 @@ export type CommonData = MinimumCommonData & {
|
||||
instanceUrl: string;
|
||||
now: number;
|
||||
federationEnabled: boolean;
|
||||
frontendViteFiles: ViteFiles | null;
|
||||
frontendBootloaderJs: string | null;
|
||||
frontendBootloaderCss: string | null;
|
||||
frontendEmbedViteFiles: ViteFiles | null;
|
||||
frontendEmbedBootloaderJs: string | null;
|
||||
frontendEmbedBootloaderCss: string | null;
|
||||
metaJson?: string;
|
||||
|
||||
@@ -46,11 +46,11 @@ export function BaseEmbed(props: PropsWithChildren<CommonProps<{
|
||||
<link rel="icon" href={props.icon ?? '/favicon.ico'} />
|
||||
<link rel="apple-touch-icon" href={props.appleTouchIcon ?? '/apple-touch-icon.png'} />
|
||||
|
||||
{!props.config.frontendEmbedManifestExists ? <script type="module" src="/embed_vite/@vite/client"></script> : null}
|
||||
{props.frontendEmbedViteFiles == null ? <script type="module" src="/embed_vite/@vite/client"></script> : null}
|
||||
|
||||
{props.config.frontendEmbedEntry.css != null ? props.config.frontendEmbedEntry.css.map((href) => (
|
||||
{(props.frontendEmbedViteFiles?.css ?? []).map((href) => (
|
||||
<link rel="stylesheet" href={`/embed_vite/${href}`} />
|
||||
)) : null}
|
||||
))}
|
||||
|
||||
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function BaseEmbed(props: PropsWithChildren<CommonProps<{
|
||||
|
||||
<script>
|
||||
const VERSION = '{props.version}';
|
||||
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEmbedEntry.file)};
|
||||
const CLIENT_ENTRY = {JSON.stringify(props.frontendEmbedViteFiles?.entryJs ?? null)};
|
||||
const LANGS = {JSON.stringify(props.langs)};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ export function Layout(props: PropsWithChildren<CommonProps<{
|
||||
{props.infoImageUrl != null ? <link rel="prefetch" as="image" href={props.infoImageUrl} /> : null}
|
||||
{props.notFoundImageUrl != null ? <link rel="prefetch" as="image" href={props.notFoundImageUrl} /> : null}
|
||||
|
||||
{!props.config.frontendManifestExists ? <script type="module" src="/vite/@vite/client"></script> : null}
|
||||
{props.frontendViteFiles == null ? <script type="module" src="/vite/@vite/client"></script> : null}
|
||||
|
||||
{props.config.frontendEntry.css != null ? props.config.frontendEntry.css.map((href) => (
|
||||
{(props.frontendViteFiles?.css ?? []).map((href) => (
|
||||
<link rel="stylesheet" href={`/vite/${href}`} />
|
||||
)) : null}
|
||||
))}
|
||||
|
||||
{props.titleSlot ?? <title safe>{props.title || 'Misskey'}</title>}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function Layout(props: PropsWithChildren<CommonProps<{
|
||||
|
||||
<script>
|
||||
const VERSION = '{props.version}';
|
||||
const CLIENT_ENTRY = {JSON.stringify(props.config.frontendEntry.file)};
|
||||
const CLIENT_ENTRY = {JSON.stringify(props.frontendViteFiles?.entryJs ?? null)};
|
||||
const LANGS = {JSON.stringify(props.langs)};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"rollup": "4.59.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.4",
|
||||
"@vitejs/plugin-vue": "6.0.5",
|
||||
"buraha": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
"frontend-shared": "workspace:*",
|
||||
@@ -26,12 +26,12 @@
|
||||
"misskey-js": "workspace:*",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.59.0",
|
||||
"sass": "1.97.3",
|
||||
"sass": "1.98.0",
|
||||
"shiki": "3.23.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"uuid": "13.0.0",
|
||||
"vite": "7.3.1",
|
||||
"vue": "3.5.29"
|
||||
"vue": "3.5.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
@@ -39,19 +39,19 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"@vue/runtime-core": "3.5.29",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/coverage-v8": "4.1.0",
|
||||
"@vue/runtime-core": "3.5.30",
|
||||
"acorn": "8.16.0",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"happy-dom": "20.7.0",
|
||||
"happy-dom": "20.8.4",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.12.10",
|
||||
|
||||
@@ -79,3 +79,9 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
|
||||
ruby: [],
|
||||
unixtime: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* デフォルトの絵文字。UIの演出などでも使用されるため空にしてはいけない
|
||||
* (絵文字パレットのデフォルト絵文字を空にする等の場合は、preferenceのdefinition側の値を空にすること)
|
||||
*/
|
||||
export const DEFAULT_EMOJIS = ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'];
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"esbuild": "0.27.4",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"nodemon": "3.1.14",
|
||||
"vue-eslint-parser": "10.4.0"
|
||||
@@ -35,6 +35,6 @@
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"vue": "3.5.29"
|
||||
"vue": "3.5.30"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.40.0",
|
||||
"@sentry/vue": "10.43.0",
|
||||
"@syuilo/aiscript": "1.2.1",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.4",
|
||||
"@vitejs/plugin-vue": "6.0.5",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.16",
|
||||
"analytics": "0.8.19",
|
||||
"broadcast-channel": "7.3.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"date-fns": "4.1.0",
|
||||
"eventemitter3": "5.0.4",
|
||||
"execa": "9.6.1",
|
||||
"exifreader": "4.36.2",
|
||||
"exifreader": "4.37.0",
|
||||
"frontend-shared": "workspace:*",
|
||||
"i18n": "workspace:*",
|
||||
"icons-subsetter": "workspace:*",
|
||||
@@ -59,7 +59,7 @@
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"matter-js": "0.20.0",
|
||||
"mediabunny": "1.35.1",
|
||||
"mediabunny": "1.39.2",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -70,7 +70,7 @@
|
||||
"qr-scanner": "1.4.2",
|
||||
"rollup": "4.59.0",
|
||||
"sanitize-html": "2.17.1",
|
||||
"sass": "1.97.3",
|
||||
"sass": "1.98.0",
|
||||
"shiki": "3.23.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.183.2",
|
||||
@@ -78,28 +78,28 @@
|
||||
"tinycolor2": "1.6.0",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.3.1",
|
||||
"vue": "3.5.29",
|
||||
"vue": "3.5.30",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@storybook/addon-essentials": "8.6.17",
|
||||
"@storybook/addon-interactions": "8.6.17",
|
||||
"@storybook/addon-links": "10.2.13",
|
||||
"@storybook/addon-mdx-gfm": "8.6.17",
|
||||
"@storybook/addon-storysource": "8.6.17",
|
||||
"@storybook/blocks": "8.6.17",
|
||||
"@storybook/components": "8.6.17",
|
||||
"@storybook/core-events": "8.6.17",
|
||||
"@storybook/manager-api": "8.6.17",
|
||||
"@storybook/preview-api": "8.6.17",
|
||||
"@storybook/react": "10.2.13",
|
||||
"@storybook/react-vite": "10.2.13",
|
||||
"@storybook/test": "8.6.17",
|
||||
"@storybook/theming": "8.6.17",
|
||||
"@storybook/types": "8.6.17",
|
||||
"@storybook/vue3": "10.2.13",
|
||||
"@storybook/vue3-vite": "10.2.13",
|
||||
"@storybook/addon-essentials": "8.6.18",
|
||||
"@storybook/addon-interactions": "8.6.18",
|
||||
"@storybook/addon-links": "10.2.17",
|
||||
"@storybook/addon-mdx-gfm": "8.6.18",
|
||||
"@storybook/addon-storysource": "8.6.18",
|
||||
"@storybook/blocks": "8.6.18",
|
||||
"@storybook/components": "8.6.18",
|
||||
"@storybook/core-events": "8.6.18",
|
||||
"@storybook/manager-api": "8.6.18",
|
||||
"@storybook/preview-api": "8.6.18",
|
||||
"@storybook/react": "10.2.17",
|
||||
"@storybook/react-vite": "10.2.17",
|
||||
"@storybook/test": "8.6.18",
|
||||
"@storybook/theming": "8.6.18",
|
||||
"@storybook/types": "8.6.18",
|
||||
"@storybook/vue3": "10.2.17",
|
||||
"@storybook/vue3-vite": "10.2.17",
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
@@ -107,17 +107,17 @@
|
||||
"@types/insert-text-at-cursor": "0.3.2",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sanitize-html": "2.16.1",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/textarea-caret": "3.0.4",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"@vue/compiler-core": "3.5.29",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/coverage-v8": "4.1.0",
|
||||
"@vue/compiler-core": "3.5.30",
|
||||
"acorn": "8.16.0",
|
||||
"astring": "1.9.0",
|
||||
"cross-env": "10.1.0",
|
||||
@@ -125,7 +125,7 @@
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"happy-dom": "20.7.0",
|
||||
"happy-dom": "20.8.4",
|
||||
"intersection-observer": "0.12.2",
|
||||
"magic-string": "0.30.21",
|
||||
"micromatch": "4.0.8",
|
||||
@@ -138,12 +138,12 @@
|
||||
"react-dom": "19.2.4",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.1.5",
|
||||
"storybook": "10.2.13",
|
||||
"storybook": "10.2.17",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.21.0",
|
||||
"vite-plugin-glsl": "1.5.5",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "4.0.18",
|
||||
"vitest": "4.1.0",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.2.5",
|
||||
"vue-eslint-parser": "10.4.0",
|
||||
|
||||
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<MkWindow
|
||||
ref="window"
|
||||
:initialWidth="800"
|
||||
:initialHeight="500"
|
||||
:canResize="true"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
|
||||
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<MkWindow
|
||||
ref="windowEl"
|
||||
:initialWidth="500"
|
||||
:initialHeight="500"
|
||||
:canResize="true"
|
||||
:closeButton="true"
|
||||
:buttonsLeft="buttonsLeft"
|
||||
|
||||
@@ -106,7 +106,9 @@ async function toggleReaction() {
|
||||
reaction: props.reaction,
|
||||
}).then(() => {
|
||||
const emoji = customEmojisMap.get(emojiName.value);
|
||||
if (emoji == null) return;
|
||||
if (emoji == null && getUnicodeEmojiOrNull(props.reaction) == null) {
|
||||
return;
|
||||
}
|
||||
noteEvents.emit(`reacted:${props.noteId}`, {
|
||||
userId: me.id,
|
||||
reaction: props.reaction,
|
||||
@@ -138,7 +140,9 @@ async function toggleReaction() {
|
||||
reaction: props.reaction,
|
||||
}).then(() => {
|
||||
const emoji = customEmojisMap.get(emojiName.value);
|
||||
if (emoji == null) return;
|
||||
if (emoji == null && getUnicodeEmojiOrNull(props.reaction) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
noteEvents.emit(`reacted:${props.noteId}`, {
|
||||
userId: me.id,
|
||||
|
||||
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@afterLeave="emit('closed')"
|
||||
>
|
||||
<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
|
||||
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
|
||||
<div :class="$style.body" class="_shadow" @pointerdown="onBodyPointerDown" @keydown="onKeydown">
|
||||
<div :class="[$style.header, { [$style.mini]: mini }]" @contextmenu.prevent.stop="onContextmenu">
|
||||
<span :class="$style.headerLeft">
|
||||
<template v-if="!minimized">
|
||||
@@ -106,8 +106,8 @@ function capturePointer(evt: PointerEvent) {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
initialWidth: number;
|
||||
initialHeight: number | null;
|
||||
initialWidth?: number | null;
|
||||
initialHeight?: number | null;
|
||||
canResize?: boolean;
|
||||
closeButton?: boolean;
|
||||
mini?: boolean;
|
||||
@@ -116,7 +116,7 @@ const props = withDefaults(defineProps<{
|
||||
buttonsLeft?: WindowButton[];
|
||||
buttonsRight?: WindowButton[];
|
||||
}>(), {
|
||||
initialWidth: 400,
|
||||
initialWidth: null,
|
||||
initialHeight: null,
|
||||
canResize: false,
|
||||
closeButton: true,
|
||||
@@ -131,6 +131,12 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const INITIAL_WINDOW_WIDTH_RATIO = 0.5;
|
||||
const INITIAL_WINDOW_HEIGHT_RATIO = 0.75;
|
||||
const INITIAL_WINDOW_WIDTH_MIN = 400; // スクリーンの最小幅に合わせるのはapplyTransormWidthの担当
|
||||
const INITIAL_WINDOW_WIDTH_MAX = 1000; // 画面幅いっぱいに広がるのを防止するための最大幅
|
||||
const INITIAL_WINDOW_HEIGHT_MIN = 500; // スクリーンの最小幅に合わせるのはapplyTransormHeightの担当
|
||||
|
||||
provide('inWindow', true);
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
@@ -216,7 +222,7 @@ function unMinimize() {
|
||||
if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px';
|
||||
}
|
||||
|
||||
function onBodyMousedown() {
|
||||
function onBodyPointerDown() {
|
||||
top();
|
||||
}
|
||||
|
||||
@@ -484,8 +490,14 @@ function onBrowserResize() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
applyTransformWidth(props.initialWidth);
|
||||
if (props.initialHeight) applyTransformHeight(props.initialHeight);
|
||||
let initialWidth = props.initialWidth;
|
||||
let initialHeight = props.initialHeight;
|
||||
|
||||
if (initialWidth == null) initialWidth = Math.min(Math.max(Math.round(window.innerWidth * INITIAL_WINDOW_WIDTH_RATIO), INITIAL_WINDOW_WIDTH_MIN), INITIAL_WINDOW_WIDTH_MAX);
|
||||
if (initialHeight == null) initialHeight = Math.max(Math.round(window.innerHeight * INITIAL_WINDOW_HEIGHT_RATIO), INITIAL_WINDOW_HEIGHT_MIN);
|
||||
|
||||
applyTransformWidth(initialWidth);
|
||||
applyTransformHeight(initialHeight);
|
||||
|
||||
if (rootEl.value) {
|
||||
applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));
|
||||
|
||||
@@ -150,6 +150,7 @@ import { definePage } from '@/page.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DEFAULT_EMOJIS } from '@@/js/const.js';
|
||||
|
||||
const patronsWithIcon = [{
|
||||
name: 'カイヤン',
|
||||
@@ -429,7 +430,12 @@ const containerEl = useTemplateRef('containerEl');
|
||||
|
||||
function iconLoaded() {
|
||||
if (containerEl.value == null) return;
|
||||
const emojis = prefer.s.emojiPalettes[0].emojis;
|
||||
const emojis = prefer.s.emojiPalettes[0]?.emojis ?? [];
|
||||
|
||||
if (emojis.length < DEFAULT_EMOJIS.length) {
|
||||
emojis.push(...DEFAULT_EMOJIS.slice(0, DEFAULT_EMOJIS.length - emojis.length));
|
||||
}
|
||||
|
||||
const containerWidth = containerEl.value.offsetWidth;
|
||||
for (let i = 0; i < 32; i++) {
|
||||
easterEggEmojis.value.push({
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { hemisphere } from '@@/js/intl-const.js';
|
||||
import { DEFAULT_EMOJIS } from '@@/js/const.js';
|
||||
import { prefersReducedMotion } from '@@/js/config.js';
|
||||
import { definePreferences } from './manager.js';
|
||||
import type { Theme } from '@/theme.js';
|
||||
@@ -103,7 +104,7 @@ export const PREF_DEF = definePreferences({
|
||||
default: () => [{
|
||||
id: genId(),
|
||||
name: '',
|
||||
emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
emojis: DEFAULT_EMOJIS,
|
||||
}] as {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"chokidar": "5.0.0",
|
||||
"esbuild": "0.27.3",
|
||||
"esbuild": "0.27.4",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.14",
|
||||
"tsx": "4.21.0"
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.11.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"esbuild": "0.27.4",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.14"
|
||||
},
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "5.5.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"openapi-types": "12.1.3",
|
||||
"openapi-typescript": "7.13.0",
|
||||
"ts-case-convert": "2.1.0",
|
||||
"tsx": "4.21.0",
|
||||
"eslint": "9.39.3"
|
||||
"eslint": "9.39.4"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2026.3.2-alpha.0",
|
||||
"version": "2026.3.2-alpha.2",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
@@ -37,17 +37,17 @@
|
||||
"directory": "packages/misskey-js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.57.6",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"esbuild": "0.27.3",
|
||||
"@microsoft/api-extractor": "7.57.7",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/coverage-v8": "4.1.0",
|
||||
"esbuild": "0.27.4",
|
||||
"execa": "9.6.1",
|
||||
"ncp": "2.0.0",
|
||||
"nodemon": "3.1.14",
|
||||
"tsd": "0.33.0",
|
||||
"vitest": "4.0.18",
|
||||
"vitest": "4.1.0",
|
||||
"vitest-websocket-mock": "0.5.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"esbuild": "0.27.4",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.14"
|
||||
},
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
"esbuild": "0.27.3",
|
||||
"esbuild": "0.27.4",
|
||||
"idb-keyval": "6.2.2",
|
||||
"misskey-js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"nodemon": "3.1.14"
|
||||
|
||||
Reference in New Issue
Block a user