From 3afef9d0d25cb0c64fa638c0bd2a9123d9081e41 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 1 May 2026 23:04:50 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=EC=97=90=EB=94=94=ED=84=B0=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=ED=9D=90=EB=A6=84=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 | 50 ++++++++++++++++++++++++--- docs/history.md | 8 +++++ docs/spec.md | 5 ++- docs/todo.md | 6 +++- docs/update.md | 8 +++++ package-lock.json | 4 +-- package.json | 2 +- 7 files changed, 73 insertions(+), 10 deletions(-) diff --git a/components/admin/AdminBlockEditor.vue b/components/admin/AdminBlockEditor.vue index 373f8e7..49ea75b 100644 --- a/components/admin/AdminBlockEditor.vue +++ b/components/admin/AdminBlockEditor.vue @@ -13,6 +13,7 @@ const blockRefs = ref([]) const activeBlockId = ref('') const slashQuery = ref('') const slashMenuDirection = ref('down') +const highlightedCommandIndex = ref(0) const isApplyingExternalValue = ref(false) let blockIdSeed = 0 @@ -368,6 +369,8 @@ const updateSlashQuery = (block) => { slashQuery.value = block.text.startsWith('/') ? block.text.slice(1).trim().toLowerCase() : '' + + highlightedCommandIndex.value = 0 } const activeBlockIndex = computed(() => editorBlocks.value.findIndex((block) => block.id === activeBlockId.value)) @@ -390,6 +393,8 @@ const visibleCommands = computed(() => { ].some((keyword) => keyword.toLowerCase().includes(slashQuery.value))) }) +const highlightedCommand = computed(() => visibleCommands.value[highlightedCommandIndex.value]) + /** * 슬래시 메뉴 명령 적용 * @param {Object} command - 블록 명령 @@ -425,6 +430,36 @@ const applyCommand = (command) => { focusBlock(index) } +/** + * 슬래시 메뉴 선택을 아래로 이동 + * @param {KeyboardEvent} event - 키보드 이벤트 + * @returns {void} + */ +const highlightNextCommand = (event) => { + if (!visibleCommands.value.length) { + return + } + + event.preventDefault() + highlightedCommandIndex.value = (highlightedCommandIndex.value + 1) % visibleCommands.value.length +} + +/** + * 슬래시 메뉴 선택을 위로 이동 + * @param {KeyboardEvent} event - 키보드 이벤트 + * @returns {void} + */ +const highlightPreviousCommand = (event) => { + if (!visibleCommands.value.length) { + return + } + + event.preventDefault() + highlightedCommandIndex.value = highlightedCommandIndex.value === 0 + ? visibleCommands.value.length - 1 + : highlightedCommandIndex.value - 1 +} + /** * 엔터 키로 다음 블록 생성 * @param {KeyboardEvent} event - 키보드 이벤트 @@ -434,16 +469,18 @@ const applyCommand = (command) => { const handleEnter = (event, index) => { const currentBlock = editorBlocks.value[index] + if (visibleCommands.value.length && currentBlock.text.startsWith('/')) { + event.preventDefault() + applyCommand(highlightedCommand.value || visibleCommands.value[0]) + return + } + if (currentBlock.type === 'code' && !event.shiftKey) { return } event.preventDefault() - if (!currentBlock.text.trim() && currentBlock.type === 'paragraph') { - return - } - if (currentBlock.type === 'divider') { editorBlocks.value.splice(index + 1, 0, createEditorBlock()) emitContent() @@ -550,6 +587,8 @@ watch(editorBlocks, () => { @focus="activateBlock(block)" @input="updateBlockText($event, index)" @keydown.enter="handleEnter($event, index)" + @keydown.down="highlightNextCommand" + @keydown.up="highlightPreviousCommand" @keydown.backspace="handleBackspace($event, index)" /> @@ -569,9 +608,10 @@ watch(editorBlocks, () => { :class="slashMenuDirection === 'up' ? 'bottom-full mb-2' : 'top-full mt-2'" >