Merge branch 'develop' into feat-12997
This commit is contained in:
commit
2955fb9f25
|
@ -4,7 +4,8 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
|
||||||
|
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
-
|
-
|
||||||
|
|
|
@ -316,6 +316,98 @@ export const handlers = [
|
||||||
|
|
||||||
Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files.
|
Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files.
|
||||||
|
|
||||||
|
## Nest
|
||||||
|
|
||||||
|
### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合
|
||||||
|
|
||||||
|
#### forwardRef
|
||||||
|
まずは簡単に`forwardRef`を試してみる
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class FooService {
|
||||||
|
constructor(
|
||||||
|
@Inject(forwardRef(() => BarService))
|
||||||
|
private barService: BarService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OnModuleInit
|
||||||
|
できなければ`OnModuleInit`を使う
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { ModuleRef } from '@nestjs/core';
|
||||||
|
import { BarService } from '@/core/BarService';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FooService implements OnModuleInit {
|
||||||
|
private barService: BarService // constructorから移動してくる
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private moduleRef: ModuleRef,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
this.barService = this.moduleRef.get(BarService.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async niceMethod() {
|
||||||
|
return await this.barService.incredibleMethod({ hoge: 'fuga' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Service Unit Test
|
||||||
|
テストで`onModuleInit`を呼び出す必要がある
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// import ...
|
||||||
|
|
||||||
|
describe('test', () => {
|
||||||
|
let app: TestingModule;
|
||||||
|
let fooService: FooService; // for test case
|
||||||
|
let barService: BarService; // for test case
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await Test.createTestingModule({
|
||||||
|
imports: ...,
|
||||||
|
providers: [
|
||||||
|
FooService,
|
||||||
|
{ // mockする (mockは必須ではないかもしれない)
|
||||||
|
provide: BarService,
|
||||||
|
useFactory: () => ({
|
||||||
|
incredibleMethod: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ // Provideにする
|
||||||
|
provide: BarService.name,
|
||||||
|
useExisting: BarService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.useMocker(...
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
fooService = app.get<FooService>(FooService);
|
||||||
|
barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;
|
||||||
|
|
||||||
|
// onModuleInitを実行する
|
||||||
|
await fooService.onModuleInit();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nice', () => {
|
||||||
|
await fooService.niceMethod();
|
||||||
|
|
||||||
|
expect(barService.incredibleMethod).toHaveBeenCalled();
|
||||||
|
expect(barService.incredibleMethod.mock.lastCall![0])
|
||||||
|
.toEqual({ hoge: 'fuga' });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
### Misskeyのドメイン固有の概念は`Mi`をprefixする
|
### Misskeyのドメイン固有の概念は`Mi`をprefixする
|
||||||
|
|
|
@ -248,7 +248,7 @@ export class DriveFileEntityService {
|
||||||
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
|
||||||
detail: true,
|
detail: true,
|
||||||
}) : null,
|
}) : null,
|
||||||
userId: opts.withUser ? file.userId : null,
|
userId: file.userId,
|
||||||
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
|
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ import * as os from '@/os.js';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import { hms } from '@/filters/hms.js';
|
import { hms } from '@/filters/hms.js';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
audio: Misskey.entities.DriveFile;
|
audio: Misskey.entities.DriveFile;
|
||||||
|
@ -96,8 +96,6 @@ function showMenu(ev: MouseEvent) {
|
||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
menu.push({
|
menu.push({
|
||||||
type: 'divider',
|
|
||||||
}, {
|
|
||||||
text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
|
text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
|
||||||
icon: props.audio.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
|
icon: props.audio.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
|
||||||
danger: true,
|
danger: true,
|
||||||
|
@ -105,6 +103,17 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($i?.id === props.audio.userId) {
|
||||||
|
menu.push({
|
||||||
|
type: 'divider',
|
||||||
|
}, {
|
||||||
|
type: 'link' as const,
|
||||||
|
text: i18n.ts._fileViewer.title,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
to: `/my/drive/file/${props.audio.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menuShowing.value = true;
|
menuShowing.value = true;
|
||||||
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
|
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
|
||||||
align: 'right',
|
align: 'right',
|
||||||
|
|
|
@ -59,7 +59,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
image: Misskey.entities.DriveFile;
|
image: Misskey.entities.DriveFile;
|
||||||
|
@ -114,6 +114,13 @@ function showMenu(ev: MouseEvent) {
|
||||||
action: () => {
|
action: () => {
|
||||||
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
|
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
|
||||||
},
|
},
|
||||||
|
}] : []), ...($i?.id === props.image.userId ? [{
|
||||||
|
type: 'divider' as const,
|
||||||
|
}, {
|
||||||
|
type: 'link' as const,
|
||||||
|
text: i18n.ts._fileViewer.title,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
to: `/my/drive/file/${props.image.id}`,
|
||||||
}] : [])], ev.currentTarget ?? ev.target);
|
}] : [])], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ import * as os from '@/os.js';
|
||||||
import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
|
import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
|
||||||
import hasAudio from '@/scripts/media-has-audio.js';
|
import hasAudio from '@/scripts/media-has-audio.js';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
video: Misskey.entities.DriveFile;
|
video: Misskey.entities.DriveFile;
|
||||||
|
@ -122,8 +122,6 @@ function showMenu(ev: MouseEvent) {
|
||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
menu.push({
|
menu.push({
|
||||||
type: 'divider',
|
|
||||||
}, {
|
|
||||||
text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
|
text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
|
||||||
icon: props.video.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
|
icon: props.video.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
|
||||||
danger: true,
|
danger: true,
|
||||||
|
@ -131,6 +129,17 @@ function showMenu(ev: MouseEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($i?.id === props.video.userId) {
|
||||||
|
menu.push({
|
||||||
|
type: 'divider',
|
||||||
|
}, {
|
||||||
|
type: 'link' as const,
|
||||||
|
text: i18n.ts._fileViewer.title,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
to: `/my/drive/file/${props.video.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menuShowing.value = true;
|
menuShowing.value = true;
|
||||||
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
|
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
|
||||||
align: 'right',
|
align: 'right',
|
||||||
|
|
|
@ -373,7 +373,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
this.currentRoute.value = res.route;
|
this.currentRoute.value = res.route;
|
||||||
this.currentKey = res.route.globalCacheKey ?? key ?? path;
|
this.currentKey = res.route.globalCacheKey ?? key ?? path;
|
||||||
|
|
||||||
if (emitChange) {
|
if (emitChange && res.route.path !== '/:(*)') {
|
||||||
this.emit('change', {
|
this.emit('change', {
|
||||||
beforePath,
|
beforePath,
|
||||||
path,
|
path,
|
||||||
|
@ -408,6 +408,9 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
if (cancel) return;
|
if (cancel) return;
|
||||||
}
|
}
|
||||||
const res = this.navigate(path, null);
|
const res = this.navigate(path, null);
|
||||||
|
if (res.route.path === '/:(*)') {
|
||||||
|
location.href = path;
|
||||||
|
} else {
|
||||||
this.emit('push', {
|
this.emit('push', {
|
||||||
beforePath,
|
beforePath,
|
||||||
path: res._parsedRoute.fullPath,
|
path: res._parsedRoute.fullPath,
|
||||||
|
@ -416,6 +419,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
key: this.currentKey,
|
key: this.currentKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public replace(path: string, key?: string | null) {
|
public replace(path: string, key?: string | null) {
|
||||||
const res = this.navigate(path, key);
|
const res = this.navigate(path, key);
|
||||||
|
|
|
@ -48,6 +48,9 @@ const devConfig = {
|
||||||
},
|
},
|
||||||
'/url': httpUrl,
|
'/url': httpUrl,
|
||||||
'/proxy': httpUrl,
|
'/proxy': httpUrl,
|
||||||
|
'/_info_card_': httpUrl,
|
||||||
|
'/bios': httpUrl,
|
||||||
|
'/cli': httpUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|
Loading…
Reference in New Issue