From 9fca8eee3073719a6c3870272bc812d4a8dab530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?= Date: Mon, 28 Nov 2022 18:23:33 +0800 Subject: [PATCH] feat: open reply dialog on timeline closes #125 --- components/publish/PublishWidget.vue | 29 +++++--------- components/status/StatusActions.vue | 26 ++++++------ components/status/StatusDetails.vue | 2 +- composables/dialog.ts | 7 +++- composables/statusDrafts.ts | 60 +++++++++++++++++++++------- pages/@[account]/[status].vue | 14 ++++--- 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index 5b3b1d64..92206864 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -3,32 +3,33 @@ import type { CreateStatusParams, StatusVisibility } from 'masto' import { fileOpen } from 'browser-fs-access' import { useDropZone } from '@vueuse/core' import { EditorContent } from '@tiptap/vue-3' +import type { Draft } from '~/composables/statusDrafts' const { draftKey, - placeholder = 'What is on your mind?', - inReplyToId, - inReplyToVisibility = 'public', + initial = getDefaultDraft() as never /* Bug of vue-core */, expanded: _expanded = false, } = defineProps<{ draftKey: string + initial?: () => Draft placeholder?: string inReplyToId?: string inReplyToVisibility?: StatusVisibility expanded?: boolean }>() +// eslint-disable-next-line prefer-const +let { draft, isEmpty } = $(useDraft(draftKey, initial)) + let isSending = $ref(false) -let { draft } = $(useDraft(draftKey, inReplyToId, inReplyToVisibility)) -const isExistDraft = $computed(() => !!draft.params.status && draft.params.status !== '

') -let isExpanded = $ref(isExistDraft || _expanded) +let isExpanded = $ref(!isEmpty || _expanded) const { editor } = useTiptap({ content: computed({ get: () => draft.params.status, set: newVal => draft.params.status = newVal, }), - placeholder, + placeholder: draft.placeholder, autofocus: isExpanded, onSubmit: publish, onFocus() { isExpanded = true }, @@ -108,6 +109,7 @@ async function publish() { 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 @@ -121,7 +123,7 @@ async function publish() { else await useMasto().statuses.update(draft.editingStatus.id, payload) - draft = getDefaultDraft({ inReplyToId, visibility: inReplyToVisibility }) + draft = initial() isPublishDialogOpen.value = false } finally { @@ -137,15 +139,6 @@ async function onDrop(files: File[] | null) { } const { isOverDropZone } = useDropZone(dropZoneRef, onDrop) - -onUnmounted(() => { - // Remove draft if it's empty - if (!draft.attachments.length && !draft.params.status) { - nextTick(() => { - delete currentUserDrafts.value[draftKey] - }) - } -}) diff --git a/composables/dialog.ts b/composables/dialog.ts index d8ebe235..c3562f54 100644 --- a/composables/dialog.ts +++ b/composables/dialog.ts @@ -19,9 +19,12 @@ export function openSigninDialog() { isSigninDialogOpen.value = true } -export function openPublishDialog(draftKey = 'dialog', draft?: Draft) { +export function openPublishDialog(draftKey = 'dialog', draft?: Draft, overwrite = false): void { dialogDraftKey.value = draftKey - if (draft) + if (overwrite) { + // TODO overwrite warning + } + if (draft && (overwrite || !currentUserDrafts.value[draftKey])) currentUserDrafts.value[draftKey] = draft isPublishDialogOpen.value = true } diff --git a/composables/statusDrafts.ts b/composables/statusDrafts.ts index e241a07c..da4c2348 100644 --- a/composables/statusDrafts.ts +++ b/composables/statusDrafts.ts @@ -1,4 +1,4 @@ -import type { Account, Attachment, CreateStatusParams, Status, StatusVisibility } from 'masto' +import type { Account, Attachment, CreateStatusParams, Status } from 'masto' import { STORAGE_KEY_DRAFTS } from '~/constants' import type { Mutable } from '~/types/utils' @@ -8,6 +8,7 @@ export interface Draft { status?: Exclude } attachments: Attachment[] + placeholder: string } export type DraftMap = Record @@ -26,30 +27,56 @@ export function getDefaultDraft({ status = '', inReplyToId, visibility = 'public', -}: Partial = {}): Draft { + placeholder = 'What is on your mind?', + attachments = [], +}: Partial> = {}): Draft { return { params: { status, inReplyToId, visibility, }, - attachments: [], + attachments, + placeholder, } } -export function getParamsFromStatus(status: Status): Draft['params'] { - return { - status: status.content, +export function getDraftFromStatus(status: Status, text?: null | string): Draft { + return getDefaultDraft({ + status: text || status.content, mediaIds: status.mediaAttachments.map(att => att.id), visibility: status.visibility, + attachments: status.mediaAttachments, + }) +} + +export function getReplyDraft(status: Status) { + return { + key: `reply-${status.id}`, + draft: () => getDefaultDraft({ + inReplyToId: status!.id, + placeholder: `Reply to ${status?.account ? getDisplayName(status.account) : 'this thread'}`, + visibility: status.visibility, + }), } } -export function useDraft(draftKey: string, inReplyToId?: string, inReplyToVisibility?: StatusVisibility) { +export const isEmptyDraft = (draft: Draft) => { + const { params, attachments } = draft + const status = params.status || '' + return (status.length === 0 || status === '

') + && attachments.length === 0 + && (params.spoilerText || '').length === 0 +} + +export function useDraft( + draftKey: string, + initial: () => Draft = () => getDefaultDraft(), +) { const draft = computed({ get() { if (!currentUserDrafts.value[draftKey]) - currentUserDrafts.value[draftKey] = getDefaultDraft({ inReplyToId, visibility: inReplyToVisibility }) + currentUserDrafts.value[draftKey] = initial() return currentUserDrafts.value[draftKey] }, set(val) { @@ -57,27 +84,30 @@ export function useDraft(draftKey: string, inReplyToId?: string, inReplyToVisibi }, }) - const isEmpty = computed(() => { - return (draft.value.params.status ?? '').trim().length === 0 - && draft.value.attachments.length === 0 + const isEmpty = computed(() => isEmptyDraft(draft.value)) + + onUnmounted(async () => { + // Remove draft if it's empty + if (isEmpty.value) { + await nextTick() + delete currentUserDrafts.value[draftKey] + } }) return { draft, isEmpty } } -export const dialogDraft = useDraft('dialog') - export function mentionUser(account: Account) { openPublishDialog('dialog', getDefaultDraft({ status: `@${account.acct} `, - })) + }), true) } export function directMessageUser(account: Account) { openPublishDialog('dialog', getDefaultDraft({ status: `@${account.acct} `, visibility: 'direct', - })) + }), true) } export function clearUserDrafts(account?: Account) { diff --git a/pages/@[account]/[status].vue b/pages/@[account]/[status].vue index 74f842fb..344efcc6 100644 --- a/pages/@[account]/[status].vue +++ b/pages/@[account]/[status].vue @@ -1,4 +1,5 @@