wip: clip
This commit is contained in:
		| @@ -215,6 +215,7 @@ imageUrl: "画像URL" | |||||||
| remove: "削除" | remove: "削除" | ||||||
| removed: "削除しました" | removed: "削除しました" | ||||||
| removeAreYouSure: "「{x}」を削除しますか?" | removeAreYouSure: "「{x}」を削除しますか?" | ||||||
|  | deleteAreYouSure: "「{x}」を削除しますか?" | ||||||
| resetAreYouSure: "リセットしますか?" | resetAreYouSure: "リセットしますか?" | ||||||
| saved: "保存しました" | saved: "保存しました" | ||||||
| messaging: "チャット" | messaging: "チャット" | ||||||
|   | |||||||
							
								
								
									
										139
									
								
								src/client/pages/clip.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/client/pages/clip.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | |||||||
|  | <template> | ||||||
|  | <div v-if="clip" class="_section"> | ||||||
|  | 	<div class="okzinsic _content _panel _vMargin"> | ||||||
|  | 		<div class="description" v-if="clip.description"> | ||||||
|  | 			<Mfm :text="clip.description" :is-note="false" :i="$store.state.i"/> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
|  | 	<XNotes class="_content _vMargin" :pagination="pagination" :detail="true"/> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { computed, defineComponent } from 'vue'; | ||||||
|  | import { faEllipsisH, faPaperclip, faPencilAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import MkContainer from '@/components/ui/container.vue'; | ||||||
|  | import XPostForm from '@/components/post-form.vue'; | ||||||
|  | import XNotes from '@/components/notes.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  | 	components: { | ||||||
|  | 		MkContainer, | ||||||
|  | 		XPostForm, | ||||||
|  | 		XNotes, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	props: { | ||||||
|  | 		clipId: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			INFO: computed(() => this.clip ? { | ||||||
|  | 				title: this.clip.name, | ||||||
|  | 				icon: faPaperclip, | ||||||
|  | 				action: { | ||||||
|  | 					icon: faEllipsisH, | ||||||
|  | 					handler: this.menu | ||||||
|  | 				} | ||||||
|  | 			} : null), | ||||||
|  | 			clip: null, | ||||||
|  | 			pagination: { | ||||||
|  | 				endpoint: 'clips/notes', | ||||||
|  | 				limit: 10, | ||||||
|  | 				params: () => ({ | ||||||
|  | 					clipId: this.clipId, | ||||||
|  | 				}) | ||||||
|  | 			}, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		isOwned(): boolean { | ||||||
|  | 			return this.$store.getters.isSignedIn && this.clip && (this.$store.state.i.id === this.clip.userId); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		clipId: { | ||||||
|  | 			async handler() { | ||||||
|  | 				this.clip = await os.api('clips/show', { | ||||||
|  | 					clipId: this.clipId, | ||||||
|  | 				}); | ||||||
|  | 			}, | ||||||
|  | 			immediate: true | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	created() { | ||||||
|  |  | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		menu(ev) { | ||||||
|  | 			os.modalMenu([this.isOwned ? { | ||||||
|  | 				icon: faPencilAlt, | ||||||
|  | 				text: this.$t('edit'), | ||||||
|  | 				action: async () => { | ||||||
|  | 					const { canceled, result } = await os.form(this.clip.name, { | ||||||
|  | 						name: { | ||||||
|  | 							type: 'string', | ||||||
|  | 							label: this.$t('name'), | ||||||
|  | 							default: this.clip.name | ||||||
|  | 						}, | ||||||
|  | 						description: { | ||||||
|  | 							type: 'string', | ||||||
|  | 							required: false, | ||||||
|  | 							multiline: true, | ||||||
|  | 							label: this.$t('description'), | ||||||
|  | 							default: this.clip.description | ||||||
|  | 						}, | ||||||
|  | 						isPublic: { | ||||||
|  | 							type: 'boolean', | ||||||
|  | 							label: this.$t('public'), | ||||||
|  | 							default: this.clip.isPublic | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 					if (canceled) return; | ||||||
|  |  | ||||||
|  | 					os.apiWithDialog('clips/update', { | ||||||
|  | 						clipId: this.clip.id, | ||||||
|  | 						...result | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			} : undefined, this.isOwned ? { | ||||||
|  | 				icon: faTrashAlt, | ||||||
|  | 				text: this.$t('delete'), | ||||||
|  | 				danger: true, | ||||||
|  | 				action: async () => { | ||||||
|  | 					const { canceled } = await os.dialog({ | ||||||
|  | 						type: 'warning', | ||||||
|  | 						text: this.$t('deleteAreYouSure', { x: this.clip.name }), | ||||||
|  | 						showCancelButton: true | ||||||
|  | 					}); | ||||||
|  | 					if (canceled) return; | ||||||
|  |  | ||||||
|  | 					await os.apiWithDialog('clips/delete', { | ||||||
|  | 						clipId: this.clip.id, | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			} : undefined], ev.currentTarget || ev.target); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .okzinsic { | ||||||
|  | 	position: relative; | ||||||
|  |  | ||||||
|  | 	> .description { | ||||||
|  | 		padding: 16px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -1,10 +1,13 @@ | |||||||
| <template> | <template> | ||||||
| <div class="_section"> | <div class="_section qtcaoidl"> | ||||||
| 	<MkButton @click="create" primary class="add"><Fa :icon="faPlus"/> {{ $t('add') }}</MkButton> | 	<MkButton @click="create" primary class="add"><Fa :icon="faPlus"/> {{ $t('add') }}</MkButton> | ||||||
|  |  | ||||||
| 	<div class="_content"> | 	<div class="_content"> | ||||||
| 		<MkPagination :pagination="pagination" #default="{items}" ref="list"> | 		<MkPagination :pagination="pagination" #default="{items}" ref="list" class="list"> | ||||||
| 			<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">{{ item.name }}</MkA> | 			<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin"> | ||||||
|  | 				<b>{{ item.name }}</b> | ||||||
|  | 				<div v-if="item.description" class="description">{{ item.description }}</div> | ||||||
|  | 			</MkA> | ||||||
| 		</MkPagination> | 		</MkPagination> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| @@ -76,3 +79,26 @@ export default defineComponent({ | |||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .qtcaoidl { | ||||||
|  | 	> .add { | ||||||
|  | 		margin: 0 auto 16px auto; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	> ._content { | ||||||
|  | 		> .list { | ||||||
|  | 			> .item { | ||||||
|  | 				display: block; | ||||||
|  | 				padding: 16px; | ||||||
|  |  | ||||||
|  | 				> .description { | ||||||
|  | 					margin-top: 8px; | ||||||
|  | 					padding-top: 8px; | ||||||
|  | 					border-top: solid 1px var(--divider); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ export const router = createRouter({ | |||||||
| 		{ path: '/channels/new', component: page('channel-editor') }, | 		{ path: '/channels/new', component: page('channel-editor') }, | ||||||
| 		{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, | 		{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, | ||||||
| 		{ path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) }, | 		{ path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) }, | ||||||
|  | 		{ path: '/clips/:clipId', component: page('clip'), props: route => ({ clipId: route.params.clipId }) }, | ||||||
| 		{ path: '/my/notifications', component: page('notifications') }, | 		{ path: '/my/notifications', component: page('notifications') }, | ||||||
| 		{ path: '/my/favorites', component: page('favorites') }, | 		{ path: '/my/favorites', component: page('favorites') }, | ||||||
| 		{ path: '/my/messages', component: page('messages') }, | 		{ path: '/my/messages', component: page('messages') }, | ||||||
|   | |||||||
| @@ -15,8 +15,10 @@ export class ClipRepository extends Repository<Clip> { | |||||||
| 		return { | 		return { | ||||||
| 			id: clip.id, | 			id: clip.id, | ||||||
| 			createdAt: clip.createdAt.toISOString(), | 			createdAt: clip.createdAt.toISOString(), | ||||||
|  | 			userId: clip.userId, | ||||||
| 			name: clip.name, | 			name: clip.name, | ||||||
| 			description: clip.description, | 			description: clip.description, | ||||||
|  | 			isPublic: clip.isPublic, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -38,6 +40,11 @@ export const packedClipSchema = { | |||||||
| 			format: 'date-time', | 			format: 'date-time', | ||||||
| 			description: 'The date that the Clip was created.' | 			description: 'The date that the Clip was created.' | ||||||
| 		}, | 		}, | ||||||
|  | 		userId: { | ||||||
|  | 			type: 'string' as const, | ||||||
|  | 			optional: false as const, nullable: false as const, | ||||||
|  | 			format: 'id', | ||||||
|  | 		}, | ||||||
| 		name: { | 		name: { | ||||||
| 			type: 'string' as const, | 			type: 'string' as const, | ||||||
| 			optional: false as const, nullable: false as const, | 			optional: false as const, nullable: false as const, | ||||||
| @@ -48,5 +55,10 @@ export const packedClipSchema = { | |||||||
| 			optional: false as const, nullable: true as const, | 			optional: false as const, nullable: true as const, | ||||||
| 			description: 'The description of the Clip.' | 			description: 'The description of the Clip.' | ||||||
| 		}, | 		}, | ||||||
|  | 		isPublic: { | ||||||
|  | 			type: 'boolean' as const, | ||||||
|  | 			optional: false as const, nullable: false as const, | ||||||
|  | 			description: 'Whether this Clip is public.', | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -18,6 +18,14 @@ export const meta = { | |||||||
|  |  | ||||||
| 		name: { | 		name: { | ||||||
| 			validator: $.str.range(1, 100), | 			validator: $.str.range(1, 100), | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		isPublic: { | ||||||
|  | 			validator: $.optional.bool | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		description: { | ||||||
|  | 			validator: $.optional.nullable.str.range(1, 2048) | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -42,7 +50,9 @@ export default define(meta, async (ps, user) => { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	await Clips.update(clip.id, { | 	await Clips.update(clip.id, { | ||||||
| 		name: ps.name | 		name: ps.name, | ||||||
|  | 		description: ps.description, | ||||||
|  | 		isPublic: ps.isPublic, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	return await Clips.pack(clip.id); | 	return await Clips.pack(clip.id); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo