diff --git a/locales/index.d.ts b/locales/index.d.ts index 2771364f745c..63bcb16e4106 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -593,8 +593,9 @@ export interface Locale { "enableInfiniteScroll": string; "visibility": string; "poll": string; - "schedule": string; + "schedulePost": string; "useCw": string; + "schedulePostList": string; "enablePlayer": string; "disablePlayer": string; "expandTweet": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 99e4636925b4..be378180dc41 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -590,8 +590,9 @@ invisibleNote: "非公開の投稿" enableInfiniteScroll: "自動でもっと見る" visibility: "公開範囲" poll: "アンケート" -schedule: "予約" +schedulePost: "予約投稿" useCw: "内容を隠す" +schedulePostList: "予約投稿一覧" enablePlayer: "プレイヤーを開く" disablePlayer: "プレイヤーを閉じる" expandTweet: "ポストを展開する" diff --git a/packages/backend/migration/1699337454434-schedulenote.js b/packages/backend/migration/1699337454434-schedulenote.js deleted file mode 100644 index 213da993b066..000000000000 --- a/packages/backend/migration/1699337454434-schedulenote.js +++ /dev/null @@ -1,11 +0,0 @@ -export class Schedulenote1699337454434 { - name = 'Schedulenote1699337454434' - - async up(queryRunner) { - await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`); - } - - async down(queryRunner) { - await queryRunner.query(`DROP TABLE "note_schedule"`); - } -} diff --git a/packages/backend/migration/1699437894737-schedulenote.js b/packages/backend/migration/1699437894737-schedulenote.js new file mode 100644 index 000000000000..d0188a45eef5 --- /dev/null +++ b/packages/backend/migration/1699437894737-schedulenote.js @@ -0,0 +1,12 @@ +export class Schedulenote1699437894737 { + name = 'Schedulenote1699437894737' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP TABLE "note_schedule"`); + } +} diff --git a/packages/backend/src/models/NoteSchedule.ts b/packages/backend/src/models/NoteSchedule.ts index aec1bb52b900..f622893ecf77 100644 --- a/packages/backend/src/models/NoteSchedule.ts +++ b/packages/backend/src/models/NoteSchedule.ts @@ -19,8 +19,12 @@ export class MiNoteSchedule { @Column('jsonb') public note:{createdAt?: Date | undefined ; apEmojis: any[] | undefined; visibility: any; apMentions: any[] | undefined; visibleUsers: MiUser[]; channel: null | MiChannel; poll: { multiple: any; choices: any; expiresAt: Date | null } | undefined; renote: null | MiNote; localOnly: any; cw: any; apHashtags: any[] | undefined; reactionAcceptance: any; files: MiDriveFile[]; text: any; reply: null | MiNote }; + @Index() @Column('varchar', { length: 260, }) public userId: MiUser['id']; + + @Column('timestamp with time zone') + public expiresAt: Date; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 4addba5aa047..f7dea3d9077e 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -264,7 +264,9 @@ import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_schedule_create from './endpoints/notes/create-schedule.js'; +import * as ep___notes_schedule_list from './endpoints/notes/list-schedule.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; +import * as ep___notes_schedule_delete from './endpoints/notes/delete-schedule.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_featured from './endpoints/notes/featured.js'; @@ -623,7 +625,9 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; const $notes_schedule_create: Provider = { provide: 'ep:notes/create-schedule', useClass: ep___notes_schedule_create.default }; +const $notes_schedule_list: Provider = { provide: 'ep:notes/list-schedule', useClass: ep___notes_schedule_list.default }; const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; +const $notes_schedule_delete: Provider = { provide: 'ep:notes/delete-schedule', useClass: ep___notes_schedule_delete.default }; const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default }; const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default }; const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default }; @@ -986,7 +990,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_conversation, $notes_create, $notes_schedule_create, + $notes_schedule_list, $notes_delete, + $notes_schedule_delete, $notes_favorites_create, $notes_favorites_delete, $notes_featured, @@ -1343,7 +1349,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_conversation, $notes_create, $notes_schedule_create, + $notes_schedule_list, $notes_delete, + $notes_schedule_delete, $notes_favorites_create, $notes_favorites_delete, $notes_featured, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 1e3c0bb0e022..386b13925692 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -264,7 +264,9 @@ import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_schedule_create from './endpoints/notes/create-schedule.js'; +import * as ep___notes_schedule_list from './endpoints/notes/list-schedule.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; +import * as ep___notes_schedule_delete from './endpoints/notes/delete-schedule.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_featured from './endpoints/notes/featured.js'; @@ -621,7 +623,9 @@ const eps = [ ['notes/conversation', ep___notes_conversation], ['notes/create', ep___notes_create], ['notes/create-schedule', ep___notes_schedule_create], + ['notes/list-schedule', ep___notes_schedule_list], ['notes/delete', ep___notes_delete], + ['notes/delete-schedule', ep___notes_schedule_delete], ['notes/favorites/create', ep___notes_favorites_create], ['notes/favorites/delete', ep___notes_favorites_delete], ['notes/featured', ep___notes_featured], diff --git a/packages/backend/src/server/api/endpoints/notes/create-schedule.ts b/packages/backend/src/server/api/endpoints/notes/create-schedule.ts index d3e57d604836..e9854639a822 100644 --- a/packages/backend/src/server/api/endpoints/notes/create-schedule.ts +++ b/packages/backend/src/server/api/endpoints/notes/create-schedule.ts @@ -197,9 +197,6 @@ export default class extends Endpoint { // eslint- @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.db) - private db: DataSource, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -388,6 +385,7 @@ export default class extends Endpoint { // eslint- id: noteId, note: note, userId: me.id, + expiresAt: new Date(ps.schedule.expiresAt), }); const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); diff --git a/packages/backend/src/server/api/endpoints/notes/delete-schedule.ts b/packages/backend/src/server/api/endpoints/notes/delete-schedule.ts new file mode 100644 index 000000000000..f9398576d39f --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/delete-schedule.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import type { NoteScheduleRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: true, + + limit: { + duration: ms('1hour'), + max: 300, + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '490be23f-8c1f-4796-819f-94cb4f9d1630', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + noteId: { type: 'string', format: 'misskey:id' }, + }, + required: ['noteId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.noteScheduleRepository) + private noteScheduleRepository: NoteScheduleRepository, + ) { + super(meta, paramDef, async (ps, me) => { + await this.noteScheduleRepository.delete({ id: ps.noteId }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/list-schedule.ts b/packages/backend/src/server/api/endpoints/notes/list-schedule.ts new file mode 100644 index 000000000000..8cd670e42132 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/list-schedule.ts @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import ms from 'ms'; +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { DI } from '@/di-symbols.js'; +import type { NoteScheduleRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: true, + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, + limit: { + duration: ms('1hour'), + max: 300, + }, + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + }, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.noteScheduleRepository) + private noteScheduleRepository: NoteScheduleRepository, + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const scheduleNotes = await this.noteScheduleRepository.findBy({ userId: me.id }); + const user = await this.userEntityService.pack(me, me); + scheduleNotes.forEach((item: any) => { + item.note.user = user; + item.note.createdAt = new Date(item.expiresAt); + item.note.isSchedule = true; + item.note.id = item.id; + }); + return scheduleNotes; + }); + } +} diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index b2236b99c2b0..0d54c3838d5f 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+ @@ -42,7 +43,8 @@ import { notePage } from '@/filters/note.js'; import { userPage } from '@/filters/user.js'; defineProps<{ - note: Misskey.entities.Note; + note: Misskey.entities.Note & {isSchedule? : boolean}; + scheduled?: boolean; }>(); const mock = inject('mock', false); diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 28b00af246f0..a42f674be046 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -42,8 +59,12 @@ const showContent = $ref(false); margin: 0; padding: 0; font-size: 0.95em; + border-bottom: solid 0.5px var(--divider); +} +.button{ + margin-right: var(--margin); + margin-bottom: var(--margin); } - .avatar { flex-shrink: 0; display: block; diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 8e2e9f6d8ad9..66a7812448a5 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -19,6 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+ +