feat(status): edit & redraft
This commit is contained in:
parent
a8bc64a0c7
commit
823f4c960a
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in a new issue