125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
/**
|
|
* 관리자 Markdown 라이브 미리보기 포커스 실행 함수를 만든다.
|
|
* @param {Object} options - 미리보기 포커스 옵션
|
|
* @param {import('vue').Ref<Object|null>} options.previewSurfaceRef - 미리보기 표면 컴포넌트 ref
|
|
* @param {boolean} [options.isClient] - 클라이언트 실행 여부 테스트 값
|
|
* @param {() => Element|null} [options.getActiveElement] - 현재 포커스 요소 반환 함수
|
|
* @returns {Object} 미리보기 포커스 실행 함수
|
|
*/
|
|
export const useAdminMarkdownPreviewFocus = (options) => {
|
|
/**
|
|
* 클라이언트 런타임 여부를 반환한다.
|
|
* @returns {boolean} 클라이언트 여부
|
|
*/
|
|
const isClientRuntime = () => options.isClient ?? import.meta.client
|
|
|
|
/**
|
|
* 미리보기 표면 컴포넌트 인스턴스를 반환한다.
|
|
* @returns {Object|null} 미리보기 표면 컴포넌트
|
|
*/
|
|
const getPreviewSurfaceApi = () => options.previewSurfaceRef.value ?? null
|
|
|
|
/**
|
|
* 미리보기 표면 DOM을 반환한다.
|
|
* @returns {HTMLElement|null} 미리보기 DOM
|
|
*/
|
|
const getPreviewSurfaceElement = () => getPreviewSurfaceApi()?.getPreviewElement?.() ?? null
|
|
|
|
/**
|
|
* 미리보기 표면 안의 지정 줄 DOM을 반환한다.
|
|
* @param {number|string|null} sourceLine - 원본 줄 번호
|
|
* @returns {Element|null} 줄 DOM
|
|
*/
|
|
const getPreviewSourceLineElement = (sourceLine) => getPreviewSurfaceApi()?.getSourceLineElement?.(sourceLine) ?? null
|
|
|
|
/**
|
|
* 라이브 편집 줄에 포커스를 이동한다.
|
|
* @param {...unknown} args - 포커스 인자
|
|
* @returns {void}
|
|
*/
|
|
const focusPreviewEditableAtLine = (...args) => {
|
|
getPreviewSurfaceApi()?.focusEditableAtLine?.(...args)
|
|
}
|
|
|
|
/**
|
|
* 라이브 문서 전체를 선택한다.
|
|
* @returns {void}
|
|
*/
|
|
const selectAllPreviewDocument = () => {
|
|
getPreviewSurfaceApi()?.selectAllLiveDocument?.()
|
|
}
|
|
|
|
/**
|
|
* 라이브 미리보기 스크롤을 맨 위로 맞춘다.
|
|
* @returns {void}
|
|
*/
|
|
const scrollPreviewToTop = () => {
|
|
const surface = getPreviewSurfaceElement()
|
|
|
|
if (surface) {
|
|
surface.scrollTop = 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 포커스된 라이브 줄에서 원본 줄 번호를 읽는다.
|
|
* @param {HTMLElement} surface - 미리보기 표면 DOM
|
|
* @returns {number|null} 원본 줄 번호
|
|
*/
|
|
const getFocusedPreviewSourceLine = (surface) => {
|
|
const activeElement = typeof options.getActiveElement === 'function'
|
|
? options.getActiveElement()
|
|
: document.activeElement
|
|
const focusedLineElement = surface.contains?.(activeElement)
|
|
? activeElement?.closest?.('[data-source-line]')
|
|
: null
|
|
const focusedLine = Number(focusedLineElement?.getAttribute?.('data-source-line'))
|
|
|
|
return Number.isFinite(focusedLine) ? focusedLine : null
|
|
}
|
|
|
|
/**
|
|
* 화면 상단에 가까운 라이브 줄에서 원본 줄 번호를 읽는다.
|
|
* @param {HTMLElement} surface - 미리보기 표면 DOM
|
|
* @returns {number|null} 원본 줄 번호
|
|
*/
|
|
const getNearestPreviewSourceLine = (surface) => {
|
|
const previewRect = surface.getBoundingClientRect()
|
|
const anchorTop = Math.max(previewRect.top, 0) + 24
|
|
const candidates = [...surface.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
|
|
}
|
|
|
|
/**
|
|
* 라이브 미리보기에서 현재 포커스 또는 화면 상단에 가까운 원본 줄을 찾는다.
|
|
* @returns {number|null} 원본 줄 번호
|
|
*/
|
|
const getCurrentPreviewSourceLine = () => {
|
|
const surface = getPreviewSurfaceElement()
|
|
|
|
if (!isClientRuntime() || !surface) {
|
|
return null
|
|
}
|
|
|
|
return getFocusedPreviewSourceLine(surface) ?? getNearestPreviewSourceLine(surface)
|
|
}
|
|
|
|
return {
|
|
focusPreviewEditableAtLine,
|
|
getCurrentPreviewSourceLine,
|
|
getPreviewSourceLineElement,
|
|
getPreviewSurfaceElement,
|
|
scrollPreviewToTop,
|
|
selectAllPreviewDocument
|
|
}
|
|
}
|