This commit is contained in:
syuilo 2025-09-24 18:30:39 +09:00
parent f6e481d8fb
commit f1421644cd
7 changed files with 74 additions and 83 deletions

View File

@ -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<MiNoteDraft> {
public async update(me: MiLocalUser, draftId: MiNoteDraft['id'], data: Partial<NoteDraftOptions>): Promise<MiNoteDraft> {
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<NoteDraftOptions>,
): Promise<MiNoteDraft> {
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

View File

@ -162,12 +162,4 @@ export class MiNoteDraft {
default: false,
})
public isActuallyScheduled: boolean;
constructor(data: Partial<MiNoteDraft>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
}

View File

@ -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<typeof meta, typeof paramDef> { // 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) {

View File

@ -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<typeof meta, typeof paramDef> { // 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) {

View File

@ -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();
});
}
</script>
<style lang="scss" module>

View File

@ -850,12 +850,12 @@ async function saveServerDraft(options: {
visibility: visibility.value,
localOnly: localOnly.value,
hashtag: hashtags.value,
...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}),
fileIds: files.value.map(f => f.id),
poll: poll.value,
...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined,
replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined,
channelId: targetChannel.value ? targetChannel.value.id : undefined,
visibleUserIds: visibleUsers.value.map(x => x.id),
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : null,
replyId: replyTargetNote.value ? replyTargetNote.value.id : null,
channelId: targetChannel.value ? targetChannel.value.id : null,
reactionAcceptance: reactionAcceptance.value,
scheduledAt: scheduledAt.value,
isActuallyScheduled: options.isActuallyScheduled ?? false,

View File

@ -29180,34 +29180,34 @@ export interface operations {
* @default public
* @enum {string}
*/
visibility?: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: string[];
cw?: string | null;
hashtag?: string | null;
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds: string[];
cw: string | null;
hashtag: string | null;
/** @default false */
localOnly?: boolean;
localOnly: boolean;
/**
* @default null
* @enum {string|null}
*/
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** Format: misskey:id */
replyId?: string | null;
replyId: string | null;
/** Format: misskey:id */
renoteId?: string | null;
renoteId: string | null;
/** Format: misskey:id */
channelId?: string | null;
text?: string | null;
fileIds?: string[];
poll?: {
channelId: string | null;
text: string | null;
fileIds: string[];
poll: {
choices: string[];
multiple?: boolean;
expiresAt?: number | null;
expiredAfter?: number | null;
} | null;
scheduledAt?: number | null;
scheduledAt: number | null;
/** @default false */
isActuallyScheduled?: boolean;
isActuallyScheduled: boolean;
};
};
};
@ -29421,20 +29421,13 @@ export interface operations {
'application/json': {
/** Format: misskey:id */
draftId: string;
/**
* @default public
* @enum {string}
*/
/** @enum {string} */
visibility?: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: string[];
cw?: string | null;
hashtag?: string | null;
/** @default false */
localOnly?: boolean;
/**
* @default null
* @enum {string|null}
*/
/** @enum {string|null} */
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** Format: misskey:id */
replyId?: string | null;
@ -29451,7 +29444,6 @@ export interface operations {
expiredAfter?: number | null;
} | null;
scheduledAt?: number | null;
/** @default false */
isActuallyScheduled?: boolean;
};
};