@@ -239,7 +142,7 @@ const isPublishDisabled = computed(() => {
{{ $t('state.uploading') }}
{
flex rounded-4 p1
hover:bg-active cursor-pointer transition-100
:aria-label="$t('action.clear_upload_failed')"
- @click="failed = []"
+ @click="failedAttachments = []"
>
@@ -268,7 +171,7 @@ const isPublishDisabled = computed(() => {
{{ $t('state.attachments_exceed_server_limit') }}
- -
+
-
{{ error[1] }}:
{{ error[0] }}
diff --git a/composables/masto/publish.ts b/composables/masto/publish.ts
new file mode 100644
index 000000000..3bca26f63
--- /dev/null
+++ b/composables/masto/publish.ts
@@ -0,0 +1,153 @@
+import { fileOpen } from 'browser-fs-access'
+import type { Ref } from 'vue'
+import type { mastodon } from 'masto'
+import type { UseDraft } from './statusDrafts'
+import type { Draft } from '~~/types'
+
+export const usePublish = (options: {
+ draftState: UseDraft
+ expanded: Ref
+ isUploading: Ref
+ initialDraft: Ref<() => Draft>
+}) => {
+ const { expanded, isUploading, initialDraft } = $(options)
+ let { draft, isEmpty } = $(options.draftState)
+ const masto = useMasto()
+
+ let isSending = $ref(false)
+ const isExpanded = $ref(false)
+
+ const shouldExpanded = $computed(() => expanded || isExpanded || !isEmpty)
+ const isPublishDisabled = $computed(() => {
+ return isEmpty || isUploading || isSending || (draft.attachments.length === 0 && !draft.params.status)
+ })
+
+ async function publishDraft() {
+ const payload = {
+ ...draft.params,
+ status: htmlToText(draft.params.status || ''),
+ mediaIds: draft.attachments.map(a => a.id),
+ ...(isGlitchEdition.value ? { 'content-type': 'text/markdown' } : {}),
+ } as mastodon.v1.CreateStatusParams
+
+ if (process.dev) {
+ // eslint-disable-next-line no-console
+ console.info({
+ raw: draft.params.status,
+ ...payload,
+ })
+ // eslint-disable-next-line no-alert
+ const result = confirm('[DEV] Payload logged to console, do you want to publish it?')
+ if (!result)
+ return
+ }
+
+ try {
+ isSending = true
+
+ let status: mastodon.v1.Status
+ if (!draft.editingStatus)
+ status = await masto.v1.statuses.create(payload)
+ else
+ status = await masto.v1.statuses.update(draft.editingStatus.id, payload)
+ if (draft.params.inReplyToId)
+ navigateToStatus({ status })
+
+ draft = initialDraft()
+
+ return status
+ }
+ finally {
+ isSending = false
+ }
+ }
+
+ return $$({
+ isSending,
+ isExpanded,
+ shouldExpanded,
+ isPublishDisabled,
+
+ publishDraft,
+ })
+}
+
+export type MediaAttachmentUploadError = [filename: string, message: string]
+
+export const useUploadMediaAttachment = (draftRef: Ref) => {
+ const draft = $(draftRef)
+ const masto = useMasto()
+ const { t } = useI18n()
+
+ let isUploading = $ref(false)
+ let isExceedingAttachmentLimit = $ref(false)
+ let failedAttachments = $ref([])
+ const dropZoneRef = ref()
+
+ async function uploadAttachments(files: File[]) {
+ isUploading = true
+ failedAttachments = []
+ // TODO: display some kind of message if too many media are selected
+ // DONE
+ const limit = currentInstance.value!.configuration.statuses.maxMediaAttachments || 4
+ for (const file of files.slice(0, limit)) {
+ if (draft.attachments.length < limit) {
+ isExceedingAttachmentLimit = false
+ try {
+ const attachment = await masto.v1.mediaAttachments.create({
+ file,
+ })
+ draft.attachments.push(attachment)
+ }
+ catch (e) {
+ // TODO: add some human-readable error message, problem is that masto api will not return response code
+ console.error(e)
+ failedAttachments = [...failedAttachments, [file.name, (e as Error).message]]
+ }
+ }
+ else {
+ isExceedingAttachmentLimit = true
+ failedAttachments = [...failedAttachments, [file.name, t('state.attachments_limit_error')]]
+ }
+ }
+ isUploading = false
+ }
+
+ async function pickAttachments() {
+ const mimeTypes = currentInstance.value!.configuration.mediaAttachments.supportedMimeTypes
+ const files = await fileOpen({
+ description: 'Attachments',
+ multiple: true,
+ mimeTypes,
+ })
+ await uploadAttachments(files)
+ }
+
+ async function setDescription(att: mastodon.v1.MediaAttachment, description: string) {
+ att.description = description
+ await masto.v1.mediaAttachments.update(att.id, { description: att.description })
+ }
+
+ function removeAttachment(index: number) {
+ draft.attachments.splice(index, 1)
+ }
+
+ async function onDrop(files: File[] | null) {
+ if (files)
+ await uploadAttachments(files)
+ }
+
+ const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
+
+ return $$({
+ isUploading,
+ isExceedingAttachmentLimit,
+ failedAttachments,
+ isOverDropZone,
+
+ uploadAttachments,
+ pickAttachments,
+ setDescription,
+ removeAttachment,
+ })
+}
diff --git a/composables/masto/routes.ts b/composables/masto/routes.ts
index 1043bb34a..e0c7a868f 100644
--- a/composables/masto/routes.ts
+++ b/composables/masto/routes.ts
@@ -63,3 +63,12 @@ export function getStatusInReplyToRoute(status: mastodon.v1.Status) {
},
})
}
+
+export const navigateToStatus = ({ status, focusReply = false }: {
+ status: mastodon.v1.Status
+ focusReply?: boolean
+}) =>
+ navigateTo({
+ path: getStatusRoute(status).href,
+ state: { focusReply },
+ })
diff --git a/composables/masto/statusDrafts.ts b/composables/masto/statusDrafts.ts
index f99ef693a..e9fac86d2 100644
--- a/composables/masto/statusDrafts.ts
+++ b/composables/masto/statusDrafts.ts
@@ -1,4 +1,5 @@
import type { mastodon } from 'masto'
+import type { ComputedRef, Ref } from 'vue'
import { STORAGE_KEY_DRAFTS } from '~/constants'
import type { Draft, DraftMap } from '~/types'
import type { Mutable } from '~/types/utils'
@@ -89,10 +90,15 @@ export const isEmptyDraft = (draft: Draft | null | undefined) => {
&& (params.spoilerText || '').length === 0
}
+export interface UseDraft {
+ draft: Ref
+ isEmpty: ComputedRef
+}
+
export function useDraft(
draftKey?: string,
initial: () => Draft = () => getDefaultDraft({}),
-) {
+): UseDraft {
const draft = draftKey
? computed({
get() {
diff --git a/composables/status.ts b/composables/status.ts
deleted file mode 100644
index 44211e032..000000000
--- a/composables/status.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import type { mastodon } from 'masto'
-
-export const navigateToStatus = ({ status, focusReply = false }: { status: mastodon.v1.Status; focusReply?: boolean }) => navigateTo({ path: getStatusRoute(status).href, state: { focusReply } })