2022-11-21 09:55:31 +03:00
|
|
|
|
<script setup lang="ts">
|
2022-11-25 16:21:02 +03:00
|
|
|
|
import { EditorContent } from '@tiptap/vue-3'
|
2023-01-10 16:22:39 +03:00
|
|
|
|
import type { mastodon } from 'masto'
|
|
|
|
|
import type { Ref } from 'vue'
|
2022-12-13 17:03:30 +03:00
|
|
|
|
import type { Draft } from '~/types'
|
2022-11-21 09:55:31 +03:00
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
draftKey,
|
2022-11-28 13:23:33 +03:00
|
|
|
|
initial = getDefaultDraft() as never /* Bug of vue-core */,
|
2023-01-10 16:22:39 +03:00
|
|
|
|
expanded = false,
|
2022-11-30 07:50:29 +03:00
|
|
|
|
placeholder,
|
2022-12-16 22:55:31 +03:00
|
|
|
|
dialogLabelledBy,
|
2022-11-21 09:55:31 +03:00
|
|
|
|
} = defineProps<{
|
2023-01-05 18:42:36 +03:00
|
|
|
|
draftKey?: string
|
2022-11-28 13:23:33 +03:00
|
|
|
|
initial?: () => Draft
|
2022-11-21 09:55:31 +03:00
|
|
|
|
placeholder?: string
|
|
|
|
|
inReplyToId?: string
|
2023-01-08 09:21:09 +03:00
|
|
|
|
inReplyToVisibility?: mastodon.v1.StatusVisibility
|
2022-11-24 18:01:45 +03:00
|
|
|
|
expanded?: boolean
|
2022-12-16 22:55:31 +03:00
|
|
|
|
dialogLabelledBy?: string
|
2022-11-21 09:55:31 +03:00
|
|
|
|
}>()
|
|
|
|
|
|
2023-01-01 18:57:49 +03:00
|
|
|
|
const emit = defineEmits<{
|
2023-01-08 09:21:09 +03:00
|
|
|
|
(evt: 'published', status: mastodon.v1.Status): void
|
2023-01-01 18:57:49 +03:00
|
|
|
|
}>()
|
2022-12-01 10:24:35 +03:00
|
|
|
|
|
2022-11-30 07:50:29 +03:00
|
|
|
|
const { t } = useI18n()
|
2023-01-09 13:04:18 +03:00
|
|
|
|
|
2023-01-10 16:22:39 +03:00
|
|
|
|
const draftState = useDraft(draftKey, initial)
|
|
|
|
|
const { draft } = $(draftState)
|
2022-11-28 13:23:33 +03:00
|
|
|
|
|
2023-01-10 16:22:39 +03:00
|
|
|
|
const {
|
|
|
|
|
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
|
|
|
|
|
uploadAttachments, pickAttachments, setDescription, removeAttachment,
|
2023-01-11 01:51:25 +03:00
|
|
|
|
dropZoneRef,
|
2023-01-10 16:22:39 +03:00
|
|
|
|
} = $(useUploadMediaAttachment($$(draft)))
|
|
|
|
|
|
|
|
|
|
let { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft } = $(usePublish(
|
|
|
|
|
{
|
|
|
|
|
draftState,
|
|
|
|
|
...$$({ expanded, isUploading, initialDraft: initial }),
|
|
|
|
|
},
|
|
|
|
|
))
|
2022-11-24 09:54:54 +03:00
|
|
|
|
|
2022-11-25 16:21:02 +03:00
|
|
|
|
const { editor } = useTiptap({
|
2022-11-25 21:10:17 +03:00
|
|
|
|
content: computed({
|
|
|
|
|
get: () => draft.params.status,
|
2023-01-05 18:42:36 +03:00
|
|
|
|
set: (newVal) => {
|
|
|
|
|
draft.params.status = newVal
|
|
|
|
|
draft.lastUpdated = Date.now()
|
|
|
|
|
},
|
2022-11-25 21:10:17 +03:00
|
|
|
|
}),
|
2022-12-13 01:35:59 +03:00
|
|
|
|
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
|
2022-11-29 14:57:05 +03:00
|
|
|
|
autofocus: shouldExpanded,
|
2022-11-25 17:07:31 +03:00
|
|
|
|
onSubmit: publish,
|
2022-12-13 01:35:59 +03:00
|
|
|
|
onFocus() {
|
|
|
|
|
if (!isExpanded && draft.initialText) {
|
|
|
|
|
editor.value?.chain().insertContent(`${draft.initialText} `).focus('end').run()
|
|
|
|
|
draft.initialText = ''
|
|
|
|
|
}
|
|
|
|
|
isExpanded = true
|
|
|
|
|
},
|
2022-11-25 16:21:02 +03:00
|
|
|
|
onPaste: handlePaste,
|
|
|
|
|
})
|
2023-01-07 00:38:01 +03:00
|
|
|
|
const characterCount = $computed(() => htmlToText(editor.value?.getHTML() || '').length)
|
2022-11-23 20:17:54 +03:00
|
|
|
|
|
|
|
|
|
async function handlePaste(evt: ClipboardEvent) {
|
|
|
|
|
const files = evt.clipboardData?.files
|
2022-11-24 11:20:21 +03:00
|
|
|
|
if (!files || files.length === 0)
|
2022-11-23 20:17:54 +03:00
|
|
|
|
return
|
|
|
|
|
|
2022-11-24 07:05:13 +03:00
|
|
|
|
evt.preventDefault()
|
2022-11-23 20:17:54 +03:00
|
|
|
|
await uploadAttachments(Array.from(files))
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-27 22:13:50 +03:00
|
|
|
|
function insertEmoji(name: string) {
|
|
|
|
|
editor.value?.chain().focus().insertEmoji(name).run()
|
2022-12-23 22:15:19 +03:00
|
|
|
|
}
|
2022-12-27 22:13:50 +03:00
|
|
|
|
function insertCustomEmoji(image: any) {
|
|
|
|
|
editor.value?.chain().focus().insertCustomEmoji(image).run()
|
2022-12-27 21:38:57 +03:00
|
|
|
|
}
|
2022-12-23 22:15:19 +03:00
|
|
|
|
|
2022-11-25 15:10:45 +03:00
|
|
|
|
async function toggleSensitive() {
|
|
|
|
|
draft.params.sensitive = !draft.params.sensitive
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-21 09:55:31 +03:00
|
|
|
|
async function publish() {
|
2023-01-10 16:22:39 +03:00
|
|
|
|
const status = await publishDraft()
|
|
|
|
|
if (status)
|
2023-01-01 18:57:49 +03:00
|
|
|
|
emit('published', status)
|
2022-11-21 09:55:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-14 19:45:46 +03:00
|
|
|
|
defineExpose({
|
|
|
|
|
focusEditor: () => {
|
|
|
|
|
editor.value?.commands?.focus?.()
|
|
|
|
|
},
|
|
|
|
|
})
|
2022-11-21 09:55:31 +03:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2022-12-26 11:00:57 +03:00
|
|
|
|
<div v-if="isMastoInitialised && currentUser" flex="~ col gap-4" py3 px2 sm:px4>
|
2022-11-24 14:35:26 +03:00
|
|
|
|
<template v-if="draft.editingStatus">
|
|
|
|
|
<div flex="~ col gap-1">
|
2022-12-16 22:55:31 +03:00
|
|
|
|
<div id="state-editing" text-secondary self-center>
|
2022-11-29 09:57:32 +03:00
|
|
|
|
{{ $t('state.editing') }}
|
2022-11-24 14:35:26 +03:00
|
|
|
|
</div>
|
2022-12-02 10:02:44 +03:00
|
|
|
|
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" px-0 />
|
2022-11-24 10:53:27 +03:00
|
|
|
|
</div>
|
2022-11-24 14:35:26 +03:00
|
|
|
|
<div border="b dashed gray/40" />
|
|
|
|
|
</template>
|
2022-11-24 17:32:20 +03:00
|
|
|
|
|
2022-12-27 22:13:50 +03:00
|
|
|
|
<div flex gap-3 flex-1>
|
2022-12-26 11:00:57 +03:00
|
|
|
|
<NuxtLink :to="getAccountRoute(currentUser.account)">
|
2023-01-06 02:35:35 +03:00
|
|
|
|
<AccountBigAvatar :account="currentUser.account" square />
|
2022-11-24 18:19:18 +03:00
|
|
|
|
</NuxtLink>
|
2022-12-06 19:37:58 +03: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 14:35:26 +03:00
|
|
|
|
<div
|
2022-11-25 12:29:16 +03:00
|
|
|
|
ref="dropZoneRef"
|
2022-12-06 19:37:58 +03:00
|
|
|
|
flex w-0 flex-col gap-3 flex-1
|
2022-11-25 16:21:02 +03:00
|
|
|
|
border="2 dashed transparent"
|
2022-11-25 12:31:32 +03:00
|
|
|
|
:class="[isSending ? 'pointer-events-none' : '', isOverDropZone ? '!border-primary' : '']"
|
2022-11-24 14:35:26 +03:00
|
|
|
|
>
|
2023-01-13 03:33:04 +03:00
|
|
|
|
<ContentMentionGroup v-if="draft.mentions?.length && shouldExpanded">
|
|
|
|
|
<div v-for="m of draft.mentions" :key="m" text-primary>
|
|
|
|
|
@{{ m }}
|
|
|
|
|
</div>
|
|
|
|
|
</ContentMentionGroup>
|
|
|
|
|
|
2022-11-25 15:10:45 +03:00
|
|
|
|
<div v-if="draft.params.sensitive">
|
|
|
|
|
<input
|
|
|
|
|
v-model="draft.params.spoilerText"
|
|
|
|
|
type="text"
|
2022-12-01 10:41:59 +03:00
|
|
|
|
:placeholder="$t('placeholder.content_warning')"
|
2022-11-25 15:10:45 +03:00
|
|
|
|
p2 border-rounded w-full bg-transparent
|
|
|
|
|
outline-none border="~ base"
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
|
2022-12-06 19:37:58 +03:00
|
|
|
|
<div relative flex-1 flex flex-col>
|
2022-11-25 16:29:42 +03:00
|
|
|
|
<EditorContent
|
|
|
|
|
:editor="editor"
|
2022-12-14 16:22:35 +03:00
|
|
|
|
flex max-w-full
|
2022-12-04 09:27:08 +03: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 16:29:42 +03:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
2022-11-24 10:53:27 +03:00
|
|
|
|
|
2022-11-25 16:21:02 +03: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 09:57:32 +03:00
|
|
|
|
{{ $t('state.uploading') }}
|
2022-11-25 16:21:02 +03:00
|
|
|
|
</div>
|
2022-12-26 08:25:35 +03:00
|
|
|
|
<div
|
2023-01-10 16:22:39 +03:00
|
|
|
|
v-else-if="failedAttachments.length > 0"
|
2022-12-26 08:25:35 +03:00
|
|
|
|
role="alert"
|
2023-01-01 21:11:50 +03:00
|
|
|
|
:aria-describedby="isExceedingAttachmentLimit ? 'upload-failed uploads-per-post' : 'upload-failed'"
|
2022-12-26 08:25:35 +03:00
|
|
|
|
flex="~ col"
|
2022-12-28 00:04:52 +03:00
|
|
|
|
gap-1 text-sm
|
2023-01-01 17:29:11 +03:00
|
|
|
|
pt-1 ps-2 pe-1 pb-2
|
2022-12-28 00:04:52 +03:00
|
|
|
|
text-red-600 dark:text-red-400
|
2022-12-26 08:25:35 +03:00
|
|
|
|
border="~ base rounded red-600 dark:red-400"
|
|
|
|
|
>
|
|
|
|
|
<head id="upload-failed" flex justify-between>
|
|
|
|
|
<div flex items-center gap-x-2 font-bold>
|
|
|
|
|
<div aria-hidden="true" i-ri:error-warning-fill />
|
|
|
|
|
<p>{{ $t('state.upload_failed') }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<CommonTooltip placement="bottom" :content="$t('action.clear_upload_failed')">
|
|
|
|
|
<button
|
|
|
|
|
flex rounded-4 p1
|
|
|
|
|
hover:bg-active cursor-pointer transition-100
|
|
|
|
|
:aria-label="$t('action.clear_upload_failed')"
|
2023-01-10 16:22:39 +03:00
|
|
|
|
@click="failedAttachments = []"
|
2022-12-26 08:25:35 +03:00
|
|
|
|
>
|
2023-01-08 10:49:32 +03:00
|
|
|
|
<span aria-hidden="true" w="1.75em" h="1.75em" i-ri:close-line />
|
2022-12-26 08:25:35 +03:00
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
|
|
|
|
</head>
|
2023-01-01 21:11:50 +03:00
|
|
|
|
<div v-if="isExceedingAttachmentLimit" id="uploads-per-post" ps-2 sm:ps-1 text-small>
|
2022-12-31 12:02:55 +03:00
|
|
|
|
{{ $t('state.attachments_exceed_server_limit') }}
|
|
|
|
|
</div>
|
2023-01-01 17:29:11 +03:00
|
|
|
|
<ol ps-2 sm:ps-1>
|
2023-01-10 16:22:39 +03:00
|
|
|
|
<li v-for="error in failedAttachments" :key="error[0]" flex="~ col sm:row" gap-y-1 sm:gap-x-2>
|
2023-01-01 21:11:50 +03:00
|
|
|
|
<strong>{{ error[1] }}:</strong>
|
|
|
|
|
<span>{{ error[0] }}</span>
|
2022-12-26 08:25:35 +03:00
|
|
|
|
</li>
|
|
|
|
|
</ol>
|
|
|
|
|
</div>
|
2022-11-25 16:21:02 +03:00
|
|
|
|
|
|
|
|
|
<div v-if="draft.attachments.length" flex="~ col gap-2" overflow-auto>
|
2022-11-24 14:35:26 +03:00
|
|
|
|
<PublishAttachment
|
|
|
|
|
v-for="(att, idx) in draft.attachments" :key="att.id"
|
|
|
|
|
:attachment="att"
|
2023-01-05 19:48:20 +03:00
|
|
|
|
:dialog-labelled-by="dialogLabelledBy ?? (draft.editingStatus ? 'state-editing' : undefined)"
|
2022-11-24 14:35:26 +03:00
|
|
|
|
@remove="removeAttachment(idx)"
|
2022-12-15 02:30:54 +03:00
|
|
|
|
@set-description="setDescription(att, $event)"
|
2022-11-24 14:35:26 +03:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
2022-12-02 10:02:44 +03:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div flex gap-4>
|
|
|
|
|
<div w-12 h-full sm:block hidden />
|
|
|
|
|
<div
|
2023-01-02 17:09:53 +03:00
|
|
|
|
v-if="shouldExpanded" flex="~ gap-1 1 wrap" m="s--1" pt-2 justify="between" max-w-full
|
2022-12-02 10:02:44 +03:00
|
|
|
|
border="t base"
|
|
|
|
|
>
|
2022-12-27 22:13:50 +03:00
|
|
|
|
<PublishEmojiPicker
|
|
|
|
|
@select="insertEmoji"
|
|
|
|
|
@select-custom="insertCustomEmoji"
|
2023-01-04 13:41:19 +03:00
|
|
|
|
>
|
|
|
|
|
<button btn-action-icon :title="$t('tooltip.emoji')">
|
|
|
|
|
<div i-ri:emotion-line />
|
|
|
|
|
</button>
|
|
|
|
|
</PublishEmojiPicker>
|
2022-12-23 22:15:19 +03:00
|
|
|
|
|
2023-01-04 13:41:19 +03:00
|
|
|
|
<CommonTooltip placement="top" :content="$t('tooltip.add_media')">
|
2022-12-02 10:02:44 +03:00
|
|
|
|
<button btn-action-icon :aria-label="$t('tooltip.add_media')" @click="pickAttachments">
|
|
|
|
|
<div i-ri:image-add-line />
|
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
|
|
|
|
|
|
|
|
|
<template v-if="editor">
|
2023-01-04 13:41:19 +03:00
|
|
|
|
<CommonTooltip placement="top" :content="$t('tooltip.toggle_code_block')">
|
2022-12-02 10:02:44 +03:00
|
|
|
|
<button
|
|
|
|
|
btn-action-icon
|
|
|
|
|
:aria-label="$t('tooltip.toggle_code_block')"
|
2022-12-27 23:42:58 +03:00
|
|
|
|
:class="editor.isActive('codeBlock') ? 'text-primary' : ''"
|
2022-12-02 10:02:44 +03:00
|
|
|
|
@click="editor?.chain().focus().toggleCodeBlock().run()"
|
|
|
|
|
>
|
|
|
|
|
<div i-ri:code-s-slash-line />
|
2022-11-24 18:48:52 +03:00
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
2022-12-02 10:02:44 +03:00
|
|
|
|
</template>
|
2022-11-24 12:15:58 +03:00
|
|
|
|
|
2022-12-02 10:02:44 +03:00
|
|
|
|
<div flex-auto />
|
2022-11-25 16:21:02 +03:00
|
|
|
|
|
2023-01-08 10:49:32 +03:00
|
|
|
|
<div dir="ltr" pointer-events-none pe-1 pt-2 text-sm tabular-nums text-secondary flex gap="0.5" :class="{ 'text-rose-500': characterCount > characterLimit }">
|
2023-01-07 00:38:01 +03:00
|
|
|
|
{{ characterCount ?? 0 }}<span text-secondary-light>/</span><span text-secondary-light>{{ characterLimit }}</span>
|
2022-12-30 09:10:29 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
2023-01-04 13:41:19 +03:00
|
|
|
|
<CommonTooltip placement="top" :content="$t('tooltip.add_content_warning')">
|
2022-12-02 10:02:44 +03:00
|
|
|
|
<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 16:26:33 +03:00
|
|
|
|
|
2023-01-02 00:52:00 +03:00
|
|
|
|
<CommonTooltip placement="top" :content="$t('tooltip.change_language')">
|
2023-01-03 15:17:04 +03:00
|
|
|
|
<CommonDropdown placement="bottom" auto-boundary-max-size>
|
2023-01-02 07:52:14 +03:00
|
|
|
|
<button btn-action-icon :aria-label="$t('tooltip.change_language')" w-12 mr--1>
|
2023-01-02 00:52:00 +03:00
|
|
|
|
<div i-ri:translate-2 />
|
|
|
|
|
<div i-ri:arrow-down-s-line text-sm text-secondary me--1 />
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<template #popper>
|
2023-01-04 13:21:18 +03:00
|
|
|
|
<PublishLanguagePicker v-model="draft.params.language" min-w-80 p3 />
|
2023-01-02 00:52:00 +03:00
|
|
|
|
</template>
|
|
|
|
|
</CommonDropdown>
|
|
|
|
|
</CommonTooltip>
|
|
|
|
|
|
2023-01-04 13:41:19 +03:00
|
|
|
|
<PublishVisibilityPicker v-model="draft.params.visibility" :editing="!!draft.editingStatus">
|
|
|
|
|
<template #default="{ visibility }">
|
2023-01-03 01:11:43 +03:00
|
|
|
|
<button :disabled="!!draft.editingStatus" :aria-label="$t('tooltip.change_content_visibility')" btn-action-icon :class="{ 'w-12': !draft.editingStatus }">
|
2023-01-04 13:41:19 +03:00
|
|
|
|
<div :class="visibility.icon" />
|
2023-01-03 01:11:43 +03:00
|
|
|
|
<div v-if="!draft.editingStatus" i-ri:arrow-down-s-line text-sm text-secondary me--1 />
|
2022-11-25 15:10:45 +03:00
|
|
|
|
</button>
|
2023-01-04 13:41:19 +03:00
|
|
|
|
</template>
|
|
|
|
|
</PublishVisibilityPicker>
|
2022-12-02 10:02:44 +03:00
|
|
|
|
|
2023-01-08 23:55:52 +03:00
|
|
|
|
<CommonTooltip id="publish-tooltip" placement="top" :content="$t('tooltip.add_publishable_content')" :disabled="!isPublishDisabled">
|
|
|
|
|
<button
|
2023-01-10 16:32:50 +03:00
|
|
|
|
btn-solid rounded-3 text-sm w-full flex="~ gap1" items-center
|
2023-01-08 23:55:52 +03:00
|
|
|
|
md:w-fit
|
|
|
|
|
class="publish-button"
|
|
|
|
|
:aria-disabled="isPublishDisabled"
|
|
|
|
|
aria-describedby="publish-tooltip"
|
|
|
|
|
@click="publish"
|
|
|
|
|
>
|
2023-01-10 16:32:50 +03:00
|
|
|
|
<div v-if="isSending" i-ri:loader-2-fill animate-spin />
|
2023-01-09 17:31:30 +03:00
|
|
|
|
<span v-if="draft.editingStatus">{{ $t('action.save_changes') }}</span>
|
|
|
|
|
<span v-else-if="draft.params.inReplyToId">{{ $t('action.reply') }}</span>
|
2023-01-10 16:32:50 +03:00
|
|
|
|
<span v-else>{{ !isSending ? $t('action.publish') : $t('state.publishing') }}</span>
|
2023-01-08 23:55:52 +03:00
|
|
|
|
</button>
|
|
|
|
|
</CommonTooltip>
|
2022-11-24 10:53:27 +03:00
|
|
|
|
</div>
|
2022-11-21 09:55:31 +03:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
2023-01-08 23:55:52 +03:00
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.publish-button[aria-disabled=true] {
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
background-color: var(--c-bg-btn-disabled);
|
|
|
|
|
color: var(--c-text-btn-disabled);
|
|
|
|
|
}
|
|
|
|
|
.publish-button[aria-disabled=true]:hover {
|
|
|
|
|
background-color: var(--c-bg-btn-disabled);
|
|
|
|
|
color: var(--c-text-btn-disabled);
|
|
|
|
|
}
|
|
|
|
|
</style>
|