Compare commits

..

2 commits

Author SHA1 Message Date
syuilo ccf34f901a Update CHANGELOG.md 2024-10-31 13:31:47 +09:00
syuilo 8a9afb8f43 wip 2024-10-30 20:29:14 +09:00
111 changed files with 338 additions and 731 deletions

View file

@ -5,7 +5,7 @@
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20.16.0"
"version": "22.11.0"
},
"ghcr.io/devcontainers-contrib/features/corepack:1": {}
},

View file

@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
api-json-name: [api-base.json, api-head.json]
include:
- api-json-name: api-base.json

View file

@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1

View file

@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
services:
postgres:
@ -71,7 +71,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
services:
postgres:

View file

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4
with:

View file

@ -26,7 +26,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
@ -61,7 +61,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
browser: [chrome]
services:

View file

@ -21,7 +21,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

View file

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1

View file

@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
node-version: [20.16.0]
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1

View file

@ -1 +1 @@
20.16.0
22.11.0

View file

@ -1,10 +1,8 @@
## 2024.11.0
## 2024.10.2
### General
- Feat: コンテンツの表示にログインを必須にできるように
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
- Enhance: 依存関係の更新
- Enhance: l10nの更新
### Client
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
@ -18,34 +16,22 @@
- 認証するアカウントを切り替えられるように
- Enhance: Self-XSS防止用の警告を追加
- Enhance: カタルーニャ語 (ca-ES) に対応
- Enhance: 個別お知らせページではMetaタグを出力するように
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)
- Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正
- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used
- Fix: リンク切れを修正
= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/305)
- Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正
### Server
- Enhance: DockerのNode.jsを22.11.0に更新
- Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588)
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715)
- Enhance: リモートユーザーの照会をオリジナルにリダイレクトするように
- Fix: フォロワーへのメッセージの絵文字をemojisに含めるように
- Fix: Nested proxy requestsを検出した際にブロックするように
[ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)
- Fix: 招待コードの発行可能な残り数算出に使用すべきロールポリシーの値が違う問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/706)
- Fix: 連合への配信時に、acctの大小文字が区別されてしまい正しくメンションが処理されないことがある問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/711)
- Fix: ローカルユーザーへのメンションを含むートが連合される際に正しいURLに変換されないことがある問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712)
- Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709)
- Fix: User Webhookテスト機能のMock Payloadを修正
### Misskey.js
- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正

View file

@ -83,10 +83,6 @@ One should not add property that has defined before by other implementation, or
## Reviewers guide
Be willing to comment on the good points and not just the things you want fixed 💯
読んでおくといいやつ
- https://blog.lacolaco.net/posts/1e2cf439b3c2/
- https://konifar-zatsu.hatenadiary.jp/entry/2024/11/05/192421
### Review perspective
- Scope
- Are the goals of the PR clear?

View file

@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.4
ARG NODE_VERSION=20.16.0-bullseye
ARG NODE_VERSION=22.11.0-bullseye
# build assets & compile TypeScript

View file

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2024.11.0-alpha.0",
"version": "2024.10.2-alpha.1",
"codename": "nasubi",
"repository": {
"type": "git",

View file

@ -406,10 +406,8 @@ export class MfmService {
mention: (node) => {
const a = doc.createElement('a');
const { username, host, acct } = node.props;
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
a.setAttribute('href', remoteUserInfo
? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri)
: `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`);
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
a.className = 'u-url mention';
a.textContent = acct;
return a;

View file

@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import type { IActivity } from '@/core/activitypub/type.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiWebhook, WebhookEventTypes, webhookEventTypes } from '@/models/Webhook.js';
import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
@ -35,7 +35,6 @@ import type {
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import { type UserWebhookPayload } from './UserWebhookService.js';
@Injectable()
export class QueueService {
@ -469,10 +468,10 @@ export class QueueService {
* @see UserWebhookDeliverProcessorService
*/
@bindThis
public userWebhookDeliver<T extends WebhookEventTypes>(
public userWebhookDeliver(
webhook: MiWebhook,
type: T,
content: UserWebhookPayload<T>,
type: typeof webhookEventTypes[number],
content: unknown,
opts?: { attempts?: number },
) {
const data: UserWebhookDeliverJobData = {

View file

@ -6,23 +6,11 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { type WebhooksRepository } from '@/models/_.js';
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import { MiWebhook } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'note' | 'reply' | 'renote' |'mention' ? {
note: Packed<'Note'>,
} :
T extends 'follow' | 'unfollow' ? {
user: Packed<'UserDetailedNotMe'>,
} :
T extends 'followed' ? {
user: Packed<'UserLite'>,
} : never;
@Injectable()
export class UserWebhookService implements OnApplicationShutdown {

View file

@ -10,7 +10,7 @@ import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWeb
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
@ -306,10 +306,10 @@ export class WebhookTestService {
* - on
*/
@bindThis
public async testUserWebhook<T extends WebhookEventTypes>(
public async testUserWebhook(
params: {
webhookId: MiWebhook['id'],
type: T,
type: WebhookEventTypes,
override?: Partial<Omit<MiWebhook, 'id'>>,
},
sender: MiUser | null,
@ -321,7 +321,7 @@ export class WebhookTestService {
}
const webhook = webhooks[0];
const send = <U extends WebhookEventTypes>(type: U, contents: UserWebhookPayload<U>) => {
const send = (contents: unknown) => {
const merged = {
...webhook,
...params.override,
@ -329,7 +329,7 @@ export class WebhookTestService {
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図.
// また、Jobの試行回数も1回だけ.
this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 });
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
};
const dummyNote1 = generateDummyNote({
@ -361,40 +361,33 @@ export class WebhookTestService {
switch (params.type) {
case 'note': {
send('note', { note: toPackedNote(dummyNote1) });
send(toPackedNote(dummyNote1));
break;
}
case 'reply': {
send('reply', { note: toPackedNote(dummyReply1) });
send(toPackedNote(dummyReply1));
break;
}
case 'renote': {
send('renote', { note: toPackedNote(dummyRenote1) });
send(toPackedNote(dummyRenote1));
break;
}
case 'mention': {
send('mention', { note: toPackedNote(dummyMention1) });
send(toPackedNote(dummyMention1));
break;
}
case 'follow': {
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
send(toPackedUserDetailedNotMe(dummyUser1));
break;
}
case 'followed': {
send('followed', { user: toPackedUserLite(dummyUser2) });
send(toPackedUserLite(dummyUser2));
break;
}
case 'unfollow': {
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
send(toPackedUserDetailedNotMe(dummyUser3));
break;
}
// まだ実装されていない (#9485)
case 'reaction': return;
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveAssertion: never = params.type;
return;
}
}
}

View file

@ -4,5 +4,5 @@
*/
export function sqlLikeEscape(s: string) {
return s.replace(/([\\%_])/g, '\\$1');
return s.replace(/([%_])/g, '\\$1');
}

View file

@ -29,7 +29,6 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import * as Acct from '@/misc/acct.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
import type { FindOptionsWhere } from 'typeorm';
@ -487,16 +486,6 @@ export class ActivityPubServerService {
return;
}
// リモートだったらリダイレクト
if (user.host != null) {
if (user.uri == null || this.utilityService.isSelfHost(user.host)) {
reply.code(500);
return;
}
reply.redirect(user.uri, 301);
return;
}
reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
@ -665,20 +654,19 @@ export class ActivityPubServerService {
const user = await this.usersRepository.findOneBy({
id: userId,
host: IsNull(),
isSuspended: false,
});
return await this.userInfo(request, reply, user);
});
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept');
const acct = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOneBy({
usernameLower: acct.username,
host: acct.host ?? IsNull(),
usernameLower: request.params.user.toLowerCase(),
host: IsNull(),
isSuspended: false,
});

View file

@ -465,7 +465,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const newName = updates.name === undefined ? user.name : updates.name;
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields;
const newFollowedMessage = profileUpdates.followedMessage === undefined ? profile.followedMessage : profileUpdates.followedMessage;
if (newName != null) {
let hasProhibitedWords = false;
@ -495,11 +494,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
]);
}
if (newFollowedMessage != null) {
const tokens = mfm.parse(newFollowedMessage);
emojis = emojis.concat(extractCustomEmojisFromMfm(tokens));
}
updates.emojis = emojis;
updates.tags = tags;

View file

@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.activeUsersChart.read(me);
return await this.noteEntityService.packMany(timeline, me);
await this.noteEntityService.packMany(timeline, me);
}
const timeline = await this.fanoutTimelineEndpointService.timeline({

View file

@ -42,26 +42,13 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type {
AnnouncementsRepository,
ChannelsRepository,
ClipsRepository,
FlashsRepository,
GalleryPostsRepository,
MiMeta,
NotesRepository,
PagesRepository,
ReversiGamesRepository,
UserProfilesRepository,
UsersRepository,
} from '@/models/_.js';
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { RoleService } from '@/core/RoleService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
@ -116,9 +103,6 @@ export class ClientServerService {
@Inject(DI.reversiGamesRepository)
private reversiGamesRepository: ReversiGamesRepository,
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private flashEntityService: FlashEntityService,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
@ -128,7 +112,6 @@ export class ClientServerService {
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
private reversiGameEntityService: ReversiGameEntityService,
private announcementEntityService: AnnouncementEntityService,
private urlPreviewService: UrlPreviewService,
private feedService: FeedService,
private roleService: RoleService,
@ -793,24 +776,6 @@ export class ClientServerService {
return await renderBase(reply);
}
});
// 個別お知らせページ
fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => {
const announcement = await this.announcementsRepository.findOneBy({
id: request.params.announcementId,
});
if (announcement) {
const _announcement = await this.announcementEntityService.pack(announcement);
reply.header('Cache-Control', 'public, max-age=3600');
return await reply.view('announcement', {
announcement: _announcement,
...await this.generateCommonPugData(this.meta),
});
} else {
return await renderBase(reply);
}
});
//#endregion
//#region noindex pages

View file

@ -1,21 +0,0 @@
extends ./base
block vars
- const title = announcement.title;
- const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text;
- const url = `${config.url}/announcements/${announcement.id}`;
block title
= `${title} | ${instanceName}`
block desc
meta(name='description' content=description)
block og
meta(property='og:type' content='article')
meta(property='og:title' content= title)
meta(property='og:description' content= description)
meta(property='og:url' content= url)
if announcement.imageUrl
meta(property='og:image' content=announcement.imageUrl)
meta(property='twitter:card' content='summary_large_image')

View file

@ -2,7 +2,6 @@ block vars
block loadClientEntry
- const entry = config.frontendEntry;
- const baseUrl = config.url;
doctype html
@ -33,7 +32,7 @@ html
link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
link(rel='manifest' href='/manifest.json')
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`)
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
link(rel='prefetch' href=serverErrorImageUrl)
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)

View file

@ -230,7 +230,6 @@ describe('Webリソース', () => {
path: path('xxxxxxxxxx'),
type: HTML,
}));
test.todo('HTMLとしてGETできる。(リモートユーザーでもリダイレクトせず)');
});
describe.each([
@ -250,7 +249,6 @@ describe('Webリソース', () => {
path: path('xxxxxxxxxx'),
accept,
}));
test.todo('はオリジナルにリダイレクトされる。(リモートユーザー)');
});
});

View file

@ -7,7 +7,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { beforeAll, describe, jest } from '@jest/globals';
import { WebhookTestService } from '@/core/WebhookTestService.js';
import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { UserWebhookService } from '@/core/UserWebhookService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { GlobalModule } from '@/GlobalModule.js';
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
@ -122,7 +122,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('note');
expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1');
expect((calls[2] as any).id).toBe('dummy-note-1');
});
test('reply', async () => {
@ -131,7 +131,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('reply');
expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1');
expect((calls[2] as any).id).toBe('dummy-reply-1');
});
test('renote', async () => {
@ -140,7 +140,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('renote');
expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1');
expect((calls[2] as any).id).toBe('dummy-renote-1');
});
test('mention', async () => {
@ -149,7 +149,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('mention');
expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1');
expect((calls[2] as any).id).toBe('dummy-mention-1');
});
test('follow', async () => {
@ -158,7 +158,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('follow');
expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1');
expect((calls[2] as any).id).toBe('dummy-user-1');
});
test('followed', async () => {
@ -167,7 +167,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('followed');
expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2');
expect((calls[2] as any).id).toBe('dummy-user-2');
});
test('unfollow', async () => {
@ -176,7 +176,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('unfollow');
expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3');
expect((calls[2] as any).id).toBe('dummy-user-3');
});
describe('NoSuchWebhookError', () => {

View file

@ -22,66 +22,23 @@ type Account = Misskey.entities.MeDetailed & { token: string };
const accountData = miLocalStorage.getItem('account');
// TODO: 外部からはreadonlyに
/**
* Reactive state for the current account. "I" as in "I am logged in".
* Initialized from local storage if available, otherwise null.
*
* @type {Account | null}
*/
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
/**
* Whether the current account is a moderator.
*
* @type {boolean}
*/
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
/**
* Whether the current account is an administrator.
*
* @type {boolean}
*/
export const iAmAdmin = $i != null && $i.isAdmin;
/**
* Whether it is necessary to sign in; checks if the current
* account is null and throws an error if so.
*
* @throws {Error} If the current account is null
* @returns {Account} The current account
*/
export function signinRequired() {
if ($i == null) throw new Error('signin required');
return $i;
}
/**
* Extracts the current number of notes from the current account.
*
* Note: This appears to only be used for the "notes1" achievement.
*
* Also, separating it like this might cause counts to get out-of-sync.
*/
export let notesCount = $i == null ? 0 : $i.notesCount;
/**
* Increments the number of notes by one.
*
* Documentation TODO: What about $i.notesCount? Why not increment that?
*/
export function incNotesCount() {
notesCount++;
}
export async function signout() {
// If we're not signed in, there's nothing to do.
if (!$i) {
// Error log:
console.error('signout() called when not signed in');
return;
}
if (!$i) return;
waiting();
miLocalStorage.removeItem('account');

View file

@ -15,7 +15,7 @@ import { updateI18n, i18n } from '@/i18n.js';
import { $i, refreshAccount, login } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { fetchInstance, instance } from '@/instance.js';
import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js';
import { deviceKind } from '@/scripts/device-kind.js';
import { reloadChannel } from '@/scripts/unison-reload.js';
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
@ -185,10 +185,6 @@ export async function common(createVue: () => App<Element>) {
}
});
watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
updateDeviceKind(kind);
}, { immediate: true });
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true });

View file

@ -160,7 +160,7 @@ async function deleteAntenna() {
function addUser() {
os.selectUser({ includeSelf: true }).then(user => {
users.value = users.value.trim();
users.value += '\n@' + Misskey.acct.toString(user);
users.value += '\n@' + Misskey.acct.toString(user as any);
users.value = users.value.trim();
});
}

View file

@ -47,12 +47,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
const props = defineProps<{
channel: Misskey.entities.Channel;
channel: Record<string, any>;
}>();
const getLastReadedAt = (): number | null => {

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:withOkButton="true"
@close="cancel()"
@ok="ok()"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header>{{ i18n.ts.cropImage }}</template>
<template #default="{ width, height }">

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')">
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
<template #header>:{{ emoji.name }}:</template>
<template #default>
<MkSpacer>

View file

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkSelect>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason != null" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
</div>
<div v-if="actions" :class="$style.buttons">
@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{
text: string;
primary?: boolean,
danger?: boolean,
callback: (...args: unknown[]) => void;
callback: (...args: any[]) => void;
}[];
showOkButton?: boolean;
showCancelButton?: boolean;

View file

@ -157,7 +157,7 @@ const ilFilesObserver = new IntersectionObserver(
(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(),
);
const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
const sortModeSelect = ref('+createdAt');
watch(folder, () => emit('cd', folder.value));
watch(sortModeSelect, () => {
@ -198,7 +198,7 @@ function onStreamDriveFolderDeleted(folderId: string) {
removeFolder(folderId);
}
function onDragover(ev: DragEvent) {
function onDragover(ev: DragEvent): any {
if (!ev.dataTransfer) return;
//
@ -243,7 +243,7 @@ function onDragleave() {
draghover.value = false;
}
function onDrop(ev: DragEvent) {
function onDrop(ev: DragEvent): any {
draghover.value = false;
if (!ev.dataTransfer) return;
@ -332,7 +332,7 @@ function createFolder() {
title: i18n.ts.createFolder,
placeholder: i18n.ts.folderName,
}).then(({ canceled, result: name }) => {
if (canceled || name == null) return;
if (canceled) return;
misskeyApi('drive/folders/create', {
name: name,
parentId: folder.value ? folder.value.id : undefined,

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:scroll="false"
:withOkButton="false"
@close="cancel()"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>

View file

@ -90,7 +90,7 @@ function computeButtonTitle(ev: MouseEvent): void {
elm.title = getEmojiName(emoji);
}
function nestedChosen(emoji: string, ev: MouseEvent) {
function nestedChosen(emoji: any, ev: MouseEvent) {
emit('chosen', emoji, ev);
}
</script>

View file

@ -409,7 +409,7 @@ function computeButtonTitle(ev: MouseEvent): void {
elm.title = getEmojiName(emoji);
}
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
function chosen(emoji: any, ev?: MouseEvent) {
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
@ -426,7 +426,7 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
// 使
if (!pinned.value?.includes(key)) {
let recents = defaultStore.state.recentlyUsedEmojis;
recents = recents.filter((emoji) => emoji !== key);
recents = recents.filter((emoji: any) => emoji !== key);
recents.unshift(key);
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
}

View file

@ -73,7 +73,7 @@ export type Extension = {
author: string;
description?: string;
permissions?: string[];
config?: Record<string, unknown>;
config?: Record<string, any>;
};
} | {
type: 'theme';

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@click="cancel()"
@ok="ok()"
@close="cancel()"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header>
{{ title }}

View file

@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue';
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { useInterval } from '@@/js/use-interval.js';
@ -53,7 +53,7 @@ import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
const props = defineProps<{
modelValue: string | number | null;
type?: InputHTMLAttributes['type'];
type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local';
required?: boolean;
readonly?: boolean;
disabled?: boolean;
@ -64,8 +64,8 @@ const props = defineProps<{
mfmAutocomplete?: boolean | SuggestionType[],
autocapitalize?: string;
spellcheck?: boolean;
inputmode?: InputHTMLAttributes['inputmode'];
step?: InputHTMLAttributes['step'];
inputmode?: 'none' | 'text' | 'search' | 'email' | 'url' | 'numeric' | 'tel' | 'decimal';
step?: any;
datalist?: string[];
min?: number;
max?: number;

View file

@ -118,7 +118,7 @@ import { hms } from '@/filters/hms.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js';
import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
import hasAudio from '@/scripts/media-has-audio.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
import { $i, iAmModerator } from '@/account.js';
@ -334,21 +334,26 @@ function togglePlayPause() {
}
function toggleFullscreen() {
if (playerEl.value == null || videoEl.value == null) return;
if (isFullscreen.value) {
exitFullscreen({
videoEl: videoEl.value,
});
isFullscreen.value = false;
} else {
requestFullscreen({
videoEl: videoEl.value,
playerEl: playerEl.value,
options: {
navigationUI: 'hide',
},
});
isFullscreen.value = true;
if (isFullscreenNotSupported && videoEl.value) {
if (isFullscreen.value) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
videoEl.value.webkitExitFullscreen();
isFullscreen.value = false;
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
videoEl.value.webkitEnterFullscreen();
isFullscreen.value = true;
}
} else if (playerEl.value) {
if (isFullscreen.value) {
document.exitFullscreen();
isFullscreen.value = false;
} else {
playerEl.value.requestFullscreen({ navigationUI: 'hide' });
isFullscreen.value = true;
}
}
}
@ -449,10 +454,8 @@ watch(loop, (to) => {
});
watch(hide, (to) => {
if (videoEl.value && to && isFullscreen.value) {
exitFullscreen({
videoEl: videoEl.value,
});
if (to && isFullscreen.value) {
document.exitFullscreen();
isFullscreen.value = false;
}
});

View file

@ -227,7 +227,7 @@ const emit = defineEmits<{
}>();
const inTimeline = inject<boolean>('inTimeline', false);
const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true));
const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(false));
const inChannel = inject('inChannel', null);
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
@ -292,18 +292,15 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
*/
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
if (mutedWords != null) {
if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
}
if (mutedWords == null) return false;
if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
if (checkOnly) return false;
if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
return 'sensitiveMute';
}
if (inTimeline && !tl_withSensitive.value && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
return false;
}

View file

@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);
const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
function ok() {
emit('done', {

View file

@ -39,7 +39,7 @@ import number from '@/filters/number.js';
import XValue from '@/components/MkObjectView.value.vue';
const props = defineProps<{
value: unknown;
value: any;
}>();
const collapsed = reactive({});
@ -50,19 +50,19 @@ if (isObject(props.value)) {
}
}
function isObject(v: unknown): v is Record<PropertyKey, unknown> {
function isObject(v): boolean {
return typeof v === 'object' && !Array.isArray(v) && v !== null;
}
function isArray(v: unknown): v is unknown[] {
function isArray(v): boolean {
return Array.isArray(v);
}
function isEmpty(v: unknown): v is Record<PropertyKey, never> | never[] {
function isEmpty(v): boolean {
return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
}
function collapsable(v: unknown): boolean {
function collapsable(v): boolean {
return (isObject(v) || isArray(v)) && !isEmpty(v);
}
</script>

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:buttonsLeft="buttonsLeft"
:buttonsRight="buttonsRight"
:contextmenu="contextmenu"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header>
<template v-if="pageMetadata">
@ -30,17 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { url } from '@@/js/config.js';
import { getScrollContainer } from '@@/js/scroll.js';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { url } from '@@/js/config.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
@ -48,7 +48,7 @@ const props = defineProps<{
initialPath: string;
}>();
const emit = defineEmits<{
defineEmits<{
(ev: 'closed'): void;
}>();
@ -58,7 +58,7 @@ const windowRouter = routerFactory(props.initialPath);
const contents = shallowRef<HTMLElement | null>(null);
const pageMetadata = ref<null | PageMetadata>(null);
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: string; }[]>([{
const history = ref<{ path: string; key: any; }[]>([{
path: windowRouter.getCurrentPath(),
key: windowRouter.getCurrentKey(),
}]);

View file

@ -19,7 +19,7 @@ defineProps<{
items: MenuItem[];
align?: 'center' | string;
width?: number;
src?: HTMLElement | null;
src?: any;
returnFocusTo?: HTMLElement | null;
}>();

View file

@ -129,13 +129,25 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
import type { PostFormProps } from '@/types/post-form.js';
const $i = signinRequired();
const modal = inject('modal');
const props = withDefaults(defineProps<PostFormProps & {
const props = withDefaults(defineProps<{
reply?: Misskey.entities.Note;
renote?: Misskey.entities.Note;
channel?: Misskey.entities.Channel; // TODO
mention?: Misskey.entities.User;
specified?: Misskey.entities.UserDetailed;
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[];
initialNote?: Misskey.entities.Note;
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;
freezeAfterPosted?: boolean;
@ -943,8 +955,8 @@ function showActions(ev: MouseEvent) {
action.handler({
text: text.value,
cw: cw.value,
}, (key, value) => {
if (typeof key !== 'string' || typeof value !== 'string') return;
}, (key, value: any) => {
if (typeof key !== 'string') return;
if (key === 'text') { text.value = value; }
if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
});
@ -1108,7 +1120,7 @@ defineExpose({
&:focus-visible {
outline: none;
> .submitInner {
.submitInner {
outline: 2px solid var(--MI_THEME-fgOnAccent);
outline-offset: -4px;
}
@ -1123,13 +1135,13 @@ defineExpose({
}
&:not(:disabled):hover {
> .submitInner {
> .inner {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
}
&:not(:disabled):active {
> .submitInner {
> .inner {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
}

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div v-show="props.modelValue.length != 0" :class="$style.root">
<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
<template #item="{ element }">
<template #item="{element}">
<div
:class="$style.file"
role="button"
@ -38,14 +38,14 @@ import type { MenuItem } from '@/types/menu.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
const props = defineProps<{
modelValue: Misskey.entities.DriveFile[];
modelValue: any[];
detachMediaFn?: (id: string) => void;
}>();
const mock = inject<boolean>('mock', false);
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
(ev: 'update:modelValue', value: any[]): void;
(ev: 'detach', id: string): void;
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
@ -113,7 +113,7 @@ async function rename(file) {
});
}
async function describe(file: Misskey.entities.DriveFile) {
async function describe(file) {
if (mock) return;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {

View file

@ -11,11 +11,23 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkModal from '@/components/MkModal.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import type { PostFormProps } from '@/types/post-form.js';
const props = withDefaults(defineProps<PostFormProps & {
const props = withDefaults(defineProps<{
reply?: Misskey.entities.Note;
renote?: Misskey.entities.Note;
channel?: any; // TODO
mention?: Misskey.entities.User;
specified?: Misskey.entities.UserDetailed;
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
initialVisibleUsers?: Misskey.entities.UserDetailed[];
initialNote?: Misskey.entities.Note;
instant?: boolean;
fixed?: boolean;
autofocus?: boolean;

View file

@ -24,17 +24,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup generic="T extends unknown">
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps<{
modelValue: T;
value: T;
modelValue: any;
value: any;
disabled?: boolean;
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: T): void;
(ev: 'update:modelValue', value: any): void;
}>();
const checked = computed(() => props.modelValue === props.value);

View file

@ -23,7 +23,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as Misskey from 'misskey-js';
import { getEmojiName } from '@@/js/emojilist.js';
import MkTooltip from './MkTooltip.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
@ -31,7 +30,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
defineProps<{
showing: boolean;
reaction: string;
users: Misskey.entities.UserLite[];
users: any[]; // TODO
count: number;
targetElement: HTMLElement;
}>();

View file

@ -277,7 +277,7 @@ async function onSubmit(): Promise<void> {
return null;
});
if (res && res.ok) {
if (res) {
if (res.status === 204 || instance.emailRequiredForSignup) {
os.alert({
type: 'success',
@ -295,8 +295,6 @@ async function onSubmit(): Promise<void> {
await login(resJson.token);
}
}
} else {
onSignupApiError();
}
submitting.value = false;

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:width="500"
:height="600"
@close="onClose"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header>{{ i18n.ts.signup }}</template>

View file

@ -28,38 +28,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts">
export type SuperMenuDef = {
title?: string;
items: ({
type: 'a';
href: string;
target?: string;
icon?: string;
text: string;
danger?: boolean;
active?: boolean;
} | {
type: 'button';
icon?: string;
text: string;
danger?: boolean;
active?: boolean;
action: (ev: MouseEvent) => void;
} | {
type: 'link';
to: string;
icon?: string;
text: string;
danger?: boolean;
active?: boolean;
})[];
};
</script>
<script lang="ts" setup>
import { } from 'vue';
defineProps<{
def: SuperMenuDef[];
def: any[];
grid?: boolean;
}>();
</script>

View file

@ -43,7 +43,6 @@ const props = withDefaults(defineProps<{
}>(), {
withRenotes: true,
withReplies: false,
withSensitive: true,
onlyFiles: false,
});

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:okButtonDisabled="false"
:canClose="false"
@close="dialog?.close()"
@closed="emit('closed')"
@closed="$emit('closed')"
@ok="ok()"
>
<template #header>{{ title || i18n.ts.generateAccessToken }}</template>

View file

@ -180,7 +180,7 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
sensitive.value = info.sensitive ?? false;
});
function adjustTweetHeight(message: MessageEvent) {
function adjustTweetHeight(message: any) {
if (message.origin !== 'https://platform.twitter.com') return;
const embed = message.data?.['twttr.embed'];
if (embed?.method !== 'twttr.private.resize') return;
@ -193,16 +193,14 @@ function openPlayer(): void {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkYouTubePlayer.vue')), {
url: requestUrl.href,
}, {
closed: () => {
dispose();
},
// TODO
});
}
window.addEventListener('message', adjustTweetHeight);
(window as any).addEventListener('message', adjustTweetHeight);
onUnmounted(() => {
window.removeEventListener('message', adjustTweetHeight);
(window as any).removeEventListener('message', adjustTweetHeight);
});
</script>

View file

@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="dialog"
:width="400"
@close="dialog?.close()"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template v-if="announcement" #header>:{{ announcement.title }}:</template>
<template v-else #header>New announcement</template>
@ -62,16 +62,9 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; }
const props = defineProps<{
user: Misskey.entities.User,
announcement?: Required<AdminAnnouncementType>,
}>();
const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean; updated?: AdminAnnouncementType; created?: AdminAnnouncementType; }): void,
(ev: 'closed'): void
announcement?: Misskey.entities.Announcement,
}>();
const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
@ -81,6 +74,11 @@ const icon = ref(props.announcement ? props.announcement.icon : 'info');
const display = ref(props.announcement ? props.announcement.display : 'dialog');
const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
(ev: 'closed'): void
}>();
async function done() {
const params = {
title: title.value,
@ -90,7 +88,7 @@ async function done() {
display: display.value,
needConfirmationToRead: needConfirmationToRead.value,
userId: props.user.id,
} satisfies Misskey.entities.AdminAnnouncementsCreateRequest;
};
if (props.announcement) {
await os.apiWithDialog('admin/announcements/update', {

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@click="cancel()"
@close="cancel()"
@ok="ok()"
@closed="emit('closed')"
@closed="$emit('closed')"
>
<template #header>{{ i18n.ts.selectUser }}</template>
<div>

View file

@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { } from 'vue';
import MkTooltip from './MkTooltip.vue';
defineProps<{
showing: boolean;
users: Misskey.entities.UserLite[];
users: any[]; // TODO
count: number;
targetElement: HTMLElement;
}>();

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
</MkSelect>
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
</header>
<Sortable
:modelValue="props.widgets"

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
appear
@afterLeave="emit('closed')"
@afterLeave="$emit('closed')"
>
<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
@ -60,13 +60,6 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
type WindowButton = {
title: string;
icon: string;
onClick: () => void;
highlighted?: boolean;
};
const minHeight = 50;
const minWidth = 250;
@ -94,8 +87,8 @@ const props = withDefaults(defineProps<{
mini?: boolean;
front?: boolean;
contextmenu?: MenuItem[] | null;
buttonsLeft?: WindowButton[];
buttonsRight?: WindowButton[];
buttonsLeft?: any[];
buttonsRight?: any[];
}>(), {
initialWidth: 400,
initialHeight: null,

View file

@ -18,19 +18,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup generic="T extends unknown">
<script lang="ts" setup>
import { ref, watch } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
const props = defineProps<{
p: () => Promise<T>;
p: () => Promise<any>;
}>();
const pending = ref(true);
const resolved = ref(false);
const rejected = ref(false);
const result = ref<T | null>(null);
const result = ref<any>(null);
const process = () => {
if (props.p == null) {

View file

@ -467,8 +467,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}
default: {
// @ts-expect-error 存在しないASTタイプ
console.error('unrecognized ast type:', token.type);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
console.error('unrecognized ast type:', (token as any).type);
return [];
}

View file

@ -3,9 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* A typesafe enum of keys for localStorage.
*/
export type Keys =
'v' |
'lastVersion' |
@ -47,45 +44,16 @@ export type Keys =
// セッション毎に廃棄されるLocalStorage代替セーフモードなどで使用できそう
//const safeSessionStorage = new Map<Keys, string>();
/**
* A utility object for interacting with the browser's localStorage.
*
* It's mostly a small wrapper around window.localStorage, but it validates
* keys with a typesafe enum, and provides a few convenience methods for JSON.
*/
export const miLocalStorage = {
/**
* Retrieves an item from localStorage.
* @param {Keys} key - The key of the item to retrieve.
* @returns {string | null} The value of the item, or null if the item does not exist.
*/
getItem: (key: Keys): string | null => {
return window.localStorage.getItem(key);
},
/**
* Stores an item in localStorage.
* @param {Keys} key - The key of the item to store.
* @param {string} value - The value of the item to store.
*/
setItem: (key: Keys, value: string): void => {
window.localStorage.setItem(key, value);
},
/**
* Removes an item from localStorage.
* @param {Keys} key - The key of the item to remove.
*/
removeItem: (key: Keys): void => {
window.localStorage.removeItem(key);
},
/**
* Retrieves an item from localStorage and parses it as JSON.
* @param {Keys} key - The key of the item to retrieve.
* @returns {any | undefined} The parsed value of the item, or undefined if the item does not exist.
*/
getItemAsJson: (key: Keys): any | undefined => {
const item = miLocalStorage.getItem(key);
if (item === null) {
@ -93,12 +61,6 @@ export const miLocalStorage = {
}
return JSON.parse(item);
},
/**
* Stores an item in localStorage as a JSON string.
* @param {Keys} key - The key of the item to store.
* @param {any} value - The value of the item to store.
*/
setItemAsJson: (key: Keys, value: any): void => {
miLocalStorage.setItem(key, JSON.stringify(value));
},

View file

@ -36,8 +36,6 @@ interface RouteDefWithRedirect extends RouteDefBase {
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
export type RouterFlag = 'forcePage';
type ParsedPath = (string | {
name: string;
startsWith?: string;
@ -109,7 +107,7 @@ export interface IRouter extends EventEmitter<RouterEvent> {
current: Resolved;
currentRef: ShallowRef<Resolved>;
currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: RouterFlag) => boolean) | null;
navHook: ((path: string, flag?: any) => boolean) | null;
/**
* eventListenerの定義後に必ず呼び出すこと
@ -118,11 +116,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
resolve(path: string): Resolved | null;
getCurrentPath(): string;
getCurrentPath(): any;
getCurrentKey(): string;
push(path: string, flag?: RouterFlag): void;
push(path: string, flag?: any): void;
replace(path: string, key?: string | null): void;
@ -199,7 +197,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
private currentKey = Date.now().toString();
private redirectCount = 0;
public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
public navHook: ((path: string, flag?: any) => boolean) | null = null;
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
super();
@ -406,7 +404,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return this.currentKey;
}
public push(path: string, flag?: RouterFlag) {
public push(path: string, flag?: any) {
const beforePath = this.currentPath;
if (path === beforePath) {
this.emit('same');

View file

@ -28,13 +28,12 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
import { focusParent } from '@/scripts/focus.js';
import type { PostFormProps } from '@/types/post-form.js';
export const openingWindowsCount = ref(0);
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
endpoint: E,
data: P,
data: P = {} as any,
token?: string | null | undefined,
customErrors?: Record<string, { title?: string; text: string; }>,
) => {
@ -95,7 +94,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Miss
export function promiseDialog<T extends Promise<any>>(
promise: T,
onSuccess?: ((res: Awaited<T>) => void) | null,
onSuccess?: ((res: any) => void) | null,
onFailure?: ((err: Misskey.api.APIError) => void) | null,
text?: string,
): T {
@ -136,40 +135,20 @@ export function promiseDialog<T extends Promise<any>>(
return promise;
}
/**
* Counter for generating unique popup IDs.
* @type {number}
*/
let popupIdCount = 0;
/**
* A reactive list of the currently opened popups. This is used in a Vue component
* in a v-for loop to render the popups.
*/
export const popups = ref<{
export const popups = ref([]) as Ref<{
id: number;
component: Component;
props: Record<string, any>;
events: Record<string, any>;
}[]>([]);
}[]>;
/**
* An object containing z-index values for different priority levels.
*/
const zIndexes = {
veryLow: 500000,
low: 1000000,
middle: 2000000,
high: 3000000,
};
/**
* Claims a z-index value for a given priority level.
* Increments the z-index value for the specified priority by 100 and returns the new value.
*
* @param {keyof typeof zIndexes} [priority='low'] - The priority level for which to claim a z-index.
* @returns {number} The new z-index value for the specified priority.
*/
export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
zIndexes[priority] += 100;
return zIndexes[priority];
@ -197,15 +176,6 @@ type EmitsExtractor<T> = {
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
};
/**
* Opens a popup with the specified component, props, and events.
*
* @template T - The type of the component.
* @param {T} component - The Vue component to display in the popup.
* @param {ComponentProps<T>} props - The props to pass to the component.
* @param {ComponentEmit<T>} [events={}] - The events to bind to the component.
* @returns {{ dispose: () => void }} An object containing a dispose function to close the popup.
*/
export function popup<T extends Component>(
component: T,
props: ComponentProps<T>,
@ -213,18 +183,13 @@ export function popup<T extends Component>(
): { dispose: () => void } {
markRaw(component);
// Generate a unique ID for this popup.
const id = ++popupIdCount;
// On disposal, remove this popup from the list of open popups.
const dispose = () => {
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ
window.setTimeout(() => {
popups.value = popups.value.filter(p => p.id !== id);
}, 0);
};
// Bundle the component, props, and events into a state object.
const state = {
component,
props,
@ -232,19 +197,13 @@ export function popup<T extends Component>(
id,
};
// Add the popup to the list of open popups.
popups.value.push(state);
// Return a function that can be called to close the popup.
return {
dispose,
};
}
/**
* Open the page with the given path in a pop-up window.
* @param path The path of the page to open.
*/
export function pageWindow(path: string) {
const { dispose } = popup(MkPageWindow, {
initialPath: path,
@ -253,11 +212,6 @@ export function pageWindow(path: string) {
});
}
/**
* Displays a toast message to the user.
*
* @param {string} message - The message to display in the toast.
*/
export function toast(message: string) {
const { dispose } = popup(MkToast, {
message,
@ -266,15 +220,6 @@ export function toast(message: string) {
});
}
/**
* Displays an alert dialog to the user.
*
* @param {Object} props - The properties for the alert dialog.
* @param {'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'} [props.type] - The type of the alert.
* @param {string} [props.title] - The title of the alert dialog.
* @param {string} [props.text] - The text content of the alert dialog.
* @returns {Promise<void>} A promise that resolves when the alert dialog is closed.
*/
export function alert(props: {
type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
title?: string;
@ -513,7 +458,7 @@ type SelectItem<C> = {
};
// default が指定されていたら result は null になり得ないことを保証する overload function
export function select<C = unknown>(props: {
export function select<C = any>(props: {
title?: string;
text?: string;
default: string;
@ -526,7 +471,7 @@ export function select<C = unknown>(props: {
} | {
canceled: false; result: C;
}>;
export function select<C = unknown>(props: {
export function select<C = any>(props: {
title?: string;
text?: string;
default?: string | null;
@ -539,7 +484,7 @@ export function select<C = unknown>(props: {
} | {
canceled: false; result: C | null;
}>;
export function select<C = unknown>(props: {
export function select<C = any>(props: {
title?: string;
text?: string;
default?: string | null;
@ -742,13 +687,13 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
}));
}
export function post(props: PostFormProps = {}): Promise<void> {
export function post(props: Record<string, any> = {}): Promise<void> {
pleaseLogin({
openOnRemote: (props.initialText || props.initialNote ? {
type: 'share',
params: {
text: props.initialText ?? props.initialNote?.text ?? '',
visibility: props.initialVisibility ?? props.initialNote?.visibility ?? 'public',
text: props.initialText ?? props.initialNote.text,
visibility: props.initialVisibility ?? props.initialNote?.visibility,
localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
},
} : undefined),

View file

@ -272,9 +272,6 @@ const patronsWithIcon = [{
}, {
name: 'Yatoigawa',
icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg',
}, {
name: '秋瀬カヲル',
icon: 'https://assets.misskey-hub.net/patrons/0f22aeb866484f4fa51db6721e3f9847.jpg',
}];
const patrons = [
@ -383,7 +380,6 @@ const patrons = [
'ケモナーのケシン',
'こまつぶり',
'まゆつな空高',
'asata',
];
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));

View file

@ -99,19 +99,19 @@ async function addUser() {
const { canceled: canceled1, result: username } = await os.inputText({
title: i18n.ts.username,
});
if (canceled1 || username == null) return;
if (canceled1) return;
const { canceled: canceled2, result: password } = await os.inputText({
title: i18n.ts.password,
type: 'password',
});
if (canceled2 || password == null) return;
if (canceled2) return;
os.apiWithDialog('admin/accounts/create', {
username: username,
password: password,
}).then(res => {
paginationComponent.value?.reload();
paginationComponent.value.reload();
});
}

View file

@ -103,7 +103,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata(() => ({
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
</script>

View file

@ -62,7 +62,7 @@ function accepted() {
state.value = 'accepted';
if (session.value && session.value.app.callbackUrl) {
const url = new URL(session.value.app.callbackUrl);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url');
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
}
}

View file

@ -116,7 +116,7 @@ const selectAll = () => {
if (selectedEmojis.value.length > 0) {
selectedEmojis.value = [];
} else {
selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id);
selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id);
}
};
@ -133,7 +133,7 @@ const add = async (ev: MouseEvent) => {
}, {
done: result => {
if (result.created) {
emojisPaginationComponent.value?.prepend(result.created);
emojisPaginationComponent.value.prepend(result.created);
}
},
closed: () => dispose(),
@ -146,12 +146,12 @@ const edit = (emoji) => {
}, {
done: result => {
if (result.updated) {
emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({
emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
...oldEmoji,
...result.updated,
}));
} else if (result.deleted) {
emojisPaginationComponent.value?.removeItem(emoji.id);
emojisPaginationComponent.value.removeItem(emoji.id);
}
},
closed: () => dispose(),
@ -226,7 +226,7 @@ const setCategoryBulk = async () => {
ids: selectedEmojis.value,
category: result,
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const setLicenseBulk = async () => {
@ -238,43 +238,43 @@ const setLicenseBulk = async () => {
ids: selectedEmojis.value,
license: result,
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const addTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled || result == null) return;
if (canceled) return;
await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const removeTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled || result == null) return;
if (canceled) return;
await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const setTagBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'Tag',
});
if (canceled || result == null) return;
if (canceled) return;
await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
ids: selectedEmojis.value,
aliases: result.split(' '),
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const delBulk = async () => {
@ -286,7 +286,7 @@ const delBulk = async () => {
await os.apiWithDialog('admin/emoji/delete-bulk', {
ids: selectedEmojis.value,
});
emojisPaginationComponent.value?.reload();
emojisPaginationComponent.value.reload();
};
const headerActions = computed(() => [{

View file

@ -9,8 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
:initialWidth="400"
:initialHeight="500"
:canResize="true"
@close="windowEl?.close()"
@closed="emit('closed')"
@close="windowEl.close()"
@closed="$emit('closed')"
>
<template v-if="emoji" #header>:{{ emoji.name }}:</template>
<template v-else #header>New emoji</template>
@ -95,19 +95,14 @@ import { selectFile } from '@/scripts/select-file.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
const props = defineProps<{
emoji?: Misskey.entities.EmojiDetailed,
}>();
const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.AdminEmojiUpdateRequest; created?: Misskey.entities.AdminEmojiUpdateRequest }): void,
(ev: 'closed'): void
emoji?: any,
}>();
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
const name = ref<string>(props.emoji ? props.emoji.name : '');
const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
const category = ref<string>(props.emoji ? props.emoji.category : '');
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
const license = ref<string>(props.emoji?.license ? props.emoji.license : '');
const license = ref<string>(props.emoji ? (props.emoji.license ?? '') : '');
const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
@ -120,7 +115,12 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
async function changeImage(ev: Event) {
const emit = defineEmits<{
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
(ev: 'closed'): void
}>();
async function changeImage(ev) {
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
const candidate = file.value.name.replace(/\.(.+)$/, '');
if (candidate.match(/^[a-z0-9_]+$/)) {
@ -140,7 +140,7 @@ async function addRole() {
rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
}
async function removeRole(role: Misskey.entities.RoleLite, ev: Event) {
async function removeRole(role, ev) {
rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id);
}
@ -172,7 +172,7 @@ async function done() {
},
});
windowEl.value?.close();
windowEl.value.close();
} else {
const created = await os.apiWithDialog('admin/emoji/add', params);
@ -180,12 +180,11 @@ async function done() {
created: created,
});
windowEl.value?.close();
windowEl.value.close();
}
}
async function del() {
if (!props.emoji) return;
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: name.value }),
@ -198,7 +197,7 @@ async function del() {
emit('done', {
deleted: true,
});
windowEl.value?.close();
windowEl.value.close();
});
}
</script>

View file

@ -55,13 +55,13 @@ const pagination = {
function accept(user) {
misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
paginationComponent.value?.reload();
paginationComponent.value.reload();
});
}
function reject(user) {
misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
paginationComponent.value?.reload();
paginationComponent.value.reload();
});
}

View file

@ -40,7 +40,7 @@ function fetch() {
return;
}
let promise: Promise<unknown>;
let promise: Promise<any>;
if (uri.startsWith('https://')) {
promise = misskeyApi('ap/show', {

View file

@ -65,7 +65,7 @@ async function onAccept(token: string) {
if (props.callback && props.callback !== '') {
const cbUrl = new URL(props.callback);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
cbUrl.searchParams.set('session', props.session);
location.href = cbUrl.toString();
} else {

View file

@ -77,15 +77,15 @@ async function create() {
clipsCache.delete();
pagingComponent.value?.reload();
pagingComponent.value.reload();
}
function onClipCreated() {
pagingComponent.value?.reload();
pagingComponent.value.reload();
}
function onClipDeleted() {
pagingComponent.value?.reload();
pagingComponent.value.reload();
}
const headerActions = computed(() => []);

View file

@ -110,7 +110,7 @@ function addUser() {
listId: list.value.id,
userId: user.id,
}).then(() => {
paginationEl.value?.reload();
paginationEl.value.reload();
});
});
}
@ -126,7 +126,7 @@ async function removeUser(item, ev) {
listId: list.value.id,
userId: item.userId,
}).then(() => {
paginationEl.value?.removeItem(item.id);
paginationEl.value.removeItem(item.id);
});
},
}], ev.currentTarget ?? ev.target);

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => emit('remove')">
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
<template #func>
<button @click="choose()">
@ -30,12 +30,11 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'image' };
modelValue: any
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'image' }): void;
(ev: 'remove'): void;
(ev: 'update:modelValue', value: any): void;
}>();
const file = ref<Misskey.entities.DriveFile | null>(null);

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => emit('remove')">
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
<section style="padding: 16px;" class="_gaps_s">
@ -34,24 +34,19 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'note' };
modelValue: any
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void;
(ev: 'update:modelValue', value: any): void;
}>();
const id = ref(props.modelValue.note);
const id = ref<any>(props.modelValue.note);
const note = ref<Misskey.entities.Note | null>(null);
watch(id, async () => {
if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) {
id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop() ?? null;
}
if (!id.value) {
note.value = null;
return;
id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop();
}
emit('update:modelValue', {

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => emit('remove')">
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
<template #func>
<button class="_button" @click="rename()">
@ -21,9 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import XContainer from '../page-editor.container.vue';
import * as os from '@/os.js';
@ -34,13 +33,14 @@ import { getPageBlockList } from '@/pages/page-editor/common.js';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'section'; },
}>();
const props = withDefaults(defineProps<{
modelValue: any,
}>(), {
modelValue: {},
});
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
(ev: 'remove'): void;
(ev: 'update:modelValue', value: any): void;
}>();
const children = ref(deepClone(props.modelValue.children ?? []));

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => emit('remove')">
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
<section>
@ -15,19 +15,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
import * as Misskey from 'misskey-js';
import XContainer from '../page-editor.container.vue';
import { i18n } from '@/i18n.js';
import { Autocomplete } from '@/scripts/autocomplete.js';
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'text' }
modelValue: any
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void;
(ev: 'update:modelValue', value: any): void;
}>();
let autocomplete: Autocomplete;

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => emit('update:modelValue', v)">
<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)">
<template #item="{element}">
<div :class="$style.item">
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->

View file

@ -52,7 +52,7 @@ const props = defineProps<{
const scope = computed(() => props.path ? props.path.split('/') : []);
const keys = ref<[string, string][]>([]);
const keys = ref<any>(null);
function fetchKeys() {
misskeyApi('i/registry/keys-with-type', {

View file

@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.
const props = defineProps<{
game: Misskey.entities.ReversiGameDetailed;
connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>;
connection: Misskey.ChannelConnection;
}>();
const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
@ -217,14 +217,14 @@ function onChangeReadyStates(states) {
game.value.user2Ready = states.user2;
}
function updateSettings(key: typeof Misskey.reversiUpdateKeys[number]) {
function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) {
props.connection.send('updateSettings', {
key: key,
value: game.value[key],
});
}
function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) {
function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) {
if (userId === $i.id) return;
if (game.value[key] === value) return;
game.value[key] = value;

View file

@ -76,11 +76,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
const parser = new Parser();
let aiscript: Interpreter;
const code = ref('');
const logs = ref<{
id: number;
text: string;
print: boolean;
}[]>([]);
const logs = ref<any[]>([]);
const root = ref<AsUiRoot>();
const components = ref<Ref<AsUiComponent>[]>([]);
const uiKey = ref(0);

View file

@ -138,13 +138,12 @@ const token = ref<string | number | null>(null);
const backupCodes = ref<string[]>();
function cancel() {
dialog.value?.close();
dialog.value.close();
}
async function tokenDone() {
if (token.value == null) return;
const res = await os.apiWithDialog('i/2fa/done', {
token: typeof token.value === 'string' ? token.value : token.value.toString(),
token: token.value.toString(),
});
backupCodes.value = res.backupCodes;
@ -167,7 +166,7 @@ function downloadBackupCodes() {
}
function allDone() {
dialog.value?.close();
dialog.value.close();
}
</script>

View file

@ -90,7 +90,7 @@ function createAccount() {
});
}
async function switchAccount(account: Misskey.entities.UserDetailed) {
async function switchAccount(account: any) {
const fetchedAccounts = await getAccounts();
const token = fetchedAccounts.find(x => x.id === account.id)!.token;
switchAccountWithToken(token);

View file

@ -55,7 +55,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import FormPagination from '@/components/MkPagination.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
@ -78,7 +77,7 @@ const pagination = {
function revoke(token) {
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
list.value?.reload();
list.value.reload();
});
}

View file

@ -110,7 +110,7 @@ const decorationsForPreview = computed(() => {
});
function cancel() {
dialog.value?.close();
dialog.value.close();
}
async function update() {
@ -120,7 +120,7 @@ async function update() {
offsetX: offsetX.value,
offsetY: offsetY.value,
});
dialog.value?.close();
dialog.value.close();
}
async function attach() {
@ -130,12 +130,12 @@ async function attach() {
offsetX: offsetX.value,
offsetY: offsetY.value,
});
dialog.value?.close();
dialog.value.close();
}
async function detach() {
emit('detach');
dialog.value?.close();
dialog.value.close();
}
</script>

View file

@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
</div>
<div :class="$style.switchBox">
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
</div>
<div :class="$style.switchBox">

View file

@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
</div>
</FormSection>

View file

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tl">
<MkTimeline
ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
:key="src + withRenotes + withReplies + onlyFiles"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"

View file

@ -257,7 +257,7 @@ function parallaxLoop() {
}
function parallax() {
const banner = bannerEl.value;
const banner = bannerEl.value as any;
if (banner == null) return;
const top = getScrollPosition(rootEl.value);

View file

@ -241,13 +241,9 @@ export class Storage<T extends StateDef> {
* getter/setterを作ります
* vue上で設定コントロールのmodelとして使う用
*/
public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
key: K,
getter?: (v: T[K]['default']) => R,
setter?: (v: R) => T[K]['default'],
): {
get: () => R;
set: (value: R) => void;
public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
get: () => T[K]['default'];
set: (value: T[K]['default']) => void;
} {
const valueRef = ref(this.state[key]);
@ -269,7 +265,7 @@ export class Storage<T extends StateDef> {
return valueRef.value;
}
},
set: (value) => {
set: (value: unknown) => {
const val = setter ? setter(value) : value;
this.set(key, val);
valueRef.value = val;

View file

@ -10,7 +10,7 @@ import { $i, iAmModerator } from '@/account.js';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({
export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
loader: loader,
loadingComponent: MkLoading,
errorComponent: MkError,

View file

@ -4,7 +4,7 @@
*/
import { EventEmitter } from 'eventemitter3';
import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
import type { App, ShallowRef } from 'vue';
@ -79,7 +79,7 @@ class MainRouterProxy implements IRouter {
return this.supplier().currentRoute;
}
get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null {
get navHook(): ((path: string, flag?: any) => boolean) | null {
return this.supplier().navHook;
}
@ -91,11 +91,11 @@ class MainRouterProxy implements IRouter {
return this.supplier().getCurrentKey();
}
getCurrentPath(): string {
getCurrentPath(): any {
return this.supplier().getCurrentPath();
}
push(path: string, flag?: RouterFlag): void {
push(path: string, flag?: any): void {
this.supplier().push(path, flag);
}

View file

@ -2,9 +2,8 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean {
// 自分自身
if (me && (note.userId === me.id)) return false;

View file

@ -3,22 +3,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type DeviceKind = 'smartphone' | 'tablet' | 'desktop';
import { defaultStore } from '@/store.js';
await defaultStore.ready;
const ua = navigator.userAgent.toLowerCase();
const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
export const DEFAULT_DEVICE_KIND: DeviceKind = (
isSmartphone
? 'smartphone'
: isTablet
? 'tablet'
: 'desktop'
);
const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
// navigator.platform may be deprecated but this check is still required
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND;
export const isFullscreenNotSupported = isIPhone || isIos;
export function updateDeviceKind(kind: DeviceKind | null) {
deviceKind = kind ?? DEFAULT_DEVICE_KIND;
}
export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
: isSmartphone ? 'smartphone'
: isTablet ? 'tablet'
: 'desktop';

Some files were not shown because too many files have changed in this diff Show more