feat: support attachment uploading

This commit is contained in:
三咲智子 2022-11-24 01:17:54 +08:00
parent de59800c2b
commit d79011e39a
No known key found for this signature in database
GPG key ID: 69992F2250DFD93E
6 changed files with 115 additions and 9 deletions

View file

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

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

View file

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

View file

@ -20,6 +20,7 @@
"@pinia/nuxt": "^0.4.3",
"@types/fs-extra": "^9.0.13",
"@types/sanitize-html": "^2.6.2",
"@types/wicg-file-system-access": "^2020.9.5",
"@unocss/nuxt": "^0.46.5",
"@vue-macros/nuxt": "^0.0.2",
"@vueuse/nuxt": "^9.5.0",

View file

@ -9,6 +9,7 @@ specifiers:
'@pinia/nuxt': ^0.4.3
'@types/fs-extra': ^9.0.13
'@types/sanitize-html': ^2.6.2
'@types/wicg-file-system-access': ^2020.9.5
'@unocss/nuxt': ^0.46.5
'@vue-macros/nuxt': ^0.0.2
'@vueuse/nuxt': ^9.5.0
@ -36,6 +37,7 @@ devDependencies:
'@pinia/nuxt': 0.4.3_typescript@4.9.3
'@types/fs-extra': 9.0.13
'@types/sanitize-html': 2.6.2
'@types/wicg-file-system-access': 2020.9.5
'@unocss/nuxt': 0.46.5
'@vue-macros/nuxt': 0.0.2_nuxt@3.0.0
'@vueuse/nuxt': 9.5.0_nuxt@3.0.0
@ -1297,6 +1299,10 @@ packages:
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
dev: true
/@types/wicg-file-system-access/2020.9.5:
resolution: {integrity: sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==}
dev: true
/@typescript-eslint/eslint-plugin/5.42.1_a76fwnskzo2n3ynumjbqxmyj7i:
resolution: {integrity: sha512-LyR6x784JCiJ1j6sH5Y0K6cdExqCCm8DJUTcwG5ThNXJj/G8o5E56u5EdG4SLy+bZAwZBswC+GYn3eGdttBVCg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}

1
shims.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="@types/wicg-file-system-access" />