予約投稿の一覧表示、削除をできるようにした
dbに投稿内容を保存するようにした Signed-off-by: mattyatea <mattyacocacora0@gmail.com>
This commit is contained in:
		
							parent
							
								
									14b48f87d8
								
							
						
					
					
						commit
						540f531b6d
					
				|  | @ -593,8 +593,9 @@ export interface Locale { | ||||||
|     "enableInfiniteScroll": string; |     "enableInfiniteScroll": string; | ||||||
|     "visibility": string; |     "visibility": string; | ||||||
|     "poll": string; |     "poll": string; | ||||||
|     "schedule": string; |     "schedulePost": string; | ||||||
|     "useCw": string; |     "useCw": string; | ||||||
|  |     "schedulePostList": string; | ||||||
|     "enablePlayer": string; |     "enablePlayer": string; | ||||||
|     "disablePlayer": string; |     "disablePlayer": string; | ||||||
|     "expandTweet": string; |     "expandTweet": string; | ||||||
|  |  | ||||||
|  | @ -590,8 +590,9 @@ invisibleNote: "非公開の投稿" | ||||||
| enableInfiniteScroll: "自動でもっと見る" | enableInfiniteScroll: "自動でもっと見る" | ||||||
| visibility: "公開範囲" | visibility: "公開範囲" | ||||||
| poll: "アンケート" | poll: "アンケート" | ||||||
| schedule: "予約" | schedulePost: "予約投稿" | ||||||
| useCw: "内容を隠す" | useCw: "内容を隠す" | ||||||
|  | schedulePostList: "予約投稿一覧" | ||||||
| enablePlayer: "プレイヤーを開く" | enablePlayer: "プレイヤーを開く" | ||||||
| disablePlayer: "プレイヤーを閉じる" | disablePlayer: "プレイヤーを閉じる" | ||||||
| expandTweet: "ポストを展開する" | expandTweet: "ポストを展開する" | ||||||
|  |  | ||||||
|  | @ -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"`); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -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"`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -19,8 +19,12 @@ export class MiNoteSchedule { | ||||||
| 	@Column('jsonb') | 	@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 }; | 	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', { | 	@Column('varchar', { | ||||||
| 		length: 260, | 		length: 260, | ||||||
| 	}) | 	}) | ||||||
| 	public userId: MiUser['id']; | 	public userId: MiUser['id']; | ||||||
|  | 
 | ||||||
|  | 	@Column('timestamp with time zone') | ||||||
|  | 	public expiresAt: Date; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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_conversation from './endpoints/notes/conversation.js'; | ||||||
| import * as ep___notes_create from './endpoints/notes/create.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_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_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_create from './endpoints/notes/favorites/create.js'; | ||||||
| import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; | import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; | ||||||
| import * as ep___notes_featured from './endpoints/notes/featured.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_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_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_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_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_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_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 }; | 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_conversation, | ||||||
| 		$notes_create, | 		$notes_create, | ||||||
| 		$notes_schedule_create, | 		$notes_schedule_create, | ||||||
|  | 		$notes_schedule_list, | ||||||
| 		$notes_delete, | 		$notes_delete, | ||||||
|  | 		$notes_schedule_delete, | ||||||
| 		$notes_favorites_create, | 		$notes_favorites_create, | ||||||
| 		$notes_favorites_delete, | 		$notes_favorites_delete, | ||||||
| 		$notes_featured, | 		$notes_featured, | ||||||
|  | @ -1343,7 +1349,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention | ||||||
| 		$notes_conversation, | 		$notes_conversation, | ||||||
| 		$notes_create, | 		$notes_create, | ||||||
| 		$notes_schedule_create, | 		$notes_schedule_create, | ||||||
|  | 		$notes_schedule_list, | ||||||
| 		$notes_delete, | 		$notes_delete, | ||||||
|  | 		$notes_schedule_delete, | ||||||
| 		$notes_favorites_create, | 		$notes_favorites_create, | ||||||
| 		$notes_favorites_delete, | 		$notes_favorites_delete, | ||||||
| 		$notes_featured, | 		$notes_featured, | ||||||
|  |  | ||||||
|  | @ -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_conversation from './endpoints/notes/conversation.js'; | ||||||
| import * as ep___notes_create from './endpoints/notes/create.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_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_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_create from './endpoints/notes/favorites/create.js'; | ||||||
| import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; | import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; | ||||||
| import * as ep___notes_featured from './endpoints/notes/featured.js'; | import * as ep___notes_featured from './endpoints/notes/featured.js'; | ||||||
|  | @ -621,7 +623,9 @@ const eps = [ | ||||||
| 	['notes/conversation', ep___notes_conversation], | 	['notes/conversation', ep___notes_conversation], | ||||||
| 	['notes/create', ep___notes_create], | 	['notes/create', ep___notes_create], | ||||||
| 	['notes/create-schedule', ep___notes_schedule_create], | 	['notes/create-schedule', ep___notes_schedule_create], | ||||||
|  | 	['notes/list-schedule', ep___notes_schedule_list], | ||||||
| 	['notes/delete', ep___notes_delete], | 	['notes/delete', ep___notes_delete], | ||||||
|  | 	['notes/delete-schedule', ep___notes_schedule_delete], | ||||||
| 	['notes/favorites/create', ep___notes_favorites_create], | 	['notes/favorites/create', ep___notes_favorites_create], | ||||||
| 	['notes/favorites/delete', ep___notes_favorites_delete], | 	['notes/favorites/delete', ep___notes_favorites_delete], | ||||||
| 	['notes/featured', ep___notes_featured], | 	['notes/featured', ep___notes_featured], | ||||||
|  |  | ||||||
|  | @ -197,9 +197,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 		@Inject(DI.usersRepository) | 		@Inject(DI.usersRepository) | ||||||
| 		private usersRepository: UsersRepository, | 		private usersRepository: UsersRepository, | ||||||
| 
 | 
 | ||||||
| 		@Inject(DI.db) |  | ||||||
| 		private db: DataSource, |  | ||||||
| 
 |  | ||||||
| 		@Inject(DI.notesRepository) | 		@Inject(DI.notesRepository) | ||||||
| 		private notesRepository: NotesRepository, | 		private notesRepository: NotesRepository, | ||||||
| 
 | 
 | ||||||
|  | @ -388,6 +385,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 					id: noteId, | 					id: noteId, | ||||||
| 					note: note, | 					note: note, | ||||||
| 					userId: me.id, | 					userId: me.id, | ||||||
|  | 					expiresAt: new Date(ps.schedule.expiresAt), | ||||||
| 				}); | 				}); | ||||||
| 
 | 
 | ||||||
| 				const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); | 				const delay = new Date(ps.schedule.expiresAt).getTime() - Date.now(); | ||||||
|  |  | ||||||
|  | @ -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<typeof meta, typeof paramDef> { // 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 }); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -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<typeof meta, typeof paramDef> { // 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; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 		<div v-if="mock"> | 		<div v-if="mock"> | ||||||
| 			<MkTime :time="note.createdAt" colored/> | 			<MkTime :time="note.createdAt" colored/> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<MkTime v-else-if="note.isSchedule" mode="absolute" :time="note.createdAt" colored/> | ||||||
| 		<MkA v-else :to="notePage(note)"> | 		<MkA v-else :to="notePage(note)"> | ||||||
| 			<MkTime :time="note.createdAt" colored/> | 			<MkTime :time="note.createdAt" colored/> | ||||||
| 		</MkA> | 		</MkA> | ||||||
|  | @ -42,7 +43,8 @@ import { notePage } from '@/filters/note.js'; | ||||||
| import { userPage } from '@/filters/user.js'; | import { userPage } from '@/filters/user.js'; | ||||||
| 
 | 
 | ||||||
| defineProps<{ | defineProps<{ | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note & {isSchedule? : boolean}; | ||||||
|  |   scheduled?: boolean; | ||||||
| }>(); | }>(); | ||||||
| 
 | 
 | ||||||
| const mock = inject<boolean>('mock', false); | const mock = inject<boolean>('mock', false); | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| --> | --> | ||||||
| 
 | 
 | ||||||
| <template> | <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/> | 	<MkAvatar :class="$style.avatar" :user="note.user" link preview/> | ||||||
| 	<div :class="$style.main"> | 	<div :class="$style.main"> | ||||||
| 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | ||||||
|  | @ -16,23 +16,40 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 			<div v-show="note.cw == null || showContent"> | 			<div v-show="note.cw == null || showContent"> | ||||||
| 				<MkSubNoteContent :class="$style.text" :note="note"/> | 				<MkSubNoteContent :class="$style.text" :note="note"/> | ||||||
| 			</div> | 			</div> | ||||||
|  | 			<div v-if="note.isSchedule" style="margin-top: 10px;"> | ||||||
|  | 				<MkButton :class="$style.button" inline @click="editScheduleNote(note.id)">{{ i18n.ts.edit }}</MkButton> | ||||||
|  | 				<MkButton :class="$style.button" inline danger @click="deleteScheduleNote()">{{ i18n.ts.delete }}</MkButton> | ||||||
|  | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { } from 'vue'; |  | ||||||
| import * as Misskey from 'misskey-js'; | import * as Misskey from 'misskey-js'; | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | import { i18n } from '../i18n.js'; | ||||||
| import MkNoteHeader from '@/components/MkNoteHeader.vue'; | import MkNoteHeader from '@/components/MkNoteHeader.vue'; | ||||||
| import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; | import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; | ||||||
| import MkCwButton from '@/components/MkCwButton.vue'; | import MkCwButton from '@/components/MkCwButton.vue'; | ||||||
| import { $i } from '@/account.js'; | import MkButton from '@/components/MkButton.vue'; | ||||||
| 
 | import * as os from '@/os.js'; | ||||||
|  | const isDeleted = ref(false); | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	note: Misskey.entities.Note; |   note: Misskey.entities.Note & {isSchedule? : boolean}; | ||||||
| }>(); | }>(); | ||||||
| 
 | 
 | ||||||
|  | async function deleteScheduleNote() { | ||||||
|  | 	await os.apiWithDialog('notes/delete-schedule', { noteId: props.note.id }) | ||||||
|  | 		.then(() => { | ||||||
|  | 			isDeleted.value = true; | ||||||
|  | 		}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function editScheduleNote(id) { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const showContent = $ref(false); | const showContent = $ref(false); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -42,8 +59,12 @@ const showContent = $ref(false); | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| 	padding: 0; | 	padding: 0; | ||||||
| 	font-size: 0.95em; | 	font-size: 0.95em; | ||||||
|  |   border-bottom: solid 0.5px var(--divider); | ||||||
|  | } | ||||||
|  | .button{ | ||||||
|  |   margin-right: var(--margin); | ||||||
|  |   margin-bottom: var(--margin); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| .avatar { | .avatar { | ||||||
| 	flex-shrink: 0; | 	flex-shrink: 0; | ||||||
| 	display: block; | 	display: block; | ||||||
|  |  | ||||||
|  | @ -19,6 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 			</button> | 			</button> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div :class="$style.headerRight"> | 		<div :class="$style.headerRight"> | ||||||
|  | 			<button v-tooltip="i18n.ts.schedulePost" class="_button" :class="[$style.headerRightItem, { [$style.headerRightButtonActive]: schedule }]" @click="toggleSchedule"><i class="ti ti-calendar-time"></i></button> | ||||||
|  | 			<button v-tooltip="i18n.ts.schedulePostList" class="_button" :class="[$style.headerRightItem]" @click="listSchedulePost"><i class="ti ti-calendar-event"></i></button> | ||||||
| 			<template v-if="!(channel != null && fixed)"> | 			<template v-if="!(channel != null && fixed)"> | ||||||
| 				<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility"> | 				<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility"> | ||||||
| 					<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span> | 					<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span> | ||||||
|  | @ -81,7 +83,6 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 		<div :class="$style.footerLeft"> | 		<div :class="$style.footerLeft"> | ||||||
| 			<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button> | 			<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button> | ||||||
| 			<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button> | 			<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button> | ||||||
| 			<button v-tooltip="i18n.ts.schedule" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: schedule }]" @click="toggleSchedule"><i class="ti ti-calendar-event"></i></button> |  | ||||||
| 			<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button> | 			<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button> | ||||||
| 			<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button> | 			<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button> | ||||||
| 			<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button> | 			<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button> | ||||||
|  | @ -127,6 +128,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; | ||||||
| import { miLocalStorage } from '@/local-storage.js'; | import { miLocalStorage } from '@/local-storage.js'; | ||||||
| import { claimAchievement } from '@/scripts/achievements.js'; | import { claimAchievement } from '@/scripts/achievements.js'; | ||||||
| import MkScheduleEditor from '@/components/MkScheduleEditor.vue'; | import MkScheduleEditor from '@/components/MkScheduleEditor.vue'; | ||||||
|  | import { listSchedulePost } from '@/os.js'; | ||||||
| 
 | 
 | ||||||
| const modal = inject('modal'); | const modal = inject('modal'); | ||||||
| 
 | 
 | ||||||
|  | @ -1061,6 +1063,10 @@ defineExpose({ | ||||||
| 		background: none; | 		background: none; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |   &.headerRightButtonActive { | ||||||
|  |     color: var(--accent); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| 	&.danger { | 	&.danger { | ||||||
| 		color: #ff2a2a; | 		color: #ff2a2a; | ||||||
| 	} | 	} | ||||||
|  | @ -1214,6 +1220,7 @@ defineExpose({ | ||||||
| 	grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); | 	grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); | ||||||
| 	grid-auto-rows: 40px; | 	grid-auto-rows: 40px; | ||||||
| 	direction: rtl; | 	direction: rtl; | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .footerButton { | .footerButton { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <MkModalWindow | ||||||
|  | 	ref="dialogEl" | ||||||
|  | 	:withOkButton="false" | ||||||
|  | 	@click="cancel()" | ||||||
|  | 	@close="cancel()" | ||||||
|  | 	@closed="$emit('closed')" | ||||||
|  | > | ||||||
|  | 	<template #header> 予約投稿一覧</template> | ||||||
|  | 	<div v-for="item in notes"> | ||||||
|  | 		<MkSpacer :marginMin="14" :marginMax="16"> | ||||||
|  | 			<MkNoteSimple scheduled="true" :note="item.note"/> | ||||||
|  | 		</MkSpacer> | ||||||
|  | 	</div> | ||||||
|  | </MkModalWindow> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { onMounted, ref } from 'vue'; | ||||||
|  | import * as Misskey from 'misskey-js'; | ||||||
|  | import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||||
|  | import * as os from '@/os.js'; | ||||||
|  | import MkNoteSimple from '@/components/MkNoteSimple.vue'; | ||||||
|  | import MkSignin from '@/components/MkSignin.vue'; | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'ok', selected: Misskey.entities.UserDetailed): void; | ||||||
|  | 	(ev: 'cancel'): void; | ||||||
|  | 	(ev: 'closed'): void; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | let dialogEl = $ref(); | ||||||
|  | const notes = ref([]); | ||||||
|  | const cancel = () => { | ||||||
|  | 	emit('cancel'); | ||||||
|  | 	dialogEl.close(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | onMounted(async () => { | ||||||
|  | 	notes.value = await os.api('notes/list-schedule'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" module> | ||||||
|  | </style> | ||||||
|  | @ -431,7 +431,13 @@ export async function selectUser(opts: { includeSelf?: boolean } = {}) { | ||||||
| 		}, 'closed'); | 		}, 'closed'); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | export async function listSchedulePost() { | ||||||
|  | 	return new Promise((resolve, reject) => { | ||||||
|  | 		popup(defineAsyncComponent(() => import('@/components/MkSchedulePostListDialog.vue')), { | ||||||
|  | 		}, { | ||||||
|  | 		}, 'closed'); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
| export async function selectDriveFile(multiple: boolean): Promise<Misskey.entities.DriveFile[]> { | export async function selectDriveFile(multiple: boolean): Promise<Misskey.entities.DriveFile[]> { | ||||||
| 	return new Promise((resolve, reject) => { | 	return new Promise((resolve, reject) => { | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue