From 6d0cbd291442e6d75eb5df31ec2d201ad28f1b0f Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 9 Mar 2023 12:06:31 +0100 Subject: [PATCH] feat: markdown editor, close #1845 --- components/publish/PublishWidget.vue | 95 ++++++++++++++++++++++++++-- composables/settings/definition.ts | 1 + locales/en.json | 2 + 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index 2578912d..49500d30 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -29,6 +29,9 @@ const { t } = useI18n() const draftState = useDraft(draftKey, initial) const { draft } = $(draftState) +const settings = useUserSettings() +const textareaEl = ref() + const { isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone, uploadAttachments, pickAttachments, setDescription, removeAttachment, @@ -48,11 +51,17 @@ const { editor } = useTiptap({ set: (newVal) => { draft.params.status = newVal draft.lastUpdated = Date.now() + if (settings.value.editorMode === 'markdown') + onTipTapChanged() }, }), placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')), autofocus: shouldExpanded, - onSubmit: publish, + onSubmit: () => { + if (settings.value.editorMode === 'markdown') + onMarkdownChanged() + publish() + }, onFocus() { if (!isExpanded && draft.initialText) { editor.value?.chain().insertContent(`${draft.initialText} `).focus('end').run() @@ -113,10 +122,16 @@ async function handlePaste(evt: ClipboardEvent) { } function insertEmoji(name: string) { - editor.value?.chain().focus().insertEmoji(name).run() + if (settings.value.editorMode === 'markdown') + insertMarkdownAtCursor(name) + else + editor.value?.chain().focus().insertEmoji(name).run() } function insertCustomEmoji(image: any) { - editor.value?.chain().focus().insertCustomEmoji(image).run() + if (settings.value.editorMode === 'markdown') + insertMarkdownAtCursor(`:${image['data-emoji-id']}:`) + else + editor.value?.chain().focus().insertCustomEmoji(image).run() } async function toggleSensitive() { @@ -129,6 +144,55 @@ async function publish() { emit('published', status) } +let markdown = $ref(htmlToText(draft.params.status || '')) + +function insertMarkdownAtCursor(text: string) { + if (!textareaEl.value) + return + textareaEl.value.focus() + const start = textareaEl.value.selectionStart || 0 + const end = textareaEl.value.selectionEnd || 0 + const before = markdown.substring(0, start) + const after = markdown.substring(end) + markdown = before + text + after + textareaEl.value.setSelectionRange(end + text.length, end + text.length) +} + +async function onMarkdownChanged() { + draft.params.status = await convertMastodonHTML(markdown) +} + +function onTipTapChanged() { + markdown = htmlToText(draft.params.status || '') +} + +function toggleEditor() { + if (settings.value.editorMode === 'markdown') { + onMarkdownChanged() + settings.value.editorMode = 'tiptap' + } + else { + onTipTapChanged() + settings.value.editorMode = 'markdown' + } +} + +watch(markdown, () => { + if (settings.value.editorMode === 'markdown') + onMarkdownChanged() +}) + +useTextareaAutosize({ + input: markdown, + element: textareaEl, +}) + +const editorClass = $computed(() => + shouldExpanded + ? 'min-h-30 md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain' + : '', +) + useWebShareTarget(async ({ data: { data, action } }: any) => { if (action !== 'compose-with-shared-data') return @@ -218,10 +282,24 @@ defineExpose({
+