라이브 미리보기 포커스 실행 분리

This commit is contained in:
2026-07-28 18:56:54 +09:00
parent 00d58fe569
commit d709b5d94c
10 changed files with 296 additions and 78 deletions

View File

@@ -78,6 +78,7 @@ import { useAdminMarkdownSlashMenu } from '../../composables/useAdminMarkdownSla
import { useAdminMarkdownHistory } from '../../composables/useAdminMarkdownHistory.js'
import { useAdminMarkdownMediaPicker } from '../../composables/useAdminMarkdownMediaPicker.js'
import { useAdminMarkdownFocusBridge } from '../../composables/useAdminMarkdownFocusBridge.js'
import { useAdminMarkdownPreviewFocus } from '../../composables/useAdminMarkdownPreviewFocus.js'
import { useAdminMarkdownTextareaFocus } from '../../composables/useAdminMarkdownTextareaFocus.js'
import { useAdminMarkdownBlockPanel } from '../../composables/useAdminMarkdownBlockPanel.js'
import {
@@ -209,35 +210,16 @@ let liveSlashTextareaScrollElement = null
*/
const getWriteTextareaElement = () => writeSurfaceRef.value?.getTextareaElement?.() ?? null
/**
* 미리보기 표면 DOM을 반환한다.
* @returns {HTMLElement|null} 미리보기 DOM
*/
const getPreviewSurfaceElement = () => previewSurfaceRef.value?.getPreviewElement?.() ?? null
/**
* 미리보기 표면 안의 지정 줄 DOM을 반환한다.
* @param {number|string|null} sourceLine - 원본 줄 번호
* @returns {Element|null} 줄 DOM
*/
const getPreviewSourceLineElement = (sourceLine) => previewSurfaceRef.value?.getSourceLineElement?.(sourceLine) ?? null
/**
* 라이브 편집 줄에 포커스를 이동한다.
* @param {...unknown} args - 포커스 인자
* @returns {void}
*/
const focusPreviewEditableAtLine = (...args) => {
previewSurfaceRef.value?.focusEditableAtLine?.(...args)
}
/**
* 라이브 문서 전체를 선택한다.
* @returns {void}
*/
const selectAllPreviewDocument = () => {
previewSurfaceRef.value?.selectAllLiveDocument?.()
}
const {
focusPreviewEditableAtLine,
getCurrentPreviewSourceLine,
getPreviewSourceLineElement,
getPreviewSurfaceElement,
scrollPreviewToTop,
selectAllPreviewDocument
} = useAdminMarkdownPreviewFocus({
previewSurfaceRef
})
const markdownValue = computed({
get: () => normalizeMarkdownContent(props.modelValue),
@@ -603,50 +585,6 @@ watch(markdownValue, (nextValue, previousValue) => {
recordEditorHistory(previousValue)
})
/**
* 라이브 미리보기 스크롤을 맨 위로 맞춘다.
* @returns {void}
*/
const scrollPreviewToTop = () => {
if (getPreviewSurfaceElement()) {
getPreviewSurfaceElement().scrollTop = 0
}
}
/**
* 라이브 미리보기에서 현재 포커스 또는 화면 상단에 가까운 원본 줄을 찾는다.
* @returns {number|null} 원본 줄 번호
*/
const getCurrentPreviewSourceLine = () => {
if (!import.meta.client || !getPreviewSurfaceElement()) {
return null
}
const activeElement = document.activeElement
const focusedLineElement = getPreviewSurfaceElement().contains(activeElement)
? activeElement?.closest?.('[data-source-line]')
: null
const focusedLine = Number(focusedLineElement?.getAttribute?.('data-source-line'))
if (Number.isFinite(focusedLine)) {
return focusedLine
}
const previewRect = getPreviewSurfaceElement().getBoundingClientRect()
const anchorTop = Math.max(previewRect.top, 0) + 24
const candidates = [...getPreviewSurfaceElement().querySelectorAll('[data-source-line]')]
.map((element) => {
const rect = element.getBoundingClientRect()
const line = Number(element.getAttribute('data-source-line'))
return { rect, line }
})
.filter(({ rect, line }) => Number.isFinite(line) && rect.height > 0 && rect.bottom >= anchorTop)
.sort((a, b) => Math.abs(a.rect.top - anchorTop) - Math.abs(b.rect.top - anchorTop))
return candidates[0]?.line ?? null
}
const {
rememberWritePositionForPreview,
rememberPreviewPosition,