Merge branch 'develop' into enh-12733
This commit is contained in:
commit
c122c4553c
|
@ -6,8 +6,12 @@
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.test.ts": "typescript"
|
"*.test.ts": "typescript"
|
||||||
},
|
},
|
||||||
"jest.jestCommandLine": "pnpm run jest",
|
|
||||||
"jest.runMode": "on-demand",
|
"jest.runMode": "on-demand",
|
||||||
|
"jest.virtualFolders": [
|
||||||
|
{ "name": "backend unit", "jestCommandLine": "pnpm -F backend run test" },
|
||||||
|
{ "name": "backend e2e", "jestCommandLine": "pnpm -F backend run test:e2e"},
|
||||||
|
{ "name": "misskey-js", "jestCommandLine": "pnpm -F misskey-js run jest" }
|
||||||
|
],
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": "explicit"
|
"source.fixAll": "explicit"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
## 2025.6.4
|
## 2025.7.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: ノートの下書き機能
|
- Feat: ノートの下書き機能
|
||||||
- Feat: クリップ内でノートを検索できるように
|
- Feat: クリップ内でノートを検索できるように
|
||||||
|
- Feat: Playを検索できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: モデログを検索できるように
|
- Feat: モデログを検索できるように
|
||||||
- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように
|
- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように
|
||||||
- Enhance: ファイルアップロード前にキャプション設定を行えるように
|
- Enhance: ファイルアップロード前にキャプション設定を行えるように
|
||||||
- Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように
|
- Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように
|
||||||
|
- Enhance: 投稿フォームにファイルをペースト/ドロップした際のUXを改善
|
||||||
- Enhance: ページネーション(一覧表示)の並び順を逆にできるように
|
- Enhance: ページネーション(一覧表示)の並び順を逆にできるように
|
||||||
- Enhance: ページネーション(一覧表示)の基準日時を指定できるように
|
- Enhance: ページネーション(一覧表示)の基準日時を指定できるように
|
||||||
- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正
|
- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正
|
||||||
|
- Fix: プラグインをアンインストールしてもセーブデータが残る問題を修正
|
||||||
|
- Fix: 数時間後Misskeyのタブに戻った際に、タブがスロットリングされている間の更新アニメーションを延々見せ続けられる問題を修正
|
||||||
|
- Fix: 非ログイン時のハイライトノートの画像がCWの有無を考慮せず表示される問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に
|
- Enhance: sinceId/untilIdが指定可能なエンドポイントにおいて、sinceDate/untilDateも指定可能に
|
||||||
|
|
|
@ -12270,9 +12270,9 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"cannotCreateDraftAnymore": string;
|
"cannotCreateDraftAnymore": string;
|
||||||
/**
|
/**
|
||||||
* リノートの下書きは作成できません。
|
* この内容では下書きを作成できません。
|
||||||
*/
|
*/
|
||||||
"cannotCreateDraftOfRenote": string;
|
"cannotCreateDraft": string;
|
||||||
/**
|
/**
|
||||||
* 下書きを削除
|
* 下書きを削除
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3288,7 +3288,7 @@ drafts: "下書き"
|
||||||
_drafts:
|
_drafts:
|
||||||
select: "下書きを選択"
|
select: "下書きを選択"
|
||||||
cannotCreateDraftAnymore: "下書きの作成可能数を超えています。"
|
cannotCreateDraftAnymore: "下書きの作成可能数を超えています。"
|
||||||
cannotCreateDraftOfRenote: "リノートの下書きは作成できません。"
|
cannotCreateDraft: "この内容では下書きを作成できません。"
|
||||||
delete: "下書きを削除"
|
delete: "下書きを削除"
|
||||||
deleteAreYouSure: "下書きを削除しますか?"
|
deleteAreYouSure: "下書きを削除しますか?"
|
||||||
noDrafts: "下書きはありません"
|
noDrafts: "下書きはありません"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.6.4-alpha.3",
|
"version": "2025.7.0-beta.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
export class FixAvatarUrl1750729939704 {
|
||||||
|
name = 'FixAvatarUrl1750729939704'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(1024)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "avatarUrl" TYPE character varying(512)`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Brackets } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { type FlashsRepository } from '@/models/_.js';
|
import { type FlashLikesRepository, MiUser, type FlashsRepository } from '@/models/_.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MisskeyPlay関係のService
|
* MisskeyPlay関係のService
|
||||||
|
@ -15,6 +18,11 @@ export class FlashService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.flashsRepository)
|
@Inject(DI.flashsRepository)
|
||||||
private flashRepository: FlashsRepository,
|
private flashRepository: FlashsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.flashLikesRepository)
|
||||||
|
private flashLikesRepository: FlashLikesRepository,
|
||||||
|
|
||||||
|
private queryService: QueryService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,4 +45,43 @@ export class FlashService {
|
||||||
|
|
||||||
return await builder.getMany();
|
return await builder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async myLikes(meId: MiUser['id'], opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number, search?: string | null }) {
|
||||||
|
const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate)
|
||||||
|
.andWhere('like.userId = :meId', { meId })
|
||||||
|
.leftJoinAndSelect('like.flash', 'flash');
|
||||||
|
|
||||||
|
if (opts.search != null) {
|
||||||
|
for (const word of opts.search.trim().split(' ')) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const likes = await query
|
||||||
|
.limit(opts.limit)
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
return likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async search(searchQuery: string, opts: { sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number, limit?: number }) {
|
||||||
|
const query = this.queryService.makePaginationQuery(this.flashRepository.createQueryBuilder('flash'), opts.sinceId, opts.untilId, opts.sinceDate, opts.untilDate)
|
||||||
|
.andWhere('flash.visibility = \'public\'');
|
||||||
|
|
||||||
|
for (const word of searchQuery.trim().split(' ')) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('flash.title ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
qb.orWhere('flash.summary ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query
|
||||||
|
.limit(opts.limit)
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ export class MiUser {
|
||||||
|
|
||||||
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
|
// avatarId が null になったとしてもこれが null でない可能性があるため、このフィールドを使うときは avatarId の non-null チェックをすること
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 512, nullable: true,
|
length: 1024, nullable: true,
|
||||||
})
|
})
|
||||||
public avatarUrl: string | null;
|
public avatarUrl: string | null;
|
||||||
|
|
||||||
|
|
|
@ -208,6 +208,7 @@ export * as 'flash/my-likes' from './endpoints/flash/my-likes.js';
|
||||||
export * as 'flash/show' from './endpoints/flash/show.js';
|
export * as 'flash/show' from './endpoints/flash/show.js';
|
||||||
export * as 'flash/unlike' from './endpoints/flash/unlike.js';
|
export * as 'flash/unlike' from './endpoints/flash/unlike.js';
|
||||||
export * as 'flash/update' from './endpoints/flash/update.js';
|
export * as 'flash/update' from './endpoints/flash/update.js';
|
||||||
|
export * as 'flash/search' from './endpoints/flash/search.js';
|
||||||
export * as 'following/create' from './endpoints/following/create.js';
|
export * as 'following/create' from './endpoints/following/create.js';
|
||||||
export * as 'following/delete' from './endpoints/following/delete.js';
|
export * as 'following/delete' from './endpoints/following/delete.js';
|
||||||
export * as 'following/invalidate' from './endpoints/following/invalidate.js';
|
export * as 'following/invalidate' from './endpoints/following/invalidate.js';
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.search != null) {
|
if (ps.search != null) {
|
||||||
for (const word of ps.search!.trim().split(' ')) {
|
for (const word of ps.search.trim().split(' ')) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
|
|
@ -5,10 +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 { FlashLikesRepository } from '@/models/_.js';
|
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
|
||||||
import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js';
|
import { FlashLikeEntityService } from '@/core/entities/FlashLikeEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { FlashService } from '@/core/FlashService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['account', 'flash'],
|
tags: ['account', 'flash'],
|
||||||
|
@ -46,6 +45,7 @@ export const paramDef = {
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
sinceDate: { type: 'integer' },
|
sinceDate: { type: 'integer' },
|
||||||
untilDate: { type: 'integer' },
|
untilDate: { type: 'integer' },
|
||||||
|
search: { type: 'string', minLength: 1, maxLength: 100, nullable: true },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -53,20 +53,18 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.flashLikesRepository)
|
|
||||||
private flashLikesRepository: FlashLikesRepository,
|
|
||||||
|
|
||||||
private flashLikeEntityService: FlashLikeEntityService,
|
private flashLikeEntityService: FlashLikeEntityService,
|
||||||
private queryService: QueryService,
|
private flashService: FlashService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.queryService.makePaginationQuery(this.flashLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
const likes = await this.flashService.myLikes(me.id, {
|
||||||
.andWhere('like.userId = :meId', { meId: me.id })
|
sinceId: ps.sinceId,
|
||||||
.leftJoinAndSelect('like.flash', 'flash');
|
untilId: ps.untilId,
|
||||||
|
sinceDate: ps.sinceDate,
|
||||||
const likes = await query
|
untilDate: ps.untilDate,
|
||||||
.limit(ps.limit)
|
limit: ps.limit,
|
||||||
.getMany();
|
search: ps.search,
|
||||||
|
});
|
||||||
|
|
||||||
return this.flashLikeEntityService.packMany(likes, me);
|
return this.flashLikeEntityService.packMany(likes, me);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { FlashService } from '@/core/FlashService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['flash'],
|
||||||
|
|
||||||
|
requireCredential: false,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'Flash',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
query: { type: 'string', minLength: 1, maxLength: 100 },
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
sinceDate: { type: 'integer' },
|
||||||
|
untilDate: { type: 'integer' },
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
|
||||||
|
},
|
||||||
|
required: ['query'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private flashService: FlashService,
|
||||||
|
private flashEntityService: FlashEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const result = await this.flashService.search(ps.query, {
|
||||||
|
sinceId: ps.sinceId,
|
||||||
|
untilId: ps.untilId,
|
||||||
|
sinceDate: ps.sinceDate,
|
||||||
|
untilDate: ps.untilDate,
|
||||||
|
limit: ps.limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.flashEntityService.packMany(result, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,10 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { FlashService } from '@/core/FlashService.js';
|
import { FlashService } from '@/core/FlashService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import { FlashLikesRepository, FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
|
import { CoreModule } from '@/core/CoreModule.js';
|
||||||
|
|
||||||
describe('FlashService', () => {
|
describe('FlashService', () => {
|
||||||
let app: TestingModule;
|
let app: TestingModule;
|
||||||
|
@ -18,6 +19,7 @@ describe('FlashService', () => {
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
let flashsRepository: FlashsRepository;
|
let flashsRepository: FlashsRepository;
|
||||||
|
let flashLikesRepository: FlashLikesRepository;
|
||||||
let usersRepository: UsersRepository;
|
let usersRepository: UsersRepository;
|
||||||
let userProfilesRepository: UserProfilesRepository;
|
let userProfilesRepository: UserProfilesRepository;
|
||||||
let idService: IdService;
|
let idService: IdService;
|
||||||
|
@ -65,6 +67,7 @@ describe('FlashService', () => {
|
||||||
app = await Test.createTestingModule({
|
app = await Test.createTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
GlobalModule,
|
GlobalModule,
|
||||||
|
CoreModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
FlashService,
|
FlashService,
|
||||||
|
@ -75,6 +78,7 @@ describe('FlashService', () => {
|
||||||
service = app.get(FlashService);
|
service = app.get(FlashService);
|
||||||
|
|
||||||
flashsRepository = app.get(DI.flashsRepository);
|
flashsRepository = app.get(DI.flashsRepository);
|
||||||
|
flashLikesRepository = app.get(DI.flashLikesRepository);
|
||||||
usersRepository = app.get(DI.usersRepository);
|
usersRepository = app.get(DI.usersRepository);
|
||||||
userProfilesRepository = app.get(DI.userProfilesRepository);
|
userProfilesRepository = app.get(DI.userProfilesRepository);
|
||||||
idService = app.get(IdService);
|
idService = app.get(IdService);
|
||||||
|
@ -88,6 +92,7 @@ describe('FlashService', () => {
|
||||||
await usersRepository.createQueryBuilder().delete().execute();
|
await usersRepository.createQueryBuilder().delete().execute();
|
||||||
await userProfilesRepository.createQueryBuilder().delete().execute();
|
await userProfilesRepository.createQueryBuilder().delete().execute();
|
||||||
await flashsRepository.createQueryBuilder().delete().execute();
|
await flashsRepository.createQueryBuilder().delete().execute();
|
||||||
|
await flashLikesRepository.createQueryBuilder().delete().execute();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
|
@ -14,15 +14,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paginator } from '@/utility/paginator.js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
paginator: Paginator;
|
paginator: IPaginator;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => Misskey.entities.Channel;
|
||||||
}>(), {
|
}>(), {
|
||||||
extractor: (item) => item,
|
extractor: (item) => item,
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,8 +31,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends Paginator">
|
<script lang="ts" setup generic="T extends IPaginator<Misskey.entities.Note>">
|
||||||
import type { Paginator } from '@/utility/paginator.js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else key="_root_" class="_gaps">
|
<div v-else key="_root_" class="_gaps">
|
||||||
<slot :items="paginator.items.value" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
<slot :items="unref(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
||||||
<div v-if="paginator.order.value === 'oldest'">
|
<div v-if="paginator.order.value === 'oldest'">
|
||||||
<MkButton v-if="!paginator.fetchingNewer.value" :class="$style.more" :wait="paginator.fetchingNewer.value" primary rounded @click="paginator.fetchNewer()">
|
<MkButton v-if="!paginator.fetchingNewer.value" :class="$style.more" :wait="paginator.fetchingNewer.value" primary rounded @click="paginator.fetchNewer()">
|
||||||
{{ i18n.ts.loadMore }}
|
{{ i18n.ts.loadMore }}
|
||||||
|
@ -44,11 +44,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends Paginator, I = UnwrapRef<T['items']>">
|
<script lang="ts" setup generic="T extends IPaginator">
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import { onMounted, watch } from 'vue';
|
import { onMounted, watch, unref } from 'vue';
|
||||||
import type { UnwrapRef } from 'vue';
|
import type { UnwrapRef } from 'vue';
|
||||||
import type { Paginator } from '@/utility/paginator.js';
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
@ -95,7 +95,7 @@ if (props.paginator.computedParams) {
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
empty: () => void;
|
empty: () => void;
|
||||||
default: (props: { items: I }) => void;
|
default: (props: { items: UnwrapRef<T['items']> }) => void;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends Paginator">
|
<script lang="ts" setup generic="T extends IPaginator">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import type { Paginator } from '@/utility/paginator.js';
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
|
|
@ -693,9 +693,7 @@ async function onPaste(ev: ClipboardEvent) {
|
||||||
}
|
}
|
||||||
if (pastedFiles.length > 0) {
|
if (pastedFiles.length > 0) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
os.launchUploader(pastedFiles, {}).then(driveFiles => {
|
uploader.addFiles(pastedFiles);
|
||||||
files.value.push(...driveFiles);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,9 +728,7 @@ async function onPaste(ev: ClipboardEvent) {
|
||||||
|
|
||||||
const fileName = formatTimeString(new Date(), pastedFileName).replace(/{{number}}/g, '0');
|
const fileName = formatTimeString(new Date(), pastedFileName).replace(/{{number}}/g, '0');
|
||||||
const file = new File([paste], `${fileName}.txt`, { type: 'text/plain' });
|
const file = new File([paste], `${fileName}.txt`, { type: 'text/plain' });
|
||||||
os.launchUploader([file], {}).then(driveFiles => {
|
uploader.addFiles([file]);
|
||||||
files.value.push(...driveFiles);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -776,9 +772,7 @@ function onDrop(ev: DragEvent): void {
|
||||||
// ファイルだったら
|
// ファイルだったら
|
||||||
if (ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
if (ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
os.launchUploader(Array.from(ev.dataTransfer.files), {}).then(driveFiles => {
|
uploader.addFiles(Array.from(ev.dataTransfer.files));
|
||||||
files.value.push(...driveFiles);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1195,7 +1189,7 @@ function showDraftMenu(ev: MouseEvent) {
|
||||||
if (!canSaveAsServerDraft.value) {
|
if (!canSaveAsServerDraft.value) {
|
||||||
return os.alert({
|
return os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: i18n.ts._drafts.cannotCreateDraftOfRenote,
|
text: i18n.ts._drafts.cannotCreateDraft,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
saveServerDraft();
|
saveServerDraft();
|
||||||
|
|
|
@ -59,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue';
|
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||||
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
|
@ -75,6 +76,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
import type { IPaginator, MisskeyEntity } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
||||||
|
@ -101,12 +103,12 @@ provide('inTimeline', true);
|
||||||
provide('tl_withSensitive', computed(() => props.withSensitive));
|
provide('tl_withSensitive', computed(() => props.withSensitive));
|
||||||
provide('inChannel', computed(() => props.src === 'channel'));
|
provide('inChannel', computed(() => props.src === 'channel'));
|
||||||
|
|
||||||
let paginator: Paginator;
|
let paginator: IPaginator<Misskey.entities.Note>;
|
||||||
|
|
||||||
if (props.src === 'antenna') {
|
if (props.src === 'antenna') {
|
||||||
paginator = markRaw(new Paginator('antennas/notes', {
|
paginator = markRaw(new Paginator('antennas/notes', {
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
antennaId: props.antenna,
|
antennaId: props.antenna!,
|
||||||
})),
|
})),
|
||||||
useShallowRef: true,
|
useShallowRef: true,
|
||||||
}));
|
}));
|
||||||
|
@ -160,21 +162,21 @@ if (props.src === 'antenna') {
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
listId: props.list,
|
listId: props.list!,
|
||||||
})),
|
})),
|
||||||
useShallowRef: true,
|
useShallowRef: true,
|
||||||
}));
|
}));
|
||||||
} else if (props.src === 'channel') {
|
} else if (props.src === 'channel') {
|
||||||
paginator = markRaw(new Paginator('channels/timeline', {
|
paginator = markRaw(new Paginator('channels/timeline', {
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
channelId: props.channel,
|
channelId: props.channel!,
|
||||||
})),
|
})),
|
||||||
useShallowRef: true,
|
useShallowRef: true,
|
||||||
}));
|
}));
|
||||||
} else if (props.src === 'role') {
|
} else if (props.src === 'role') {
|
||||||
paginator = markRaw(new Paginator('roles/notes', {
|
paginator = markRaw(new Paginator('roles/notes', {
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
roleId: props.role,
|
roleId: props.role!,
|
||||||
})),
|
})),
|
||||||
useShallowRef: true,
|
useShallowRef: true,
|
||||||
}));
|
}));
|
||||||
|
@ -223,6 +225,20 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const visibility = useDocumentVisibility();
|
||||||
|
let isPausingUpdate = false;
|
||||||
|
|
||||||
|
watch(visibility, () => {
|
||||||
|
if (visibility.value === 'hidden') {
|
||||||
|
isPausingUpdate = true;
|
||||||
|
} else { // 'visible'
|
||||||
|
isPausingUpdate = false;
|
||||||
|
if (isTop()) {
|
||||||
|
releaseQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let adInsertionCounter = 0;
|
let adInsertionCounter = 0;
|
||||||
|
|
||||||
const MIN_POLLING_INTERVAL = 1000 * 10;
|
const MIN_POLLING_INTERVAL = 1000 * 10;
|
||||||
|
@ -236,7 +252,7 @@ if (!store.s.realtimeMode) {
|
||||||
// TODO: 先頭のノートの作成日時が1日以上前であれば流速が遅いTLと見做してインターバルを通常より延ばす
|
// TODO: 先頭のノートの作成日時が1日以上前であれば流速が遅いTLと見做してインターバルを通常より延ばす
|
||||||
useInterval(async () => {
|
useInterval(async () => {
|
||||||
paginator.fetchNewer({
|
paginator.fetchNewer({
|
||||||
toQueue: !isTop(),
|
toQueue: !isTop() || isPausingUpdate,
|
||||||
});
|
});
|
||||||
}, POLLING_INTERVAL, {
|
}, POLLING_INTERVAL, {
|
||||||
immediate: false,
|
immediate: false,
|
||||||
|
@ -245,7 +261,7 @@ if (!store.s.realtimeMode) {
|
||||||
|
|
||||||
useGlobalEvent('notePosted', (note) => {
|
useGlobalEvent('notePosted', (note) => {
|
||||||
paginator.fetchNewer({
|
paginator.fetchNewer({
|
||||||
toQueue: !isTop(),
|
toQueue: !isTop() || isPausingUpdate,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -256,17 +272,17 @@ useGlobalEvent('noteDeleted', (noteId) => {
|
||||||
|
|
||||||
function releaseQueue() {
|
function releaseQueue() {
|
||||||
paginator.releaseQueue();
|
paginator.releaseQueue();
|
||||||
scrollToTop(rootEl.value);
|
scrollToTop(rootEl.value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepend(note: Misskey.entities.Note) {
|
function prepend(note: Misskey.entities.Note & MisskeyEntity) {
|
||||||
adInsertionCounter++;
|
adInsertionCounter++;
|
||||||
|
|
||||||
if (instance.notesPerOneAd > 0 && adInsertionCounter % instance.notesPerOneAd === 0) {
|
if (instance.notesPerOneAd > 0 && adInsertionCounter % instance.notesPerOneAd === 0) {
|
||||||
note._shouldInsertAd_ = true;
|
note._shouldInsertAd_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTop()) {
|
if (isTop() && !isPausingUpdate) {
|
||||||
paginator.prepend(note);
|
paginator.prepend(note);
|
||||||
} else {
|
} else {
|
||||||
paginator.enqueue(note);
|
paginator.enqueue(note);
|
||||||
|
@ -281,12 +297,13 @@ function prepend(note: Misskey.entities.Note) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection: Misskey.ChannelConnection | null = null;
|
let connection: Misskey.IChannelConnection | null = null;
|
||||||
let connection2: Misskey.ChannelConnection | null = null;
|
let connection2: Misskey.IChannelConnection | null = null;
|
||||||
|
|
||||||
const stream = store.s.realtimeMode ? useStream() : null;
|
const stream = store.s.realtimeMode ? useStream() : null;
|
||||||
|
|
||||||
function connectChannel() {
|
function connectChannel() {
|
||||||
|
if (stream == null) return;
|
||||||
if (props.src === 'antenna') {
|
if (props.src === 'antenna') {
|
||||||
if (props.antenna == null) return;
|
if (props.antenna == null) return;
|
||||||
connection = stream.useChannel('antenna', {
|
connection = stream.useChannel('antenna', {
|
||||||
|
|
|
@ -45,7 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
|
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||||
import type { notificationTypes } from '@@/js/const.js';
|
import type { notificationTypes } from '@@/js/const.js';
|
||||||
|
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
||||||
import XNotification from '@/components/MkNotification.vue';
|
import XNotification from '@/components/MkNotification.vue';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
|
@ -92,6 +94,49 @@ if (!store.s.realtimeMode) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTop() {
|
||||||
|
if (scrollContainer == null) return true;
|
||||||
|
if (rootEl.value == null) return true;
|
||||||
|
const scrollTop = scrollContainer.scrollTop;
|
||||||
|
const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop;
|
||||||
|
return scrollTop <= tlTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseQueue() {
|
||||||
|
paginator.releaseQueue();
|
||||||
|
scrollToTop(rootEl.value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollContainer: HTMLElement | null = null;
|
||||||
|
|
||||||
|
function onScrollContainerScroll() {
|
||||||
|
if (isTop()) {
|
||||||
|
paginator.releaseQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(rootEl, (el) => {
|
||||||
|
if (el && scrollContainer == null) {
|
||||||
|
scrollContainer = getScrollContainer(el);
|
||||||
|
if (scrollContainer == null) return;
|
||||||
|
scrollContainer.addEventListener('scroll', onScrollContainerScroll, { passive: true }); // ほんとはscrollendにしたいけどiosが非対応
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
const visibility = useDocumentVisibility();
|
||||||
|
let isPausingUpdate = false;
|
||||||
|
|
||||||
|
watch(visibility, () => {
|
||||||
|
if (visibility.value === 'hidden') {
|
||||||
|
isPausingUpdate = true;
|
||||||
|
} else { // 'visible'
|
||||||
|
isPausingUpdate = false;
|
||||||
|
if (isTop()) {
|
||||||
|
releaseQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function onNotification(notification) {
|
function onNotification(notification) {
|
||||||
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
|
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
|
||||||
if (isMuted || window.document.visibilityState === 'visible') {
|
if (isMuted || window.document.visibilityState === 'visible') {
|
||||||
|
@ -101,7 +146,11 @@ function onNotification(notification) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMuted) {
|
if (!isMuted) {
|
||||||
paginator.prepend(notification);
|
if (isTop() && !isPausingUpdate) {
|
||||||
|
paginator.prepend(notification);
|
||||||
|
} else {
|
||||||
|
paginator.enqueue(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +158,7 @@ function reload() {
|
||||||
return paginator.reload();
|
return paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;
|
let connection: Misskey.IChannelConnection<Misskey.Channels['main']> | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
paginator.init();
|
paginator.init();
|
||||||
|
|
|
@ -16,15 +16,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paginator } from '@/utility/paginator.js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkUserInfo from '@/components/MkUserInfo.vue';
|
import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
paginator: Paginator;
|
paginator: IPaginator;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => Misskey.entities.UserDetailed;
|
||||||
}>(), {
|
}>(), {
|
||||||
extractor: (item) => item,
|
extractor: (item) => item,
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPagination :paginator="pinnedUsersPaginator">
|
<MkPagination :paginator="pinnedUsersPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
<XUser v-for="item in items" :key="item.id" :user="item"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPagination :paginator="popularUsersPaginator">
|
<MkPagination :paginator="popularUsersPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
<XUser v-for="item in items" :key="item.id" :user="item"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
@ -34,7 +34,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="paginator.computedParams.value.origin === 'local'">
|
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="paginator.computedParams?.value?.origin === 'local'">
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const origin = ref<Misskey.entities.AdminDriveFilesRequest['origin']>('local');
|
const origin = ref<NonNullable<Misskey.entities.AdminDriveFilesRequest['origin']>>('local');
|
||||||
const type = ref<string | null>(null);
|
const type = ref<string | null>(null);
|
||||||
const searchHost = ref('');
|
const searchHost = ref('');
|
||||||
const userId = ref('');
|
const userId = ref('');
|
||||||
|
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPagination :paginator="paginator">
|
<MkPagination :paginator="paginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/>
|
<MkInviteCode v-for="item in items" :key="item.id" :invite="item" :onDeleted="deleted" moderator/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { computed, markRaw, ref, useTemplateRef } from 'vue';
|
import { computed, markRaw, ref, useTemplateRef } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -68,8 +69,8 @@ import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const type = ref('all');
|
const type = ref<NonNullable<Misskey.entities.AdminInviteListRequest['type']>>('all');
|
||||||
const sort = ref('+createdAt');
|
const sort = ref<NonNullable<Misskey.entities.AdminInviteListRequest['sort']>>('+createdAt');
|
||||||
|
|
||||||
const paginator = markRaw(new Paginator('admin/invite/list', {
|
const paginator = markRaw(new Paginator('admin/invite/list', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|
|
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedItems.includes(item.id) }]">
|
<div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpened]: expandedItems.includes(item.id) }]">
|
||||||
<div :class="$style.userItemMain">
|
<div :class="$style.userItemMain">
|
||||||
<MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`">
|
<MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`">
|
||||||
<MkUserCardMini :user="item.user"/>
|
<MkUserCardMini :user="item.user"/>
|
||||||
|
@ -76,12 +76,12 @@ const props = defineProps<{
|
||||||
|
|
||||||
const usersPaginator = markRaw(new Paginator('admin/roles/users', {
|
const usersPaginator = markRaw(new Paginator('admin/roles/users', {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => props.id ? ({
|
||||||
roleId: props.id,
|
roleId: props.id,
|
||||||
})),
|
}) : undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const expandedItems = ref([]);
|
const expandedItems = ref<string[]>([]);
|
||||||
|
|
||||||
const role = reactive(await misskeyApi('admin/roles/show', {
|
const role = reactive(await misskeyApi('admin/roles/show', {
|
||||||
roleId: props.id,
|
roleId: props.id,
|
||||||
|
@ -199,7 +199,7 @@ definePage(() => ({
|
||||||
transition: transform 0.1s ease-out;
|
transition: transform 0.1s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userItem.userItemOpend {
|
.userItem.userItemOpened {
|
||||||
.chevron {
|
.chevron {
|
||||||
transform: rotateX(180deg);
|
transform: rotateX(180deg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
<template #label>{{ i18n.ts.username }}</template>
|
<template #label>{{ i18n.ts.username }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="paginator.computedParams.value.origin === 'local'">
|
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="paginator.computedParams?.value?.origin === 'local'">
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" :paginator="paginator">
|
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`">
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${user.updatedAt ? dateString(user.updatedAt) : 'Unknown'}`" :class="$style.user" :to="`/admin/user/${user.id}`">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkUserCardMini :user="user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'search'">
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkInput v-model="searchQuery" :large="true" type="search">
|
||||||
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
|
</MkInput>
|
||||||
|
<MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton>
|
||||||
|
<MkPagination v-if="searchPaginator" v-slot="{items}" :key="searchKey" :paginator="searchPaginator">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'featured'">
|
||||||
<MkPagination v-slot="{items}" :paginator="featuredFlashsPaginator">
|
<MkPagination v-slot="{items}" :paginator="featuredFlashsPaginator">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
|
@ -26,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'">
|
||||||
<MkPagination v-slot="{items}" :paginator="likedFlashsPaginator">
|
<MkPagination v-slot="{items}" :paginator="likedFlashsPaginator" withControl>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,10 +51,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, markRaw, ref } from 'vue';
|
import { computed, markRaw, ref, shallowRef } from 'vue';
|
||||||
|
import type { IPaginator } from '@/utility/paginator.js';
|
||||||
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
@ -50,6 +66,10 @@ const router = useRouter();
|
||||||
|
|
||||||
const tab = ref('featured');
|
const tab = ref('featured');
|
||||||
|
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const searchPaginator = shallowRef<IPaginator | null>(null);
|
||||||
|
const searchKey = ref(0);
|
||||||
|
|
||||||
const featuredFlashsPaginator = markRaw(new Paginator('flash/featured', {
|
const featuredFlashsPaginator = markRaw(new Paginator('flash/featured', {
|
||||||
limit: 5,
|
limit: 5,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
|
@ -59,12 +79,28 @@ const myFlashsPaginator = markRaw(new Paginator('flash/my', {
|
||||||
}));
|
}));
|
||||||
const likedFlashsPaginator = markRaw(new Paginator('flash/my-likes', {
|
const likedFlashsPaginator = markRaw(new Paginator('flash/my-likes', {
|
||||||
limit: 5,
|
limit: 5,
|
||||||
|
canSearch: true,
|
||||||
|
searchParamName: 'search',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
router.push('/play/new');
|
router.push('/play/new');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
if (searchQuery.value.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchPaginator.value = markRaw(new Paginator('flash/search', {
|
||||||
|
params: {
|
||||||
|
query: searchQuery.value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
searchKey.value++;
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
icon: 'ti ti-plus',
|
icon: 'ti ti-plus',
|
||||||
text: i18n.ts.create,
|
text: i18n.ts.create,
|
||||||
|
@ -72,6 +108,10 @@ const headerActions = computed(() => [{
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => [{
|
||||||
|
key: 'search',
|
||||||
|
title: i18n.ts.search,
|
||||||
|
icon: 'ti ti-search',
|
||||||
|
}, {
|
||||||
key: 'featured',
|
key: 'featured',
|
||||||
title: i18n.ts._play.featured,
|
title: i18n.ts._play.featured,
|
||||||
icon: 'ti ti-flare',
|
icon: 'ti ti-flare',
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPagination :paginator="paginator">
|
<MkPagination :paginator="paginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkInviteCode v-for="item in (items as Misskey.entities.InviteCode[])" :key="item.id" :invite="item" :onDeleted="deleted"/>
|
<MkInviteCode v-for="item in items" :key="item.id" :invite="item" :onDeleted="deleted"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
|
|
@ -100,7 +100,7 @@ const prevChannelPaginator = markRaw(new Paginator('channels/timeline', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'older',
|
initialDirection: 'older',
|
||||||
computedParams: computed(() => note.value ? ({
|
computedParams: computed(() => note.value && note.value.channelId != null ? ({
|
||||||
channelId: note.value.channelId,
|
channelId: note.value.channelId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
}));
|
}));
|
||||||
|
@ -109,7 +109,7 @@ const nextChannelPaginator = markRaw(new Paginator('channels/timeline', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'newer',
|
initialDirection: 'newer',
|
||||||
computedParams: computed(() => note.value ? ({
|
computedParams: computed(() => note.value && note.value.channelId != null ? ({
|
||||||
channelId: note.value.channelId,
|
channelId: note.value.channelId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -135,9 +135,9 @@ const page = ref<Misskey.entities.Page | null>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
const otherPostsPaginator = markRaw(new Paginator('users/pages', {
|
const otherPostsPaginator = markRaw(new Paginator('users/pages', {
|
||||||
limit: 6,
|
limit: 6,
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => page.value ? ({
|
||||||
userId: page.value.user.id,
|
userId: page.value.user.id,
|
||||||
})),
|
}) : undefined),
|
||||||
}));
|
}));
|
||||||
const path = computed(() => props.username + '/' + props.pageName);
|
const path = computed(() => props.username + '/' + props.pageName);
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { computed, markRaw, ref, watch } from 'vue';
|
import { computed, markRaw, ref, watch } from 'vue';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import type { StyleValue } from 'vue';
|
import type { StyleValue } from 'vue';
|
||||||
|
@ -62,7 +63,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const sortMode = ref('+size');
|
const sortMode = ref<Misskey.entities.DriveFilesRequest['sort']>('+size');
|
||||||
const paginator = markRaw(new Paginator('drive/files', {
|
const paginator = markRaw(new Paginator('drive/files', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
computedParams: computed(() => ({ sort: sortMode.value })),
|
computedParams: computed(() => ({ sort: sortMode.value })),
|
||||||
|
|
|
@ -208,9 +208,9 @@ const blockingPaginator = markRaw(new Paginator('blocking/list', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const expandedRenoteMuteItems = ref([]);
|
const expandedRenoteMuteItems = ref<string[]>([]);
|
||||||
const expandedMuteItems = ref([]);
|
const expandedMuteItems = ref<string[]>([]);
|
||||||
const expandedBlockItems = ref([]);
|
const expandedBlockItems = ref<string[]>([]);
|
||||||
|
|
||||||
const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord');
|
const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord');
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ async function unblock(user, ev) {
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleRenoteMuteItem(item) {
|
async function toggleRenoteMuteItem(item: { id: string }) {
|
||||||
if (expandedRenoteMuteItems.value.includes(item.id)) {
|
if (expandedRenoteMuteItems.value.includes(item.id)) {
|
||||||
expandedRenoteMuteItems.value = expandedRenoteMuteItems.value.filter(x => x !== item.id);
|
expandedRenoteMuteItems.value = expandedRenoteMuteItems.value.filter(x => x !== item.id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -261,7 +261,7 @@ async function toggleRenoteMuteItem(item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleMuteItem(item) {
|
async function toggleMuteItem(item: { id: string }) {
|
||||||
if (expandedMuteItems.value.includes(item.id)) {
|
if (expandedMuteItems.value.includes(item.id)) {
|
||||||
expandedMuteItems.value = expandedMuteItems.value.filter(x => x !== item.id);
|
expandedMuteItems.value = expandedMuteItems.value.filter(x => x !== item.id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -269,7 +269,7 @@ async function toggleMuteItem(item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleBlockItem(item) {
|
async function toggleBlockItem(item: { id: string }) {
|
||||||
if (expandedBlockItems.value.includes(item.id)) {
|
if (expandedBlockItems.value.includes(item.id)) {
|
||||||
expandedBlockItems.value = expandedBlockItems.value.filter(x => x !== item.id);
|
expandedBlockItems.value = expandedBlockItems.value.filter(x => x !== item.id);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -59,10 +59,21 @@ const visibleUsers = ref([] as Misskey.entities.UserDetailed[]);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
let noteText = '';
|
let noteText = '';
|
||||||
if (title.value) noteText += `[ ${title.value} ]\n`;
|
if (title.value) {
|
||||||
// Googleニュース対策
|
noteText += `[ ${title.value} ]\n`;
|
||||||
if (text?.startsWith(`${title.value}.\n`)) noteText += text.replace(`${title.value}.\n`, '');
|
|
||||||
else if (text && title.value !== text) noteText += `${text}\n`;
|
//#region add text to note text
|
||||||
|
if (text?.startsWith(title.value)) {
|
||||||
|
// For the Google app https://github.com/misskey-dev/misskey/issues/16224
|
||||||
|
noteText += text.replace(title.value, '').trimStart();
|
||||||
|
} else if (text) {
|
||||||
|
noteText += `${text}\n`;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
} else if (text) {
|
||||||
|
noteText += `${text}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
try {
|
try {
|
||||||
// Normalize the URL to URL-encoded and puny-coded from with the URL constructor.
|
// Normalize the URL to URL-encoded and puny-coded from with the URL constructor.
|
||||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
|
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
|
||||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.files && note.files.length > 0" :class="$style.richcontent">
|
<div v-if="note.files && note.files.length > 0 && (note.cw == null || showContent)" :class="$style.richcontent">
|
||||||
<MkMediaList :mediaList="note.files.slice(0, 4)"/>
|
<MkMediaList :mediaList="note.files.slice(0, 4)"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.reactionCount > 0" :class="$style.reactions">
|
<div v-if="note.reactionCount > 0" :class="$style.reactions">
|
||||||
|
|
|
@ -155,6 +155,13 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
|
||||||
export async function uninstallPlugin(plugin: Plugin) {
|
export async function uninstallPlugin(plugin: Plugin) {
|
||||||
abortPlugin(plugin);
|
abortPlugin(plugin);
|
||||||
prefer.commit('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId));
|
prefer.commit('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId));
|
||||||
|
|
||||||
|
Object.keys(window.localStorage).forEach(key => {
|
||||||
|
if (key.startsWith('aiscript:plugins:' + plugin.installId)) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) {
|
if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) {
|
||||||
await os.apiWithDialog('i/revoke-token', {
|
await os.apiWithDialog('i/revoke-token', {
|
||||||
token: store.s.pluginTokens[plugin.installId],
|
token: store.s.pluginTokens[plugin.installId],
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { ref, shallowRef, triggerRef } from 'vue';
|
import { ref, shallowRef, triggerRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { ComputedRef, DeepReadonly, Ref, ShallowRef } from 'vue';
|
import type { ComputedRef, Ref, ShallowRef } from 'vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
||||||
const MAX_ITEMS = 30;
|
const MAX_ITEMS = 30;
|
||||||
|
@ -17,14 +17,60 @@ export type MisskeyEntity = {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
_shouldInsertAd_?: boolean;
|
_shouldInsertAd_?: boolean;
|
||||||
[x: string]: any;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, T extends { id: string; } = (Misskey.Endpoints[Endpoint]['res'] extends (infer I)[] ? I extends { id: string } ? I : { id: string } : { id: string })> {
|
type FilterByEpRes<E extends Record<string, any>> = {
|
||||||
|
[K in keyof E]: E[K]['res'] extends Array<{ id: string }> ? K : never
|
||||||
|
}[keyof E];
|
||||||
|
export type PaginatorCompatibleEndpointPaths = FilterByEpRes<Misskey.Endpoints>;
|
||||||
|
export type PaginatorCompatibleEndpoints = {
|
||||||
|
[K in PaginatorCompatibleEndpointPaths]: Misskey.Endpoints[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IPaginator<T = unknown, _T = T & MisskeyEntity> {
|
||||||
/**
|
/**
|
||||||
* 外部から直接操作しないでください
|
* 外部から直接操作しないでください
|
||||||
*/
|
*/
|
||||||
public items: ShallowRef<T[]> | Ref<T[]>;
|
items: Ref<_T[]> | ShallowRef<_T[]>;
|
||||||
|
queuedAheadItemsCount: Ref<number>;
|
||||||
|
fetching: Ref<boolean>;
|
||||||
|
fetchingOlder: Ref<boolean>;
|
||||||
|
fetchingNewer: Ref<boolean>;
|
||||||
|
canFetchOlder: Ref<boolean>;
|
||||||
|
canSearch: boolean;
|
||||||
|
error: Ref<boolean>;
|
||||||
|
computedParams: ComputedRef<Misskey.Endpoints[PaginatorCompatibleEndpointPaths]['req'] | null | undefined> | null;
|
||||||
|
initialId: MisskeyEntity['id'] | null;
|
||||||
|
initialDate: number | null;
|
||||||
|
initialDirection: 'newer' | 'older';
|
||||||
|
noPaging: boolean;
|
||||||
|
searchQuery: Ref<null | string>;
|
||||||
|
order: Ref<'newest' | 'oldest'>;
|
||||||
|
|
||||||
|
init(): Promise<void>;
|
||||||
|
reload(): Promise<void>;
|
||||||
|
fetchOlder(): Promise<void>;
|
||||||
|
fetchNewer(options?: { toQueue?: boolean }): Promise<void>;
|
||||||
|
trim(trigger?: boolean): void;
|
||||||
|
unshiftItems(newItems: (_T)[]): void;
|
||||||
|
pushItems(oldItems: (_T)[]): void;
|
||||||
|
prepend(item: _T): void;
|
||||||
|
enqueue(item: _T): void;
|
||||||
|
releaseQueue(): void;
|
||||||
|
removeItem(id: string): void;
|
||||||
|
updateItem(id: string, updater: (item: _T) => _T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Paginator<
|
||||||
|
Endpoint extends PaginatorCompatibleEndpointPaths,
|
||||||
|
E extends PaginatorCompatibleEndpoints[Endpoint] = PaginatorCompatibleEndpoints[Endpoint],
|
||||||
|
T extends E['res'][number] & MisskeyEntity = E['res'][number] & MisskeyEntity,
|
||||||
|
SRef extends boolean = false,
|
||||||
|
> implements IPaginator {
|
||||||
|
/**
|
||||||
|
* 外部から直接操作しないでください
|
||||||
|
*/
|
||||||
|
public items: SRef extends true ? ShallowRef<T[]> : Ref<T[]>;
|
||||||
|
|
||||||
public queuedAheadItemsCount = ref(0);
|
public queuedAheadItemsCount = ref(0);
|
||||||
public fetching = ref(true);
|
public fetching = ref(true);
|
||||||
|
@ -35,18 +81,18 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
public error = ref(false);
|
public error = ref(false);
|
||||||
private endpoint: Endpoint;
|
private endpoint: Endpoint;
|
||||||
private limit: number;
|
private limit: number;
|
||||||
private params: Misskey.Endpoints[Endpoint]['req'] | (() => Misskey.Endpoints[Endpoint]['req']);
|
private params: E['req'] | (() => E['req']);
|
||||||
public computedParams: ComputedRef<Misskey.Endpoints[Endpoint]['req']> | null;
|
public computedParams: ComputedRef<E['req'] | null | undefined> | null;
|
||||||
public initialId: MisskeyEntity['id'] | null = null;
|
public initialId: MisskeyEntity['id'] | null = null;
|
||||||
public initialDate: number | null = null;
|
public initialDate: number | null = null;
|
||||||
public initialDirection: 'newer' | 'older';
|
public initialDirection: 'newer' | 'older';
|
||||||
private offsetMode: boolean;
|
private offsetMode: boolean;
|
||||||
public noPaging: boolean;
|
public noPaging: boolean;
|
||||||
public searchQuery = ref<null | string>('');
|
public searchQuery = ref<null | string>('');
|
||||||
private searchParamName: string;
|
private searchParamName: keyof E['req'] | 'search';
|
||||||
private canFetchDetection: 'safe' | 'limit' | null = null;
|
private canFetchDetection: 'safe' | 'limit' | null = null;
|
||||||
private aheadQueue: T[] = [];
|
private aheadQueue: T[] = [];
|
||||||
private useShallowRef: boolean;
|
private useShallowRef: SRef;
|
||||||
|
|
||||||
// 配列内の要素をどのような順序で並べるか
|
// 配列内の要素をどのような順序で並べるか
|
||||||
// newest: 新しいものが先頭 (default)
|
// newest: 新しいものが先頭 (default)
|
||||||
|
@ -56,8 +102,8 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
|
|
||||||
constructor(endpoint: Endpoint, props: {
|
constructor(endpoint: Endpoint, props: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
params?: Misskey.Endpoints[Endpoint]['req'] | (() => Misskey.Endpoints[Endpoint]['req']);
|
params?: E['req'] | (() => E['req']);
|
||||||
computedParams?: ComputedRef<Misskey.Endpoints[Endpoint]['req']>;
|
computedParams?: ComputedRef<E['req'] | null | undefined>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
||||||
|
@ -75,14 +121,19 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
|
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
|
||||||
canFetchDetection?: 'safe' | 'limit';
|
canFetchDetection?: 'safe' | 'limit';
|
||||||
|
|
||||||
useShallowRef?: boolean;
|
useShallowRef?: SRef;
|
||||||
|
|
||||||
canSearch?: boolean;
|
canSearch?: boolean;
|
||||||
searchParamName?: keyof Misskey.Endpoints[Endpoint]['req'];
|
searchParamName?: keyof E['req'];
|
||||||
}) {
|
}) {
|
||||||
this.endpoint = endpoint;
|
this.endpoint = endpoint;
|
||||||
this.useShallowRef = props.useShallowRef ?? false;
|
this.useShallowRef = (props.useShallowRef ?? false) as SRef;
|
||||||
this.items = this.useShallowRef ? shallowRef([] as T[]) : ref([] as T[]);
|
if (this.useShallowRef) {
|
||||||
|
this.items = shallowRef<T[]>([]);
|
||||||
|
} else {
|
||||||
|
this.items = ref<T[]>([]) as Ref<T[]>;
|
||||||
|
}
|
||||||
|
|
||||||
this.limit = props.limit ?? FIRST_FETCH_LIMIT;
|
this.limit = props.limit ?? FIRST_FETCH_LIMIT;
|
||||||
this.params = props.params ?? {};
|
this.params = props.params ?? {};
|
||||||
this.computedParams = props.computedParams ?? null;
|
this.computedParams = props.computedParams ?? null;
|
||||||
|
@ -130,7 +181,7 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
this.queuedAheadItemsCount.value = 0;
|
this.queuedAheadItemsCount.value = 0;
|
||||||
this.fetching.value = true;
|
this.fetching.value = true;
|
||||||
|
|
||||||
await misskeyApi<T[]>(this.endpoint, {
|
const data: E['req'] = {
|
||||||
...(typeof this.params === 'function' ? this.params() : this.params),
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
...(this.computedParams ? this.computedParams.value : {}),
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
@ -145,39 +196,46 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
untilId: this.initialId ?? undefined,
|
untilId: this.initialId ?? undefined,
|
||||||
untilDate: this.initialDate ?? undefined,
|
untilDate: this.initialDate ?? undefined,
|
||||||
} : {}),
|
} : {}),
|
||||||
}).then(res => {
|
};
|
||||||
// 逆順で返ってくるので
|
|
||||||
if ((this.initialId || this.initialDate) && this.initialDirection === 'newer') {
|
|
||||||
res.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < res.length; i++) {
|
const apiRes = (await misskeyApi(this.endpoint, data).catch(err => {
|
||||||
const item = res[i];
|
|
||||||
if (i === 3) item._shouldInsertAd_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pushItems(res);
|
|
||||||
|
|
||||||
if (this.canFetchDetection === 'limit') {
|
|
||||||
if (res.length < FIRST_FETCH_LIMIT) {
|
|
||||||
this.canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
this.canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
|
||||||
if (res.length === 0 || this.noPaging) {
|
|
||||||
this.canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
this.canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.error.value = false;
|
|
||||||
this.fetching.value = false;
|
|
||||||
}, err => {
|
|
||||||
this.error.value = true;
|
this.error.value = true;
|
||||||
this.fetching.value = false;
|
this.fetching.value = false;
|
||||||
});
|
return null;
|
||||||
|
})) as T[] | null;
|
||||||
|
|
||||||
|
if (apiRes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逆順で返ってくるので
|
||||||
|
if ((this.initialId || this.initialDate) && this.initialDirection === 'newer') {
|
||||||
|
apiRes.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < apiRes.length; i++) {
|
||||||
|
const item = apiRes[i];
|
||||||
|
if (i === 3) item._shouldInsertAd_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushItems(apiRes);
|
||||||
|
|
||||||
|
if (this.canFetchDetection === 'limit') {
|
||||||
|
if (apiRes.length < FIRST_FETCH_LIMIT) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
||||||
|
if (apiRes.length === 0 || this.noPaging) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error.value = false;
|
||||||
|
this.fetching.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public reload(): Promise<void> {
|
public reload(): Promise<void> {
|
||||||
|
@ -187,7 +245,8 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
public async fetchOlder(): Promise<void> {
|
public async fetchOlder(): Promise<void> {
|
||||||
if (!this.canFetchOlder.value || this.fetching.value || this.fetchingOlder.value || this.items.value.length === 0) return;
|
if (!this.canFetchOlder.value || this.fetching.value || this.fetchingOlder.value || this.items.value.length === 0) return;
|
||||||
this.fetchingOlder.value = true;
|
this.fetchingOlder.value = true;
|
||||||
await misskeyApi<T[]>(this.endpoint, {
|
|
||||||
|
const data: E['req'] = {
|
||||||
...(typeof this.params === 'function' ? this.params() : this.params),
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
...(this.computedParams ? this.computedParams.value : {}),
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
@ -197,37 +256,46 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
} : {
|
} : {
|
||||||
untilId: this.getOldestId(),
|
untilId: this.getOldestId(),
|
||||||
}),
|
}),
|
||||||
}).then(res => {
|
};
|
||||||
for (let i = 0; i < res.length; i++) {
|
|
||||||
const item = res[i];
|
|
||||||
if (i === 10) item._shouldInsertAd_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pushItems(res);
|
const apiRes = (await misskeyApi<T[]>(this.endpoint, data).catch(err => {
|
||||||
|
return null;
|
||||||
|
})) as T[] | null;
|
||||||
|
|
||||||
if (this.canFetchDetection === 'limit') {
|
this.fetchingOlder.value = false;
|
||||||
if (res.length < FIRST_FETCH_LIMIT) {
|
|
||||||
this.canFetchOlder.value = false;
|
if (apiRes == null) {
|
||||||
} else {
|
return;
|
||||||
this.canFetchOlder.value = true;
|
}
|
||||||
}
|
|
||||||
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
for (let i = 0; i < apiRes.length; i++) {
|
||||||
if (res.length === 0) {
|
const item = apiRes[i];
|
||||||
this.canFetchOlder.value = false;
|
if (i === 10) item._shouldInsertAd_ = true;
|
||||||
} else {
|
}
|
||||||
this.canFetchOlder.value = true;
|
|
||||||
}
|
this.pushItems(apiRes);
|
||||||
|
|
||||||
|
if (this.canFetchDetection === 'limit') {
|
||||||
|
if (apiRes.length < FIRST_FETCH_LIMIT) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
||||||
this.fetchingOlder.value = false;
|
if (apiRes.length === 0) {
|
||||||
});
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchNewer(options: {
|
public async fetchNewer(options: {
|
||||||
toQueue?: boolean;
|
toQueue?: boolean;
|
||||||
} = {}): Promise<void> {
|
} = {}): Promise<void> {
|
||||||
this.fetchingNewer.value = true;
|
this.fetchingNewer.value = true;
|
||||||
await misskeyApi<T[]>(this.endpoint, {
|
|
||||||
|
const data: E['req'] = {
|
||||||
...(typeof this.params === 'function' ? this.params() : this.params),
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
...(this.computedParams ? this.computedParams.value : {}),
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
@ -237,25 +305,29 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
} : {
|
} : {
|
||||||
sinceId: this.getNewestId(),
|
sinceId: this.getNewestId(),
|
||||||
}),
|
}),
|
||||||
}).then(res => {
|
};
|
||||||
if (res.length === 0) return; // これやらないと余計なre-renderが走る
|
|
||||||
|
|
||||||
if (options.toQueue) {
|
const apiRes = (await misskeyApi<T[]>(this.endpoint, data).catch(err => {
|
||||||
this.aheadQueue.unshift(...res.toReversed());
|
return null;
|
||||||
if (this.aheadQueue.length > MAX_QUEUE_ITEMS) {
|
})) as T[] | null;
|
||||||
this.aheadQueue = this.aheadQueue.slice(0, MAX_QUEUE_ITEMS);
|
|
||||||
}
|
this.fetchingNewer.value = false;
|
||||||
this.queuedAheadItemsCount.value = this.aheadQueue.length;
|
|
||||||
} else {
|
if (apiRes == null || apiRes.length === 0) return; // これやらないと余計なre-renderが走る
|
||||||
if (this.order.value === 'oldest') {
|
|
||||||
this.pushItems(res);
|
if (options.toQueue) {
|
||||||
} else {
|
this.aheadQueue.unshift(...apiRes.toReversed());
|
||||||
this.unshiftItems(res.toReversed());
|
if (this.aheadQueue.length > MAX_QUEUE_ITEMS) {
|
||||||
}
|
this.aheadQueue = this.aheadQueue.slice(0, MAX_QUEUE_ITEMS);
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
this.queuedAheadItemsCount.value = this.aheadQueue.length;
|
||||||
this.fetchingNewer.value = false;
|
} else {
|
||||||
});
|
if (this.order.value === 'oldest') {
|
||||||
|
this.pushItems(apiRes);
|
||||||
|
} else {
|
||||||
|
this.unshiftItems(apiRes.toReversed());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public trim(trigger = true): void {
|
public trim(trigger = true): void {
|
||||||
|
@ -309,13 +381,13 @@ export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateItem(id: string, updator: (item: T) => T): void {
|
public updateItem(id: string, updater: (item: T) => T): void {
|
||||||
// TODO: queueのも更新
|
// TODO: queueのも更新
|
||||||
|
|
||||||
const index = this.items.value.findIndex(x => x.id === id);
|
const index = this.items.value.findIndex(x => x.id === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
const item = this.items.value[index]!;
|
const item = this.items.value[index]!;
|
||||||
this.items.value[index] = updator(item);
|
this.items.value[index] = updater(item);
|
||||||
if (this.useShallowRef) triggerRef(this.items);
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1805,6 +1805,8 @@ declare namespace entities {
|
||||||
FlashMyResponse,
|
FlashMyResponse,
|
||||||
FlashMyLikesRequest,
|
FlashMyLikesRequest,
|
||||||
FlashMyLikesResponse,
|
FlashMyLikesResponse,
|
||||||
|
FlashSearchRequest,
|
||||||
|
FlashSearchResponse,
|
||||||
FlashShowRequest,
|
FlashShowRequest,
|
||||||
FlashShowResponse,
|
FlashShowResponse,
|
||||||
FlashUnlikeRequest,
|
FlashUnlikeRequest,
|
||||||
|
@ -2286,6 +2288,12 @@ type FlashMyRequest = operations['flash___my']['requestBody']['content']['applic
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
|
type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type FlashSearchRequest = operations['flash___search']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type FlashSearchResponse = operations['flash___search']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
|
type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.6.4-alpha.3",
|
"version": "2025.7.0-beta.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -2438,6 +2438,17 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
request<E extends 'flash/search', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -340,6 +340,8 @@ import type {
|
||||||
FlashMyResponse,
|
FlashMyResponse,
|
||||||
FlashMyLikesRequest,
|
FlashMyLikesRequest,
|
||||||
FlashMyLikesResponse,
|
FlashMyLikesResponse,
|
||||||
|
FlashSearchRequest,
|
||||||
|
FlashSearchResponse,
|
||||||
FlashShowRequest,
|
FlashShowRequest,
|
||||||
FlashShowResponse,
|
FlashShowResponse,
|
||||||
FlashUnlikeRequest,
|
FlashUnlikeRequest,
|
||||||
|
@ -869,6 +871,7 @@ export type Endpoints = {
|
||||||
'flash/like': { req: FlashLikeRequest; res: EmptyResponse };
|
'flash/like': { req: FlashLikeRequest; res: EmptyResponse };
|
||||||
'flash/my': { req: FlashMyRequest; res: FlashMyResponse };
|
'flash/my': { req: FlashMyRequest; res: FlashMyResponse };
|
||||||
'flash/my-likes': { req: FlashMyLikesRequest; res: FlashMyLikesResponse };
|
'flash/my-likes': { req: FlashMyLikesRequest; res: FlashMyLikesResponse };
|
||||||
|
'flash/search': { req: FlashSearchRequest; res: FlashSearchResponse };
|
||||||
'flash/show': { req: FlashShowRequest; res: FlashShowResponse };
|
'flash/show': { req: FlashShowRequest; res: FlashShowResponse };
|
||||||
'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse };
|
'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse };
|
||||||
'flash/update': { req: FlashUpdateRequest; res: EmptyResponse };
|
'flash/update': { req: FlashUpdateRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -343,6 +343,8 @@ export type FlashMyRequest = operations['flash___my']['requestBody']['content'][
|
||||||
export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
|
export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
|
||||||
export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json'];
|
export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json'];
|
||||||
export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json'];
|
export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json'];
|
||||||
|
export type FlashSearchRequest = operations['flash___search']['requestBody']['content']['application/json'];
|
||||||
|
export type FlashSearchResponse = operations['flash___search']['responses']['200']['content']['application/json'];
|
||||||
export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
|
export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
|
||||||
export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json'];
|
export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json'];
|
||||||
export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json'];
|
export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json'];
|
||||||
|
|
|
@ -1997,6 +1997,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['flash___my-likes'];
|
post: operations['flash___my-likes'];
|
||||||
};
|
};
|
||||||
|
'/flash/search': {
|
||||||
|
/**
|
||||||
|
* flash/search
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
post: operations['flash___search'];
|
||||||
|
};
|
||||||
'/flash/show': {
|
'/flash/show': {
|
||||||
/**
|
/**
|
||||||
* flash/show
|
* flash/show
|
||||||
|
@ -21394,6 +21403,7 @@ export interface operations {
|
||||||
untilId?: string;
|
untilId?: string;
|
||||||
sinceDate?: number;
|
sinceDate?: number;
|
||||||
untilDate?: number;
|
untilDate?: number;
|
||||||
|
search?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -21458,6 +21468,79 @@ export interface operations {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
flash___search: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
query: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
sinceId?: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
untilId?: string;
|
||||||
|
sinceDate?: number;
|
||||||
|
untilDate?: number;
|
||||||
|
/** @default 5 */
|
||||||
|
limit?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Flash'][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
flash___show: {
|
flash___show: {
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
|
|
Loading…
Reference in New Issue