wip: clip

This commit is contained in:
syuilo 2020-11-15 12:34:47 +09:00
parent d53c55ecb5
commit 8e8459fa55
6 changed files with 193 additions and 4 deletions

View file

@ -215,6 +215,7 @@ imageUrl: "画像URL"
remove: "削除"
removed: "削除しました"
removeAreYouSure: "「{x}」を削除しますか?"
deleteAreYouSure: "「{x}」を削除しますか?"
resetAreYouSure: "リセットしますか?"
saved: "保存しました"
messaging: "チャット"

139
src/client/pages/clip.vue Normal file
View 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>

View file

@ -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>

View file

@ -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') },

View file

@ -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.',
},
},
};

View file

@ -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);