Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # package.json # packages/backend/src/server/api/stream/channels/hybrid-timeline.ts # packages/frontend/package.json
This commit is contained in:
commit
cc5037cb85
|
@ -18,15 +18,17 @@
|
||||||
- Feat: アイコンデコレーション機能
|
- Feat: アイコンデコレーション機能
|
||||||
- Enhance: すでにフォローしたすべての人の返信をTLに追加できるように
|
- Enhance: すでにフォローしたすべての人の返信をTLに追加できるように
|
||||||
|
|
||||||
## Client
|
### Client
|
||||||
- Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました
|
- Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました
|
||||||
- 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください
|
- 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください
|
||||||
https://misskey-hub.net/docs/advanced/publish-on-your-website.html
|
https://misskey-hub.net/docs/advanced/publish-on-your-website.html
|
||||||
- Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正
|
- Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Enhance: RedisへのTLのキャッシュをオフにできるように
|
||||||
- Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正
|
- Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正
|
||||||
- Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正
|
- Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正
|
||||||
|
- Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正
|
||||||
|
|
||||||
## 2023.10.2
|
## 2023.10.2
|
||||||
|
|
||||||
|
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -1224,6 +1224,7 @@ export interface Locale {
|
||||||
"manifestJsonOverride": string;
|
"manifestJsonOverride": string;
|
||||||
"shortName": string;
|
"shortName": string;
|
||||||
"shortNameDescription": string;
|
"shortNameDescription": string;
|
||||||
|
"fanoutTimelineDescription": string;
|
||||||
};
|
};
|
||||||
"_accountMigration": {
|
"_accountMigration": {
|
||||||
"moveFrom": string;
|
"moveFrom": string;
|
||||||
|
|
|
@ -1222,6 +1222,7 @@ _serverSettings:
|
||||||
manifestJsonOverride: "manifest.jsonのオーバーライド"
|
manifestJsonOverride: "manifest.jsonのオーバーライド"
|
||||||
shortName: "略称"
|
shortName: "略称"
|
||||||
shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。"
|
shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。"
|
||||||
|
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
|
||||||
|
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
moveFrom: "別のアカウントからこのアカウントに移行"
|
moveFrom: "別のアカウントからこのアカウントに移行"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.11.0-beta.2-prismisskey.1",
|
"version": "2023.11.0-beta.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -18,11 +18,11 @@
|
||||||
"build-assets": "node ./scripts/build-assets.mjs",
|
"build-assets": "node ./scripts/build-assets.mjs",
|
||||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-and-start": "cross-env NODE_ENV=development pnpm build && cross-env NODE_ENV=development pnpm start",
|
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
"migrate": "cd packages/backend && pnpm migrate",
|
"migrate": "cd packages/backend && pnpm migrate",
|
||||||
|
"revert": "cd packages/backend && pnpm revert",
|
||||||
"check:connect": "cd packages/backend && pnpm check:connect",
|
"check:connect": "cd packages/backend && pnpm check:connect",
|
||||||
"migrateandstart": "pnpm migrate && pnpm start",
|
"migrateandstart": "pnpm migrate && pnpm start",
|
||||||
"watch": "pnpm dev",
|
"watch": "pnpm dev",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.8.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.3.2",
|
"cypress": "13.3.2",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.52.0",
|
||||||
"start-server-and-test": "2.0.1"
|
"start-server-and-test": "2.0.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|
16
packages/backend/migration/1698041201306-enable-ftt.js
Normal file
16
packages/backend/migration/1698041201306-enable-ftt.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class EnableFtt1698041201306 {
|
||||||
|
name = 'EnableFtt1698041201306'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "enableFanoutTimeline" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFanoutTimeline"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
"start": "node ./built/index.js",
|
"start": "node ./built/index.js",
|
||||||
"start:test": "NODE_ENV=test node ./built/index.js",
|
"start:test": "NODE_ENV=test node ./built/index.js",
|
||||||
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
|
||||||
|
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||||
"check:connect": "node ./check_connect.js",
|
"check:connect": "node ./check_connect.js",
|
||||||
"build": "swc src -d built -D",
|
"build": "swc src -d built -D",
|
||||||
"watch:swc": "swc src -d built -D -w",
|
"watch:swc": "swc src -d built -D -w",
|
||||||
|
@ -76,9 +77,9 @@
|
||||||
"@nestjs/testing": "10.2.7",
|
"@nestjs/testing": "10.2.7",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@simplewebauthn/server": "8.3.2",
|
"@simplewebauthn/server": "8.3.2",
|
||||||
"@sinonjs/fake-timers": "11.2.1",
|
"@sinonjs/fake-timers": "11.2.2",
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.93",
|
"@swc/core": "1.3.94",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"archiver": "6.0.1",
|
"archiver": "6.0.1",
|
||||||
|
@ -124,7 +125,7 @@
|
||||||
"nanoid": "5.0.2",
|
"nanoid": "5.0.2",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.6",
|
"nodemailer": "6.9.7",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
|
@ -142,7 +143,7 @@
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.3",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.20.3",
|
"re2": "1.20.4",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
|
@ -155,7 +156,7 @@
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "github:misskey-dev/summaly",
|
"summaly": "github:misskey-dev/summaly",
|
||||||
"systeminformation": "5.21.12",
|
"systeminformation": "5.21.13",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.1",
|
"tmp": "0.2.1",
|
||||||
"tsc-alias": "1.8.8",
|
"tsc-alias": "1.8.8",
|
||||||
|
@ -216,7 +217,7 @@
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.8.0",
|
||||||
"aws-sdk-client-mock": "3.0.0",
|
"aws-sdk-client-mock": "3.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.28.1",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
|
|
|
@ -77,7 +77,13 @@ export interface MainEventTypes {
|
||||||
unreadAntenna: MiAntenna;
|
unreadAntenna: MiAntenna;
|
||||||
readAllAnnouncements: undefined;
|
readAllAnnouncements: undefined;
|
||||||
myTokenRegenerated: undefined;
|
myTokenRegenerated: undefined;
|
||||||
signin: MiSignin;
|
signin: {
|
||||||
|
id: MiSignin['id'];
|
||||||
|
createdAt: string;
|
||||||
|
ip: string;
|
||||||
|
headers: Record<string, any>;
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
registryUpdated: {
|
registryUpdated: {
|
||||||
scope?: string[];
|
scope?: string[];
|
||||||
key: string;
|
key: string;
|
||||||
|
|
|
@ -56,6 +56,7 @@ import { SearchService } from '@/core/SearchService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
|
@ -216,6 +217,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
private instanceChart: InstanceChart,
|
private instanceChart: InstanceChart,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
private userBlockingService: UserBlockingService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -292,6 +294,18 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check blocking
|
||||||
|
if (data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)) {
|
||||||
|
if (data.renote.userHost === null) {
|
||||||
|
if (data.renote.userId !== user.id) {
|
||||||
|
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||||
|
if (blocked) {
|
||||||
|
throw new Error('blocked');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返信対象がpublicではないならhomeにする
|
// 返信対象がpublicではないならhomeにする
|
||||||
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
||||||
data.visibility = 'home';
|
data.visibility = 'home';
|
||||||
|
@ -825,6 +839,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
@bindThis
|
@bindThis
|
||||||
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
if (!meta.enableFanoutTimeline) return;
|
||||||
|
|
||||||
const r = this.redisForTimelines.pipeline();
|
const r = this.redisForTimelines.pipeline();
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class QueryService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public makePaginationQuery<T extends ObjectLiteral>(q: SelectQueryBuilder<T>, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number): SelectQueryBuilder<T> {
|
public makePaginationQuery<T extends ObjectLiteral>(q: SelectQueryBuilder<T>, sinceId?: string | null, untilId?: string | null, sinceDate?: number | null, untilDate?: number | null): SelectQueryBuilder<T> {
|
||||||
if (sinceId && untilId) {
|
if (sinceId && untilId) {
|
||||||
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId });
|
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId });
|
||||||
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });
|
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });
|
||||||
|
|
|
@ -7,10 +7,12 @@ import { Injectable } from '@nestjs/common';
|
||||||
import type { } from '@/models/Blocking.js';
|
import type { } from '@/models/Blocking.js';
|
||||||
import type { MiSignin } from '@/models/Signin.js';
|
import type { MiSignin } from '@/models/Signin.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SigninEntityService {
|
export class SigninEntityService {
|
||||||
constructor(
|
constructor(
|
||||||
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +20,13 @@ export class SigninEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiSignin,
|
src: MiSignin,
|
||||||
) {
|
) {
|
||||||
return src;
|
return {
|
||||||
|
id: src.id,
|
||||||
|
createdAt: this.idService.parse(src.id).date.toISOString(),
|
||||||
|
ip: src.ip,
|
||||||
|
headers: src.headers,
|
||||||
|
success: src.success,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -504,6 +504,11 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public preservedUsernames: string[];
|
public preservedUsernames: string[];
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public enableFanoutTimeline: boolean;
|
||||||
|
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
default: 300,
|
default: 300,
|
||||||
})
|
})
|
||||||
|
|
|
@ -106,11 +106,11 @@ export const meta = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
silencedHosts: {
|
silencedHosts: {
|
||||||
type: "array",
|
type: 'array',
|
||||||
optional: true,
|
optional: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
items: {
|
items: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
@ -291,6 +291,10 @@ export const meta = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
enableFanoutTimeline: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
perLocalUserUserTimelineCacheMax: {
|
perLocalUserUserTimelineCacheMax: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -423,6 +427,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||||
manifestJsonOverride: instance.manifestJsonOverride,
|
manifestJsonOverride: instance.manifestJsonOverride,
|
||||||
|
enableFanoutTimeline: instance.enableFanoutTimeline,
|
||||||
perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax,
|
perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax,
|
||||||
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
|
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
|
||||||
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
|
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const paramDef = {
|
||||||
serverRules: { type: 'array', items: { type: 'string' } },
|
serverRules: { type: 'array', items: { type: 'string' } },
|
||||||
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
||||||
manifestJsonOverride: { type: 'string' },
|
manifestJsonOverride: { type: 'string' },
|
||||||
|
enableFanoutTimeline: { type: 'boolean' },
|
||||||
perLocalUserUserTimelineCacheMax: { type: 'integer' },
|
perLocalUserUserTimelineCacheMax: { type: 'integer' },
|
||||||
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
||||||
perUserHomeTimelineCacheMax: { type: 'integer' },
|
perUserHomeTimelineCacheMax: { type: 'integer' },
|
||||||
|
@ -491,6 +492,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
set.manifestJsonOverride = ps.manifestJsonOverride;
|
set.manifestJsonOverride = ps.manifestJsonOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.enableFanoutTimeline !== undefined) {
|
||||||
|
set.enableFanoutTimeline = ps.enableFanoutTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.perLocalUserUserTimelineCacheMax !== undefined) {
|
if (ps.perLocalUserUserTimelineCacheMax !== undefined) {
|
||||||
set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax;
|
set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -75,6 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private funoutTimelineService: FunoutTimelineService,
|
private funoutTimelineService: FunoutTimelineService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
|
private metaService: MetaService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
|
@ -85,6 +88,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.stlDisabled);
|
throw new ApiError(meta.errors.stlDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverSettings = await this.metaService.fetch();
|
||||||
|
|
||||||
|
if (serverSettings.enableFanoutTimeline) {
|
||||||
const [
|
const [
|
||||||
userIdsWhoMeMuting,
|
userIdsWhoMeMuting,
|
||||||
userIdsWhoMeMutingRenotes,
|
userIdsWhoMeMutingRenotes,
|
||||||
|
@ -163,9 +169,45 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
} else { // fallback to db
|
} else { // fallback to db
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
includeMyRenotes: ps.includeMyRenotes,
|
||||||
|
includeRenotedMyNotes: ps.includeRenotedMyNotes,
|
||||||
|
includeLocalRenotes: ps.includeLocalRenotes,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
withReplies: ps.withReplies,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
includeMyRenotes: ps.includeMyRenotes,
|
||||||
|
includeRenotedMyNotes: ps.includeRenotedMyNotes,
|
||||||
|
includeLocalRenotes: ps.includeLocalRenotes,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
withReplies: ps.withReplies,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFromDb(ps: {
|
||||||
|
untilId: string | null,
|
||||||
|
sinceId: string | null,
|
||||||
|
limit: number,
|
||||||
|
includeMyRenotes: boolean,
|
||||||
|
includeRenotedMyNotes: boolean,
|
||||||
|
includeLocalRenotes: boolean,
|
||||||
|
withFiles: boolean,
|
||||||
|
withReplies: boolean,
|
||||||
|
}, me: MiLocalUser) {
|
||||||
const followees = await this.userFollowingService.getFollowees(me.id);
|
const followees = await this.userFollowingService.getFollowees(me.id);
|
||||||
|
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
if (followees.length > 0) {
|
if (followees.length > 0) {
|
||||||
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
|
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
|
||||||
|
@ -242,6 +284,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -69,6 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private funoutTimelineService: FunoutTimelineService,
|
private funoutTimelineService: FunoutTimelineService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
|
private metaService: MetaService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
|
@ -79,6 +82,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.ltlDisabled);
|
throw new ApiError(meta.errors.ltlDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverSettings = await this.metaService.fetch();
|
||||||
|
|
||||||
|
if (serverSettings.enableFanoutTimeline) {
|
||||||
const [
|
const [
|
||||||
userIdsWhoMeMuting,
|
userIdsWhoMeMuting,
|
||||||
userIdsWhoMeMutingRenotes,
|
userIdsWhoMeMutingRenotes,
|
||||||
|
@ -145,8 +151,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
} else { // fallback to db
|
} else { // fallback to db
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
withReplies: ps.withReplies,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
withReplies: ps.withReplies,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFromDb(ps: {
|
||||||
|
sinceId: string | null,
|
||||||
|
untilId: string | null,
|
||||||
|
limit: number,
|
||||||
|
withFiles: boolean,
|
||||||
|
withReplies: boolean,
|
||||||
|
}, me: MiLocalUser | null) {
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
ps.sinceId, ps.untilId)
|
||||||
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
|
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
|
@ -185,6 +218,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||||
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
@ -63,11 +65,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private funoutTimelineService: FunoutTimelineService,
|
private funoutTimelineService: FunoutTimelineService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
|
private metaService: MetaService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||||
|
|
||||||
|
const serverSettings = await this.metaService.fetch();
|
||||||
|
|
||||||
|
if (serverSettings.enableFanoutTimeline) {
|
||||||
const [
|
const [
|
||||||
followings,
|
followings,
|
||||||
userIdsWhoMeMuting,
|
userIdsWhoMeMuting,
|
||||||
|
@ -124,10 +130,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
} else { // fallback to db
|
} else { // fallback to db
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
includeMyRenotes: ps.includeMyRenotes,
|
||||||
|
includeRenotedMyNotes: ps.includeRenotedMyNotes,
|
||||||
|
includeLocalRenotes: ps.includeLocalRenotes,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return await this.getFromDb({
|
||||||
|
untilId,
|
||||||
|
sinceId,
|
||||||
|
limit: ps.limit,
|
||||||
|
includeMyRenotes: ps.includeMyRenotes,
|
||||||
|
includeRenotedMyNotes: ps.includeRenotedMyNotes,
|
||||||
|
includeLocalRenotes: ps.includeLocalRenotes,
|
||||||
|
withFiles: ps.withFiles,
|
||||||
|
}, me);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFromDb(ps: { untilId: string | null; sinceId: string | null; limit: number; includeMyRenotes: boolean; includeRenotedMyNotes: boolean; includeLocalRenotes: boolean; withFiles: boolean; }, me: MiLocalUser) {
|
||||||
const followees = await this.userFollowingService.getFollowees(me.id);
|
const followees = await this.userFollowingService.getFollowees(me.id);
|
||||||
|
|
||||||
//#region Construct query
|
//#region Construct query
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||||
.andWhere('note.channelId IS NULL')
|
.andWhere('note.channelId IS NULL')
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
.innerJoinAndSelect('note.user', 'user')
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
|
@ -201,6 +232,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,12 +59,16 @@ class HomeTimelineChannel extends Channel {
|
||||||
if (!note.visibleUserIds!.includes(this.user!.id)) return;
|
if (!note.visibleUserIds!.includes(this.user!.id)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
if (note.reply) {
|
||||||
if (note.reply && !this.following[note.userId]?.withReplies) {
|
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
|
if (this.following[note.userId]?.withReplies) {
|
||||||
|
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||||
|
if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
|
||||||
|
} else {
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
|
if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
|
||||||
|
|
||||||
|
|
|
@ -72,12 +72,17 @@ class HybridTimelineChannel extends Channel {
|
||||||
|
|
||||||
// Ignore notes from instances the user has muted
|
// Ignore notes from instances the user has muted
|
||||||
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances))) return;
|
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances))) return;
|
||||||
// 関係ない返信は除外
|
|
||||||
if (note.reply && !this.following[note.userId]?.withReplies && !this.withReplies) {
|
if (note.reply) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
|
if ((this.following[note.userId]?.withReplies ?? false) || this.withReplies) {
|
||||||
|
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||||
|
if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
|
||||||
|
} else {
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
|
if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
|
||||||
|
|
||||||
|
|
|
@ -90,12 +90,16 @@ class UserListChannel extends Channel {
|
||||||
if (!note.visibleUserIds!.includes(this.user!.id)) return;
|
if (!note.visibleUserIds!.includes(this.user!.id)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
if (note.reply) {
|
||||||
if (note.reply && !this.membershipsMap[note.userId]?.withReplies) {
|
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
|
if (this.membershipsMap[note.userId]?.withReplies) {
|
||||||
|
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
|
||||||
|
if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
|
||||||
|
} else {
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||||
|
|
|
@ -159,6 +159,10 @@ describe('Streaming', () => {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信が流れない', async () => {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
test('フォローしていないユーザーの投稿は流れない', async () => {
|
test('フォローしていないユーザーの投稿は流れない', async () => {
|
||||||
const fired = await waitFire(
|
const fired = await waitFire(
|
||||||
kyoko, 'homeTimeline', // kyoko:home
|
kyoko, 'homeTimeline', // kyoko:home
|
||||||
|
|
|
@ -93,6 +93,7 @@ describe('ActivityPub', () => {
|
||||||
const metaInitial = {
|
const metaInitial = {
|
||||||
cacheRemoteFiles: true,
|
cacheRemoteFiles: true,
|
||||||
cacheRemoteSensitiveFiles: true,
|
cacheRemoteSensitiveFiles: true,
|
||||||
|
enableFanoutTimeline: true,
|
||||||
perUserHomeTimelineCacheMax: 100,
|
perUserHomeTimelineCacheMax: 100,
|
||||||
perLocalUserUserTimelineCacheMax: 100,
|
perLocalUserUserTimelineCacheMax: 100,
|
||||||
perRemoteUserUserTimelineCacheMax: 100,
|
perRemoteUserUserTimelineCacheMax: 100,
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
"@tabler/icons-webfont": "2.37.0",
|
"@tabler/icons-webfont": "2.37.0",
|
||||||
"@vitejs/plugin-vue": "4.4.0",
|
"@vitejs/plugin-vue": "4.4.0",
|
||||||
"@vue-macros/reactivity-transform": "0.3.23",
|
"@vue-macros/reactivity-transform": "0.3.23",
|
||||||
"@vue/compiler-sfc": "3.3.4",
|
"@vue/compiler-sfc": "3.3.6",
|
||||||
"@vue/compiler-sfc": "3.3.5",
|
|
||||||
"astring": "1.8.6",
|
"astring": "1.8.6",
|
||||||
"autosize": "6.0.1",
|
"autosize": "6.0.1",
|
||||||
"broadcast-channel": "5.5.0",
|
"broadcast-channel": "5.5.0",
|
||||||
|
@ -74,7 +73,7 @@
|
||||||
"v-code-diff": "1.7.1",
|
"v-code-diff": "1.7.1",
|
||||||
"vanilla-tilt": "1.8.1",
|
"vanilla-tilt": "1.8.1",
|
||||||
"vite": "4.5.0",
|
"vite": "4.5.0",
|
||||||
"vue": "3.3.5",
|
"vue": "3.3.6",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
"vue-prism-editor": "2.0.0-alpha.2",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
|
@ -113,11 +112,11 @@
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.8.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.8.0",
|
||||||
"@vitest/coverage-v8": "0.34.6",
|
"@vitest/coverage-v8": "0.34.6",
|
||||||
"@vue/runtime-core": "3.3.5",
|
"@vue/runtime-core": "3.3.6",
|
||||||
"acorn": "8.10.0",
|
"acorn": "8.10.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.3.2",
|
"cypress": "13.3.2",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.28.1",
|
||||||
"eslint-plugin-vue": "9.17.0",
|
"eslint-plugin-vue": "9.17.0",
|
||||||
"fast-glob": "3.3.1",
|
"fast-glob": "3.3.1",
|
||||||
|
|
|
@ -175,7 +175,7 @@ export async function common(createVue: () => App<Element>) {
|
||||||
defaultStore.set('darkMode', isDeviceDarkmode());
|
defaultStore.set('darkMode', isDeviceDarkmode());
|
||||||
}
|
}
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
|
||||||
if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
|
if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
|
||||||
defaultStore.set('darkMode', mql.matches);
|
defaultStore.set('darkMode', mql.matches);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,9 +87,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>Timeline caching</template>
|
<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
|
||||||
|
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
|
<MkSwitch v-model="enableFanoutTimeline">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
|
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
|
||||||
<template #label>perLocalUserUserTimelineCacheMax</template>
|
<template #label>perLocalUserUserTimelineCacheMax</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
@ -165,6 +170,7 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false);
|
||||||
let enableServiceWorker: boolean = $ref(false);
|
let enableServiceWorker: boolean = $ref(false);
|
||||||
let swPublicKey: any = $ref(null);
|
let swPublicKey: any = $ref(null);
|
||||||
let swPrivateKey: any = $ref(null);
|
let swPrivateKey: any = $ref(null);
|
||||||
|
let enableFanoutTimeline: boolean = $ref(false);
|
||||||
let perLocalUserUserTimelineCacheMax: number = $ref(0);
|
let perLocalUserUserTimelineCacheMax: number = $ref(0);
|
||||||
let perRemoteUserUserTimelineCacheMax: number = $ref(0);
|
let perRemoteUserUserTimelineCacheMax: number = $ref(0);
|
||||||
let perUserHomeTimelineCacheMax: number = $ref(0);
|
let perUserHomeTimelineCacheMax: number = $ref(0);
|
||||||
|
@ -185,6 +191,7 @@ async function init(): Promise<void> {
|
||||||
enableServiceWorker = meta.enableServiceWorker;
|
enableServiceWorker = meta.enableServiceWorker;
|
||||||
swPublicKey = meta.swPublickey;
|
swPublicKey = meta.swPublickey;
|
||||||
swPrivateKey = meta.swPrivateKey;
|
swPrivateKey = meta.swPrivateKey;
|
||||||
|
enableFanoutTimeline = meta.enableFanoutTimeline;
|
||||||
perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
|
perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
|
||||||
perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
|
perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
|
||||||
perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
|
perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
|
||||||
|
@ -206,6 +213,7 @@ async function save(): void {
|
||||||
enableServiceWorker,
|
enableServiceWorker,
|
||||||
swPublicKey,
|
swPublicKey,
|
||||||
swPrivateKey,
|
swPrivateKey,
|
||||||
|
enableFanoutTimeline,
|
||||||
perLocalUserUserTimelineCacheMax,
|
perLocalUserUserTimelineCacheMax,
|
||||||
perRemoteUserUserTimelineCacheMax,
|
perRemoteUserUserTimelineCacheMax,
|
||||||
perUserHomeTimelineCacheMax,
|
perUserHomeTimelineCacheMax,
|
||||||
|
|
|
@ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted, nextTick } from 'vue';
|
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
|
||||||
import MkLoading from '@/components/global/MkLoading.vue';
|
import MkLoading from '@/components/global/MkLoading.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
@ -120,9 +120,8 @@ const errorKV = ref<{
|
||||||
description: '',
|
description: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const url = ref<string | null>(null);
|
||||||
const url = urlParams.get('url');
|
const hash = ref<string | null>(null);
|
||||||
const hash = urlParams.get('hash');
|
|
||||||
|
|
||||||
const data = ref<{
|
const data = ref<{
|
||||||
type: 'plugin' | 'theme';
|
type: 'plugin' | 'theme';
|
||||||
|
@ -152,7 +151,7 @@ function goToMisskey(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
if (!url || !hash) {
|
if (!url.value || !hash.value) {
|
||||||
errorKV.value = {
|
errorKV.value = {
|
||||||
title: i18n.ts._externalResourceInstaller._errors._invalidParams.title,
|
title: i18n.ts._externalResourceInstaller._errors._invalidParams.title,
|
||||||
description: i18n.ts._externalResourceInstaller._errors._invalidParams.description,
|
description: i18n.ts._externalResourceInstaller._errors._invalidParams.description,
|
||||||
|
@ -161,8 +160,8 @@ async function fetch() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await os.api('fetch-external-resources', {
|
const res = await os.api('fetch-external-resources', {
|
||||||
url,
|
url: url.value,
|
||||||
hash,
|
hash: hash.value,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
switch (err.id) {
|
switch (err.id) {
|
||||||
case 'bb774091-7a15-4a70-9dc5-6ac8cf125856':
|
case 'bb774091-7a15-4a70-9dc5-6ac8cf125856':
|
||||||
|
@ -297,10 +296,17 @@ async function install() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onActivated(() => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
url.value = urlParams.get('url');
|
||||||
|
hash.value = urlParams.get('hash');
|
||||||
fetch();
|
fetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
uiPhase.value = 'fetching';
|
||||||
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"@types/node": "20.8.7",
|
"@types/node": "20.8.7",
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.8.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.8.0",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.52.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
"jest-websocket-mock": "2.5.0",
|
"jest-websocket-mock": "2.5.0",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.93",
|
"@swc/core": "1.3.94",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"reconnecting-websocket": "4.4.0"
|
"reconnecting-websocket": "4.4.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.8.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.28.1",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
|
|
604
pnpm-lock.yaml
604
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue