글쓰기 에디터 문단 처리와 설정 패널 액션 보정

This commit is contained in:
2026-05-07 15:02:41 +09:00
parent 398877fd92
commit 5bda4d5472
9 changed files with 199 additions and 57 deletions

View File

@@ -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({
</script>
<template>
<div class="admin-block-editor min-h-[32rem] bg-transparent py-4 text-ink">
<div class="admin-block-editor__surface post-prose grid gap-1">
<div class="admin-block-editor bg-transparent py-4 text-ink">
<div class="admin-block-editor__surface post-prose">
<div
v-for="(block, index) in editorBlocks"
:key="block.id"
class="admin-block-editor__row group/block relative rounded transition-colors"
:class="{
'admin-block-editor__row--selected bg-[#eff1f2] ring-1 ring-[#d8dde1]': selectedBlockId === block.id,
'admin-block-editor__row--dragging opacity-50': draggingBlockId === block.id
'admin-block-editor__row--dragging opacity-50': draggingBlockId === block.id,
'admin-block-editor__row--text': isTextBlock(block),
'admin-block-editor__row--structure': !isTextBlock(block)
}"
:data-editor-block-id="block.id"
@dragover.prevent
@@ -1226,11 +1279,11 @@ defineExpose({
<span aria-hidden="true"></span>
</button>
<hr v-if="block.type === 'divider'" class="admin-block-editor__divider my-6 border-line">
<hr v-if="block.type === 'divider'" class="admin-block-editor__divider border-line">
<figure
v-else-if="block.type === 'image'"
class="admin-block-editor__media group my-6"
class="admin-block-editor__media group"
:class="getImageWidthClass(block.width)"
tabindex="0"
@focus="activateBlock(block)"
@@ -1281,7 +1334,7 @@ defineExpose({
<figure
v-else-if="block.type === 'gallery'"
class="admin-block-editor__gallery group my-6"
class="admin-block-editor__gallery group"
tabindex="0"
@focus="activateBlock(block)"
@click="activateBlock(block)"
@@ -1317,7 +1370,7 @@ defineExpose({
<section
v-else-if="block.type === 'toggle'"
class="admin-block-editor__toggle my-6 rounded border border-line bg-paper p-5"
class="admin-block-editor__toggle rounded border border-line bg-paper p-5"
@focusin="activateBlock(block)"
@click="activateBlock(block)"
@keydown.enter="handleEnter($event, index)"
@@ -1341,7 +1394,7 @@ defineExpose({
<section
v-else-if="block.type === 'embed'"
class="admin-block-editor__embed my-6 rounded border border-dashed border-line bg-surface p-5"
class="admin-block-editor__embed rounded border border-dashed border-line bg-surface p-5"
@focusin="activateBlock(block)"
@click="activateBlock(block)"
@keydown.enter="handleEnter($event, index)"
@@ -1460,6 +1513,18 @@ defineExpose({
color: #1f2328;
}
.admin-block-editor__row + .admin-block-editor__row--text {
margin-top: 32px;
}
.admin-block-editor__row + .admin-block-editor__row--structure {
margin-top: 32px;
}
.admin-block-editor__row--structure + .admin-block-editor__row--structure {
margin-top: 32px;
}
.admin-block-editor__code {
background: #15171a;
color: #f8fafc;