From 5bda4d5472cb796f771dda3f6c19f37bb5865a83 Mon Sep 17 00:00:00 2001 From: zenn Date: Thu, 7 May 2026 15:02:41 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=80=EC=93=B0=EA=B8=B0=20=EC=97=90?= =?UTF-8?q?=EB=94=94=ED=84=B0=20=EB=AC=B8=EB=8B=A8=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EC=99=80=20=EC=84=A4=EC=A0=95=20=ED=8C=A8=EB=84=90=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=20=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminBlockEditor.vue | 101 +++++++++++++++++++++----- components/admin/AdminPostForm.vue | 82 +++++++++++++++++---- docs/history.md | 10 +++ docs/map.md | 4 +- docs/spec.md | 8 +- docs/update.md | 11 +++ package-lock.json | 4 +- package.json | 2 +- pages/admin/posts/[id].vue | 34 ++++----- 9 files changed, 199 insertions(+), 57 deletions(-) diff --git a/components/admin/AdminBlockEditor.vue b/components/admin/AdminBlockEditor.vue index fc08249..cdd2fe7 100644 --- a/components/admin/AdminBlockEditor.vue +++ b/components/admin/AdminBlockEditor.vue @@ -508,6 +508,37 @@ const updateSlashMenuDirection = (index) => { }) } +/** + * 텍스트 블록 DOM의 현재 텍스트 반환 + * @param {number} index - 블록 인덱스 + * @returns {string} 현재 텍스트 + */ +const getTextBlockDomText = (index) => { + const element = blockRefs.value[index] + + return element?.innerText.replace(/\n$/, '') || '' +} + +/** + * 텍스트 블록 DOM 값을 상태에 즉시 반영 + * @param {number} index - 블록 인덱스 + * @returns {Object|undefined} 갱신한 블록 + */ +const syncTextBlockFromDom = (index) => { + const block = editorBlocks.value[index] + + if (!block || !isTextBlock(block)) { + return block + } + + block.text = getTextBlockDomText(index) + activeBlockId.value = block.id + updateSlashQuery(block) + updateSlashMenuDirection(index) + + return block +} + /** * 블록 타입에 맞는 태그명 반환 * @param {Object} block - 에디터 블록 @@ -537,15 +568,15 @@ const getBlockTag = (block) => { const getBlockClass = (block) => [ 'admin-block-editor__block outline-none transition-colors', { - 'admin-block-editor__paragraph min-h-8 text-[17px] leading-8': block.type === 'paragraph', - 'admin-block-editor__heading mt-8 min-h-10 font-semibold leading-tight': block.type === 'heading', + 'admin-block-editor__paragraph min-h-8 whitespace-pre-wrap text-[17px] leading-8': block.type === 'paragraph', + 'admin-block-editor__heading min-h-10 font-semibold leading-tight': block.type === 'heading', 'admin-block-editor__heading--h1 text-5xl': block.type === 'heading' && block.level === 1, 'admin-block-editor__heading--h2 text-4xl': block.type === 'heading' && block.level === 2, 'admin-block-editor__heading--h3 text-3xl': block.type === 'heading' && block.level === 3, - 'admin-block-editor__quote my-5 border-l-4 border-ink bg-surface px-5 py-3 text-xl font-medium leading-8': block.type === 'quote', - 'admin-block-editor__callout my-5 min-h-14 rounded border border-line bg-surface px-5 py-4 text-[16px] leading-7': block.type === 'callout', + 'admin-block-editor__quote border-l-4 border-ink bg-surface px-5 py-3 text-xl font-medium leading-8': block.type === 'quote', + 'admin-block-editor__callout min-h-14 rounded border border-line bg-surface px-5 py-4 text-[16px] leading-7': block.type === 'callout', 'admin-block-editor__list relative min-h-8 pl-7 text-[17px] leading-8 before:absolute before:left-2 before:top-3 before:h-2 before:w-2 before:rounded-full before:bg-current': block.type === 'list', - 'admin-block-editor__code my-5 min-h-14 whitespace-pre-wrap rounded bg-[#15171a] px-4 py-3 font-mono text-sm leading-6 text-white': block.type === 'code' + 'admin-block-editor__code min-h-14 whitespace-pre-wrap rounded bg-[#15171a] px-4 py-3 font-mono text-sm leading-6 text-white': block.type === 'code' } ] @@ -574,7 +605,7 @@ const getImageWidthClass = (width) => { */ const updateBlockText = (event, index) => { const block = editorBlocks.value[index] - const text = event.target.innerText.replace(/\n$/, '') + const text = getTextBlockDomText(index) block.text = text activeBlockId.value = block.id @@ -608,7 +639,14 @@ const finishTextComposition = (event, index) => { nextTick(() => { window.setTimeout(() => { - updateBlockText(event, index) + const block = syncTextBlockFromDom(index) + + if (!block) { + return + } + + applyMarkdownShortcut(block, index) + emitContent() }, 0) }) } @@ -938,6 +976,8 @@ const removeGalleryImage = (block, imageIndex) => { * @returns {void} */ const highlightNextCommand = (event) => { + syncTextBlockFromDom(activeBlockIndex.value) + if (!visibleCommands.value.length) { return } @@ -952,6 +992,8 @@ const highlightNextCommand = (event) => { * @returns {void} */ const highlightPreviousCommand = (event) => { + syncTextBlockFromDom(activeBlockIndex.value) + if (!visibleCommands.value.length) { return } @@ -969,16 +1011,25 @@ const highlightPreviousCommand = (event) => { * @returns {void} */ const handleEnter = (event, index) => { - const currentBlock = editorBlocks.value[index] + const currentBlock = syncTextBlockFromDom(index) if (isComposingText.value || event.isComposing || event.keyCode === 229) { event.preventDefault() return } - if (visibleCommands.value.length && currentBlock.text.startsWith('/')) { + if (event.shiftKey && isTextBlock(currentBlock)) { + return + } + + if (currentBlock.text.startsWith('/')) { event.preventDefault() - applyCommand(highlightedCommand.value || visibleCommands.value[0]) + const command = highlightedCommand.value || visibleCommands.value[0] + + if (command) { + applyCommand(command) + } + return } @@ -1198,15 +1249,17 @@ defineExpose({