Improve: unclip (#8823)
* Refactor clip page to use Composition API * Refactor clip page * Refactor clip page * Refactor clip page * Improve: unclip * Fix unclip * Fix unclip * chore: better type and name * Fix * Fix clipPage vue provider Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
		
							parent
							
								
									d7bab7cf0b
								
							
						
					
					
						commit
						5b7595d9d7
					
				
					 7 changed files with 110 additions and 9 deletions
				
			
		|  | @ -643,6 +643,8 @@ clip: "クリップ" | ||||||
| createNew: "新規作成" | createNew: "新規作成" | ||||||
| optional: "任意" | optional: "任意" | ||||||
| createNewClip: "新しいクリップを作成" | createNewClip: "新しいクリップを作成" | ||||||
|  | unclip: "クリップ解除" | ||||||
|  | confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?" | ||||||
| public: "パブリック" | public: "パブリック" | ||||||
| i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。" | i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。" | ||||||
| manageAccessTokens: "アクセストークンの管理" | manageAccessTokens: "アクセストークンの管理" | ||||||
|  |  | ||||||
|  | @ -99,6 +99,7 @@ import * as ep___charts_user_notes from './endpoints/charts/user/notes.js'; | ||||||
| import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; | import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; | ||||||
| import * as ep___charts_users from './endpoints/charts/users.js'; | import * as ep___charts_users from './endpoints/charts/users.js'; | ||||||
| import * as ep___clips_addNote from './endpoints/clips/add-note.js'; | import * as ep___clips_addNote from './endpoints/clips/add-note.js'; | ||||||
|  | import * as ep___clips_removeNote from './endpoints/clips/remove-note.js'; | ||||||
| import * as ep___clips_create from './endpoints/clips/create.js'; | import * as ep___clips_create from './endpoints/clips/create.js'; | ||||||
| import * as ep___clips_delete from './endpoints/clips/delete.js'; | import * as ep___clips_delete from './endpoints/clips/delete.js'; | ||||||
| import * as ep___clips_list from './endpoints/clips/list.js'; | import * as ep___clips_list from './endpoints/clips/list.js'; | ||||||
|  | @ -409,6 +410,7 @@ const eps = [ | ||||||
| 	['charts/user/reactions', ep___charts_user_reactions], | 	['charts/user/reactions', ep___charts_user_reactions], | ||||||
| 	['charts/users', ep___charts_users], | 	['charts/users', ep___charts_users], | ||||||
| 	['clips/add-note', ep___clips_addNote], | 	['clips/add-note', ep___clips_addNote], | ||||||
|  | 	['clips/remove-note', ep___clips_removeNote], | ||||||
| 	['clips/create', ep___clips_create], | 	['clips/create', ep___clips_create], | ||||||
| 	['clips/delete', ep___clips_delete], | 	['clips/delete', ep___clips_delete], | ||||||
| 	['clips/list', ep___clips_list], | 	['clips/list', ep___clips_list], | ||||||
|  |  | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | import define from '../../define.js'; | ||||||
|  | import { ClipNotes, Clips } from '@/models/index.js'; | ||||||
|  | import { ApiError } from '../../error.js'; | ||||||
|  | import { getNote } from '../../common/getters.js'; | ||||||
|  | 
 | ||||||
|  | export const meta = { | ||||||
|  | 	tags: ['account', 'notes', 'clips'], | ||||||
|  | 
 | ||||||
|  | 	requireCredential: true, | ||||||
|  | 
 | ||||||
|  | 	kind: 'write:account', | ||||||
|  | 
 | ||||||
|  | 	errors: { | ||||||
|  | 		noSuchClip: { | ||||||
|  | 			message: 'No such clip.', | ||||||
|  | 			code: 'NO_SUCH_CLIP', | ||||||
|  | 			id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		noSuchNote: { | ||||||
|  | 			message: 'No such note.', | ||||||
|  | 			code: 'NO_SUCH_NOTE', | ||||||
|  | 			id: 'aff017de-190e-434b-893e-33a9ff5049d8', | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | export const paramDef = { | ||||||
|  | 	type: 'object', | ||||||
|  | 	properties: { | ||||||
|  | 		clipId: { type: 'string', format: 'misskey:id' }, | ||||||
|  | 		noteId: { type: 'string', format: 'misskey:id' }, | ||||||
|  | 	}, | ||||||
|  | 	required: ['clipId', 'noteId'], | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | // eslint-disable-next-line import/no-default-export
 | ||||||
|  | export default define(meta, paramDef, async (ps, user) => { | ||||||
|  | 	const clip = await Clips.findOneBy({ | ||||||
|  | 		id: ps.clipId, | ||||||
|  | 		userId: user.id, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (clip == null) { | ||||||
|  | 		throw new ApiError(meta.errors.noSuchClip); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const note = await getNote(ps.noteId).catch(e => { | ||||||
|  | 		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); | ||||||
|  | 		throw e; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	await ClipNotes.delete({ | ||||||
|  | 		noteId: note.id, | ||||||
|  | 		clipId: clip.id, | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | @ -251,12 +251,12 @@ function onContextmenu(ev: MouseEvent): void { | ||||||
| 		ev.preventDefault(); | 		ev.preventDefault(); | ||||||
| 		react(); | 		react(); | ||||||
| 	} else { | 	} else { | ||||||
| 		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus); | 		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), ev).then(focus); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function menu(viaKeyboard = false): void { | function menu(viaKeyboard = false): void { | ||||||
| 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { | 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), menuButton.value, { | ||||||
| 		viaKeyboard, | 		viaKeyboard, | ||||||
| 	}).then(focus); | 	}).then(focus); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; | import { computed, inject, onMounted, onUnmounted, reactive, ref, Ref } from 'vue'; | ||||||
| import * as mfm from 'mfm-js'; | import * as mfm from 'mfm-js'; | ||||||
| import * as misskey from 'misskey-js'; | import * as misskey from 'misskey-js'; | ||||||
| import MkNoteSub from './MkNoteSub.vue'; | import MkNoteSub from './MkNoteSub.vue'; | ||||||
|  | @ -225,6 +225,8 @@ function undoReact(note): void { | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const cullentClipPage = inject<Ref<misskey.entities.Clip>>('cullentClipPage'); | ||||||
|  | 
 | ||||||
| function onContextmenu(ev: MouseEvent): void { | function onContextmenu(ev: MouseEvent): void { | ||||||
| 	const isLink = (el: HTMLElement) => { | 	const isLink = (el: HTMLElement) => { | ||||||
| 		if (el.tagName === 'A') return true; | 		if (el.tagName === 'A') return true; | ||||||
|  | @ -239,12 +241,12 @@ function onContextmenu(ev: MouseEvent): void { | ||||||
| 		ev.preventDefault(); | 		ev.preventDefault(); | ||||||
| 		react(); | 		react(); | ||||||
| 	} else { | 	} else { | ||||||
| 		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus); | 		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, cullentClipPage }), ev).then(focus); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function menu(viaKeyboard = false): void { | function menu(viaKeyboard = false): void { | ||||||
| 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { | 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, cullentClipPage }), menuButton.value, { | ||||||
| 		viaKeyboard, | 		viaKeyboard, | ||||||
| 	}).then(focus); | 	}).then(focus); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed, watch } from 'vue'; | import { computed, watch, provide } from 'vue'; | ||||||
| import * as misskey from 'misskey-js'; | import * as misskey from 'misskey-js'; | ||||||
| import XNotes from '@/components/notes.vue'; | import XNotes from '@/components/notes.vue'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
|  | @ -47,6 +47,8 @@ watch(() => props.clipId, async () => { | ||||||
| 	immediate: true, | 	immediate: true, | ||||||
| });  | });  | ||||||
| 
 | 
 | ||||||
|  | provide('cullentClipPage', $$(clip)); | ||||||
|  | 
 | ||||||
| defineExpose({ | defineExpose({ | ||||||
| 	[symbols.PAGE_INFO]: computed(() => clip ? { | 	[symbols.PAGE_INFO]: computed(() => clip ? { | ||||||
| 		title: clip.name, | 		title: clip.name, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { defineAsyncComponent, Ref } from 'vue'; | import { defineAsyncComponent, Ref, inject } from 'vue'; | ||||||
| import * as misskey from 'misskey-js'; | import * as misskey from 'misskey-js'; | ||||||
| import { $i } from '@/account'; | import { $i } from '@/account'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
|  | @ -14,6 +14,8 @@ export function getNoteMenu(props: { | ||||||
| 	menuButton: Ref<HTMLElement>; | 	menuButton: Ref<HTMLElement>; | ||||||
| 	translation: Ref<any>; | 	translation: Ref<any>; | ||||||
| 	translating: Ref<boolean>; | 	translating: Ref<boolean>; | ||||||
|  | 	isDeleted: Ref<boolean>; | ||||||
|  | 	cullentClipPage?: Ref<misskey.entities.Clip>; | ||||||
| }) { | }) { | ||||||
| 	const isRenote = ( | 	const isRenote = ( | ||||||
| 		props.note.renote != null && | 		props.note.renote != null && | ||||||
|  | @ -125,12 +127,37 @@ export function getNoteMenu(props: { | ||||||
| 		}, null, ...clips.map(clip => ({ | 		}, null, ...clips.map(clip => ({ | ||||||
| 			text: clip.name, | 			text: clip.name, | ||||||
| 			action: () => { | 			action: () => { | ||||||
| 				os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); | 				os.promiseDialog( | ||||||
|  | 					os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }), | ||||||
|  | 					null, | ||||||
|  | 					async (err) => { | ||||||
|  | 						if (err.id === '734806c4-542c-463a-9311-15c512803965') { | ||||||
|  | 							const confirm = await os.confirm({ | ||||||
|  | 								type: 'warning', | ||||||
|  | 								text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }), | ||||||
|  | 							}); | ||||||
|  | 							if (!confirm.canceled) { | ||||||
|  | 								os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }); | ||||||
|  | 								if (props.cullentClipPage?.value.id === clip.id) props.isDeleted.value = true; | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							os.alert({ | ||||||
|  | 								type: 'error', | ||||||
|  | 								text: err.message + '\n' + err.id, | ||||||
|  | 							}); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				); | ||||||
| 			} | 			} | ||||||
| 		}))], props.menuButton.value, { | 		}))], props.menuButton.value, { | ||||||
| 		}).then(focus); | 		}).then(focus); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	async function unclip(): Promise<void> { | ||||||
|  | 		os.apiWithDialog('clips/remove-note', { clipId: props.cullentClipPage.value.id, noteId: appearNote.id }); | ||||||
|  | 		props.isDeleted.value = true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	async function promote(): Promise<void> { | 	async function promote(): Promise<void> { | ||||||
| 		const { canceled, result: days } = await os.inputNumber({ | 		const { canceled, result: days } = await os.inputNumber({ | ||||||
| 			title: i18n.ts.numberOfDays, | 			title: i18n.ts.numberOfDays, | ||||||
|  | @ -169,7 +196,16 @@ export function getNoteMenu(props: { | ||||||
| 			noteId: appearNote.id | 			noteId: appearNote.id | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		menu = [{ | 		menu = [ | ||||||
|  | 		...( | ||||||
|  | 			props.cullentClipPage?.value.userId === $i.id ? [{ | ||||||
|  | 				icon: 'fas fa-circle-minus', | ||||||
|  | 				text: i18n.ts.unclip, | ||||||
|  | 				danger: true, | ||||||
|  | 				action: unclip, | ||||||
|  | 			}, null] : [] | ||||||
|  | 		), | ||||||
|  | 		{ | ||||||
| 			icon: 'fas fa-copy', | 			icon: 'fas fa-copy', | ||||||
| 			text: i18n.ts.copyContent, | 			text: i18n.ts.copyContent, | ||||||
| 			action: copyContent | 			action: copyContent | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue