diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c3af806a..4b6f208d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Changes - API: users/notes, notes/local-timeline で fileType 指定はできなくなりました - API: notes/featured でページネーションは他APIと同様 untilId を使って行うようになりました +- API: drive/files/attached-notes がページネーションに対応しました ### General - Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました @@ -31,6 +32,9 @@ - Feat: タイムラインがリアルタイム更新中に広告を挿入できるようになりました - デフォルトは無効 - 頻度はコントロールパネルから設定できます。運営中のサーバーのTLの流速を見て、最適な値を指定してください。 +- Feat: 「ファイルの詳細」ページを追加 + - ドライブのファイルの拡大プレビューができるように + - ファイルが添付されたノートの一覧が表示できるように - Enhance: ソフトワードミュートとハードワードミュートは統合されました - Enhance: モデレーションログ機能の強化 - Enhance: ローカリゼーションの更新 diff --git a/locales/index.d.ts b/locales/index.d.ts index 8a429e3b66..5227caa083 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2294,6 +2294,15 @@ export interface Locale { "deleteAd": string; "updateAd": string; }; + "_fileViewer": { + "title": string; + "type": string; + "size": string; + "url": string; + "uploadedAt": string; + "attachedNotes": string; + "thisPageCanBeSeenFromTheAuthor": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 52e06e720d..bf179ca733 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2206,3 +2206,12 @@ _moderationLogTypes: createAd: "広告を作成" deleteAd: "広告を削除" updateAd: "広告を更新" + +_fileViewer: + title: "ファイルの詳細" + type: "ファイルタイプ" + size: "ファイルサイズ" + url: "URL" + uploadedAt: "追加日" + attachedNotes: "添付されているノート" + thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。" diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 779231a856..14a13b09c9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, DriveFilesRepository } from '@/models/_.js'; +import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -41,6 +42,9 @@ export const meta = { export const paramDef = { type: 'object', properties: { + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, fileId: { type: 'string', format: 'misskey:id' }, }, required: ['fileId'], @@ -56,6 +60,7 @@ export default class extends Endpoint { // eslint- private notesRepository: NotesRepository, private noteEntityService: NoteEntityService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { // Fetch file @@ -68,9 +73,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchFile); } - const notes = await this.notesRepository.createQueryBuilder('note') - .where(':file = ANY(note.fileIds)', { file: file.id }) - .getMany(); + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); + query.andWhere(':file = ANY(note.fileIds)', { file: file.id }); + + const notes = await query.limit(ps.limit).getMany(); return await this.noteEntityService.packMany(notes, me, { detail: true, diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index e3f96724d9..96704996f9 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -45,8 +45,11 @@ import bytes from '@/filters/bytes.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; +import { useRouter } from '@/router.js'; import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js'; +const router = useRouter(); + const props = withDefaults(defineProps<{ file: Misskey.entities.DriveFile; folder: Misskey.entities.DriveFolder | null; @@ -71,7 +74,7 @@ function onClick(ev: MouseEvent) { if (props.selectMode) { emit('chosen', props.file); } else { - os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); + router.push(`/my/drive/file/${props.file.id}`); } } diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue new file mode 100644 index 0000000000..ae9256b8e3 --- /dev/null +++ b/packages/frontend/src/pages/drive.file.info.vue @@ -0,0 +1,302 @@ + + + + + + + diff --git a/packages/frontend/src/pages/drive.file.notes.vue b/packages/frontend/src/pages/drive.file.notes.vue new file mode 100644 index 0000000000..ee1a0ee9b0 --- /dev/null +++ b/packages/frontend/src/pages/drive.file.notes.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue new file mode 100644 index 0000000000..2c1e5d20a7 --- /dev/null +++ b/packages/frontend/src/pages/drive.file.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 294f66aaaf..6c33d0d8ee 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -467,6 +467,10 @@ export const routes = [{ path: '/my/drive', component: page(() => import('./pages/drive.vue')), loginRequired: true, +}, { + path: '/my/drive/file/:fileId', + component: page(() => import('./pages/drive.file.vue')), + loginRequired: true, }, { path: '/my/follow-requests', component: page(() => import('./pages/follow-requests.vue')), diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts index 0964108249..8b2144a22f 100644 --- a/packages/frontend/src/scripts/get-drive-file-menu.ts +++ b/packages/frontend/src/scripts/get-drive-file-menu.ts @@ -27,7 +27,7 @@ function rename(file: Misskey.entities.DriveFile) { function describe(file: Misskey.entities.DriveFile) { os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { - default: file.comment != null ? file.comment : '', + default: file.comment ?? '', file: file, }, { done: caption => { @@ -112,6 +112,11 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss text: i18n.ts.download, icon: 'ti ti-download', download: file.name, + }, null, { + type: 'link', + to: `/my/drive/file/${file.id}`, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-file', }, null, { text: i18n.ts.delete, icon: 'ti ti-trash',