From 2528508cff9d8c90abd33e46b15220a49a00e2e2 Mon Sep 17 00:00:00 2001
From: NoriDev <m1nthing2322@gmail.com>
Date: Thu, 31 Oct 2024 13:52:01 +0900
Subject: feat: 노트 게시를 예약할 수 있음 (yojo-art/cherrypick#483,
 [Type4ny-Project/Type4ny@271c872c](https://github.com/Type4ny-Project/Type4ny/commit/271c872c97f215ef5d8e0be62251dd422a52e5b1))
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/frontend/src/components/MkNoteSimple.vue | 55 ++++++++++++++++++++++-
 1 file changed, 53 insertions(+), 2 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteSimple.vue')

diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 542e3e79ea..7d2bbb31d3 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
 -->
 
 <template>
-<div :class="$style.root">
+<div v-show="!isDeleted" :class="$style.root" :tabindex="!isDeleted ? '-1' : undefined">
 	<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
 	<div :class="$style.main">
 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
@@ -15,6 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</p>
 			<div v-show="note.cw == null || showContent">
 				<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/>
+				<div v-if="note.isSchedule" style="margin-top: 10px;">
+					<MkButton :class="$style.button" inline @click.stop.prevent="editScheduleNote()"><i class="ti ti-eraser"></i> {{ i18n.ts.deleteAndEdit }}</MkButton>
+					<MkButton :class="$style.button" inline danger @click.stop.prevent="deleteScheduleNote()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+				</div>
 			</div>
 		</div>
 	</div>
@@ -24,18 +28,60 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
+import * as os from '@/os.js';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
-	note: Misskey.entities.Note;
+	note: Misskey.entities.Note & {
+		isSchedule? : boolean,
+		scheduledNoteId?: string
+	};
 	expandAllCws?: boolean;
 	hideFiles?: boolean;
 }>();
 
 let showContent = ref(defaultStore.state.uncollapseCW);
+const isDeleted = ref(false);
+
+const emit = defineEmits<{
+	(ev: 'editScheduleNote'): void;
+}>();
+
+async function deleteScheduleNote() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.deleteConfirm,
+		okText: i18n.ts.delete,
+		cancelText: i18n.ts.cancel,
+	});
+	if (canceled) return;
+	await os.apiWithDialog('notes/schedule/delete', { noteId: props.note.id })
+		.then(() => {
+			isDeleted.value = true;
+		});
+}
+
+async function editScheduleNote() {
+	try {
+		await misskeyApi('notes/schedule/delete', { noteId: props.note.id })
+			.then(() => {
+				isDeleted.value = true;
+			});
+	} catch (err) {
+		console.error(err);
+	}
+
+	await os.post({
+		initialNote: props.note,
+		renote: props.note.renote,
+		reply: props.note.reply,
+		channel: props.note.channel,
+	});
+	emit('editScheduleNote');
+}
 
 watch(() => props.expandAllCws, (expandAllCws) => {
 	if (expandAllCws !== showContent.value) showContent.value = expandAllCws;
@@ -50,6 +96,11 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 	font-size: 0.95em;
 }
 
+.button{
+	margin-right: var(--margin);
+	margin-bottom: var(--margin);
+}
+
 .avatar {
 	flex-shrink: 0;
 	display: block;
-- 
cgit v1.2.3-freya


From 4f58b8de20625da577a2b7a8055d065bbddb94d1 Mon Sep 17 00:00:00 2001
From: Marie <github@yuugi.dev>
Date: Sun, 3 Nov 2024 03:39:19 +0100
Subject: fix: drive content not being loaded

---
 .../server/api/endpoints/notes/schedule/create.ts  | 28 +++-------------------
 .../server/api/endpoints/notes/schedule/list.ts    |  3 +++
 packages/frontend/src/components/MkMediaList.vue   |  2 +-
 packages/frontend/src/components/MkNoteSimple.vue  |  2 ++
 .../src/components/MkSchedulePostListDialog.vue    |  3 +++
 packages/misskey-js/etc/misskey-js.api.md          |  2 +-
 packages/misskey-js/src/autogen/types.ts           |  8 -------
 7 files changed, 13 insertions(+), 35 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteSimple.vue')

diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts
index ecdfa4bf2e..c22c29ae31 100644
--- a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts
@@ -6,7 +6,7 @@
 import ms from 'ms';
 import { In } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import { isPureRenote } from 'cherrypick-js/note.js';
+import { isPureRenote } from '@/misc/is-renote.js';
 import type { MiUser } from '@/models/User.js';
 import type {
 	UsersRepository,
@@ -19,7 +19,6 @@ import type {
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiNote } from '@/models/Note.js';
 import type { MiChannel } from '@/models/Channel.js';
-import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { QueueService } from '@/core/QueueService.js';
@@ -129,7 +128,6 @@ export const paramDef = {
 		} },
 		cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 },
 		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
-		disableRightClick: { type: 'boolean', default: false },
 		noExtractMentions: { type: 'boolean', default: false },
 		noExtractHashtags: { type: 'boolean', default: false },
 		noExtractEmojis: { type: 'boolean', default: false },
@@ -141,7 +139,6 @@ export const paramDef = {
 		text: {
 			type: 'string',
 			minLength: 1,
-			maxLength: MAX_NOTE_TEXT_LENGTH,
 			nullable: true,
 		},
 		fileIds: {
@@ -175,16 +172,6 @@ export const paramDef = {
 			},
 			required: ['choices'],
 		},
-		event: {
-			type: 'object',
-			nullable: true,
-			properties: {
-				title: { type: 'string', minLength: 1, maxLength: 128, nullable: false },
-				start: { type: 'integer', nullable: false },
-				end: { type: 'integer', nullable: true },
-				metadata: { type: 'object' },
-			},
-		},
 		scheduleNote: {
 			type: 'object',
 			nullable: false,
@@ -227,11 +214,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private queueService: QueueService,
 		private roleService: RoleService,
-    private idService: IdService,
+		private idService: IdService,
 	) {
-		super({
-			...meta,
-		}, paramDef, async (ps, me) => {
+		super(meta, paramDef, async (ps, me) => {
 			const scheduleNoteCount = await this.noteScheduleRepository.countBy({ userId: me.id });
 			const scheduleNoteMax = (await this.roleService.getUserPolicies(me.id)).scheduleNoteMax;
 			if (scheduleNoteCount >= scheduleNoteMax) {
@@ -358,13 +343,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				apMentions: ps.noExtractMentions ? [] : undefined,
 				apHashtags: ps.noExtractHashtags ? [] : undefined,
 				apEmojis: ps.noExtractEmojis ? [] : undefined,
-				event: ps.event ? {
-					start: new Date(ps.event.start!).toISOString(),
-					end: ps.event.end ? new Date(ps.event.end).toISOString() : null,
-					title: ps.event.title!,
-					metadata: ps.event.metadata ?? {},
-				} : undefined,
-				disableRightClick: ps.disableRightClick,
 			};
 
 			if (ps.scheduleNote.scheduledAt) {
diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/list.ts b/packages/backend/src/server/api/endpoints/notes/schedule/list.ts
index 88da4f4043..4895733d4e 100644
--- a/packages/backend/src/server/api/endpoints/notes/schedule/list.ts
+++ b/packages/backend/src/server/api/endpoints/notes/schedule/list.ts
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import type { MiNote, MiNoteSchedule, NoteScheduleRepository } from '@/models/_.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { QueryService } from '@/core/QueryService.js';
 import { Packed } from '@/misc/json-schema.js';
 import { noteVisibilities } from '@/types.js';
@@ -81,6 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteScheduleRepository: NoteScheduleRepository,
 
 		private userEntityService: UserEntityService,
+		private driveFileEntityService: DriveFileEntityService,
 		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -115,6 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 						reactionAcceptance: item.note.reactionAcceptance ?? null,
 						visibleUsers: item.note.visibleUsers ? await userEntityService.packMany(item.note.visibleUsers.map(u => u.id), me) : [],
 						fileIds: item.note.files ? item.note.files : [],
+						files: await this.driveFileEntityService.packManyByIds(item.note.files),
 						createdAt: item.scheduledAt.toISOString(),
 						isSchedule: true,
 						id: item.id,
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 5209489046..4ef929e81f 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -35,13 +35,13 @@ import * as Misskey from 'misskey-js';
 import PhotoSwipeLightbox from 'photoswipe/lightbox';
 import PhotoSwipe from 'photoswipe';
 import 'photoswipe/style.css';
+import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@@/js/const.js';
 import XBanner from '@/components/MkMediaBanner.vue';
 import XImage from '@/components/MkMediaImage.vue';
 import XVideo from '@/components/MkMediaVideo.vue';
 import XModPlayer from '@/components/SkModPlayer.vue';
 import XFlashPlayer from '@/components/SkFlashPlayer.vue';
 import * as os from '@/os.js';
-import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES, FILE_TYPE_FLASH_CONTENT, FILE_EXT_FLASH_CONTENT } from '@@/js/const.js';
 import { defaultStore } from '@/store.js';
 import { focusParent } from '@/scripts/focus.js';
 
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 7d2bbb31d3..48bf53fab5 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -33,6 +33,8 @@ import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note & {
diff --git a/packages/frontend/src/components/MkSchedulePostListDialog.vue b/packages/frontend/src/components/MkSchedulePostListDialog.vue
index cf793c7110..8311981a75 100644
--- a/packages/frontend/src/components/MkSchedulePostListDialog.vue
+++ b/packages/frontend/src/components/MkSchedulePostListDialog.vue
@@ -52,8 +52,11 @@ const paginationEl = ref();
 const pagination: Paging = {
 	endpoint: 'notes/schedule/list',
 	limit: 10,
+	offsetMode: true,
 };
 
+console.log(pagination);
+
 function listUpdate() {
 	paginationEl.value.reload();
 }
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index a74a4521e7..ca7a374a67 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2953,7 +2953,7 @@ type PartialRolePolicyOverride = Partial<{
 }>;
 
 // @public (undocumented)
-export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
+export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
 
 // @public (undocumented)
 type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 2e6320c5be..c8d7194405 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -24475,8 +24475,6 @@ export type operations = {
            */
           reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
           /** @default false */
-          disableRightClick?: boolean;
-          /** @default false */
           noExtractMentions?: boolean;
           /** @default false */
           noExtractHashtags?: boolean;
@@ -24495,12 +24493,6 @@ export type operations = {
             expiresAt?: number | null;
             expiredAfter?: number | null;
           }) | null;
-          event?: ({
-            title?: string;
-            start?: number;
-            end?: number | null;
-            metadata?: Record<string, never>;
-          }) | null;
           scheduleNote: {
             scheduledAt?: number;
           };
-- 
cgit v1.2.3-freya


From 3efb99dd576a2e3492fea7bd15a77c0414289fff Mon Sep 17 00:00:00 2001
From: Marie <github@yuugi.dev>
Date: Mon, 4 Nov 2024 18:05:29 +0100
Subject: fix: missing MKButton import

---
 packages/frontend/src/components/MkNoteSimple.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'packages/frontend/src/components/MkNoteSimple.vue')

diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 48bf53fab5..6db95f1987 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -32,6 +32,7 @@ import * as os from '@/os.js';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
+import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-- 
cgit v1.2.3-freya


From 7eb0996b60943cf4aeb7e753fec9cfac5d2322ed Mon Sep 17 00:00:00 2001
From: Marie <github@yuugi.dev>
Date: Mon, 4 Nov 2024 18:06:38 +0100
Subject: upd: use apiWithDialog instead of misskeyApi

---
 packages/frontend/src/components/MkNoteSimple.vue | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteSimple.vue')

diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 6db95f1987..e7a357d1b6 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -34,7 +34,6 @@ import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -68,14 +67,10 @@ async function deleteScheduleNote() {
 }
 
 async function editScheduleNote() {
-	try {
-		await misskeyApi('notes/schedule/delete', { noteId: props.note.id })
-			.then(() => {
-				isDeleted.value = true;
-			});
-	} catch (err) {
-		console.error(err);
-	}
+	await os.apiWithDialog('notes/schedule/delete', { noteId: props.note.id })
+		.then(() => {
+			isDeleted.value = true;
+		});
 
 	await os.post({
 		initialNote: props.note,
-- 
cgit v1.2.3-freya


From bf01dcd8fb770d88f8509bbcb63bdaef1aa97fa5 Mon Sep 17 00:00:00 2001
From: Marie <github@yuugi.dev>
Date: Mon, 9 Dec 2024 18:58:57 +0100
Subject: Apply suggestions

---
 .../processors/ScheduleNotePostProcessorService.ts | 65 ++++++++++++----------
 packages/frontend/src/components/MkNoteSimple.vue  |  2 +-
 2 files changed, 37 insertions(+), 30 deletions(-)

(limited to 'packages/frontend/src/components/MkNoteSimple.vue')

diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts
index ea43448ed0..62e3d1072f 100644
--- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts
+++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts
@@ -11,6 +11,7 @@ import type { ChannelsRepository, DriveFilesRepository, MiDriveFile, NoteSchedul
 import { DI } from '@/di-symbols.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import type { MiScheduleNoteType } from '@/models/NoteSchedule.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 import type { ScheduleNotePostJobData } from '../types.js';
@@ -39,7 +40,36 @@ export class ScheduleNotePostProcessorService {
 		this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
 	}
 
-    @bindThis
+	@bindThis
+	private async isValidNoteSchedule(note: MiScheduleNoteType, id: string): Promise<boolean> {
+		const reply = note.reply ? await this.notesRepository.findOneBy({ id: note.reply }) : undefined;
+		const renote = note.reply ? await this.notesRepository.findOneBy({ id: note.renote }) : undefined;
+		const channel = note.channel ? await this.channelsRepository.findOneBy({ id: note.channel, isArchived: false }) : undefined;
+		if (note.reply && !reply) {
+			this.logger.warn('Schedule Note Failed Reason: parent note to reply does not exist');
+			this.notificationService.createNotification(id, 'scheduledNoteFailed', {
+				reason: 'Replied to note on your scheduled note no longer exists',
+			});
+			return false;
+		}
+		if (note.renote && !renote) {
+			this.logger.warn('Schedule Note Failed Reason: attached quote note no longer exists');
+			this.notificationService.createNotification(id, 'scheduledNoteFailed', {
+				reason: 'A quoted note from one of your scheduled notes no longer exists',
+			});
+			return false;
+		}
+		if (note.channel && !channel) {
+			this.logger.warn('Schedule Note Failed Reason: Channel does not exist');
+			this.notificationService.createNotification(id, 'scheduledNoteFailed', {
+				reason: 'An attached channel on your scheduled note no longer exists',
+			});
+			return false;
+		}
+		return true;
+	}
+
+	@bindThis
 	public async process(job: Bull.Job<ScheduleNotePostJobData>): Promise<void> {
 		this.noteScheduleRepository.findOneBy({ id: job.data.scheduleNoteId }).then(async (data) => {
 			if (!data) {
@@ -47,11 +77,10 @@ export class ScheduleNotePostProcessorService {
 			} else {
 				const me = await this.usersRepository.findOneBy({ id: data.userId });
 				const note = data.note;
-
-				//idの形式でキューに積んであったのをDBから取り寄せる
 				const reply = note.reply ? await this.notesRepository.findOneBy({ id: note.reply }) : undefined;
 				const renote = note.reply ? await this.notesRepository.findOneBy({ id: note.renote }) : undefined;
 				const channel = note.channel ? await this.channelsRepository.findOneBy({ id: note.channel, isArchived: false }) : undefined;
+
 				let files: MiDriveFile[] = [];
 				const fileIds = note.files;
 
@@ -72,37 +101,15 @@ export class ScheduleNotePostProcessorService {
 					return;
 				}
 
-				if (note.files.length !== files.length) {
-					this.logger.warn('Schedule Note Failed Reason: files are missing in the user\'s drive');
-					this.notificationService.createNotification(me.id, 'scheduledNoteFailed', {
-						reason: 'Some attached files on your scheduled note no longer exist',
-					});
-					await this.noteScheduleRepository.remove(data);
-					return;
-				}
-
-				if (note.reply && !reply) {
-					this.logger.warn('Schedule Note Failed Reason: parent note to reply does not exist');
-					this.notificationService.createNotification(me.id, 'scheduledNoteFailed', {
-						reason: 'Replied to note on your scheduled note no longer exists',
-					});
-					await this.noteScheduleRepository.remove(data);
-					return;
-				}
-
-				if (note.renote && !renote) {
-					this.logger.warn('Schedule Note Failed Reason: attached quote note no longer exists');
-					this.notificationService.createNotification(me.id, 'scheduledNoteFailed', {
-						reason: 'A quoted note from one of your scheduled notes no longer exists',
-					});
+				if (!await this.isValidNoteSchedule(note, me.id)) {
 					await this.noteScheduleRepository.remove(data);
 					return;
 				}
 
-				if (note.channel && !channel) {
-					this.logger.warn('Schedule Note Failed Reason: Channel does not exist');
+				if (note.files.length !== files.length) {
+					this.logger.warn('Schedule Note Failed Reason: files are missing in the user\'s drive');
 					this.notificationService.createNotification(me.id, 'scheduledNoteFailed', {
-						reason: 'An attached channel on your scheduled note no longer exists',
+						reason: 'Some attached files on your scheduled note no longer exist',
 					});
 					await this.noteScheduleRepository.remove(data);
 					return;
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index e7a357d1b6..924262d62e 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
 -->
 
 <template>
-<div v-show="!isDeleted" :class="$style.root" :tabindex="!isDeleted ? '-1' : undefined">
+<div v-if="!isDeleted" :class="$style.root">
 	<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
 	<div :class="$style.main">
 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
-- 
cgit v1.2.3-freya

