diff --git a/packages/backend/src/core/NoteDraftService.ts b/packages/backend/src/core/NoteDraftService.ts index e01feb2545..ff010a38ff 100644 --- a/packages/backend/src/core/NoteDraftService.ts +++ b/packages/backend/src/core/NoteDraftService.ts @@ -17,22 +17,23 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { isRenote, isQuote } from '@/misc/is-renote.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { QueueService } from '@/core/QueueService.js'; +import { deepClone } from '@/misc/clone.js'; export type NoteDraftOptions = { - replyId?: MiNote['id'] | null; - renoteId?: MiNote['id'] | null; - text?: string | null; - cw?: string | null; - localOnly?: boolean | null; - reactionAcceptance?: typeof noteReactionAcceptances[number]; - visibility?: typeof noteVisibilities[number]; - fileIds?: MiDriveFile['id'][]; - visibleUserIds?: MiUser['id'][]; - hashtag?: string; - channelId?: MiChannel['id'] | null; - poll?: (IPoll & { expiredAfter?: number | null }) | null; - scheduledAt?: Date | null; - isActuallyScheduled?: boolean; + replyId: MiNote['id'] | null; + renoteId: MiNote['id'] | null; + text: string | null; + cw: string | null; + localOnly: boolean | null; + reactionAcceptance: typeof noteReactionAcceptances[number]; + visibility: typeof noteVisibilities[number]; + fileIds: MiDriveFile['id'][]; + visibleUserIds: MiUser['id'][]; + hashtag: string | null; + channelId: MiChannel['id'] | null; + poll: (IPoll & { expiredAfter?: number | null }) | null; + scheduledAt: Date | null; + isActuallyScheduled: boolean; }; @Injectable() @@ -109,7 +110,7 @@ export class NoteDraftService { } @bindThis - public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: NoteDraftOptions): Promise { + public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: Partial): Promise { const draft = await this.noteDraftsRepository.findOneBy({ id: draftId, userId: me.id, @@ -180,7 +181,7 @@ export class NoteDraftService { public async checkAndSetDraftNoteOptions( me: MiLocalUser, draft: MiNoteDraft, - data: NoteDraftOptions, + data: Partial, ): Promise { data.visibility ??= 'public'; data.localOnly ??= false; @@ -191,8 +192,6 @@ export class NoteDraftService { data.localOnly = true; } - let appliedDraft = draft; - //#region visibleUsers let visibleUsers: MiUser[] = []; if (data.visibleUserIds != null) { @@ -205,7 +204,7 @@ export class NoteDraftService { //#region files let files: MiDriveFile[] = []; const fileIds = data.fileIds ?? null; - if (fileIds != null) { + if (fileIds != null && fileIds.length > 0) { files = await this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId AND file.id IN (:...fileIds)', { userId: me.id, @@ -310,8 +309,8 @@ export class NoteDraftService { } //#endregion - appliedDraft = { - ...appliedDraft, + return { + ...draft, visibility: data.visibility, cw: data.cw ?? null, fileIds: fileIds ?? [], @@ -331,8 +330,6 @@ export class NoteDraftService { scheduledAt: data.scheduledAt ?? null, isActuallyScheduled: data.isActuallyScheduled ?? false, } satisfies MiNoteDraft; - - return appliedDraft; } @bindThis diff --git a/packages/backend/src/models/NoteDraft.ts b/packages/backend/src/models/NoteDraft.ts index 411374fc96..0ece02c943 100644 --- a/packages/backend/src/models/NoteDraft.ts +++ b/packages/backend/src/models/NoteDraft.ts @@ -162,12 +162,4 @@ export class MiNoteDraft { default: false, }) public isActuallyScheduled: boolean; - - constructor(data: Partial) { - if (data == null) return; - - for (const [k, v] of Object.entries(data)) { - (this as any)[k] = v; - } - } } diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts index 5d18a2e42c..0529d0bcdc 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/create.ts @@ -162,7 +162,7 @@ export const paramDef = { fileIds: { type: 'array', uniqueItems: true, - minItems: 1, + minItems: 0, maxItems: 16, items: { type: 'string', format: 'misskey:id' }, }, @@ -186,7 +186,7 @@ export const paramDef = { scheduledAt: { type: 'integer', nullable: true }, isActuallyScheduled: { type: 'boolean', default: false }, }, - required: [], + required: ['visibility', 'visibleUserIds', 'cw', 'hashtag', 'localOnly', 'reactionAcceptance', 'replyId', 'renoteId', 'channelId', 'text', 'fileIds', 'poll', 'scheduledAt', 'isActuallyScheduled'], } as const; @Injectable() @@ -203,19 +203,19 @@ export default class extends Endpoint { // eslint- multiple: ps.poll.multiple ?? false, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, expiredAfter: ps.poll.expiredAfter ?? null, - } : undefined, - text: ps.text ?? null, - replyId: ps.replyId ?? undefined, - renoteId: ps.renoteId ?? undefined, - cw: ps.cw ?? null, - ...(ps.hashtag ? { hashtag: ps.hashtag } : {}), + } : null, + text: ps.text, + replyId: ps.replyId, + renoteId: ps.renoteId, + cw: ps.cw, + hashtag: ps.hashtag, localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUserIds: ps.visibleUserIds ?? [], - channelId: ps.channelId ?? undefined, + visibleUserIds: ps.visibleUserIds, + channelId: ps.channelId, scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, - isActuallyScheduled: ps.isActuallyScheduled ?? false, + isActuallyScheduled: ps.isActuallyScheduled, }).catch((err) => { if (err instanceof IdentifiableError) { switch (err.id) { diff --git a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts index 7457a82dee..9755c857f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/drafts/update.ts +++ b/packages/backend/src/server/api/endpoints/notes/drafts/update.ts @@ -171,14 +171,14 @@ export const paramDef = { type: 'object', properties: { draftId: { type: 'string', nullable: false, format: 'misskey:id' }, - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'] }, visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 }, hashtag: { type: 'string', nullable: true, maxLength: 200 }, - localOnly: { type: 'boolean', default: false }, - reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null }, + localOnly: { type: 'boolean' }, + reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'] }, replyId: { type: 'string', format: 'misskey:id', nullable: true }, renoteId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true }, @@ -194,7 +194,7 @@ export const paramDef = { fileIds: { type: 'array', uniqueItems: true, - minItems: 1, + minItems: 0, maxItems: 16, items: { type: 'string', format: 'misskey:id' }, }, @@ -216,7 +216,7 @@ export const paramDef = { required: ['choices'], }, scheduledAt: { type: 'integer', nullable: true }, - isActuallyScheduled: { type: 'boolean', default: false }, + isActuallyScheduled: { type: 'boolean' }, }, required: ['draftId'], } as const; @@ -236,18 +236,18 @@ export default class extends Endpoint { // eslint- expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, expiredAfter: ps.poll.expiredAfter ?? null, } : undefined, - text: ps.text ?? null, - replyId: ps.replyId ?? undefined, - renoteId: ps.renoteId ?? undefined, - cw: ps.cw ?? null, + text: ps.text, + replyId: ps.replyId, + renoteId: ps.renoteId, + cw: ps.cw, ...(ps.hashtag ? { hashtag: ps.hashtag } : {}), localOnly: ps.localOnly, reactionAcceptance: ps.reactionAcceptance, visibility: ps.visibility, - visibleUserIds: ps.visibleUserIds ?? [], - channelId: ps.channelId ?? undefined, + visibleUserIds: ps.visibleUserIds, + channelId: ps.channelId, scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, - isActuallyScheduled: ps.isActuallyScheduled ?? false, + isActuallyScheduled: ps.isActuallyScheduled, }).catch((err) => { if (err instanceof IdentifiableError) { switch (err.id) { diff --git a/packages/frontend/src/components/MkNoteDraftsDialog.vue b/packages/frontend/src/components/MkNoteDraftsDialog.vue index 6e35ff82e2..4b2c2229b1 100644 --- a/packages/frontend/src/components/MkNoteDraftsDialog.vue +++ b/packages/frontend/src/components/MkNoteDraftsDialog.vue @@ -219,6 +219,16 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) { draftsPaginator.reload(); }); } + +async function cancelSchedule(draft: Misskey.entities.NoteDraft) { + os.apiWithDialog('notes/drafts/update', { + draftId: draft.id, + isActuallyScheduled: false, + scheduledAt: null, + }).then(() => { + scheduledPaginator.reload(); + }); +}