Merge branch 'develop' into feat/plugin-mute
This commit is contained in:
commit
c7629578a9
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -22,15 +22,24 @@
|
||||||
- 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
|
||||||
|
- Enhance: データセーバー有効時はアニメーション付きのアバター画像が停止するように
|
||||||
|
- Enhance: プラグインを削除した際には、使用されていたアクセストークンも同時に削除されるようになりました
|
||||||
|
- Enhance: プラグインで`Plugin:register_note_view_interruptor`を用いてnoteの代わりにnullを返却することでノートを非表示にできるようになりました
|
||||||
|
- Enhance: AiScript関数`Mk:nyaize()`が追加されました
|
||||||
- Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正
|
- Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正
|
||||||
- Feat: プラグインで`Plugin:register_note_view_interruptor`を用いてnoteの代わりにnullを返却することでノートを非表示にできるようになりました
|
- Fix: ユーザーページの ノート > ファイル付き タブにリプライが表示されてしまう
|
||||||
|
- Fix: 「検索」MFMにおいて一部の検索キーワードが正しく認識されない問題を修正
|
||||||
|
- Fix: 一部の言語でMisskey Webがクラッシュする問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: RedisへのTLのキャッシュをオフにできるように
|
- Enhance: RedisへのTLのキャッシュをオフにできるように
|
||||||
|
- Enhance: フォローしているチャンネルをフォロー解除した時(またはその逆)、タイムラインに反映される間隔を改善
|
||||||
- Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正
|
- Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正
|
||||||
- Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正
|
- Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正
|
||||||
- Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正
|
- Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正
|
||||||
- Fix: RedisへのTLキャッシュが有効の場合にHTL/LTL/STLが空になることがある問題を修正
|
- Fix: RedisへのTLキャッシュが有効の場合にHTL/LTL/STLが空になることがある問題を修正
|
||||||
|
- Fix: STLでフォローしていないチャンネルが取得される問題を修正
|
||||||
|
- Fix: `hashtags/trend`にてRedisからトレンドの情報が取得できない際にInternal Server Errorになる問題を修正
|
||||||
|
|
||||||
## 2023.10.2
|
## 2023.10.2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.9.2",
|
"packageManager": "pnpm@8.10.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
|
|
@ -52,10 +52,10 @@
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.3.2",
|
"cypress": "13.3.3",
|
||||||
"eslint": "8.52.0",
|
"eslint": "8.52.0",
|
||||||
"start-server-and-test": "2.0.1"
|
"start-server-and-test": "2.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,10 @@
|
||||||
"@nestjs/core": "10.2.7",
|
"@nestjs/core": "10.2.7",
|
||||||
"@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.4",
|
||||||
"@sinonjs/fake-timers": "11.2.2",
|
"@sinonjs/fake-timers": "11.2.2",
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.94",
|
"@swc/core": "1.3.95",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"archiver": "6.0.1",
|
"archiver": "6.0.1",
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"bullmq": "4.12.5",
|
"bullmq": "4.12.6",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.1",
|
"cbor": "9.0.1",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
|
|
@ -143,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.4",
|
"re2": "1.20.5",
|
||||||
"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",
|
||||||
|
|
@ -156,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.13",
|
"systeminformation": "5.21.15",
|
||||||
"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",
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@simplewebauthn/typescript-types": "8.0.0",
|
"@simplewebauthn/typescript-types": "8.3.4",
|
||||||
"@swc/jest": "0.2.29",
|
"@swc/jest": "0.2.29",
|
||||||
"@types/accepts": "1.3.6",
|
"@types/accepts": "1.3.6",
|
||||||
"@types/archiver": "5.3.4",
|
"@types/archiver": "5.3.4",
|
||||||
|
|
@ -190,7 +190,7 @@
|
||||||
"@types/jsrsasign": "10.5.11",
|
"@types/jsrsasign": "10.5.11",
|
||||||
"@types/mime-types": "2.1.3",
|
"@types/mime-types": "2.1.3",
|
||||||
"@types/ms": "0.7.33",
|
"@types/ms": "0.7.33",
|
||||||
"@types/node": "20.8.7",
|
"@types/node": "20.8.9",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.13",
|
"@types/nodemailer": "6.4.13",
|
||||||
"@types/oauth": "0.9.3",
|
"@types/oauth": "0.9.3",
|
||||||
|
|
@ -213,12 +213,12 @@
|
||||||
"@types/vary": "1.1.2",
|
"@types/vary": "1.1.2",
|
||||||
"@types/web-push": "3.6.2",
|
"@types/web-push": "3.6.2",
|
||||||
"@types/ws": "8.5.8",
|
"@types/ws": "8.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.9.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.52.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.29.0",
|
||||||
"execa": "8.0.1",
|
"execa": "8.0.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
|
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
|
||||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
@ -26,7 +26,6 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
||||||
public userFollowingChannelsCache: RedisKVCache<Set<string>>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
|
|
@ -53,9 +52,6 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
@Inject(DI.followingsRepository)
|
@Inject(DI.followingsRepository)
|
||||||
private followingsRepository: FollowingsRepository,
|
private followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
@Inject(DI.channelFollowingsRepository)
|
|
||||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
) {
|
) {
|
||||||
//this.onMessage = this.onMessage.bind(this);
|
//this.onMessage = this.onMessage.bind(this);
|
||||||
|
|
@ -150,13 +146,7 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
fromRedisConverter: (value) => JSON.parse(value),
|
fromRedisConverter: (value) => JSON.parse(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.userFollowingChannelsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowingChannels', {
|
// NOTE: チャンネルのフォロー状況キャッシュはChannelFollowingServiceで行っている
|
||||||
lifetime: 1000 * 60 * 30, // 30m
|
|
||||||
memoryCacheLifetime: 1000 * 60, // 1m
|
|
||||||
fetcher: (key) => this.channelFollowingsRepository.find({ where: { followerId: key }, select: ['followeeId'] }).then(xs => new Set(xs.map(x => x.followeeId))),
|
|
||||||
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
|
||||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.redisForSub.on('message', this.onMessage);
|
this.redisForSub.on('message', this.onMessage);
|
||||||
}
|
}
|
||||||
|
|
@ -221,7 +211,6 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
this.userBlockedCache.dispose();
|
this.userBlockedCache.dispose();
|
||||||
this.renoteMutingsCache.dispose();
|
this.renoteMutingsCache.dispose();
|
||||||
this.userFollowingsCache.dispose();
|
this.userFollowingsCache.dispose();
|
||||||
this.userFollowingChannelsCache.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import Redis from 'ioredis';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { ChannelFollowingsRepository } from '@/models/_.js';
|
||||||
|
import { MiChannel } from '@/models/_.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { MiLocalUser } from '@/models/User.js';
|
||||||
|
import { RedisKVCache } from '@/misc/cache.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChannelFollowingService implements OnModuleInit {
|
||||||
|
public userFollowingChannelsCache: RedisKVCache<Set<string>>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
@Inject(DI.redisForSub)
|
||||||
|
private redisForSub: Redis.Redis,
|
||||||
|
@Inject(DI.channelFollowingsRepository)
|
||||||
|
private channelFollowingsRepository: ChannelFollowingsRepository,
|
||||||
|
private idService: IdService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
|
) {
|
||||||
|
this.userFollowingChannelsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowingChannels', {
|
||||||
|
lifetime: 1000 * 60 * 30, // 30m
|
||||||
|
memoryCacheLifetime: 1000 * 60, // 1m
|
||||||
|
fetcher: (key) => this.channelFollowingsRepository.find({
|
||||||
|
where: { followerId: key },
|
||||||
|
select: ['followeeId'],
|
||||||
|
}).then(xs => new Set(xs.map(x => x.followeeId))),
|
||||||
|
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||||
|
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.redisForSub.on('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async follow(
|
||||||
|
requestUser: MiLocalUser,
|
||||||
|
targetChannel: MiChannel,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.channelFollowingsRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
followerId: requestUser.id,
|
||||||
|
followeeId: targetChannel.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('followChannel', {
|
||||||
|
userId: requestUser.id,
|
||||||
|
channelId: targetChannel.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async unfollow(
|
||||||
|
requestUser: MiLocalUser,
|
||||||
|
targetChannel: MiChannel,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.channelFollowingsRepository.delete({
|
||||||
|
followerId: requestUser.id,
|
||||||
|
followeeId: targetChannel.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('unfollowChannel', {
|
||||||
|
userId: requestUser.id,
|
||||||
|
channelId: targetChannel.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onMessage(_: string, data: string): Promise<void> {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
|
if (obj.channel === 'internal') {
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'followChannel': {
|
||||||
|
this.userFollowingChannelsCache.refresh(body.userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'unfollowChannel': {
|
||||||
|
this.userFollowingChannelsCache.delete(body.userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.userFollowingChannelsCache.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,6 +63,7 @@ import { SearchService } from './SearchService.js';
|
||||||
import { ClipService } from './ClipService.js';
|
import { ClipService } from './ClipService.js';
|
||||||
import { FeaturedService } from './FeaturedService.js';
|
import { FeaturedService } from './FeaturedService.js';
|
||||||
import { FunoutTimelineService } from './FunoutTimelineService.js';
|
import { FunoutTimelineService } from './FunoutTimelineService.js';
|
||||||
|
import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||||
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
||||||
import FederationChart from './chart/charts/federation.js';
|
import FederationChart from './chart/charts/federation.js';
|
||||||
import NotesChart from './chart/charts/notes.js';
|
import NotesChart from './chart/charts/notes.js';
|
||||||
|
|
@ -193,6 +194,7 @@ const $SearchService: Provider = { provide: 'SearchService', useExisting: Search
|
||||||
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
|
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
|
||||||
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
|
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
|
||||||
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
|
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
|
||||||
|
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
|
||||||
|
|
||||||
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
|
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
|
||||||
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
|
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
|
||||||
|
|
@ -327,6 +329,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
ClipService,
|
ClipService,
|
||||||
FeaturedService,
|
FeaturedService,
|
||||||
FunoutTimelineService,
|
FunoutTimelineService,
|
||||||
|
ChannelFollowingService,
|
||||||
ChartLoggerService,
|
ChartLoggerService,
|
||||||
FederationChart,
|
FederationChart,
|
||||||
NotesChart,
|
NotesChart,
|
||||||
|
|
@ -454,6 +457,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$ClipService,
|
$ClipService,
|
||||||
$FeaturedService,
|
$FeaturedService,
|
||||||
$FunoutTimelineService,
|
$FunoutTimelineService,
|
||||||
|
$ChannelFollowingService,
|
||||||
$ChartLoggerService,
|
$ChartLoggerService,
|
||||||
$FederationChart,
|
$FederationChart,
|
||||||
$NotesChart,
|
$NotesChart,
|
||||||
|
|
@ -582,6 +586,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
ClipService,
|
ClipService,
|
||||||
FeaturedService,
|
FeaturedService,
|
||||||
FunoutTimelineService,
|
FunoutTimelineService,
|
||||||
|
ChannelFollowingService,
|
||||||
FederationChart,
|
FederationChart,
|
||||||
NotesChart,
|
NotesChart,
|
||||||
UsersChart,
|
UsersChart,
|
||||||
|
|
@ -708,6 +713,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$ClipService,
|
$ClipService,
|
||||||
$FeaturedService,
|
$FeaturedService,
|
||||||
$FunoutTimelineService,
|
$FunoutTimelineService,
|
||||||
|
$ChannelFollowingService,
|
||||||
$FederationChart,
|
$FederationChart,
|
||||||
$NotesChart,
|
$NotesChart,
|
||||||
$UsersChart,
|
$UsersChart,
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export class FeaturedService {
|
||||||
`${name}:${currentWindow}`, 0, threshold, 'REV', 'WITHSCORES');
|
`${name}:${currentWindow}`, 0, threshold, 'REV', 'WITHSCORES');
|
||||||
redisPipeline.zrange(
|
redisPipeline.zrange(
|
||||||
`${name}:${previousWindow}`, 0, threshold, 'REV', 'WITHSCORES');
|
`${name}:${previousWindow}`, 0, threshold, 'REV', 'WITHSCORES');
|
||||||
const [currentRankingResult, previousRankingResult] = await redisPipeline.exec().then(result => result ? result.map(r => r[1] as string[]) : [[], []]);
|
const [currentRankingResult, previousRankingResult] = await redisPipeline.exec().then(result => result ? result.map(r => (r[1] ?? []) as string[]) : [[], []]);
|
||||||
|
|
||||||
const ranking = new Map<string, number>();
|
const ranking = new Map<string, number>();
|
||||||
for (let i = 0; i < currentRankingResult.length; i += 2) {
|
for (let i = 0; i < currentRankingResult.length; i += 2) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||||
import MainStreamConnection from './stream/Connection.js';
|
import MainStreamConnection from './stream/Connection.js';
|
||||||
import { ChannelsService } from './stream/ChannelsService.js';
|
import { ChannelsService } from './stream/ChannelsService.js';
|
||||||
|
|
@ -39,6 +40,7 @@ export class StreamingApiServerService {
|
||||||
private channelsService: ChannelsService,
|
private channelsService: ChannelsService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private usersService: UserService,
|
private usersService: UserService,
|
||||||
|
private channelFollowingService: ChannelFollowingService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +95,7 @@ export class StreamingApiServerService {
|
||||||
this.noteReadService,
|
this.noteReadService,
|
||||||
this.notificationService,
|
this.notificationService,
|
||||||
this.cacheService,
|
this.cacheService,
|
||||||
|
this.channelFollowingService,
|
||||||
user, app,
|
user, app,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js';
|
import type { ChannelsRepository } from '@/models/_.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
|
@ -41,11 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.channelsRepository)
|
@Inject(DI.channelsRepository)
|
||||||
private channelsRepository: ChannelsRepository,
|
private channelsRepository: ChannelsRepository,
|
||||||
|
private channelFollowingService: ChannelFollowingService,
|
||||||
@Inject(DI.channelFollowingsRepository)
|
|
||||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
|
||||||
|
|
||||||
private idService: IdService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const channel = await this.channelsRepository.findOneBy({
|
const channel = await this.channelsRepository.findOneBy({
|
||||||
|
|
@ -56,11 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchChannel);
|
throw new ApiError(meta.errors.noSuchChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.channelFollowingsRepository.insert({
|
await this.channelFollowingService.follow(me, channel);
|
||||||
id: this.idService.gen(),
|
|
||||||
followerId: me.id,
|
|
||||||
followeeId: channel.id,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js';
|
import type { ChannelsRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
|
@ -40,9 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.channelsRepository)
|
@Inject(DI.channelsRepository)
|
||||||
private channelsRepository: ChannelsRepository,
|
private channelsRepository: ChannelsRepository,
|
||||||
|
private channelFollowingService: ChannelFollowingService,
|
||||||
@Inject(DI.channelFollowingsRepository)
|
|
||||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const channel = await this.channelsRepository.findOneBy({
|
const channel = await this.channelsRepository.findOneBy({
|
||||||
|
|
@ -53,10 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchChannel);
|
throw new ApiError(meta.errors.noSuchChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.channelFollowingsRepository.delete({
|
await this.channelFollowingService.unfollow(me, channel);
|
||||||
followerId: me.id,
|
|
||||||
followeeId: channel.id,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,12 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
tokenId: { type: 'string', format: 'misskey:id' },
|
tokenId: { type: 'string', format: 'misskey:id' },
|
||||||
|
token: { type: 'string' },
|
||||||
},
|
},
|
||||||
required: ['tokenId'],
|
anyOf: [
|
||||||
|
{ required: ['tokenId'] },
|
||||||
|
{ required: ['token'] },
|
||||||
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -29,13 +33,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private accessTokensRepository: AccessTokensRepository,
|
private accessTokensRepository: AccessTokensRepository,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
|
if (ps.tokenId) {
|
||||||
|
const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
|
||||||
|
|
||||||
if (tokenExist) {
|
if (tokenExist) {
|
||||||
await this.accessTokensRepository.delete({
|
await this.accessTokensRepository.delete({
|
||||||
id: ps.tokenId,
|
id: ps.tokenId,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} else if (ps.token) {
|
||||||
|
const tokenExist = await this.accessTokensRepository.exist({ where: { token: ps.token } });
|
||||||
|
|
||||||
|
if (tokenExist) {
|
||||||
|
await this.accessTokensRepository.delete({
|
||||||
|
token: ps.token,
|
||||||
|
userId: me.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets } from 'typeorm';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
|
import type { NotesRepository, FollowingsRepository, MiNote, ChannelFollowingsRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
|
|
@ -69,6 +69,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.channelFollowingsRepository)
|
||||||
|
private channelFollowingsRepository: ChannelFollowingsRepository,
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
|
|
@ -208,6 +211,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
withReplies: boolean,
|
withReplies: boolean,
|
||||||
}, me: MiLocalUser) {
|
}, me: MiLocalUser) {
|
||||||
const followees = await this.userFollowingService.getFollowees(me.id);
|
const followees = await this.userFollowingService.getFollowees(me.id);
|
||||||
|
const followingChannels = await this.channelFollowingsRepository.find({
|
||||||
|
where: {
|
||||||
|
followerId: me.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
|
|
@ -226,6 +234,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
|
if (followingChannels.length > 0) {
|
||||||
|
const followingChannelIds = followingChannels.map(x => x.followeeId);
|
||||||
|
|
||||||
|
query.andWhere('note.channelId IN (:...followingChannelIds) OR note.channelId IS NULL', { followingChannelIds });
|
||||||
|
} else {
|
||||||
|
query.andWhere('note.channelId IS NULL');
|
||||||
|
}
|
||||||
|
|
||||||
if (!ps.withReplies) {
|
if (!ps.withReplies) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
qb
|
qb
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||||
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import type { ChannelsService } from './ChannelsService.js';
|
import type { ChannelsService } from './ChannelsService.js';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
import type Channel from './channel.js';
|
import type Channel from './channel.js';
|
||||||
|
|
@ -42,6 +43,7 @@ export default class Connection {
|
||||||
private noteReadService: NoteReadService,
|
private noteReadService: NoteReadService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
|
private channelFollowingService: ChannelFollowingService,
|
||||||
|
|
||||||
user: MiUser | null | undefined,
|
user: MiUser | null | undefined,
|
||||||
token: MiAccessToken | null | undefined,
|
token: MiAccessToken | null | undefined,
|
||||||
|
|
@ -56,7 +58,7 @@ export default class Connection {
|
||||||
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes] = await Promise.all([
|
const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes] = await Promise.all([
|
||||||
this.cacheService.userProfileCache.fetch(this.user.id),
|
this.cacheService.userProfileCache.fetch(this.user.id),
|
||||||
this.cacheService.userFollowingsCache.fetch(this.user.id),
|
this.cacheService.userFollowingsCache.fetch(this.user.id),
|
||||||
this.cacheService.userFollowingChannelsCache.fetch(this.user.id),
|
this.channelFollowingService.userFollowingChannelsCache.fetch(this.user.id),
|
||||||
this.cacheService.userMutingsCache.fetch(this.user.id),
|
this.cacheService.userMutingsCache.fetch(this.user.id),
|
||||||
this.cacheService.userBlockedCache.fetch(this.user.id),
|
this.cacheService.userBlockedCache.fetch(this.user.id),
|
||||||
this.cacheService.renoteMutingsCache.fetch(this.user.id),
|
this.cacheService.renoteMutingsCache.fetch(this.user.id),
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@
|
||||||
"@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.6",
|
"@vue/compiler-sfc": "3.3.7",
|
||||||
"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.1",
|
||||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
|
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.6.1",
|
"canvas-confetti": "1.6.1",
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"chromatic": "7.4.0",
|
"chromatic": "7.5.4",
|
||||||
"compare-versions": "6.1.0",
|
"compare-versions": "6.1.0",
|
||||||
"cropperjs": "2.0.0-beta.4",
|
"cropperjs": "2.0.0-beta.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
|
|
@ -59,10 +59,10 @@
|
||||||
"querystring": "0.2.1",
|
"querystring": "0.2.1",
|
||||||
"rollup": "4.1.4",
|
"rollup": "4.1.4",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"sass": "1.69.4",
|
"sass": "1.69.5",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.157.0",
|
"three": "0.158.0",
|
||||||
"throttle-debounce": "5.0.0",
|
"throttle-debounce": "5.0.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.8",
|
"tsc-alias": "1.8.8",
|
||||||
|
|
@ -73,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.6",
|
"vue": "3.3.7",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
"vue-prism-editor": "2.0.0-alpha.2",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next"
|
||||||
},
|
},
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
"@types/estree": "1.0.3",
|
"@types/estree": "1.0.3",
|
||||||
"@types/matter-js": "0.19.2",
|
"@types/matter-js": "0.19.2",
|
||||||
"@types/micromatch": "4.0.4",
|
"@types/micromatch": "4.0.4",
|
||||||
"@types/node": "20.8.7",
|
"@types/node": "20.8.9",
|
||||||
"@types/punycode": "2.1.1",
|
"@types/punycode": "2.1.1",
|
||||||
"@types/sanitize-html": "2.9.3",
|
"@types/sanitize-html": "2.9.3",
|
||||||
"@types/throttle-debounce": "5.0.1",
|
"@types/throttle-debounce": "5.0.1",
|
||||||
|
|
@ -109,21 +109,21 @@
|
||||||
"@types/uuid": "9.0.6",
|
"@types/uuid": "9.0.6",
|
||||||
"@types/websocket": "1.0.8",
|
"@types/websocket": "1.0.8",
|
||||||
"@types/ws": "8.5.8",
|
"@types/ws": "8.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
"@vitest/coverage-v8": "0.34.6",
|
"@vitest/coverage-v8": "0.34.6",
|
||||||
"@vue/runtime-core": "3.3.6",
|
"@vue/runtime-core": "3.3.7",
|
||||||
"acorn": "8.10.0",
|
"acorn": "8.11.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.3.2",
|
"cypress": "13.3.3",
|
||||||
"eslint": "8.52.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.29.0",
|
||||||
"eslint-plugin-vue": "9.17.0",
|
"eslint-plugin-vue": "9.18.1",
|
||||||
"fast-glob": "3.3.1",
|
"fast-glob": "3.3.1",
|
||||||
"happy-dom": "10.0.3",
|
"happy-dom": "10.0.3",
|
||||||
"micromatch": "4.0.5",
|
"micromatch": "4.0.5",
|
||||||
"msw": "1.3.2",
|
"msw": "1.3.2",
|
||||||
"msw-storybook-addon": "1.9.0",
|
"msw-storybook-addon": "1.10.0",
|
||||||
"nodemon": "3.0.1",
|
"nodemon": "3.0.1",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
|
@ -136,6 +136,6 @@
|
||||||
"vitest": "0.34.6",
|
"vitest": "0.34.6",
|
||||||
"vitest-fetch-mock": "0.2.2",
|
"vitest-fetch-mock": "0.2.2",
|
||||||
"vue-eslint-parser": "9.3.2",
|
"vue-eslint-parser": "9.3.2",
|
||||||
"vue-tsc": "1.8.19"
|
"vue-tsc": "1.8.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
|
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
|
||||||
<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter">
|
<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" autocapitalize="off" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter">
|
||||||
<!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 -->
|
<!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 -->
|
||||||
<div ref="emojisEl" class="emojis" tabindex="-1">
|
<div ref="emojisEl" class="emojis" tabindex="-1">
|
||||||
<section class="result">
|
<section class="result">
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ const props = defineProps<{
|
||||||
const query = ref(props.q);
|
const query = ref(props.q);
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
window.open(`https://www.google.com/search?q=${query.value}`, '_blank');
|
const sp = new URLSearchParams();
|
||||||
|
sp.append('q', query.value);
|
||||||
|
window.open(`https://www.google.com/search?${sp.toString()}`, '_blank');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:pattern="pattern"
|
:pattern="pattern"
|
||||||
:autocomplete="autocomplete"
|
:autocomplete="autocomplete"
|
||||||
|
:autocapitalize="autocapitalize"
|
||||||
:spellcheck="spellcheck"
|
:spellcheck="spellcheck"
|
||||||
:step="step"
|
:step="step"
|
||||||
:list="id"
|
:list="id"
|
||||||
|
|
@ -58,6 +59,7 @@ const props = defineProps<{
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
|
autocapitalize?: string;
|
||||||
spellcheck?: boolean;
|
spellcheck?: boolean;
|
||||||
step?: any;
|
step?: any;
|
||||||
datalist?: string[];
|
datalist?: string[];
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||||
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
||||||
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
|
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'account'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
|
||||||
</div>
|
</div>
|
||||||
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||||
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
||||||
|
|
@ -53,19 +53,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
|
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
|
||||||
<div style="container-type: inline-size;">
|
<div style="container-type: inline-size;">
|
||||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
|
||||||
<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
|
<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||||
<div :class="$style.text">
|
<div :class="$style.text">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||||
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
|
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,19 +67,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</header>
|
</header>
|
||||||
<div :class="$style.noteContent">
|
<div :class="$style.noteContent">
|
||||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
|
||||||
<MkCwButton v-model="showContent" :note="appearNote"/>
|
<MkCwButton v-model="showContent" :note="appearNote"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent">
|
<div v-show="appearNote.cw == null || showContent">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||||
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
|
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
|
||||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote.files.length > 0">
|
<div v-if="appearNote.files.length > 0">
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Mfm :text="text.trim()" :author="user" :i="user"/>
|
<Mfm :text="text.trim()" :author="user" :nyaize="'account'" :i="user"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||||
<div>
|
<div>
|
||||||
<p v-if="note.cw != null" :class="$style.cw">
|
<p v-if="note.cw != null" :class="$style.cw">
|
||||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emojiUrls="note.emojis"/>
|
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i" :emojiUrls="note.emojis"/>
|
||||||
<MkCwButton v-model="showContent" :note="note"/>
|
<MkCwButton v-model="showContent" :note="note"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="note.cw == null || showContent">
|
<div v-show="note.cw == null || showContent">
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||||
<div>
|
<div>
|
||||||
<p v-if="note.cw != null" :class="$style.cw">
|
<p v-if="note.cw != null" :class="$style.cw">
|
||||||
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i"/>
|
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i"/>
|
||||||
<MkCwButton v-model="showContent" :note="note"/>
|
<MkCwButton v-model="showContent" :note="note"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="note.cw == null || showContent">
|
<div v-show="note.cw == null || showContent">
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ const bound = $computed(() => props.link
|
||||||
? { to: userPage(props.user), target: props.target }
|
? { to: userPage(props.user), target: props.target }
|
||||||
: {});
|
: {});
|
||||||
|
|
||||||
const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
|
const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode)
|
||||||
? getStaticImageUrl(props.user.avatarUrl)
|
? getStaticImageUrl(props.user.avatarUrl)
|
||||||
: props.user.avatarUrl);
|
: props.user.avatarUrl);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import MkSparkle from '@/components/MkSparkle.vue';
|
||||||
import MkA from '@/components/global/MkA.vue';
|
import MkA from '@/components/global/MkA.vue';
|
||||||
import { host } from '@/config.js';
|
import { host } from '@/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { nyaize } from '@/scripts/nyaize.js';
|
import { nyaize as doNyaize } from '@/scripts/nyaize.js';
|
||||||
|
|
||||||
const QUOTE_STYLE = `
|
const QUOTE_STYLE = `
|
||||||
display: block;
|
display: block;
|
||||||
|
|
@ -28,21 +28,27 @@ border-left: solid 3px var(--fg);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
`.split('\n').join(' ');
|
`.split('\n').join(' ');
|
||||||
|
|
||||||
export default function(props: {
|
type MfmProps = {
|
||||||
text: string;
|
text: string;
|
||||||
plain?: boolean;
|
plain?: boolean;
|
||||||
nowrap?: boolean;
|
nowrap?: boolean;
|
||||||
author?: Misskey.entities.UserLite;
|
author?: Misskey.entities.UserLite;
|
||||||
i?: Misskey.entities.UserLite;
|
i?: Misskey.entities.UserLite | null;
|
||||||
isNote?: boolean;
|
isNote?: boolean;
|
||||||
emojiUrls?: string[];
|
emojiUrls?: string[];
|
||||||
rootScale?: number;
|
rootScale?: number;
|
||||||
}) {
|
nyaize: boolean | 'account';
|
||||||
const isNote = props.isNote !== undefined ? props.isNote : true;
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default function(props: MfmProps) {
|
||||||
|
const isNote = props.isNote ?? true;
|
||||||
|
const shouldNyaize = props.nyaize ? props.nyaize === 'account' ? props.author?.isCat : false : false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (props.text == null || props.text === '') return;
|
if (props.text == null || props.text === '') return;
|
||||||
|
|
||||||
const ast = (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
|
const rootAst = (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
|
||||||
|
|
||||||
const validTime = (t: string | null | undefined) => {
|
const validTime = (t: string | null | undefined) => {
|
||||||
if (t == null) return null;
|
if (t == null) return null;
|
||||||
|
|
@ -55,13 +61,14 @@ export default function(props: {
|
||||||
* Gen Vue Elements from MFM AST
|
* Gen Vue Elements from MFM AST
|
||||||
* @param ast MFM AST
|
* @param ast MFM AST
|
||||||
* @param scale How times large the text is
|
* @param scale How times large the text is
|
||||||
|
* @param disableNyaize Whether nyaize is disabled or not
|
||||||
*/
|
*/
|
||||||
const genEl = (ast: mfm.MfmNode[], scale: number, disableNyaize = false) => ast.map((token): VNode | string | (VNode | string)[] => {
|
const genEl = (ast: mfm.MfmNode[], scale: number, disableNyaize = false) => ast.map((token): VNode | string | (VNode | string)[] => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||||
if (!disableNyaize && props.author?.isCat) {
|
if (!disableNyaize && shouldNyaize) {
|
||||||
text = nyaize(text);
|
text = doNyaize(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.plain) {
|
if (!props.plain) {
|
||||||
|
|
@ -377,5 +384,5 @@ export default function(props: {
|
||||||
return h('span', {
|
return h('span', {
|
||||||
// https://codeday.me/jp/qa/20190424/690106.html
|
// https://codeday.me/jp/qa/20190424/690106.html
|
||||||
style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
|
style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
|
||||||
}, genEl(ast, props.rootScale ?? 1));
|
}, genEl(rootAst, props.rootScale ?? 1));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
|
<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
|
||||||
|
|
||||||
<div class="query">
|
<div class="query">
|
||||||
<MkInput v-model="q" class="" :placeholder="i18n.ts.search">
|
<MkInput v-model="q" class="" :placeholder="i18n.ts.search" autocapitalize="off">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSpacer :contentMax="900">
|
<MkSpacer :contentMax="900">
|
||||||
<div class="ogwlenmc">
|
<div class="ogwlenmc">
|
||||||
<div v-if="tab === 'local'" class="local">
|
<div v-if="tab === 'local'" class="local">
|
||||||
<MkInput v-model="query" :debounce="true" type="search">
|
<MkInput v-model="query" :debounce="true" type="search" autocapitalize="off">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
<template #label>{{ i18n.ts.search }}</template>
|
<template #label>{{ i18n.ts.search }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-else-if="tab === 'remote'" class="remote">
|
<div v-else-if="tab === 'remote'" class="remote">
|
||||||
<FormSplit>
|
<FormSplit>
|
||||||
<MkInput v-model="queryRemote" :debounce="true" type="search">
|
<MkInput v-model="queryRemote" :debounce="true" type="search" autocapitalize="off">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
<template #label>{{ i18n.ts.search }}</template>
|
<template #label>{{ i18n.ts.search }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
|
<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
|
||||||
<MkInput v-model="name" pattern="[a-z0-9_]">
|
<MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off">
|
||||||
<template #label>{{ i18n.ts.name }}</template>
|
<template #label>{{ i18n.ts.name }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||||
<template #label>{{ i18n.ts.category }}</template>
|
<template #label>{{ i18n.ts.category }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="aliases">
|
<MkInput v-model="aliases" autocapitalize="off">
|
||||||
<template #label>{{ i18n.ts.tags }}</template>
|
<template #label>{{ i18n.ts.tags }}</template>
|
||||||
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
|
<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
const plugins = ref(ColdDeviceStorage.get('plugins'));
|
const plugins = ref(ColdDeviceStorage.get('plugins'));
|
||||||
|
|
||||||
function uninstall(plugin) {
|
async function uninstall(plugin) {
|
||||||
ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
|
ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
|
||||||
os.success();
|
await os.apiWithDialog('i/revoke-token', {
|
||||||
|
token: plugin.token,
|
||||||
|
});
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
unisonReload();
|
unisonReload();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ const pagination = {
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withRenotes: include.value === 'all',
|
withRenotes: include.value === 'all',
|
||||||
withReplies: include.value === 'all' || include.value === 'files',
|
withReplies: include.value === 'all',
|
||||||
withChannelNotes: include.value === 'all',
|
withChannelNotes: include.value === 'all',
|
||||||
withFiles: include.value === 'files',
|
withFiles: include.value === 'files',
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { $i } from '@/account.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { customEmojis } from '@/custom-emojis.js';
|
import { customEmojis } from '@/custom-emojis.js';
|
||||||
import { url, lang } from '@/config.js';
|
import { url, lang } from '@/config.js';
|
||||||
|
import { nyaize } from '@/scripts/nyaize.js';
|
||||||
|
|
||||||
export function createAiScriptEnv(opts) {
|
export function createAiScriptEnv(opts) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -71,5 +72,9 @@ export function createAiScriptEnv(opts) {
|
||||||
'Mk:url': values.FN_NATIVE(() => {
|
'Mk:url': values.FN_NATIVE(() => {
|
||||||
return values.STR(window.location.href);
|
return values.STR(window.location.href);
|
||||||
}),
|
}),
|
||||||
|
'Mk:nyaize': values.FN_NATIVE(([text]) => {
|
||||||
|
utils.assertString(text);
|
||||||
|
return values.STR(nyaize(text.value));
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,41 @@
|
||||||
import { lang } from '@/config.js';
|
import { lang } from '@/config.js';
|
||||||
|
|
||||||
export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
|
export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
|
||||||
export const dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
|
|
||||||
year: 'numeric',
|
let _dateTimeFormat: Intl.DateTimeFormat;
|
||||||
month: 'numeric',
|
try {
|
||||||
day: 'numeric',
|
_dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
|
||||||
hour: 'numeric',
|
year: 'numeric',
|
||||||
minute: 'numeric',
|
month: 'numeric',
|
||||||
second: 'numeric',
|
day: 'numeric',
|
||||||
});
|
hour: 'numeric',
|
||||||
export const numberFormat = new Intl.NumberFormat(versatileLang);
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
if (_DEV_) console.log('[Intl] Fallback to en-US');
|
||||||
|
|
||||||
|
// Fallback to en-US
|
||||||
|
_dateTimeFormat = new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export const dateTimeFormat = _dateTimeFormat;
|
||||||
|
|
||||||
|
let _numberFormat: Intl.NumberFormat;
|
||||||
|
try {
|
||||||
|
_numberFormat = new Intl.NumberFormat(versatileLang);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
if (_DEV_) console.log('[Intl] Fallback to en-US');
|
||||||
|
|
||||||
|
// Fallback to en-US
|
||||||
|
_numberFormat = new Intl.NumberFormat('en-US');
|
||||||
|
}
|
||||||
|
export const numberFormat = _numberFormat;
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
"@microsoft/api-extractor": "7.38.0",
|
"@microsoft/api-extractor": "7.38.0",
|
||||||
"@swc/jest": "0.2.29",
|
"@swc/jest": "0.2.29",
|
||||||
"@types/jest": "29.5.6",
|
"@types/jest": "29.5.6",
|
||||||
"@types/node": "20.8.7",
|
"@types/node": "20.8.9",
|
||||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
"eslint": "8.52.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",
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/cli": "0.1.62",
|
"@swc/cli": "0.1.62",
|
||||||
"@swc/core": "1.3.94",
|
"@swc/core": "1.3.95",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"reconnecting-websocket": "4.4.0"
|
"reconnecting-websocket": "4.4.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "6.8.0",
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||||
"eslint": "8.52.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.29.0",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
|
|
|
||||||
945
pnpm-lock.yaml
945
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue