Merge upstream (Misskey.io)
This commit is contained in:
commit
8bd86ce456
42 changed files with 564 additions and 71 deletions
2
.github/workflows/api-misskey-js.yml
vendored
2
.github/workflows/api-misskey-js.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "FORMATTED_BRANCH_NAME=$(echo ${{ github.ref_name }} | sed -e 's/\//-/g' )" >> $GITHUB_ENV
|
echo "FORMATTED_BRANCH_NAME=$(echo ${{ github.ref_name }} | sed -e 's/\//-/g' )" >> $GITHUB_ENV
|
||||||
- name: Build and Push to GitHub Container Registry
|
- name: Build and Push to GitHub Container Registry
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
|
|
2
.github/workflows/dockle.yml
vendored
2
.github/workflows/dockle.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Build an image from Dockerfile
|
- name: Build an image from Dockerfile
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
|
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -55,7 +55,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -80,7 +80,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
4
.github/workflows/test-backend.yml
vendored
4
.github/workflows/test-backend.yml
vendored
|
@ -47,7 +47,7 @@ jobs:
|
||||||
- name: Install FFmpeg
|
- name: Install FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -97,7 +97,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-frontend.yml
vendored
2
.github/workflows/test-frontend.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node-version }}
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
2
.github/workflows/validate-api-json.yml
vendored
2
.github/workflows/validate-api-json.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
8
locales/index.d.ts
vendored
8
locales/index.d.ts
vendored
|
@ -5359,6 +5359,10 @@ export interface Locale extends ILocale {
|
||||||
* 初期設定をあとでやり直しますか?
|
* 初期設定をあとでやり直しますか?
|
||||||
*/
|
*/
|
||||||
"laterAreYouSure": string;
|
"laterAreYouSure": string;
|
||||||
|
/**
|
||||||
|
* Botアカウントは管理者を必ず記載する必要があります。以下から管理者のアカウントを選択してください。
|
||||||
|
*/
|
||||||
|
"mustBeSetBotOwner": string;
|
||||||
};
|
};
|
||||||
"_initialTutorial": {
|
"_initialTutorial": {
|
||||||
/**
|
/**
|
||||||
|
@ -6868,6 +6872,10 @@ export interface Locale extends ILocale {
|
||||||
* サウンド設定でドライブのファイルを利用
|
* サウンド設定でドライブのファイルを利用
|
||||||
*/
|
*/
|
||||||
"canUseDriveFileInSoundSettings": string;
|
"canUseDriveFileInSoundSettings": string;
|
||||||
|
/**
|
||||||
|
* リアクションの利用
|
||||||
|
*/
|
||||||
|
"canUseReaction": string;
|
||||||
/**
|
/**
|
||||||
* アイコンデコレーションの最大取付個数
|
* アイコンデコレーションの最大取付個数
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1344,6 +1344,7 @@ _initialAccountSetting:
|
||||||
startTutorial: "チュートリアルを開始"
|
startTutorial: "チュートリアルを開始"
|
||||||
skipAreYouSure: "初期設定をスキップしますか?"
|
skipAreYouSure: "初期設定をスキップしますか?"
|
||||||
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
||||||
|
mustBeSetBotOwner: "Botアカウントは管理者を必ず記載する必要があります。以下から管理者のアカウントを選択してください。"
|
||||||
|
|
||||||
_initialTutorial:
|
_initialTutorial:
|
||||||
launchTutorial: "チュートリアルを見る"
|
launchTutorial: "チュートリアルを見る"
|
||||||
|
@ -1774,6 +1775,7 @@ _role:
|
||||||
canSearchNotes: "ノート検索の利用"
|
canSearchNotes: "ノート検索の利用"
|
||||||
canUseTranslator: "翻訳機能の利用"
|
canUseTranslator: "翻訳機能の利用"
|
||||||
canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用"
|
canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用"
|
||||||
|
canUseReaction: "リアクションの利用"
|
||||||
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||||
_condition:
|
_condition:
|
||||||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||||
|
|
|
@ -2226,6 +2226,7 @@ _instanceCharts:
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "首頁"
|
home: "首頁"
|
||||||
local: "本地"
|
local: "本地"
|
||||||
|
media: "媒體"
|
||||||
social: "社交"
|
social: "社交"
|
||||||
global: "公開"
|
global: "公開"
|
||||||
_play:
|
_play:
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"@bull-board/fastify": "5.18.1",
|
"@bull-board/fastify": "5.18.1",
|
||||||
"@bull-board/ui": "5.18.1",
|
"@bull-board/ui": "5.18.1",
|
||||||
"@discordapp/twemoji": "15.0.3",
|
"@discordapp/twemoji": "15.0.3",
|
||||||
|
"@elastic/elasticsearch": "^8.14.0",
|
||||||
"@fastify/accepts": "4.3.0",
|
"@fastify/accepts": "4.3.0",
|
||||||
"@fastify/cookie": "9.3.1",
|
"@fastify/cookie": "9.3.1",
|
||||||
"@fastify/cors": "9.0.1",
|
"@fastify/cors": "9.0.1",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Global, Inject, Module } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { MeiliSearch } from 'meilisearch';
|
import { MeiliSearch } from 'meilisearch';
|
||||||
|
import { Client as ElasticSearch } from '@elastic/elasticsearch';
|
||||||
import { DI } from './di-symbols.js';
|
import { DI } from './di-symbols.js';
|
||||||
import { Config, loadConfig } from './config.js';
|
import { Config, loadConfig } from './config.js';
|
||||||
import { createPostgresDataSource } from './postgres.js';
|
import { createPostgresDataSource } from './postgres.js';
|
||||||
|
@ -44,6 +45,30 @@ const $meilisearch: Provider = {
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $elasticsearch: Provider = {
|
||||||
|
provide: DI.elasticsearch,
|
||||||
|
useFactory: (config: Config) => {
|
||||||
|
if (config.elasticsearch) {
|
||||||
|
return new ElasticSearch({
|
||||||
|
nodes: {
|
||||||
|
url: new URL(`${config.elasticsearch.ssl ? 'https' : 'http'}://${config.elasticsearch.host}:${config.elasticsearch.port}`),
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: config.elasticsearch.rejectUnauthorized,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
auth: (config.elasticsearch.user && config.elasticsearch.pass) ? {
|
||||||
|
username: config.elasticsearch.user,
|
||||||
|
password: config.elasticsearch.pass,
|
||||||
|
} : undefined,
|
||||||
|
pingTimeout: 30000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: [DI.config],
|
||||||
|
};
|
||||||
|
|
||||||
const $redis: Provider = {
|
const $redis: Provider = {
|
||||||
provide: DI.redis,
|
provide: DI.redis,
|
||||||
useFactory: (config: Config) => {
|
useFactory: (config: Config) => {
|
||||||
|
@ -160,8 +185,8 @@ const $redisForTimelines: Provider = {
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RepositoryModule],
|
imports: [RepositoryModule],
|
||||||
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
|
providers: [$config, $db, $meilisearch, $elasticsearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
|
||||||
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
|
exports: [$config, $db, $meilisearch, $elasticsearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
|
||||||
})
|
})
|
||||||
export class GlobalModule implements OnApplicationShutdown {
|
export class GlobalModule implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -66,6 +66,16 @@ type Source = {
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
elasticsearch?: {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
user: string;
|
||||||
|
pass: string;
|
||||||
|
ssl?: boolean;
|
||||||
|
rejectUnauthorized?: boolean;
|
||||||
|
index: string;
|
||||||
|
};
|
||||||
|
|
||||||
skebStatus?: {
|
skebStatus?: {
|
||||||
method: string;
|
method: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
|
@ -149,6 +159,15 @@ export type Config = {
|
||||||
index: string;
|
index: string;
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
elasticsearch: {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
user: string;
|
||||||
|
pass: string;
|
||||||
|
ssl?: boolean;
|
||||||
|
rejectUnauthorized?: boolean;
|
||||||
|
index: string;
|
||||||
|
} | undefined;
|
||||||
skebStatus: {
|
skebStatus: {
|
||||||
method: string;
|
method: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
|
@ -272,6 +291,7 @@ export function loadConfig(): Config {
|
||||||
dbReplications: config.dbReplications,
|
dbReplications: config.dbReplications,
|
||||||
dbSlaves: config.dbSlaves,
|
dbSlaves: config.dbSlaves,
|
||||||
meilisearch: config.meilisearch,
|
meilisearch: config.meilisearch,
|
||||||
|
elasticsearch: config.elasticsearch,
|
||||||
redis,
|
redis,
|
||||||
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
|
||||||
redisForSystemQueue: config.redisForSystemQueue ? convertRedisOptions(config.redisForSystemQueue, host) : redisForJobQueue,
|
redisForSystemQueue: config.redisForSystemQueue ? convertRedisOptions(config.redisForSystemQueue, host) : redisForJobQueue,
|
||||||
|
|
|
@ -53,6 +53,7 @@ import { UserFollowingService } from './UserFollowingService.js';
|
||||||
import { UserKeypairService } from './UserKeypairService.js';
|
import { UserKeypairService } from './UserKeypairService.js';
|
||||||
import { UserListService } from './UserListService.js';
|
import { UserListService } from './UserListService.js';
|
||||||
import { UserMutingService } from './UserMutingService.js';
|
import { UserMutingService } from './UserMutingService.js';
|
||||||
|
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
|
||||||
import { UserSuspendService } from './UserSuspendService.js';
|
import { UserSuspendService } from './UserSuspendService.js';
|
||||||
import { UserAuthService } from './UserAuthService.js';
|
import { UserAuthService } from './UserAuthService.js';
|
||||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||||
|
@ -191,6 +192,7 @@ const $UserFollowingService: Provider = { provide: 'UserFollowingService', useEx
|
||||||
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
|
const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
|
||||||
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
||||||
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
|
const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting: UserMutingService };
|
||||||
|
const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService', useExisting: UserRenoteMutingService };
|
||||||
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
||||||
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
||||||
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
||||||
|
@ -332,6 +334,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
UserKeypairService,
|
UserKeypairService,
|
||||||
UserListService,
|
UserListService,
|
||||||
UserMutingService,
|
UserMutingService,
|
||||||
|
UserRenoteMutingService,
|
||||||
UserSuspendService,
|
UserSuspendService,
|
||||||
UserAuthService,
|
UserAuthService,
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
|
@ -467,6 +470,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$UserKeypairService,
|
$UserKeypairService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
$UserMutingService,
|
$UserMutingService,
|
||||||
|
$UserRenoteMutingService,
|
||||||
$UserSuspendService,
|
$UserSuspendService,
|
||||||
$UserAuthService,
|
$UserAuthService,
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
|
@ -603,6 +607,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
UserKeypairService,
|
UserKeypairService,
|
||||||
UserListService,
|
UserListService,
|
||||||
UserMutingService,
|
UserMutingService,
|
||||||
|
UserRenoteMutingService,
|
||||||
UserSuspendService,
|
UserSuspendService,
|
||||||
UserAuthService,
|
UserAuthService,
|
||||||
VideoProcessingService,
|
VideoProcessingService,
|
||||||
|
@ -737,6 +742,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$UserKeypairService,
|
$UserKeypairService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
$UserMutingService,
|
$UserMutingService,
|
||||||
|
$UserRenoteMutingService,
|
||||||
$UserSuspendService,
|
$UserSuspendService,
|
||||||
$UserAuthService,
|
$UserAuthService,
|
||||||
$VideoProcessingService,
|
$VideoProcessingService,
|
||||||
|
|
|
@ -116,10 +116,10 @@ export class ReactionService {
|
||||||
if (!await this.noteEntityService.isVisibleForMe(note, user.id)) {
|
if (!await this.noteEntityService.isVisibleForMe(note, user.id)) {
|
||||||
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
|
||||||
}
|
}
|
||||||
|
const policies = await this.roleService.getUserPolicies(user.id);
|
||||||
|
|
||||||
let reaction = _reaction ?? FALLBACK;
|
let reaction = _reaction ?? FALLBACK;
|
||||||
|
if (note.reactionAcceptance === 'likeOnly' || !policies.canUseReaction || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
||||||
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
|
|
||||||
reaction = '\u2764';
|
reaction = '\u2764';
|
||||||
} else if (_reaction) {
|
} else if (_reaction) {
|
||||||
const custom = reaction.match(isCustomEmojiRegexp);
|
const custom = reaction.match(isCustomEmojiRegexp);
|
||||||
|
|
|
@ -53,6 +53,7 @@ export type RolePolicies = {
|
||||||
canSearchNotes: boolean;
|
canSearchNotes: boolean;
|
||||||
canUseTranslator: boolean;
|
canUseTranslator: boolean;
|
||||||
canUseDriveFileInSoundSettings: boolean;
|
canUseDriveFileInSoundSettings: boolean;
|
||||||
|
canUseReaction: boolean;
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
driveCapacityMb: number;
|
driveCapacityMb: number;
|
||||||
alwaysMarkNsfw: boolean;
|
alwaysMarkNsfw: boolean;
|
||||||
|
@ -92,6 +93,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canSearchNotes: false,
|
canSearchNotes: false,
|
||||||
canUseTranslator: true,
|
canUseTranslator: true,
|
||||||
canUseDriveFileInSoundSettings: false,
|
canUseDriveFileInSoundSettings: false,
|
||||||
|
canUseReaction: true,
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
driveCapacityMb: 100,
|
driveCapacityMb: 100,
|
||||||
alwaysMarkNsfw: false,
|
alwaysMarkNsfw: false,
|
||||||
|
@ -405,6 +407,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
||||||
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
||||||
canUseDriveFileInSoundSettings: calc('canUseDriveFileInSoundSettings', vs => vs.some(v => v === true)),
|
canUseDriveFileInSoundSettings: calc('canUseDriveFileInSoundSettings', vs => vs.some(v => v === true)),
|
||||||
|
canUseReaction: calc('canUseReaction', vs => vs.some(v => v === true)),
|
||||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||||
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
|
||||||
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { MiNote } from '@/models/Note.js';
|
import { MiNote } from '@/models/Note.js';
|
||||||
import { MiUser } from '@/models/_.js';
|
import { MiUser } from '@/models/_.js';
|
||||||
import type { NotesRepository } from '@/models/_.js';
|
import type { NotesRepository } from '@/models/_.js';
|
||||||
|
@ -16,9 +17,10 @@ import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
import { UserEntityService } from './entities/UserEntityService.js';
|
import { UserEntityService } from './entities/UserEntityService.js';
|
||||||
import type { Index, MeiliSearch } from 'meilisearch';
|
import type { Index, MeiliSearch } from 'meilisearch';
|
||||||
// import { nonMaxSuppressionV3Impl } from '@tensorflow/tfjs-core/dist/backends/non_max_suppression_impl.js';
|
import type { Client as ElasticSearch } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
type K = string;
|
type K = string;
|
||||||
type V = string | number | boolean;
|
type V = string | number | boolean;
|
||||||
|
@ -67,6 +69,8 @@ function compileQuery(q: Q): string {
|
||||||
export class SearchService {
|
export class SearchService {
|
||||||
private readonly meilisearchIndexScope: 'local' | 'global' | string[] = 'local';
|
private readonly meilisearchIndexScope: 'local' | 'global' | string[] = 'local';
|
||||||
private meilisearchNoteIndex: Index | null = null;
|
private meilisearchNoteIndex: Index | null = null;
|
||||||
|
private elasticsearchNoteIndex: string | null = null;
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
|
@ -75,6 +79,9 @@ export class SearchService {
|
||||||
@Inject(DI.meilisearch)
|
@Inject(DI.meilisearch)
|
||||||
private meilisearch: MeiliSearch | null,
|
private meilisearch: MeiliSearch | null,
|
||||||
|
|
||||||
|
@Inject(DI.elasticsearch)
|
||||||
|
private elasticsearch: ElasticSearch | null,
|
||||||
|
|
||||||
@Inject(DI.notesRepository)
|
@Inject(DI.notesRepository)
|
||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
|
@ -82,9 +89,15 @@ export class SearchService {
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('note:search');
|
||||||
|
|
||||||
if (meilisearch) {
|
if (meilisearch) {
|
||||||
this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
|
this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
|
||||||
|
if (config.meilisearch?.scope) {
|
||||||
|
this.meilisearchIndexScope = config.meilisearch.scope;
|
||||||
|
}
|
||||||
/*this.meilisearchNoteIndex.updateSettings({
|
/*this.meilisearchNoteIndex.updateSettings({
|
||||||
searchableAttributes: [
|
searchableAttributes: [
|
||||||
'text',
|
'text',
|
||||||
|
@ -107,10 +120,52 @@ export class SearchService {
|
||||||
maxTotalHits: 10000,
|
maxTotalHits: 10000,
|
||||||
},
|
},
|
||||||
});*/
|
});*/
|
||||||
|
} else if (this.elasticsearch) {
|
||||||
|
this.elasticsearchNoteIndex = `${config.elasticsearch!.index}---notes`;
|
||||||
|
this.elasticsearch.indices.exists({
|
||||||
|
index: this.elasticsearchNoteIndex,
|
||||||
|
}).then((indexExists: boolean) => {
|
||||||
|
if (!indexExists) {
|
||||||
|
this.elasticsearch?.indices.create(
|
||||||
|
{
|
||||||
|
index: this.elasticsearchNoteIndex + `-${new Date().toISOString().slice(0, 7).replace(/-/g, '')}`,
|
||||||
|
mappings: {
|
||||||
|
properties: {
|
||||||
|
text: { type: 'text' },
|
||||||
|
cw: { type: 'text' },
|
||||||
|
createdAt: { type: 'long' },
|
||||||
|
userId: { type: 'keyword' },
|
||||||
|
userHost: { type: 'keyword' },
|
||||||
|
channelId: { type: 'keyword' },
|
||||||
|
tags: { type: 'keyword' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
index: {
|
||||||
|
analysis: {
|
||||||
|
tokenizer: {
|
||||||
|
kuromoji: {
|
||||||
|
type: 'kuromoji_tokenizer',
|
||||||
|
mode: 'search',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
analyzer: {
|
||||||
|
kuromoji_analyzer: {
|
||||||
|
type: 'custom',
|
||||||
|
tokenizer: 'kuromoji',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).catch((error: any) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}).catch((error: any) => {
|
||||||
if (config.meilisearch?.scope) {
|
this.logger.error('Error while checking if index exists', error);
|
||||||
this.meilisearchIndexScope = config.meilisearch.scope;
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +202,23 @@ export class SearchService {
|
||||||
}], {
|
}], {
|
||||||
primaryKey: 'id',
|
primaryKey: 'id',
|
||||||
});
|
});
|
||||||
|
} else if (this.elasticsearch) {
|
||||||
|
const body = {
|
||||||
|
createdAt: this.idService.parse(note.id).date.getTime(),
|
||||||
|
userId: note.userId,
|
||||||
|
userHost: note.userHost,
|
||||||
|
channelId: note.channelId,
|
||||||
|
cw: note.cw,
|
||||||
|
text: note.text,
|
||||||
|
tags: note.tags,
|
||||||
|
};
|
||||||
|
await this.elasticsearch.index({
|
||||||
|
index: this.elasticsearchNoteIndex + `-${new Date().toISOString().slice(0, 7).replace(/-/g, '')}` as string,
|
||||||
|
id: note.id,
|
||||||
|
body: body,
|
||||||
|
}).catch((error: any) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +227,7 @@ export class SearchService {
|
||||||
// if (!['home', 'public'].includes(note.visibility)) return;
|
// if (!['home', 'public'].includes(note.visibility)) return;
|
||||||
|
|
||||||
if (this.meilisearch) {
|
if (this.meilisearch) {
|
||||||
this.meilisearchNoteIndex!.deleteDocument(note.id);
|
this.meilisearchNoteIndex?.deleteDocument(note.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +296,67 @@ export class SearchService {
|
||||||
const dataFilter = data.filter(d => d.result);
|
const dataFilter = data.filter(d => d.result);
|
||||||
const filteredNotes = dataFilter.map(d => d.note);
|
const filteredNotes = dataFilter.map(d => d.note);
|
||||||
return filteredNotes.sort((a, b) => a.id > b.id ? -1 : 1);
|
return filteredNotes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||||
|
} else if (this.elasticsearch) {
|
||||||
|
const esFilter: any = {
|
||||||
|
bool: {
|
||||||
|
must: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination.untilId) esFilter.bool.must.push({ range: { createdAt: { lt: this.idService.parse(pagination.untilId).date.getTime() } } });
|
||||||
|
if (pagination.sinceId) esFilter.bool.must.push({ range: { createdAt: { gt: this.idService.parse(pagination.sinceId).date.getTime() } } });
|
||||||
|
if (opts.userId) esFilter.bool.must.push({ term: { userId: opts.userId } });
|
||||||
|
if (opts.channelId) esFilter.bool.must.push({ term: { channelId: opts.channelId } });
|
||||||
|
if (opts.host) {
|
||||||
|
if (opts.host === '.') {
|
||||||
|
esFilter.bool.must.push({ bool: { must_not: [{ exists: { field: 'userHost' } }] } });
|
||||||
|
} else {
|
||||||
|
esFilter.bool.must.push({ term: { userHost: opts.host } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q !== '') {
|
||||||
|
esFilter.bool.must.push({
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ wildcard: { 'text': { value: q } } },
|
||||||
|
{ simple_query_string: { fields: ['text'], 'query': q, default_operator: 'and' } },
|
||||||
|
{ wildcard: { 'cw': { value: q } } },
|
||||||
|
{ simple_query_string: { fields: ['cw'], 'query': q, default_operator: 'and' } },
|
||||||
|
],
|
||||||
|
minimum_should_match: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await (this.elasticsearch.search)({
|
||||||
|
index: this.elasticsearchNoteIndex + '*' as string,
|
||||||
|
body: {
|
||||||
|
query: esFilter,
|
||||||
|
sort: [{ createdAt: { order: 'desc' } }],
|
||||||
|
},
|
||||||
|
_source: ['id', 'createdAt'],
|
||||||
|
size: pagination.limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
const noteIds = res.hits.hits.map((hit: any) => hit._id);
|
||||||
|
if (noteIds.length === 0) return [];
|
||||||
|
const [
|
||||||
|
userIdsWhoMeMuting,
|
||||||
|
userIdsWhoBlockingMe,
|
||||||
|
] = me ? await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(me.id),
|
||||||
|
this.cacheService.userBlockedCache.fetch(me.id),
|
||||||
|
]) : [new Set<string>(), new Set<string>()];
|
||||||
|
const notes = (await this.notesRepository.findBy({
|
||||||
|
id: In(noteIds),
|
||||||
|
})).filter(note => {
|
||||||
|
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
|
||||||
|
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||||
} else {
|
} else {
|
||||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
|
||||||
|
|
||||||
|
|
51
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
51
packages/backend/src/core/UserRenoteMutingService.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||||
|
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserRenoteMutingService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.renoteMutingsRepository)
|
||||||
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
private cacheService: CacheService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async mute(user: MiUser, target: MiUser): Promise<void> {
|
||||||
|
await this.renoteMutingsRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
muterId: user.id,
|
||||||
|
muteeId: target.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.cacheService.renoteMutingsCache.refresh(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async unmute(mutings: MiRenoteMuting[]): Promise<void> {
|
||||||
|
if (mutings.length === 0) return;
|
||||||
|
|
||||||
|
await this.renoteMutingsRepository.delete({
|
||||||
|
id: In(mutings.map(m => m.id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const muterIds = [...new Set(mutings.map(m => m.muterId))];
|
||||||
|
for (const muterId of muterIds) {
|
||||||
|
await this.cacheService.renoteMutingsCache.refresh(muterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,4 +52,12 @@ export default class PerUserPvChart extends Chart<typeof schema> { // eslint-dis
|
||||||
'pv.visitor': 1,
|
'pv.visitor': 1,
|
||||||
}, user.id);
|
}, user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getChartUsers(span: 'hour' | 'day', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{
|
||||||
|
userId: string;
|
||||||
|
count: number;
|
||||||
|
}[]> {
|
||||||
|
return await this.getChartPv(span, amount, cursor, limit, offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,8 @@ export default abstract class Chart<T extends Schema> {
|
||||||
//private repositoryForDay: Repository<RawRecord<T>>;
|
//private repositoryForDay: Repository<RawRecord<T>>;
|
||||||
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number;}>;
|
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number;}>;
|
||||||
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number;}>;
|
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number;}>;
|
||||||
|
private repositoryUserPvForHour: Repository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
|
||||||
|
private repositoryUserPvForDay: Repository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
|
||||||
/**
|
/**
|
||||||
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
||||||
*/
|
*/
|
||||||
|
@ -273,6 +274,8 @@ export default abstract class Chart<T extends Schema> {
|
||||||
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
||||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
||||||
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day);
|
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day);
|
||||||
|
this.repositoryUserPvForHour = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(hour);
|
||||||
|
this.repositoryUserPvForDay = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(day);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -722,4 +725,51 @@ export default abstract class Chart<T extends Schema> {
|
||||||
}
|
}
|
||||||
return object as Unflatten<ChartResult<T>>;
|
return object as Unflatten<ChartResult<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getChartPv(span: 'hour' | 'day', amount: number, cursor: Date | null, limit: number, offset: number): Promise<
|
||||||
|
{
|
||||||
|
userId: string,
|
||||||
|
count: number,
|
||||||
|
}[]
|
||||||
|
> {
|
||||||
|
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate();
|
||||||
|
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never;
|
||||||
|
|
||||||
|
const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
|
||||||
|
|
||||||
|
const gt =
|
||||||
|
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
|
||||||
|
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
|
||||||
|
new Error('not happen') as never;
|
||||||
|
|
||||||
|
const repository =
|
||||||
|
span === 'hour' ? this.repositoryUserPvForHour :
|
||||||
|
span === 'day' ? this.repositoryUserPvForDay :
|
||||||
|
new Error('not happen') as never;
|
||||||
|
|
||||||
|
// ログ取得
|
||||||
|
const logs = await repository.createQueryBuilder()
|
||||||
|
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
|
||||||
|
.orderBy('___pv_visitor + ___upv_visitor + ___pv_user + ___upv_user', 'DESC')
|
||||||
|
.skip(offset)
|
||||||
|
.take(limit)
|
||||||
|
.getMany() as {
|
||||||
|
___pv_visitor: number,
|
||||||
|
___upv_visitor: number,
|
||||||
|
___pv_user: number,
|
||||||
|
___upv_user: number,
|
||||||
|
group: string,
|
||||||
|
}[];
|
||||||
|
const result = [] as {
|
||||||
|
userId: string,
|
||||||
|
count: number,
|
||||||
|
}[];
|
||||||
|
for (const row of logs) {
|
||||||
|
const userId = row.group;
|
||||||
|
const count = row.___pv_user + row.___upv_user + row.___pv_visitor + row.___upv_visitor;
|
||||||
|
result.push({ userId, count });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,13 +501,16 @@ export class UserEntityService implements OnModuleInit {
|
||||||
} : undefined) : undefined,
|
} : undefined) : undefined,
|
||||||
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||||
onlineStatus: this.getOnlineStatus(user),
|
onlineStatus: this.getOnlineStatus(user),
|
||||||
// パフォーマンス上の理由でローカルユーザーのみ
|
badgeRoles: this.roleService.getUserBadgeRoles(user.id).then((rs) => rs
|
||||||
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.sort((a, b) => b.displayOrder - a.displayOrder).map(r => ({
|
.filter((r) => r.isPublic || iAmModerator)
|
||||||
|
.sort((a, b) => b.displayOrder - a.displayOrder)
|
||||||
|
.map((r) => ({
|
||||||
name: r.name,
|
name: r.name,
|
||||||
iconUrl: r.iconUrl,
|
iconUrl: r.iconUrl,
|
||||||
displayOrder: r.displayOrder,
|
displayOrder: r.displayOrder,
|
||||||
behavior: r.badgeBehavior ?? undefined,
|
behavior: r.badgeBehavior ?? undefined,
|
||||||
}))) : undefined,
|
})),
|
||||||
|
),
|
||||||
|
|
||||||
...(isDetailed ? {
|
...(isDetailed ? {
|
||||||
url: profile?.url,
|
url: profile?.url,
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const DI = {
|
||||||
config: Symbol('config'),
|
config: Symbol('config'),
|
||||||
db: Symbol('db'),
|
db: Symbol('db'),
|
||||||
meilisearch: Symbol('meilisearch'),
|
meilisearch: Symbol('meilisearch'),
|
||||||
|
elasticsearch: Symbol('elasticsearch'),
|
||||||
redis: Symbol('redis'),
|
redis: Symbol('redis'),
|
||||||
redisForPub: Symbol('redisForPub'),
|
redisForPub: Symbol('redisForPub'),
|
||||||
redisForSub: Symbol('redisForSub'),
|
redisForSub: Symbol('redisForSub'),
|
||||||
|
|
|
@ -248,6 +248,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canUseReaction: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
canHideAds: {
|
canHideAds: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -6,11 +6,10 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
|
||||||
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { GetterService } from '@/server/api/GetterService.js';
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { UserRenoteMutingService } from '@/core/UserRenoteMutingService.js';
|
||||||
|
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -64,7 +63,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private idService: IdService,
|
private userRenoteMutingService: UserRenoteMutingService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const muter = me;
|
const muter = me;
|
||||||
|
@ -81,21 +80,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if already muting
|
// Check if already muting
|
||||||
const exist = await this.renoteMutingsRepository.findOneBy({
|
const exist = await this.renoteMutingsRepository.exists({
|
||||||
|
where: {
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist != null) {
|
if (exist) {
|
||||||
throw new ApiError(meta.errors.alreadyMuting);
|
throw new ApiError(meta.errors.alreadyMuting);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mute
|
// Create mute
|
||||||
await this.renoteMutingsRepository.insert({
|
await this.userRenoteMutingService.mute(muter, mutee);
|
||||||
id: this.idService.gen(),
|
|
||||||
muterId: muter.id,
|
|
||||||
muteeId: mutee.id,
|
|
||||||
} as MiRenoteMuting);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['role', 'users'],
|
tags: ['role', 'users'],
|
||||||
|
@ -92,10 +93,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return await Promise.all(assigns.map(async assign => ({
|
return (await Promise.allSettled(assigns.map(async assign => ({
|
||||||
id: assign.id,
|
id: assign.id,
|
||||||
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
||||||
})));
|
}))))
|
||||||
|
.filter((result): result is PromiseFulfilledResult<{ id: string; user: Packed<'UserDetailed'> }> => result.status === 'fulfilled')
|
||||||
|
.map(result => result.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -31,7 +32,7 @@ export const paramDef = {
|
||||||
properties: {
|
properties: {
|
||||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
offset: { type: 'integer', default: 0 },
|
offset: { type: 'integer', default: 0 },
|
||||||
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
|
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt', '+pv', '-pv'] },
|
||||||
state: { type: 'string', enum: ['all', 'alive'], default: 'all' },
|
state: { type: 'string', enum: ['all', 'alive'], default: 'all' },
|
||||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
|
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
|
||||||
hostname: {
|
hostname: {
|
||||||
|
@ -49,6 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
private perUserPvChart: PerUserPvChart,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
|
@ -70,7 +72,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (ps.hostname) {
|
if (ps.hostname) {
|
||||||
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
|
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
|
||||||
}
|
}
|
||||||
|
const chartUsers: { userId: string; count: number; }[] = [];
|
||||||
|
if (ps.sort?.endsWith('pv')) {
|
||||||
|
await this.perUserPvChart.getChartUsers('hour', 0, null, ps.limit, ps.offset).then(users => {
|
||||||
|
chartUsers.push(...users);
|
||||||
|
});
|
||||||
|
}
|
||||||
switch (ps.sort) {
|
switch (ps.sort) {
|
||||||
case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
|
case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
|
||||||
case '-follower': query.orderBy('user.followersCount', 'ASC'); break;
|
case '-follower': query.orderBy('user.followersCount', 'ASC'); break;
|
||||||
|
@ -78,6 +85,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
case '-createdAt': query.orderBy('user.id', 'ASC'); break;
|
case '-createdAt': query.orderBy('user.id', 'ASC'); break;
|
||||||
case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break;
|
case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break;
|
||||||
case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break;
|
case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break;
|
||||||
|
case '+pv':
|
||||||
|
if (chartUsers.length > 0) {
|
||||||
|
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '-pv':
|
||||||
|
if (chartUsers.length > 0) {
|
||||||
|
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
|
||||||
|
}
|
||||||
|
break;
|
||||||
default: query.orderBy('user.id', 'ASC'); break;
|
default: query.orderBy('user.id', 'ASC'); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +105,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
query.offset(ps.offset);
|
query.offset(ps.offset);
|
||||||
|
|
||||||
const users = await query.getMany();
|
const users = await query.getMany();
|
||||||
|
if (ps.sort === '+pv') {
|
||||||
|
users.sort((a, b) => {
|
||||||
|
const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
|
||||||
|
const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
|
||||||
|
return bPv - aPv;
|
||||||
|
});
|
||||||
|
} else if (ps.sort === '-pv') {
|
||||||
|
users.sort((a, b) => {
|
||||||
|
const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
|
||||||
|
const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
|
||||||
|
return aPv - bPv;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
|
return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { GetterService } from '@/server/api/GetterService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
|
@ -131,10 +132,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
|
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
|
||||||
|
|
||||||
// Make replies object (includes weights)
|
// Make replies object (includes weights)
|
||||||
const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
|
const repliesObj = (await Promise.allSettled(topRepliedUsers.map(async (user) => ({
|
||||||
user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
|
user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
|
||||||
weight: repliedUsers[user] / peak,
|
weight: repliedUsers[user] / peak,
|
||||||
})));
|
}))))
|
||||||
|
.filter((result): result is PromiseFulfilledResult<{ user: Packed<'UserDetailed'>; weight: number }> => result.status === 'fulfilled')
|
||||||
|
.map(result => result.value);
|
||||||
|
|
||||||
return repliesObj;
|
return repliesObj;
|
||||||
});
|
});
|
||||||
|
|
|
@ -233,7 +233,7 @@ describe('ユーザー', () => {
|
||||||
rolePublic = await role(root, { isPublic: true, name: 'Public Role' });
|
rolePublic = await role(root, { isPublic: true, name: 'Public Role' });
|
||||||
await api('admin/roles/assign', { userId: userRolePublic.id, roleId: rolePublic.id }, root);
|
await api('admin/roles/assign', { userId: userRolePublic.id, roleId: rolePublic.id }, root);
|
||||||
userRoleBadge = await signup({ username: 'userRoleBadge' });
|
userRoleBadge = await signup({ username: 'userRoleBadge' });
|
||||||
roleBadge = await role(root, { asBadge: true, name: 'Badge Role' });
|
roleBadge = await role(root, { asBadge: true, name: 'Badge Role', isPublic: true });
|
||||||
await api('admin/roles/assign', { userId: userRoleBadge.id, roleId: roleBadge.id }, root);
|
await api('admin/roles/assign', { userId: userRoleBadge.id, roleId: roleBadge.id }, root);
|
||||||
userSilenced = await signup({ username: 'userSilenced' });
|
userSilenced = await signup({ username: 'userSilenced' });
|
||||||
await post(userSilenced, { text: 'test' });
|
await post(userSilenced, { text: 'test' });
|
||||||
|
@ -667,7 +667,16 @@ describe('ユーザー', () => {
|
||||||
displayOrder: roleBadge.displayOrder,
|
displayOrder: roleBadge.displayOrder,
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
assert.deepStrictEqual(response.roles, []); // バッヂだからといってrolesが取れるとは限らない
|
assert.deepStrictEqual(response.roles, [{
|
||||||
|
id: roleBadge.id,
|
||||||
|
name: roleBadge.name,
|
||||||
|
color: roleBadge.color,
|
||||||
|
iconUrl: roleBadge.iconUrl,
|
||||||
|
description: roleBadge.description,
|
||||||
|
isModerator: roleBadge.isModerator,
|
||||||
|
isAdministrator: roleBadge.isAdministrator,
|
||||||
|
displayOrder: roleBadge.displayOrder,
|
||||||
|
}]);
|
||||||
});
|
});
|
||||||
test('をID指定のリスト形式で取得することができる(空)', async () => {
|
test('をID指定のリスト形式で取得することができる(空)', async () => {
|
||||||
const parameters = { userIds: [] };
|
const parameters = { userIds: [] };
|
||||||
|
|
|
@ -119,9 +119,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-ban"></i>
|
<i class="ti ti-ban"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
||||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
|
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction" class="ti ti-heart"></i>
|
||||||
<i v-else class="ti ti-plus"></i>
|
<i v-else class="ti ti-plus"></i>
|
||||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||||
</button>
|
</button>
|
||||||
|
@ -160,6 +160,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
|
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkButton from './MkButton.vue';
|
||||||
import MkNoteSub from '@/components/MkNoteSub.vue';
|
import MkNoteSub from '@/components/MkNoteSub.vue';
|
||||||
import MkNoteHeader from '@/components/MkNoteHeader.vue';
|
import MkNoteHeader from '@/components/MkNoteHeader.vue';
|
||||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
|
@ -168,7 +169,6 @@ import MkReactionsViewerDetails from '@/components/MkReactionsViewer.details.vue
|
||||||
import MkMediaList from '@/components/MkMediaList.vue';
|
import MkMediaList from '@/components/MkMediaList.vue';
|
||||||
import MkCwButton from '@/components/MkCwButton.vue';
|
import MkCwButton from '@/components/MkCwButton.vue';
|
||||||
import MkPoll from '@/components/MkPoll.vue';
|
import MkPoll from '@/components/MkPoll.vue';
|
||||||
import MkButton from './MkButton.vue';
|
|
||||||
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
|
@ -385,7 +385,7 @@ function reply(viaKeyboard = false): void {
|
||||||
function react(viaKeyboard = false): void {
|
function react(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
if (props.mock) {
|
if (props.mock) {
|
||||||
|
|
|
@ -127,9 +127,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-ban"></i>
|
<i class="ti ti-ban"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
||||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
|
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction" class="ti ti-heart"></i>
|
||||||
<i v-else class="ti ti-plus"></i>
|
<i v-else class="ti ti-plus"></i>
|
||||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||||
</button>
|
</button>
|
||||||
|
@ -406,7 +406,7 @@ function reply(viaKeyboard = false): void {
|
||||||
function react(viaKeyboard = false): void {
|
function react(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
|
|
|
@ -53,7 +53,7 @@ const popularUsers: Paging = {
|
||||||
params: {
|
params: {
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+follower',
|
sort: '+pv',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -25,12 +25,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts._profile.description }}</template>
|
<template #label>{{ i18n.ts._profile.description }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
|
<MkSwitch v-model="useAsBot">
|
||||||
|
<template #label>{{ i18n.ts.flagAsBot }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<div v-if="useAsBot" class="_gaps_m">
|
||||||
|
<div>
|
||||||
|
<MkInfo>{{ i18n.ts._initialAccountSetting.mustBeSetBotOwner }}</MkInfo>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MkButton @click="selectBotOwner">{{ i18n.ts.selectUser }}</MkButton>
|
||||||
|
<MkUserCardMini v-if="botOwner" :user="botOwner"></MkUserCardMini>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MkInfo>{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}</MkInfo>
|
<MkInfo>{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}</MkInfo>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -45,6 +62,12 @@ const $i = signinRequired();
|
||||||
|
|
||||||
const name = ref($i.name ?? '');
|
const name = ref($i.name ?? '');
|
||||||
const description = ref($i.description ?? '');
|
const description = ref($i.description ?? '');
|
||||||
|
const useAsBot = ref($i.isBot ?? false);
|
||||||
|
const botOwner = ref<Misskey.entities.UserDetailed | null>(null);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'nextButtonEnabled', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
watch(name, () => {
|
watch(name, () => {
|
||||||
os.apiWithDialog('i/update', {
|
os.apiWithDialog('i/update', {
|
||||||
|
@ -62,6 +85,12 @@ watch(description, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(useAsBot, () => {
|
||||||
|
watchBotSettings();
|
||||||
|
os.apiWithDialog('i/update', { isBot: useAsBot.value });
|
||||||
|
});
|
||||||
|
watch(botOwner, watchBotSettings);
|
||||||
|
|
||||||
function setAvatar(ev) {
|
function setAvatar(ev) {
|
||||||
chooseFileFromPc(false).then(async (files) => {
|
chooseFileFromPc(false).then(async (files) => {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
|
@ -88,6 +117,25 @@ function setAvatar(ev) {
|
||||||
$i.avatarUrl = i.avatarUrl;
|
$i.avatarUrl = i.avatarUrl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectBotOwner() {
|
||||||
|
os.selectUser({ includeSelf: false, localOnly: true }).then(_user => {
|
||||||
|
botOwner.value = _user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchBotSettings() {
|
||||||
|
if (useAsBot.value) {
|
||||||
|
if (botOwner.value != null) {
|
||||||
|
description.value = (description.value + '\n管理者: @' + botOwner.value.username).trim();
|
||||||
|
emit('nextButtonEnabled', true);
|
||||||
|
} else {
|
||||||
|
emit('nextButtonEnabled', false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit('nextButtonEnabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:width="500"
|
:width="500"
|
||||||
:height="550"
|
:height="550"
|
||||||
data-cy-user-setup
|
data-cy-user-setup
|
||||||
@close="close(true)"
|
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template v-if="page === 1" #header><i class="ti ti-user-edit"></i> {{ i18n.ts._initialAccountSetting.profileSetting }}</template>
|
<template v-if="page === 1" #header><i class="ti ti-user-edit"></i> {{ i18n.ts._initialAccountSetting.profileSetting }}</template>
|
||||||
|
@ -48,12 +47,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div style="height: 100cqh; overflow: auto;">
|
<div style="height: 100cqh; overflow: auto;">
|
||||||
<div :class="$style.pageRoot">
|
<div :class="$style.pageRoot">
|
||||||
<MkSpacer :marginMin="20" :marginMax="28" :class="$style.pageMain">
|
<MkSpacer :marginMin="20" :marginMax="28" :class="$style.pageMain">
|
||||||
<XProfile/>
|
<XProfile :onNextButtonEnabled="(state) => page1NextButtonDisabled = !state"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<div :class="$style.pageFooter">
|
<div :class="$style.pageFooter">
|
||||||
<div class="_buttonsCenter">
|
<div class="_buttonsCenter">
|
||||||
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
<MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
<MkButton primary rounded gradate data-cy-user-setup-continue :disabled="page1NextButtonDisabled" @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,6 +150,8 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||||
const page = ref(defaultStore.state.accountSetupWizard);
|
const page = ref(defaultStore.state.accountSetupWizard);
|
||||||
|
|
||||||
|
const page1NextButtonDisabled = ref(false);
|
||||||
|
|
||||||
watch(page, () => {
|
watch(page, () => {
|
||||||
defaultStore.set('accountSetupWizard', page.value);
|
defaultStore.set('accountSetupWizard', page.value);
|
||||||
});
|
});
|
||||||
|
|
|
@ -92,6 +92,7 @@ export const ROLE_POLICIES = [
|
||||||
'canSearchNotes',
|
'canSearchNotes',
|
||||||
'canUseTranslator',
|
'canUseTranslator',
|
||||||
'canUseDriveFileInSoundSettings',
|
'canUseDriveFileInSoundSettings',
|
||||||
|
'canUseReaction',
|
||||||
'canHideAds',
|
'canHideAds',
|
||||||
'driveCapacityMb',
|
'driveCapacityMb',
|
||||||
'alwaysMarkNsfw',
|
'alwaysMarkNsfw',
|
||||||
|
|
|
@ -523,6 +523,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUseReaction, 'canUseReaction'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canUseReaction }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canUseReaction.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canUseReaction.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canUseReaction)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canUseReaction.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canUseReaction.value" :disabled="role.policies.canUseReaction.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canUseReaction.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
|
||||||
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
|
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
|
|
@ -98,7 +98,7 @@ const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
|
||||||
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+follower',
|
sort: '+pv',
|
||||||
} };
|
} };
|
||||||
const recentlyUpdatedUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
const recentlyUpdatedUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
|
|
|
@ -65,9 +65,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
|
<MkSwitch v-model="keepCw">{{ i18n.ts.keepCw }}</MkSwitch>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
|
<FormSection>
|
||||||
|
<template #label>{{ i18n.ts.hideSensitiveInformation }}</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkSwitch v-model="hideDirectMessages">
|
||||||
|
{{ i18n.ts.flagAsCat }}
|
||||||
|
<template #caption>{{ i18n.ts.flagAsCatDescription }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
<div class="_gaps_m">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -99,6 +112,7 @@ const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNot
|
||||||
const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
|
const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
|
||||||
const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
|
const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
|
||||||
const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
|
const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
|
||||||
|
const hideDirectMessages = computed(defaultStore.makeGetterSetter('hideDirectMessages'));
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
misskeyApi('i/update', {
|
misskeyApi('i/update', {
|
||||||
|
|
|
@ -80,6 +80,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
hideDirectMessages: {
|
||||||
|
where: 'deviceAccount',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
showFullAcct: {
|
showFullAcct: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -25482,7 +25482,7 @@ export type operations = {
|
||||||
/** @default 0 */
|
/** @default 0 */
|
||||||
offset?: number;
|
offset?: number;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt';
|
sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt' | '+pv' | '-pv';
|
||||||
/**
|
/**
|
||||||
* @default all
|
* @default all
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
|
|
49
pnpm-lock.yaml
generated
49
pnpm-lock.yaml
generated
|
@ -86,6 +86,9 @@ importers:
|
||||||
'@discordapp/twemoji':
|
'@discordapp/twemoji':
|
||||||
specifier: 15.0.3
|
specifier: 15.0.3
|
||||||
version: 15.0.3
|
version: 15.0.3
|
||||||
|
'@elastic/elasticsearch':
|
||||||
|
specifier: ^8.14.0
|
||||||
|
version: 8.14.0
|
||||||
'@fastify/accepts':
|
'@fastify/accepts':
|
||||||
specifier: 4.3.0
|
specifier: 4.3.0
|
||||||
version: 4.3.0
|
version: 4.3.0
|
||||||
|
@ -2199,6 +2202,14 @@ packages:
|
||||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
'@elastic/elasticsearch@8.14.0':
|
||||||
|
resolution: {integrity: sha512-MGrgCI4y+Ozssf5Q2IkVJlqt5bUMnKIICG2qxeOfrJNrVugMCBCAQypyesmSSocAtNm8IX3LxfJ3jQlFHmKe2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@elastic/transport@8.7.0':
|
||||||
|
resolution: {integrity: sha512-IqXT7a8DZPJtqP2qmX1I2QKmxYyN27kvSW4g6pInESE1SuGwZDp2FxHJ6W2kwmYOJwQdAt+2aWwzXO5jHo9l4A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@emnapi/runtime@1.2.0':
|
'@emnapi/runtime@1.2.0':
|
||||||
resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
|
resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
|
||||||
|
|
||||||
|
@ -2989,6 +3000,10 @@ packages:
|
||||||
'@open-draft/until@2.1.0':
|
'@open-draft/until@2.1.0':
|
||||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||||
|
|
||||||
|
'@opentelemetry/api@1.9.0':
|
||||||
|
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
'@peculiar/asn1-android@2.3.10':
|
'@peculiar/asn1-android@2.3.10':
|
||||||
resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==}
|
resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==}
|
||||||
|
|
||||||
|
@ -10410,6 +10425,10 @@ packages:
|
||||||
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
||||||
engines: {node: '>=14.0'}
|
engines: {node: '>=14.0'}
|
||||||
|
|
||||||
|
undici@6.19.2:
|
||||||
|
resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==}
|
||||||
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.0:
|
unicode-canonical-property-names-ecmascript@2.0.0:
|
||||||
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
@ -10689,6 +10708,9 @@ packages:
|
||||||
vue-component-type-helpers@2.0.19:
|
vue-component-type-helpers@2.0.19:
|
||||||
resolution: {integrity: sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==}
|
resolution: {integrity: sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==}
|
||||||
|
|
||||||
|
vue-component-type-helpers@2.0.26:
|
||||||
|
resolution: {integrity: sha512-sO9qQ8oC520SW6kqlls0iqDak53gsTVSrYylajgjmkt1c0vcgjsGSy1KzlDrbEx8pm02IEYhlUkU5hCYf8rwtg==}
|
||||||
|
|
||||||
vue-demi@0.14.7:
|
vue-demi@0.14.7:
|
||||||
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
|
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -12469,6 +12491,25 @@ snapshots:
|
||||||
|
|
||||||
'@discoveryjs/json-ext@0.5.7': {}
|
'@discoveryjs/json-ext@0.5.7': {}
|
||||||
|
|
||||||
|
'@elastic/elasticsearch@8.14.0':
|
||||||
|
dependencies:
|
||||||
|
'@elastic/transport': 8.7.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@elastic/transport@8.7.0':
|
||||||
|
dependencies:
|
||||||
|
'@opentelemetry/api': 1.9.0
|
||||||
|
debug: 4.3.4(supports-color@8.1.1)
|
||||||
|
hpagent: 1.2.0
|
||||||
|
ms: 2.1.3
|
||||||
|
secure-json-parse: 2.7.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
undici: 6.19.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@emnapi/runtime@1.2.0':
|
'@emnapi/runtime@1.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
|
@ -13346,6 +13387,8 @@ snapshots:
|
||||||
|
|
||||||
'@open-draft/until@2.1.0': {}
|
'@open-draft/until@2.1.0': {}
|
||||||
|
|
||||||
|
'@opentelemetry/api@1.9.0': {}
|
||||||
|
|
||||||
'@peculiar/asn1-android@2.3.10':
|
'@peculiar/asn1-android@2.3.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/asn1-schema': 2.3.8
|
'@peculiar/asn1-schema': 2.3.8
|
||||||
|
@ -14738,7 +14781,7 @@ snapshots:
|
||||||
ts-dedent: 2.2.0
|
ts-dedent: 2.2.0
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.4.15(typescript@5.4.5)
|
vue: 3.4.15(typescript@5.4.5)
|
||||||
vue-component-type-helpers: 2.0.19
|
vue-component-type-helpers: 2.0.26
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
- prettier
|
- prettier
|
||||||
|
@ -22388,6 +22431,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/busboy': 2.1.1
|
'@fastify/busboy': 2.1.1
|
||||||
|
|
||||||
|
undici@6.19.2: {}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
||||||
|
|
||||||
unicode-match-property-ecmascript@2.0.0:
|
unicode-match-property-ecmascript@2.0.0:
|
||||||
|
@ -22667,6 +22712,8 @@ snapshots:
|
||||||
|
|
||||||
vue-component-type-helpers@2.0.19: {}
|
vue-component-type-helpers@2.0.19: {}
|
||||||
|
|
||||||
|
vue-component-type-helpers@2.0.26: {}
|
||||||
|
|
||||||
vue-demi@0.14.7(vue@3.4.15(typescript@5.4.5)):
|
vue-demi@0.14.7(vue@3.4.15(typescript@5.4.5)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.4.15(typescript@5.4.5)
|
vue: 3.4.15(typescript@5.4.5)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue