feat: support attachment uploading
This commit is contained in:
parent
de59800c2b
commit
d79011e39a
6 changed files with 115 additions and 9 deletions
|
@ -2,7 +2,7 @@
|
|||
<div flex="~ col" items-center>
|
||||
<div i-ri:forbid-line text-10 mt10 mb2 />
|
||||
<div text-lg>
|
||||
<slot>Not found</slot>
|
||||
<slot>404 Not Found</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
24
components/publish/PublishAttachment.vue
Normal file
24
components/publish/PublishAttachment.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import type { Attachment } from 'masto'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
attachment: Attachment
|
||||
alt?: string
|
||||
removable?: boolean
|
||||
}>(), {
|
||||
removable: true,
|
||||
})
|
||||
|
||||
defineEmits<{
|
||||
(evt: 'remove'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div relative group>
|
||||
<status-attachment :attachment="attachment" w-full />
|
||||
<div absolute right-2 top-2 hover:bg="gray/40" transition-100 p-1 rounded-5 cursor-pointer op-0 group-hover:op-100>
|
||||
<div v-if="removable" i-ri:close-line text-3 @click="$emit('remove')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { CreateStatusParamsWithStatus } from 'masto'
|
||||
import type { Attachment, CreateStatusParams, CreateStatusParamsWithStatus } from 'masto'
|
||||
|
||||
const {
|
||||
draftKey,
|
||||
|
@ -19,13 +19,65 @@ function getDefaultStatus(): CreateStatusParamsWithStatus {
|
|||
inReplyToId,
|
||||
}
|
||||
}
|
||||
const draft = useLocalStorage<CreateStatusParamsWithStatus>(storageKey, getDefaultStatus())
|
||||
let draft = $(useLocalStorage<CreateStatusParamsWithStatus>(storageKey, getDefaultStatus()))
|
||||
let attachments = $(useLocalStorage<Attachment[]>(`${storageKey}-attachments`, []))
|
||||
const status = $computed(() => {
|
||||
return {
|
||||
...draft,
|
||||
mediaIds: attachments.map(a => a.id),
|
||||
} as CreateStatusParams
|
||||
})
|
||||
|
||||
let isUploading = $ref<boolean>(false)
|
||||
|
||||
async function handlePaste(evt: ClipboardEvent) {
|
||||
const files = evt.clipboardData?.files
|
||||
if (!files)
|
||||
return
|
||||
|
||||
await uploadAttachments(Array.from(files))
|
||||
}
|
||||
|
||||
async function pickAttachments() {
|
||||
if (!globalThis.showOpenFilePicker)
|
||||
// TODO: Safari don't support it.
|
||||
return
|
||||
|
||||
const handles = await showOpenFilePicker({
|
||||
multiple: true,
|
||||
// TODO: add more kinds of files: videos, audios
|
||||
types: [{
|
||||
description: 'Images',
|
||||
accept: {
|
||||
'image/*': ['.png', '.gif', '.jpeg', '.jpg', '.webp', '.avif', '.heic'],
|
||||
},
|
||||
}],
|
||||
})
|
||||
const files = await Promise.all(handles.map(handle => handle.getFile()))
|
||||
await uploadAttachments(files)
|
||||
}
|
||||
|
||||
async function uploadAttachments(files: File[]) {
|
||||
isUploading = true
|
||||
for (const file of files) {
|
||||
const attachment = await masto.mediaAttachments.create({
|
||||
file,
|
||||
})
|
||||
attachments.push(attachment)
|
||||
}
|
||||
isUploading = false
|
||||
}
|
||||
|
||||
async function removeAttachment(index: number) {
|
||||
attachments.splice(index, 1)
|
||||
}
|
||||
|
||||
async function publish() {
|
||||
try {
|
||||
isSending = true
|
||||
await masto.statuses.create(draft.value)
|
||||
draft.value = getDefaultStatus()
|
||||
await masto.statuses.create(status)
|
||||
draft = getDefaultStatus()
|
||||
attachments = []
|
||||
}
|
||||
finally {
|
||||
isSending = false
|
||||
|
@ -33,8 +85,9 @@ async function publish() {
|
|||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (!draft.value.status) {
|
||||
draft.value = undefined
|
||||
if (!draft.status) {
|
||||
// @ts-expect-error draft cannot be undefined
|
||||
draft = undefined
|
||||
nextTick(() => {
|
||||
localStorage.removeItem(storageKey)
|
||||
})
|
||||
|
@ -44,7 +97,7 @@ onUnmounted(() => {
|
|||
|
||||
<template>
|
||||
<div
|
||||
flex flex-col gap-4
|
||||
flex flex-col gap-3
|
||||
:class="isSending ? 'pointer-events-none' : ''"
|
||||
>
|
||||
<textarea
|
||||
|
@ -52,11 +105,32 @@ onUnmounted(() => {
|
|||
:placeholder="placeholder"
|
||||
p2 border-rounded w-full h-40
|
||||
bg-gray:10 outline-none border="~ base"
|
||||
@paste="handlePaste"
|
||||
/>
|
||||
|
||||
<div flex="~" gap-2>
|
||||
<button hover:bg-active p2 rounded-5 @click="pickAttachments">
|
||||
<div i-ri:upload-line />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div flex="~ col gap-2" max-h-50vh overflow-auto>
|
||||
<publish-attachment
|
||||
v-for="(att, idx) in attachments" :key="att.id"
|
||||
:attachment="att"
|
||||
@remove="removeAttachment(idx)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="isUploading" flex gap-2 justify-end items-center>
|
||||
<div op50 i-ri:loader-2-fill animate-spin text-2xl />
|
||||
Uploading...
|
||||
</div>
|
||||
|
||||
<div flex justify-end>
|
||||
<button
|
||||
btn-solid
|
||||
:disabled="!draft.status"
|
||||
:disabled="isUploading || (attachments.length === 0 && !draft.status)"
|
||||
@click="publish"
|
||||
>
|
||||
Publish!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue