Merge branch 'develop' into mahjong

This commit is contained in:
syuilo 2024-02-14 21:03:58 +09:00
commit 4ae591a2c7
51 changed files with 1549 additions and 2354 deletions

113
.github/workflows/storybook.yml vendored Normal file
View File

@ -0,0 +1,113 @@
name: Storybook
on:
push:
branches:
- master
- develop
- dev/storybook8 # for testing
pull_request_target:
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--max_old_space_size=7168"
steps:
- uses: actions/checkout@v3.6.0
if: github.event_name != 'pull_request_target'
with:
fetch-depth: 0
submodules: true
- uses: actions/checkout@v3.6.0
if: github.event_name == 'pull_request_target'
with:
fetch-depth: 0
submodules: true
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Checkout actual HEAD
if: github.event_name == 'pull_request_target'
id: rev
run: |
echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Use Node.js 20.x
uses: actions/setup-node@v3.8.1
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build misskey-js
run: pnpm --filter misskey-js build
- name: Build storybook
run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master'
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master'
id: chromatic_push
run: |
DIFF="${{ github.event.before }} HEAD"
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
DIFF="HEAD"
fi
CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then
echo "success=true" >> $GITHUB_OUTPUT
else
echo "success=false" >> $GITHUB_OUTPUT
fi
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name == 'pull_request_target'
id: chromatic_pull_request
run: |
DIFF="${{ steps.rev.outputs.base }} HEAD"
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
DIFF="HEAD"
fi
CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
BRANCH="${{ github.event.pull_request.head.ref }}"
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Notify that Chromatic detects changes
uses: actions/github-script@v6.4.0
if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
})
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: storybook
path: packages/frontend/storybook-static

View File

@ -115,6 +115,7 @@ jobs:
run: pnpm exec cypress install run: pnpm exec cypress install
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v6 uses: cypress-io/github-action@v6
timeout-minutes: 15
with: with:
install: false install: false
start: pnpm start:test start: pnpm start:test

View File

@ -24,7 +24,7 @@
- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正 - Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
* すべてのリモートユーザーのリアクション一覧を見えないようにします * すべてのリモートユーザーのリアクション一覧を見えないようにします
- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように - Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207 - Fix: 特定のキーワード及び正規表現にマッチする文字列を含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
* デフォルトは空欄なので適用前と同等の動作になります * デフォルトは空欄なので適用前と同等の動作になります
### Client ### Client

View File

@ -122,6 +122,19 @@ command.
If you have not changed it from the default, it will be "http://localhost:3000". If you have not changed it from the default, it will be "http://localhost:3000".
If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts. If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
### `MK_DEV_PREFER=backend pnpm dev`
pnpm dev has another mode with `MK_DEV_PREFER=backend`.
```
MK_DEV_PREFER=backend pnpm dev
```
- This mode is closer to the production environment than the default mode.
- Vite runs behind the backend (the backend will proxy Vite at /vite).
- You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml).
- To change the port of Vite, specify with `VITE_PORT` environment variable.
- HMR may not work in some environments such as Windows.
### Dev Container ### Dev Container
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment. Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
To use Dev Container, open the project directory on VSCode with Dev Containers installed. To use Dev Container, open the project directory on VSCode with Dev Containers installed.

View File

@ -162,12 +162,12 @@ describe('After user signed in', () => {
it('successfully loads', () => { it('successfully loads', () => {
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup-continue]', { timeout: 12000 }).should('be.visible'); cy.get('[data-cy-user-setup-continue]', { timeout: 30000 }).should('be.visible');
}); });
it('account setup wizard', () => { it('account setup wizard', () => {
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup-continue]', { timeout: 12000 }).click(); cy.get('[data-cy-user-setup-continue]', { timeout: 30000 }).click();
cy.get('[data-cy-user-setup-user-name] input').type('ありす'); cy.get('[data-cy-user-setup-user-name] input').type('ありす');
cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ'); cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
@ -205,7 +205,7 @@ describe('After user setup', () => {
// アカウント初期設定ウィザード // アカウント初期設定ウィザード
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click(); cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 30000 }).click();
cy.get('[data-cy-modal-dialog-ok]').click(); cy.get('[data-cy-modal-dialog-ok]').click();
}); });

View File

@ -14,7 +14,7 @@ describe('Router transition', () => {
// アカウント初期設定ウィザード // アカウント初期設定ウィザード
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする // 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click(); cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 30000 }).click();
cy.wait(500); cy.wait(500);
cy.get('[data-cy-modal-dialog-ok]').click(); cy.get('[data-cy-modal-dialog-ok]').click();
}); });

View File

@ -14,9 +14,9 @@ export type FanoutTimelineName =
| `homeTimeline:${string}` | `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included | `homeTimelineWithFiles:${string}` // only notes with files are included
// local timeline // local timeline
| 'localTimeline' // replies are not included | `localTimeline` // replies are not included
| 'localTimelineWithFiles' // only non-reply notes with files are included | `localTimelineWithFiles` // only non-reply notes with files are included
| 'localTimelineWithReplies' // only replies are included | `localTimelineWithReplies` // only replies are included
| `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id. | `localTimelineWithReplyTo:${string}` // Only replies to specific local user are included. Parameter is reply user id.
// antenna // antenna

View File

@ -263,11 +263,9 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
} }
if (!user.host) {
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) { if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
throw new NoteCreateService.ContainsProhibitedWordsError(); throw new NoteCreateService.ContainsProhibitedWordsError();
} }
}
const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);

View File

@ -12,11 +12,11 @@ import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
import type httpSignature from '@peertube/http-signature'; import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq'; import type * as Bull from 'bullmq';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
@Injectable() @Injectable()
export class QueueService { export class QueueService {

View File

@ -31,7 +31,7 @@ export class ApMfmService {
const parsed = mfm.parse(srcMfm); const parsed = mfm.parse(srcMfm);
if (!apAppend && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
noMisskeyContent = true; noMisskeyContent = true;
} }

View File

@ -0,0 +1,9 @@
import type { onRequestHookHandler } from 'fastify';
export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
const index = request.url.indexOf('?');
if (~index) {
reply.redirect(301, request.url.slice(0, index));
}
done();
};

View File

@ -34,7 +34,7 @@ export class RelationshipProcessorService {
@bindThis @bindThis
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> { public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? 'with replies' : 'without replies'}`); this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`);
await this.userFollowingService.follow(job.data.from, job.data.to, { await this.userFollowingService.follow(job.data.from, job.data.to, {
requestId: job.data.requestId, requestId: job.data.requestId,
silent: job.data.silent, silent: job.data.silent,

View File

@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js'; import { isMimeImage } from '@/misc/is-mime-image.js';
import { correctFilename } from '@/misc/correct-filename.js'; import { correctFilename } from '@/misc/correct-filename.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
@ -67,6 +68,8 @@ export class FileServerService {
done(); done();
}); });
fastify.register((fastify, options, done) => {
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
fastify.get('/files/app-default.jpg', (request, reply) => { fastify.get('/files/app-default.jpg', (request, reply) => {
const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
reply.header('Content-Type', 'image/jpeg'); reply.header('Content-Type', 'image/jpeg');
@ -79,8 +82,9 @@ export class FileServerService {
.catch(err => this.errorHandler(request, reply, err)); .catch(err => this.errorHandler(request, reply, err));
}); });
fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
return await this.sendDriveFile(request, reply) return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
.catch(err => this.errorHandler(request, reply, err)); });
done();
}); });
fastify.get<{ fastify.get<{

View File

@ -38,7 +38,7 @@ export class NodeinfoServerService {
public getLinks() { public getLinks() {
return [{ return [{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: this.config.url + nodeinfo2_1path, href: this.config.url + nodeinfo2_1path
}, { }, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: this.config.url + nodeinfo2_0path, href: this.config.url + nodeinfo2_0path,

View File

@ -25,8 +25,8 @@ export const meta = {
items: { items: {
type: 'object', type: 'object',
}, },
}, }
}, }
}, },
} as const; } as const;

View File

@ -71,7 +71,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' }
}, },
required: ['userId'], required: ['userId'],
} as const; } as const;

View File

@ -6,7 +6,7 @@
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 { UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import { safeForSql } from '@/misc/safe-for-sql.js'; import { safeForSql } from "@/misc/safe-for-sql.js";
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';

View File

@ -14,7 +14,7 @@ export const meta = {
tags: ['account'], tags: ['account'],
requireCredential: true, requireCredential: true,
kind: 'read:account', kind: "read:account",
res: { res: {
type: 'object', type: 'object',

View File

@ -38,9 +38,9 @@ export const meta = {
type: 'array', type: 'array',
uniqueItems: true, uniqueItems: true,
items: { items: {
type: 'string', type: 'string'
},
}, },
}
}, },
}, },
}, },

View File

@ -35,7 +35,7 @@ export const meta = {
type: 'array', type: 'array',
uniqueItems: true, uniqueItems: true,
items: { items: {
type: 'string', type: 'string'
}, },
}, },
isAuthorized: { isAuthorized: {

View File

@ -22,7 +22,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -22,7 +22,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -22,8 +22,8 @@ export const meta = {
type: 'array', type: 'array',
items: { items: {
type: 'string', type: 'string',
}, }
}, }
}, },
domain: { domain: {
type: 'string', type: 'string',
@ -31,7 +31,7 @@ export const meta = {
}, },
}, },
}, },
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -33,7 +33,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -45,7 +45,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },

View File

@ -23,7 +23,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -35,7 +35,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },
@ -43,8 +43,8 @@ export const meta = {
latestSentAt: { type: 'string', format: 'date-time', nullable: true }, latestSentAt: { type: 'string', format: 'date-time', nullable: true },
latestStatus: { type: 'integer', nullable: true }, latestStatus: { type: 'integer', nullable: true },
}, },
}, }
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -30,7 +30,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
userId: { userId: {
type: 'string', type: 'string',
@ -42,7 +42,7 @@ export const meta = {
items: { items: {
type: 'string', type: 'string',
enum: webhookEventTypes, enum: webhookEventTypes,
}, }
}, },
url: { type: 'string' }, url: { type: 'string' },
secret: { type: 'string' }, secret: { type: 'string' },

View File

@ -47,7 +47,7 @@ export const meta = {
bothWithRepliesAndWithFiles: { bothWithRepliesAndWithFiles: {
message: 'Specifying both withReplies and withFiles is not supported', message: 'Specifying both withReplies and withFiles is not supported',
code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f', id: 'dfaa3eb7-8002-4cb7-bcc4-1095df46656f'
}, },
}, },
} as const; } as const;

View File

@ -18,7 +18,7 @@ export const meta = {
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
format: 'misskey:id', format: 'misskey:id'
}, },
required: { required: {
type: 'boolean', type: 'boolean',
@ -34,8 +34,8 @@ export const meta = {
default: 'hello', default: 'hello',
nullable: true, nullable: true,
}, },
}, }
}, }
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@ -34,6 +34,7 @@ import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { 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 type Logger from '@/logger.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
@ -253,12 +254,17 @@ export class ClientServerService {
//#region vite assets //#region vite assets
if (this.config.clientManifestExists) { if (this.config.clientManifestExists) {
fastify.register((fastify, options, done) => {
fastify.register(fastifyStatic, { fastify.register(fastifyStatic, {
root: viteOut, root: viteOut,
prefix: '/vite/', prefix: '/vite/',
maxAge: ms('30 days'), maxAge: ms('30 days'),
immutable: true,
decorateReply: false, decorateReply: false,
}); });
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
done();
});
} else { } else {
const port = (process.env.VITE_PORT ?? '5173'); const port = (process.env.VITE_PORT ?? '5173');
fastify.register(fastifyProxy, { fastify.register(fastifyProxy, {
@ -292,12 +298,17 @@ export class ClientServerService {
decorateReply: false, decorateReply: false,
}); });
fastify.register((fastify, options, done) => {
fastify.register(fastifyStatic, { fastify.register(fastifyStatic, {
root: tarball, root: tarball,
prefix: '/tarball/', prefix: '/tarball/',
maxAge: ms('30 days'),
immutable: true, immutable: true,
decorateReply: false, decorateReply: false,
}); });
fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
done();
});
fastify.get('/favicon.ico', async (request, reply) => { fastify.get('/favicon.ico', async (request, reply) => {
return reply.sendFile('/favicon.ico', staticAssets); return reply.sendFile('/favicon.ico', staticAssets);

View File

@ -7,10 +7,11 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert'; import * as assert from 'assert';
import { MiNote } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js';
import type { Packed } from '@/misc/json-schema.js';
import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
import type{ Repository } from 'typeorm'; import type{ Repository } from 'typeorm'
import type { Packed } from '@/misc/json-schema.js';
describe('Drive', () => { describe('Drive', () => {
let Notes: Repository<MiNote>; let Notes: Repository<MiNote>;
@ -30,7 +31,7 @@ describe('Drive', () => {
const marker = Math.random().toString(); const marker = Math.random().toString();
const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'; const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'
const catcher = makeStreamCatcher( const catcher = makeStreamCatcher(
alice, alice,
@ -50,7 +51,7 @@ describe('Drive', () => {
assert.strictEqual(res.status, 204); assert.strictEqual(res.status, 204);
assert.strictEqual(file.name, 'Lenna.jpg'); assert.strictEqual(file.name, 'Lenna.jpg');
assert.strictEqual(file.type, 'image/jpeg'); assert.strictEqual(file.type, 'image/jpeg');
}); })
test('ローカルからアップロードできる', async () => { test('ローカルからアップロードできる', async () => {
// APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする // APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする
@ -58,27 +59,27 @@ describe('Drive', () => {
const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' }); const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' });
assert.strictEqual(res.body?.name, 'テスト画像.jpg'); assert.strictEqual(res.body?.name, 'テスト画像.jpg');
assert.strictEqual(res.body.type, 'image/jpeg'); assert.strictEqual(res.body?.type, 'image/jpeg');
}); })
test('添付ノート一覧を取得できる', async () => { test('添付ノート一覧を取得できる', async () => {
const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id); const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id)
const note0 = await post(alice, { fileIds: [ids[0]] }); const note0 = await post(alice, { fileIds: [ids[0]] });
const note1 = await post(alice, { fileIds: [ids[0], ids[1]] }); const note1 = await post(alice, { fileIds: [ids[0], ids[1]] });
const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice); const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice);
assert.strictEqual(attached0.body.length, 2); assert.strictEqual(attached0.body.length, 2);
assert.strictEqual(attached0.body[0].id, note1.id); assert.strictEqual(attached0.body[0].id, note1.id)
assert.strictEqual(attached0.body[1].id, note0.id); assert.strictEqual(attached0.body[1].id, note0.id)
const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice); const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice);
assert.strictEqual(attached1.body.length, 1); assert.strictEqual(attached1.body.length, 1);
assert.strictEqual(attached1.body[0].id, note1.id); assert.strictEqual(attached1.body[0].id, note1.id)
const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice); const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice);
assert.strictEqual(attached2.body.length, 0); assert.strictEqual(attached2.body.length, 0)
}); })
test('添付ノート一覧は他の人から見えない', async () => { test('添付ノート一覧は他の人から見えない', async () => {
const file = await uploadFile(alice); const file = await uploadFile(alice);
@ -88,6 +89,7 @@ describe('Drive', () => {
const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob); const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob);
assert.strictEqual(res.status, 400); assert.strictEqual(res.status, 400);
assert.strictEqual('error' in res.body, true); assert.strictEqual('error' in res.body, true);
});
})
}); });

View File

@ -663,7 +663,7 @@ describe('Note', () => {
assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS'); assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
}); });
test('禁止ワードを含んでいてもリモートノートはエラーにならない', async () => { test('禁止ワードを含んでるリモートノートもエラーになる', async () => {
const prohibited = await api('admin/update-meta', { const prohibited = await api('admin/update-meta', {
prohibitedWords: [ prohibitedWords: [
'test', 'test',
@ -678,7 +678,7 @@ describe('Note', () => {
text: 'hogetesthuge', text: 'hogetesthuge',
}, tom); }, tom);
assert.strictEqual(note1.status, 200); assert.strictEqual(note1.status, 400);
}); });
}); });

View File

@ -100,6 +100,7 @@ describe('ActivityPub', () => {
perRemoteUserUserTimelineCacheMax: 100, perRemoteUserUserTimelineCacheMax: 100,
blockedHosts: [] as string[], blockedHosts: [] as string[],
sensitiveWords: [] as string[], sensitiveWords: [] as string[],
prohibitedWords: [] as string[],
} as MiMeta; } as MiMeta;
let meta = metaInitial; let meta = metaInitial;

View File

@ -13,10 +13,10 @@ import fetch, { File, RequestInit } from 'node-fetch';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { Packed } from '@/misc/json-schema.js';
import { entities } from '../src/postgres.js'; import { entities } from '../src/postgres.js';
import { loadConfig } from '../src/config.js'; import { loadConfig } from '../src/config.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
import { Packed } from '@/misc/json-schema.js';
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
@ -123,9 +123,9 @@ export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', len
function timeoutPromise<T>(p: Promise<T>, timeout: number): Promise<T> { function timeoutPromise<T>(p: Promise<T>, timeout: number): Promise<T> {
return Promise.race([ return Promise.race([
p, p,
new Promise((reject) => { new Promise((reject) =>{
setTimeout(() => { reject(new Error('timed out')); }, timeout); setTimeout(() => { reject(new Error('timed out')); }, timeout)
}) as never, }) as never
]); ]);
} }
@ -343,7 +343,7 @@ export const uploadUrl = async (user: UserToken, url: string): Promise<Packed<'D
'main', 'main',
(msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker,
(msg) => msg.body.file as Packed<'DriveFile'>, (msg) => msg.body.file as Packed<'DriveFile'>,
60 * 1000, 60 * 1000
); );
await api('drive/files/upload-from-url', { await api('drive/files/upload-from-url', {
@ -439,15 +439,15 @@ export function makeStreamCatcher<T>(
cond: (message: Record<string, any>) => boolean, cond: (message: Record<string, any>) => boolean,
extractor: (message: Record<string, any>) => T, extractor: (message: Record<string, any>) => T,
timeout = 60 * 1000): Promise<T> { timeout = 60 * 1000): Promise<T> {
let ws: WebSocket; let ws: WebSocket
const p = new Promise<T>(async (resolve) => { const p = new Promise<T>(async (resolve) => {
ws = await connectStream(user, channel, (msg) => { ws = await connectStream(user, channel, (msg) => {
if (cond(msg)) { if (cond(msg)) {
resolve(extractor(msg)); resolve(extractor(msg))
} }
}); });
}).finally(() => { }).finally(() => {
ws.close(); ws?.close();
}); });
return timeoutPromise(p, timeout); return timeoutPromise(p, timeout);

View File

@ -3,25 +3,28 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { resolve } from 'node:path'; import { createRequire } from 'node:module';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import type { StorybookConfig } from '@storybook/vue3-vite'; import type { StorybookConfig } from '@storybook/vue3-vite';
import { type Plugin, mergeConfig } from 'vite'; import { type Plugin, mergeConfig } from 'vite';
import turbosnap from 'vite-plugin-turbosnap'; import turbosnap from 'vite-plugin-turbosnap';
const dirname = fileURLToPath(new URL('.', import.meta.url)); const require = createRequire(import.meta.url);
const _dirname = fileURLToPath(new URL('.', import.meta.url));
const config = { const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [ addons: [
'@storybook/addon-essentials', getAbsolutePath('@storybook/addon-essentials'),
'@storybook/addon-interactions', getAbsolutePath('@storybook/addon-interactions'),
'@storybook/addon-links', getAbsolutePath('@storybook/addon-links'),
'@storybook/addon-storysource', getAbsolutePath('@storybook/addon-storysource'),
resolve(dirname, '../node_modules/storybook-addon-misskey-theme'), getAbsolutePath('@storybook/addon-mdx-gfm'),
resolve(_dirname, '../node_modules/storybook-addon-misskey-theme'),
], ],
framework: { framework: {
name: '@storybook/vue3-vite', name: getAbsolutePath('@storybook/vue3-vite') as '@storybook/vue3-vite',
options: {}, options: {},
}, },
docs: { docs: {
@ -37,10 +40,13 @@ const config = {
} }
return mergeConfig(config, { return mergeConfig(config, {
plugins: [ plugins: [
{
// XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8 // XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8
(turbosnap as any as typeof turbosnap['default'])({ ...(turbosnap as any as typeof turbosnap['default'])({
rootDir: config.root ?? process.cwd(), rootDir: config.root ?? process.cwd(),
}), }),
name: 'fake-turbosnap',
},
], ],
build: { build: {
target: [ target: [
@ -53,3 +59,7 @@ const config = {
}, },
} satisfies StorybookConfig; } satisfies StorybookConfig;
export default config; export default config;
function getAbsolutePath(value: string): string {
return dirname(require.resolve(join(value, 'package.json')));
}

View File

@ -3,8 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { addons } from '@storybook/addons';
import { FORCE_REMOUNT } from '@storybook/core-events'; import { FORCE_REMOUNT } from '@storybook/core-events';
import { addons } from '@storybook/preview-api';
import { type Preview, setup } from '@storybook/vue3'; import { type Preview, setup } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic'; import isChromatic from 'chromatic/isChromatic';
import { initialize, mswDecorator } from 'msw-storybook-addon'; import { initialize, mswDecorator } from 'msw-storybook-addon';

View File

@ -8,7 +8,7 @@
"build": "vite build", "build": "vite build",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", "build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
"build-storybook": "pnpm build-storybook-pre && storybook build", "build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
"chromatic": "chromatic", "chromatic": "chromatic",
"test": "vitest --run --globals", "test": "vitest --run --globals",
"test-and-coverage": "vitest --run --coverage --globals", "test-and-coverage": "vitest --run --coverage --globals",
@ -28,7 +28,7 @@
"@tabler/icons-webfont": "2.44.0", "@tabler/icons-webfont": "2.44.0",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.0.0",
"@vitejs/plugin-vue": "5.0.3", "@vitejs/plugin-vue": "5.0.3",
"@vue/compiler-sfc": "3.4.18", "@vue/compiler-sfc": "3.4.15",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
"astring": "1.8.6", "astring": "1.8.6",
"broadcast-channel": "7.0.0", "broadcast-channel": "7.0.0",
@ -73,30 +73,30 @@
"uuid": "9.0.1", "uuid": "9.0.1",
"v-code-diff": "1.7.2", "v-code-diff": "1.7.2",
"vite": "5.1.0", "vite": "5.1.0",
"vue": "3.4.18", "vue": "3.4.15",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0", "@misskey-dev/eslint-plugin": "1.0.0",
"@misskey-dev/summaly": "5.0.3", "@misskey-dev/summaly": "5.0.3",
"@storybook/addon-actions": "7.6.10", "@storybook/addon-actions": "8.0.0-beta.2",
"@storybook/addon-essentials": "7.6.10", "@storybook/addon-essentials": "8.0.0-beta.2",
"@storybook/addon-interactions": "7.6.10", "@storybook/addon-interactions": "8.0.0-beta.2",
"@storybook/addon-links": "7.6.10", "@storybook/addon-links": "8.0.0-beta.2",
"@storybook/addon-storysource": "7.6.10", "@storybook/addon-mdx-gfm": "8.0.0-beta.2",
"@storybook/addons": "7.6.10", "@storybook/addon-storysource": "8.0.0-beta.2",
"@storybook/blocks": "7.6.10", "@storybook/blocks": "8.0.0-beta.2",
"@storybook/core-events": "7.6.10", "@storybook/components": "8.0.0-beta.2",
"@storybook/jest": "0.2.3", "@storybook/core-events": "8.0.0-beta.2",
"@storybook/manager-api": "7.6.10", "@storybook/manager-api": "8.0.0-beta.2",
"@storybook/preview-api": "7.6.10", "@storybook/preview-api": "8.0.0-beta.2",
"@storybook/react": "7.6.10", "@storybook/react": "8.0.0-beta.2",
"@storybook/react-vite": "7.6.10", "@storybook/react-vite": "8.0.0-beta.2",
"@storybook/testing-library": "0.2.2", "@storybook/test": "8.0.0-beta.2",
"@storybook/theming": "7.6.10", "@storybook/theming": "8.0.0-beta.2",
"@storybook/types": "7.6.10", "@storybook/types": "8.0.0-beta.2",
"@storybook/vue3": "7.6.10", "@storybook/vue3": "8.0.0-beta.2",
"@storybook/vue3-vite": "7.6.10", "@storybook/vue3-vite": "8.0.0-beta.2",
"@testing-library/vue": "8.0.2", "@testing-library/vue": "8.0.2",
"@types/escape-regexp": "0.0.3", "@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
@ -130,12 +130,12 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"start-server-and-test": "2.0.3", "start-server-and-test": "2.0.3",
"storybook": "7.6.10", "storybook": "8.0.0-beta.2",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.6", "vitest": "0.34.6",
"vitest-fetch-mock": "0.2.2", "vitest-fetch-mock": "0.2.2",
"vue-component-type-helpers": "^1.8.27", "vue-component-type-helpers": "1.8.27",
"vue-eslint-parser": "9.4.2", "vue-eslint-parser": "9.4.2",
"vue-tsc": "1.8.27" "vue-tsc": "1.8.27"
} }

View File

@ -5,8 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest'; import { expect, userEvent, waitFor, within } from '@storybook/test';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw'; import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js'; import { userDetailed } from '../../.storybook/fakes.js';

View File

@ -123,7 +123,7 @@ function callback(response?: string) {
function onReceivedMessage(message: MessageEvent) { function onReceivedMessage(message: MessageEvent) {
if (message.data.token) { if (message.data.token) {
if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) { if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) {
callback(<string>message.data.token); callback(message.data.token);
} }
} }
} }

View File

@ -4,8 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect, userEvent, waitFor, within } from '@storybook/test';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { galleryPost } from '../../.storybook/fakes.js'; import { galleryPost } from '../../.storybook/fakes.js';
import MkGalleryPostPreview from './MkGalleryPostPreview.vue'; import MkGalleryPostPreview from './MkGalleryPostPreview.vue';

View File

@ -4,8 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect, userEvent, waitFor, within } from '@storybook/test';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { onBeforeUnmount } from 'vue'; import { onBeforeUnmount } from 'vue';
import MkSignupServerRules from './MkSignupDialog.rules.vue'; import MkSignupServerRules from './MkSignupDialog.rules.vue';

View File

@ -4,8 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect, userEvent, within } from '@storybook/test';
import { userEvent, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import MkA from './MkA.vue'; import MkA from './MkA.vue';
import { tick } from '@/scripts/test-utils.js'; import { tick } from '@/scripts/test-utils.js';

View File

@ -5,8 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest'; import { expect, waitFor } from '@storybook/test';
import { waitFor } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import MkError from './MkError.vue'; import MkError from './MkError.vue';
export const Default = { export const Default = {

View File

@ -5,8 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { within } from '@storybook/testing-library'; import { expect, within } from '@storybook/test';
import { expect } from '@storybook/jest';
import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js'; import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
export const Default = { export const Default = {
render(args) { render(args) {

View File

@ -4,7 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { waitFor } from '@storybook/testing-library'; import { waitFor } from '@storybook/test';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import MkPageHeader from './MkPageHeader.vue'; import MkPageHeader from './MkPageHeader.vue';
export const Empty = { export const Empty = {

View File

@ -4,7 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect } from '@storybook/test';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import MkTime from './MkTime.vue'; import MkTime from './MkTime.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';

View File

@ -4,8 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect, userEvent, waitFor, within } from '@storybook/test';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw'; import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks.js'; import { commonHandlers } from '../../../.storybook/mocks.js';

View File

@ -4,7 +4,7 @@
*/ */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect } from '@storybook/test';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js'; import { userDetailed } from '../../../.storybook/fakes.js';
import MkUserName from './MkUserName.vue'; import MkUserName from './MkUserName.vue';

View File

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div>{{ i18n.ts.youShouldUpgradeClient }}</div> <div>{{ i18n.ts.youShouldUpgradeClient }}</div>
<MkButton style="margin: 8px auto;" @click="reload">{{ i18n.ts.reload }}</MkButton> <MkButton style="margin: 8px auto;" @click="reload">{{ i18n.ts.reload }}</MkButton>
</template> </template>
<div><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></div> <div><MkLink url="https://misskey-hub.net/docs/for-users/resources/troubleshooting/" target="_blank">{{ i18n.ts.troubleshooting }}</MkLink></div>
<div v-if="error" style="opacity: 0.7;">ERROR: {{ error }}</div> <div v-if="error" style="opacity: 0.7;">ERROR: {{ error }}</div>
</div> </div>
</div> </div>
@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkLink from '@/components/MkLink.vue';
import { version } from '@/config.js'; import { version } from '@/config.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { unisonReload } from '@/scripts/unison-reload.js'; import { unisonReload } from '@/scripts/unison-reload.js';

View File

@ -101,7 +101,7 @@ const announcements = {
limit: 10, limit: 10,
}; };
const isTimelineAvailable = ref(instance.policies.ltlAvailable || instance.policies.gtlAvailable); const isTimelineAvailable = ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
const showMenu = ref(false); const showMenu = ref(false);
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD); const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ execa('pnpm', ['--filter', 'backend', 'dev'], {
stderr: process.stderr, stderr: process.stderr,
}); });
execa('pnpm', ['--filter', 'frontend', 'dev'], { execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'dev' : 'watch'], {
cwd: _dirname + '/../', cwd: _dirname + '/../',
stdout: process.stdout, stdout: process.stdout,
stderr: process.stderr, stderr: process.stderr,