feat(status): edit & redraft

This commit is contained in:
三咲智子 2022-11-24 19:35:26 +08:00
parent a8bc64a0c7
commit 823f4c960a
No known key found for this signature in database
GPG key ID: 69992F2250DFD93E
4 changed files with 159 additions and 83 deletions

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CreateStatusParams, CreateStatusParamsWithStatus, StatusVisibility } from 'masto' import type { CreateStatusParams, StatusVisibility } from 'masto'
const { const {
draftKey, draftKey,
@ -12,22 +12,7 @@ const {
}>() }>()
let isSending = $ref(false) let isSending = $ref(false)
function getDefaultStatus(): CreateStatusParamsWithStatus { let { draft } = $(useDraft(draftKey, inReplyToId))
return {
status: '',
inReplyToId,
visibility: 'public',
}
}
const draft = $computed(() => {
if (!currentUserDrafts.value[draftKey]) {
currentUserDrafts.value[draftKey] = {
params: getDefaultStatus(),
attachments: [],
}
}
return currentUserDrafts.value[draftKey]
})
const status = $computed(() => { const status = $computed(() => {
return { return {
@ -93,9 +78,15 @@ function chooseVisibility(visibility: StatusVisibility) {
async function publish() { async function publish() {
try { try {
isSending = true isSending = true
await masto.statuses.create(status) if (!draft.editingStatus)
draft.params = getDefaultStatus() await masto.statuses.create(status)
draft.attachments = [] else await masto.statuses.update(draft.editingStatus.id, status)
draft = {
params: getDefaultStatus(inReplyToId),
attachments: [],
}
isPublishDialogOpen.value = false
} }
finally { finally {
isSending = false isSending = false
@ -112,68 +103,79 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<div v-if="currentUser" p4 flex gap-4> <div v-if="currentUser" flex="~ col">
<AccountAvatar :account="currentUser.account" w-12 h-12 /> <template v-if="draft.editingStatus">
<div <div flex="~ col gap-1">
flex flex-col gap-3 flex-auto <div text-gray self-center>
:class="isSending ? 'pointer-events-none' : ''" Editing
> </div>
<textarea <StatusCard :status="draft.editingStatus" :actions="false" :hover="false" />
v-model="draft.params.status" </div>
:placeholder="placeholder" <div border="b dashed gray/40" />
p2 border-rounded w-full bg-transparent </template>
outline-none border="~ base" <div p4 flex gap-4>
@paste="handlePaste" <AccountAvatar :account="currentUser.account" w-12 h-12 />
/> <div
flex flex-col gap-3 flex-auto
<div flex="~ col gap-2" max-h-50vh overflow-auto> :class="isSending ? 'pointer-events-none' : ''"
<PublishAttachment >
v-for="(att, idx) in draft.attachments" :key="att.id" <textarea
:attachment="att" v-model="draft.params.status"
@remove="removeAttachment(idx)" :placeholder="placeholder"
p2 border-rounded w-full bg-transparent
outline-none border="~ base"
@paste="handlePaste"
/> />
</div>
<div v-if="isUploading" flex gap-2 justify-end items-center> <div flex="~ col gap-2" max-h-50vh overflow-auto>
<div op50 i-ri:loader-2-fill animate-spin text-2xl /> <PublishAttachment
Uploading... v-for="(att, idx) in draft.attachments" :key="att.id"
</div> :attachment="att"
@remove="removeAttachment(idx)"
/>
</div>
<div flex="~ gap-2"> <div v-if="isUploading" flex gap-2 justify-end items-center>
<button btn-action-icon @click="pickAttachments"> <div op50 i-ri:loader-2-fill animate-spin text-2xl />
<div i-ri:upload-line /> Uploading...
</button> </div>
<CommonDropdown> <div flex="~ gap-2">
<button btn-action-icon> <button btn-action-icon @click="pickAttachments">
<div :class="currentVisibility.icon" /> <div i-ri:upload-line />
</button> </button>
<template #popper> <CommonDropdown>
<CommonDropdownItem <button btn-action-icon>
v-for="visibility in STATUS_VISIBILITIES" <div :class="currentVisibility.icon" />
:key="visibility.value" </button>
:icon="visibility.icon"
:checked="visibility.value === draft.params.visibility"
@click="chooseVisibility(visibility.value)"
>
{{ visibility.label }}
<template #description>
{{ visibility.description }}
</template>
</CommonDropdownItem>
</template>
</CommonDropdown>
<div flex-auto /> <template #popper>
<CommonDropdownItem
v-for="visibility in STATUS_VISIBILITIES"
:key="visibility.value"
:icon="visibility.icon"
:checked="visibility.value === draft.params.visibility"
@click="chooseVisibility(visibility.value)"
>
{{ visibility.label }}
<template #description>
{{ visibility.description }}
</template>
</CommonDropdownItem>
</template>
</CommonDropdown>
<button <div flex-auto />
btn-solid rounded-full
:disabled="isUploading || (draft.attachments.length === 0 && !draft.params.status)" <button
@click="publish" btn-solid rounded-full
> :disabled="isUploading || (draft.attachments.length === 0 && !draft.params.status)"
Publish! @click="publish"
</button> >
{{ !draft.editingStatus ? 'Publish!' : 'Save changes' }}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -54,6 +54,11 @@ const toggleBookmark = () => toggleStatusAction(
'bookmarked', 'bookmarked',
masto.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id), masto.statuses[status.bookmarked ? 'unbookmark' : 'bookmark'](status.id),
) )
const togglePin = async () => toggleStatusAction(
'pinned',
masto.statuses[status.pinned ? 'unpin' : 'pin'](status.id),
)
const copyLink = async () => { const copyLink = async () => {
await clipboard.copy(location.href) await clipboard.copy(location.href)
} }
@ -69,10 +74,34 @@ const deleteStatus = async () => {
// TODO when timeline, remove this item // TODO when timeline, remove this item
} }
const togglePin = async () => toggleStatusAction(
'pinned', const deleteAndRedraft = async () => {
masto.statuses[status.pinned ? 'unpin' : 'pin'](status.id), // TODO confirm to delete
)
const { text } = await masto.statuses.remove(status.id)
if (!dialogDraft.isEmpty) {
// TODO confirm to overwrite
}
dialogDraft.draft.value = {
params: { ...getParamsFromStatus(status), status: text! },
attachments: [],
}
openPublishDialog()
}
function editStatus() {
if (!dialogDraft.isEmpty) {
// TODO confirm to overwrite
}
dialogDraft.draft.value = {
editingStatus: status,
params: getParamsFromStatus(status),
attachments: [],
}
openPublishDialog()
}
</script> </script>
<template> <template>
@ -151,8 +180,7 @@ const togglePin = async () => toggleStatusAction(
{{ status.pinned ? 'Unpin on profile' : 'Pin on profile' }} {{ status.pinned ? 'Unpin on profile' : 'Pin on profile' }}
</CommonDropdownItem> </CommonDropdownItem>
<!-- TODO --> <CommonDropdownItem v-if="isAuthor" icon="i-ri:edit-line" @click="editStatus">
<CommonDropdownItem v-if="isAuthor" icon="i-ri:edit-line">
Edit Edit
</CommonDropdownItem> </CommonDropdownItem>
@ -165,6 +193,7 @@ const togglePin = async () => toggleStatusAction(
<CommonDropdownItem <CommonDropdownItem
v-if="isAuthor" icon="i-ri:eraser-line" text-red-600 v-if="isAuthor" icon="i-ri:eraser-line" text-red-600
@click="deleteAndRedraft"
> >
Delete & re-draft Delete & re-draft
</CommonDropdownItem> </CommonDropdownItem>

View file

@ -5,9 +5,11 @@ const props = withDefaults(
defineProps<{ defineProps<{
status: Status status: Status
actions?: boolean actions?: boolean
hover?: boolean
}>(), }>(),
{ {
actions: true, actions: true,
hover: true,
}, },
) )
@ -68,7 +70,7 @@ const timeago = useTimeAgo(() => status.createdAt, {
</script> </script>
<template> <template>
<div ref="el" flex flex-col gap-2 px-4 hover:bg-active transition-100 cursor-pointer @click="onclick"> <div ref="el" flex flex-col gap-2 px-4 transition-100 cursor-pointer :class="{ 'hover:bg-active': hover }" @click="onclick">
<div v-if="rebloggedBy" pl8> <div v-if="rebloggedBy" pl8>
<div flex gap-1 items-center text-gray:75 text-sm> <div flex gap-1 items-center text-gray:75 text-sm>
<div i-ri:repeat-fill mr-1 /> <div i-ri:repeat-fill mr-1 />

View file

@ -1,9 +1,10 @@
import type { Attachment, CreateStatusParamsWithStatus } from 'masto' import type { Attachment, CreateStatusParams, CreateStatusParamsWithStatus, Status } from 'masto'
import { STORAGE_KEY_DRAFTS } from '~/constants' import { STORAGE_KEY_DRAFTS } from '~/constants'
import type { Mutable } from '~/types/utils' import type { Mutable } from '~/types/utils'
export type DraftMap = Record<string, { export type DraftMap = Record<string, {
params: Mutable<CreateStatusParamsWithStatus> editingStatus?: Status
params: Omit<Mutable<CreateStatusParams>, 'status'> & { status?: Exclude<CreateStatusParams['status'], null> }
attachments: Attachment[] attachments: Attachment[]
}> }>
@ -17,3 +18,45 @@ export const currentUserDrafts = computed(() => {
allDrafts.value[id] = {} allDrafts.value[id] = {}
return allDrafts.value[id] return allDrafts.value[id]
}) })
export function getDefaultStatus(inReplyToId?: string): CreateStatusParamsWithStatus {
return {
status: '',
inReplyToId,
visibility: 'public',
}
}
export function getParamsFromStatus(status: Status) {
return {
status: status.content,
mediaIds: status.mediaAttachments.map(att => att.id),
visibility: status.visibility,
}
}
export function useDraft(draftKey: string, inReplyToId?: string) {
const draft = computed({
get() {
if (!currentUserDrafts.value[draftKey]) {
currentUserDrafts.value[draftKey] = {
params: getDefaultStatus(inReplyToId),
attachments: [],
}
}
return currentUserDrafts.value[draftKey]
},
set(val) {
currentUserDrafts.value[draftKey] = val
},
})
const isEmpty = computed(() => {
return (draft.value.params.status ?? '').trim().length === 0
&& draft.value.attachments.length === 0
})
return { draft, isEmpty }
}
export const dialogDraft = useDraft('dialog')