diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c3af806..4b6f208d0 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 8a429e3b6..5227caa08 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 52e06e720..bf179ca73 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 779231a85..14a13b09c 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 e3f96724d..96704996f 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 000000000..ae9256b8e --- /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 000000000..ee1a0ee9b --- /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 000000000..2c1e5d20a --- /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 294f66aaa..6c33d0d8e 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 096410824..8b2144a22 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',