2022-11-21 06:55:31 +00:00
|
|
|
|
<script setup lang="ts">
|
2022-12-14 23:30:54 +00:00
|
|
|
|
import type { Attachment, CreateStatusParams, StatusVisibility } from 'masto'
|
2022-11-24 11:44:24 +00:00
|
|
|
|
import { fileOpen } from 'browser-fs-access'
|
2022-11-25 09:29:16 +00:00
|
|
|
|
import { useDropZone } from '@vueuse/core'
|
2022-11-25 13:21:02 +00:00
|
|
|
|
import { EditorContent } from '@tiptap/vue-3'
|
2022-12-13 14:03:30 +00:00
|
|
|
|
import type { Draft } from '~/types'
|
2022-11-21 06:55:31 +00:00
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
draftKey,
|
2022-11-28 10:23:33 +00:00
|
|
|
|
initial = getDefaultDraft() as never /* Bug of vue-core */,
|
2022-11-24 15:01:45 +00:00
|
|
|
|
expanded: _expanded = false,
|
2022-11-30 04:50:29 +00:00
|
|
|
|
placeholder,
|
2022-12-16 19:55:31 +00:00
|
|
|
|
dialogLabelledBy,
|
2022-11-21 06:55:31 +00:00
|
|
|
|
} = defineProps<{
|
|
|
|
|
draftKey: string
|
2022-11-28 10:23:33 +00:00
|
|
|
|
initial?: () => Draft
|
2022-11-21 06:55:31 +00:00
|
|
|
|
placeholder?: string
|
|
|
|
|
inReplyToId?: string
|
2022-11-28 09:57:42 +00:00
|
|
|
|
inReplyToVisibility?: StatusVisibility
|
2022-11-24 15:01:45 +00:00
|
|
|
|
expanded?: boolean
|
2022-12-16 19:55:31 +00:00
|
|
|
|
dialogLabelledBy?: string
|
2022-11-21 06:55:31 +00:00
|
|
|
|
}>()
|
|
|
|
|
|
2022-12-01 07:24:35 +00:00
|
|
|
|
const emit = defineEmits(['published'])
|
|
|
|
|
|
2022-11-30 04:50:29 +00:00
|
|
|
|
const { t } = useI18n()
|
2022-11-28 10:23:33 +00:00
|
|
|
|
// eslint-disable-next-line prefer-const
|
|
|
|
|
let { draft, isEmpty } = $(useDraft(draftKey, initial))
|
|
|
|
|
|
2022-11-21 06:55:31 +00:00
|
|
|
|
let isSending = $ref(false)
|
2022-11-29 11:57:05 +00:00
|
|
|
|
let isExpanded = $ref(false)
|
|
|
|
|
const shouldExpanded = $computed(() => _expanded || isExpanded || !isEmpty)
|
2022-11-24 06:54:54 +00:00
|
|
|
|
|
2022-11-25 13:21:02 +00:00
|
|
|
|
const { editor } = useTiptap({
|
2022-11-25 18:10:17 +00:00
|
|
|
|
content: computed({
|
|
|
|
|
get: () => draft.params.status,
|
|
|
|
|
set: newVal => draft.params.status = newVal,
|
|
|
|
|
}),
|
2022-12-12 22:35:59 +00:00
|
|
|
|
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
|
2022-11-29 11:57:05 +00:00
|
|
|
|
autofocus: shouldExpanded,
|
2022-11-25 14:07:31 +00:00
|
|
|
|
onSubmit: publish,
|
2022-12-12 22:35:59 +00:00
|
|
|
|
onFocus() {
|
|
|
|
|
if (!isExpanded && draft.initialText) {
|
|
|
|
|
editor.value?.chain().insertContent(`${draft.initialText} `).focus('end').run()
|
|
|
|
|
draft.initialText = ''
|
|
|
|
|
}
|
|
|
|
|
isExpanded = true
|
|
|
|
|
},
|
2022-11-25 13:21:02 +00:00
|
|
|
|
onPaste: handlePaste,
|
|
|
|
|
})
|
|
|
|
|
|
2022-11-24 09:15:58 +00:00
|
|
|
|
const currentVisibility = $computed(() => {
|
2022-11-25 16:17:15 +00:00
|
|
|
|
return STATUS_VISIBILITIES.find(v => v.value === draft.params.visibility) || STATUS_VISIBILITIES[0]
|
2022-11-24 09:15:58 +00:00
|
|
|
|
})
|
|
|
|
|
|
2022-11-23 17:17:54 +00:00
|
|
|
|
let isUploading = $ref<boolean>(false)
|
|
|
|
|
|
|
|
|
|
async function handlePaste(evt: ClipboardEvent) {
|
|
|
|
|
const files = evt.clipboardData?.files
|
2022-11-24 08:20:21 +00:00
|
|
|
|
if (!files || files.length === 0)
|
2022-11-23 17:17:54 +00:00
|
|
|
|
return
|
|
|
|
|
|
2022-11-24 04:05:13 +00:00
|
|
|
|
evt.preventDefault()
|
2022-11-23 17:17:54 +00:00
|
|
|
|
await uploadAttachments(Array.from(files))
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-23 19:15:19 +00:00
|
|
|
|
function insertText(text: string) {
|
|
|
|
|
editor.value?.chain().insertContent(text).focus().run()
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 17:17:54 +00:00
|
|
|
|
async function pickAttachments() {
|
2022-11-24 11:44:24 +00:00
|
|
|
|
const files = await fileOpen([
|
|
|
|
|
{
|
|
|
|
|
description: 'Attachments',
|
|
|
|
|
multiple: true,
|
|
|
|
|
mimeTypes: ['image/*'],
|
|
|
|
|
extensions: ['.png', '.gif', '.jpeg', '.jpg', '.webp', '.avif', '.heic', '.heif'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
description: 'Attachments',
|
|
|
|
|
mimeTypes: ['video/*'],
|
|
|
|
|
extensions: ['.webm', '.mp4', '.m4v', '.mov', '.ogv', '.3gp'],
|
|
|
|
|
},
|
|
|
|
|
{
|
2022-11-24 04:05:13 +00:00
|
|
|
|
description: 'Attachments',
|
2022-11-24 11:44:24 +00:00
|
|
|
|
mimeTypes: ['audio/*'],
|
|
|
|
|
extensions: ['.mp3', '.ogg', '.oga', '.wav', '.flac', '.opus', '.aac', '.m4a', '.3gp', '.wma'],
|
|
|
|
|
},
|
|
|
|
|
])
|
2022-11-23 17:17:54 +00:00
|
|
|
|
await uploadAttachments(files)
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 12:10:45 +00:00
|
|
|
|
async function toggleSensitive() {
|
|
|
|
|
draft.params.sensitive = !draft.params.sensitive
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-25 14:04:50 +00:00
|
|
|
|
const masto = useMasto()
|
|
|
|
|
|
2022-11-23 17:17:54 +00:00
|
|
|
|
async function uploadAttachments(files: File[]) {
|
|
|
|
|
isUploading = true
|
|
|
|
|
for (const file of files) {
|
2022-12-25 14:04:50 +00:00
|
|
|
|
const attachment = await masto.mediaAttachments.create({
|
2022-11-23 17:17:54 +00:00
|
|
|
|
file,
|
|
|
|
|
})
|
2022-11-24 06:54:54 +00:00
|
|
|
|
draft.attachments.push(attachment)
|
2022-11-23 17:17:54 +00:00
|
|
|
|
}
|
|
|
|
|
isUploading = false
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 23:30:54 +00:00
|
|
|
|
async function setDescription(att: Attachment, description: string) {
|
|
|
|
|
att.description = description
|
2022-12-25 14:04:50 +00:00
|
|
|
|
await masto.mediaAttachments.update(att.id, { description: att.description })
|
2022-12-14 23:30:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-24 04:05:13 +00:00
|
|
|
|
function removeAttachment(index: number) {
|
2022-11-24 06:54:54 +00:00
|
|
|
|
draft.attachments.splice(index, 1)
|
2022-11-23 17:17:54 +00:00
|
|
|
|
}
|
2022-11-21 06:55:31 +00:00
|
|
|
|
|
2022-11-24 09:15:58 +00:00
|
|
|
|
function chooseVisibility(visibility: StatusVisibility) {
|
|
|
|
|
draft.params.visibility = visibility
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-21 06:55:31 +00:00
|
|
|
|
async function publish() {
|
2022-11-25 16:17:15 +00:00
|
|
|
|
const payload = {
|
|
|
|
|
...draft.params,
|
|
|
|
|
status: htmlToText(draft.params.status || ''),
|
|
|
|
|
mediaIds: draft.attachments.map(a => a.id),
|
|
|
|
|
} as CreateStatusParams
|
|
|
|
|
|
2022-11-25 13:21:02 +00:00
|
|
|
|
if (process.dev) {
|
2022-11-25 16:17:15 +00:00
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.info({
|
|
|
|
|
raw: draft.params.status,
|
|
|
|
|
...payload,
|
|
|
|
|
})
|
2022-11-28 10:23:33 +00:00
|
|
|
|
// eslint-disable-next-line no-alert
|
2022-11-25 16:17:15 +00:00
|
|
|
|
const result = confirm('[DEV] Payload logged to console, do you want to publish it?')
|
|
|
|
|
if (!result)
|
|
|
|
|
return
|
2022-11-25 13:21:02 +00:00
|
|
|
|
}
|
2022-11-25 16:17:15 +00:00
|
|
|
|
|
2022-11-21 06:55:31 +00:00
|
|
|
|
try {
|
|
|
|
|
isSending = true
|
2022-11-25 16:17:15 +00:00
|
|
|
|
|
2022-11-24 11:35:26 +00:00
|
|
|
|
if (!draft.editingStatus)
|
2022-12-25 14:04:50 +00:00
|
|
|
|
await masto.statuses.create(payload)
|
2022-11-25 13:21:02 +00:00
|
|
|
|
else
|
2022-12-25 14:04:50 +00:00
|
|
|
|
await masto.statuses.update(draft.editingStatus.id, payload)
|
2022-11-24 11:35:26 +00:00
|
|
|
|
|
2022-11-28 10:23:33 +00:00
|
|
|
|
draft = initial()
|
2022-11-24 11:35:26 +00:00
|
|
|
|
isPublishDialogOpen.value = false
|
2022-12-01 07:24:35 +00:00
|
|
|
|
emit('published')
|
2022-11-21 06:55:31 +00:00
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
isSending = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 09:29:16 +00:00
|
|
|
|
const dropZoneRef = ref<HTMLDivElement>()
|
|
|
|
|
|
|
|
|
|
async function onDrop(files: File[] | null) {
|
|
|
|
|
if (files)
|
|
|
|
|
await uploadAttachments(files)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
|
2022-12-14 16:45:46 +00:00
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
focusEditor: () => {
|
|
|
|
|
editor.value?.commands?.focus?.()
|
|
|
|
|
},
|
|
|
|
|
})
|
2022-11-21 06:55:31 +00:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2022-12-17 16:55:29 +00:00
|
|
|
|
<div v-if="isMastoInitialised && currentUser" flex="~ col gap-4" py4 px2 sm:px4>
|
2022-11-24 11:35:26 +00:00
|
|
|
|
<template v-if="draft.editingStatus">
|
|
|
|
|
<div flex="~ col gap-1">
|
2022-12-16 19:55:31 +00:00
|
|
|
|
<div id="state-editing" text-secondary self-center>
|
2022-11-29 06:57:32 +00:00
|
|
|
|
{{ $t('state.editing') }}
|
2022-11-24 11:35:26 +00:00
|
|
|
|
</div>
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" px-0 />
|
2022-11-24 07:53:27 +00:00
|
|
|
|
</div>
|
2022-11-24 11:35:26 +00:00
|
|
|
|
<div border="b dashed gray/40" />
|
|
|
|
|
</template>
|
2022-11-24 14:32:20 +00:00
|
|
|
|
|
2022-12-06 16:37:58 +00:00
|
|
|
|
<div flex gap-4 flex-1>
|
2022-11-30 17:15:18 +00:00
|
|
|
|
<NuxtLink w-12 h-12 :to="getAccountRoute(currentUser.account)">
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<AccountAvatar :account="currentUser.account" f-full h-full />
|
2022-11-24 15:19:18 +00:00
|
|
|
|
</NuxtLink>
|
2022-12-06 16:37:58 +00:00
|
|
|
|
<!-- This `w-0` style is used to avoid overflow problems in flex layouts,so don't remove it unless you know what you're doing -->
|
2022-11-24 11:35:26 +00:00
|
|
|
|
<div
|
2022-11-25 09:29:16 +00:00
|
|
|
|
ref="dropZoneRef"
|
2022-12-06 16:37:58 +00:00
|
|
|
|
flex w-0 flex-col gap-3 flex-1
|
2022-11-25 13:21:02 +00:00
|
|
|
|
border="2 dashed transparent"
|
2022-11-25 09:31:32 +00:00
|
|
|
|
:class="[isSending ? 'pointer-events-none' : '', isOverDropZone ? '!border-primary' : '']"
|
2022-11-24 11:35:26 +00:00
|
|
|
|
>
|
2022-11-25 12:10:45 +00:00
|
|
|
|
<div v-if="draft.params.sensitive">
|
|
|
|
|
<input
|
|
|
|
|
v-model="draft.params.spoilerText"
|
|
|
|
|
type="text"
|
2022-12-01 07:41:59 +00:00
|
|
|
|
:placeholder="$t('placeholder.content_warning')"
|
2022-11-25 12:10:45 +00:00
|
|
|
|
p2 border-rounded w-full bg-transparent
|
|
|
|
|
outline-none border="~ base"
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
|
2022-12-06 16:37:58 +00:00
|
|
|
|
<div relative flex-1 flex flex-col>
|
2022-11-25 13:29:42 +00:00
|
|
|
|
<EditorContent
|
|
|
|
|
:editor="editor"
|
2022-12-14 13:22:35 +00:00
|
|
|
|
flex max-w-full
|
2022-12-04 06:27:08 +00:00
|
|
|
|
:class="shouldExpanded ? 'min-h-30 md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain' : ''"
|
2022-11-25 13:29:42 +00:00
|
|
|
|
/>
|
2022-11-29 11:57:05 +00:00
|
|
|
|
<div v-if="shouldExpanded" absolute right-0 bottom-0 pointer-events-none text-sm text-secondary-light>
|
2022-11-25 14:01:28 +00:00
|
|
|
|
{{ characterLimit - editor?.storage.characterCount.characters() }}
|
2022-11-25 13:29:42 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2022-11-24 07:53:27 +00:00
|
|
|
|
|
2022-11-25 13:21:02 +00:00
|
|
|
|
<div v-if="isUploading" flex gap-1 items-center text-sm p1 text-primary>
|
|
|
|
|
<div i-ri:loader-2-fill animate-spin />
|
2022-11-29 06:57:32 +00:00
|
|
|
|
{{ $t('state.uploading') }}
|
2022-11-25 13:21:02 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="draft.attachments.length" flex="~ col gap-2" overflow-auto>
|
2022-11-24 11:35:26 +00:00
|
|
|
|
<PublishAttachment
|
|
|
|
|
v-for="(att, idx) in draft.attachments" :key="att.id"
|
|
|
|
|
:attachment="att"
|
2022-12-16 19:55:31 +00:00
|
|
|
|
:dialog-labelled-by="dialogLabelledBy ?? (draft.editingStatus ? 'state-editing' : null)"
|
2022-11-24 11:35:26 +00:00
|
|
|
|
@remove="removeAttachment(idx)"
|
2022-12-14 23:30:54 +00:00
|
|
|
|
@set-description="setDescription(att, $event)"
|
2022-11-24 11:35:26 +00:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
2022-12-02 07:02:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div flex gap-4>
|
|
|
|
|
<div w-12 h-full sm:block hidden />
|
|
|
|
|
<div
|
|
|
|
|
v-if="shouldExpanded" flex="~ gap-2 1" m="l--1" pt-2 justify="between" max-full
|
|
|
|
|
border="t base"
|
|
|
|
|
>
|
2022-12-23 19:15:19 +00:00
|
|
|
|
<PublishEmojiPicker @select="insertText" />
|
|
|
|
|
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<CommonTooltip placement="bottom" :content="$t('tooltip.add_media')">
|
|
|
|
|
<button btn-action-icon :aria-label="$t('tooltip.add_media')" @click="pickAttachments">
|
|
|
|
|
<div i-ri:image-add-line />
|
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
|
|
|
|
|
|
|
|
|
<template v-if="editor">
|
|
|
|
|
<CommonTooltip placement="bottom" :content="$t('tooltip.toggle_code_block')">
|
|
|
|
|
<button
|
|
|
|
|
btn-action-icon
|
|
|
|
|
:aria-label="$t('tooltip.toggle_code_block')"
|
|
|
|
|
:class="editor.isActive('codeBlock') ? 'op100' : 'op50'"
|
|
|
|
|
@click="editor?.chain().focus().toggleCodeBlock().run()"
|
|
|
|
|
>
|
|
|
|
|
<div i-ri:code-s-slash-line />
|
2022-11-24 15:48:52 +00:00
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
2022-12-02 07:02:44 +00:00
|
|
|
|
</template>
|
2022-11-24 09:15:58 +00:00
|
|
|
|
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<div flex-auto />
|
2022-11-25 13:21:02 +00:00
|
|
|
|
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<CommonTooltip placement="bottom" :content="$t('tooltip.add_content_warning')">
|
|
|
|
|
<button btn-action-icon :aria-label="$t('tooltip.add_content_warning')" @click="toggleSensitive">
|
|
|
|
|
<div v-if="draft.params.sensitive" i-ri:alarm-warning-fill text-orange />
|
|
|
|
|
<div v-else i-ri:alarm-warning-line />
|
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
2022-11-24 13:26:33 +00:00
|
|
|
|
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<CommonTooltip placement="bottom" :content="$t('tooltip.change_content_visibility')">
|
|
|
|
|
<CommonDropdown>
|
|
|
|
|
<button :aria-label="$t('tooltip.change_content_visibility')" btn-action-icon w-12>
|
|
|
|
|
<div :class="currentVisibility.icon" />
|
|
|
|
|
<div i-ri:arrow-down-s-line text-sm text-secondary mr--1 />
|
2022-11-25 12:10:45 +00:00
|
|
|
|
</button>
|
2022-11-24 11:35:26 +00:00
|
|
|
|
|
2022-12-02 07:02:44 +00:00
|
|
|
|
<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)"
|
|
|
|
|
>
|
|
|
|
|
{{ $t(`visibility.${visibility.value}`) }}
|
|
|
|
|
<template #description>
|
|
|
|
|
{{ $t(`visibility.${visibility.value}_desc`) }}
|
|
|
|
|
</template>
|
|
|
|
|
</CommonDropdownItem>
|
|
|
|
|
</template>
|
|
|
|
|
</CommonDropdown>
|
|
|
|
|
</CommonTooltip>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
btn-solid rounded-full text-sm
|
|
|
|
|
:disabled="isEmpty || isUploading || (draft.attachments.length === 0 && !draft.params.status)"
|
|
|
|
|
@click="publish"
|
|
|
|
|
>
|
|
|
|
|
{{ !draft.editingStatus ? $t('action.publish') : $t('action.save_changes') }}
|
|
|
|
|
</button>
|
2022-11-24 07:53:27 +00:00
|
|
|
|
</div>
|
2022-11-21 06:55:31 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|