From 8e8459fa559865428963b08e6422fba9617240f5 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sun, 15 Nov 2020 12:34:47 +0900 Subject: [PATCH] wip: clip --- locales/ja-JP.yml | 1 + src/client/pages/clip.vue | 139 +++++++++++++++++++++++ src/client/pages/my-clips/index.vue | 32 +++++- src/client/router.ts | 1 + src/models/repositories/clip.ts | 12 ++ src/server/api/endpoints/clips/update.ts | 12 +- 6 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 src/client/pages/clip.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ff39eec153..7d8ba1753f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -215,6 +215,7 @@ imageUrl: "画像URL" remove: "削除" removed: "削除しました" removeAreYouSure: "「{x}」を削除しますか?" +deleteAreYouSure: "「{x}」を削除しますか?" resetAreYouSure: "リセットしますか?" saved: "保存しました" messaging: "チャット" diff --git a/src/client/pages/clip.vue b/src/client/pages/clip.vue new file mode 100644 index 0000000000..ad9e076fd6 --- /dev/null +++ b/src/client/pages/clip.vue @@ -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> diff --git a/src/client/pages/my-clips/index.vue b/src/client/pages/my-clips/index.vue index 93adb94a4b..efead39fcb 100644 --- a/src/client/pages/my-clips/index.vue +++ b/src/client/pages/my-clips/index.vue @@ -1,10 +1,13 @@ <template> -<div class="_section"> +<div class="_section qtcaoidl"> <MkButton @click="create" primary class="add"><Fa :icon="faPlus"/> {{ $t('add') }}</MkButton> <div class="_content"> - <MkPagination :pagination="pagination" #default="{items}" ref="list"> - <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">{{ item.name }}</MkA> + <MkPagination :pagination="pagination" #default="{items}" ref="list" class="list"> + <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> </div> </div> @@ -76,3 +79,26 @@ export default defineComponent({ } }); </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> diff --git a/src/client/router.ts b/src/client/router.ts index da2945be2c..413e72c320 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -37,6 +37,7 @@ export const router = createRouter({ { path: '/channels/new', component: page('channel-editor') }, { path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, { 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/favorites', component: page('favorites') }, { path: '/my/messages', component: page('messages') }, diff --git a/src/models/repositories/clip.ts b/src/models/repositories/clip.ts index 7cc3fb7110..f5c70a1829 100644 --- a/src/models/repositories/clip.ts +++ b/src/models/repositories/clip.ts @@ -15,8 +15,10 @@ export class ClipRepository extends Repository<Clip> { return { id: clip.id, createdAt: clip.createdAt.toISOString(), + userId: clip.userId, name: clip.name, description: clip.description, + isPublic: clip.isPublic, }; } } @@ -38,6 +40,11 @@ export const packedClipSchema = { format: 'date-time', description: 'The date that the Clip was created.' }, + userId: { + type: 'string' as const, + optional: false as const, nullable: false as const, + format: 'id', + }, name: { type: 'string' 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, 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.', + }, }, }; diff --git a/src/server/api/endpoints/clips/update.ts b/src/server/api/endpoints/clips/update.ts index 483941214c..4a1a31eb95 100644 --- a/src/server/api/endpoints/clips/update.ts +++ b/src/server/api/endpoints/clips/update.ts @@ -18,6 +18,14 @@ export const meta = { name: { 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, { - name: ps.name + name: ps.name, + description: ps.description, + isPublic: ps.isPublic, }); return await Clips.pack(clip.id);